rust/crates/ra_ide_api/src/goto_definition.rs

140 lines
4.2 KiB
Rust
Raw Normal View History

2019-01-08 20:33:36 +01:00
use ra_db::{FileId, Cancelable, SyntaxDatabase};
use ra_syntax::{
TextRange, AstNode, ast, SyntaxKind::{NAME, MODULE},
algo::find_node_at_offset,
};
use crate::{FilePosition, NavigationTarget, db::RootDatabase};
2019-01-08 23:38:51 +01:00
pub(crate) fn goto_definition(
2019-01-08 20:33:36 +01:00
db: &RootDatabase,
position: FilePosition,
) -> Cancelable<Option<Vec<NavigationTarget>>> {
let file = db.source_file(position.file_id);
let syntax = file.syntax();
if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(syntax, position.offset) {
2019-01-08 23:38:51 +01:00
return Ok(Some(reference_definition(db, position.file_id, name_ref)?));
2019-01-08 20:33:36 +01:00
}
if let Some(name) = find_node_at_offset::<ast::Name>(syntax, position.offset) {
2019-01-08 23:38:51 +01:00
return name_definition(db, position.file_id, name);
2019-01-08 20:33:36 +01:00
}
Ok(None)
}
2019-01-08 23:38:51 +01:00
pub(crate) fn reference_definition(
2019-01-08 20:33:36 +01:00
db: &RootDatabase,
file_id: FileId,
name_ref: &ast::NameRef,
) -> Cancelable<Vec<NavigationTarget>> {
if let Some(fn_descr) =
hir::source_binder::function_from_child_node(db, file_id, name_ref.syntax())?
{
let scope = fn_descr.scopes(db)?;
// First try to resolve the symbol locally
if let Some(entry) = scope.resolve_local_name(name_ref) {
let nav = NavigationTarget {
file_id,
name: entry.name().to_string().into(),
range: entry.ptr().range(),
kind: NAME,
ptr: None,
};
return Ok(vec![nav]);
};
}
// If that fails try the index based approach.
let navs = db
.index_resolve(name_ref)?
.into_iter()
.map(NavigationTarget::from_symbol)
.collect();
Ok(navs)
}
2019-01-08 23:38:51 +01:00
fn name_definition(
2019-01-08 20:33:36 +01:00
db: &RootDatabase,
file_id: FileId,
name: &ast::Name,
) -> Cancelable<Option<Vec<NavigationTarget>>> {
if let Some(module) = name.syntax().parent().and_then(ast::Module::cast) {
if module.has_semi() {
if let Some(child_module) =
hir::source_binder::module_from_declaration(db, file_id, module)?
{
2019-01-08 23:38:51 +01:00
let (file_id, _) = child_module.definition_source(db)?;
2019-01-08 20:33:36 +01:00
let name = match child_module.name(db)? {
Some(name) => name.to_string().into(),
None => "".into(),
};
let nav = NavigationTarget {
file_id,
name,
range: TextRange::offset_len(0.into(), 0.into()),
kind: MODULE,
ptr: None,
};
return Ok(Some(vec![nav]));
}
}
}
Ok(None)
}
#[cfg(test)]
mod tests {
use test_utils::assert_eq_dbg;
use crate::mock_analysis::analysis_and_position;
#[test]
2019-01-08 23:38:51 +01:00
fn goto_definition_works_in_items() {
2019-01-08 20:33:36 +01:00
let (analysis, pos) = analysis_and_position(
"
//- /lib.rs
struct Foo;
enum E { X(Foo<|>) }
",
);
2019-01-08 23:38:51 +01:00
let symbols = analysis.goto_definition(pos).unwrap().unwrap();
2019-01-08 20:33:36 +01:00
assert_eq_dbg(
r#"[NavigationTarget { file_id: FileId(1), name: "Foo",
kind: STRUCT_DEF, range: [0; 11),
ptr: Some(LocalSyntaxPtr { range: [0; 11), kind: STRUCT_DEF }) }]"#,
&symbols,
);
}
#[test]
2019-01-08 23:38:51 +01:00
fn goto_definition_works_for_module_declaration() {
2019-01-08 20:33:36 +01:00
let (analysis, pos) = analysis_and_position(
"
//- /lib.rs
mod <|>foo;
//- /foo.rs
// empty
",
);
2019-01-08 23:38:51 +01:00
let symbols = analysis.goto_definition(pos).unwrap().unwrap();
2019-01-08 20:33:36 +01:00
assert_eq_dbg(
r#"[NavigationTarget { file_id: FileId(2), name: "foo", kind: MODULE, range: [0; 0), ptr: None }]"#,
&symbols,
);
let (analysis, pos) = analysis_and_position(
"
//- /lib.rs
mod <|>foo;
//- /foo/mod.rs
// empty
",
);
2019-01-08 23:38:51 +01:00
let symbols = analysis.goto_definition(pos).unwrap().unwrap();
2019-01-08 20:33:36 +01:00
assert_eq_dbg(
r#"[NavigationTarget { file_id: FileId(2), name: "foo", kind: MODULE, range: [0; 0), ptr: None }]"#,
&symbols,
);
}
}