diff options
-rw-r--r-- | crates/ra_editor/src/folding_ranges.rs | 107 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/generated.rs | 18 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/mod.rs | 26 | ||||
-rw-r--r-- | crates/ra_syntax/src/grammar.ron | 1 |
4 files changed, 112 insertions, 40 deletions
diff --git a/crates/ra_editor/src/folding_ranges.rs b/crates/ra_editor/src/folding_ranges.rs index 1d89ac853..d45210ec7 100644 --- a/crates/ra_editor/src/folding_ranges.rs +++ b/crates/ra_editor/src/folding_ranges.rs | |||
@@ -22,65 +22,96 @@ pub struct Fold { | |||
22 | 22 | ||
23 | pub fn folding_ranges(file: &File) -> Vec<Fold> { | 23 | pub fn folding_ranges(file: &File) -> Vec<Fold> { |
24 | let mut res = vec![]; | 24 | let mut res = vec![]; |
25 | let mut visited = FxHashSet::default(); | 25 | let mut group_members = FxHashSet::default(); |
26 | 26 | ||
27 | for node in file.syntax().descendants() { | 27 | for node in file.syntax().descendants() { |
28 | if visited.contains(&node) { | 28 | // Fold items that span multiple lines |
29 | continue; | 29 | if let Some(kind) = fold_kind(node.kind()) { |
30 | if has_newline(node) { | ||
31 | res.push(Fold { range: node.range(), kind }); | ||
32 | } | ||
30 | } | 33 | } |
31 | 34 | ||
32 | if let Some(comment) = ast::Comment::cast(node) { | 35 | // Also fold item *groups* that span multiple lines |
33 | // Multiline comments (`/* ... */`) can only be folded if they span multiple lines | 36 | |
34 | let range = if let ast::CommentFlavor::Multiline = comment.flavor() { | 37 | // Note: we need to skip elements of the group that we have already visited, |
35 | if comment.text().contains('\n') { | 38 | // otherwise there will be folds for the whole group and for its sub groups |
36 | Some(comment.syntax().range()) | 39 | if group_members.contains(&node) { |
37 | } else { | 40 | continue; |
38 | None | ||
39 | } | ||
40 | } else { | ||
41 | contiguous_range_for(SyntaxKind::COMMENT, node, &mut visited) | ||
42 | }; | ||
43 | |||
44 | range.map(|range| res.push(Fold { range, kind: FoldKind::Comment })); | ||
45 | } | 41 | } |
46 | 42 | ||
47 | if let SyntaxKind::USE_ITEM = node.kind() { | 43 | if let Some(kind) = fold_kind(node.kind()) { |
48 | contiguous_range_for(SyntaxKind::USE_ITEM, node, &mut visited) | 44 | contiguous_range_for_group(node.kind(), node, &mut group_members) |
49 | .map(|range| res.push(Fold { range, kind: FoldKind::Imports})); | 45 | .map(|range| res.push(Fold { range, kind })); |
50 | }; | 46 | } |
51 | } | 47 | } |
52 | 48 | ||
53 | res | 49 | res |
54 | } | 50 | } |
55 | 51 | ||
56 | fn contiguous_range_for<'a>( | 52 | fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> { |
57 | kind: SyntaxKind, | 53 | match kind { |
58 | node: SyntaxNodeRef<'a>, | 54 | SyntaxKind::COMMENT => Some(FoldKind::Comment), |
55 | SyntaxKind::USE_ITEM => Some(FoldKind::Imports), | ||
56 | _ => None | ||
57 | } | ||
58 | } | ||
59 | |||
60 | fn has_newline( | ||
61 | node: SyntaxNodeRef, | ||
62 | ) -> bool { | ||
63 | for descendant in node.descendants() { | ||
64 | if let Some(ws) = ast::Whitespace::cast(descendant) { | ||
65 | if ws.has_newlines() { | ||
66 | return true; | ||
67 | } | ||
68 | } else if let Some(comment) = ast::Comment::cast(descendant) { | ||
69 | if comment.has_newlines() { | ||
70 | return true; | ||
71 | } | ||
72 | } | ||
73 | } | ||
74 | |||
75 | false | ||
76 | } | ||
77 | |||
78 | fn contiguous_range_for_group<'a>( | ||
79 | group_kind: SyntaxKind, | ||
80 | first: SyntaxNodeRef<'a>, | ||
59 | visited: &mut FxHashSet<SyntaxNodeRef<'a>>, | 81 | visited: &mut FxHashSet<SyntaxNodeRef<'a>>, |
60 | ) -> Option<TextRange> { | 82 | ) -> Option<TextRange> { |
61 | visited.insert(node); | 83 | visited.insert(first); |
84 | |||
85 | let mut last = first; | ||
62 | 86 | ||
63 | let left = node; | 87 | for node in first.siblings(Direction::Next) { |
64 | let mut right = node; | ||
65 | for node in node.siblings(Direction::Next) { | ||
66 | visited.insert(node); | 88 | visited.insert(node); |
67 | match node.kind() { | 89 | if let Some(ws) = ast::Whitespace::cast(node) { |
68 | SyntaxKind::WHITESPACE if !node.leaf_text().unwrap().as_str().contains("\n\n") => (), | 90 | // There is a blank line, which means the group ends here |
69 | k => { | 91 | if ws.count_newlines_lazy().take(2).count() == 2 { |
70 | if k == kind { | 92 | break; |
71 | right = node | ||
72 | } else { | ||
73 | break; | ||
74 | } | ||
75 | } | 93 | } |
94 | |||
95 | // Ignore whitespace without blank lines | ||
96 | continue; | ||
97 | } | ||
98 | |||
99 | // The group ends when an element of a different kind is reached | ||
100 | if node.kind() != group_kind { | ||
101 | break; | ||
76 | } | 102 | } |
103 | |||
104 | // Keep track of the last node in the group | ||
105 | last = node; | ||
77 | } | 106 | } |
78 | if left != right { | 107 | |
108 | if first != last { | ||
79 | Some(TextRange::from_to( | 109 | Some(TextRange::from_to( |
80 | left.range().start(), | 110 | first.range().start(), |
81 | right.range().end(), | 111 | last.range().end(), |
82 | )) | 112 | )) |
83 | } else { | 113 | } else { |
114 | // The group consists of only one element, therefore it cannot be folded | ||
84 | None | 115 | None |
85 | } | 116 | } |
86 | } | 117 | } |
diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs index ef7b5b1a1..85aa5e0dd 100644 --- a/crates/ra_syntax/src/ast/generated.rs +++ b/crates/ra_syntax/src/ast/generated.rs | |||
@@ -2193,3 +2193,21 @@ impl<'a> WhileExpr<'a> { | |||
2193 | } | 2193 | } |
2194 | } | 2194 | } |
2195 | 2195 | ||
2196 | // Whitespace | ||
2197 | #[derive(Debug, Clone, Copy)] | ||
2198 | pub struct Whitespace<'a> { | ||
2199 | syntax: SyntaxNodeRef<'a>, | ||
2200 | } | ||
2201 | |||
2202 | impl<'a> AstNode<'a> for Whitespace<'a> { | ||
2203 | fn cast(syntax: SyntaxNodeRef<'a>) -> Option<Self> { | ||
2204 | match syntax.kind() { | ||
2205 | WHITESPACE => Some(Whitespace { syntax }), | ||
2206 | _ => None, | ||
2207 | } | ||
2208 | } | ||
2209 | fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax } | ||
2210 | } | ||
2211 | |||
2212 | impl<'a> Whitespace<'a> {} | ||
2213 | |||
diff --git a/crates/ra_syntax/src/ast/mod.rs b/crates/ra_syntax/src/ast/mod.rs index 10dac72e5..00c852274 100644 --- a/crates/ra_syntax/src/ast/mod.rs +++ b/crates/ra_syntax/src/ast/mod.rs | |||
@@ -100,8 +100,8 @@ impl<'a> Lifetime<'a> { | |||
100 | } | 100 | } |
101 | 101 | ||
102 | impl<'a> Comment<'a> { | 102 | impl<'a> Comment<'a> { |
103 | pub fn text(&self) -> SmolStr { | 103 | pub fn text(&self) -> &SmolStr { |
104 | self.syntax().leaf_text().unwrap().clone() | 104 | self.syntax().leaf_text().unwrap() |
105 | } | 105 | } |
106 | 106 | ||
107 | pub fn flavor(&self) -> CommentFlavor { | 107 | pub fn flavor(&self) -> CommentFlavor { |
@@ -120,6 +120,14 @@ impl<'a> Comment<'a> { | |||
120 | pub fn prefix(&self) -> &'static str { | 120 | pub fn prefix(&self) -> &'static str { |
121 | self.flavor().prefix() | 121 | self.flavor().prefix() |
122 | } | 122 | } |
123 | |||
124 | pub fn count_newlines_lazy(&self) -> impl Iterator<Item = &()> { | ||
125 | self.text().chars().filter(|&c| c == '\n').map(|_| &()) | ||
126 | } | ||
127 | |||
128 | pub fn has_newlines(&self) -> bool { | ||
129 | self.count_newlines_lazy().count() > 0 | ||
130 | } | ||
123 | } | 131 | } |
124 | 132 | ||
125 | #[derive(Debug)] | 133 | #[derive(Debug)] |
@@ -142,6 +150,20 @@ impl CommentFlavor { | |||
142 | } | 150 | } |
143 | } | 151 | } |
144 | 152 | ||
153 | impl<'a> Whitespace<'a> { | ||
154 | pub fn text(&self) -> &SmolStr { | ||
155 | &self.syntax().leaf_text().unwrap() | ||
156 | } | ||
157 | |||
158 | pub fn count_newlines_lazy(&self) -> impl Iterator<Item = &()> { | ||
159 | self.text().chars().filter(|&c| c == '\n').map(|_| &()) | ||
160 | } | ||
161 | |||
162 | pub fn has_newlines(&self) -> bool { | ||
163 | self.count_newlines_lazy().count() > 0 | ||
164 | } | ||
165 | } | ||
166 | |||
145 | impl<'a> Name<'a> { | 167 | impl<'a> Name<'a> { |
146 | pub fn text(&self) -> SmolStr { | 168 | pub fn text(&self) -> SmolStr { |
147 | let ident = self.syntax().first_child() | 169 | let ident = self.syntax().first_child() |
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron index 9da0c2c13..d538739de 100644 --- a/crates/ra_syntax/src/grammar.ron +++ b/crates/ra_syntax/src/grammar.ron | |||
@@ -538,5 +538,6 @@ Grammar( | |||
538 | options: [ "NameRef" ] | 538 | options: [ "NameRef" ] |
539 | ), | 539 | ), |
540 | "Comment": (), | 540 | "Comment": (), |
541 | "Whitespace": (), | ||
541 | }, | 542 | }, |
542 | ) | 543 | ) |