Send inlay hint resolve requests

Kirill Bulatov created

Change summary

crates/editor/src/display_map/inlay_map.rs |   4 
crates/editor/src/element.rs               | 177 ++++--
crates/editor/src/inlay_hint_cache.rs      |  81 ++
crates/project/src/lsp_command.rs          | 624 ++++++++++++++---------
crates/project/src/project.rs              | 140 ++++
crates/rpc/proto/zed.proto                 |  16 
crates/rpc/src/proto.rs                    |   4 
crates/rpc/src/rpc.rs                      |   2 
8 files changed, 705 insertions(+), 343 deletions(-)

Detailed changes

crates/editor/src/display_map/inlay_map.rs 🔗

@@ -1125,7 +1125,6 @@ mod tests {
                 Anchor::min(),
                 &InlayHint {
                     label: InlayHintLabel::String("a".to_string()),
-                    buffer_id: 0,
                     position: text::Anchor::default(),
                     padding_left: false,
                     padding_right: false,
@@ -1146,7 +1145,6 @@ mod tests {
                 Anchor::min(),
                 &InlayHint {
                     label: InlayHintLabel::String("a".to_string()),
-                    buffer_id: 0,
                     position: text::Anchor::default(),
                     padding_left: true,
                     padding_right: true,
@@ -1167,7 +1165,6 @@ mod tests {
                 Anchor::min(),
                 &InlayHint {
                     label: InlayHintLabel::String(" a ".to_string()),
-                    buffer_id: 0,
                     position: text::Anchor::default(),
                     padding_left: false,
                     padding_right: false,
@@ -1188,7 +1185,6 @@ mod tests {
                 Anchor::min(),
                 &InlayHint {
                     label: InlayHintLabel::String(" a ".to_string()),
-                    buffer_id: 0,
                     position: text::Anchor::default(),
                     padding_left: true,
                     padding_right: true,

crates/editor/src/element.rs 🔗

@@ -42,7 +42,7 @@ use language::{
 };
 use project::{
     project_settings::{GitGutterSetting, ProjectSettings},
-    InlayHintLabelPart, ProjectPath,
+    InlayHintLabelPart, ProjectPath, ResolveState,
 };
 use smallvec::SmallVec;
 use std::{
@@ -456,82 +456,21 @@ impl EditorElement {
     ) -> bool {
         // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
         // Don't trigger hover popover if mouse is hovering over context menu
+        let mut go_to_definition_point = None;
+        let mut hover_at_point = None;
         if text_bounds.contains_point(position) {
             let point_for_position = position_map.point_for_position(text_bounds, position);
             if let Some(point) = point_for_position.as_valid() {
-                update_go_to_definition_link(editor, Some(point), cmd, shift, cx);
-                hover_at(editor, Some(point), cx);
-                return true;
+                go_to_definition_point = Some(point);
+                hover_at_point = Some(point);
             } else {
-                let hint_start_offset = position_map
-                    .snapshot
-                    .display_point_to_inlay_offset(point_for_position.previous_valid, Bias::Left);
-                let hint_end_offset = position_map
-                    .snapshot
-                    .display_point_to_inlay_offset(point_for_position.next_valid, Bias::Right);
-                let offset_overshoot = point_for_position.column_overshoot_after_line_end as usize;
-                let hovered_offset = if offset_overshoot == 0 {
-                    Some(position_map.snapshot.display_point_to_inlay_offset(
-                        point_for_position.exact_unclipped,
-                        Bias::Left,
-                    ))
-                } else if (hint_end_offset - hint_start_offset).0 >= offset_overshoot {
-                    Some(InlayOffset(hint_start_offset.0 + offset_overshoot))
-                } else {
-                    None
-                };
-                if let Some(hovered_offset) = hovered_offset {
-                    let buffer = editor.buffer().read(cx);
-                    let snapshot = buffer.snapshot(cx);
-                    let previous_valid_anchor = snapshot.anchor_at(
-                        point_for_position
-                            .previous_valid
-                            .to_point(&position_map.snapshot.display_snapshot),
-                        Bias::Left,
-                    );
-                    let next_valid_anchor = snapshot.anchor_at(
-                        point_for_position
-                            .next_valid
-                            .to_point(&position_map.snapshot.display_snapshot),
-                        Bias::Right,
-                    );
-                    if let Some(hovered_hint) = editor
-                        .visible_inlay_hints(cx)
-                        .into_iter()
-                        .skip_while(|hint| {
-                            hint.position.cmp(&previous_valid_anchor, &snapshot).is_lt()
-                        })
-                        .take_while(|hint| hint.position.cmp(&next_valid_anchor, &snapshot).is_le())
-                        .max_by_key(|hint| hint.id)
-                    {
-                        if let Some(cached_hint) = editor
-                            .inlay_hint_cache()
-                            .hint_by_id(previous_valid_anchor.excerpt_id, hovered_hint.id)
-                        {
-                            match &cached_hint.label {
-                                project::InlayHintLabel::String(regular_label) => {
-                                    // TODO kb remove + check for tooltip for hover and resolve, if needed
-                                    eprintln!("regular string: {regular_label}");
-                                }
-                                project::InlayHintLabel::LabelParts(label_parts) => {
-                                    if let Some(hovered_hint_part) = find_hovered_hint_part(
-                                        &label_parts,
-                                        hint_start_offset..hint_end_offset,
-                                        hovered_offset,
-                                    ) {
-                                        // TODO kb remove + check for tooltip and location and resolve, if needed
-                                        eprintln!("hint_part: {hovered_hint_part:?}");
-                                    }
-                                }
-                            };
-                        }
-                    }
-                }
+                (go_to_definition_point, hover_at_point) =
+                    inlay_link_and_hover_points(position_map, point_for_position, editor, cx);
             }
         };
 
-        update_go_to_definition_link(editor, None, cmd, shift, cx);
-        hover_at(editor, None, cx);
+        update_go_to_definition_link(editor, go_to_definition_point, cmd, shift, cx);
+        hover_at(editor, hover_at_point, cx);
         true
     }
 
@@ -1876,6 +1815,104 @@ impl EditorElement {
     }
 }
 
+fn inlay_link_and_hover_points(
+    position_map: &PositionMap,
+    point_for_position: PointForPosition,
+    editor: &mut Editor,
+    cx: &mut ViewContext<'_, '_, Editor>,
+) -> (Option<DisplayPoint>, Option<DisplayPoint>) {
+    let hint_start_offset = position_map
+        .snapshot
+        .display_point_to_inlay_offset(point_for_position.previous_valid, Bias::Left);
+    let hint_end_offset = position_map
+        .snapshot
+        .display_point_to_inlay_offset(point_for_position.next_valid, Bias::Right);
+    let offset_overshoot = point_for_position.column_overshoot_after_line_end as usize;
+    let hovered_offset = if offset_overshoot == 0 {
+        Some(
+            position_map
+                .snapshot
+                .display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left),
+        )
+    } else if (hint_end_offset - hint_start_offset).0 >= offset_overshoot {
+        Some(InlayOffset(hint_start_offset.0 + offset_overshoot))
+    } else {
+        None
+    };
+    let mut go_to_definition_point = None;
+    let mut hover_at_point = None;
+    if let Some(hovered_offset) = hovered_offset {
+        let buffer = editor.buffer().read(cx);
+        let snapshot = buffer.snapshot(cx);
+        let previous_valid_anchor = snapshot.anchor_at(
+            point_for_position
+                .previous_valid
+                .to_point(&position_map.snapshot.display_snapshot),
+            Bias::Left,
+        );
+        let next_valid_anchor = snapshot.anchor_at(
+            point_for_position
+                .next_valid
+                .to_point(&position_map.snapshot.display_snapshot),
+            Bias::Right,
+        );
+        if let Some(hovered_hint) = editor
+            .visible_inlay_hints(cx)
+            .into_iter()
+            .skip_while(|hint| hint.position.cmp(&previous_valid_anchor, &snapshot).is_lt())
+            .take_while(|hint| hint.position.cmp(&next_valid_anchor, &snapshot).is_le())
+            .max_by_key(|hint| hint.id)
+        {
+            let inlay_hint_cache = editor.inlay_hint_cache();
+            if let Some(cached_hint) =
+                inlay_hint_cache.hint_by_id(previous_valid_anchor.excerpt_id, hovered_hint.id)
+            {
+                match &cached_hint.resolve_state {
+                    ResolveState::CanResolve(_, _) => {
+                        if let Some(buffer_id) = previous_valid_anchor.buffer_id {
+                            inlay_hint_cache.spawn_hint_resolve(
+                                buffer_id,
+                                previous_valid_anchor.excerpt_id,
+                                hovered_hint.id,
+                                cx,
+                            );
+                        }
+                    }
+                    ResolveState::Resolved => {
+                        match &cached_hint.label {
+                            project::InlayHintLabel::String(_) => {
+                                if cached_hint.tooltip.is_some() {
+                                    dbg!(&cached_hint.tooltip); // TODO kb
+                                                                // hover_at_point = Some(hovered_offset);
+                                }
+                            }
+                            project::InlayHintLabel::LabelParts(label_parts) => {
+                                if let Some(hovered_hint_part) = find_hovered_hint_part(
+                                    &label_parts,
+                                    hint_start_offset..hint_end_offset,
+                                    hovered_offset,
+                                ) {
+                                    if hovered_hint_part.tooltip.is_some() {
+                                        dbg!(&hovered_hint_part.tooltip); // TODO kb
+                                                                          // hover_at_point = Some(hovered_offset);
+                                    }
+                                    if let Some(location) = &hovered_hint_part.location {
+                                        dbg!(location); // TODO kb
+                                                        // go_to_definition_point = Some(location);
+                                    }
+                                }
+                            }
+                        };
+                    }
+                    ResolveState::Resolving => {}
+                }
+            }
+        }
+    }
+
+    (go_to_definition_point, hover_at_point)
+}
+
 fn find_hovered_hint_part<'a>(
     label_parts: &'a [InlayHintLabelPart],
     hint_range: Range<InlayOffset>,

crates/editor/src/inlay_hint_cache.rs 🔗

@@ -13,7 +13,7 @@ use gpui::{ModelContext, ModelHandle, Task, ViewContext};
 use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot};
 use log::error;
 use parking_lot::RwLock;
-use project::InlayHint;
+use project::{InlayHint, ResolveState};
 
 use collections::{hash_map, HashMap, HashSet};
 use language::language_settings::InlayHintSettings;
@@ -60,7 +60,7 @@ struct ExcerptHintsUpdate {
     excerpt_id: ExcerptId,
     remove_from_visible: Vec<InlayId>,
     remove_from_cache: HashSet<InlayId>,
-    add_to_cache: HashSet<InlayHint>,
+    add_to_cache: Vec<InlayHint>,
 }
 
 #[derive(Debug, Clone, Copy)]
@@ -409,6 +409,79 @@ impl InlayHintCache {
     pub fn version(&self) -> usize {
         self.version
     }
+
+    pub fn spawn_hint_resolve(
+        &self,
+        buffer_id: u64,
+        excerpt_id: ExcerptId,
+        id: InlayId,
+        cx: &mut ViewContext<'_, '_, Editor>,
+    ) {
+        if let Some(excerpt_hints) = self.hints.get(&excerpt_id) {
+            let mut guard = excerpt_hints.write();
+            if let Some(cached_hint) = guard
+                .hints
+                .iter_mut()
+                .find(|(hint_id, _)| hint_id == &id)
+                .map(|(_, hint)| hint)
+            {
+                if let ResolveState::CanResolve(server_id, _) = &cached_hint.resolve_state {
+                    let hint_to_resolve = cached_hint.clone();
+                    let server_id = *server_id;
+                    cached_hint.resolve_state = ResolveState::Resolving;
+                    drop(guard);
+                    cx.spawn(|editor, mut cx| async move {
+                        let resolved_hint_task = editor.update(&mut cx, |editor, cx| {
+                            editor
+                                .buffer()
+                                .read(cx)
+                                .buffer(buffer_id)
+                                .and_then(|buffer| {
+                                    let project = editor.project.as_ref()?;
+                                    Some(project.update(cx, |project, cx| {
+                                        project.resolve_inlay_hint(
+                                            hint_to_resolve,
+                                            buffer,
+                                            server_id,
+                                            cx,
+                                        )
+                                    }))
+                                })
+                        })?;
+                        if let Some(resolved_hint_task) = resolved_hint_task {
+                            if let Some(mut resolved_hint) =
+                                resolved_hint_task.await.context("hint resolve task")?
+                            {
+                                editor.update(&mut cx, |editor, _| {
+                                    if let Some(excerpt_hints) =
+                                        editor.inlay_hint_cache.hints.get(&excerpt_id)
+                                    {
+                                        let mut guard = excerpt_hints.write();
+                                        if let Some(cached_hint) = guard
+                                            .hints
+                                            .iter_mut()
+                                            .find(|(hint_id, _)| hint_id == &id)
+                                            .map(|(_, hint)| hint)
+                                        {
+                                            if cached_hint.resolve_state == ResolveState::Resolving
+                                            {
+                                                resolved_hint.resolve_state =
+                                                    ResolveState::Resolved;
+                                                *cached_hint = resolved_hint;
+                                            }
+                                        }
+                                    }
+                                })?;
+                            }
+                        }
+
+                        anyhow::Ok(())
+                    })
+                    .detach_and_log_err(cx);
+                }
+            }
+        }
+    }
 }
 
 fn spawn_new_update_tasks(
@@ -632,7 +705,7 @@ fn calculate_hint_updates(
     cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
     visible_hints: &[Inlay],
 ) -> Option<ExcerptHintsUpdate> {
-    let mut add_to_cache: HashSet<InlayHint> = HashSet::default();
+    let mut add_to_cache = Vec::<InlayHint>::new();
     let mut excerpt_hints_to_persist = HashMap::default();
     for new_hint in new_excerpt_hints {
         if !contains_position(&fetch_range, new_hint.position, buffer_snapshot) {
@@ -659,7 +732,7 @@ fn calculate_hint_updates(
             None => true,
         };
         if missing_from_cache {
-            add_to_cache.insert(new_hint);
+            add_to_cache.push(new_hint);
         }
     }
 

crates/project/src/lsp_command.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
     DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintLabel,
-    InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink,
+    InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Item, Location, LocationLink,
     MarkupContent, Project, ProjectTransaction, ResolveState,
 };
 use anyhow::{anyhow, Context, Result};
@@ -12,8 +12,9 @@ use language::{
     language_settings::{language_settings, InlayHintKind},
     point_from_lsp, point_to_lsp,
     proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
-    range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction,
-    Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped,
+    range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind,
+    CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction,
+    Unclipped,
 };
 use lsp::{DocumentHighlightKind, LanguageServer, LanguageServerId, ServerCapabilities};
 use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
@@ -1776,6 +1777,371 @@ impl LspCommand for OnTypeFormatting {
     }
 }
 
+impl InlayHints {
+    pub fn lsp_to_project_hint(
+        lsp_hint: lsp::InlayHint,
+        buffer_handle: &ModelHandle<Buffer>,
+        resolve_state: ResolveState,
+        force_no_type_left_padding: bool,
+        cx: &AppContext,
+    ) -> InlayHint {
+        let kind = lsp_hint.kind.and_then(|kind| match kind {
+            lsp::InlayHintKind::TYPE => Some(InlayHintKind::Type),
+            lsp::InlayHintKind::PARAMETER => Some(InlayHintKind::Parameter),
+            _ => None,
+        });
+        let buffer = buffer_handle.read(cx);
+        let position = buffer.clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left);
+        let padding_left = if force_no_type_left_padding && kind == Some(InlayHintKind::Type) {
+            false
+        } else {
+            lsp_hint.padding_left.unwrap_or(false)
+        };
+        InlayHint {
+            position: if kind == Some(InlayHintKind::Parameter) {
+                buffer.anchor_before(position)
+            } else {
+                buffer.anchor_after(position)
+            },
+            padding_left,
+            padding_right: lsp_hint.padding_right.unwrap_or(false),
+            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: match markup_content.kind {
+                                            lsp::MarkupKind::PlainText => HoverBlockKind::PlainText,
+                                            lsp::MarkupKind::Markdown => HoverBlockKind::Markdown,
+                                        },
+                                        value: markup_content.value,
+                                    })
+                                }
+                            }),
+                            location: label_part.location.map(|lsp_location| {
+                                let target_start = buffer.clip_point_utf16(
+                                    point_from_lsp(lsp_location.range.start),
+                                    Bias::Left,
+                                );
+                                let target_end = buffer.clip_point_utf16(
+                                    point_from_lsp(lsp_location.range.end),
+                                    Bias::Left,
+                                );
+                                Location {
+                                    buffer: buffer_handle.clone(),
+                                    range: buffer.anchor_after(target_start)
+                                        ..buffer.anchor_before(target_end),
+                                }
+                            }),
+                        })
+                        .collect(),
+                ),
+            },
+            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: match markup_content.kind {
+                            lsp::MarkupKind::PlainText => HoverBlockKind::PlainText,
+                            lsp::MarkupKind::Markdown => HoverBlockKind::Markdown,
+                        },
+                        value: markup_content.value,
+                    })
+                }
+            }),
+            resolve_state,
+        }
+    }
+
+    pub fn project_to_proto_hint(response_hint: InlayHint, cx: &AppContext) -> proto::InlayHint {
+        let (state, lsp_resolve_state) = match response_hint.resolve_state {
+            ResolveState::CanResolve(server_id, resolve_data) => (
+                0,
+                resolve_data
+                    .map(|json_data| {
+                        serde_json::to_string(&json_data)
+                            .expect("failed to serialize resolve json data")
+                    })
+                    .map(|value| proto::resolve_state::LspResolveState {
+                        server_id: server_id.0 as u64,
+                        value,
+                    }),
+            ),
+            ResolveState::Resolved => (1, None),
+            ResolveState::Resolving => (2, None),
+        };
+        let resolve_state = Some(proto::ResolveState {
+            state,
+            lsp_resolve_state,
+        });
+        proto::InlayHint {
+            position: Some(language::proto::serialize_anchor(&response_hint.position)),
+            padding_left: response_hint.padding_left,
+            padding_right: response_hint.padding_right,
+            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 {
+                                            is_markdown: markup_content.kind == HoverBlockKind::Markdown,
+                                            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.read(cx).remote_id(),
+                                }),
+                            }).collect()
+                        })
+                    }
+                }),
+            }),
+            kind: response_hint.kind.map(|kind| kind.name().to_string()),
+            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 {
+                                is_markdown: markup_content.kind == HoverBlockKind::Markdown,
+                                value: markup_content.value,
+                            },
+                        )
+                    }
+                };
+                proto::InlayHintTooltip {
+                    content: Some(proto_tooltip),
+                }
+            }),
+            resolve_state,
+        }
+    }
+
+    pub async fn proto_to_project_hint(
+        message_hint: proto::InlayHint,
+        project: &ModelHandle<Project>,
+        cx: &mut AsyncAppContext,
+    ) -> anyhow::Result<InlayHint> {
+        let buffer_id = message_hint
+            .position
+            .as_ref()
+            .and_then(|location| location.buffer_id)
+            .context("missing buffer id")?;
+        let resolve_state = message_hint.resolve_state.as_ref().unwrap_or_else(|| {
+            panic!("incorrect proto inlay hint message: no resolve state in hint {message_hint:?}",)
+        });
+        let resolve_state_data = resolve_state
+            .lsp_resolve_state.as_ref()
+            .map(|lsp_resolve_state| {
+                serde_json::from_str::<Option<lsp::LSPAny>>(&lsp_resolve_state.value)
+                    .with_context(|| format!("incorrect proto inlay hint message: non-json resolve state {lsp_resolve_state:?}"))
+                    .map(|state| (LanguageServerId(lsp_resolve_state.server_id as usize), state))
+            })
+            .transpose()?;
+        let resolve_state = match resolve_state.state {
+            0 => ResolveState::Resolved,
+            1 => {
+                let (server_id, lsp_resolve_state) = resolve_state_data.with_context(|| {
+                    format!(
+                        "No lsp resolve data for the hint that can be resolved: {message_hint:?}"
+                    )
+                })?;
+                ResolveState::CanResolve(server_id, lsp_resolve_state)
+            }
+            2 => ResolveState::Resolving,
+            invalid => {
+                anyhow::bail!("Unexpected resolve state {invalid} for hint {message_hint:?}")
+            }
+        };
+        Ok(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 {
+                        let buffer = project
+                            .update(cx, |this, cx| this.wait_for_remote_buffer(buffer_id, cx))
+                            .await?;
+                        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: if markup_content.is_markdown {
+                                        HoverBlockKind::Markdown
+                                    } else {
+                                        HoverBlockKind::PlainText
+                                    },
+                                    value: markup_content.value,
+                                }),
+                                None => InlayHintLabelPartTooltip::String(String::new()),
+                            }),
+                            location: match part.location {
+                                Some(location) => 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,
+                                }),
+                                None => None,
+                            },
+                        });
+                    }
+
+                    InlayHintLabel::LabelParts(label_parts)
+                }
+            },
+            padding_left: message_hint.padding_left,
+            padding_right: message_hint.padding_right,
+            kind: message_hint
+                .kind
+                .as_deref()
+                .and_then(InlayHintKind::from_name),
+            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: if markup_content.is_markdown {
+                                HoverBlockKind::Markdown
+                            } else {
+                                HoverBlockKind::PlainText
+                            },
+                            value: markup_content.value,
+                        })
+                    }
+                })
+            }),
+            resolve_state,
+        })
+    }
+
+    // TODO kb instead, store all LSP data inside the project::InlayHint?
+    pub fn project_to_lsp_hint(
+        hint: InlayHint,
+        project: &ModelHandle<Project>,
+        snapshot: &BufferSnapshot,
+        cx: &AsyncAppContext,
+    ) -> lsp::InlayHint {
+        lsp::InlayHint {
+            position: point_to_lsp(hint.position.to_point_utf16(snapshot)),
+            kind: hint.kind.map(|kind| match kind {
+                InlayHintKind::Type => lsp::InlayHintKind::TYPE,
+                InlayHintKind::Parameter => lsp::InlayHintKind::PARAMETER,
+            }),
+            text_edits: None,
+            tooltip: hint.tooltip.and_then(|tooltip| {
+                Some(match tooltip {
+                    InlayHintTooltip::String(s) => lsp::InlayHintTooltip::String(s),
+                    InlayHintTooltip::MarkupContent(markup_content) => {
+                        lsp::InlayHintTooltip::MarkupContent(lsp::MarkupContent {
+                            kind: match markup_content.kind {
+                                HoverBlockKind::PlainText => lsp::MarkupKind::PlainText,
+                                HoverBlockKind::Markdown => lsp::MarkupKind::Markdown,
+                                HoverBlockKind::Code { .. } => return None,
+                            },
+                            value: markup_content.value,
+                        })
+                    }
+                })
+            }),
+            label: match hint.label {
+                InlayHintLabel::String(s) => lsp::InlayHintLabel::String(s),
+                InlayHintLabel::LabelParts(label_parts) => lsp::InlayHintLabel::LabelParts(
+                    label_parts
+                        .into_iter()
+                        .map(|part| lsp::InlayHintLabelPart {
+                            value: part.value,
+                            tooltip: part.tooltip.and_then(|tooltip| {
+                                Some(match tooltip {
+                                    InlayHintLabelPartTooltip::String(s) => {
+                                        lsp::InlayHintLabelPartTooltip::String(s)
+                                    }
+                                    InlayHintLabelPartTooltip::MarkupContent(markup_content) => {
+                                        lsp::InlayHintLabelPartTooltip::MarkupContent(
+                                            lsp::MarkupContent {
+                                                kind: match markup_content.kind {
+                                                    HoverBlockKind::PlainText => {
+                                                        lsp::MarkupKind::PlainText
+                                                    }
+                                                    HoverBlockKind::Markdown => {
+                                                        lsp::MarkupKind::Markdown
+                                                    }
+                                                    HoverBlockKind::Code { .. } => return None,
+                                                },
+                                                value: markup_content.value,
+                                            },
+                                        )
+                                    }
+                                })
+                            }),
+                            location: part.location.and_then(|location| {
+                                let path = cx.read(|cx| {
+                                    let project_path = location.buffer.read(cx).project_path(cx)?;
+                                    project.read(cx).absolute_path(&project_path, cx)
+                                })?;
+                                Some(lsp::Location::new(
+                                    lsp::Url::from_file_path(path).unwrap(),
+                                    range_to_lsp(
+                                        location.range.start.to_point_utf16(snapshot)
+                                            ..location.range.end.to_point_utf16(snapshot),
+                                    ),
+                                ))
+                            }),
+                            command: None,
+                        })
+                        .collect(),
+                ),
+            },
+            padding_left: Some(hint.padding_left),
+            padding_right: Some(hint.padding_right),
+            data: match hint.resolve_state {
+                ResolveState::CanResolve(_, data) => data,
+                ResolveState::Resolving | ResolveState::Resolved => None,
+            },
+        }
+    }
+}
+
 #[async_trait(?Send)]
 impl LspCommand for InlayHints {
     type Response = Vec<InlayHint>;
@@ -1829,7 +2195,6 @@ impl LspCommand for InlayHints {
         let force_no_type_left_padding =
             lsp_adapter.name.0.as_ref() == "typescript-language-server";
         cx.read(|cx| {
-            let origin_buffer = buffer.read(cx);
             Ok(message
                 .unwrap_or_default()
                 .into_iter()
@@ -1840,88 +2205,18 @@ impl LspCommand for InlayHints {
                                 resolve_provider: Some(true),
                                 ..
                             },
-                        ))) => ResolveState::CanResolve(lsp_hint.data),
+                        ))) => {
+                            ResolveState::CanResolve(lsp_server.server_id(), lsp_hint.data.clone())
+                        }
                         _ => ResolveState::Resolved,
                     };
