Draft the initial protobuf changes

Kirill Bulatov created

Change summary

crates/collab/src/rpc.rs          |   1 
crates/editor/src/editor.rs       |   1 
crates/project/src/lsp_command.rs | 186 +++++++++++++++++++++++++++++---
crates/project/src/project.rs     |  42 +++++++
crates/rpc/proto/zed.proto        |  59 ++++++++++
crates/rpc/src/proto.rs           |   4 
6 files changed, 274 insertions(+), 19 deletions(-)

Detailed changes

crates/collab/src/rpc.rs 🔗

@@ -226,6 +226,7 @@ impl Server {
             .add_request_handler(forward_project_request::<proto::DeleteProjectEntry>)
             .add_request_handler(forward_project_request::<proto::ExpandProjectEntry>)
             .add_request_handler(forward_project_request::<proto::OnTypeFormatting>)
+            .add_request_handler(forward_project_request::<proto::InlayHints>)
             .add_message_handler(create_buffer_for_peer)
             .add_request_handler(update_buffer)
             .add_message_handler(update_buffer_file)

crates/editor/src/editor.rs 🔗

@@ -7240,7 +7240,6 @@ impl Editor {
                     predecessor: *predecessor,
                     excerpts: excerpts.clone(),
                 });
-                // TODO kb wrong?
                 false
             }
             multi_buffer::Event::ExcerptsRemoved { ids } => {

crates/project/src/lsp_command.rs 🔗

@@ -3,7 +3,7 @@ use crate::{
     InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink,
     MarkupContent, Project, ProjectTransaction,
 };
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
 use client::proto::{self, PeerId};
 use fs::LineEnding;
@@ -1790,7 +1790,7 @@ impl LspCommand for OnTypeFormatting {
 impl LspCommand for InlayHints {
     type Response = Vec<InlayHint>;
     type LspRequest = lsp::InlayHintRequest;
-    type ProtoRequest = proto::OnTypeFormatting;
+    type ProtoRequest = proto::InlayHints;
 
     fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool {
         let Some(inlay_hint_provider) = &server_capabilities.inlay_hint_provider else { return false };
@@ -1892,40 +1892,190 @@ impl LspCommand for InlayHints {
         })
     }
 
-    fn to_proto(&self, _: u64, _: &Buffer) -> proto::OnTypeFormatting {
-        todo!("TODO kb")
+    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::InlayHints {
+        proto::InlayHints {
+            project_id,
+            buffer_id: buffer.remote_id(),
+            start: Some(language::proto::serialize_anchor(&self.range.start)),
+            end: Some(language::proto::serialize_anchor(&self.range.end)),
+            version: serialize_version(&buffer.version()),
+        }
     }
 
     async fn from_proto(
-        _: proto::OnTypeFormatting,
+        message: proto::InlayHints,
         _: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
-        _: AsyncAppContext,
+        buffer: ModelHandle<Buffer>,
+        mut cx: AsyncAppContext,
     ) -> Result<Self> {
-        todo!("TODO kb")
+        let start = message
+            .start
+            .and_then(language::proto::deserialize_anchor)
+            .context("invalid start")?;
+        let end = message
+            .end
+            .and_then(language::proto::deserialize_anchor)
+            .context("invalid end")?;
+        // TODO kb has it to be multiple versions instead?
+        buffer
+            .update(&mut cx, |buffer, _| {
+                buffer.wait_for_version(deserialize_version(&message.version))
+            })
+            .await?;
+
+        Ok(Self { range: start..end })
     }
 
     fn response_to_proto(
-        _: Vec<InlayHint>,
+        response: Vec<InlayHint>,
         _: &mut Project,
         _: PeerId,
-        _: &clock::Global,
+        buffer_version: &clock::Global,
         _: &mut AppContext,
-    ) -> proto::OnTypeFormattingResponse {
-        todo!("TODO kb")
+    ) -> proto::InlayHintsResponse {
+        proto::InlayHintsResponse {
+            hints: response
+                .into_iter()
+                .map(|response_hint| proto::InlayHint {
+                    position: Some(language::proto::serialize_anchor(&response_hint.position)),
+                    label: Some(proto::InlayHintLabel {
+                        label: Some(match response_hint.label {
+                            InlayHintLabel::String(s) => proto::inlay_hint_label::Label::Value(s),
+                            InlayHintLabel::LabelParts(label_parts) => {
+                                proto::inlay_hint_label::Label::LabelParts(proto::InlayHintLabelParts {
+                                    parts: label_parts.into_iter().map(|label_part| proto::InlayHintLabelPart {
+                                        value: label_part.value,
+                                        tooltip: label_part.tooltip.map(|tooltip| {
+                                            let proto_tooltip = match tooltip {
+                                                InlayHintLabelPartTooltip::String(s) => proto::inlay_hint_label_part_tooltip::Content::Value(s),
+                                                InlayHintLabelPartTooltip::MarkupContent(markup_content) => proto::inlay_hint_label_part_tooltip::Content::MarkupContent(proto::MarkupContent {
+                                                    kind: markup_content.kind,
+                                                    value: markup_content.value,
+                                                }),
+                                            };
+                                            proto::InlayHintLabelPartTooltip {content: Some(proto_tooltip)}
+                                        }),
+                                        location: label_part.location.map(|location| proto::Location {
+                                            start: Some(serialize_anchor(&location.range.start)),
+                                            end: Some(serialize_anchor(&location.range.end)),
+                                            buffer_id: location.buffer.id() as u64,
+                                        }),
+                                    }).collect()
+                                })
+                            }
+                        }),
+                    }),
+                    kind: response_hint.kind,
+                    tooltip: response_hint.tooltip.map(|response_tooltip| {
+                        let proto_tooltip = match response_tooltip {
+                            InlayHintTooltip::String(s) => {
+                                proto::inlay_hint_tooltip::Content::Value(s)
+                            }
+                            InlayHintTooltip::MarkupContent(markup_content) => {
+                                proto::inlay_hint_tooltip::Content::MarkupContent(
+                                    proto::MarkupContent {
+                                        kind: markup_content.kind,
+                                        value: markup_content.value,
+                                    },
+                                )
+                            }
+                        };
+                        proto::InlayHintTooltip {
+                            content: Some(proto_tooltip),
+                        }
+                    }),
+                })
+                .collect(),
+            version: serialize_version(buffer_version),
+        }
     }
 
     async fn response_from_proto(
         self,
-        _: proto::OnTypeFormattingResponse,
-        _: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
-        _: AsyncAppContext,
+        message: proto::InlayHintsResponse,
+        project: ModelHandle<Project>,
+        buffer: ModelHandle<Buffer>,
+        mut cx: AsyncAppContext,
     ) -> Result<Vec<InlayHint>> {
-        todo!("TODO kb")
+        buffer
+            .update(&mut cx, |buffer, _| {
+                buffer.wait_for_version(deserialize_version(&message.version))
+            })
+            .await?;
+
+        let mut hints = Vec::new();
+        for message_hint in message.hints {
+            let hint = InlayHint {
+                position: message_hint
+                    .position
+                    .and_then(language::proto::deserialize_anchor)
+                    .context("invalid position")?,
+                label: match message_hint
+                    .label
+                    .and_then(|label| label.label)
+                    .context("missing label")?
+                {
+                    proto::inlay_hint_label::Label::Value(s) => InlayHintLabel::String(s),
+                    proto::inlay_hint_label::Label::LabelParts(parts) => {
+                        let mut label_parts = Vec::new();
+                        for part in parts.parts {
+                            label_parts.push(InlayHintLabelPart {
+                                value: part.value,
+                                tooltip: part.tooltip.map(|tooltip| match tooltip.content {
+                                    Some(proto::inlay_hint_label_part_tooltip::Content::Value(s)) => InlayHintLabelPartTooltip::String(s),
+                                    Some(proto::inlay_hint_label_part_tooltip::Content::MarkupContent(markup_content)) => InlayHintLabelPartTooltip::MarkupContent(MarkupContent {
+                                        kind: markup_content.kind,
+                                        value: markup_content.value,
+                                    }),
+                                    None => InlayHintLabelPartTooltip::String(String::new()),
+                                }),
+                                location: match part.location {
+                                    Some(location) => {
+                                        let target_buffer = project
+                                            .update(&mut cx, |this, cx| {
+                                                this.wait_for_remote_buffer(location.buffer_id, cx)
+                                            })
+                                            .await?;
+                                        Some(Location {
+                                        range: location
+                                            .start
+                                            .and_then(language::proto::deserialize_anchor)
+                                            .context("invalid start")?
+                                            ..location
+                                                .end
+                                                .and_then(language::proto::deserialize_anchor)
+                                                .context("invalid end")?,
+                                        buffer: target_buffer,
+                                    })},
+                                    None => None,
+                                },
+                            });
+                        }
+
+                        InlayHintLabel::LabelParts(label_parts)
+                    }
+                },
+                kind: message_hint.kind,
+                tooltip: message_hint.tooltip.and_then(|tooltip| {
+                    Some(match tooltip.content? {
+                        proto::inlay_hint_tooltip::Content::Value(s) => InlayHintTooltip::String(s),
+                        proto::inlay_hint_tooltip::Content::MarkupContent(markup_content) => {
+                            InlayHintTooltip::MarkupContent(MarkupContent {
+                                kind: markup_content.kind,
+                                value: markup_content.value,
+                            })
+                        }
+                    })
+                }),
+            };
+
+            hints.push(hint);
+        }
+
+        Ok(hints)
     }
 
-    fn buffer_id_from_proto(message: &proto::OnTypeFormatting) -> u64 {
+    fn buffer_id_from_proto(message: &proto::InlayHints) -> u64 {
         message.buffer_id
     }
 }

crates/project/src/project.rs 🔗

@@ -525,6 +525,7 @@ impl Project {
         client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion);
         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_reload_buffers);
         client.add_model_request_handler(Self::handle_synchronize_buffers);
         client.add_model_request_handler(Self::handle_format_buffers);
@@ -6645,6 +6646,47 @@ impl Project {
         Ok(proto::OnTypeFormattingResponse { transaction })
     }
 
+    async fn handle_inlay_hints(
+        this: ModelHandle<Self>,
+        envelope: TypedEnvelope<proto::InlayHints>,
+        _: Arc<Client>,
+        mut cx: AsyncAppContext,
+    ) -> Result<proto::InlayHintsResponse> {
+        let sender_id = envelope.original_sender_id()?;
+        let buffer = this.update(&mut cx, |this, cx| {
+            this.opened_buffers
+                .get(&envelope.payload.buffer_id)
+                .and_then(|buffer| buffer.upgrade(cx))
+                .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))
+        })?;
+        let buffer_version = deserialize_version(&envelope.payload.version);
+
+        buffer
+            .update(&mut cx, |buffer, _| {
+                buffer.wait_for_version(buffer_version.clone())
+            })
+            .await
+            .with_context(|| {
+                format!(
+                    "waiting for version {:?} for buffer {}",
+                    buffer_version,
+                    buffer.id()
+                )
+            })?;
+
+        let buffer_hints = this
+            .update(&mut cx, |project, cx| {
+                let end = buffer.read(cx).len();
+                project.inlay_hints(buffer, 0..end, cx)
+            })
+            .await
+            .context("inlay hints fetch")?;
+
+        Ok(this.update(&mut cx, |project, cx| {
+            InlayHints::response_to_proto(buffer_hints, project, sender_id, &buffer_version, cx)
+        }))
+    }
+
     async fn handle_lsp_command<T: LspCommand>(
         this: ModelHandle<Self>,
         envelope: TypedEnvelope<T::ProtoRequest>,

crates/rpc/proto/zed.proto 🔗

@@ -136,6 +136,9 @@ message Envelope {
         OnTypeFormattingResponse on_type_formatting_response = 112;
 
         UpdateWorktreeSettings update_worktree_settings = 113;
+
+        InlayHints inlay_hints = 114;
+        InlayHintsResponse inlay_hints_response = 115;
     }
 }
 
