@@ -2,10 +2,10 @@ use std::{iter, mem::discriminant};
2
2
3
3
use crate :: {
4
4
doc_links:: token_as_doc_comment, navigation_target:: ToNav , FilePosition , NavigationTarget ,
5
- RangeInfo , TryToNav , UpmappingResult ,
5
+ RangeInfo , TryToNav ,
6
6
} ;
7
7
use hir:: {
8
- AsAssocItem , AssocItem , DescendPreference , InFile , MacroFileIdExt , ModuleDef , Semantics ,
8
+ AsAssocItem , AssocItem , DescendPreference , FileRange , InFile , MacroFileIdExt , ModuleDef , Semantics
9
9
} ;
10
10
use ide_db:: {
11
11
base_db:: { AnchoredPath , FileLoader } ,
@@ -14,11 +14,12 @@ use ide_db::{
14
14
FileId , RootDatabase ,
15
15
} ;
16
16
use itertools:: Itertools ;
17
+
17
18
use syntax:: {
18
- ast:: { self , HasLoopBody , Label } ,
19
+ ast:: { self , HasLoopBody } ,
19
20
match_ast, AstNode , AstToken ,
20
21
SyntaxKind :: * ,
21
- SyntaxToken , TextRange , T ,
22
+ SyntaxNode , SyntaxToken , TextRange , T ,
22
23
} ;
23
24
24
25
// Feature: Go to Definition
@@ -208,136 +209,127 @@ fn handle_control_flow_keywords(
208
209
match token. kind ( ) {
209
210
// For `fn` / `loop` / `while` / `for` / `async`, return the keyword it self,
210
211
// so that VSCode will find the references when using `ctrl + click`
211
- T ! [ fn ] | T ! [ async ] | T ! [ try] | T ! [ return ] => try_find_fn_or_closure ( sema, token) ,
212
- T ! [ loop ] | T ! [ while ] | T ! [ break ] | T ! [ continue ] => try_find_loop ( sema, token) ,
212
+ T ! [ fn ] | T ! [ async ] | T ! [ try] | T ! [ return ] => nav_for_exit_points ( sema, token) ,
213
+ T ! [ loop ] | T ! [ while ] | T ! [ break ] | T ! [ continue ] => nav_for_break_points ( sema, token) ,
213
214
T ! [ for ] if token. parent ( ) . and_then ( ast:: ForExpr :: cast) . is_some ( ) => {
214
- try_find_loop ( sema, token)
215
+ nav_for_break_points ( sema, token)
215
216
}
216
217
_ => None ,
217
218
}
218
219
}
219
220
220
- fn try_find_fn_or_closure (
221
+ pub ( crate ) fn find_fn_or_blocks (
222
+ sema : & Semantics < ' _ , RootDatabase > ,
223
+ token : & SyntaxToken ,
224
+ ) -> Vec < SyntaxNode > {
225
+ let find_ancestors = |token : SyntaxToken | {
226
+ let token_kind = token. kind ( ) ;
227
+
228
+ for anc in sema. token_ancestors_with_macros ( token) {
229
+ let node = match_ast ! {
230
+ match anc {
231
+ ast:: Fn ( fn_) => fn_. syntax( ) . clone( ) ,
232
+ ast:: ClosureExpr ( c) => c. syntax( ) . clone( ) ,
233
+ ast:: BlockExpr ( blk) => {
234
+ match blk. modifier( ) {
235
+ Some ( ast:: BlockModifier :: Async ( _) ) => blk. syntax( ) . clone( ) ,
236
+ Some ( ast:: BlockModifier :: Try ( _) ) if token_kind != T ![ return ] => blk. syntax( ) . clone( ) ,
237
+ _ => continue ,
238
+ }
239
+ } ,
240
+ _ => continue ,
241
+ }
242
+ } ;
243
+
244
+ return Some ( node) ;
245
+ }
246
+ None
247
+ } ;
248
+
249
+ sema. descend_into_macros ( DescendPreference :: None , token. clone ( ) )
250
+ . into_iter ( )
251
+ . filter_map ( find_ancestors)
252
+ . collect_vec ( )
253
+ }
254
+
255
+ fn nav_for_exit_points (
221
256
sema : & Semantics < ' _ , RootDatabase > ,
222
257
token : & SyntaxToken ,
223
258
) -> Option < Vec < NavigationTarget > > {
224
- fn find_exit_point (
225
- sema : & Semantics < ' _ , RootDatabase > ,
226
- token : SyntaxToken ,
227
- ) -> Option < UpmappingResult < NavigationTarget > > {
228
- let db = sema . db ;
229
-
230
- for anc in sema. token_ancestors_with_macros ( token . clone ( ) ) {
231
- let file_id = sema . hir_file_for ( & anc ) ;
259
+ let db = sema . db ;
260
+ let token_kind = token . kind ( ) ;
261
+
262
+ let navs = find_fn_or_blocks ( sema , token )
263
+ . into_iter ( )
264
+ . filter_map ( |node| {
265
+ let file_id = sema. hir_file_for ( & node ) ;
266
+
232
267
match_ast ! {
233
- match anc {
268
+ match node {
234
269
ast:: Fn ( fn_) => {
235
- let fn_: ast:: Fn = fn_;
236
- let nav = sema. to_def( & fn_) ?. try_to_nav( db) ?;
270
+ let mut nav = sema. to_def( & fn_) ?. try_to_nav( db) ?;
237
271
// For async token, we navigate to itself, which triggers
238
272
// VSCode to find the references
239
- let focus_token = if matches!( token . kind ( ) , T ![ async ] ) {
273
+ let focus_token = if matches!( token_kind , T ![ async ] ) {
240
274
fn_. async_token( ) ?
241
275
} else {
242
276
fn_. fn_token( ) ?
243
277
} ;
244
278
245
- let focus_range = InFile :: new( file_id, focus_token. text_range( ) )
246
- . original_node_file_range_opt( db)
247
- . map( |( frange, _) | frange. range) ;
248
- return Some ( nav. map( |it| {
249
- if focus_range. is_some_and( |range| it. full_range. contains_range( range) ) {
250
- NavigationTarget { focus_range, ..it }
251
- } else {
252
- it
279
+ let focus_frange = InFile :: new( file_id, focus_token. text_range( ) )
280
+ . original_node_file_range_opt( db)
281
+ . map( |( frange, _) | frange) ;
282
+
283
+ if let Some ( FileRange { file_id, range } ) = focus_frange {
284
+ let contains_frange = |nav: & NavigationTarget | {
285
+ nav. file_id == file_id && nav. full_range. contains_range( range)
286
+ } ;
287
+
288
+ if let Some ( def_site) = nav. def_site. as_mut( ) {
289
+ if contains_frange( def_site) {
290
+ def_site. focus_range = Some ( range) ;
291
+ }
292
+ } else if contains_frange( & nav. call_site) {
293
+ nav. call_site. focus_range = Some ( range) ;
253
294
}
254
- } ) ) ;
295
+ }
296
+
297
+ Some ( nav)
255
298
} ,
256
299
ast:: ClosureExpr ( c) => {
257
- let pipe_tok = c. param_list( ) . and_then( |it| it. pipe_token( ) ) ?. into( ) ;
258
- let c_infile = InFile :: new( file_id, c. into( ) ) ;
259
- let nav = NavigationTarget :: from_expr( db, c_infile, pipe_tok) ;
260
- return Some ( nav) ;
300
+ let pipe_tok = c. param_list( ) . and_then( |it| it. pipe_token( ) ) ?. text_range( ) ;
301
+ let closure_in_file = InFile :: new( file_id, c. into( ) ) ;
302
+ Some ( NavigationTarget :: from_expr( db, closure_in_file, Some ( pipe_tok) ) )
261
303
} ,
262
304
ast:: BlockExpr ( blk) => {
263
305
match blk. modifier( ) {
264
306
Some ( ast:: BlockModifier :: Async ( _) ) => {
265
- let async_tok = blk. async_token( ) ?. into( ) ;
266
- let blk_infile = InFile :: new( file_id, blk. into( ) ) ;
267
- let nav = NavigationTarget :: from_expr( db, blk_infile, async_tok) ;
268
- return Some ( nav) ;
307
+ let async_tok = blk. async_token( ) ?. text_range( ) ;
308
+ let blk_in_file = InFile :: new( file_id, blk. into( ) ) ;
309
+ Some ( NavigationTarget :: from_expr( db, blk_in_file, Some ( async_tok) ) )
269
310
} ,
270
- Some ( ast:: BlockModifier :: Try ( _) ) if token. kind( ) != T ![ return ] => {
271
- let try_tok = blk. try_token( ) ?. into( ) ;
272
- let blk_infile = InFile :: new( file_id, blk. into( ) ) ;
273
- let nav = NavigationTarget :: from_expr( db, blk_infile, try_tok) ;
274
- return Some ( nav) ;
311
+ Some ( ast:: BlockModifier :: Try ( _) ) if token_kind != T ![ return ] => {
312
+ let try_tok = blk. try_token( ) ?. text_range( ) ;
313
+ let blk_in_file = InFile :: new( file_id, blk. into( ) ) ;
314
+ Some ( NavigationTarget :: from_expr( db, blk_in_file, Some ( try_tok) ) )
275
315
} ,
276
- _ => { }
316
+ _ => None ,
277
317
}
278
318
} ,
279
- _ => { }
319
+ _ => None ,
280
320
}
281
321
}
282
- }
283
- None
284
- }
285
-
286
- sema. descend_into_macros ( DescendPreference :: None , token. clone ( ) )
287
- . into_iter ( )
288
- . filter_map ( |descended| find_exit_point ( sema, descended) )
322
+ } )
289
323
. flatten ( )
290
- . collect_vec ( )
291
- . into ( )
324
+ . collect_vec ( ) ;
325
+
326
+ Some ( navs)
292
327
}
293
328
294
- fn try_find_loop (
329
+ pub ( crate ) fn find_loops (
295
330
sema : & Semantics < ' _ , RootDatabase > ,
296
331
token : & SyntaxToken ,
297
- ) -> Option < Vec < NavigationTarget > > {
298
- fn find_break_point (
299
- sema : & Semantics < ' _ , RootDatabase > ,
300
- token : SyntaxToken ,
301
- label_matches : impl Fn ( Option < Label > ) -> bool ,
302
- ) -> Option < UpmappingResult < NavigationTarget > > {
303
- let db = sema. db ;
304
- let file_id = sema. hir_file_for ( & token. parent ( ) ?) ;
305
-
306
- for anc in sema. token_ancestors_with_macros ( token. clone ( ) ) . filter_map ( ast:: Expr :: cast) {
307
- match anc {
308
- ast:: Expr :: LoopExpr ( loop_) if label_matches ( loop_. label ( ) ) => {
309
- let expr = ast:: Expr :: LoopExpr ( loop_. clone ( ) ) ;
310
- let loop_tok = loop_. loop_token ( ) ?. into ( ) ;
311
- let nav = NavigationTarget :: from_expr ( db, InFile :: new ( file_id, expr) , loop_tok) ;
312
- return Some ( nav) ;
313
- }
314
- ast:: Expr :: WhileExpr ( while_) if label_matches ( while_. label ( ) ) => {
315
- let expr = ast:: Expr :: WhileExpr ( while_. clone ( ) ) ;
316
- let while_tok = while_. while_token ( ) ?. into ( ) ;
317
- let nav =
318
- NavigationTarget :: from_expr ( db, InFile :: new ( file_id, expr) , while_tok) ;
319
- return Some ( nav) ;
320
- }
321
- ast:: Expr :: ForExpr ( for_) if label_matches ( for_. label ( ) ) => {
322
- let expr = ast:: Expr :: ForExpr ( for_. clone ( ) ) ;
323
- let for_tok = for_. for_token ( ) ?. into ( ) ;
324
- let nav = NavigationTarget :: from_expr ( db, InFile :: new ( file_id, expr) , for_tok) ;
325
- return Some ( nav) ;
326
- }
327
- ast:: Expr :: BlockExpr ( blk)
328
- if blk. label ( ) . is_some ( ) && label_matches ( blk. label ( ) ) =>
329
- {
330
- let expr = ast:: Expr :: BlockExpr ( blk. clone ( ) ) ;
331
- let lbl = blk. label ( ) . unwrap ( ) . syntax ( ) . clone ( ) . into ( ) ;
332
- let nav = NavigationTarget :: from_expr ( db, InFile :: new ( file_id, expr) , lbl) ;
333
- return Some ( nav) ;
334
- }
335
- _ => { }
336
- }
337
- }
338
- None
339
- }
340
-
332
+ ) -> Option < Vec < ast:: Expr > > {
341
333
let parent = token. parent ( ) ?;
342
334
let lbl = match_ast ! {
343
335
match parent {
@@ -353,14 +345,60 @@ fn try_find_loop(
353
345
( Some ( _) , None ) => false ,
354
346
} ;
355
347
348
+ let find_ancestors = |token : SyntaxToken | {
349
+ for anc in sema. token_ancestors_with_macros ( token) . filter_map ( ast:: Expr :: cast) {
350
+ let node = match & anc {
351
+ ast:: Expr :: LoopExpr ( loop_) if label_matches ( loop_. label ( ) ) => anc,
352
+ ast:: Expr :: WhileExpr ( while_) if label_matches ( while_. label ( ) ) => anc,
353
+ ast:: Expr :: ForExpr ( for_) if label_matches ( for_. label ( ) ) => anc,
354
+ ast:: Expr :: BlockExpr ( blk)
355
+ if blk. label ( ) . is_some ( ) && label_matches ( blk. label ( ) ) =>
356
+ {
357
+ anc
358
+ }
359
+ _ => continue ,
360
+ } ;
361
+
362
+ return Some ( node) ;
363
+ }
364
+ None
365
+ } ;
366
+
356
367
sema. descend_into_macros ( DescendPreference :: None , token. clone ( ) )
357
368
. into_iter ( )
358
- . filter_map ( |descended| find_break_point ( sema, descended, label_matches) )
359
- . flatten ( )
369
+ . filter_map ( find_ancestors)
360
370
. collect_vec ( )
361
371
. into ( )
362
372
}
363
373
374
+ fn nav_for_break_points (
375
+ sema : & Semantics < ' _ , RootDatabase > ,
376
+ token : & SyntaxToken ,
377
+ ) -> Option < Vec < NavigationTarget > > {
378
+ let db = sema. db ;
379
+
380
+ let navs = find_loops ( sema, token) ?
381
+ . into_iter ( )
382
+ . filter_map ( |expr| {
383
+ let file_id = sema. hir_file_for ( expr. syntax ( ) ) ;
384
+ let expr_in_file = InFile :: new ( file_id, expr. clone ( ) ) ;
385
+ let focus_range = match expr {
386
+ ast:: Expr :: LoopExpr ( loop_) => loop_. loop_token ( ) ?. text_range ( ) ,
387
+ ast:: Expr :: WhileExpr ( while_) => while_. while_token ( ) ?. text_range ( ) ,
388
+ ast:: Expr :: ForExpr ( for_) => for_. for_token ( ) ?. text_range ( ) ,
389
+ // We garentee that the label exists
390
+ ast:: Expr :: BlockExpr ( blk) => blk. label ( ) . unwrap ( ) . syntax ( ) . text_range ( ) ,
391
+ _ => return None ,
392
+ } ;
393
+ let nav = NavigationTarget :: from_expr ( db, expr_in_file, Some ( focus_range) ) ;
394
+ Some ( nav)
395
+ } )
396
+ . flatten ( )
397
+ . collect_vec ( ) ;
398
+
399
+ Some ( navs)
400
+ }
401
+
364
402
fn def_to_nav ( db : & RootDatabase , def : Definition ) -> Vec < NavigationTarget > {
365
403
def. try_to_nav ( db) . map ( |it| it. collect ( ) ) . unwrap_or_default ( )
366
404
}
0 commit comments