Send inlay hint resolve requests

This commit is contained in:
Kirill Bulatov 2023-08-18 21:09:04 +03:00
parent 3434990b70
commit 80e8714241
8 changed files with 705 additions and 343 deletions

View file

@ -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,

View file

@ -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>,

View file

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

View file

@ -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)

View file

@ -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>,

View file

@ -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;
}

View file

@ -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,

View file

@ -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;