-                    let kind = lsp_hint.kind.and_then(|kind| match kind {
-                        lsp::InlayHintKind::TYPE => Some(InlayHintKind::Type),
-                        lsp::InlayHintKind::PARAMETER => Some(InlayHintKind::Parameter),
-                        _ => None,
-                    });
-                    let position = origin_buffer
-                        .clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left);
-                    let padding_left =
-                        if force_no_type_left_padding && kind == Some(InlayHintKind::Type) {
-                            false
-                        } else {
-                            lsp_hint.padding_left.unwrap_or(false)
-                        };
-                    InlayHint {
-                        buffer_id: origin_buffer.remote_id(),
-                        position: if kind == Some(InlayHintKind::Parameter) {
-                            origin_buffer.anchor_before(position)
-                        } else {
-                            origin_buffer.anchor_after(position)
-                        },
-                        padding_left,
-                        padding_right: lsp_hint.padding_right.unwrap_or(false),
-                        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,
-                        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,
-                                })
-                            }
-                        }),
+                    InlayHints::lsp_to_project_hint(
+                        lsp_hint,
+                        &buffer,
                         resolve_state,
-                    }
+                        force_no_type_left_padding,
+                        cx,
+                    )
                 })
                 .collect())
         })
@@ -1970,69 +2265,7 @@ impl LspCommand for InlayHints {
         proto::InlayHintsResponse {
             hints: response
                 .into_iter()
-                .map(|response_hint| {
-                    let (state, lsp_resolve_state) = match response_hint.resolve_state {
-                        ResolveState::CanResolve(resolve_data) => {
-                            (0, resolve_data.map(|json_data| serde_json::to_string(&json_data).expect("failed to serialize resolve json data")).map(|value| proto::resolve_state::LspResolveState{ value }))
-                        }
-                        ResolveState::Resolved => (1, None),
-                        ResolveState::Resolving => (2, None),
-                    };
-                    let resolve_state = Some(proto::ResolveState {
-                        state, lsp_resolve_state
-                    });
-                    proto::InlayHint {
-                        position: Some(language::proto::serialize_anchor(&response_hint.position)),
-                        padding_left: response_hint.padding_left,
-                        padding_right: response_hint.padding_right,
-                        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.read(cx).remote_id(),
-                                            }),
-                                        }).collect()
-                                    })
-                                }
-                            }),
-                        }),
-                        kind: response_hint.kind.map(|kind| kind.name().to_string()),
-                        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),
-                            }
-                        }),
-                        resolve_state,
-                    }})
+                .map(|response_hint| InlayHints::project_to_proto_hint(response_hint, cx))
                 .collect(),
             version: serialize_version(buffer_version),
         }
