aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_editor
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_editor')
-rw-r--r--crates/ra_editor/src/folding_ranges.rs177
1 files changed, 111 insertions, 66 deletions
diff --git a/crates/ra_editor/src/folding_ranges.rs b/crates/ra_editor/src/folding_ranges.rs
index 3aabd54ae..a1699d449 100644
--- a/crates/ra_editor/src/folding_ranges.rs
+++ b/crates/ra_editor/src/folding_ranges.rs
@@ -1,8 +1,10 @@
1use rustc_hash::FxHashSet; 1use rustc_hash::FxHashSet;
2 2
3use ra_syntax::{ 3use ra_syntax::{
4 ast,
5 AstNode,
4 File, TextRange, SyntaxNodeRef, 6 File, TextRange, SyntaxNodeRef,
5 SyntaxKind, 7 SyntaxKind::{self, *},
6 Direction, 8 Direction,
7}; 9};
8 10
@@ -20,67 +22,97 @@ pub struct Fold {
20 22
21pub fn folding_ranges(file: &File) -> Vec<Fold> { 23pub fn folding_ranges(file: &File) -> Vec<Fold> {
22 let mut res = vec![]; 24 let mut res = vec![];
23 let mut visited = FxHashSet::default(); 25 let mut visited_comments = FxHashSet::default();
24 26
25 for node in file.syntax().descendants() { 27 for node in file.syntax().descendants() {
26 if visited.contains(&node) { 28 // Fold items that span multiple lines
29 if let Some(kind) = fold_kind(node.kind()) {
30 if has_newline(node) {
31 res.push(Fold { range: node.range(), kind });
32 }
33 }
34
35 // Also fold groups of comments
36 if visited_comments.contains(&node) {
27 continue; 37 continue;
28 } 38 }
39 if node.kind() == COMMENT {
40 contiguous_range_for_comment(node, &mut visited_comments)
41 .map(|range| res.push(Fold { range, kind: FoldKind::Comment }));
42 }
43 }
44
45 res
46}
29 47
30 let range_and_kind = match node.kind() { 48fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> {
31 SyntaxKind::COMMENT => ( 49 match kind {
32 contiguous_range_for(SyntaxKind::COMMENT, node, &mut visited), 50 COMMENT => Some(FoldKind::Comment),
33 Some(FoldKind::Comment), 51 USE_ITEM => Some(FoldKind::Imports),
34 ), 52 _ => None
35 SyntaxKind::USE_ITEM => ( 53 }
36 contiguous_range_for(SyntaxKind::USE_ITEM, node, &mut visited), 54}
37 Some(FoldKind::Imports), 55
38 ), 56fn has_newline(
39 _ => (None, None), 57 node: SyntaxNodeRef,
40 }; 58) -> bool {
41 59 for descendant in node.descendants() {
42 match range_and_kind { 60 if let Some(ws) = ast::Whitespace::cast(descendant) {
43 (Some(range), Some(kind)) => { 61 if ws.has_newlines() {
44 res.push(Fold { 62 return true;
45 range: range, 63 }
46 kind: kind 64 } else if let Some(comment) = ast::Comment::cast(descendant) {
47 }); 65 if comment.has_newlines() {
66 return true;
48 } 67 }
49 _ => {}
50 } 68 }
51 } 69 }
52 70
53 res 71 false
54} 72}
55 73
56fn contiguous_range_for<'a>( 74fn contiguous_range_for_comment<'a>(
57 kind: SyntaxKind, 75 first: SyntaxNodeRef<'a>,
58 node: SyntaxNodeRef<'a>,
59 visited: &mut FxHashSet<SyntaxNodeRef<'a>>, 76 visited: &mut FxHashSet<SyntaxNodeRef<'a>>,
60) -> Option<TextRange> { 77) -> Option<TextRange> {
61 visited.insert(node); 78 visited.insert(first);
62 79
63 let left = node; 80 // Only fold comments of the same flavor
64 let mut right = node; 81 let group_flavor = ast::Comment::cast(first)?.flavor();
65 for node in node.siblings(Direction::Next) { 82
66 visited.insert(node); 83 let mut last = first;
67 match node.kind() { 84 for node in first.siblings(Direction::Next) {
68 SyntaxKind::WHITESPACE if !node.leaf_text().unwrap().as_str().contains("\n\n") => (), 85 if let Some(ws) = ast::Whitespace::cast(node) {
69 k => { 86 // There is a blank line, which means the group ends here
70 if k == kind { 87 if ws.count_newlines_lazy().take(2).count() == 2 {
71 right = node 88 break;
72 } else { 89 }
73 break; 90
74 } 91 // Ignore whitespace without blank lines
92 continue;
93 }
94
95 match ast::Comment::cast(node) {
96 Some(next_comment) if next_comment.flavor() == group_flavor => {
97 visited.insert(node);
98 last = node;
99 }
100 // The comment group ends because either:
101 // * An element of a different kind was reached
102 // * A comment of a different flavor was reached
103 _ => {
104 break
75 } 105 }
76 } 106 }
77 } 107 }
78 if left != right { 108
109 if first != last {
79 Some(TextRange::from_to( 110 Some(TextRange::from_to(
80 left.range().start(), 111 first.range().start(),
81 right.range().end(), 112 last.range().end(),
82 )) 113 ))
83 } else { 114 } else {
115 // The group consists of only one element, therefore it cannot be folded
84 None 116 None
85 } 117 }
86} 118}
@@ -88,52 +120,65 @@ fn contiguous_range_for<'a>(
88#[cfg(test)] 120#[cfg(test)]
89mod tests { 121mod tests {
90 use super::*; 122 use super::*;
123 use test_utils::extract_ranges;
124
125 fn do_check(text: &str, fold_kinds: &[FoldKind]) {
126 let (ranges, text) = extract_ranges(text);
127 let file = File::parse(&text);
128 let folds = folding_ranges(&file);
129
130 assert_eq!(folds.len(), ranges.len());
131 for ((fold, range), fold_kind) in folds.into_iter().zip(ranges.into_iter()).zip(fold_kinds.into_iter()) {
132 assert_eq!(fold.range.start(), range.start());
133 assert_eq!(fold.range.end(), range.end());
134 assert_eq!(&fold.kind, fold_kind);
135 }
136 }
91 137
92 #[test] 138 #[test]
93 fn test_fold_comments() { 139 fn test_fold_comments() {
94 let text = r#" 140 let text = r#"
95// Hello 141<|>// Hello
96// this is a multiline 142// this is a multiline
97// comment 143// comment
98// 144//<|>
99 145
100// But this is not 146// But this is not
101 147
102fn main() { 148fn main() {
103 // We should 149 <|>// We should
104 // also 150 // also
105 // fold 151 // fold
106 // this one. 152 // this one.<|>
153 <|>//! But this one is different
154 //! because it has another flavor<|>
155 <|>/* As does this
156 multiline comment */<|>
107}"#; 157}"#;
108 158
109 let file = File::parse(&text); 159 let fold_kinds = &[
110 let folds = folding_ranges(&file); 160 FoldKind::Comment,
111 assert_eq!(folds.len(), 2); 161 FoldKind::Comment,
112 assert_eq!(folds[0].range.start(), 1.into()); 162 FoldKind::Comment,
113 assert_eq!(folds[0].range.end(), 46.into()); 163 FoldKind::Comment,
114 assert_eq!(folds[0].kind, FoldKind::Comment); 164 ];
115 165 do_check(text, fold_kinds);
116 assert_eq!(folds[1].range.start(), 84.into());
117 assert_eq!(folds[1].range.end(), 137.into());
118 assert_eq!(folds[1].kind, FoldKind::Comment);
119 } 166 }
120 167
121 #[test] 168 #[test]
122 fn test_fold_imports() { 169 fn test_fold_imports() {
123 let text = r#" 170 let text = r#"
124use std::str; 171<|>use std::{
125use std::vec; 172 str,
126use std::io as iop; 173 vec,
174 io as iop
175};<|>
127 176
128fn main() { 177fn main() {
129}"#; 178}"#;
130 179
131 let file = File::parse(&text); 180 let folds = &[FoldKind::Imports];
132 let folds = folding_ranges(&file); 181 do_check(text, folds);
133 assert_eq!(folds.len(), 1);
134 assert_eq!(folds[0].range.start(), 1.into());
135 assert_eq!(folds[0].range.end(), 48.into());
136 assert_eq!(folds[0].kind, FoldKind::Imports);
137 } 182 }
138 183
139 184