Fix inlay hint bugs (#2895)

Kirill Bulatov created

* https://github.com/zed-industries/zed/pull/2891

Fixes ranges pointing at incorrect positions inside multi-codepoint
characters

* https://github.com/zed-industries/zed/pull/2890

Defers hint links' document URL resolution into buffer up until the
hover & cmd-click is made by the user.

Release Notes:

- N/A

Change summary

crates/editor/src/editor.rs                | 222 +++++++++++++++++------
crates/editor/src/element.rs               |   8 
crates/editor/src/hover_popover.rs         |  15 +
crates/editor/src/inlay_hint_cache.rs      |  13 
crates/editor/src/link_go_to_definition.rs | 210 ++++++++++++----------
crates/project/src/lsp_command.rs          | 208 ++++++---------------
crates/project/src/project.rs              |  29 +-
crates/rpc/proto/zed.proto                 |   5 
8 files changed, 380 insertions(+), 330 deletions(-)

Detailed changes

crates/editor/src/editor.rs πŸ”—

@@ -23,7 +23,7 @@ pub mod test;
 
 use ::git::diff::DiffHunk;
 use aho_corasick::AhoCorasick;
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, Context, Result};
 use blink_manager::BlinkManager;
 use client::{ClickhouseEvent, TelemetrySettings};
 use clock::{Global, ReplicaId};
@@ -60,21 +60,24 @@ use itertools::Itertools;
 pub use language::{char_kind, CharKind};
 use language::{
     language_settings::{self, all_language_settings, InlayHintSettings},
-    AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape,
-    Diagnostic, DiagnosticSeverity, File, IndentKind, IndentSize, Language, OffsetRangeExt,
-    OffsetUtf16, Point, Selection, SelectionGoal, TransactionId,
+    point_from_lsp, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion,
+    CursorShape, Diagnostic, DiagnosticSeverity, File, IndentKind, IndentSize, Language,
+    LanguageServerName, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal,
+    TransactionId,
 };
 use link_go_to_definition::{
-    hide_link_definition, show_link_definition, DocumentRange, InlayRange, LinkGoToDefinitionState,
+    hide_link_definition, show_link_definition, DocumentRange, GoToDefinitionLink, InlayRange,
+    LinkGoToDefinitionState,
 };
 use log::error;
+use lsp::LanguageServerId;
 use multi_buffer::ToOffsetUtf16;
 pub use multi_buffer::{
     Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
     ToPoint,
 };
 use ordered_float::OrderedFloat;
