Store inlay hint resolve data

This commit is contained in:
Kirill Bulatov 2023-08-18 15:54:30 +03:00
parent e4b78e322e
commit 3434990b70
4 changed files with 134 additions and 53 deletions

View file

@ -1109,7 +1109,7 @@ mod tests {
use super::*; use super::*;
use crate::{InlayId, MultiBuffer}; use crate::{InlayId, MultiBuffer};
use gpui::AppContext; use gpui::AppContext;
use project::{InlayHint, InlayHintLabel}; use project::{InlayHint, InlayHintLabel, ResolveState};
use rand::prelude::*; use rand::prelude::*;
use settings::SettingsStore; use settings::SettingsStore;
use std::{cmp::Reverse, env, sync::Arc}; use std::{cmp::Reverse, env, sync::Arc};
@ -1131,6 +1131,7 @@ mod tests {
padding_right: false, padding_right: false,
tooltip: None, tooltip: None,
kind: None, kind: None,
resolve_state: ResolveState::Resolved,
}, },
) )
.text .text
@ -1151,6 +1152,7 @@ mod tests {
padding_right: true, padding_right: true,
tooltip: None, tooltip: None,
kind: None, kind: None,
resolve_state: ResolveState::Resolved,
}, },
) )
.text .text
@ -1171,6 +1173,7 @@ mod tests {
padding_right: false, padding_right: false,
tooltip: None, tooltip: None,
kind: None, kind: None,
resolve_state: ResolveState::Resolved,
}, },
) )
.text .text
@ -1191,6 +1194,7 @@ mod tests {
padding_right: true, padding_right: true,
tooltip: None, tooltip: None,
kind: None, kind: None,
resolve_state: ResolveState::Resolved,
}, },
) )
.text .text

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintLabel, DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintLabel,
InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink, InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink,
MarkupContent, Project, ProjectTransaction, MarkupContent, Project, ProjectTransaction, ResolveState,
}; };
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
@ -1817,7 +1817,8 @@ impl LspCommand for InlayHints {
server_id: LanguageServerId, server_id: LanguageServerId,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<Vec<InlayHint>> { ) -> Result<Vec<InlayHint>> {
let (lsp_adapter, _) = language_server_for_buffer(&project, &buffer, server_id, &mut cx)?; let (lsp_adapter, lsp_server) =
language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
// `typescript-language-server` adds padding to the left for type hints, turning // `typescript-language-server` adds padding to the left for type hints, turning
// `const foo: boolean` into `const foo : boolean` which looks odd. // `const foo: boolean` into `const foo : boolean` which looks odd.
// `rust-analyzer` does not have the padding for this case, and we have to accomodate both. // `rust-analyzer` does not have the padding for this case, and we have to accomodate both.
@ -1833,6 +1834,15 @@ impl LspCommand for InlayHints {
.unwrap_or_default() .unwrap_or_default()
.into_iter() .into_iter()
.map(|lsp_hint| { .map(|lsp_hint| {
let resolve_state = match lsp_server.capabilities().inlay_hint_provider {
Some(lsp::OneOf::Right(lsp::InlayHintServerCapabilities::Options(
lsp::InlayHintOptions {
resolve_provider: Some(true),
..
},
))) => ResolveState::CanResolve(lsp_hint.data),
_ => ResolveState::Resolved,
};
let kind = lsp_hint.kind.and_then(|kind| match kind { let kind = lsp_hint.kind.and_then(|kind| match kind {
lsp::InlayHintKind::TYPE => Some(InlayHintKind::Type), lsp::InlayHintKind::TYPE => Some(InlayHintKind::Type),
lsp::InlayHintKind::PARAMETER => Some(InlayHintKind::Parameter), lsp::InlayHintKind::PARAMETER => Some(InlayHintKind::Parameter),
@ -1910,6 +1920,7 @@ impl LspCommand for InlayHints {
}) })
} }
}), }),
resolve_state,
} }
}) })
.collect()) .collect())
@ -1959,57 +1970,69 @@ impl LspCommand for InlayHints {
proto::InlayHintsResponse { proto::InlayHintsResponse {
hints: response hints: response
.into_iter() .into_iter()
.map(|response_hint| proto::InlayHint { .map(|response_hint| {
position: Some(language::proto::serialize_anchor(&response_hint.position)), let (state, lsp_resolve_state) = match response_hint.resolve_state {
padding_left: response_hint.padding_left, ResolveState::CanResolve(resolve_data) => {
padding_right: response_hint.padding_right, (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 }))
label: Some(proto::InlayHintLabel { }
label: Some(match response_hint.label { ResolveState::Resolved => (1, None),
InlayHintLabel::String(s) => proto::inlay_hint_label::Label::Value(s), ResolveState::Resolving => (2, None),
InlayHintLabel::LabelParts(label_parts) => { };
proto::inlay_hint_label::Label::LabelParts(proto::InlayHintLabelParts { let resolve_state = Some(proto::ResolveState {
parts: label_parts.into_iter().map(|label_part| proto::InlayHintLabelPart { state, lsp_resolve_state
value: label_part.value, });
tooltip: label_part.tooltip.map(|tooltip| { proto::InlayHint {
let proto_tooltip = match tooltip { position: Some(language::proto::serialize_anchor(&response_hint.position)),
InlayHintLabelPartTooltip::String(s) => proto::inlay_hint_label_part_tooltip::Content::Value(s), padding_left: response_hint.padding_left,
InlayHintLabelPartTooltip::MarkupContent(markup_content) => proto::inlay_hint_label_part_tooltip::Content::MarkupContent(proto::MarkupContent { padding_right: response_hint.padding_right,
kind: markup_content.kind, label: Some(proto::InlayHintLabel {
value: markup_content.value, label: Some(match response_hint.label {
}), InlayHintLabel::String(s) => proto::inlay_hint_label::Label::Value(s),
}; InlayHintLabel::LabelParts(label_parts) => {
proto::InlayHintLabelPartTooltip {content: Some(proto_tooltip)} proto::inlay_hint_label::Label::LabelParts(proto::InlayHintLabelParts {
}), parts: label_parts.into_iter().map(|label_part| proto::InlayHintLabelPart {
location: label_part.location.map(|location| proto::Location { value: label_part.value,
start: Some(serialize_anchor(&location.range.start)), tooltip: label_part.tooltip.map(|tooltip| {
end: Some(serialize_anchor(&location.range.end)), let proto_tooltip = match tooltip {
buffer_id: location.buffer.read(cx).remote_id(), 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 {
}).collect() 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,
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),
}
}),
})
.collect(), .collect(),
version: serialize_version(buffer_version), version: serialize_version(buffer_version),
} }
@ -2021,7 +2044,7 @@ impl LspCommand for InlayHints {
project: ModelHandle<Project>, project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>, buffer: ModelHandle<Buffer>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<Vec<InlayHint>> { ) -> anyhow::Result<Vec<InlayHint>> {
buffer buffer
.update(&mut cx, |buffer, _| { .update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(&message.version)) buffer.wait_for_version(deserialize_version(&message.version))
@ -2035,6 +2058,27 @@ impl LspCommand for InlayHints {
.as_ref() .as_ref()
.and_then(|location| location.buffer_id) .and_then(|location| location.buffer_id)
.context("missing 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 { let hint = InlayHint {
buffer_id, buffer_id,
position: message_hint position: message_hint
@ -2103,6 +2147,7 @@ impl LspCommand for InlayHints {
} }
}) })
}), }),
resolve_state,
}; };
hints.push(hint); hints.push(hint);

View file

@ -342,6 +342,22 @@ pub struct InlayHint {
pub padding_left: bool, pub padding_left: bool,
pub padding_right: bool, pub padding_right: bool,
pub tooltip: Option<InlayHintTooltip>, pub tooltip: Option<InlayHintTooltip>,
pub resolve_state: ResolveState,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResolveState {
Resolved,
CanResolve(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 { impl InlayHint {

View file

@ -754,6 +754,7 @@ message InlayHint {
bool padding_left = 4; bool padding_left = 4;
bool padding_right = 5; bool padding_right = 5;
InlayHintTooltip tooltip = 6; InlayHintTooltip tooltip = 6;
ResolveState resolve_state = 7;
} }
message InlayHintLabel { message InlayHintLabel {
@ -787,6 +788,21 @@ message InlayHintLabelPartTooltip {
} }
} }
message ResolveState {
State state = 1;
LspResolveState lsp_resolve_state = 2;
enum State {
Resolved = 0;
CanResolve = 1;
Resolving = 2;
}
message LspResolveState {
string value = 1;
}
}
message RefreshInlayHints { message RefreshInlayHints {
uint64 project_id = 1; uint64 project_id = 1;
} }