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