@@ -2053,104 +2286,7 @@ impl LspCommand for InlayHints {
 
         let mut hints = Vec::new();
         for message_hint in message.hints {
-            let buffer_id = message_hint
-                .position
-                .as_ref()
-                .and_then(|location| location.buffer_id)
-                .context("missing buffer id")?;
-            let resolve_state = message_hint.resolve_state.as_ref().unwrap_or_else(|| {
-                panic!(
-                    "incorrect proto inlay hint message: no resolve state in hint {message_hint:?}",
-                )
-            });
-
-            let lsp_resolve_state = resolve_state
-                .lsp_resolve_state.as_ref()
-                .map(|lsp_resolve_state| {
-                    serde_json::from_str::<lsp::LSPAny>(&lsp_resolve_state.value)
-                        .with_context(|| format!("incorrect proto inlay hint message: non-json resolve state {lsp_resolve_state:?}"))
-                })
-                .transpose()?;
-            let resolve_state = match resolve_state.state {
-                0 => ResolveState::Resolved,
-                1 => ResolveState::CanResolve(lsp_resolve_state),
-                2 => ResolveState::Resolving,
-                invalid => {
-                    anyhow::bail!("Unexpected resolve state {invalid} for hint {message_hint:?}")
-                }
-            };
-            let hint = InlayHint {
-                buffer_id,
-                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)
-                    }
-                },
-                padding_left: message_hint.padding_left,
-                padding_right: message_hint.padding_right,
-                kind: message_hint
-                    .kind
-                    .as_deref()
-                    .and_then(InlayHintKind::from_name),
-                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,
-                            })
-                        }
-                    })
-                }),
-                resolve_state,
-            };
-
-            hints.push(hint);
+            hints.push(InlayHints::proto_to_project_hint(message_hint, &project, &mut cx).await?);
         }
 
         Ok(hints)

