Get minimal POC working

Isaac Clayton created

Change summary

crates/editor/src/editor.rs       |  45 ++++++++++++-
crates/project/src/lsp_command.rs | 100 +++++++++++++++++++++++++++++++++
crates/project/src/project.rs     |  12 +++
crates/rpc/proto/zed.proto        |  84 ++++++++++++++++-----------
crates/rpc/src/proto.rs           |   4 +
5 files changed, 204 insertions(+), 41 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -2439,16 +2439,49 @@ impl Editor {
             return;
         };
 
-        let hover = HoverPopover {
-            // TODO: beginning of symbol based on range
-            point: action.0,
-            text: "Test hover information".to_string(),
-            runs: Vec::new(),
+        let snapshot = self.snapshot(cx);
+        let (buffer, buffer_position) = if let Some(output) = self
+            .buffer
+            .read(cx)
+            .text_anchor_for_position(action.0.to_point(&snapshot.display_snapshot), cx)
+        {
+            output
+        } else {
+            return;
         };
 
+        let hover = project.update(cx, |project, cx| {
+            project.hover(&buffer, buffer_position.clone(), cx)
+        });
+
+        let point = action.0.clone();
+
         let id = post_inc(&mut self.next_completion_id);
         let task = cx.spawn_weak(|this, mut cx| {
             async move {
+                // TODO: what to show while language server is loading?
+                let text: String = match hover.await? {
+                    None => "Language server is warming up...".into(),
+                    Some(hover) => match hover.contents {
+                        lsp::HoverContents::Scalar(marked_string) => match marked_string {
+                            lsp::MarkedString::String(string) => string,
+                            lsp::MarkedString::LanguageString(string) => string.value,
+                        },
+                        lsp::HoverContents::Array(marked_strings) => {
+                            // TODO: what to do?
+                            todo!()
+                        }
+                        lsp::HoverContents::Markup(markup) => markup.value,
+                    },
+                };
+
+                let mut hover_popover = HoverPopover {
+                    // TODO: fix tooltip to beginning of symbol based on range
+                    point,
+                    text,
+                    runs: Vec::new(),
+                };
+
                 if let Some(this) = this.upgrade(&cx) {
                     this.update(&mut cx, |this, cx| {
                         if !matches!(
@@ -2459,7 +2492,7 @@ impl Editor {
                         }
 
                         if this.focused {
-                            this.show_context_menu(ContextMenu::Hover(hover), cx);
+                            this.show_context_menu(ContextMenu::Hover(hover_popover), cx);
                         }
 
                         cx.notify();

crates/project/src/lsp_command.rs 🔗

@@ -80,6 +80,10 @@ pub(crate) struct GetDocumentHighlights {
     pub position: PointUtf16,
 }
 
+pub(crate) struct GetHover {
+    pub position: PointUtf16,
+}
+
 #[async_trait(?Send)]
 impl LspCommand for PrepareRename {
     type Response = Option<Range<Anchor>>;
@@ -794,3 +798,99 @@ impl LspCommand for GetDocumentHighlights {
         message.buffer_id
     }
 }
+
+#[async_trait(?Send)]
+impl LspCommand for GetHover {
+    // TODO: proper response type
+    type Response = Option<lsp::Hover>;
+    type LspRequest = lsp::request::HoverRequest;
+    type ProtoRequest = proto::GetHover;
+
+    fn to_lsp(&self, path: &Path, cx: &AppContext) -> lsp::HoverParams {
+        lsp::HoverParams {
+            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(),
+        }
+    }
+
+    async fn response_from_lsp(
+        self,
+        message: Option<lsp::Hover>,
+        project: ModelHandle<Project>,
+        buffer: ModelHandle<Buffer>,
+        cx: AsyncAppContext,
+    ) -> Result<Self::Response> {
+        // let (lsp_adapter, language_server) = project
+        //     .read_with(&cx, |project, cx| {
+        //         project
+        //             .language_server_for_buffer(buffer.read(cx), cx)
+        //             .cloned()
+        //     })
+        //     .ok_or_else(|| anyhow!("no language server found for buffer"))?;
+
+        // TODO: what here?
+        Ok(Some(
+            message.ok_or_else(|| anyhow!("invalid lsp response"))?,
+        ))
+    }
+
+    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest {
+        proto::GetHover {
+            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: Self::ProtoRequest,
+        project: 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: Self::Response,
+        project: &mut Project,
+        peer_id: PeerId,
+        buffer_version: &clock::Global,
+        cx: &AppContext,
+    ) -> proto::GetHoverResponse {
+        todo!()
+    }
+
+    async fn response_from_proto(
+        self,
+        message: <Self::ProtoRequest as proto::RequestMessage>::Response,
+        project: ModelHandle<Project>,
+        buffer: ModelHandle<Buffer>,
+        cx: AsyncAppContext,
+    ) -> Result<Self::Response> {
+        todo!()
+    }
+
+    fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64 {
+        message.buffer_id
+    }
+}

crates/project/src/project.rs 🔗

@@ -302,6 +302,7 @@ impl Project {
         client.add_model_request_handler(Self::handle_format_buffers);
         client.add_model_request_handler(Self::handle_get_code_actions);
         client.add_model_request_handler(Self::handle_get_completions);
+        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::<GetDocumentHighlights>);
         client.add_model_request_handler(Self::handle_lsp_command::<GetReferences>);
@@ -2884,6 +2885,17 @@ impl Project {
         }
     }
 
+    pub fn hover<T: ToPointUtf16>(
+        &self,
+        buffer: &ModelHandle<Buffer>,
+        position: T,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<Option<lsp::Hover>>> {
+        // TODO: proper return type
+        let position = position.to_point_utf16(buffer.read(cx));
+        self.request_lsp(buffer.clone(), GetHover { position }, cx)
+    }
+
     pub fn completions<T: ToPointUtf16>(
         &self,
         source_buffer_handle: &ModelHandle<Buffer>,

crates/rpc/proto/zed.proto 🔗

@@ -66,41 +66,43 @@ message Envelope {
         ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 56;
         GetCodeActions get_code_actions = 57;
         GetCodeActionsResponse get_code_actions_response = 58;
-        ApplyCodeAction apply_code_action = 59;
-        ApplyCodeActionResponse apply_code_action_response = 60;
-        PrepareRename prepare_rename = 61;
-        PrepareRenameResponse prepare_rename_response = 62;
-        PerformRename perform_rename = 63;
-        PerformRenameResponse perform_rename_response = 64;
-        SearchProject search_project = 65;
-        SearchProjectResponse search_project_response = 66;
-
-        GetChannels get_channels = 67;
-        GetChannelsResponse get_channels_response = 68;
-        JoinChannel join_channel = 69;
-        JoinChannelResponse join_channel_response = 70;
-        LeaveChannel leave_channel = 71;
-        SendChannelMessage send_channel_message = 72;
-        SendChannelMessageResponse send_channel_message_response = 73;
-        ChannelMessageSent channel_message_sent = 74;
-        GetChannelMessages get_channel_messages = 75;
-        GetChannelMessagesResponse get_channel_messages_response = 76;
-
-        UpdateContacts update_contacts = 77;
-        UpdateInviteInfo update_invite_info = 78;
-        ShowContacts show_contacts = 79;
-
-        GetUsers get_users = 80;
-        FuzzySearchUsers fuzzy_search_users = 81;
-        UsersResponse users_response = 82;
-        RequestContact request_contact = 83;
-        RespondToContactRequest respond_to_contact_request = 84;
-        RemoveContact remove_contact = 85;
-
-        Follow follow = 86;
-        FollowResponse follow_response = 87;
-        UpdateFollowers update_followers = 88;
-        Unfollow unfollow = 89;
+        GetHover get_hover = 59;
+        GetHoverResponse get_hover_response = 60;
+        ApplyCodeAction apply_code_action = 61;
+        ApplyCodeActionResponse apply_code_action_response = 62;
+        PrepareRename prepare_rename = 63;
+        PrepareRenameResponse prepare_rename_response = 64;
+        PerformRename perform_rename = 65;
+        PerformRenameResponse perform_rename_response = 66;
+        SearchProject search_project = 67;
+        SearchProjectResponse search_project_response = 68;
+
+        GetChannels get_channels = 69;
+        GetChannelsResponse get_channels_response = 70;
+        JoinChannel join_channel = 71;
+        JoinChannelResponse join_channel_response = 72;
+        LeaveChannel leave_channel = 73;
+        SendChannelMessage send_channel_message = 74;
+        SendChannelMessageResponse send_channel_message_response = 75;
+        ChannelMessageSent channel_message_sent = 76;
+        GetChannelMessages get_channel_messages = 77;
+        GetChannelMessagesResponse get_channel_messages_response = 78;
+
+        UpdateContacts update_contacts = 79;
+        UpdateInviteInfo update_invite_info = 80;
+        ShowContacts show_contacts = 81;
+
+        GetUsers get_users = 82;
+        FuzzySearchUsers fuzzy_search_users = 83;
+        UsersResponse users_response = 84;
+        RequestContact request_contact = 85;
+        RespondToContactRequest respond_to_contact_request = 86;
+        RemoveContact remove_contact = 87;
+
+        Follow follow = 88;
+        FollowResponse follow_response = 89;
+        UpdateFollowers update_followers = 90;
+        Unfollow unfollow = 91;
     }
 }
 
@@ -426,6 +428,18 @@ message GetCodeActionsResponse {
     repeated VectorClockEntry version = 2;
 }
 
+message GetHover {
+    uint64 project_id = 1;
+    uint64 buffer_id = 2;
+    Anchor position = 3;
+    repeated VectorClockEntry version = 5;
+}
+
+message GetHoverResponse {
+    repeated CodeAction actions = 1;
+    repeated VectorClockEntry version = 2;
+}
+
 message ApplyCodeAction {
     uint64 project_id = 1;
     uint64 buffer_id = 2;

crates/rpc/src/proto.rs 🔗

@@ -99,6 +99,8 @@ messages!(
     (GetChannelsResponse, Foreground),
     (GetCodeActions, Background),
     (GetCodeActionsResponse, Background),
+    (GetHover, Background),
+    (GetHoverResponse, Background),
     (GetCompletions, Background),
     (GetCompletionsResponse, Background),
     (GetDefinition, Background),
@@ -175,6 +177,7 @@ request_messages!(
     (GetChannelMessages, GetChannelMessagesResponse),
     (GetChannels, GetChannelsResponse),
     (GetCodeActions, GetCodeActionsResponse),
+    (GetHover, GetHoverResponse),
     (GetCompletions, GetCompletionsResponse),
     (GetDefinition, GetDefinitionResponse),
     (GetDocumentHighlights, GetDocumentHighlightsResponse),
@@ -221,6 +224,7 @@ entity_messages!(
     GetCompletions,
     GetDefinition,
     GetDocumentHighlights,
+    GetHover,
     GetReferences,
     GetProjectSymbols,
     JoinProject,