mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-29 14:33:29 +00:00
feat: goto-def on keywords
This commit is contained in:
parent
aa4768f7be
commit
37085d9dcd
2 changed files with 419 additions and 7 deletions
|
@ -2,9 +2,12 @@ use std::{iter, mem::discriminant};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
doc_links::token_as_doc_comment, navigation_target::ToNav, FilePosition, NavigationTarget,
|
doc_links::token_as_doc_comment, navigation_target::ToNav, FilePosition, NavigationTarget,
|
||||||
RangeInfo, TryToNav,
|
RangeInfo, TryToNav, UpmappingResult,
|
||||||
|
};
|
||||||
|
use hir::{
|
||||||
|
AsAssocItem, AssocItem, DescendPreference, HirFileId, InFile, MacroFileIdExt, ModuleDef,
|
||||||
|
Semantics,
|
||||||
};
|
};
|
||||||
use hir::{AsAssocItem, AssocItem, DescendPreference, MacroFileIdExt, ModuleDef, Semantics};
|
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
base_db::{AnchoredPath, FileLoader},
|
base_db::{AnchoredPath, FileLoader},
|
||||||
defs::{Definition, IdentClass},
|
defs::{Definition, IdentClass},
|
||||||
|
@ -12,7 +15,12 @@ use ide_db::{
|
||||||
FileId, RootDatabase,
|
FileId, RootDatabase,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use syntax::{ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextRange, T};
|
use syntax::{
|
||||||
|
ast::{self, HasLoopBody},
|
||||||
|
match_ast, AstNode, AstToken,
|
||||||
|
SyntaxKind::{self, *},
|
||||||
|
SyntaxNode, SyntaxToken, TextRange, T,
|
||||||
|
};
|
||||||
|
|
||||||
// Feature: Go to Definition
|
// Feature: Go to Definition
|
||||||
//
|
//
|
||||||
|
@ -68,6 +76,10 @@ pub(crate) fn goto_definition(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(navs) = handle_control_flow_keywords(sema, &original_token) {
|
||||||
|
return Some(RangeInfo::new(original_token.text_range(), navs));
|
||||||
|
}
|
||||||
|
|
||||||
let navs = sema
|
let navs = sema
|
||||||
.descend_into_macros(DescendPreference::None, original_token.clone())
|
.descend_into_macros(DescendPreference::None, original_token.clone())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -190,6 +202,194 @@ fn try_filter_trait_item_definition(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_control_flow_keywords(
|
||||||
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
|
token: &SyntaxToken,
|
||||||
|
) -> Option<Vec<NavigationTarget>> {
|
||||||
|
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![for] if token.parent().and_then(ast::ForExpr::cast).is_some() => {
|
||||||
|
try_find_loop(sema, token)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_find_fn_or_closure(
|
||||||
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
|
token: &SyntaxToken,
|
||||||
|
) -> Option<Vec<NavigationTarget>> {
|
||||||
|
fn find_exit_point(
|
||||||
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
|
file_id: HirFileId,
|
||||||
|
ancestors: impl Iterator<Item = SyntaxNode>,
|
||||||
|
) -> Option<UpmappingResult<NavigationTarget>> {
|
||||||
|
let db = sema.db;
|
||||||
|
|
||||||
|
for anc in ancestors {
|
||||||
|
match_ast! {
|
||||||
|
match anc {
|
||||||
|
ast::Fn(fn_) => {
|
||||||
|
let hir_fn: hir::Function = sema.to_def(&fn_)?;
|
||||||
|
let nav = hir_fn.try_to_nav(db)?;
|
||||||
|
|
||||||
|
// For async token, we navigate to itself, which triggers
|
||||||
|
// VSCode to find the references
|
||||||
|
let focus_token = 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
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
ast::ClosureExpr(c) => {
|
||||||
|
let pipe_tok = c.param_list().and_then(|it| it.pipe_token())?.into();
|
||||||
|
let nav = NavigationTarget::from_expr(db, InFile::new(file_id, c.into()), pipe_tok);
|
||||||
|
return Some(nav);
|
||||||
|
},
|
||||||
|
ast::BlockExpr(blk) => match blk.modifier() {
|
||||||
|
Some(ast::BlockModifier::Async(_)) => {
|
||||||
|
let async_tok = blk.async_token()?.into();
|
||||||
|
let nav = NavigationTarget::from_expr(db, InFile::new(file_id, blk.into()), async_tok);
|
||||||
|
return Some(nav);
|
||||||
|
},
|
||||||
|
Some(ast::BlockModifier::Try(_)) if cursor_token_kind != T![return] => {
|
||||||
|
let try_tok = blk.try_token()?.into();
|
||||||
|
let nav = NavigationTarget::from_expr(db, InFile::new(file_id, blk.into()), try_tok);
|
||||||
|
return Some(nav);
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
sema.descend_into_macros(DescendPreference::None, token.clone())
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|descended| {
|
||||||
|
let file_id = sema.hir_file_for(&descended.parent()?);
|
||||||
|
|
||||||
|
// Try to find the function in the macro file
|
||||||
|
find_exit_point(sema, file_id, descended.parent_ancestors()).or_else(|| {
|
||||||
|
// If not found, try to find it in the root file
|
||||||
|
if file_id.is_macro() {
|
||||||
|
token
|
||||||
|
.parent_ancestors()
|
||||||
|
.find(|it| ast::TokenTree::can_cast(it.kind()))
|
||||||
|
.and_then(|parent| {
|
||||||
|
let file_id = sema.hir_file_for(&parent);
|
||||||
|
find_exit_point(sema, file_id, parent.ancestors())
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect_vec()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_find_loop(
|
||||||
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
|
token: &SyntaxToken,
|
||||||
|
) -> Option<Vec<NavigationTarget>> {
|
||||||
|
fn find_break_point(
|
||||||
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
|
file_id: HirFileId,
|
||||||
|
ancestors: impl Iterator<Item = SyntaxNode>,
|
||||||
|
lbl: &Option<ast::Lifetime>,
|
||||||
|
) -> Option<UpmappingResult<NavigationTarget>> {
|
||||||
|
let db = sema.db;
|
||||||
|
let label_matches = |it: Option<ast::Label>| match lbl {
|
||||||
|
Some(lbl) => {
|
||||||
|
Some(lbl.text()) == it.and_then(|it| it.lifetime()).as_ref().map(|it| it.text())
|
||||||
|
}
|
||||||
|
None => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
for anc in ancestors.filter_map(ast::Expr::cast) {
|
||||||
|
match anc {
|
||||||
|
ast::Expr::LoopExpr(loop_) if label_matches(loop_.label()) => {
|
||||||
|
let expr = ast::Expr::LoopExpr(loop_.clone());
|
||||||
|
let loop_tok = loop_.loop_token()?.into();
|
||||||
|
let nav = NavigationTarget::from_expr(db, InFile::new(file_id, expr), loop_tok);
|
||||||
|
return Some(nav);
|
||||||
|
}
|
||||||
|
ast::Expr::WhileExpr(while_) if label_matches(while_.label()) => {
|
||||||
|
let expr = ast::Expr::WhileExpr(while_.clone());
|
||||||
|
let while_tok = while_.while_token()?.into();
|
||||||
|
let nav =
|
||||||
|
NavigationTarget::from_expr(db, InFile::new(file_id, expr), while_tok);
|
||||||
|
return Some(nav);
|
||||||
|
}
|
||||||
|
ast::Expr::ForExpr(for_) if label_matches(for_.label()) => {
|
||||||
|
let expr = ast::Expr::ForExpr(for_.clone());
|
||||||
|
let for_tok = for_.for_token()?.into();
|
||||||
|
let nav = NavigationTarget::from_expr(db, InFile::new(file_id, expr), for_tok);
|
||||||
|
return Some(nav);
|
||||||
|
}
|
||||||
|
ast::Expr::BlockExpr(blk)
|
||||||
|
if blk.label().is_some() && label_matches(blk.label()) =>
|
||||||
|
{
|
||||||
|
let expr = ast::Expr::BlockExpr(blk.clone());
|
||||||
|
let lbl_tok = blk.label().unwrap().lifetime()?.lifetime_ident_token()?.into();
|
||||||
|
let nav = NavigationTarget::from_expr(db, InFile::new(file_id, expr), lbl_tok);
|
||||||
|
return Some(nav);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent = token.parent()?;
|
||||||
|
let lbl = match_ast! {
|
||||||
|
match parent {
|
||||||
|
ast::BreakExpr(break_) => break_.lifetime(),
|
||||||
|
ast::ContinueExpr(continue_) => continue_.lifetime(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sema.descend_into_macros(DescendPreference::None, token.clone())
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|descended| {
|
||||||
|
let file_id = sema.hir_file_for(&descended.parent()?);
|
||||||
|
|
||||||
|
// Try to find the function in the macro file
|
||||||
|
find_break_point(sema, file_id, descended.parent_ancestors(), &lbl).or_else(|| {
|
||||||
|
// If not found, try to find it in the root file
|
||||||
|
if file_id.is_macro() {
|
||||||
|
token
|
||||||
|
.parent_ancestors()
|
||||||
|
.find(|it| ast::TokenTree::can_cast(it.kind()))
|
||||||
|
.and_then(|parent| {
|
||||||
|
let file_id = sema.hir_file_for(&parent);
|
||||||
|
find_break_point(sema, file_id, parent.ancestors(), &lbl)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect_vec()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
fn def_to_nav(db: &RootDatabase, def: Definition) -> Vec<NavigationTarget> {
|
fn def_to_nav(db: &RootDatabase, def: Definition) -> Vec<NavigationTarget> {
|
||||||
def.try_to_nav(db).map(|it| it.collect()).unwrap_or_default()
|
def.try_to_nav(db).map(|it| it.collect()).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
@ -2313,4 +2513,200 @@ pub mod prelude {
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn goto_def_on_return_kw() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
macro_rules! N {
|
||||||
|
($i:ident, $x:expr, $blk:expr) => {
|
||||||
|
for $i in 0..$x {
|
||||||
|
$blk
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
fn f() {
|
||||||
|
// ^^
|
||||||
|
N!(i, 5, {
|
||||||
|
println!("{}", i);
|
||||||
|
return$0;
|
||||||
|
});
|
||||||
|
|
||||||
|
for i in 1..5 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(|| {
|
||||||
|
return;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn goto_def_on_return_kw_in_closure() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
macro_rules! N {
|
||||||
|
($i:ident, $x:expr, $blk:expr) => {
|
||||||
|
for $i in 0..$x {
|
||||||
|
$blk
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
fn f() {
|
||||||
|
N!(i, 5, {
|
||||||
|
println!("{}", i);
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
|
for i in 1..5 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(|| {
|
||||||
|
// ^
|
||||||
|
return$0;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn goto_def_on_break_kw() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
for i in 1..5 {
|
||||||
|
// ^^^
|
||||||
|
break$0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn goto_def_on_continue_kw() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
for i in 1..5 {
|
||||||
|
// ^^^
|
||||||
|
continue$0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn goto_def_on_break_kw_for_block() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
'a:{
|
||||||
|
// ^^
|
||||||
|
break$0 'a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn goto_def_on_break_with_label() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn foo() {
|
||||||
|
'outer: loop {
|
||||||
|
// ^^^^
|
||||||
|
'inner: loop {
|
||||||
|
'innermost: loop {
|
||||||
|
}
|
||||||
|
break$0 'outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn goto_def_on_return_in_try() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
fn f() {
|
||||||
|
// ^^
|
||||||
|
try {
|
||||||
|
return$0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn goto_def_on_break_in_try() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
for i in 1..100 {
|
||||||
|
// ^^^
|
||||||
|
let x: Result<(), ()> = try {
|
||||||
|
break$0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn goto_def_on_return_in_async_block() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
async {
|
||||||
|
// ^^^^^
|
||||||
|
return$0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn goto_def_on_for_kw() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
for$0 i in 1..5 {}
|
||||||
|
// ^^^
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn goto_def_on_fn_kw() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
fn$0 foo() {}
|
||||||
|
// ^^
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ use ide_db::{
|
||||||
use stdx::never;
|
use stdx::never;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{self, HasName},
|
ast::{self, HasName},
|
||||||
format_smolstr, AstNode, SmolStr, SyntaxNode, TextRange, ToSmolStr,
|
format_smolstr, AstNode, SmolStr, SyntaxElement, SyntaxNode, TextRange, ToSmolStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// `NavigationTarget` represents an element in the editor's UI which you can
|
/// `NavigationTarget` represents an element in the editor's UI which you can
|
||||||
|
@ -152,6 +152,22 @@ impl NavigationTarget {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_expr(
|
||||||
|
db: &RootDatabase,
|
||||||
|
InFile { file_id, value }: InFile<ast::Expr>,
|
||||||
|
focus_syntax: SyntaxElement,
|
||||||
|
) -> UpmappingResult<NavigationTarget> {
|
||||||
|
let name: SmolStr = "<expr>".into();
|
||||||
|
let kind = SymbolKind::Label;
|
||||||
|
let focus_range = Some(focus_syntax.text_range());
|
||||||
|
|
||||||
|
orig_range_with_focus_r(db, file_id, value.syntax().text_range(), focus_range).map(
|
||||||
|
|(FileRange { file_id, range: full_range }, focus_range)| {
|
||||||
|
NavigationTarget::from_syntax(file_id, name.clone(), focus_range, full_range, kind)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn from_syntax(
|
fn from_syntax(
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
name: SmolStr,
|
name: SmolStr,
|
||||||
|
@ -710,7 +726,7 @@ impl<T> IntoIterator for UpmappingResult<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> UpmappingResult<T> {
|
impl<T> UpmappingResult<T> {
|
||||||
fn map<U>(self, f: impl Fn(T) -> U) -> UpmappingResult<U> {
|
pub(crate) fn map<U>(self, f: impl Fn(T) -> U) -> UpmappingResult<U> {
|
||||||
UpmappingResult { call_site: f(self.call_site), def_site: self.def_site.map(f) }
|
UpmappingResult { call_site: f(self.call_site), def_site: self.def_site.map(f) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -736,9 +752,9 @@ fn orig_range_with_focus_r(
|
||||||
db: &RootDatabase,
|
db: &RootDatabase,
|
||||||
hir_file: HirFileId,
|
hir_file: HirFileId,
|
||||||
value: TextRange,
|
value: TextRange,
|
||||||
name: Option<TextRange>,
|
focus_range: Option<TextRange>,
|
||||||
) -> UpmappingResult<(FileRange, Option<TextRange>)> {
|
) -> UpmappingResult<(FileRange, Option<TextRange>)> {
|
||||||
let Some(name) = name else { return orig_range_r(db, hir_file, value) };
|
let Some(name) = focus_range else { return orig_range_r(db, hir_file, value) };
|
||||||
|
|
||||||
let call_kind =
|
let call_kind =
|
||||||
|| db.lookup_intern_macro_call(hir_file.macro_file().unwrap().macro_call_id).kind;
|
|| db.lookup_intern_macro_call(hir_file.macro_file().unwrap().macro_call_id).kind;
|
||||||
|
|
Loading…
Reference in a new issue