Add "go to type definition" action

ForLoveOfCats created

Change summary

assets/keymaps/default.json             |   1 
crates/editor/src/editor.rs             |  29 +++
crates/editor/src/mouse_context_menu.rs |   5 
crates/lsp/src/lsp.rs                   |   1 
crates/project/src/lsp_command.rs       | 241 +++++++++++++++++++++++++++
crates/project/src/project.rs           |  10 +
crates/rpc/proto/zed.proto              | 171 ++++++++++--------
crates/rpc/src/proto.rs                 |   4 
crates/rpc/src/rpc.rs                   |   2 
crates/zed/src/menus.rs                 |   4 
10 files changed, 385 insertions(+), 83 deletions(-)

Detailed changes

assets/keymaps/default.json 🔗

@@ -192,6 +192,7 @@
             "shift-f8": "editor::GoToPrevDiagnostic",
             "f2": "editor::Rename",
             "f12": "editor::GoToDefinition",
+            "cmd-f12": "editor::GoToTypeDefinition",
             "alt-shift-f12": "editor::FindAllReferences",
             "ctrl-m": "editor::MoveToEnclosingBracket",
             "alt-cmd-[": "editor::Fold",

crates/editor/src/editor.rs 🔗

@@ -187,6 +187,7 @@ actions!(
         SelectLargerSyntaxNode,
         SelectSmallerSyntaxNode,
         GoToDefinition,
+        GoToTypeDefinition,
         MoveToEnclosingBracket,
         UndoSelection,
         RedoSelection,
@@ -297,6 +298,7 @@ pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(Editor::go_to_diagnostic);
     cx.add_action(Editor::go_to_prev_diagnostic);
     cx.add_action(Editor::go_to_definition);
+    cx.add_action(Editor::go_to_type_definition);
     cx.add_action(Editor::page_up);
     cx.add_action(Editor::page_down);
     cx.add_action(Editor::fold);
@@ -895,6 +897,11 @@ pub struct NavigationData {
 
 pub struct EditorCreated(pub ViewHandle<Editor>);
 
+enum GotoDefinitionKind {
+    Symbol,
+    Type,
+}
+
 impl Editor {
     pub fn single_line(
         field_editor_style: Option<GetFieldEditorTheme>,
@@ -4693,6 +4700,22 @@ impl Editor {
         workspace: &mut Workspace,
         _: &GoToDefinition,
         cx: &mut ViewContext<Workspace>,
+    ) {
+        Self::go_to_definition_of_kind(GotoDefinitionKind::Symbol, workspace, cx);
+    }
+
+    pub fn go_to_type_definition(
+        workspace: &mut Workspace,
+        _: &GoToTypeDefinition,
+        cx: &mut ViewContext<Workspace>,
+    ) {
+        Self::go_to_definition_of_kind(GotoDefinitionKind::Type, workspace, cx);
+    }
+
+    fn go_to_definition_of_kind(
+        kind: GotoDefinitionKind,
+        workspace: &mut Workspace,
+        cx: &mut ViewContext<Workspace>,
     ) {
         let active_item = workspace.active_item(cx);
         let editor_handle = if let Some(editor) = active_item
@@ -4714,7 +4737,11 @@ impl Editor {
         };
 
         let project = workspace.project().clone();
-        let definitions = project.update(cx, |project, cx| project.definition(&buffer, head, cx));
+        let definitions = project.update(cx, |project, cx| match kind {
+            GotoDefinitionKind::Symbol => project.definition(&buffer, head, cx),
+            GotoDefinitionKind::Type => project.type_definition(&buffer, head, cx),
+        });
+
         cx.spawn(|workspace, mut cx| async move {
             let definitions = definitions.await?;
             workspace.update(&mut cx, |workspace, cx| {

crates/editor/src/mouse_context_menu.rs 🔗

@@ -2,8 +2,8 @@ use context_menu::ContextMenuItem;
 use gpui::{geometry::vector::Vector2F, impl_internal_actions, MutableAppContext, ViewContext};
 
 use crate::{
-    DisplayPoint, Editor, EditorMode, Event, FindAllReferences, GoToDefinition, Rename, SelectMode,
-    ToggleCodeActions,
+    DisplayPoint, Editor, EditorMode, Event, FindAllReferences, GoToDefinition, GoToTypeDefinition,
+    Rename, SelectMode, ToggleCodeActions,
 };
 
 #[derive(Clone, PartialEq)]
@@ -50,6 +50,7 @@ pub fn deploy_context_menu(
             vec![
                 ContextMenuItem::item("Rename Symbol", Rename),
                 ContextMenuItem::item("Go To Definition", GoToDefinition),
+                ContextMenuItem::item("Go To Type Definition", GoToTypeDefinition),
                 ContextMenuItem::item("Find All References", FindAllReferences),
                 ContextMenuItem::item(
                     "Code Actions",

crates/lsp/src/lsp.rs 🔗

@@ -1,3 +1,4 @@
+pub use lsp_types::request::*;
 pub use lsp_types::*;
 
 use anyhow::{anyhow, Context, Result};

crates/project/src/lsp_command.rs 🔗

@@ -75,6 +75,10 @@ pub(crate) struct GetDefinition {
     pub position: PointUtf16,
 }
 
+pub(crate) struct GetTypeDefinition {
+    pub position: PointUtf16,
+}
+
 pub(crate) struct GetReferences {
     pub position: PointUtf16,
 }
@@ -565,6 +569,243 @@ impl LspCommand for GetDefinition {
     }
 }
 
+#[async_trait(?Send)]
+impl LspCommand for GetTypeDefinition {
+    type Response = Vec<LocationLink>;
+    type LspRequest = lsp::request::GotoTypeDefinition;
+    type ProtoRequest = proto::GetTypeDefinition;
+
+    fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::GotoTypeDefinitionParams {
+        lsp::GotoTypeDefinitionParams {
+            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::GotoTypeDefinitionResponse>,
+        project: ModelHandle<Project>,
+        buffer: ModelHandle<Buffer>,
+        mut cx: AsyncAppContext,
+    ) -> Result<Vec<LocationLink>> {
+        let mut definitions = Vec::new();
+        let (lsp_adapter, language_server) = project
+            .read_with(&cx, |project, cx| {
+                project
+                    .language_server_for_buffer(buffer.read(cx), cx)
+                    .map(|(adapter, server)| (adapter.clone(), server.clone()))
+            })
+            .ok_or_else(|| anyhow!("no language server found for buffer"))?;
+
+        if let Some(message) = message {
+            let mut unresolved_links = Vec::new();
+            match message {
+                lsp::GotoTypeDefinitionResponse::Scalar(loc) => {
+                    unresolved_links.push((None, loc.uri, loc.range));
+                }
+                lsp::GotoTypeDefinitionResponse::Array(locs) => {
+                    unresolved_links.extend(locs.into_iter().map(|l| (None, l.uri, l.range)));
+                }
+                lsp::GotoTypeDefinitionResponse::Link(links) => {
+                    unresolved_links.extend(links.into_iter().map(|l| {
+                        (
+                            l.origin_selection_range,
+                            l.target_uri,
+                            l.target_selection_range,
+                        )
+                    }));
+                }
+            }
+
+            for (origin_range, target_uri, target_range) in unresolved_links {
+                let target_buffer_handle = project
+                    .update(&mut cx, |this, cx| {
+                        this.open_local_buffer_via_lsp(
+                            target_uri,
+                            language_server.server_id(),
+                            lsp_adapter.name.clone(),
+                            cx,
+                        )
+                    })
+                    .await?;
+
+                cx.read(|cx| {
+                    let origin_location = origin_range.map(|origin_range| {
+                        let origin_buffer = buffer.read(cx);
+                        let origin_start = origin_buffer
+                            .clip_point_utf16(point_from_lsp(origin_range.start), Bias::Left);
+                        let origin_end = origin_buffer
+                            .clip_point_utf16(point_from_lsp(origin_range.end), Bias::Left);
+                        Location {
+                            buffer: buffer.clone(),
+                            range: origin_buffer.anchor_after(origin_start)
+                                ..origin_buffer.anchor_before(origin_end),
+                        }
+                    });
+
+                    let target_buffer = target_buffer_handle.read(cx);
+                    let target_start = target_buffer
+                        .clip_point_utf16(point_from_lsp(target_range.start), Bias::Left);
+                    let target_end = target_buffer
+                        .clip_point_utf16(point_from_lsp(target_range.end), Bias::Left);
+                    let target_location = Location {
+                        buffer: target_buffer_handle,
+                        range: target_buffer.anchor_after(target_start)
+                            ..target_buffer.anchor_before(target_end),
+                    };
+
+                    definitions.push(LocationLink {
+                        origin: origin_location,
+                        target: target_location,
+                    })
+                });
+            }
+        }
+
+        Ok(definitions)
+    }
+
+    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetTypeDefinition {
+        proto::GetTypeDefinition {
+            project_id,
+            buffer_id: buffer.remote_id(),
+            position: Some(language::proto::serialize_anchor(
+                &buffer.anchor_before(self.position),
+            )),
+            version: serialize_version(&buffer.version()),
+        }
+    }
+
+    async fn from_proto(
+        message: proto::GetTypeDefinition,
+        _: ModelHandle<Project>,
+        buffer: ModelHandle<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.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
+        })
+    }
+
+    fn response_to_proto(
+        response: Vec<LocationLink>,
+        project: &mut Project,
+        peer_id: PeerId,
+        _: &clock::Global,
+        cx: &AppContext,
+    ) -> proto::GetTypeDefinitionResponse {
+        let links = response
+            .into_iter()
+            .map(|definition| {
+                let origin = definition.origin.map(|origin| {
+                    let buffer = project.serialize_buffer_for_peer(&origin.buffer, peer_id, cx);
+                    proto::Location {
+                        start: Some(serialize_anchor(&origin.range.start)),
+                        end: Some(serialize_anchor(&origin.range.end)),
+                        buffer: Some(buffer),
+                    }
+                });
+
+                let buffer =
+                    project.serialize_buffer_for_peer(&definition.target.buffer, peer_id, cx);
+                let target = proto::Location {
+                    start: Some(serialize_anchor(&definition.target.range.start)),
+                    end: Some(serialize_anchor(&definition.target.range.end)),
+                    buffer: Some(buffer),
+                };
+
+                proto::LocationLink {
+                    origin,
+                    target: Some(target),
+                }
+            })
+            .collect();
+        proto::GetTypeDefinitionResponse { links }
+    }
+
+    async fn response_from_proto(
+        self,
+        message: proto::GetTypeDefinitionResponse,
+        project: ModelHandle<Project>,
+        _: ModelHandle<Buffer>,
+        mut cx: AsyncAppContext,
+    ) -> Result<Vec<LocationLink>> {
+        let mut links = Vec::new();
+        for link in message.links {
+            let origin = match link.origin {
+                Some(origin) => {
+                    let buffer = origin
+                        .buffer
+                        .ok_or_else(|| anyhow!("missing origin buffer"))?;
+                    let buffer = project
+                        .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
+                        .await?;
+                    let start = origin
+                        .start
+                        .and_then(deserialize_anchor)
+                        .ok_or_else(|| anyhow!("missing origin start"))?;
+                    let end = origin
+                        .end
+                        .and_then(deserialize_anchor)
+                        .ok_or_else(|| anyhow!("missing origin end"))?;
+                    buffer
+                        .update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
+                        .await;
+                    Some(Location {
+                        buffer,
+                        range: start..end,
+                    })
+                }
+                None => None,
+            };
+
+            let target = link.target.ok_or_else(|| anyhow!("missing target"))?;
+            let buffer = target.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
+            let buffer = project
+                .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
+                .await?;
+            let start = target
+                .start
+                .and_then(deserialize_anchor)
+                .ok_or_else(|| anyhow!("missing target start"))?;
+            let end = target
+                .end
+                .and_then(deserialize_anchor)
+                .ok_or_else(|| anyhow!("missing target end"))?;
+            buffer
+                .update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
+                .await;
+            let target = Location {
+                buffer,
+                range: start..end,
+            };
+
+            links.push(LocationLink { origin, target })
+        }
+        Ok(links)
+    }
+
+    fn buffer_id_from_proto(message: &proto::GetTypeDefinition) -> u64 {
+        message.buffer_id
+    }
+}
+
 #[async_trait(?Send)]
 impl LspCommand for GetReferences {
     type Response = Vec<Location>;

crates/project/src/project.rs 🔗

@@ -3250,6 +3250,16 @@ impl Project {
         self.request_lsp(buffer.clone(), GetDefinition { position }, cx)
     }
 
+    pub fn type_definition<T: ToPointUtf16>(
+        &self,
+        buffer: &ModelHandle<Buffer>,
+        position: T,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<Vec<LocationLink>>> {
+        let position = position.to_point_utf16(buffer.read(cx));
+        self.request_lsp(buffer.clone(), GetTypeDefinition { position }, cx)
+    }
+
     pub fn references<T: ToPointUtf16>(
         &self,
         buffer: &ModelHandle<Buffer>,

crates/rpc/proto/zed.proto 🔗

@@ -26,85 +26,87 @@ message Envelope {
 
         GetDefinition get_definition = 20;
         GetDefinitionResponse get_definition_response = 21;
-        GetReferences get_references = 22;
-        GetReferencesResponse get_references_response = 23;
-        GetDocumentHighlights get_document_highlights = 24;
-        GetDocumentHighlightsResponse get_document_highlights_response = 25;
-        GetProjectSymbols get_project_symbols = 26;
-        GetProjectSymbolsResponse get_project_symbols_response = 27;
-        OpenBufferForSymbol open_buffer_for_symbol = 28;
-        OpenBufferForSymbolResponse open_buffer_for_symbol_response = 29;
-
-        UpdateProject update_project = 30;
-        RegisterProjectActivity register_project_activity = 31;
-        UpdateWorktree update_worktree = 32;
-        UpdateWorktreeExtensions update_worktree_extensions = 33;
-
-        CreateProjectEntry create_project_entry = 34;
-        RenameProjectEntry rename_project_entry = 35;
-        CopyProjectEntry copy_project_entry = 36;
-        DeleteProjectEntry delete_project_entry = 37;
-        ProjectEntryResponse project_entry_response = 38;
-
-        UpdateDiagnosticSummary update_diagnostic_summary = 39;
-        StartLanguageServer start_language_server = 40;
-        UpdateLanguageServer update_language_server = 41;
-
-        OpenBufferById open_buffer_by_id = 42;
-        OpenBufferByPath open_buffer_by_path = 43;
-        OpenBufferResponse open_buffer_response = 44;
-        UpdateBuffer update_buffer = 45;
-        UpdateBufferFile update_buffer_file = 46;
-        SaveBuffer save_buffer = 47;
-        BufferSaved buffer_saved = 48;
-        BufferReloaded buffer_reloaded = 49;
-        ReloadBuffers reload_buffers = 50;
-        ReloadBuffersResponse reload_buffers_response = 51;
-        FormatBuffers format_buffers = 52;
-        FormatBuffersResponse format_buffers_response = 53;
-        GetCompletions get_completions = 54;
-        GetCompletionsResponse get_completions_response = 55;
-        ApplyCompletionAdditionalEdits apply_completion_additional_edits = 56;
-        ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 57;
-        GetCodeActions get_code_actions = 58;
-        GetCodeActionsResponse get_code_actions_response = 59;
-        GetHover get_hover = 60;
-        GetHoverResponse get_hover_response = 61;
-        ApplyCodeAction apply_code_action = 62;
-        ApplyCodeActionResponse apply_code_action_response = 63;
-        PrepareRename prepare_rename = 64;
-        PrepareRenameResponse prepare_rename_response = 65;
-        PerformRename perform_rename = 66;
-        PerformRenameResponse perform_rename_response = 67;
-        SearchProject search_project = 68;
-        SearchProjectResponse search_project_response = 69;
-
-        GetChannels get_channels = 70;
-        GetChannelsResponse get_channels_response = 71;
-        JoinChannel join_channel = 72;
-        JoinChannelResponse join_channel_response = 73;
-        LeaveChannel leave_channel = 74;
-        SendChannelMessage send_channel_message = 75;
-        SendChannelMessageResponse send_channel_message_response = 76;
-        ChannelMessageSent channel_message_sent = 77;
-        GetChannelMessages get_channel_messages = 78;
-        GetChannelMessagesResponse get_channel_messages_response = 79;
-
-        UpdateContacts update_contacts = 80;
-        UpdateInviteInfo update_invite_info = 81;
-        ShowContacts show_contacts = 82;
-
-        GetUsers get_users = 83;
-        FuzzySearchUsers fuzzy_search_users = 84;
-        UsersResponse users_response = 85;
-        RequestContact request_contact = 86;
-        RespondToContactRequest respond_to_contact_request = 87;
-        RemoveContact remove_contact = 88;
-
-        Follow follow = 89;
-        FollowResponse follow_response = 90;
-        UpdateFollowers update_followers = 91;
-        Unfollow unfollow = 92;
+        GetTypeDefinition get_type_definition = 22;
+        GetTypeDefinitionResponse get_type_definition_response = 23;
+        GetReferences get_references = 24;
+        GetReferencesResponse get_references_response = 25;
+        GetDocumentHighlights get_document_highlights = 26;
+        GetDocumentHighlightsResponse get_document_highlights_response = 27;
+        GetProjectSymbols get_project_symbols = 28;
+        GetProjectSymbolsResponse get_project_symbols_response = 29;
+        OpenBufferForSymbol open_buffer_for_symbol = 30;
+        OpenBufferForSymbolResponse open_buffer_for_symbol_response = 31;
+
+        UpdateProject update_project = 32;
+        RegisterProjectActivity register_project_activity = 33;
+        UpdateWorktree update_worktree = 34;
+        UpdateWorktreeExtensions update_worktree_extensions = 35;
+
+        CreateProjectEntry create_project_entry = 36;
+        RenameProjectEntry rename_project_entry = 37;
+        CopyProjectEntry copy_project_entry = 38;
+        DeleteProjectEntry delete_project_entry = 39;
+        ProjectEntryResponse project_entry_response = 40;
+
+        UpdateDiagnosticSummary update_diagnostic_summary = 41;
+        StartLanguageServer start_language_server = 42;
+        UpdateLanguageServer update_language_server = 43;
+
+        OpenBufferById open_buffer_by_id = 44;
+        OpenBufferByPath open_buffer_by_path = 45;
+        OpenBufferResponse open_buffer_response = 46;
+        UpdateBuffer update_buffer = 47;
+        UpdateBufferFile update_buffer_file = 48;
+        SaveBuffer save_buffer = 49;
+        BufferSaved buffer_saved = 50;
+        BufferReloaded buffer_reloaded = 51;
+        ReloadBuffers reload_buffers = 52;
+        ReloadBuffersResponse reload_buffers_response = 53;
+        FormatBuffers format_buffers = 54;
+        FormatBuffersResponse format_buffers_response = 55;
+        GetCompletions get_completions = 56;
+        GetCompletionsResponse get_completions_response = 57;
+        ApplyCompletionAdditionalEdits apply_completion_additional_edits = 58;
+        ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 59;
+        GetCodeActions get_code_actions = 60;
+        GetCodeActionsResponse get_code_actions_response = 61;
+        GetHover get_hover = 62;
+        GetHoverResponse get_hover_response = 63;
+        ApplyCodeAction apply_code_action = 64;
+        ApplyCodeActionResponse apply_code_action_response = 65;
+        PrepareRename prepare_rename = 66;
+        PrepareRenameResponse prepare_rename_response = 67;
+        PerformRename perform_rename = 68;
+        PerformRenameResponse perform_rename_response = 69;
+        SearchProject search_project = 70;
+        SearchProjectResponse search_project_response = 71;
+
+        GetChannels get_channels = 72;
+        GetChannelsResponse get_channels_response = 73;
+        JoinChannel join_channel = 74;
+        JoinChannelResponse join_channel_response = 75;
+        LeaveChannel leave_channel = 76;
+        SendChannelMessage send_channel_message = 77;
+        SendChannelMessageResponse send_channel_message_response = 78;
+        ChannelMessageSent channel_message_sent = 79;
+        GetChannelMessages get_channel_messages = 80;
+        GetChannelMessagesResponse get_channel_messages_response = 81;
+
+        UpdateContacts update_contacts = 82;
+        UpdateInviteInfo update_invite_info = 83;
+        ShowContacts show_contacts = 84;
+
+        GetUsers get_users = 85;
+        FuzzySearchUsers fuzzy_search_users = 86;
+        UsersResponse users_response = 87;
+        RequestContact request_contact = 88;
+        RespondToContactRequest respond_to_contact_request = 89;
+        RemoveContact remove_contact = 90;
+
+        Follow follow = 91;
+        FollowResponse follow_response = 92;
+        UpdateFollowers update_followers = 93;
+        Unfollow unfollow = 94;
     }
 }
 
@@ -263,6 +265,17 @@ message GetDefinitionResponse {
     repeated LocationLink links = 1;
 }
 
+message GetTypeDefinition {
+     uint64 project_id = 1;
+     uint64 buffer_id = 2;
+     Anchor position = 3;
+     repeated VectorClockEntry version = 4;
+ }
+
+message GetTypeDefinitionResponse {
+    repeated LocationLink links = 1;
+}
+
 message GetReferences {
      uint64 project_id = 1;
      uint64 buffer_id = 2;

crates/rpc/src/proto.rs 🔗

@@ -106,6 +106,8 @@ messages!(
     (GetCompletionsResponse, Background),
     (GetDefinition, Background),
     (GetDefinitionResponse, Background),
+    (GetTypeDefinition, Background),
+    (GetTypeDefinitionResponse, Background),
     (GetDocumentHighlights, Background),
     (GetDocumentHighlightsResponse, Background),
     (GetReferences, Background),
@@ -183,6 +185,7 @@ request_messages!(
     (GetHover, GetHoverResponse),
     (GetCompletions, GetCompletionsResponse),
     (GetDefinition, GetDefinitionResponse),
+    (GetTypeDefinition, GetTypeDefinitionResponse),
     (GetDocumentHighlights, GetDocumentHighlightsResponse),
     (GetReferences, GetReferencesResponse),
     (GetProjectSymbols, GetProjectSymbolsResponse),
@@ -226,6 +229,7 @@ entity_messages!(
     GetCodeActions,
     GetCompletions,
     GetDefinition,
+    GetTypeDefinition,
     GetDocumentHighlights,
     GetHover,
     GetReferences,

crates/rpc/src/rpc.rs 🔗

@@ -6,4 +6,4 @@ pub use conn::Connection;
 pub use peer::*;
 mod macros;
 
-pub const PROTOCOL_VERSION: u32 = 28;
+pub const PROTOCOL_VERSION: u32 = 29;

crates/zed/src/menus.rs 🔗

@@ -274,6 +274,10 @@ pub fn menus() -> Vec<Menu<'static>> {
                     name: "Go to Definition",
                     action: Box::new(editor::GoToDefinition),
                 },
+                MenuItem::Action {
+                    name: "Go to Type Definition",
+                    action: Box::new(editor::GoToTypeDefinition),
+                },
                 MenuItem::Action {
                     name: "Go to References",
                     action: Box::new(editor::FindAllReferences),