From 5a74e93c334506a8b865b6b03b2d4cd4a94d6973 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 22 Jun 2021 20:43:48 +0200 Subject: [PATCH] Implement goto_declaration support --- crates/ide/src/goto_declaration.rs | 107 ++++++++++++++++++++++++++ crates/ide/src/goto_definition.rs | 2 +- crates/ide/src/lib.rs | 9 +++ crates/rust-analyzer/src/caps.rs | 4 +- crates/rust-analyzer/src/handlers.rs | 15 ++++ crates/rust-analyzer/src/main_loop.rs | 1 + 6 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 crates/ide/src/goto_declaration.rs diff --git a/crates/ide/src/goto_declaration.rs b/crates/ide/src/goto_declaration.rs new file mode 100644 index 00000000000..fb23efdc824 --- /dev/null +++ b/crates/ide/src/goto_declaration.rs @@ -0,0 +1,107 @@ +use hir::Semantics; +use ide_db::{ + defs::{Definition, NameClass, NameRefClass}, + RootDatabase, +}; +use syntax::{ast, match_ast, AstNode, SyntaxKind::*, T}; + +use crate::{goto_definition, FilePosition, NavigationTarget, RangeInfo}; + +// Feature: Go to Declaration +// +// Navigates to the declaration of an identifier. This is the same as the definition except for +// modules where this goes to the identifier of the declaration instead of the contents. +pub(crate) fn goto_declaration( + db: &RootDatabase, + position: FilePosition, +) -> Option>> { + let sema = Semantics::new(db); + let file = sema.parse(position.file_id).syntax().clone(); + let res = (|| { + // try + let original_token = file + .token_at_offset(position.offset) + .find(|it| matches!(it.kind(), IDENT | T![self] | T![super] | T![crate]))?; + let token = sema.descend_into_macros(original_token.clone()); + let parent = token.parent()?; + let def = match_ast! { + match parent { + ast::NameRef(name_ref) => { + let name_kind = NameRefClass::classify(&sema, &name_ref)?; + name_kind.referenced(sema.db) + }, + ast::Name(name) => { + NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db) + }, + _ => return None, + } + }; + match def { + Definition::ModuleDef(hir::ModuleDef::Module(module)) => Some(RangeInfo::new( + original_token.text_range(), + vec![NavigationTarget::from_module_to_decl(db, module)], + )), + _ => return None, + } + })(); + res.or_else(|| goto_definition::goto_definition(db, position)) +} + +#[cfg(test)] +mod tests { + use ide_db::base_db::FileRange; + + use crate::fixture; + + fn check(ra_fixture: &str) { + let (analysis, position, expected) = fixture::nav_target_annotation(ra_fixture); + let mut navs = analysis + .goto_declaration(position) + .unwrap() + .expect("no declaration or definition found") + .info; + if navs.len() == 0 { + panic!("unresolved reference") + } + assert_eq!(navs.len(), 1); + + let nav = navs.pop().unwrap(); + assert_eq!(expected, FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() }); + } + + #[test] + fn goto_decl_module_outline() { + check( + r#" +//- /main.rs +mod foo; + // ^^^ +//- /foo.rs +use self$0; +"#, + ) + } + + #[test] + fn goto_decl_module_inline() { + check( + r#" +mod foo { + // ^^^ + use self$0; +} +"#, + ) + } + + #[test] + fn goto_decl_falls_back_to_goto_def() { + check( + r#" +struct Foo; + // ^^^ +use self::Foo$0; +"#, + ) + } +} diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index df6c35780c9..0887af2ae33 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -35,7 +35,7 @@ pub(crate) fn goto_definition( let file = sema.parse(position.file_id).syntax().clone(); let original_token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind { - IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | COMMENT => 2, + IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] | COMMENT => 2, kind if kind.is_trivia() => 0, _ => 1, })?; diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index ca14533f388..38606e09743 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -28,6 +28,7 @@ mod expand_macro; mod extend_selection; mod file_structure; mod folding_ranges; +mod goto_declaration; mod goto_definition; mod goto_implementation; mod goto_type_definition; @@ -374,6 +375,14 @@ impl Analysis { self.with_db(|db| goto_definition::goto_definition(db, position)) } + /// Returns the declaration from the symbol at `position`. + pub fn goto_declaration( + &self, + position: FilePosition, + ) -> Cancellable>>> { + self.with_db(|db| goto_declaration::goto_declaration(db, position)) + } + /// Returns the impls from the symbol at `position`. pub fn goto_implementation( &self, diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs index fe5255240d3..ddee4750408 100644 --- a/crates/rust-analyzer/src/caps.rs +++ b/crates/rust-analyzer/src/caps.rs @@ -1,7 +1,7 @@ //! Advertises the capabilities of the LSP Server. use lsp_types::{ CallHierarchyServerCapability, ClientCapabilities, CodeActionKind, CodeActionOptions, - CodeActionProviderCapability, CodeLensOptions, CompletionOptions, + CodeActionProviderCapability, CodeLensOptions, CompletionOptions, DeclarationCapability, DocumentOnTypeFormattingOptions, FileOperationFilter, FileOperationPattern, FileOperationPatternKind, FileOperationRegistrationOptions, FoldingRangeProviderCapability, HoverProviderCapability, ImplementationProviderCapability, OneOf, RenameOptions, SaveOptions, @@ -38,7 +38,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities { retrigger_characters: None, work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None }, }), - declaration_provider: None, + declaration_provider: Some(DeclarationCapability::Simple(true)), definition_provider: Some(OneOf::Left(true)), type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)), implementation_provider: Some(ImplementationProviderCapability::Simple(true)), diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index dcead5f5c3a..3e3a6fc6a52 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -556,6 +556,21 @@ pub(crate) fn handle_goto_definition( Ok(Some(res)) } +pub(crate) fn handle_goto_declaration( + snap: GlobalStateSnapshot, + params: lsp_types::request::GotoDeclarationParams, +) -> Result> { + let _p = profile::span("handle_goto_declaration"); + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let nav_info = match snap.analysis.goto_declaration(position)? { + None => return Ok(None), + Some(it) => it, + }; + let src = FileRange { file_id: position.file_id, range: nav_info.range }; + let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?; + Ok(Some(res)) +} + pub(crate) fn handle_goto_implementation( snap: GlobalStateSnapshot, params: lsp_types::request::GotoImplementationParams, diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 1417d837977..82a31362af6 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -531,6 +531,7 @@ impl GlobalState { .on::(handlers::handle_on_type_formatting) .on::(handlers::handle_document_symbol) .on::(handlers::handle_goto_definition) + .on::(handlers::handle_goto_declaration) .on::(handlers::handle_goto_implementation) .on::(handlers::handle_goto_type_definition) .on::(handlers::handle_completion)