Detailed changes
@@ -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,
@@ -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>,
@@ -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);
}
}
@@ -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)
@@ -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>,
@@ -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;
}
@@ -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,
@@ -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;