diff options
Diffstat (limited to 'crates/ra_ide_api/src/folding_ranges.rs')
-rw-r--r-- | crates/ra_ide_api/src/folding_ranges.rs | 137 |
1 files changed, 81 insertions, 56 deletions
diff --git a/crates/ra_ide_api/src/folding_ranges.rs b/crates/ra_ide_api/src/folding_ranges.rs index b96145f05..6987fcc9e 100644 --- a/crates/ra_ide_api/src/folding_ranges.rs +++ b/crates/ra_ide_api/src/folding_ranges.rs | |||
@@ -1,9 +1,9 @@ | |||
1 | use rustc_hash::FxHashSet; | 1 | use rustc_hash::FxHashSet; |
2 | 2 | ||
3 | use ra_syntax::{ | 3 | use ra_syntax::{ |
4 | AstNode, Direction, SourceFile, SyntaxNode, TextRange, | 4 | SourceFile, SyntaxNode, TextRange, Direction, SyntaxElement, |
5 | SyntaxKind::{self, *}, | 5 | SyntaxKind::{self, *}, |
6 | ast::{self, VisibilityOwner}, | 6 | ast::{self, AstNode, AstToken, VisibilityOwner}, |
7 | }; | 7 | }; |
8 | 8 | ||
9 | #[derive(Debug, PartialEq, Eq)] | 9 | #[derive(Debug, PartialEq, Eq)] |
@@ -26,34 +26,49 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> { | |||
26 | let mut visited_imports = FxHashSet::default(); | 26 | let mut visited_imports = FxHashSet::default(); |
27 | let mut visited_mods = FxHashSet::default(); | 27 | let mut visited_mods = FxHashSet::default(); |
28 | 28 | ||
29 | for node in file.syntax().descendants() { | 29 | for element in file.syntax().descendants_with_tokens() { |
30 | // Fold items that span multiple lines | 30 | // Fold items that span multiple lines |
31 | if let Some(kind) = fold_kind(node.kind()) { | 31 | if let Some(kind) = fold_kind(element.kind()) { |
32 | if node.text().contains('\n') { | 32 | let is_multiline = match element { |
33 | res.push(Fold { range: node.range(), kind }); | 33 | SyntaxElement::Node(node) => node.text().contains('\n'), |
34 | SyntaxElement::Token(token) => token.text().contains('\n'), | ||
35 | }; | ||
36 | if is_multiline { | ||
37 | res.push(Fold { range: element.range(), kind }); | ||
38 | continue; | ||
34 | } | 39 | } |
35 | } | 40 | } |
36 | 41 | ||
37 | // Fold groups of comments | 42 | match element { |
38 | if node.kind() == COMMENT && !visited_comments.contains(&node) { | 43 | SyntaxElement::Token(token) => { |
39 | if let Some(range) = contiguous_range_for_comment(node, &mut visited_comments) { | 44 | // Fold groups of comments |
40 | res.push(Fold { range, kind: FoldKind::Comment }) | 45 | if let Some(comment) = ast::Comment::cast(token) { |
46 | if !visited_comments.contains(&comment) { | ||
47 | if let Some(range) = | ||
48 | contiguous_range_for_comment(comment, &mut visited_comments) | ||
49 | { | ||
50 | res.push(Fold { range, kind: FoldKind::Comment }) | ||
51 | } | ||
52 | } | ||
53 | } | ||
41 | } | 54 | } |
42 | } | 55 | SyntaxElement::Node(node) => { |
43 | 56 | // Fold groups of imports | |
44 | // Fold groups of imports | 57 | if node.kind() == USE_ITEM && !visited_imports.contains(&node) { |
45 | if node.kind() == USE_ITEM && !visited_imports.contains(&node) { | 58 | if let Some(range) = contiguous_range_for_group(node, &mut visited_imports) { |
46 | if let Some(range) = contiguous_range_for_group(node, &mut visited_imports) { | 59 | res.push(Fold { range, kind: FoldKind::Imports }) |
47 | res.push(Fold { range, kind: FoldKind::Imports }) | 60 | } |
48 | } | 61 | } |
49 | } | 62 | |
50 | 63 | // Fold groups of mods | |
51 | // Fold groups of mods | 64 | if node.kind() == MODULE && !has_visibility(&node) && !visited_mods.contains(&node) |
52 | if node.kind() == MODULE && !has_visibility(&node) && !visited_mods.contains(&node) { | 65 | { |
53 | if let Some(range) = | 66 | if let Some(range) = |
54 | contiguous_range_for_group_unless(node, has_visibility, &mut visited_mods) | 67 | contiguous_range_for_group_unless(node, has_visibility, &mut visited_mods) |
55 | { | 68 | { |
56 | res.push(Fold { range, kind: FoldKind::Mods }) | 69 | res.push(Fold { range, kind: FoldKind::Mods }) |
70 | } | ||
71 | } | ||
57 | } | 72 | } |
58 | } | 73 | } |
59 | } | 74 | } |
@@ -90,16 +105,21 @@ fn contiguous_range_for_group_unless<'a>( | |||
90 | visited.insert(first); | 105 | visited.insert(first); |
91 | 106 | ||
92 | let mut last = first; | 107 | let mut last = first; |
93 | for node in first.siblings(Direction::Next) { | 108 | for element in first.siblings_with_tokens(Direction::Next) { |
94 | if let Some(ws) = ast::Whitespace::cast(node) { | 109 | let node = match element { |
95 | // There is a blank line, which means that the group ends here | 110 | SyntaxElement::Token(token) => { |
96 | if ws.count_newlines_lazy().take(2).count() == 2 { | 111 | if let Some(ws) = ast::Whitespace::cast(token) { |
112 | if !ws.spans_multiple_lines() { | ||
113 | // Ignore whitespace without blank lines | ||
114 | continue; | ||
115 | } | ||
116 | } | ||
117 | // There is a blank line or another token, which means that the | ||
118 | // group ends here | ||
97 | break; | 119 | break; |
98 | } | 120 | } |
99 | 121 | SyntaxElement::Node(node) => node, | |
100 | // Ignore whitespace without blank lines | 122 | }; |
101 | continue; | ||
102 | } | ||
103 | 123 | ||
104 | // Stop if we find a node that doesn't belong to the group | 124 | // Stop if we find a node that doesn't belong to the group |
105 | if node.kind() != first.kind() || unless(node) { | 125 | if node.kind() != first.kind() || unless(node) { |
@@ -119,40 +139,45 @@ fn contiguous_range_for_group_unless<'a>( | |||
119 | } | 139 | } |
120 | 140 | ||
121 | fn contiguous_range_for_comment<'a>( | 141 | fn contiguous_range_for_comment<'a>( |
122 | first: &'a SyntaxNode, | 142 | first: ast::Comment<'a>, |
123 | visited: &mut FxHashSet<&'a SyntaxNode>, | 143 | visited: &mut FxHashSet<ast::Comment<'a>>, |
124 | ) -> Option<TextRange> { | 144 | ) -> Option<TextRange> { |
125 | visited.insert(first); | 145 | visited.insert(first); |
126 | 146 | ||
127 | // Only fold comments of the same flavor | 147 | // Only fold comments of the same flavor |
128 | let group_flavor = ast::Comment::cast(first)?.flavor(); | 148 | let group_kind = first.kind(); |
149 | if !group_kind.shape.is_line() { | ||
150 | return None; | ||
151 | } | ||
129 | 152 | ||
130 | let mut last = first; | 153 | let mut last = first; |
131 | for node in first.siblings(Direction::Next) { | 154 | for element in first.syntax().siblings_with_tokens(Direction::Next) { |
132 | if let Some(ws) = ast::Whitespace::cast(node) { | 155 | match element { |
133 | // There is a blank line, which means the group ends here | 156 | SyntaxElement::Token(token) => { |
134 | if ws.count_newlines_lazy().take(2).count() == 2 { | 157 | if let Some(ws) = ast::Whitespace::cast(token) { |
158 | if !ws.spans_multiple_lines() { | ||
159 | // Ignore whitespace without blank lines | ||
160 | continue; | ||
161 | } | ||
162 | } | ||
163 | if let Some(c) = ast::Comment::cast(token) { | ||
164 | if c.kind() == group_kind { | ||
165 | visited.insert(c); | ||
166 | last = c; | ||
167 | continue; | ||
168 | } | ||
169 | } | ||
170 | // The comment group ends because either: | ||
171 | // * An element of a different kind was reached | ||
172 | // * A comment of a different flavor was reached | ||
135 | break; | 173 | break; |
136 | } | 174 | } |
137 | 175 | SyntaxElement::Node(_) => break, | |
138 | // Ignore whitespace without blank lines | 176 | }; |
139 | continue; | ||
140 | } | ||
141 | |||
142 | match ast::Comment::cast(node) { | ||
143 | Some(next_comment) if next_comment.flavor() == group_flavor => { | ||
144 | visited.insert(node); | ||
145 | last = node; | ||
146 | } | ||
147 | // The comment group ends because either: | ||
148 | // * An element of a different kind was reached | ||
149 | // * A comment of a different flavor was reached | ||
150 | _ => break, | ||
151 | } | ||
152 | } | 177 | } |
153 | 178 | ||
154 | if first != last { | 179 | if first != last { |
155 | Some(TextRange::from_to(first.range().start(), last.range().end())) | 180 | Some(TextRange::from_to(first.syntax().range().start(), last.syntax().range().end())) |
156 | } else { | 181 | } else { |
157 | // The group consists of only one element, therefore it cannot be folded | 182 | // The group consists of only one element, therefore it cannot be folded |
158 | None | 183 | None |