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 <mrnugget@gmail.com>
This commit is contained in:
Luis Cossío 2024-08-06 05:20:51 -04:00 committed by GitHub
parent 82db5dedfb
commit 7b5fdcee7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 188 additions and 6 deletions

View File

@ -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"
}
},
{

View File

@ -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",

View File

@ -210,6 +210,8 @@ gpui::actions!(
Format,
GoToDefinition,
GoToDefinitionSplit,
GoToDeclaration,
GoToDeclarationSplit,
GoToDiagnostic,
GoToHunk,
GoToImplementation,

View File

@ -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<Self>,
) -> Task<Result<bool>> {
self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, cx)
}
pub fn go_to_declaration_split(
&mut self,
_: &GoToDeclaration,
cx: &mut ViewContext<Self>,
) -> Task<Result<bool>> {
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),
});

View File

@ -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);
});

View File

@ -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))

View File

@ -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<LocationLink>;
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<LanguageServer>,
_: &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<lsp::GotoDeclarationResponse>,
project: Model<Project>,
buffer: Model<Buffer>,
server_id: LanguageServerId,
cx: AsyncAppContext,
) -> Result<Vec<LocationLink>> {
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<Project>,
buffer: Model<Buffer>,
mut cx: AsyncAppContext,
) -> Result<Self> {
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<LocationLink>,
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<Project>,
_: Model<Buffer>,
cx: AsyncAppContext,
) -> Result<Vec<LocationLink>> {
location_links_from_proto(message.links, project, cx).await
}
fn buffer_id_from_proto(message: &proto::GetDeclaration) -> Result<BufferId> {
BufferId::new(message.buffer_id)
}
}
#[async_trait(?Send)]
impl LspCommand for GetImplementation {
type Response = Vec<LocationLink>;

View File

@ -714,6 +714,7 @@ impl Project {
client.add_model_request_handler(Self::handle_lsp_command::<GetCompletions>);
client.add_model_request_handler(Self::handle_lsp_command::<GetHover>);
client.add_model_request_handler(Self::handle_lsp_command::<GetDefinition>);
client.add_model_request_handler(Self::handle_lsp_command::<GetDeclaration>);
client.add_model_request_handler(Self::handle_lsp_command::<GetTypeDefinition>);
client.add_model_request_handler(Self::handle_lsp_command::<GetDocumentHighlights>);
client.add_model_request_handler(Self::handle_lsp_command::<GetReferences>);
@ -5463,6 +5464,30 @@ impl Project {
self.definition_impl(buffer, position, cx)
}
fn declaration_impl(
&self,
buffer: &Model<Buffer>,
position: PointUtf16,
cx: &mut ModelContext<Self>,
) -> Task<Result<Vec<LocationLink>>> {
self.request_lsp(
buffer.clone(),
LanguageServerToQuery::Primary,
GetDeclaration { position },
cx,
)
}
pub fn declaration<T: ToPointUtf16>(
&self,
buffer: &Model<Buffer>,
position: T,
cx: &mut ModelContext<Self>,
) -> Task<Result<Vec<LocationLink>>> {
let position = position.to_point_utf16(buffer.read(cx));
self.declaration_impl(buffer, position, cx)
}
fn type_definition_impl(
&self,
buffer: &Model<Buffer>,

View File

@ -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;

View File

@ -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,

View File

@ -145,6 +145,7 @@ pub fn app_menus() -> Vec<Menu> {
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(),

View File

@ -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` |

View File

@ -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