-use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction};
+use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
 use rand::{seq::SliceRandom, thread_rng};
 use scroll::{
     autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
@@ -6551,7 +6554,14 @@ impl Editor {
         cx.spawn_labeled("Fetching Definition...", |editor, mut cx| async move {
             let definitions = definitions.await?;
             editor.update(&mut cx, |editor, cx| {
-                editor.navigate_to_definitions(definitions, split, cx);
+                editor.navigate_to_definitions(
+                    definitions
+                        .into_iter()
+                        .map(GoToDefinitionLink::Text)
+                        .collect(),
+                    split,
+                    cx,
+                );
             })?;
             Ok::<(), anyhow::Error>(())
         })
@@ -6560,7 +6570,7 @@ impl Editor {
 
     pub fn navigate_to_definitions(
         &mut self,
-        mut definitions: Vec<LocationLink>,
+        mut definitions: Vec<GoToDefinitionLink>,
         split: bool,
         cx: &mut ViewContext<Editor>,
     ) {
@@ -6571,67 +6581,167 @@ impl Editor {
         // If there is one definition, just open it directly
         if definitions.len() == 1 {
             let definition = definitions.pop().unwrap();
-            let range = definition
-                .target
-                .range
-                .to_offset(definition.target.buffer.read(cx));
-
-            let range = self.range_for_match(&range);
-            if Some(&definition.target.buffer) == self.buffer.read(cx).as_singleton().as_ref() {
-                self.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                    s.select_ranges([range]);
-                });
-            } else {
-                cx.window_context().defer(move |cx| {
-                    let target_editor: ViewHandle<Self> = workspace.update(cx, |workspace, cx| {
-                        if split {
-                            workspace.split_project_item(definition.target.buffer.clone(), cx)
+            let target_task = match definition {
+                GoToDefinitionLink::Text(link) => Task::Ready(Some(Ok(Some(link.target)))),
+                GoToDefinitionLink::InlayHint(lsp_location, server_id) => {
+                    self.compute_target_location(lsp_location, server_id, cx)
+                }
+            };
+            cx.spawn(|editor, mut cx| async move {
+                let target = target_task.await.context("target resolution task")?;
+                if let Some(target) = target {
+                    editor.update(&mut cx, |editor, cx| {
+                        let range = target.range.to_offset(target.buffer.read(cx));
+                        let range = editor.range_for_match(&range);
+                        if Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
+                            editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
+                                s.select_ranges([range]);
+                            });
                         } else {
-                            workspace.open_project_item(definition.target.buffer.clone(), cx)
+                            cx.window_context().defer(move |cx| {
+                                let target_editor: ViewHandle<Self> =
+                                    workspace.update(cx, |workspace, cx| {
+                                        if split {
+                                            workspace.split_project_item(target.buffer.clone(), cx)
+                                        } else {
+                                            workspace.open_project_item(target.buffer.clone(), cx)
+                                        }
+                                    });
+                                target_editor.update(cx, |target_editor, cx| {
+                                    // When selecting a definition in a different buffer, disable the nav history
+                                    // to avoid creating a history entry at the previous cursor location.
+                                    pane.update(cx, |pane, _| pane.disable_history());
+                                    target_editor.change_selections(
+                                        Some(Autoscroll::fit()),
+                                        cx,
+                                        |s| {
+                                            s.select_ranges([range]);
+                                        },
+                                    );
+                                    pane.update(cx, |pane, _| pane.enable_history());
+                                });
+                            });
                         }
-                    });
-                    target_editor.update(cx, |target_editor, cx| {
-                        // When selecting a definition in a different buffer, disable the nav history
-                        // to avoid creating a history entry at the previous cursor location.
-                        pane.update(cx, |pane, _| pane.disable_history());
-                        target_editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
-                            s.select_ranges([range]);
-                        });
-                        pane.update(cx, |pane, _| pane.enable_history());
-                    });
-                });
-            }
+                    })
+                } else {
+                    Ok(())
+                }
+            })
+            .detach_and_log_err(cx);
         } else if !definitions.is_empty() {
             let replica_id = self.replica_id(cx);
-            cx.window_context().defer(move |cx| {
-                let title = definitions
-                    .iter()
-                    .find(|definition| definition.origin.is_some())
-                    .and_then(|definition| {
-                        definition.origin.as_ref().map(|origin| {
-                            let buffer = origin.buffer.read(cx);
-                            format!(
-                                "Definitions for {}",
-                                buffer
-                                    .text_for_range(origin.range.clone())
-                                    .collect::<String>()
-                            )
-                        })
+            cx.spawn(|editor, mut cx| async move {
+                let (title, location_tasks) = editor
+                    .update(&mut cx, |editor, cx| {
+                        let title = definitions
+                            .iter()
+                            .find_map(|definition| match definition {
+                                GoToDefinitionLink::Text(link) => {
+                                    link.origin.as_ref().map(|origin| {
+                                        let buffer = origin.buffer.read(cx);
+                                        format!(
+                                            "Definitions for {}",
+                                            buffer
+                                                .text_for_range(origin.range.clone())
+                                                .collect::<String>()
+                                        )
+                                    })
+                                }
+                                GoToDefinitionLink::InlayHint(_, _) => None,
+                            })
+                            .unwrap_or("Definitions".to_string());
+                        let location_tasks = definitions
+                            .into_iter()
+                            .map(|definition| match definition {
+                                GoToDefinitionLink::Text(link) => {
+                                    Task::Ready(Some(Ok(Some(link.target))))
+                                }
+                                GoToDefinitionLink::InlayHint(lsp_location, server_id) => {
+                                    editor.compute_target_location(lsp_location, server_id, cx)
+                                }
+                            })
+                            .collect::<Vec<_>>();
+                        (title, location_tasks)
                     })
-                    .unwrap_or("Definitions".to_owned());
-                let locations = definitions
+                    .context("location tasks preparation")?;
+
+                let locations = futures::future::join_all(location_tasks)
+                    .await
                     .into_iter()
-                    .map(|definition| definition.target)
-                    .collect();
-                workspace.update(cx, |workspace, cx| {
+                    .filter_map(|location| location.transpose())
+                    .collect::<Result<_>>()
+                    .context("location tasks")?;
+                workspace.update(&mut cx, |workspace, cx| {
                     Self::open_locations_in_multibuffer(
                         workspace, locations, replica_id, title, split, cx,
                     )
                 });
-            });
+
+                anyhow::Ok(())
+            })
+            .detach_and_log_err(cx);
         }
     }
 
+    fn compute_target_location(
+        &self,
+        lsp_location: lsp::Location,
+        server_id: LanguageServerId,
+        cx: &mut ViewContext<Editor>,
+    ) -> Task<anyhow::Result<Option<Location>>> {
+        let Some(project) = self.project.clone() else {
+            return Task::Ready(Some(Ok(None)));
+        };
+
+        cx.spawn(move |editor, mut cx| async move {
+            let location_task = editor.update(&mut cx, |editor, cx| {
+                project.update(cx, |project, cx| {
+                    let language_server_name =
+                        editor.buffer.read(cx).as_singleton().and_then(|buffer| {
+                            project
+                                .language_server_for_buffer(buffer.read(cx), server_id, cx)
+                                .map(|(_, lsp_adapter)| {
+                                    LanguageServerName(Arc::from(lsp_adapter.name()))
+                                })
+                        });
+                    language_server_name.map(|language_server_name| {
+                        project.open_local_buffer_via_lsp(
+                            lsp_location.uri.clone(),
+                            server_id,
+                            language_server_name,
+                            cx,
+                        )
+                    })
+                })
+            })?;
+            let location = match location_task {
+                Some(task) => Some({
+                    let target_buffer_handle = task.await.context("open local buffer")?;
+                    let range = {
+                        target_buffer_handle.update(&mut cx, |target_buffer, _| {
+                            let target_start = target_buffer.clip_point_utf16(
+                                point_from_lsp(lsp_location.range.start),
+                                Bias::Left,
+                            );
+                            let target_end = target_buffer.clip_point_utf16(
+                                point_from_lsp(lsp_location.range.end),
+                                Bias::Left,
+                            );
+                            target_buffer.anchor_after(target_start)
+                                ..target_buffer.anchor_before(target_end)
+                        })
+                    };
+                    Location {
+                        buffer: target_buffer_handle,
+                        range,
+                    }
+                }),
+                None => None,
+            };
+            Ok(location)
+        })
+    }
+
     pub fn find_all_references(
         workspace: &mut Workspace,
         _: &FindAllReferences,

crates/editor/src/element.rs πŸ”—

@@ -395,9 +395,7 @@ impl EditorElement {
 
         update_go_to_definition_link(
             editor,
-            point
-                .map(GoToDefinitionTrigger::Text)
-                .unwrap_or(GoToDefinitionTrigger::None),
+            point.map(GoToDefinitionTrigger::Text),
             cmd,
             shift,
             cx,
@@ -468,7 +466,7 @@ impl EditorElement {
                 Some(point) => {
                     update_go_to_definition_link(
                         editor,
-                        GoToDefinitionTrigger::Text(point),
+                        Some(GoToDefinitionTrigger::Text(point)),
                         cmd,
                         shift,
                         cx,
@@ -487,7 +485,7 @@ impl EditorElement {
                 }
             }
         } else {
-            update_go_to_definition_link(editor, GoToDefinitionTrigger::None, cmd, shift, cx);
+            update_go_to_definition_link(editor, None, cmd, shift, cx);
             hover_at(editor, None, cx);
         }
 

crates/editor/src/hover_popover.rs πŸ”—

@@ -57,19 +57,30 @@ pub struct InlayHover {
 
 pub fn find_hovered_hint_part(
     label_parts: Vec<InlayHintLabelPart>,
+    padding_left: bool,
+    padding_right: bool,
     hint_range: Range<InlayOffset>,
     hovered_offset: InlayOffset,
 ) -> Option<(InlayHintLabelPart, Range<InlayOffset>)> {
     if hovered_offset >= hint_range.start && hovered_offset <= hint_range.end {
         let mut hovered_character = (hovered_offset - hint_range.start).0;
         let mut part_start = hint_range.start;
-        for part in label_parts {
+        let last_label_part_index = label_parts.len() - 1;
+        for (i, part) in label_parts.into_iter().enumerate() {
             let part_len = part.value.chars().count();
             if hovered_character >= part_len {
                 hovered_character -= part_len;
                 part_start.0 += part_len;
             } else {
-                return Some((part, part_start..InlayOffset(part_start.0 + part_len)));
+                let mut part_end = InlayOffset(part_start.0 + part_len);
+                if padding_left {
+                    part_start.0 += 1;
+                    part_end.0 += 1;
+                }
+                if padding_right && i == last_label_part_index {
+                    part_end.0 -= 1;
+                }
+                return Some((part, part_start..part_end));
             }
         }
     }

crates/editor/src/inlay_hint_cache.rs πŸ”—

@@ -19,6 +19,7 @@ use project::{InlayHint, ResolveState};
 
 use collections::{hash_map, HashMap, HashSet};
 use language::language_settings::InlayHintSettings;
+use sum_tree::Bias;
 use text::ToOffset;
 use util::post_inc;
 
@@ -632,8 +633,8 @@ fn determine_query_ranges(
         return None;
     } else {
         vec![
-            buffer.anchor_before(excerpt_visible_range.start)
-                ..buffer.anchor_after(excerpt_visible_range.end),
+            buffer.anchor_before(snapshot.clip_offset(excerpt_visible_range.start, Bias::Left))
+                ..buffer.anchor_after(snapshot.clip_offset(excerpt_visible_range.end, Bias::Right)),
         ]
     };
 
@@ -651,8 +652,8 @@ fn determine_query_ranges(
             .min(full_excerpt_range_end_offset)
             .min(buffer.len());
         vec![
-            buffer.anchor_before(after_visible_range_start)
-                ..buffer.anchor_after(after_range_end_offset),
+            buffer.anchor_before(snapshot.clip_offset(after_visible_range_start, Bias::Left))
+                ..buffer.anchor_after(snapshot.clip_offset(after_range_end_offset, Bias::Right)),
         ]
     };
 
@@ -668,8 +669,8 @@ fn determine_query_ranges(
             .saturating_sub(excerpt_visible_len)
             .max(full_excerpt_range_start_offset);
         vec![
-            buffer.anchor_before(before_range_start_offset)
-                ..buffer.anchor_after(before_visible_range_end),
+            buffer.anchor_before(snapshot.clip_offset(before_range_start_offset, Bias::Left))
+                ..buffer.anchor_after(snapshot.clip_offset(before_visible_range_end, Bias::Right)),
         ]
     };
 
@@ -6,9 +6,10 @@ use crate::{
 };
 use gpui::{Task, ViewContext};
 use language::{Bias, ToOffset};
+use lsp::LanguageServerId;
 use project::{
-    HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, Location,
-    LocationLink, ResolveState,
+    HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink,
+    ResolveState,
 };
 use std::ops::Range;
 use util::TryFutureExt;
@@ -18,14 +19,19 @@ pub struct LinkGoToDefinitionState {
     pub last_trigger_point: Option<TriggerPoint>,
     pub symbol_range: Option<DocumentRange>,
     pub kind: Option<LinkDefinitionKind>,
-    pub definitions: Vec<LocationLink>,
+    pub definitions: Vec<GoToDefinitionLink>,
     pub task: Option<Task<Option<()>>>,
 }
 
 pub enum GoToDefinitionTrigger {
     Text(DisplayPoint),
-    InlayHint(InlayRange, LocationLink),
-    None,
+    InlayHint(InlayRange, lsp::Location, LanguageServerId),
+}
+
+#[derive(Debug, Clone)]
+pub enum GoToDefinitionLink {
+    Text(LocationLink),
+    InlayHint(lsp::Location, LanguageServerId),
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -38,7 +44,7 @@ pub struct InlayRange {
 #[derive(Debug, Clone)]
 pub enum TriggerPoint {
     Text(Anchor),
-    InlayHint(InlayRange, LocationLink),
+    InlayHint(InlayRange, lsp::Location, LanguageServerId),
 }
 
 #[derive(Debug, Clone, PartialEq, Eq)]
@@ -61,12 +67,12 @@ impl DocumentRange {
                 let point_after_start = range.start.cmp(point, &snapshot.buffer_snapshot).is_le();
                 point_after_start && range.end.cmp(point, &snapshot.buffer_snapshot).is_ge()
             }
-            (DocumentRange::Inlay(range), TriggerPoint::InlayHint(point, _)) => {
+            (DocumentRange::Inlay(range), TriggerPoint::InlayHint(point, _, _)) => {
                 range.highlight_start.cmp(&point.highlight_end).is_le()
                     && range.highlight_end.cmp(&point.highlight_end).is_ge()
             }
             (DocumentRange::Inlay(_), TriggerPoint::Text(_))
-            | (DocumentRange::Text(_), TriggerPoint::InlayHint(_, _)) => false,
+            | (DocumentRange::Text(_), TriggerPoint::InlayHint(_, _, _)) => false,
         }
     }
 }
@@ -75,7 +81,7 @@ impl TriggerPoint {
     fn anchor(&self) -> &Anchor {
         match self {
             TriggerPoint::Text(anchor) => anchor,
-            TriggerPoint::InlayHint(coordinates, _) => &coordinates.inlay_position,
+            TriggerPoint::InlayHint(coordinates, _, _) => &coordinates.inlay_position,
         }
     }
 
@@ -88,14 +94,14 @@ impl TriggerPoint {
                     LinkDefinitionKind::Symbol
                 }
             }
-            TriggerPoint::InlayHint(_, _) => LinkDefinitionKind::Type,
+            TriggerPoint::InlayHint(_, _, _) => LinkDefinitionKind::Type,
         }
     }
 }
 
 pub fn update_go_to_definition_link(
     editor: &mut Editor,
-    origin: GoToDefinitionTrigger,
+    origin: Option<GoToDefinitionTrigger>,
     cmd_held: bool,
     shift_held: bool,
     cx: &mut ViewContext<Editor>,
@@ -105,13 +111,15 @@ pub fn update_go_to_definition_link(
     // Store new mouse point as an anchor
     let snapshot = editor.snapshot(cx);
     let trigger_point = match origin {
-        GoToDefinitionTrigger::Text(p) => {
+        Some(GoToDefinitionTrigger::Text(p)) => {
             Some(TriggerPoint::Text(snapshot.buffer_snapshot.anchor_before(
                 p.to_offset(&snapshot.display_snapshot, Bias::Left),
             )))
         }
-        GoToDefinitionTrigger::InlayHint(p, target) => Some(TriggerPoint::InlayHint(p, target)),
-        GoToDefinitionTrigger::None => None,
+        Some(GoToDefinitionTrigger::InlayHint(p, lsp_location, language_server_id)) => {
+            Some(TriggerPoint::InlayHint(p, lsp_location, language_server_id))
+        }
+        None => None,
     };
 
     // If the new point is the same as the previously stored one, return early
@@ -210,6 +218,15 @@ pub fn update_inlay_link_and_hover_points(
                     ResolveState::Resolved => {
                         match cached_hint.label {
                             project::InlayHintLabel::String(_) => {
+                                let mut highlight_start = hint_start_offset;
+                                let mut highlight_end = hint_end_offset;
+                                if cached_hint.padding_left {
+                                    highlight_start.0 += 1;
+                                    highlight_end.0 += 1;
+                                }
+                                if cached_hint.padding_right {
+                                    highlight_end.0 -= 1;
+                                }
                                 if let Some(tooltip) = cached_hint.tooltip {
                                     hover_popover::hover_at_inlay(
                                         editor,
@@ -230,8 +247,8 @@ pub fn update_inlay_link_and_hover_points(
                                             triggered_from: hovered_offset,
                                             range: InlayRange {
                                                 inlay_position: hovered_hint.position,
-                                                highlight_start: hint_start_offset,
-                                                highlight_end: hint_end_offset,
+                                                highlight_start,
+                                                highlight_end,
                                             },
                                         },
                                         cx,
@@ -243,6 +260,8 @@ pub fn update_inlay_link_and_hover_points(
                                 if let Some((hovered_hint_part, part_range)) =
                                     hover_popover::find_hovered_hint_part(
                                         label_parts,
+                                        cached_hint.padding_left,
+                                        cached_hint.padding_right,
                                         hint_start_offset..hint_end_offset,
                                         hovered_offset,
                                     )
@@ -277,35 +296,25 @@ pub fn update_inlay_link_and_hover_points(
                                         );
                                         hover_updated = true;
                                     }
-                                    if let Some(location) = hovered_hint_part.location {
-                                        if let Some(buffer) =
-                                            cached_hint.position.buffer_id.and_then(|buffer_id| {
-                                                editor.buffer().read(cx).buffer(buffer_id)
-                                            })
-                                        {
-                                            go_to_definition_updated = true;
-                                            update_go_to_definition_link(
-                                                editor,
-                                                GoToDefinitionTrigger::InlayHint(
-                                                    InlayRange {
-                                                        inlay_position: hovered_hint.position,
-                                                        highlight_start: part_range.start,
-                                                        highlight_end: part_range.end,
-                                                    },
-                                                    LocationLink {
-                                                        origin: Some(Location {
-                                                            buffer,
-                                                            range: cached_hint.position
-                                                                ..cached_hint.position,
-                                                        }),
-                                                        target: location,
-                                                    },
-                                                ),
-                                                cmd_held,
-                                                shift_held,
-                                                cx,
-                                            );
-                                        }
+                                    if let Some((language_server_id, location)) =
+                                        hovered_hint_part.location
+                                    {
+                                        go_to_definition_updated = true;
+                                        update_go_to_definition_link(
+                                            editor,
+                                            Some(GoToDefinitionTrigger::InlayHint(
+                                                InlayRange {
+                                                    inlay_position: hovered_hint.position,
+                                                    highlight_start: part_range.start,
+                                                    highlight_end: part_range.end,
+                                                },
+                                                location,
+                                                language_server_id,
+                                            )),
+                                            cmd_held,
+                                            shift_held,
+                                            cx,
+                                        );
                                     }
                                 }
                             }
@@ -317,13 +326,7 @@ pub fn update_inlay_link_and_hover_points(
         }
 
         if !go_to_definition_updated {
-            update_go_to_definition_link(
-                editor,
-                GoToDefinitionTrigger::None,
-                cmd_held,
-                shift_held,
-                cx,
-            );
+            update_go_to_definition_link(editor, None, cmd_held, shift_held, cx);
         }
         if !hover_updated {
             hover_popover::hover_at(editor, None, cx);
@@ -415,17 +418,22 @@ pub fn show_link_definition(
                                     let end = snapshot
                                         .buffer_snapshot
                                         .anchor_in_excerpt(excerpt_id.clone(), origin.range.end);
-
                                     DocumentRange::Text(start..end)
                                 })
                             }),
-                            definition_result,
+                            definition_result
+                                .into_iter()
+                                .map(GoToDefinitionLink::Text)
+                                .collect(),
                         )
                     })
                 }
-                TriggerPoint::InlayHint(trigger_source, trigger_target) => Some((
-                    Some(DocumentRange::Inlay(trigger_source.clone())),
-                    vec![trigger_target.clone()],
+                TriggerPoint::InlayHint(trigger_source, lsp_location, server_id) => Some((
+                    Some(DocumentRange::Inlay(*trigger_source)),
+                    vec![GoToDefinitionLink::InlayHint(
+                        lsp_location.clone(),
+                        *server_id,
+                    )],
                 )),
             };
 
@@ -446,43 +454,52 @@ pub fn show_link_definition(
                     // the current location.
                     let any_definition_does_not_contain_current_location =
                         definitions.iter().any(|definition| {
-                            let target = &definition.target;
-                            if target.buffer == buffer {
-                                let range = &target.range;
-                                // Expand range by one character as lsp definition ranges include positions adjacent
-                                // but not contained by the symbol range
-                                let start = buffer_snapshot.clip_offset(
-                                    range.start.to_offset(&buffer_snapshot).saturating_sub(1),
-                                    Bias::Left,
-                                );
-                                let end = buffer_snapshot.clip_offset(
-                                    range.end.to_offset(&buffer_snapshot) + 1,
-                                    Bias::Right,
-                                );
-                                let offset = buffer_position.to_offset(&buffer_snapshot);
-                                !(start <= offset && end >= offset)
-                            } else {
-                                true
+                            match &definition {
+                                GoToDefinitionLink::Text(link) => {
+                                    if link.target.buffer == buffer {
+                                        let range = &link.target.range;
+                                        // Expand range by one character as lsp definition ranges include positions adjacent
+                                        // but not contained by the symbol range
+                                        let start = buffer_snapshot.clip_offset(
+                                            range
+                                                .start
+                                                .to_offset(&buffer_snapshot)
+                                                .saturating_sub(1),
+                                            Bias::Left,
+                                        );
+                                        let end = buffer_snapshot.clip_offset(
+                                            range.end.to_offset(&buffer_snapshot) + 1,
+                                            Bias::Right,
+                                        );
+                                        let offset = buffer_position.to_offset(&buffer_snapshot);
+                                        !(start <= offset && end >= offset)
+                                    } else {
+                                        true
+                                    }
+                                }
+                                GoToDefinitionLink::InlayHint(_, _) => true,
                             }
                         });
 
                     if any_definition_does_not_contain_current_location {
                         // Highlight symbol using theme link definition highlight style
                         let style = theme::current(cx).editor.link_definition;
-                        let highlight_range = symbol_range.unwrap_or_else(|| match trigger_point {
-                            TriggerPoint::Text(trigger_anchor) => {
-                                let snapshot = &snapshot.buffer_snapshot;
-                                // If no symbol range returned from language server, use the surrounding word.
-                                let (offset_range, _) = snapshot.surrounding_word(trigger_anchor);
-                                DocumentRange::Text(
-                                    snapshot.anchor_before(offset_range.start)
-                                        ..snapshot.anchor_after(offset_range.end),
-                                )
-                            }
-                            TriggerPoint::InlayHint(inlay_coordinates, _) => {
-                                DocumentRange::Inlay(inlay_coordinates)
-                            }
-                        });
+                        let highlight_range =
+                            symbol_range.unwrap_or_else(|| match &trigger_point {
+                                TriggerPoint::Text(trigger_anchor) => {
+                                    let snapshot = &snapshot.buffer_snapshot;
+                                    // If no symbol range returned from language server, use the surrounding word.
+                                    let (offset_range, _) =
+                                        snapshot.surrounding_word(*trigger_anchor);
+                                    DocumentRange::Text(
+                                        snapshot.anchor_before(offset_range.start)
+                                            ..snapshot.anchor_after(offset_range.end),
+                                    )
+                                }
+                                TriggerPoint::InlayHint(inlay_coordinates, _, _) => {
+                                    DocumentRange::Inlay(*inlay_coordinates)
+                                }
+                            });
 
                         match highlight_range {
                             DocumentRange::Text(text_range) => this
@@ -647,7 +664,7 @@ mod tests {
         cx.update_editor(|editor, cx| {
             update_go_to_definition_link(
                 editor,
-                GoToDefinitionTrigger::Text(hover_point),
+                Some(GoToDefinitionTrigger::Text(hover_point)),
                 true,
                 true,
                 cx,
@@ -759,7 +776,7 @@ mod tests {
         cx.update_editor(|editor, cx| {
             update_go_to_definition_link(
                 editor,
-                GoToDefinitionTrigger::Text(hover_point),
+                Some(GoToDefinitionTrigger::Text(hover_point)),
                 true,
                 false,
                 cx,
@@ -799,7 +816,7 @@ mod tests {
         cx.update_editor(|editor, cx| {
             update_go_to_definition_link(
                 editor,
-                GoToDefinitionTrigger::Text(hover_point),
+                Some(GoToDefinitionTrigger::Text(hover_point)),
                 true,
                 false,
                 cx,
@@ -827,7 +844,7 @@ mod tests {
         cx.update_editor(|editor, cx| {
             update_go_to_definition_link(
                 editor,
-                GoToDefinitionTrigger::Text(hover_point),
+                Some(GoToDefinitionTrigger::Text(hover_point)),
                 true,
                 false,
                 cx,
@@ -850,7 +867,7 @@ mod tests {
         cx.update_editor(|editor, cx| {
             update_go_to_definition_link(
                 editor,
-                GoToDefinitionTrigger::Text(hover_point),
+                Some(GoToDefinitionTrigger::Text(hover_point)),
                 false,
                 false,
                 cx,
@@ -915,7 +932,7 @@ mod tests {
         cx.update_editor(|editor, cx| {
             update_go_to_definition_link(
                 editor,
-                GoToDefinitionTrigger::Text(hover_point),
+                Some(GoToDefinitionTrigger::Text(hover_point)),
                 true,
                 false,
                 cx,
@@ -935,7 +952,7 @@ mod tests {
         cx.update_editor(|editor, cx| {
             update_go_to_definition_link(
                 editor,
-                GoToDefinitionTrigger::Text(hover_point),
+                Some(GoToDefinitionTrigger::Text(hover_point)),
                 true,
                 false,
                 cx,
@@ -958,6 +975,7 @@ mod tests {
                 // the cached location instead
                 Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
             });
+        cx.foreground().run_until_parked();
         cx.assert_editor_state(indoc! {"
             fn «testˇ»() { do_work(); }
             fn do_work() { test(); }
@@ -1037,7 +1055,7 @@ mod tests {
         cx.update_editor(|editor, cx| {
             update_go_to_definition_link(
                 editor,
-                GoToDefinitionTrigger::Text(hover_point),
+                Some(GoToDefinitionTrigger::Text(hover_point)),
                 true,
                 false,
                 cx,

crates/project/src/lsp_command.rs πŸ”—

@@ -1,6 +1,6 @@
 use crate::{
     DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintLabel,
-    InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Item, Location, LocationLink,
+    InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink,
     MarkupContent, Project, ProjectTransaction, ResolveState,
 };
 use anyhow::{anyhow, Context, Result};
@@ -14,8 +14,8 @@ use language::{
     point_from_lsp, point_to_lsp,
     proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
     range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind,
-    CodeAction, Completion, LanguageServerName, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16,
-    Transaction, Unclipped,
+    CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction,
+    Unclipped,
 };
 use lsp::{DocumentHighlightKind, LanguageServer, LanguageServerId, OneOf, ServerCapabilities};
 use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
@@ -1787,7 +1787,6 @@ impl LspCommand for OnTypeFormatting {
 impl InlayHints {
     pub async fn lsp_to_project_hint(
         lsp_hint: lsp::InlayHint,
-        project: &ModelHandle<Project>,
         buffer_handle: &ModelHandle<Buffer>,
         server_id: LanguageServerId,
         resolve_state: ResolveState,
@@ -1809,15 +1808,9 @@ impl InlayHints {
                 buffer.anchor_after(position)
             }
         });
-        let label = Self::lsp_inlay_label_to_project(
-            &buffer_handle,
-            project,
-            server_id,
-            lsp_hint.label,
-            cx,
-        )
-        .await
-        .context("lsp to project inlay hint conversion")?;
+        let label = Self::lsp_inlay_label_to_project(lsp_hint.label, server_id)
+            .await
+            .context("lsp to project inlay hint conversion")?;
         let padding_left = if force_no_type_left_padding && kind == Some(InlayHintKind::Type) {
             false
         } else {
@@ -1847,72 +1840,14 @@ impl InlayHints {
     }
 
     async fn lsp_inlay_label_to_project(
-        buffer: &ModelHandle<Buffer>,
-        project: &ModelHandle<Project>,
-        server_id: LanguageServerId,
         lsp_label: lsp::InlayHintLabel,
-        cx: &mut AsyncAppContext,
+        server_id: LanguageServerId,
     ) -> anyhow::Result<InlayHintLabel> {
         let label = match lsp_label {
             lsp::InlayHintLabel::String(s) => InlayHintLabel::String(s),
             lsp::InlayHintLabel::LabelParts(lsp_parts) => {
-                let mut parts_data = Vec::with_capacity(lsp_parts.len());
-                buffer.update(cx, |buffer, cx| {
-                    for lsp_part in lsp_parts {
-                        let location_buffer_task = match &lsp_part.location {
-                            Some(lsp_location) => {
-                                let location_buffer_task = project.update(cx, |project, cx| {
-                                    let language_server_name = project
-                                        .language_server_for_buffer(buffer, server_id, cx)
-                                        .map(|(_, lsp_adapter)| {
-                                            LanguageServerName(Arc::from(lsp_adapter.name()))
-                                        });
-                                    language_server_name.map(|language_server_name| {
-                                        project.open_local_buffer_via_lsp(
-                                            lsp_location.uri.clone(),
-                                            server_id,
-                                            language_server_name,
-                                            cx,
-                                        )
-                                    })
-                                });
-                                Some(lsp_location.clone()).zip(location_buffer_task)
-                            }
-                            None => None,
-                        };
-
-                        parts_data.push((lsp_part, location_buffer_task));
-                    }
-                });
-
-                let mut parts = Vec::with_capacity(parts_data.len());
-                for (lsp_part, location_buffer_task) in parts_data {
-                    let location = match location_buffer_task {
-                        Some((lsp_location, target_buffer_handle_task)) => {
-                            let target_buffer_handle = target_buffer_handle_task
-                                .await
-                                .context("resolving location for label part buffer")?;
-                            let range = cx.read(|cx| {
-                                let target_buffer = target_buffer_handle.read(cx);
-                                let target_start = target_buffer.clip_point_utf16(
-                                    point_from_lsp(lsp_location.range.start),
-                                    Bias::Left,
-                                );
-                                let target_end = target_buffer.clip_point_utf16(
-                                    point_from_lsp(lsp_location.range.end),
-                                    Bias::Left,
-                                );
-                                target_buffer.anchor_after(target_start)
-                                    ..target_buffer.anchor_before(target_end)
-                            });
-                            Some(Location {
-                                buffer: target_buffer_handle,
-                                range,
-                            })
-                        }
-                        None => None,
-                    };
-
+                let mut parts = Vec::with_capacity(lsp_parts.len());
+                for lsp_part in lsp_parts {
                     parts.push(InlayHintLabelPart {
                         value: lsp_part.value,
                         tooltip: lsp_part.tooltip.map(|tooltip| match tooltip {
@@ -1929,7 +1864,7 @@ impl InlayHints {
                                 })
                             }
                         }),
-                        location,
+                        location: Some(server_id).zip(lsp_part.location),
                     });
                 }
                 InlayHintLabel::LabelParts(parts)
@@ -1939,7 +1874,7 @@ impl InlayHints {
         Ok(label)
     }
 
-    pub fn project_to_proto_hint(response_hint: InlayHint, cx: &AppContext) -> proto::InlayHint {
+    pub fn project_to_proto_hint(response_hint: InlayHint) -> proto::InlayHint {
         let (state, lsp_resolve_state) = match response_hint.resolve_state {
             ResolveState::Resolved => (0, None),
             ResolveState::CanResolve(server_id, resolve_data) => (
@@ -1969,7 +1904,11 @@ impl InlayHints {
                     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 {
+                            parts: label_parts.into_iter().map(|label_part| {
+                                let location_url = label_part.location.as_ref().map(|(_, location)| location.uri.to_string());
+                                let location_range_start = label_part.location.as_ref().map(|(_, location)| point_from_lsp(location.range.start).0).map(|point| proto::PointUtf16 { row: point.row, column: point.column });
+                                let location_range_end = label_part.location.as_ref().map(|(_, location)| point_from_lsp(location.range.end).0).map(|point| proto::PointUtf16 { row: point.row, column: point.column });
+                                proto::InlayHintLabelPart {
                                 value: label_part.value,
                                 tooltip: label_part.tooltip.map(|tooltip| {
                                     let proto_tooltip = match tooltip {
@@ -1981,12 +1920,11 @@ impl InlayHints {
                                     };
                                     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()
+                                location_url,
+                                location_range_start,
+                                location_range_end,
+                                language_server_id: label_part.location.as_ref().map(|(server_id, _)| server_id.0 as u64),
+                            }}).collect()
                         })
                     }
                 }),
@@ -1994,16 +1932,12 @@ impl InlayHints {
             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::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::inlay_hint_tooltip::Content::MarkupContent(proto::MarkupContent {
+                            is_markdown: markup_content.kind == HoverBlockKind::Markdown,
+                            value: markup_content.value,
+                        })
                     }
                 };
                 proto::InlayHintTooltip {
@@ -2014,16 +1948,7 @@ impl InlayHints {
         }
     }
 
-    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")?;
+    pub fn proto_to_project_hint(message_hint: proto::InlayHint) -> anyhow::Result<InlayHint> {
         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:?}",)
         });
@@ -2064,9 +1989,6 @@ impl InlayHints {
                 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 {
@@ -2087,19 +2009,35 @@ impl InlayHints {
                                 }),
                                 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,
+                            location: {
+                                match part
+                                    .location_url
+                                    .zip(
+                                        part.location_range_start.and_then(|start| {
+                                            Some(start..part.location_range_end?)
+                                        }),
+                                    )
+                                    .zip(part.language_server_id)
+                                {
+                                    Some(((uri, range), server_id)) => Some((
+                                        LanguageServerId(server_id as usize),
+                                        lsp::Location {
+                                            uri: lsp::Url::parse(&uri)
+                                                .context("invalid uri in hint part {part:?}")?,
+                                            range: lsp::Range::new(
+                                                point_to_lsp(PointUtf16::new(
+                                                    range.start.row,
+                                                    range.start.column,
+                                                )),
+                                                point_to_lsp(PointUtf16::new(
+                                                    range.end.row,
+                                                    range.end.column,
+                                                )),
+                                            ),
+                                        },
+                                    )),
+                                    None => None,
+                                }
                             },
                         });
                     }
@@ -2132,12 +2070,7 @@ impl InlayHints {
         })
     }
 
-    pub fn project_to_lsp_hint(
-        hint: InlayHint,
-        project: &ModelHandle<Project>,
-        snapshot: &BufferSnapshot,
-        cx: &AsyncAppContext,
-    ) -> lsp::InlayHint {
+    pub fn project_to_lsp_hint(hint: InlayHint, snapshot: &BufferSnapshot) -> lsp::InlayHint {
         lsp::InlayHint {
             position: point_to_lsp(hint.position.to_point_utf16(snapshot)),
             kind: hint.kind.map(|kind| match kind {
@@ -2190,22 +2123,7 @@ impl InlayHints {
                                     }
                                 })
                             }),
-                            location: part.location.and_then(|location| {
-                                let (path, location_snapshot) = cx.read(|cx| {
-                                    let buffer = location.buffer.read(cx);
-                                    let project_path = buffer.project_path(cx)?;
-                                    let location_snapshot = buffer.snapshot();
-                                    let path = project.read(cx).absolute_path(&project_path, cx);
-                                    path.zip(Some(location_snapshot))
-                                })?;
-                                Some(lsp::Location::new(
-                                    lsp::Url::from_file_path(path).unwrap(),
-                                    range_to_lsp(
-                                        location.range.start.to_point_utf16(&location_snapshot)
-                                            ..location.range.end.to_point_utf16(&location_snapshot),
-                                    ),
-                                ))
-                            }),
+                            location: part.location.map(|(_, location)| location),
                             command: None,
                         })
                         .collect(),
@@ -2299,12 +2217,10 @@ impl LspCommand for InlayHints {
                 ResolveState::Resolved
             };
 
-            let project = project.clone();
             let buffer = buffer.clone();
             cx.spawn(|mut cx| async move {
                 InlayHints::lsp_to_project_hint(
                     lsp_hint,
-                    &project,
                     &buffer,
                     server_id,
                     resolve_state,
@@ -2359,12 +2275,12 @@ impl LspCommand for InlayHints {
         _: &mut Project,
         _: PeerId,
         buffer_version: &clock::Global,
-        cx: &mut AppContext,
+        _: &mut AppContext,
     ) -> proto::InlayHintsResponse {
         proto::InlayHintsResponse {
             hints: response
                 .into_iter()
-                .map(|response_hint| InlayHints::project_to_proto_hint(response_hint, cx))
+                .map(|response_hint| InlayHints::project_to_proto_hint(response_hint))
                 .collect(),
             version: serialize_version(buffer_version),
         }
@@ -2373,7 +2289,7 @@ impl LspCommand for InlayHints {
     async fn response_from_proto(
         self,
         message: proto::InlayHintsResponse,
-        project: ModelHandle<Project>,
+        _: ModelHandle<Project>,
         buffer: ModelHandle<Buffer>,
         mut cx: AsyncAppContext,
     ) -> anyhow::Result<Vec<InlayHint>> {
@@ -2385,7 +2301,7 @@ impl LspCommand for InlayHints {
 
         let mut hints = Vec::new();
         for message_hint in message.hints {
-            hints.push(InlayHints::proto_to_project_hint(message_hint, &project, &mut cx).await?);
+            hints.push(InlayHints::proto_to_project_hint(message_hint)?);
         }
 
         Ok(hints)

crates/project/src/project.rs πŸ”—

@@ -369,7 +369,7 @@ pub enum InlayHintLabel {
 pub struct InlayHintLabelPart {
     pub value: String,
     pub tooltip: Option<InlayHintLabelPartTooltip>,
-    pub location: Option<Location>,
+    pub location: Option<(LanguageServerId, lsp::Location)>,
 }
 
 #[derive(Debug, Clone, PartialEq, Eq)]
@@ -1708,7 +1708,7 @@ impl Project {
     }
 
     /// LanguageServerName is owned, because it is inserted into a map
-    fn open_local_buffer_via_lsp(
+    pub fn open_local_buffer_via_lsp(
         &mut self,
         abs_path: lsp::Url,
         language_server_id: LanguageServerId,
@@ -5069,16 +5069,15 @@ impl Project {
             }
 
             let buffer_snapshot = buffer.snapshot();
-            cx.spawn(|project, mut cx| async move {
+            cx.spawn(|_, mut cx| async move {
                 let resolve_task = lang_server.request::<lsp::request::InlayHintResolveRequest>(
-                    InlayHints::project_to_lsp_hint(hint, &project, &buffer_snapshot, &cx),
+                    InlayHints::project_to_lsp_hint(hint, &buffer_snapshot),
                 );
                 let resolved_hint = resolve_task
                     .await
                     .context("inlay hint resolve LSP request")?;
                 let resolved_hint = InlayHints::lsp_to_project_hint(
                     resolved_hint,
-                    &project,
                     &buffer_handle,
                     server_id,
                     ResolveState::Resolved,
@@ -5094,19 +5093,16 @@ impl Project {
                 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.clone(), cx)),
+                hint: Some(InlayHints::project_to_proto_hint(hint.clone())),
             };
-            cx.spawn(|project, mut cx| async move {
+            cx.spawn(|_, _| 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
-                            .context("inlay hints proto resolve response conversion")
-                    }
+                    Some(resolved_hint) => InlayHints::proto_to_project_hint(resolved_hint)
+                        .context("inlay hints proto resolve response conversion"),
                     None => Ok(hint),
                 }
             })
@@ -7091,8 +7087,7 @@ impl Project {
             .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
+        let hint = InlayHints::proto_to_project_hint(proto_hint)
             .context("resolved proto inlay hint conversion")?;
         let buffer = this.update(&mut cx, |this, cx| {
             this.opened_buffers
@@ -7111,10 +7106,8 @@ impl Project {
             })
             .await
             .context("inlay hints fetch")?;
-        let resolved_hint = cx.read(|cx| InlayHints::project_to_proto_hint(response_hint, cx));
-
         Ok(proto::ResolveInlayHintResponse {
-            hint: Some(resolved_hint),
+            hint: Some(InlayHints::project_to_proto_hint(response_hint)),
         })
     }
 
@@ -7882,7 +7875,7 @@ impl Project {
         self.language_servers_for_buffer(buffer, cx).next()
     }
 
-    fn language_server_for_buffer(
+    pub fn language_server_for_buffer(
         &self,
         buffer: &Buffer,
         server_id: LanguageServerId,

crates/rpc/proto/zed.proto πŸ”—

@@ -773,7 +773,10 @@ message InlayHintLabelParts {
 message InlayHintLabelPart {
     string value = 1;
     InlayHintLabelPartTooltip tooltip = 2;
-    Location location = 3;
+    optional string location_url = 3;
+    PointUtf16 location_range_start = 4;
+    PointUtf16 location_range_end = 5;
+    optional uint64 language_server_id = 6;
 }
 
 message InlayHintTooltip {