crates/project/src/project.rs 🔗

@@ -333,9 +333,8 @@ pub struct Location {
     pub range: Range<language::Anchor>,
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct InlayHint {
-    pub buffer_id: u64,
     pub position: language::Anchor,
     pub label: InlayHintLabel,
     pub kind: Option<InlayHintKind>,
@@ -348,18 +347,10 @@ pub struct InlayHint {
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub enum ResolveState {
     Resolved,
-    CanResolve(Option<lsp::LSPAny>),
+    CanResolve(LanguageServerId, Option<lsp::LSPAny>),
     Resolving,
 }
 
-impl Hash for ResolveState {
-    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
-        // Regular `lsp::LSPAny` is not hashable, so we can't hash it.
-        // LSP expects this data to not to change between requests, so we only hash the discriminant.
-        std::mem::discriminant(self).hash(state);
-    }
-}
-
 impl InlayHint {
     pub fn text(&self) -> String {
         match &self.label {
@@ -369,34 +360,34 @@ impl InlayHint {
     }
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub enum InlayHintLabel {
     String(String),
     LabelParts(Vec<InlayHintLabelPart>),
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct InlayHintLabelPart {
     pub value: String,
     pub tooltip: Option<InlayHintLabelPartTooltip>,
     pub location: Option<Location>,
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub enum InlayHintTooltip {
     String(String),
     MarkupContent(MarkupContent),
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub enum InlayHintLabelPartTooltip {
     String(String),
     MarkupContent(MarkupContent),
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct MarkupContent {
-    pub kind: String,
+    pub kind: HoverBlockKind,
     pub value: String,
 }
 
@@ -430,7 +421,7 @@ pub struct HoverBlock {
     pub kind: HoverBlockKind,
 }
 
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, Debug, PartialEq, Eq)]
 pub enum HoverBlockKind {
     PlainText,
     Markdown,
@@ -567,6 +558,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_inlay_hint);
         client.add_model_request_handler(Self::handle_refresh_inlay_hints);
         client.add_model_request_handler(Self::handle_reload_buffers);
         client.add_model_request_handler(Self::handle_synchronize_buffers);
@@ -4985,7 +4977,7 @@ impl Project {
         buffer_handle: ModelHandle<Buffer>,
         range: Range<T>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<InlayHint>>> {
+    ) -> Task<anyhow::Result<Vec<InlayHint>>> {
         let buffer = buffer_handle.read(cx);
         let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
         let range_start = range.start;
@@ -5035,6 +5027,79 @@ impl Project {
         }
     }
 
+    pub fn resolve_inlay_hint(
+        &self,
+        hint: InlayHint,
+        buffer_handle: ModelHandle<Buffer>,
+        server_id: LanguageServerId,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<anyhow::Result<Option<InlayHint>>> {
+        if self.is_local() {
+            let buffer = buffer_handle.read(cx);
+            let (_, lang_server) = if let Some((adapter, server)) =
+                self.language_server_for_buffer(buffer, server_id, cx)
+            {
+                (adapter.clone(), server.clone())
+            } else {
+                return Task::ready(Ok(None));
+            };
+            let can_resolve = lang_server
+                .capabilities()
+                .completion_provider
+                .as_ref()
+                .and_then(|options| options.resolve_provider)
+                .unwrap_or(false);
+            if !can_resolve {
+                return Task::ready(Ok(None));
+            }
+
+            let buffer_snapshot = buffer.snapshot();
+            cx.spawn(|project, cx| async move {
+                let resolve_task = lang_server.request::<lsp::request::InlayHintResolveRequest>(
+                    InlayHints::project_to_lsp_hint(hint, &project, &buffer_snapshot, &cx),
+                );
+                let resolved_hint = resolve_task
+                    .await
+                    .context("inlay hint resolve LSP request")?;
+                let resolved_hint = cx.read(|cx| {
+                    InlayHints::lsp_to_project_hint(
+                        resolved_hint,
+                        &buffer_handle,
+                        ResolveState::Resolved,
+                        false,
+                        cx,
+                    )
+                });
+                Ok(Some(resolved_hint))
+            })
+        } else if let Some(project_id) = self.remote_id() {
+            let client = self.client.clone();
+            let request = proto::ResolveInlayHint {
+                project_id,
+                buffer_id: buffer_handle.read(cx).remote_id(),
+                language_server_id: server_id.0 as u64,
+                hint: Some(InlayHints::project_to_proto_hint(hint, cx)),
+            };
+            cx.spawn(|project, mut cx| async move {
+                let response = client
+                    .request(request)
+                    .await
+                    .context("inlay hints proto request")?;
+                match response.hint {
+                    Some(resolved_hint) => {
+                        InlayHints::proto_to_project_hint(resolved_hint, &project, &mut cx)
+                            .await
+                            .map(Some)
+                            .context("inlay hints proto response conversion")
+                    }
+                    None => Ok(None),
+                }
+            })
+        } else {
+            Task::ready(Err(anyhow!("project does not have a remote id")))
+        }
+    }
+
     #[allow(clippy::type_complexity)]
     pub fn search(
         &self,
@@ -6832,6 +6897,43 @@ impl Project {
         }))
     }
 
+    async fn handle_resolve_inlay_hint(
+        this: ModelHandle<Self>,
+        envelope: TypedEnvelope<proto::ResolveInlayHint>,
+        _: Arc<Client>,
+        mut cx: AsyncAppContext,
+    ) -> Result<proto::ResolveInlayHintResponse> {
+        let proto_hint = envelope
+            .payload
+            .hint
+            .expect("incorrect protobuf resolve inlay hint message: missing the inlay hint");
+        let hint = InlayHints::proto_to_project_hint(proto_hint, &this, &mut cx)
+            .await
+            .context("resolved proto inlay hint conversion")?;
+        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 resolved_hint = this
+            .update(&mut cx, |project, cx| {
+                project.resolve_inlay_hint(
+                    hint,
+                    buffer,
+                    LanguageServerId(envelope.payload.language_server_id as usize),
+                    cx,
+                )
+            })
+            .await
+            .context("inlay hints fetch")?
+            .map(|hint| cx.read(|cx| InlayHints::project_to_proto_hint(hint, cx)));
+
+        Ok(proto::ResolveInlayHintResponse {
+            hint: resolved_hint,
+        })
+    }
+
     async fn handle_refresh_inlay_hints(
         this: ModelHandle<Self>,
         _: TypedEnvelope<proto::RefreshInlayHints>,

crates/rpc/proto/zed.proto 🔗

@@ -128,6 +128,8 @@ message Envelope {
 
         InlayHints inlay_hints = 116;
         InlayHintsResponse inlay_hints_response = 117;
+        ResolveInlayHint resolve_inlay_hint = 131;
+        ResolveInlayHintResponse resolve_inlay_hint_response = 132;
         RefreshInlayHints refresh_inlay_hints = 118;
 
         CreateChannel create_channel = 119;
@@ -800,15 +802,27 @@ message ResolveState {
 
     message LspResolveState {
         string value = 1;
+        uint64 server_id = 2;
     }
 }
 
+message ResolveInlayHint {
+    uint64 project_id = 1;
+    uint64 buffer_id = 2;
+    uint64 language_server_id = 3;
+    InlayHint hint = 4;
+}
+
+message ResolveInlayHintResponse {
+    InlayHint hint = 1;
+}
+
 message RefreshInlayHints {
     uint64 project_id = 1;
 }
 
 message MarkupContent {
-    string kind = 1;
+    bool is_markdown = 1;
     string value = 2;
 }
 

crates/rpc/src/proto.rs 🔗

@@ -197,6 +197,8 @@ messages!(
     (OnTypeFormattingResponse, Background),
     (InlayHints, Background),
     (InlayHintsResponse, Background),
+    (ResolveInlayHint, Background),
+    (ResolveInlayHintResponse, Background),
     (RefreshInlayHints, Foreground),
     (Ping, Foreground),
     (PrepareRename, Background),
@@ -299,6 +301,7 @@ request_messages!(
     (PrepareRename, PrepareRenameResponse),
     (OnTypeFormatting, OnTypeFormattingResponse),
     (InlayHints, InlayHintsResponse),
+    (ResolveInlayHint, ResolveInlayHintResponse),
     (RefreshInlayHints, Ack),
     (ReloadBuffers, ReloadBuffersResponse),
     (RequestContact, Ack),
@@ -355,6 +358,7 @@ entity_messages!(
     PerformRename,
     OnTypeFormatting,
     InlayHints,
+    ResolveInlayHint,
     RefreshInlayHints,
     PrepareRename,
     ReloadBuffers,

crates/rpc/src/rpc.rs 🔗

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