diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index ed9db32631..a0008f4c8f 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -2,10 +2,10 @@ use std::{iter, mem::discriminant}; use crate::{ doc_links::token_as_doc_comment, navigation_target::ToNav, FilePosition, NavigationTarget, - RangeInfo, TryToNav, UpmappingResult, + RangeInfo, TryToNav, }; use hir::{ - AsAssocItem, AssocItem, DescendPreference, InFile, MacroFileIdExt, ModuleDef, Semantics, + AsAssocItem, AssocItem, DescendPreference, FileRange, InFile, MacroFileIdExt, ModuleDef, Semantics }; use ide_db::{ base_db::{AnchoredPath, FileLoader}, @@ -14,11 +14,12 @@ use ide_db::{ FileId, RootDatabase, }; use itertools::Itertools; + use syntax::{ - ast::{self, HasLoopBody, Label}, + ast::{self, HasLoopBody}, match_ast, AstNode, AstToken, SyntaxKind::*, - SyntaxToken, TextRange, T, + SyntaxNode, SyntaxToken, TextRange, T, }; // Feature: Go to Definition @@ -208,136 +209,127 @@ fn handle_control_flow_keywords( match token.kind() { // For `fn` / `loop` / `while` / `for` / `async`, return the keyword it self, // so that VSCode will find the references when using `ctrl + click` - T![fn] | T![async] | T![try] | T![return] => try_find_fn_or_closure(sema, token), - T![loop] | T![while] | T![break] | T![continue] => try_find_loop(sema, token), + T![fn] | T![async] | T![try] | T![return] => nav_for_exit_points(sema, token), + T![loop] | T![while] | T![break] | T![continue] => nav_for_break_points(sema, token), T![for] if token.parent().and_then(ast::ForExpr::cast).is_some() => { - try_find_loop(sema, token) + nav_for_break_points(sema, token) } _ => None, } } -fn try_find_fn_or_closure( +pub(crate) fn find_fn_or_blocks( + sema: &Semantics<'_, RootDatabase>, + token: &SyntaxToken, +) -> Vec { + let find_ancestors = |token: SyntaxToken| { + let token_kind = token.kind(); + + for anc in sema.token_ancestors_with_macros(token) { + let node = match_ast! { + match anc { + ast::Fn(fn_) => fn_.syntax().clone(), + ast::ClosureExpr(c) => c.syntax().clone(), + ast::BlockExpr(blk) => { + match blk.modifier() { + Some(ast::BlockModifier::Async(_)) => blk.syntax().clone(), + Some(ast::BlockModifier::Try(_)) if token_kind != T![return] => blk.syntax().clone(), + _ => continue, + } + }, + _ => continue, + } + }; + + return Some(node); + } + None + }; + + sema.descend_into_macros(DescendPreference::None, token.clone()) + .into_iter() + .filter_map(find_ancestors) + .collect_vec() +} + +fn nav_for_exit_points( sema: &Semantics<'_, RootDatabase>, token: &SyntaxToken, ) -> Option> { - fn find_exit_point( - sema: &Semantics<'_, RootDatabase>, - token: SyntaxToken, - ) -> Option> { - let db = sema.db; + let db = sema.db; + let token_kind = token.kind(); + + let navs = find_fn_or_blocks(sema, token) + .into_iter() + .filter_map(|node| { + let file_id = sema.hir_file_for(&node); - for anc in sema.token_ancestors_with_macros(token.clone()) { - let file_id = sema.hir_file_for(&anc); match_ast! { - match anc { + match node { ast::Fn(fn_) => { - let fn_: ast::Fn = fn_; - let nav = sema.to_def(&fn_)?.try_to_nav(db)?; + let mut nav = sema.to_def(&fn_)?.try_to_nav(db)?; // For async token, we navigate to itself, which triggers // VSCode to find the references - let focus_token = if matches!(token.kind(), T![async]) { + let focus_token = if matches!(token_kind, T![async]) { fn_.async_token()? } else { fn_.fn_token()? }; - let focus_range = InFile::new(file_id, focus_token.text_range()) - .original_node_file_range_opt(db) - .map(|(frange, _)| frange.range); - return Some(nav.map(|it| { - if focus_range.is_some_and(|range| it.full_range.contains_range(range)) { - NavigationTarget { focus_range, ..it } - } else { - it + let focus_frange = InFile::new(file_id, focus_token.text_range()) + .original_node_file_range_opt(db) + .map(|(frange, _)| frange); + + if let Some(FileRange { file_id, range }) = focus_frange { + let contains_frange = |nav: &NavigationTarget| { + nav.file_id == file_id && nav.full_range.contains_range(range) + }; + + if let Some(def_site) = nav.def_site.as_mut() { + if contains_frange(def_site) { + def_site.focus_range = Some(range); + } + } else if contains_frange(&nav.call_site) { + nav.call_site.focus_range = Some(range); } - })); + } + + Some(nav) }, ast::ClosureExpr(c) => { - let pipe_tok = c.param_list().and_then(|it| it.pipe_token())?.into(); - let c_infile = InFile::new(file_id, c.into()); - let nav = NavigationTarget::from_expr(db, c_infile, pipe_tok); - return Some(nav); + let pipe_tok = c.param_list().and_then(|it| it.pipe_token())?.text_range(); + let closure_in_file = InFile::new(file_id, c.into()); + Some(NavigationTarget::from_expr(db, closure_in_file, Some(pipe_tok))) }, ast::BlockExpr(blk) => { match blk.modifier() { Some(ast::BlockModifier::Async(_)) => { - let async_tok = blk.async_token()?.into(); - let blk_infile = InFile::new(file_id, blk.into()); - let nav = NavigationTarget::from_expr(db, blk_infile, async_tok); - return Some(nav); + let async_tok = blk.async_token()?.text_range(); + let blk_in_file = InFile::new(file_id, blk.into()); + Some(NavigationTarget::from_expr(db, blk_in_file, Some(async_tok))) }, - Some(ast::BlockModifier::Try(_)) if token.kind() != T![return] => { - let try_tok = blk.try_token()?.into(); - let blk_infile = InFile::new(file_id, blk.into()); - let nav = NavigationTarget::from_expr(db, blk_infile, try_tok); - return Some(nav); + Some(ast::BlockModifier::Try(_)) if token_kind != T![return] => { + let try_tok = blk.try_token()?.text_range(); + let blk_in_file = InFile::new(file_id, blk.into()); + Some(NavigationTarget::from_expr(db, blk_in_file, Some(try_tok))) }, - _ => {} + _ => None, } }, - _ => {} + _ => None, } } - } - None - } - - sema.descend_into_macros(DescendPreference::None, token.clone()) - .into_iter() - .filter_map(|descended| find_exit_point(sema, descended)) + }) .flatten() - .collect_vec() - .into() + .collect_vec(); + + Some(navs) } -fn try_find_loop( +pub(crate) fn find_loops( sema: &Semantics<'_, RootDatabase>, token: &SyntaxToken, -) -> Option> { - fn find_break_point( - sema: &Semantics<'_, RootDatabase>, - token: SyntaxToken, - label_matches: impl Fn(Option