Initial protocol check commit

Kirill Bulatov created

Change summary

crates/editor/src/editor.rs       |  25 +++++
crates/lsp/src/lsp.rs             |   8 +
crates/project/src/lsp_command.rs | 153 ++++++++++++++++++++++++++++++++
crates/project/src/project.rs     |  50 ++++++++++
4 files changed, 234 insertions(+), 2 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -2151,6 +2151,10 @@ impl Editor {
                 }
             }
 
+            if let Some(hints_task) = this.request_inlay_hints(cx) {
+                hints_task.detach_and_log_err(cx);
+            }
+
             if had_active_copilot_suggestion {
                 this.refresh_copilot_suggestions(true, cx);
                 if !this.has_active_copilot_suggestion(cx) {
@@ -2577,6 +2581,27 @@ impl Editor {
         }
     }
 
+    // TODO kb proper inlay hints handling
+    fn request_inlay_hints(&self, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
+        let project = self.project.as_ref()?;
+        let position = self.selections.newest_anchor().head();
+        let (buffer, _) = self
+            .buffer
+            .read(cx)
+            .text_anchor_for_position(position.clone(), cx)?;
+
+        let end = buffer.read(cx).len();
+        let inlay_hints_task = project.update(cx, |project, cx| {
+            project.inlay_hints(buffer.clone(), 0..end, cx)
+        });
+
+        Some(cx.spawn(|_, _| async move {
+            let inlay_hints = inlay_hints_task.await?;
+            dbg!(inlay_hints);
+            Ok(())
+        }))
+    }
+
     fn trigger_on_type_formatting(
         &self,
         input: String,

crates/lsp/src/lsp.rs 🔗

@@ -388,6 +388,9 @@ impl LanguageServer {
                         resolve_support: None,
                         ..WorkspaceSymbolClientCapabilities::default()
                     }),
+                    inlay_hint: Some(InlayHintWorkspaceClientCapabilities {
+                        refresh_support: Default::default(),
+                    }),
                     ..Default::default()
                 }),
                 text_document: Some(TextDocumentClientCapabilities {
@@ -429,6 +432,11 @@ impl LanguageServer {
                         content_format: Some(vec![MarkupKind::Markdown]),
                         ..Default::default()
                     }),
+                    // TODO kb add the resolution at least
+                    inlay_hint: Some(InlayHintClientCapabilities {
+                        resolve_support: None,
+                        dynamic_registration: Some(false),
+                    }),
                     ..Default::default()
                 }),
                 experimental: Some(json!({

crates/project/src/lsp_command.rs 🔗

@@ -1,6 +1,7 @@
 use crate::{
-    DocumentHighlight, Hover, HoverBlock, HoverBlockKind, Location, LocationLink, Project,
-    ProjectTransaction,
+    DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintLabel,
+    InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink,
+    MarkupContent, Project, ProjectTransaction,
 };
 use anyhow::{anyhow, Result};
 use async_trait::async_trait;
@@ -126,6 +127,10 @@ pub(crate) struct OnTypeFormatting {
     pub push_to_history: bool,
 }
 
+pub(crate) struct InlayHints {
+    pub range: Range<Anchor>,
+}
+
 pub(crate) struct FormattingOptions {
     tab_size: u32,
 }
@@ -1780,3 +1785,147 @@ impl LspCommand for OnTypeFormatting {
         message.buffer_id
     }
 }
+
+#[async_trait(?Send)]
+impl LspCommand for InlayHints {
+    type Response = Vec<InlayHint>;
+    type LspRequest = lsp::InlayHintRequest;
+    type ProtoRequest = proto::OnTypeFormatting;
+
+    fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool {
+        let Some(inlay_hint_provider) = &server_capabilities.inlay_hint_provider else { return false };
+        match inlay_hint_provider {
+            lsp::OneOf::Left(enabled) => *enabled,
+            lsp::OneOf::Right(inlay_hint_capabilities) => match inlay_hint_capabilities {
+                lsp::InlayHintServerCapabilities::Options(_) => true,
+                // TODO kb there could be dynamic registrations, resolve options
+                lsp::InlayHintServerCapabilities::RegistrationOptions(_) => false,
+            },
+        }
+    }
+
+    fn to_lsp(
+        &self,
+        path: &Path,
+        buffer: &Buffer,
+        _: &Arc<LanguageServer>,
+        _: &AppContext,
+    ) -> lsp::InlayHintParams {
+        lsp::InlayHintParams {
+            text_document: lsp::TextDocumentIdentifier {
+                uri: lsp::Url::from_file_path(path).unwrap(),
+            },
+            range: range_to_lsp(self.range.to_point_utf16(buffer)),
+            work_done_progress_params: Default::default(),
+        }
+    }
+
+    async fn response_from_lsp(
+        self,
+        message: Option<Vec<lsp::InlayHint>>,
+        _: ModelHandle<Project>,
+        buffer: ModelHandle<Buffer>,
+        _: LanguageServerId,
+        cx: AsyncAppContext,
+    ) -> Result<Vec<InlayHint>> {
+        cx.read(|cx| {
+            let origin_buffer = buffer.read(cx);
+            Ok(message
+                .unwrap_or_default()
+                .into_iter()
+                .map(|lsp_hint| InlayHint {
+                    position: origin_buffer.anchor_after(
+                        origin_buffer
+                            .clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left),
+                    ),
+                    label: match lsp_hint.label {
+                        lsp::InlayHintLabel::String(s) => InlayHintLabel::String(s),
+                        lsp::InlayHintLabel::LabelParts(lsp_parts) => InlayHintLabel::LabelParts(
+                            lsp_parts
+                                .into_iter()
+                                .map(|label_part| InlayHintLabelPart {
+                                    value: label_part.value,
+                                    tooltip: label_part.tooltip.map(|tooltip| match tooltip {
+                                        lsp::InlayHintLabelPartTooltip::String(s) => {
+                                            InlayHintLabelPartTooltip::String(s)
+                                        }
+                                        lsp::InlayHintLabelPartTooltip::MarkupContent(
+                                            markup_content,
+                                        ) => InlayHintLabelPartTooltip::MarkupContent(
+                                            MarkupContent {
+                                                kind: format!("{:?}", markup_content.kind),
+                                                value: markup_content.value,
+                                            },
+                                        ),
+                                    }),
+                                    location: label_part.location.map(|lsp_location| {
+                                        let target_start = origin_buffer.clip_point_utf16(
+                                            point_from_lsp(lsp_location.range.start),
+                                            Bias::Left,
+                                        );
+                                        let target_end = origin_buffer.clip_point_utf16(
+                                            point_from_lsp(lsp_location.range.end),
+                                            Bias::Left,
+                                        );
+                                        Location {
+                                            buffer: buffer.clone(),
+                                            range: origin_buffer.anchor_after(target_start)
+                                                ..origin_buffer.anchor_before(target_end),
+                                        }
+                                    }),
+                                })
+                                .collect(),
+                        ),
+                    },
+                    kind: lsp_hint.kind.map(|kind| format!("{kind:?}")),
+                    tooltip: lsp_hint.tooltip.map(|tooltip| match tooltip {
+                        lsp::InlayHintTooltip::String(s) => InlayHintTooltip::String(s),
+                        lsp::InlayHintTooltip::MarkupContent(markup_content) => {
+                            InlayHintTooltip::MarkupContent(MarkupContent {
+                                kind: format!("{:?}", markup_content.kind),
+                                value: markup_content.value,
+                            })
+                        }
+                    }),
+                })
+                .collect())
+        })
+    }
+
+    fn to_proto(&self, _: u64, _: &Buffer) -> proto::OnTypeFormatting {
+        todo!("TODO kb")
+    }
+
+    async fn from_proto(
+        _: proto::OnTypeFormatting,
+        _: ModelHandle<Project>,
+        _: ModelHandle<Buffer>,
+        _: AsyncAppContext,
+    ) -> Result<Self> {
+        todo!("TODO kb")
+    }
+
+    fn response_to_proto(
+        _: Vec<InlayHint>,
+        _: &mut Project,
+        _: PeerId,
+        _: &clock::Global,
+        _: &mut AppContext,
+    ) -> proto::OnTypeFormattingResponse {
+        todo!("TODO kb")
+    }
+
+    async fn response_from_proto(
+        self,
+        _: proto::OnTypeFormattingResponse,
+        _: ModelHandle<Project>,
+        _: ModelHandle<Buffer>,
+        _: AsyncAppContext,
+    ) -> Result<Vec<InlayHint>> {
+        todo!("TODO kb")
+    }
+
+    fn buffer_id_from_proto(message: &proto::OnTypeFormatting) -> u64 {
+        message.buffer_id
+    }
+}

crates/project/src/project.rs 🔗

@@ -325,6 +325,45 @@ pub struct Location {
     pub range: Range<language::Anchor>,
 }
 
+#[derive(Debug)]
+pub struct InlayHint {
+    pub position: Anchor,
+    pub label: InlayHintLabel,
+    pub kind: Option<String>,
+    pub tooltip: Option<InlayHintTooltip>,
+}
+
+#[derive(Debug)]
+pub enum InlayHintLabel {
+    String(String),
+    LabelParts(Vec<InlayHintLabelPart>),
+}
+
+#[derive(Debug)]
+pub struct InlayHintLabelPart {
+    pub value: String,
+    pub tooltip: Option<InlayHintLabelPartTooltip>,
+    pub location: Option<Location>,
+}
+
+#[derive(Debug)]
+pub enum InlayHintTooltip {
+    String(String),
+    MarkupContent(MarkupContent),
+}
+
+#[derive(Debug)]
+pub enum InlayHintLabelPartTooltip {
+    String(String),
+    MarkupContent(MarkupContent),
+}
+
+#[derive(Debug)]
+pub struct MarkupContent {
+    pub kind: String,
+    pub value: String,
+}
+
 #[derive(Debug, Clone)]
 pub struct LocationLink {
     pub origin: Option<Location>,
@@ -4837,6 +4876,17 @@ impl Project {
         )
     }
 
+    pub fn inlay_hints<T: ToOffset>(
+        &self,
+        buffer_handle: ModelHandle<Buffer>,
+        range: Range<T>,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<Vec<InlayHint>>> {
+        let buffer = buffer_handle.read(cx);
+        let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
+        self.request_lsp(buffer_handle, InlayHints { range }, cx)
+    }
+
     #[allow(clippy::type_complexity)]
     pub fn search(
         &self,