Editor: support go to implementation (#7890)

Leon Huston created

Release Notes:

- Added "Go to implementation" support in editor.

Change summary

crates/editor/src/actions.rs            |  2 
crates/editor/src/editor.rs             | 14 +++
crates/editor/src/element.rs            |  2 
crates/editor/src/mouse_context_menu.rs |  5 
crates/lsp/src/lsp.rs                   |  1 
crates/project/src/lsp_command.rs       | 97 +++++++++++++++++++++++++++
crates/project/src/project.rs           | 26 ++++++
crates/rpc/proto/zed.proto              | 24 ++++++
crates/rpc/src/proto.rs                 |  4 +
9 files changed, 171 insertions(+), 4 deletions(-)

Detailed changes

crates/editor/src/actions.rs 🔗

@@ -165,6 +165,8 @@ gpui::actions!(
         GoToPrevHunk,
         GoToTypeDefinition,
         GoToTypeDefinitionSplit,
+        GoToImplementation,
+        GoToImplementationSplit,
         OpenUrl,
         HalfPageDown,
         HalfPageUp,

crates/editor/src/editor.rs 🔗

@@ -1346,6 +1346,7 @@ pub(crate) struct NavigationData {
 enum GotoDefinitionKind {
     Symbol,
     Type,
+    Implementation,
 }
 
 #[derive(Debug, Clone)]
@@ -7352,6 +7353,18 @@ impl Editor {
         self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx);
     }
 
+    pub fn go_to_implementation(&mut self, _: &GoToImplementation, cx: &mut ViewContext<Self>) {
+        self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, cx);
+    }
+
+    pub fn go_to_implementation_split(
+        &mut self,
+        _: &GoToImplementationSplit,
+        cx: &mut ViewContext<Self>,
+    ) {
+        self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, cx);
+    }
+
     pub fn go_to_type_definition(&mut self, _: &GoToTypeDefinition, cx: &mut ViewContext<Self>) {
         self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, cx);
     }
