aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_editor/src/folding_ranges.rs107
-rw-r--r--crates/ra_syntax/src/ast/generated.rs18
-rw-r--r--crates/ra_syntax/src/ast/mod.rs26
-rw-r--r--crates/ra_syntax/src/grammar.ron1
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
23pub fn folding_ranges(file: &File) -> Vec<Fold> { 23pub 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
56fn contiguous_range_for<'a>( 52fn 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
60fn 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
78fn 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)]
2198pub struct Whitespace<'a> {
2199 syntax: SyntaxNodeRef<'a>,
2200}
2201
2202impl<'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
2212impl<'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
102impl<'a> Comment<'a> { 102impl<'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
153impl<'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
145impl<'a> Name<'a> { 167impl<'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)