diff options
Diffstat (limited to 'crates/ra_editor')
-rw-r--r-- | crates/ra_editor/src/folding_ranges.rs | 107 |
1 files changed, 69 insertions, 38 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 | ||
23 | pub fn folding_ranges(file: &File) -> Vec<Fold> { | 23 | pub 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 | ||
56 | fn contiguous_range_for<'a>( | 52 | fn 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 | |||
60 | fn 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 | |||
78 | fn 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 | } |