aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api/src/folding_ranges.rs
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2019-11-27 18:32:33 +0000
committerAleksey Kladov <[email protected]>2019-11-27 18:35:06 +0000
commit757e593b253b4df7e6fc8bf15a4d4f34c9d484c5 (patch)
treed972d3a7e6457efdb5e0c558a8350db1818d07ae /crates/ra_ide_api/src/folding_ranges.rs
parentd9a36a736bfb91578a36505e7237212959bb55fe (diff)
rename ra_ide_api -> ra_ide
Diffstat (limited to 'crates/ra_ide_api/src/folding_ranges.rs')
-rw-r--r--crates/ra_ide_api/src/folding_ranges.rs378
1 files changed, 0 insertions, 378 deletions
diff --git a/crates/ra_ide_api/src/folding_ranges.rs b/crates/ra_ide_api/src/folding_ranges.rs
deleted file mode 100644
index 4eeb76d14..000000000
--- a/crates/ra_ide_api/src/folding_ranges.rs
+++ /dev/null
@@ -1,378 +0,0 @@
1//! FIXME: write short doc here
2
3use rustc_hash::FxHashSet;
4
5use ra_syntax::{
6 ast::{self, AstNode, AstToken, VisibilityOwner},
7 Direction, NodeOrToken, SourceFile,
8 SyntaxKind::{self, *},
9 SyntaxNode, TextRange,
10};
11
12#[derive(Debug, PartialEq, Eq)]
13pub enum FoldKind {
14 Comment,
15 Imports,
16 Mods,
17 Block,
18}
19
20#[derive(Debug)]
21pub struct Fold {
22 pub range: TextRange,
23 pub kind: FoldKind,
24}
25
26pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> {
27 let mut res = vec![];
28 let mut visited_comments = FxHashSet::default();
29 let mut visited_imports = FxHashSet::default();
30 let mut visited_mods = FxHashSet::default();
31
32 for element in file.syntax().descendants_with_tokens() {
33 // Fold items that span multiple lines
34 if let Some(kind) = fold_kind(element.kind()) {
35 let is_multiline = match &element {
36 NodeOrToken::Node(node) => node.text().contains_char('\n'),
37 NodeOrToken::Token(token) => token.text().contains('\n'),
38 };
39 if is_multiline {
40 res.push(Fold { range: element.text_range(), kind });
41 continue;
42 }
43 }
44
45 match element {
46 NodeOrToken::Token(token) => {
47 // Fold groups of comments
48 if let Some(comment) = ast::Comment::cast(token) {
49 if !visited_comments.contains(&comment) {
50 if let Some(range) =
51 contiguous_range_for_comment(comment, &mut visited_comments)
52 {
53 res.push(Fold { range, kind: FoldKind::Comment })
54 }
55 }
56 }
57 }
58 NodeOrToken::Node(node) => {
59 // Fold groups of imports
60 if node.kind() == USE_ITEM && !visited_imports.contains(&node) {
61 if let Some(range) = contiguous_range_for_group(&node, &mut visited_imports) {
62 res.push(Fold { range, kind: FoldKind::Imports })
63 }
64 }
65
66 // Fold groups of mods
67 if node.kind() == MODULE && !has_visibility(&node) && !visited_mods.contains(&node)
68 {
69 if let Some(range) =
70 contiguous_range_for_group_unless(&node, has_visibility, &mut visited_mods)
71 {
72 res.push(Fold { range, kind: FoldKind::Mods })
73 }
74 }
75 }
76 }
77 }
78
79 res
80}
81
82fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> {
83 match kind {
84 COMMENT => Some(FoldKind::Comment),
85 USE_ITEM => Some(FoldKind::Imports),
86 RECORD_FIELD_DEF_LIST
87 | RECORD_FIELD_PAT_LIST
88 | ITEM_LIST
89 | EXTERN_ITEM_LIST
90 | USE_TREE_LIST
91 | BLOCK
92 | MATCH_ARM_LIST
93 | ENUM_VARIANT_LIST
94 | TOKEN_TREE => Some(FoldKind::Block),
95 _ => None,
96 }
97}
98
99fn has_visibility(node: &SyntaxNode) -> bool {
100 ast::Module::cast(node.clone()).and_then(|m| m.visibility()).is_some()
101}
102
103fn contiguous_range_for_group(
104 first: &SyntaxNode,
105 visited: &mut FxHashSet<SyntaxNode>,
106) -> Option<TextRange> {
107 contiguous_range_for_group_unless(first, |_| false, visited)
108}
109
110fn contiguous_range_for_group_unless(
111 first: &SyntaxNode,
112 unless: impl Fn(&SyntaxNode) -> bool,
113 visited: &mut FxHashSet<SyntaxNode>,
114) -> Option<TextRange> {
115 visited.insert(first.clone());
116
117 let mut last = first.clone();
118 for element in first.siblings_with_tokens(Direction::Next) {
119 let node = match element {
120 NodeOrToken::Token(token) => {
121 if let Some(ws) = ast::Whitespace::cast(token) {
122 if !ws.spans_multiple_lines() {
123 // Ignore whitespace without blank lines
124 continue;
125 }
126 }
127 // There is a blank line or another token, which means that the
128 // group ends here
129 break;
130 }
131 NodeOrToken::Node(node) => node,
132 };
133
134 // Stop if we find a node that doesn't belong to the group
135 if node.kind() != first.kind() || unless(&node) {
136 break;
137 }
138
139 visited.insert(node.clone());
140 last = node;
141 }
142
143 if first != &last {
144 Some(TextRange::from_to(first.text_range().start(), last.text_range().end()))
145 } else {
146 // The group consists of only one element, therefore it cannot be folded
147 None
148 }
149}
150
151fn contiguous_range_for_comment(
152 first: ast::Comment,
153 visited: &mut FxHashSet<ast::Comment>,
154) -> Option<TextRange> {
155 visited.insert(first.clone());
156
157 // Only fold comments of the same flavor
158 let group_kind = first.kind();
159 if !group_kind.shape.is_line() {
160 return None;
161 }
162
163 let mut last = first.clone();
164 for element in first.syntax().siblings_with_tokens(Direction::Next) {
165 match element {
166 NodeOrToken::Token(token) => {
167 if let Some(ws) = ast::Whitespace::cast(token.clone()) {
168 if !ws.spans_multiple_lines() {
169 // Ignore whitespace without blank lines
170 continue;
171 }
172 }
173 if let Some(c) = ast::Comment::cast(token) {
174 if c.kind() == group_kind {
175 visited.insert(c.clone());
176 last = c;
177 continue;
178 }
179 }
180 // The comment group ends because either:
181 // * An element of a different kind was reached
182 // * A comment of a different flavor was reached
183 break;
184 }
185 NodeOrToken::Node(_) => break,
186 };
187 }
188
189 if first != last {
190 Some(TextRange::from_to(
191 first.syntax().text_range().start(),
192 last.syntax().text_range().end(),
193 ))
194 } else {
195 // The group consists of only one element, therefore it cannot be folded
196 None
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use test_utils::extract_ranges;
204
205 fn do_check(text: &str, fold_kinds: &[FoldKind]) {
206 let (ranges, text) = extract_ranges(text, "fold");
207 let parse = SourceFile::parse(&text);
208 let folds = folding_ranges(&parse.tree());
209
210 assert_eq!(
211 folds.len(),
212 ranges.len(),
213 "The amount of folds is different than the expected amount"
214 );
215 assert_eq!(
216 folds.len(),
217 fold_kinds.len(),
218 "The amount of fold kinds is different than the expected amount"
219 );
220 for ((fold, range), fold_kind) in
221 folds.iter().zip(ranges.into_iter()).zip(fold_kinds.iter())
222 {
223 assert_eq!(fold.range.start(), range.start());
224 assert_eq!(fold.range.end(), range.end());
225 assert_eq!(&fold.kind, fold_kind);
226 }
227 }
228
229 #[test]
230 fn test_fold_comments() {
231 let text = r#"
232<fold>// Hello
233// this is a multiline
234// comment
235//</fold>
236
237// But this is not
238
239fn main() <fold>{
240 <fold>// We should
241 // also
242 // fold
243 // this one.</fold>
244 <fold>//! But this one is different
245 //! because it has another flavor</fold>
246 <fold>/* As does this
247 multiline comment */</fold>
248}</fold>"#;
249
250 let fold_kinds = &[
251 FoldKind::Comment,
252 FoldKind::Block,
253 FoldKind::Comment,
254 FoldKind::Comment,
255 FoldKind::Comment,
256 ];
257 do_check(text, fold_kinds);
258 }
259
260 #[test]
261 fn test_fold_imports() {
262 let text = r#"
263<fold>use std::<fold>{
264 str,
265 vec,
266 io as iop
267}</fold>;</fold>
268
269fn main() <fold>{
270}</fold>"#;
271
272 let folds = &[FoldKind::Imports, FoldKind::Block, FoldKind::Block];
273 do_check(text, folds);
274 }
275
276 #[test]
277 fn test_fold_mods() {
278 let text = r#"
279
280pub mod foo;
281<fold>mod after_pub;
282mod after_pub_next;</fold>
283
284<fold>mod before_pub;
285mod before_pub_next;</fold>
286pub mod bar;
287
288mod not_folding_single;
289pub mod foobar;
290pub not_folding_single_next;
291
292<fold>#[cfg(test)]
293mod with_attribute;
294mod with_attribute_next;</fold>
295
296fn main() <fold>{
297}</fold>"#;
298
299 let folds = &[FoldKind::Mods, FoldKind::Mods, FoldKind::Mods, FoldKind::Block];
300 do_check(text, folds);
301 }
302
303 #[test]
304 fn test_fold_import_groups() {
305 let text = r#"
306<fold>use std::str;
307use std::vec;
308use std::io as iop;</fold>
309
310<fold>use std::mem;
311use std::f64;</fold>
312
313use std::collections::HashMap;
314// Some random comment
315use std::collections::VecDeque;
316
317fn main() <fold>{
318}</fold>"#;
319
320 let folds = &[FoldKind::Imports, FoldKind::Imports, FoldKind::Block];
321 do_check(text, folds);
322 }
323
324 #[test]
325 fn test_fold_import_and_groups() {
326 let text = r#"
327<fold>use std::str;
328use std::vec;
329use std::io as iop;</fold>
330
331<fold>use std::mem;
332use std::f64;</fold>
333
334<fold>use std::collections::<fold>{
335 HashMap,
336 VecDeque,
337}</fold>;</fold>
338// Some random comment
339
340fn main() <fold>{
341}</fold>"#;
342
343 let folds = &[
344 FoldKind::Imports,
345 FoldKind::Imports,
346 FoldKind::Imports,
347 FoldKind::Block,
348 FoldKind::Block,
349 ];
350 do_check(text, folds);
351 }
352
353 #[test]
354 fn test_folds_macros() {
355 let text = r#"
356macro_rules! foo <fold>{
357 ($($tt:tt)*) => { $($tt)* }
358}</fold>
359"#;
360
361 let folds = &[FoldKind::Block];
362 do_check(text, folds);
363 }
364
365 #[test]
366 fn test_fold_match_arms() {
367 let text = r#"
368fn main() <fold>{
369 match 0 <fold>{
370 0 => 0,
371 _ => 1,
372 }</fold>
373}</fold>"#;
374
375 let folds = &[FoldKind::Block, FoldKind::Block];
376 do_check(text, folds);
377 }
378}