@@ -2,10 +2,10 @@ use std::{iter, mem::discriminant};
22
33use crate :: {
44 doc_links:: token_as_doc_comment, navigation_target:: ToNav , FilePosition , NavigationTarget ,
5- RangeInfo , TryToNav , UpmappingResult ,
5+ RangeInfo , TryToNav ,
66} ;
77use hir:: {
8- AsAssocItem , AssocItem , DescendPreference , InFile , MacroFileIdExt , ModuleDef , Semantics ,
8+ AsAssocItem , AssocItem , DescendPreference , FileRange , InFile , MacroFileIdExt , ModuleDef , Semantics
99} ;
1010use ide_db:: {
1111 base_db:: { AnchoredPath , FileLoader } ,
@@ -14,11 +14,12 @@ use ide_db::{
1414 FileId , RootDatabase ,
1515} ;
1616use itertools:: Itertools ;
17+
1718use syntax:: {
18- ast:: { self , HasLoopBody , Label } ,
19+ ast:: { self , HasLoopBody } ,
1920 match_ast, AstNode , AstToken ,
2021 SyntaxKind :: * ,
21- SyntaxToken , TextRange , T ,
22+ SyntaxNode , SyntaxToken , TextRange , T ,
2223} ;
2324
2425// Feature: Go to Definition
@@ -208,136 +209,127 @@ fn handle_control_flow_keywords(
208209 match token. kind ( ) {
209210 // For `fn` / `loop` / `while` / `for` / `async`, return the keyword it self,
210211 // 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) ,
213214 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)
215216 }
216217 _ => None ,
217218 }
218219}
219220
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 (
221256 sema : & Semantics < ' _ , RootDatabase > ,
222257 token : & SyntaxToken ,
223258) -> 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+
232267 match_ast ! {
233- match anc {
268+ match node {
234269 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) ?;
237271 // For async token, we navigate to itself, which triggers
238272 // 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 ] ) {
240274 fn_. async_token( ) ?
241275 } else {
242276 fn_. fn_token( ) ?
243277 } ;
244278
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) ;
253294 }
254- } ) ) ;
295+ }
296+
297+ Some ( nav)
255298 } ,
256299 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) ) )
261303 } ,
262304 ast:: BlockExpr ( blk) => {
263305 match blk. modifier( ) {
264306 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) ) )
269310 } ,
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) ) )
275315 } ,
276- _ => { }
316+ _ => None ,
277317 }
278318 } ,
279- _ => { }
319+ _ => None ,
280320 }
281321 }
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+ } )
289323 . flatten ( )
290- . collect_vec ( )
291- . into ( )
324+ . collect_vec ( ) ;
325+
326+ Some ( navs)
292327}
293328
294- fn try_find_loop (
329+ pub ( crate ) fn find_loops (
295330 sema : & Semantics < ' _ , RootDatabase > ,
296331 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 > > {
341333 let parent = token. parent ( ) ?;
342334 let lbl = match_ast ! {
343335 match parent {
@@ -353,14 +345,60 @@ fn try_find_loop(
353345 ( Some ( _) , None ) => false ,
354346 } ;
355347
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+
356367 sema. descend_into_macros ( DescendPreference :: None , token. clone ( ) )
357368 . into_iter ( )
358- . filter_map ( |descended| find_break_point ( sema, descended, label_matches) )
359- . flatten ( )
369+ . filter_map ( find_ancestors)
360370 . collect_vec ( )
361371 . into ( )
362372}
363373
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+
364402fn def_to_nav ( db : & RootDatabase , def : Definition ) -> Vec < NavigationTarget > {
365403 def. try_to_nav ( db) . map ( |it| it. collect ( ) ) . unwrap_or_default ( )
366404}
0 commit comments