aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api/src/folding_ranges.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api/src/folding_ranges.rs')
-rw-r--r--crates/ra_ide_api/src/folding_ranges.rs134
1 files changed, 78 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..a6fe8a5d5 100644
--- a/crates/ra_ide_api/src/folding_ranges.rs
+++ b/crates/ra_ide_api/src/folding_ranges.rs
@@ -1,9 +1,9 @@
1use rustc_hash::FxHashSet; 1use rustc_hash::FxHashSet;
2 2
3use ra_syntax::{ 3use ra_syntax::{
4 AstNode, Direction, SourceFile, SyntaxNode, TextRange, 4 AstNode, SourceFile, SyntaxNode, TextRange, Direction, SyntaxElement,
5 SyntaxKind::{self, *}, 5 SyntaxKind::{self, *},
6 ast::{self, VisibilityOwner}, 6 ast::{self, VisibilityOwner, Comment},
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,42 @@ fn contiguous_range_for_group_unless<'a>(
119} 139}
120 140
121fn contiguous_range_for_comment<'a>( 141fn contiguous_range_for_comment<'a>(
122 first: &'a SyntaxNode, 142 first: Comment<'a>,
123 visited: &mut FxHashSet<&'a SyntaxNode>, 143 visited: &mut FxHashSet<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_flavor = first.flavor();
129 149
130 let mut last = first; 150 let mut last = first;
131 for node in first.siblings(Direction::Next) { 151 for element in first.syntax().siblings_with_tokens(Direction::Next) {
132 if let Some(ws) = ast::Whitespace::cast(node) { 152 match element {
133 // There is a blank line, which means the group ends here 153 SyntaxElement::Token(token) => {
134 if ws.count_newlines_lazy().take(2).count() == 2 { 154 if let Some(ws) = ast::Whitespace::cast(token) {
155 if !ws.spans_multiple_lines() {
156 // Ignore whitespace without blank lines
157 continue;
158 }
159 }
160 if let Some(c) = Comment::cast(token) {
161 if c.flavor() == group_flavor {
162 visited.insert(c);
163 last = c;
164 continue;
165 }
166 }
167 // The comment group ends because either:
168 // * An element of a different kind was reached
169 // * A comment of a different flavor was reached
135 break; 170 break;
136 } 171 }
137 172 SyntaxElement::Node(_) => break,
138 // Ignore whitespace without blank lines 173 };
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 } 174 }
153 175
154 if first != last { 176 if first != last {
155 Some(TextRange::from_to(first.range().start(), last.range().end())) 177 Some(TextRange::from_to(first.syntax().range().start(), last.syntax().range().end()))
156 } else { 178 } else {
157 // The group consists of only one element, therefore it cannot be folded 179 // The group consists of only one element, therefore it cannot be folded
158 None 180 None