@@ -705,6 +708,62 @@ message OnTypeFormattingResponse {
     Transaction transaction = 1;
 }
 
+message InlayHints {
+    uint64 project_id = 1;
+    uint64 buffer_id = 2;
+    Anchor start = 3;
+    Anchor end = 4;
+    repeated VectorClockEntry version = 5;
+}
+
+message InlayHintsResponse {
+    repeated InlayHint hints = 1;
+    repeated VectorClockEntry version = 2;
+}
+
+message InlayHint {
+    Anchor position = 1;
+    InlayHintLabel label = 2;
+    optional string kind = 3;
+    InlayHintTooltip tooltip = 4;
+}
+
+message InlayHintLabel {
+    oneof label {
+        string value = 1;
+        InlayHintLabelParts label_parts = 2;
+    }
+}
+
+message InlayHintLabelParts {
+    repeated InlayHintLabelPart parts = 1;
+}
+
+message InlayHintLabelPart {
+    string value = 1;
+    InlayHintLabelPartTooltip tooltip = 2;
+    Location location = 3;
+}
+
+message InlayHintTooltip {
+    oneof content {
+        string value = 1;
+        MarkupContent markup_content = 2;
+    }
+}
+
+message InlayHintLabelPartTooltip {
+    oneof content {
+        string value = 1;
+        MarkupContent markup_content = 2;
+    }
+}
+
+message MarkupContent {
+    string kind = 1;
+    string value = 2;
+}
+
 message PerformRenameResponse {
     ProjectTransaction transaction = 2;
 }

crates/rpc/src/proto.rs 🔗

@@ -198,6 +198,8 @@ messages!(
     (PerformRenameResponse, Background),
     (OnTypeFormatting, Background),
     (OnTypeFormattingResponse, Background),
+    (InlayHints, Background),
+    (InlayHintsResponse, Background),
     (Ping, Foreground),
     (PrepareRename, Background),
     (PrepareRenameResponse, Background),
@@ -286,6 +288,7 @@ request_messages!(
     (PerformRename, PerformRenameResponse),
     (PrepareRename, PrepareRenameResponse),
     (OnTypeFormatting, OnTypeFormattingResponse),
+    (InlayHints, InlayHintsResponse),
     (ReloadBuffers, ReloadBuffersResponse),
     (RequestContact, Ack),
     (RemoveContact, Ack),
@@ -332,6 +335,7 @@ entity_messages!(
     OpenBufferForSymbol,
     PerformRename,
     OnTypeFormatting,
+    InlayHints,
     PrepareRename,
     ReloadBuffers,
     RemoveProjectCollaborator,