@@ -7389,6 +7402,7 @@ impl Editor {
         let definitions = project.update(cx, |project, cx| match kind {
             GotoDefinitionKind::Symbol => project.definition(&buffer, head, cx),
             GotoDefinitionKind::Type => project.type_definition(&buffer, head, cx),
+            GotoDefinitionKind::Implementation => project.implementation(&buffer, head, cx),
         });
 
         cx.spawn(|editor, mut cx| async move {

crates/editor/src/element.rs 🔗

@@ -260,6 +260,8 @@ impl EditorElement {
         register_action(view, cx, Editor::go_to_prev_hunk);
         register_action(view, cx, Editor::go_to_definition);
         register_action(view, cx, Editor::go_to_definition_split);
+        register_action(view, cx, Editor::go_to_implementation);
+        register_action(view, cx, Editor::go_to_implementation_split);
         register_action(view, cx, Editor::go_to_type_definition);
         register_action(view, cx, Editor::go_to_type_definition_split);
         register_action(view, cx, Editor::open_url);

crates/editor/src/mouse_context_menu.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
-    DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, GoToTypeDefinition,
-    Rename, RevealInFinder, SelectMode, ToggleCodeActions,
+    DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, GoToImplementation,
+    GoToTypeDefinition, Rename, RevealInFinder, SelectMode, ToggleCodeActions,
 };
 use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext};
 
@@ -48,6 +48,7 @@ pub fn deploy_context_menu(
             menu.action("Rename Symbol", Box::new(Rename))
                 .action("Go to Definition", Box::new(GoToDefinition))
                 .action("Go to Type Definition", Box::new(GoToTypeDefinition))
+                .action("Go to Implementation", Box::new(GoToImplementation))
                 .action("Find All References", Box::new(FindAllReferences))
                 .action(
                     "Code Actions",

crates/lsp/src/lsp.rs 🔗

@@ -1136,6 +1136,7 @@ impl LanguageServer {
             document_formatting_provider: Some(OneOf::Left(true)),
             document_range_formatting_provider: Some(OneOf::Left(true)),
             definition_provider: Some(OneOf::Left(true)),
+            implementation_provider: Some(ImplementationProviderCapability::Simple(true)),
             type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)),
             ..Default::default()
         }

crates/project/src/lsp_command.rs 🔗

@@ -105,6 +105,10 @@ pub(crate) struct GetTypeDefinition {
     pub position: PointUtf16,
 }
 
+pub(crate) struct GetImplementation {
+    pub position: PointUtf16,
+}
+
 pub(crate) struct GetReferences {
     pub position: PointUtf16,
 }
@@ -492,6 +496,99 @@ impl LspCommand for GetDefinition {
     }
 }
 
+#[async_trait(?Send)]
+impl LspCommand for GetImplementation {
+    type Response = Vec<LocationLink>;
+    type LspRequest = lsp::request::GotoImplementation;
+    type ProtoRequest = proto::GetImplementation;
+
+    fn to_lsp(
+        &self,
+        path: &Path,
+        _: &Buffer,
+        _: &Arc<LanguageServer>,
+        _: &AppContext,
+    ) -> lsp::GotoImplementationParams {
+        lsp::GotoImplementationParams {
+            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::GotoImplementationResponse>,
+        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::GetImplementation {
+        proto::GetImplementation {
+            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::GetImplementation,
+        _: 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::GetImplementationResponse {
+        let links = location_links_to_proto(response, project, peer_id, cx);
+        proto::GetImplementationResponse { links }
+    }
+
+    async fn response_from_proto(
+        self,
+        message: proto::GetImplementationResponse,
+        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::GetImplementation) -> Result<BufferId> {
+        BufferId::new(message.buffer_id)
+    }
+}
+
 #[async_trait(?Send)]
 impl LspCommand for GetTypeDefinition {
     type Response = Vec<LocationLink>;

crates/project/src/project.rs 🔗

@@ -4646,6 +4646,7 @@ impl Project {
             cx,
         )
     }
+
     pub fn type_definition<T: ToPointUtf16>(
         &self,
         buffer: &Model<Buffer>,
@@ -4653,10 +4654,33 @@ impl Project {
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<LocationLink>>> {
         let position = position.to_point_utf16(buffer.read(cx));
-
         self.type_definition_impl(buffer, position, cx)
     }
 
+    fn implementation_impl(
+        &self,
+        buffer: &Model<Buffer>,
+        position: PointUtf16,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<Vec<LocationLink>>> {
+        self.request_lsp(
+            buffer.clone(),
+            LanguageServerToQuery::Primary,
+            GetImplementation { position },
+            cx,
+        )
+    }
+
+    pub fn implementation<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.implementation_impl(buffer, position, cx)
+    }
+
     fn references_impl(
         &self,
         buffer: &Model<Buffer>,

crates/rpc/proto/zed.proto 🔗

@@ -12,6 +12,14 @@ message Envelope {
     uint32 id = 1;
     optional uint32 responding_to = 2;
     optional PeerId original_sender_id = 3;
+    
+    /*
+        When you are adding a new message type, instead of adding it in semantic order
+        and bumping the message ID's of everything that follows, add it at the end of the
+        file and bump the max number. See this
+        https://github.com/zed-industries/zed/pull/7890#discussion_r1496621823
+
+    */
     oneof payload {
         Hello hello = 4;
         Ack ack = 5;
@@ -48,6 +56,7 @@ message Envelope {
         GetDefinitionResponse get_definition_response = 33;
         GetTypeDefinition get_type_definition = 34;
         GetTypeDefinitionResponse get_type_definition_response = 35;
+        
         GetReferences get_references = 36;
         GetReferencesResponse get_references_response = 37;
         GetDocumentHighlights get_document_highlights = 38;
@@ -183,7 +192,10 @@ message Envelope {
         LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155;
         SetRoomParticipantRole set_room_participant_role = 156;
 
-        UpdateUserChannels update_user_channels = 157;
+        UpdateUserChannels update_user_channels = 157; 
+
+        GetImplementation get_implementation = 162;
+        GetImplementationResponse get_implementation_response = 163;
     }
 
     reserved 158 to 161;
@@ -503,6 +515,16 @@ message GetTypeDefinition {
 message GetTypeDefinitionResponse {
     repeated LocationLink links = 1;
 }
+message GetImplementation {
+     uint64 project_id = 1;
+     uint64 buffer_id = 2;
+     Anchor position = 3;
+     repeated VectorClockEntry version = 4;
+ }
+
+message GetImplementationResponse {
+    repeated LocationLink links = 1;
+}
 
 message GetReferences {
      uint64 project_id = 1;

crates/rpc/src/proto.rs 🔗

@@ -192,6 +192,8 @@ messages!(
     (GetReferencesResponse, Background),
     (GetTypeDefinition, Background),
     (GetTypeDefinitionResponse, Background),
+    (GetImplementation, Background),
+    (GetImplementationResponse, Background),
     (GetUsers, Foreground),
     (Hello, Foreground),
     (IncomingCall, Foreground),
@@ -312,6 +314,7 @@ request_messages!(
     (GetCodeActions, GetCodeActionsResponse),
     (GetCompletions, GetCompletionsResponse),
     (GetDefinition, GetDefinitionResponse),
+    (GetImplementation, GetImplementationResponse),
     (GetDocumentHighlights, GetDocumentHighlightsResponse),
     (GetHover, GetHoverResponse),
     (GetNotifications, GetNotificationsResponse),
@@ -388,6 +391,7 @@ entity_messages!(
     GetCodeActions,
     GetCompletions,
     GetDefinition,
+    GetImplementation,
     GetDocumentHighlights,
     GetHover,
     GetProjectSymbols,