aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/folding_ranges.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/folding_ranges.rs')
-rw-r--r--crates/ra_ide/src/folding_ranges.rs401
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
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 ArgList,
19}
20
21#[derive(Debug)]
22pub struct Fold {
23 pub range: TextRange,
24 pub kind: FoldKind,
25}
26
27pub(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
83fn 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
102fn has_visibility(node: &SyntaxNode) -> bool {
103 ast::Module::cast(node.clone()).and_then(|m| m.visibility()).is_some()
104}
105
106fn 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
113fn 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
154fn 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)]
201mod 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
243fn 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
266fn main() <fold block>{
267}</fold>"#,
268 );
269 }
270
271 #[test]
272 fn test_fold_mods() {
273 check(
274 r#"
275
276pub mod foo;
277<fold mods>mod after_pub;
278mod after_pub_next;</fold>
279
280<fold mods>mod before_pub;
281mod before_pub_next;</fold>
282pub mod bar;
283
284mod not_folding_single;
285pub mod foobar;
286pub not_folding_single_next;
287
288<fold mods>#[cfg(test)]
289mod with_attribute;
290mod with_attribute_next;</fold>
291
292fn 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;
302use std::vec;
303use std::io as iop;</fold>
304
305<fold imports>use std::mem;
306use std::f64;</fold>
307
308use std::collections::HashMap;
309// Some random comment
310use std::collections::VecDeque;
311
312fn 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;
322use std::vec;
323use std::io as iop;</fold>
324
325<fold imports>use std::mem;
326use std::f64;</fold>
327
328<fold imports>use std::collections::<fold block>{
329 HashMap,
330 VecDeque,
331}</fold>;</fold>
332// Some random comment
333
334fn main() <fold block>{
335}</fold>"#,
336 );
337 }
338
339 #[test]
340 fn test_folds_macros() {
341 check(
342 r#"
343macro_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#"
354fn 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#"
368fn 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#"
383const _: S = S <fold block>{
384
385}</fold>;
386"#,
387 )
388 }
389
390 #[test]
391 fn fold_multiline_params() {
392 check(
393 r#"
394fn foo<fold arglist>(
395 x: i32,
396 y: String,
397)</fold> {}
398"#,
399 )
400 }
401}