Enable completion menu to resolve documentation when guest

Julia created

Change summary

crates/collab/src/rpc.rs          |   1 
crates/editor/src/editor.rs       |  74 +++++++++++-
crates/language/src/buffer.rs     |  13 +-
crates/project/src/lsp_command.rs |  12 +
crates/project/src/project.rs     |  35 ++++++
crates/rpc/proto/zed.proto        | 177 +++++++++++++++++---------------
crates/rpc/src/proto.rs           |   7 +
crates/rpc/src/rpc.rs             |   2 
8 files changed, 217 insertions(+), 104 deletions(-)

Detailed changes

crates/collab/src/rpc.rs 🔗

@@ -224,6 +224,7 @@ impl Server {
             .add_request_handler(forward_project_request::<proto::OpenBufferByPath>)
             .add_request_handler(forward_project_request::<proto::GetCompletions>)
             .add_request_handler(forward_project_request::<proto::ApplyCompletionAdditionalEdits>)
+            .add_request_handler(forward_project_request::<proto::ResolveCompletionDocumentation>)
             .add_request_handler(forward_project_request::<proto::GetCodeActions>)
             .add_request_handler(forward_project_request::<proto::ApplyCodeAction>)
             .add_request_handler(forward_project_request::<proto::PrepareRename>)

crates/editor/src/editor.rs 🔗

@@ -60,10 +60,10 @@ use itertools::Itertools;
 pub use language::{char_kind, CharKind};
 use language::{
     language_settings::{self, all_language_settings, InlayHintSettings},
-    point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion,
-    CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind, IndentSize,
-    Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal,
-    TransactionId,
+    markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel,
+    Completion, CursorShape, Diagnostic, DiagnosticSeverity, Documentation, File, IndentKind,
+    IndentSize, Language, LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection,
+    SelectionGoal, TransactionId,
 };
 use link_go_to_definition::{
     hide_link_definition, show_link_definition, GoToDefinitionLink, InlayHighlight,
@@ -80,7 +80,7 @@ use ordered_float::OrderedFloat;
 use parking_lot::RwLock;
 use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
 use rand::{seq::SliceRandom, thread_rng};
-use rpc::proto::PeerId;
+use rpc::proto::{self, PeerId};
 use scroll::{
     autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
 };
@@ -999,7 +999,7 @@ impl CompletionsMenu {
         let completions = self.completions.clone();
         let completions_guard = completions.read();
         let completion = &completions_guard[index];
-        if completion.lsp_completion.documentation.is_some() {
+        if completion.documentation.is_some() {
             return;
         }
 
@@ -1007,6 +1007,57 @@ impl CompletionsMenu {
         let completion = completion.lsp_completion.clone();
         drop(completions_guard);
 
+        if project.read(cx).is_remote() {
+            let Some(project_id) = project.read(cx).remote_id() else {
+                log::error!("Remote project without remote_id");
+                return;
+            };
+
+            let client = project.read(cx).client();
+            let request = proto::ResolveCompletionDocumentation {
+                project_id,
+                language_server_id: server_id.0 as u64,
+                lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(),
+            };
+
+            cx.spawn(|this, mut cx| async move {
+                let Some(response) = client
+                    .request(request)
+                    .await
+                    .context("completion documentation resolve proto request")
+                    .log_err()
+                else {
+                    return;
+                };
+
+                if response.text.is_empty() {
+                    let mut completions = completions.write();
+                    let completion = &mut completions[index];
+                    completion.documentation = Some(Documentation::Undocumented);
+                }
+
+                let documentation = if response.is_markdown {
+                    Documentation::MultiLineMarkdown(
+                        markdown::parse_markdown(&response.text, &language_registry, None).await,
+                    )
+                } else if response.text.lines().count() <= 1 {
+                    Documentation::SingleLine(response.text)
+                } else {
+                    Documentation::MultiLinePlainText(response.text)
+                };
+
+                let mut completions = completions.write();
+                let completion = &mut completions[index];
+                completion.documentation = Some(documentation);
+                drop(completions);
+
+                _ = this.update(&mut cx, |_, cx| cx.notify());
+            })
+            .detach();
+
+            return;
+        }
+
         let Some(server) = project.read(cx).language_server_for_id(server_id) else {
             return;
         };
@@ -1037,11 +1088,14 @@ impl CompletionsMenu {
 
                 let mut completions = completions.write();
                 let completion = &mut completions[index];
-                completion.documentation = documentation;
-                completion.lsp_completion.documentation = Some(lsp_documentation);
+                completion.documentation = Some(documentation);
                 drop(completions);
 
                 _ = this.update(&mut cx, |_, cx| cx.notify());
+            } else {
+                let mut completions = completions.write();
+                let completion = &mut completions[index];
+                completion.documentation = Some(Documentation::Undocumented);
             }
         })
         .detach();
@@ -1061,10 +1115,10 @@ impl CompletionsMenu {
             .max_by_key(|(_, mat)| {
                 let completions = self.completions.read();
                 let completion = &completions[mat.candidate_id];
-                let documentation = &completion.lsp_completion.documentation;
+                let documentation = &completion.documentation;
 
                 let mut len = completion.label.text.chars().count();
-                if let Some(lsp::Documentation::String(text)) = documentation {
+                if let Some(Documentation::SingleLine(text)) = documentation {
                     len += text.chars().count();
                 }
 

crates/language/src/buffer.rs 🔗

@@ -149,28 +149,28 @@ pub async fn prepare_completion_documentation(
     documentation: &lsp::Documentation,
     language_registry: &Arc<LanguageRegistry>,
     language: Option<Arc<Language>>,
-) -> Option<Documentation> {
+) -> Documentation {
     match documentation {
         lsp::Documentation::String(text) => {
             if text.lines().count() <= 1 {
-                Some(Documentation::SingleLine(text.clone()))
+                Documentation::SingleLine(text.clone())
             } else {
-                Some(Documentation::MultiLinePlainText(text.clone()))
+                Documentation::MultiLinePlainText(text.clone())
             }
         }
 
         lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value }) => match kind {
             lsp::MarkupKind::PlainText => {
                 if value.lines().count() <= 1 {
-                    Some(Documentation::SingleLine(value.clone()))
+                    Documentation::SingleLine(value.clone())
                 } else {
-                    Some(Documentation::MultiLinePlainText(value.clone()))
+                    Documentation::MultiLinePlainText(value.clone())
                 }
             }
 
             lsp::MarkupKind::Markdown => {
                 let parsed = parse_markdown(value, language_registry, language).await;
-                Some(Documentation::MultiLineMarkdown(parsed))
+                Documentation::MultiLineMarkdown(parsed)
             }
         },
     }
@@ -178,6 +178,7 @@ pub async fn prepare_completion_documentation(
 
 #[derive(Clone, Debug)]
 pub enum Documentation {
+    Undocumented,
     SingleLine(String),
     MultiLinePlainText(String),
     MultiLineMarkdown(ParsedMarkdown),

crates/project/src/lsp_command.rs 🔗

@@ -1466,12 +1466,14 @@ impl LspCommand for GetCompletions {
                         }
 
                         let documentation = if let Some(lsp_docs) = &lsp_completion.documentation {
-                            prepare_completion_documentation(
-                                lsp_docs,
-                                &language_registry,
-                                language.clone(),
+                            Some(
+                                prepare_completion_documentation(
+                                    lsp_docs,
+                                    &language_registry,
+                                    language.clone(),
+                                )
+                                .await,
                             )
-                            .await
                         } else {
                             None
                         };

crates/project/src/project.rs 🔗

@@ -580,6 +580,7 @@ impl Project {
         client.add_model_request_handler(Self::handle_apply_code_action);
         client.add_model_request_handler(Self::handle_on_type_formatting);
         client.add_model_request_handler(Self::handle_inlay_hints);
+        client.add_model_request_handler(Self::handle_resolve_completion_documentation);
         client.add_model_request_handler(Self::handle_resolve_inlay_hint);
         client.add_model_request_handler(Self::handle_refresh_inlay_hints);
         client.add_model_request_handler(Self::handle_reload_buffers);
@@ -7155,6 +7156,40 @@ impl Project {
         })
     }
 
+    async fn handle_resolve_completion_documentation(
+        this: ModelHandle<Self>,
+        envelope: TypedEnvelope<proto::ResolveCompletionDocumentation>,
+        _: Arc<Client>,
+        mut cx: AsyncAppContext,
+    ) -> Result<proto::ResolveCompletionDocumentationResponse> {
+        let lsp_completion = serde_json::from_slice(&envelope.payload.lsp_completion)?;
+
+        let completion = this
+            .read_with(&mut cx, |this, _| {
+                let id = LanguageServerId(envelope.payload.language_server_id as usize);
+                let Some(server) = this.language_server_for_id(id) else {
+                    return Err(anyhow!("No language server {id}"));
+                };
+
+                Ok(server.request::<lsp::request::ResolveCompletionItem>(lsp_completion))
+            })?
+            .await?;
+
+        let mut is_markdown = false;
+        let text = match completion.documentation {
+            Some(lsp::Documentation::String(text)) => text,
+
+            Some(lsp::Documentation::MarkupContent(lsp::MarkupContent { kind, value })) => {
+                is_markdown = kind == lsp::MarkupKind::Markdown;
+                value
+            }
+
+            _ => String::new(),
+        };
+
+        Ok(proto::ResolveCompletionDocumentationResponse { text, is_markdown })
+    }
+
     async fn handle_apply_code_action(
         this: ModelHandle<Self>,
         envelope: TypedEnvelope<proto::ApplyCodeAction>,

crates/rpc/proto/zed.proto 🔗

@@ -89,88 +89,90 @@ message Envelope {
         FormatBuffersResponse format_buffers_response = 70;
         GetCompletions get_completions = 71;
         GetCompletionsResponse get_completions_response = 72;
-        ApplyCompletionAdditionalEdits apply_completion_additional_edits = 73;
-        ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 74;
-        GetCodeActions get_code_actions = 75;
-        GetCodeActionsResponse get_code_actions_response = 76;
-        GetHover get_hover = 77;
-        GetHoverResponse get_hover_response = 78;
-        ApplyCodeAction apply_code_action = 79;
-        ApplyCodeActionResponse apply_code_action_response = 80;
-        PrepareRename prepare_rename = 81;
-        PrepareRenameResponse prepare_rename_response = 82;
-        PerformRename perform_rename = 83;
-        PerformRenameResponse perform_rename_response = 84;
-        SearchProject search_project = 85;
-        SearchProjectResponse search_project_response = 86;
-
-        UpdateContacts update_contacts = 87;
-        UpdateInviteInfo update_invite_info = 88;
-        ShowContacts show_contacts = 89;
-
-        GetUsers get_users = 90;
-        FuzzySearchUsers fuzzy_search_users = 91;
-        UsersResponse users_response = 92;
-        RequestContact request_contact = 93;
-        RespondToContactRequest respond_to_contact_request = 94;
-        RemoveContact remove_contact = 95;
-
-        Follow follow = 96;
-        FollowResponse follow_response = 97;
-        UpdateFollowers update_followers = 98;
-        Unfollow unfollow = 99;
-        GetPrivateUserInfo get_private_user_info = 100;
-        GetPrivateUserInfoResponse get_private_user_info_response = 101;
-        UpdateDiffBase update_diff_base = 102;
-
-        OnTypeFormatting on_type_formatting = 103;
-        OnTypeFormattingResponse on_type_formatting_response = 104;
-
-        UpdateWorktreeSettings update_worktree_settings = 105;
-
-        InlayHints inlay_hints = 106;
-        InlayHintsResponse inlay_hints_response = 107;
-        ResolveInlayHint resolve_inlay_hint = 108;
-        ResolveInlayHintResponse resolve_inlay_hint_response = 109;
-        RefreshInlayHints refresh_inlay_hints = 110;
-
-        CreateChannel create_channel = 111;
-        CreateChannelResponse create_channel_response = 112;
-        InviteChannelMember invite_channel_member = 113;
-        RemoveChannelMember remove_channel_member = 114;
-        RespondToChannelInvite respond_to_channel_invite = 115;
-        UpdateChannels update_channels = 116;
-        JoinChannel join_channel = 117;
-        DeleteChannel delete_channel = 118;
-        GetChannelMembers get_channel_members = 119;
-        GetChannelMembersResponse get_channel_members_response = 120;
-        SetChannelMemberAdmin set_channel_member_admin = 121;
-        RenameChannel rename_channel = 122;
-        RenameChannelResponse rename_channel_response = 123;
-
-        JoinChannelBuffer join_channel_buffer = 124;
-        JoinChannelBufferResponse join_channel_buffer_response = 125;
-        UpdateChannelBuffer update_channel_buffer = 126;
-        LeaveChannelBuffer leave_channel_buffer = 127;
-        UpdateChannelBufferCollaborators update_channel_buffer_collaborators = 128;
-        RejoinChannelBuffers rejoin_channel_buffers = 129;
-        RejoinChannelBuffersResponse rejoin_channel_buffers_response = 130;
-        AckBufferOperation ack_buffer_operation = 143;
-
-        JoinChannelChat join_channel_chat = 131;
-        JoinChannelChatResponse join_channel_chat_response = 132;
-        LeaveChannelChat leave_channel_chat = 133;
-        SendChannelMessage send_channel_message = 134;
-        SendChannelMessageResponse send_channel_message_response = 135;
-        ChannelMessageSent channel_message_sent = 136;
-        GetChannelMessages get_channel_messages = 137;
-        GetChannelMessagesResponse get_channel_messages_response = 138;
-        RemoveChannelMessage remove_channel_message = 139;
-        AckChannelMessage ack_channel_message = 144;
-
-        LinkChannel link_channel = 140;
-        UnlinkChannel unlink_channel = 141;
-        MoveChannel move_channel = 142; // current max: 144
+        ResolveCompletionDocumentation resolve_completion_documentation = 73;
+        ResolveCompletionDocumentationResponse resolve_completion_documentation_response = 74;
+        ApplyCompletionAdditionalEdits apply_completion_additional_edits = 75;
+        ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 76;
+        GetCodeActions get_code_actions = 77;
+        GetCodeActionsResponse get_code_actions_response = 78;
+        GetHover get_hover = 79;
+        GetHoverResponse get_hover_response = 80;
+        ApplyCodeAction apply_code_action = 81;
+        ApplyCodeActionResponse apply_code_action_response = 82;
+        PrepareRename prepare_rename = 83;
+        PrepareRenameResponse prepare_rename_response = 84;
+        PerformRename perform_rename = 85;
+        PerformRenameResponse perform_rename_response = 86;
+        SearchProject search_project = 87;
+        SearchProjectResponse search_project_response = 88;
+
+        UpdateContacts update_contacts = 89;
+        UpdateInviteInfo update_invite_info = 90;
+        ShowContacts show_contacts = 91;
+
+        GetUsers get_users = 92;
+        FuzzySearchUsers fuzzy_search_users = 93;
+        UsersResponse users_response = 94;
+        RequestContact request_contact = 95;
+        RespondToContactRequest respond_to_contact_request = 96;
+        RemoveContact remove_contact = 97;
+
+        Follow follow = 98;
+        FollowResponse follow_response = 99;
+        UpdateFollowers update_followers = 100;
+        Unfollow unfollow = 101;
+        GetPrivateUserInfo get_private_user_info = 102;
+        GetPrivateUserInfoResponse get_private_user_info_response = 103;
+        UpdateDiffBase update_diff_base = 104;
+
+        OnTypeFormatting on_type_formatting = 105;
+        OnTypeFormattingResponse on_type_formatting_response = 106;
+
+        UpdateWorktreeSettings update_worktree_settings = 107;
+
+        InlayHints inlay_hints = 108;
+        InlayHintsResponse inlay_hints_response = 109;
+        ResolveInlayHint resolve_inlay_hint = 110;
+        ResolveInlayHintResponse resolve_inlay_hint_response = 111;
+        RefreshInlayHints refresh_inlay_hints = 112;
+
+        CreateChannel create_channel = 113;
+        CreateChannelResponse create_channel_response = 114;
+        InviteChannelMember invite_channel_member = 115;
+        RemoveChannelMember remove_channel_member = 116;
+        RespondToChannelInvite respond_to_channel_invite = 117;
+        UpdateChannels update_channels = 118;
+        JoinChannel join_channel = 119;
+        DeleteChannel delete_channel = 120;
+        GetChannelMembers get_channel_members = 121;
+        GetChannelMembersResponse get_channel_members_response = 122;
+        SetChannelMemberAdmin set_channel_member_admin = 123;
+        RenameChannel rename_channel = 124;
+        RenameChannelResponse rename_channel_response = 125;
+
+        JoinChannelBuffer join_channel_buffer = 126;
+        JoinChannelBufferResponse join_channel_buffer_response = 127;
+        UpdateChannelBuffer update_channel_buffer = 128;
+        LeaveChannelBuffer leave_channel_buffer = 129;
+        UpdateChannelBufferCollaborators update_channel_buffer_collaborators = 130;
+        RejoinChannelBuffers rejoin_channel_buffers = 131;
+        RejoinChannelBuffersResponse rejoin_channel_buffers_response = 132;
+        AckBufferOperation ack_buffer_operation = 145;
+
+        JoinChannelChat join_channel_chat = 133;
+        JoinChannelChatResponse join_channel_chat_response = 134;
+        LeaveChannelChat leave_channel_chat = 135;
+        SendChannelMessage send_channel_message = 136;
+        SendChannelMessageResponse send_channel_message_response = 137;
+        ChannelMessageSent channel_message_sent = 138;
+        GetChannelMessages get_channel_messages = 139;
+        GetChannelMessagesResponse get_channel_messages_response = 140;
+        RemoveChannelMessage remove_channel_message = 141;
+        AckChannelMessage ack_channel_message = 146;
+
+        LinkChannel link_channel = 142;
+        UnlinkChannel unlink_channel = 143;
+        MoveChannel move_channel = 144; // current max: 146
     }
 }
 
@@ -832,6 +834,17 @@ message ResolveState {
     }
 }
 
+message ResolveCompletionDocumentation {
+    uint64 project_id = 1;
+    uint64 language_server_id = 2;
+    bytes lsp_completion = 3;
+}
+
+message ResolveCompletionDocumentationResponse {
+    string text = 1;
+    bool is_markdown = 2;
+}
+
 message ResolveInlayHint {
     uint64 project_id = 1;
     uint64 buffer_id = 2;

crates/rpc/src/proto.rs 🔗

@@ -205,6 +205,8 @@ messages!(
     (OnTypeFormattingResponse, Background),
     (InlayHints, Background),
     (InlayHintsResponse, Background),
+    (ResolveCompletionDocumentation, Background),
+    (ResolveCompletionDocumentationResponse, Background),
     (ResolveInlayHint, Background),
     (ResolveInlayHintResponse, Background),
     (RefreshInlayHints, Foreground),
@@ -318,6 +320,10 @@ request_messages!(
     (PrepareRename, PrepareRenameResponse),
     (OnTypeFormatting, OnTypeFormattingResponse),
     (InlayHints, InlayHintsResponse),
+    (
+        ResolveCompletionDocumentation,
+        ResolveCompletionDocumentationResponse
+    ),
     (ResolveInlayHint, ResolveInlayHintResponse),
     (RefreshInlayHints, Ack),
     (ReloadBuffers, ReloadBuffersResponse),
@@ -381,6 +387,7 @@ entity_messages!(
     PerformRename,
     OnTypeFormatting,
     InlayHints,
+    ResolveCompletionDocumentation,
     ResolveInlayHint,
     RefreshInlayHints,
     PrepareRename,

crates/rpc/src/rpc.rs 🔗

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