1
1
use rustc_hash:: FxHashSet ;
2
2
3
3
use syntax:: {
4
- ast:: { self , AstNode , AstToken , VisibilityOwner } ,
5
- Direction , NodeOrToken , SourceFile ,
4
+ ast:: { self , AstNode , AstToken } ,
5
+ match_ast , Direction , NodeOrToken , SourceFile ,
6
6
SyntaxKind :: { self , * } ,
7
- SyntaxNode , TextRange , TextSize ,
7
+ TextRange , TextSize ,
8
8
} ;
9
9
10
+ use std:: hash:: Hash ;
11
+
12
+ const REGION_START : & str = "// region:" ;
13
+ const REGION_END : & str = "// endregion" ;
14
+
10
15
#[ derive( Debug , PartialEq , Eq ) ]
11
16
pub enum FoldKind {
12
17
Comment ,
@@ -30,17 +35,18 @@ pub struct Fold {
30
35
31
36
// Feature: Folding
32
37
//
33
- // Defines folding regions for curly braced blocks, runs of consecutive import
34
- // statements , and `region` / `endregion` comment markers.
38
+ // Defines folding regions for curly braced blocks, runs of consecutive use, mod, const or static
39
+ // items , and `region` / `endregion` comment markers.
35
40
pub ( crate ) fn folding_ranges ( file : & SourceFile ) -> Vec < Fold > {
36
41
let mut res = vec ! [ ] ;
37
42
let mut visited_comments = FxHashSet :: default ( ) ;
38
43
let mut visited_imports = FxHashSet :: default ( ) ;
39
44
let mut visited_mods = FxHashSet :: default ( ) ;
40
45
let mut visited_consts = FxHashSet :: default ( ) ;
41
46
let mut visited_statics = FxHashSet :: default ( ) ;
47
+
42
48
// regions can be nested, here is a LIFO buffer
43
- let mut regions_starts : Vec < TextSize > = vec ! [ ] ;
49
+ let mut region_starts : Vec < TextSize > = vec ! [ ] ;
44
50
45
51
for element in file. syntax ( ) . descendants_with_tokens ( ) {
46
52
// Fold items that span multiple lines
@@ -59,71 +65,60 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> {
59
65
NodeOrToken :: Token ( token) => {
60
66
// Fold groups of comments
61
67
if let Some ( comment) = ast:: Comment :: cast ( token) {
62
- if !visited_comments. contains ( & comment) {
63
- // regions are not real comments
64
- if comment. text ( ) . trim ( ) . starts_with ( "// region:" ) {
65
- regions_starts. push ( comment. syntax ( ) . text_range ( ) . start ( ) ) ;
66
- } else if comment. text ( ) . trim ( ) . starts_with ( "// endregion" ) {
67
- if let Some ( region) = regions_starts. pop ( ) {
68
- res. push ( Fold {
69
- range : TextRange :: new (
70
- region,
71
- comment. syntax ( ) . text_range ( ) . end ( ) ,
72
- ) ,
73
- kind : FoldKind :: Region ,
74
- } )
75
- }
76
- } else {
77
- if let Some ( range) =
78
- contiguous_range_for_comment ( comment, & mut visited_comments)
79
- {
80
- res. push ( Fold { range, kind : FoldKind :: Comment } )
81
- }
68
+ if visited_comments. contains ( & comment) {
69
+ continue ;
70
+ }
71
+ let text = comment. text ( ) . trim_start ( ) ;
72
+ if text. starts_with ( REGION_START ) {
73
+ region_starts. push ( comment. syntax ( ) . text_range ( ) . start ( ) ) ;
74
+ } else if text. starts_with ( REGION_END ) {
75
+ if let Some ( region) = region_starts. pop ( ) {
76
+ res. push ( Fold {
77
+ range : TextRange :: new ( region, comment. syntax ( ) . text_range ( ) . end ( ) ) ,
78
+ kind : FoldKind :: Region ,
79
+ } )
82
80
}
81
+ } else if let Some ( range) =
82
+ contiguous_range_for_comment ( comment, & mut visited_comments)
83
+ {
84
+ res. push ( Fold { range, kind : FoldKind :: Comment } )
83
85
}
84
86
}
85
87
}
86
88
NodeOrToken :: Node ( node) => {
87
- // Fold groups of imports
88
- if node. kind ( ) == USE && !visited_imports. contains ( & node) {
89
- if let Some ( range) = contiguous_range_for_group ( & node, & mut visited_imports) {
90
- res. push ( Fold { range, kind : FoldKind :: Imports } )
91
- }
92
- }
93
-
94
- // Fold groups of mods
95
- if let Some ( module) = ast:: Module :: cast ( node. clone ( ) ) {
96
- if !has_visibility ( & node)
97
- && !visited_mods. contains ( & node)
98
- && module. item_list ( ) . is_none ( )
99
- {
100
- if let Some ( range) = contiguous_range_for_group_unless (
101
- & node,
102
- has_visibility,
103
- & mut visited_mods,
104
- ) {
105
- res. push ( Fold { range, kind : FoldKind :: Mods } )
106
- }
107
- }
108
- }
109
-
110
- // Fold groups of consts
111
- if node. kind ( ) == CONST && !visited_consts. contains ( & node) {
112
- if let Some ( range) = contiguous_range_for_group ( & node, & mut visited_consts) {
113
- res. push ( Fold { range, kind : FoldKind :: Consts } )
114
- }
115
- }
116
- // Fold groups of consts
117
- if node. kind ( ) == STATIC && !visited_statics. contains ( & node) {
118
- if let Some ( range) = contiguous_range_for_group ( & node, & mut visited_statics) {
119
- res. push ( Fold { range, kind : FoldKind :: Statics } )
120
- }
121
- }
122
-
123
- // Fold where clause
124
- if node. kind ( ) == WHERE_CLAUSE {
125
- if let Some ( range) = fold_range_for_where_clause ( & node) {
126
- res. push ( Fold { range, kind : FoldKind :: WhereClause } )
89
+ match_ast ! {
90
+ match node {
91
+ ast:: Module ( module) => {
92
+ if module. item_list( ) . is_none( ) {
93
+ if let Some ( range) = contiguous_range_for_item_group(
94
+ module,
95
+ & mut visited_mods,
96
+ ) {
97
+ res. push( Fold { range, kind: FoldKind :: Mods } )
98
+ }
99
+ }
100
+ } ,
101
+ ast:: Use ( use_) => {
102
+ if let Some ( range) = contiguous_range_for_item_group( use_, & mut visited_imports) {
103
+ res. push( Fold { range, kind: FoldKind :: Imports } )
104
+ }
105
+ } ,
106
+ ast:: Const ( konst) => {
107
+ if let Some ( range) = contiguous_range_for_item_group( konst, & mut visited_consts) {
108
+ res. push( Fold { range, kind: FoldKind :: Consts } )
109
+ }
110
+ } ,
111
+ ast:: Static ( statik) => {
112
+ if let Some ( range) = contiguous_range_for_item_group( statik, & mut visited_statics) {
113
+ res. push( Fold { range, kind: FoldKind :: Statics } )
114
+ }
115
+ } ,
116
+ ast:: WhereClause ( where_clause) => {
117
+ if let Some ( range) = fold_range_for_where_clause( where_clause) {
118
+ res. push( Fold { range, kind: FoldKind :: WhereClause } )
119
+ }
120
+ } ,
121
+ _ => ( ) ,
127
122
}
128
123
}
129
124
}
@@ -154,26 +149,16 @@ fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> {
154
149
}
155
150
}
156
151
157
- fn has_visibility ( node : & SyntaxNode ) -> bool {
158
- ast:: Module :: cast ( node. clone ( ) ) . and_then ( |m| m. visibility ( ) ) . is_some ( )
159
- }
160
-
161
- fn contiguous_range_for_group (
162
- first : & SyntaxNode ,
163
- visited : & mut FxHashSet < SyntaxNode > ,
164
- ) -> Option < TextRange > {
165
- contiguous_range_for_group_unless ( first, |_| false , visited)
166
- }
167
-
168
- fn contiguous_range_for_group_unless (
169
- first : & SyntaxNode ,
170
- unless : impl Fn ( & SyntaxNode ) -> bool ,
171
- visited : & mut FxHashSet < SyntaxNode > ,
172
- ) -> Option < TextRange > {
173
- visited. insert ( first. clone ( ) ) ;
152
+ fn contiguous_range_for_item_group < N > ( first : N , visited : & mut FxHashSet < N > ) -> Option < TextRange >
153
+ where
154
+ N : ast:: VisibilityOwner + Clone + Hash + Eq ,
155
+ {
156
+ if !visited. insert ( first. clone ( ) ) {
157
+ return None ;
158
+ }
174
159
175
- let mut last = first. clone ( ) ;
176
- for element in first. siblings_with_tokens ( Direction :: Next ) {
160
+ let ( mut last, mut last_vis ) = ( first. clone ( ) , first . visibility ( ) ) ;
161
+ for element in first. syntax ( ) . siblings_with_tokens ( Direction :: Next ) {
177
162
let node = match element {
178
163
NodeOrToken :: Token ( token) => {
179
164
if let Some ( ws) = ast:: Whitespace :: cast ( token) {
@@ -189,23 +174,35 @@ fn contiguous_range_for_group_unless(
189
174
NodeOrToken :: Node ( node) => node,
190
175
} ;
191
176
192
- // Stop if we find a node that doesn't belong to the group
193
- if node. kind ( ) != first. kind ( ) || unless ( & node) {
194
- break ;
177
+ if let Some ( next) = N :: cast ( node) {
178
+ let next_vis = next. visibility ( ) ;
179
+ if eq_visibility ( next_vis. clone ( ) , last_vis) {
180
+ visited. insert ( next. clone ( ) ) ;
181
+ last_vis = next_vis;
182
+ last = next;
183
+ continue ;
184
+ }
195
185
}
196
-
197
- visited. insert ( node. clone ( ) ) ;
198
- last = node;
186
+ // Stop if we find an item of a different kind or with a different visibility.
187
+ break ;
199
188
}
200
189
201
- if first != & last {
202
- Some ( TextRange :: new ( first. text_range ( ) . start ( ) , last. text_range ( ) . end ( ) ) )
190
+ if first != last {
191
+ Some ( TextRange :: new ( first. syntax ( ) . text_range ( ) . start ( ) , last. syntax ( ) . text_range ( ) . end ( ) ) )
203
192
} else {
204
193
// The group consists of only one element, therefore it cannot be folded
205
194
None
206
195
}
207
196
}
208
197
198
+ fn eq_visibility ( vis0 : Option < ast:: Visibility > , vis1 : Option < ast:: Visibility > ) -> bool {
199
+ match ( vis0, vis1) {
200
+ ( None , None ) => true ,
201
+ ( Some ( vis0) , Some ( vis1) ) => vis0. is_eq_to ( & vis1) ,
202
+ _ => false ,
203
+ }
204
+ }
205
+
209
206
fn contiguous_range_for_comment (
210
207
first : ast:: Comment ,
211
208
visited : & mut FxHashSet < ast:: Comment > ,
@@ -230,12 +227,9 @@ fn contiguous_range_for_comment(
230
227
}
231
228
if let Some ( c) = ast:: Comment :: cast ( token) {
232
229
if c. kind ( ) == group_kind {
230
+ let text = c. text ( ) . trim_start ( ) ;
233
231
// regions are not real comments
234
- if c. text ( ) . trim ( ) . starts_with ( "// region:" )
235
- || c. text ( ) . trim ( ) . starts_with ( "// endregion" )
236
- {
237
- break ;
238
- } else {
232
+ if !( text. starts_with ( REGION_START ) || text. starts_with ( REGION_END ) ) {
239
233
visited. insert ( c. clone ( ) ) ;
240
234
last = c;
241
235
continue ;
@@ -259,19 +253,14 @@ fn contiguous_range_for_comment(
259
253
}
260
254
}
261
255
262
- fn fold_range_for_where_clause ( node : & SyntaxNode ) -> Option < TextRange > {
263
- let first_where_pred = node . first_child ( ) ;
264
- let last_where_pred = node . last_child ( ) ;
256
+ fn fold_range_for_where_clause ( where_clause : ast :: WhereClause ) -> Option < TextRange > {
257
+ let first_where_pred = where_clause . predicates ( ) . next ( ) ;
258
+ let last_where_pred = where_clause . predicates ( ) . last ( ) ;
265
259
266
260
if first_where_pred != last_where_pred {
267
- let mut it = node. descendants_with_tokens ( ) ;
268
- if let ( Some ( _where_clause) , Some ( where_kw) , Some ( last_comma) ) =
269
- ( it. next ( ) , it. next ( ) , it. last ( ) )
270
- {
271
- let start = where_kw. text_range ( ) . end ( ) ;
272
- let end = last_comma. text_range ( ) . end ( ) ;
273
- return Some ( TextRange :: new ( start, end) ) ;
274
- }
261
+ let start = where_clause. where_token ( ) ?. text_range ( ) . end ( ) ;
262
+ let end = where_clause. syntax ( ) . text_range ( ) . end ( ) ;
263
+ return Some ( TextRange :: new ( start, end) ) ;
275
264
}
276
265
None
277
266
}
@@ -286,16 +275,18 @@ mod tests {
286
275
let ( ranges, text) = extract_tags ( ra_fixture, "fold" ) ;
287
276
288
277
let parse = SourceFile :: parse ( & text) ;
289
- let folds = folding_ranges ( & parse. tree ( ) ) ;
278
+ let mut folds = folding_ranges ( & parse. tree ( ) ) ;
279
+ folds. sort_by_key ( |fold| ( fold. range . start ( ) , fold. range . end ( ) ) ) ;
280
+
290
281
assert_eq ! (
291
282
folds. len( ) ,
292
283
ranges. len( ) ,
293
284
"The amount of folds is different than the expected amount"
294
285
) ;
295
286
296
287
for ( fold, ( range, attr) ) in folds. iter ( ) . zip ( ranges. into_iter ( ) ) {
297
- assert_eq ! ( fold. range. start( ) , range. start( ) ) ;
298
- assert_eq ! ( fold. range. end( ) , range. end( ) ) ;
288
+ assert_eq ! ( fold. range. start( ) , range. start( ) , "mismatched start of folding ranges" ) ;
289
+ assert_eq ! ( fold. range. end( ) , range. end( ) , "mismatched end of folding ranges" ) ;
299
290
300
291
let kind = match fold. kind {
301
292
FoldKind :: Comment => "comment" ,
@@ -525,7 +516,10 @@ const FOO: [usize; 4] = <fold array>[
525
516
// 1. some normal comment
526
517
<fold region>// region: test
527
518
// 2. some normal comment
528
- calling_function(x,y);
519
+ <fold region>// region: inner
520
+ fn f() {}
521
+ // endregion</fold>
522
+ fn f2() {}
529
523
// endregion: test</fold>
530
524
"# ,
531
525
)
0 commit comments