From 7b5fdcee7fb8f1500eaaec01d0d4de8e823a1ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Coss=C3=ADo?= Date: Tue, 6 Aug 2024 05:20:51 -0400 Subject: [PATCH] lsp: Support Goto Declaration (#15785) Adds support for [Goto Declaration](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_declaration) LSP command. I am particularly interested in [this for Rust projects](https://rust-analyzer.github.io/manual.html#go-to-declaration), to be able to navigate to the place where a trait method is declared, coming from a trait method implementation. I noticed this was something I could do in VSCode before, but was somehow missing is Zed. Thanks to the already existing infrastructure for Goto Definition, I just followed and copy-paste-adapted it for Goto Declaration. As a bonus, I added `ctrl-F12` and `alt-ctrl-F12` as default macOS keybindings for `GoToDeclaration` and `GoToDeclarationSplit`, respectively. They are not keybindings from another editor, but I figured they made sense to be grouped along with the other *F12 commands. ### Release Notes: - Added "Go to declaration" editor action. - vim: Breaking change to keybindings after introduction of the `Go to declaration` editor action. The new keybindings are the following (and can be found [here](https://zed.dev/docs/vim), alongside the other key bindings): - `g d` - Go to definition - `g D` - Go to declaration - `g y` - Go to type definition - `g I` - Go to implementation https://github.com/user-attachments/assets/ee5c10a8-94f0-4e50-afbb-6f71db540c1b --------- Co-authored-by: Thorsten Ball --- assets/keymaps/default-macos.json | 4 +- assets/keymaps/vim.json | 5 +- crates/editor/src/actions.rs | 2 + crates/editor/src/editor.rs | 18 ++++ crates/editor/src/element.rs | 6 ++ crates/editor/src/mouse_context_menu.rs | 2 + crates/project/src/lsp_command.rs | 104 ++++++++++++++++++++++++ crates/project/src/project.rs | 25 ++++++ crates/proto/proto/zed.proto | 15 +++- crates/proto/src/proto.rs | 4 + crates/zed/src/zed/app_menus.rs | 1 + docs/src/key-bindings.md | 2 + docs/src/vim.md | 6 +- 13 files changed, 188 insertions(+), 6 deletions(-) diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index fccf287a55..f2c417157b 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -127,7 +127,9 @@ "cmd-'": "editor::ToggleHunkDiff", "cmd-\"": "editor::ExpandAllHunkDiffs", "cmd-alt-g b": "editor::ToggleGitBlame", - "cmd-i": "editor::ShowSignatureHelp" + "cmd-i": "editor::ShowSignatureHelp", + "ctrl-f12": "editor::GoToDeclaration", + "alt-ctrl-f12": "editor::GoToDeclarationSplit" } }, { diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 1458645903..12b6a21730 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -89,8 +89,9 @@ "g t": "pane::ActivateNextItem", "g shift-t": "pane::ActivatePrevItem", "g d": "editor::GoToDefinition", - "g shift-d": "editor::GoToTypeDefinition", - "g cmd-d": "editor::GoToImplementation", + "g shift-d": "editor::GoToDeclaration", + "g y": "editor::GoToTypeDefinition", + "g shift-i": "editor::GoToImplementation", "g x": "editor::OpenUrl", "g n": "vim::SelectNextMatch", "g shift-n": "vim::SelectPreviousMatch", diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index d6b3403da7..d7ea183a97 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -210,6 +210,8 @@ gpui::actions!( Format, GoToDefinition, GoToDefinitionSplit, + GoToDeclaration, + GoToDeclarationSplit, GoToDiagnostic, GoToHunk, GoToImplementation, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 4908fa57ed..6d973e29e4 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1539,6 +1539,7 @@ pub(crate) struct NavigationData { enum GotoDefinitionKind { Symbol, + Declaration, Type, Implementation, } @@ -8948,6 +8949,22 @@ impl Editor { self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx) } + pub fn go_to_declaration( + &mut self, + _: &GoToDeclaration, + cx: &mut ViewContext, + ) -> Task> { + self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, cx) + } + + pub fn go_to_declaration_split( + &mut self, + _: &GoToDeclaration, + cx: &mut ViewContext, + ) -> Task> { + self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, cx) + } + pub fn go_to_implementation( &mut self, _: &GoToImplementation, @@ -9008,6 +9025,7 @@ impl Editor { let project = workspace.read(cx).project().clone(); let definitions = project.update(cx, |project, cx| match kind { GotoDefinitionKind::Symbol => project.definition(&buffer, head, cx), + GotoDefinitionKind::Declaration => project.declaration(&buffer, head, cx), GotoDefinitionKind::Type => project.type_definition(&buffer, head, cx), GotoDefinitionKind::Implementation => project.implementation(&buffer, head, cx), }); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 05db51883f..16c46b5768 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -300,6 +300,12 @@ impl EditorElement { register_action(view, cx, |editor, a, cx| { editor.go_to_definition_split(a, cx).detach_and_log_err(cx); }); + register_action(view, cx, |editor, a, cx| { + editor.go_to_declaration(a, cx).detach_and_log_err(cx); + }); + register_action(view, cx, |editor, a, cx| { + editor.go_to_declaration_split(a, cx).detach_and_log_err(cx); + }); register_action(view, cx, |editor, a, cx| { editor.go_to_implementation(a, cx).detach_and_log_err(cx); }); diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index 8428cb89b6..9c8cf85dda 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -1,5 +1,6 @@ use std::ops::Range; +use crate::GoToDeclaration; use crate::{ selections_collection::SelectionsCollection, Copy, CopyPermalinkToLine, Cut, DisplayPoint, DisplaySnapshot, Editor, EditorMode, FindAllReferences, GoToDefinition, GoToImplementation, @@ -163,6 +164,7 @@ pub fn deploy_context_menu( .on_blur_subscription(Subscription::new(|| {})) .action("Rename Symbol", Box::new(Rename)) .action("Go to Definition", Box::new(GoToDefinition)) + .action("Go to Declaration", Box::new(GoToDeclaration)) .action("Go to Type Definition", Box::new(GoToTypeDefinition)) .action("Go to Implementation", Box::new(GoToImplementation)) .action("Find All References", Box::new(FindAllReferences)) diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 593298f5d4..f0773abe33 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -117,6 +117,10 @@ pub struct GetDefinition { pub position: PointUtf16, } +pub(crate) struct GetDeclaration { + pub position: PointUtf16, +} + pub(crate) struct GetTypeDefinition { pub position: PointUtf16, } @@ -521,6 +525,106 @@ impl LspCommand for GetDefinition { } } +#[async_trait(?Send)] +impl LspCommand for GetDeclaration { + type Response = Vec; + type LspRequest = lsp::request::GotoDeclaration; + type ProtoRequest = proto::GetDeclaration; + + fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool { + capabilities + .server_capabilities + .declaration_provider + .is_some() + } + + fn to_lsp( + &self, + path: &Path, + _: &Buffer, + _: &Arc, + _: &AppContext, + ) -> lsp::GotoDeclarationParams { + lsp::GotoDeclarationParams { + text_document_position_params: lsp::TextDocumentPositionParams { + text_document: lsp::TextDocumentIdentifier { + uri: lsp::Url::from_file_path(path).unwrap(), + }, + position: point_to_lsp(self.position), + }, + work_done_progress_params: Default::default(), + partial_result_params: Default::default(), + } + } + + async fn response_from_lsp( + self, + message: Option, + project: Model, + buffer: Model, + server_id: LanguageServerId, + cx: AsyncAppContext, + ) -> Result> { + location_links_from_lsp(message, project, buffer, server_id, cx).await + } + + fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDeclaration { + proto::GetDeclaration { + project_id, + buffer_id: buffer.remote_id().into(), + position: Some(language::proto::serialize_anchor( + &buffer.anchor_before(self.position), + )), + version: serialize_version(&buffer.version()), + } + } + + async fn from_proto( + message: proto::GetDeclaration, + _: Model, + buffer: Model, + mut cx: AsyncAppContext, + ) -> Result { + let position = message + .position + .and_then(deserialize_anchor) + .ok_or_else(|| anyhow!("invalid position"))?; + buffer + .update(&mut cx, |buffer, _| { + buffer.wait_for_version(deserialize_version(&message.version)) + })? + .await?; + Ok(Self { + position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?, + }) + } + + fn response_to_proto( + response: Vec, + project: &mut Project, + peer_id: PeerId, + _: &clock::Global, + cx: &mut AppContext, + ) -> proto::GetDeclarationResponse { + let links = location_links_to_proto(response, project, peer_id, cx); + proto::GetDeclarationResponse { links } + } + + async fn response_from_proto( + self, + message: proto::GetDeclarationResponse, + project: Model, + _: Model, + cx: AsyncAppContext, + ) -> Result> { + location_links_from_proto(message.links, project, cx).await + } + + fn buffer_id_from_proto(message: &proto::GetDeclaration) -> Result { + BufferId::new(message.buffer_id) + } +} + #[async_trait(?Send)] impl LspCommand for GetImplementation { type Response = Vec; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 0776f8ca65..f2dcc192ee 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -714,6 +714,7 @@ impl Project { client.add_model_request_handler(Self::handle_lsp_command::); client.add_model_request_handler(Self::handle_lsp_command::); client.add_model_request_handler(Self::handle_lsp_command::); + client.add_model_request_handler(Self::handle_lsp_command::); client.add_model_request_handler(Self::handle_lsp_command::); client.add_model_request_handler(Self::handle_lsp_command::); client.add_model_request_handler(Self::handle_lsp_command::); @@ -5463,6 +5464,30 @@ impl Project { self.definition_impl(buffer, position, cx) } + fn declaration_impl( + &self, + buffer: &Model, + position: PointUtf16, + cx: &mut ModelContext, + ) -> Task>> { + self.request_lsp( + buffer.clone(), + LanguageServerToQuery::Primary, + GetDeclaration { position }, + cx, + ) + } + + pub fn declaration( + &self, + buffer: &Model, + position: T, + cx: &mut ModelContext, + ) -> Task>> { + let position = position.to_point_utf16(buffer.read(cx)); + self.declaration_impl(buffer, position, cx) + } + fn type_definition_impl( &self, buffer: &Model, diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index 2697069b17..22407553a4 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -48,6 +48,8 @@ message Envelope { GetDefinition get_definition = 32; GetDefinitionResponse get_definition_response = 33; + GetDeclaration get_declaration = 237; + GetDeclarationResponse get_declaration_response = 238; // current max GetTypeDefinition get_type_definition = 34; GetTypeDefinitionResponse get_type_definition_response = 35; @@ -696,12 +698,23 @@ message GetDefinition { uint64 buffer_id = 2; Anchor position = 3; repeated VectorClockEntry version = 4; - } +} message GetDefinitionResponse { repeated LocationLink links = 1; } +message GetDeclaration { + uint64 project_id = 1; + uint64 buffer_id = 2; + Anchor position = 3; + repeated VectorClockEntry version = 4; +} + +message GetDeclarationResponse { + repeated LocationLink links = 1; +} + message GetTypeDefinition { uint64 project_id = 1; uint64 buffer_id = 2; diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index aca40cad26..a2282bc6a7 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -239,6 +239,8 @@ messages!( (GetCompletionsResponse, Background), (GetDefinition, Background), (GetDefinitionResponse, Background), + (GetDeclaration, Background), + (GetDeclarationResponse, Background), (GetDocumentHighlights, Background), (GetDocumentHighlightsResponse, Background), (GetHover, Background), @@ -437,6 +439,7 @@ request_messages!( (GetCodeActions, GetCodeActionsResponse), (GetCompletions, GetCompletionsResponse), (GetDefinition, GetDefinitionResponse), + (GetDeclaration, GetDeclarationResponse), (GetImplementation, GetImplementationResponse), (GetDocumentHighlights, GetDocumentHighlightsResponse), (GetHover, GetHoverResponse), @@ -551,6 +554,7 @@ entity_messages!( GetCodeActions, GetCompletions, GetDefinition, + GetDeclaration, GetImplementation, GetDocumentHighlights, GetHover, diff --git a/crates/zed/src/zed/app_menus.rs b/crates/zed/src/zed/app_menus.rs index a0052b5b8d..2995595589 100644 --- a/crates/zed/src/zed/app_menus.rs +++ b/crates/zed/src/zed/app_menus.rs @@ -145,6 +145,7 @@ pub fn app_menus() -> Vec { MenuItem::action("Go to Line/Column...", editor::actions::ToggleGoToLine), MenuItem::separator(), MenuItem::action("Go to Definition", editor::actions::GoToDefinition), + MenuItem::action("Go to Declaration", editor::actions::GoToDeclaration), MenuItem::action("Go to Type Definition", editor::actions::GoToTypeDefinition), MenuItem::action("Find All References", editor::actions::FindAllReferences), MenuItem::separator(), diff --git a/docs/src/key-bindings.md b/docs/src/key-bindings.md index 21f1994671..0cabb2a8d4 100644 --- a/docs/src/key-bindings.md +++ b/docs/src/key-bindings.md @@ -218,6 +218,8 @@ See the [tasks documentation](/docs/tasks#custom-keybindings-for-tasks) for more | Format | Editor | `⌘ + Shift + I` | | Go to definition | Editor | `F12` | | Go to definition split | Editor | `Alt + F12` | +| Go to declaration | Editor | `Ctrl + F12` | +| Go to declaration split | Editor | `Alt + Ctrl + F12` | | Go to diagnostic | Editor | `F8` | | Go to implementation | Editor | `Shift + F12` | | Go to prev diagnostic | Editor | `Shift + F8` | diff --git a/docs/src/vim.md b/docs/src/vim.md index 4742e81940..a00ccab70f 100644 --- a/docs/src/vim.md +++ b/docs/src/vim.md @@ -17,8 +17,10 @@ Vim mode has several "core Zed" key bindings, that will help you make the most o ``` # Language server g d Go to definition -g D Go to type definition -g cmd-d Go to implementation +g D Go to declaration +g y Go to type definition +g I Go to implementation + c d Rename (change definition) g A Go to All references to the current word