Resolve inlay hint data

Skip every propery set in inlay hint client resolve capabilities,
reducing overall json footprint.
This commit is contained in:
Kirill Bulatov 2023-08-28 00:11:26 +03:00
parent 0e002fe5c6
commit e07fbabcfe
9 changed files with 200 additions and 51 deletions

View file

@ -52,6 +52,42 @@ pub struct InlayHintsConfig {
pub closure_style: ClosureStyle, pub closure_style: ClosureStyle,
pub max_length: Option<usize>, pub max_length: Option<usize>,
pub closing_brace_hints_min_lines: Option<usize>, pub closing_brace_hints_min_lines: Option<usize>,
pub fields_to_resolve: InlayFieldsToResolve,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InlayFieldsToResolve {
pub client_capability_fields: Vec<String>,
}
impl InlayFieldsToResolve {
pub const fn empty() -> Self {
Self { client_capability_fields: Vec::new() }
}
pub fn is_empty(&self) -> bool {
self.client_capability_fields.is_empty()
}
pub fn resolve_text_edits(&self) -> bool {
self.client_capability_fields.iter().find(|s| s.as_str() == "textEdits").is_some()
}
pub fn resolve_hint_tooltip(&self) -> bool {
self.client_capability_fields.iter().find(|s| s.as_str() == "tooltip").is_some()
}
pub fn resolve_label_tooltip(&self) -> bool {
self.client_capability_fields.iter().find(|s| s.as_str() == "label.tooltip").is_some()
}
pub fn resolve_label_location(&self) -> bool {
self.client_capability_fields.iter().find(|s| s.as_str() == "label.location").is_some()
}
pub fn resolve_label_command(&self) -> bool {
self.client_capability_fields.iter().find(|s| s.as_str() == "label.command").is_some()
}
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
@ -529,6 +565,7 @@ fn closure_has_block_body(closure: &ast::ClosureExpr) -> bool {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use expect_test::Expect; use expect_test::Expect;
use hir::ClosureStyle; use hir::ClosureStyle;
use itertools::Itertools; use itertools::Itertools;
@ -538,7 +575,7 @@ mod tests {
use crate::DiscriminantHints; use crate::DiscriminantHints;
use crate::{fixture, inlay_hints::InlayHintsConfig, LifetimeElisionHints}; use crate::{fixture, inlay_hints::InlayHintsConfig, LifetimeElisionHints};
use super::ClosureReturnTypeHints; use super::{ClosureReturnTypeHints, InlayFieldsToResolve};
pub(super) const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig { pub(super) const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig {
discriminant_hints: DiscriminantHints::Never, discriminant_hints: DiscriminantHints::Never,
@ -559,6 +596,7 @@ mod tests {
param_names_for_lifetime_elision_hints: false, param_names_for_lifetime_elision_hints: false,
max_length: None, max_length: None,
closing_brace_hints_min_lines: None, closing_brace_hints_min_lines: None,
fields_to_resolve: InlayFieldsToResolve::empty(),
}; };
pub(super) const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig { pub(super) const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig {
type_hints: true, type_hints: true,
@ -567,7 +605,19 @@ mod tests {
closure_return_type_hints: ClosureReturnTypeHints::WithBlock, closure_return_type_hints: ClosureReturnTypeHints::WithBlock,
binding_mode_hints: true, binding_mode_hints: true,
lifetime_elision_hints: LifetimeElisionHints::Always, lifetime_elision_hints: LifetimeElisionHints::Always,
..DISABLED_CONFIG discriminant_hints: DiscriminantHints::Never,
render_colons: false,
closure_capture_hints: false,
adjustment_hints: AdjustmentHints::Never,
adjustment_hints_mode: AdjustmentHintsMode::Prefix,
adjustment_hints_hide_outside_unsafe: false,
hide_named_constructor_hints: false,
hide_closure_initialization_hints: false,
closure_style: ClosureStyle::ImplFn,
param_names_for_lifetime_elision_hints: false,
max_length: None,
closing_brace_hints_min_lines: None,
fields_to_resolve: InlayFieldsToResolve::empty(),
}; };
#[track_caller] #[track_caller]

View file

@ -91,9 +91,9 @@ pub use crate::{
MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind,
}, },
inlay_hints::{ inlay_hints::{
AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints, InlayHint, AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints,
InlayHintLabel, InlayHintLabelPart, InlayHintPosition, InlayHintsConfig, InlayKind, InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintPosition,
InlayTooltip, LifetimeElisionHints, InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints,
}, },
join_lines::JoinLinesConfig, join_lines::JoinLinesConfig,
markup::Markup, markup::Markup,

View file

@ -12,6 +12,7 @@ use ide_db::{
}; };
use syntax::{AstNode, SyntaxKind::*, TextRange, T}; use syntax::{AstNode, SyntaxKind::*, TextRange, T};
use crate::inlay_hints::InlayFieldsToResolve;
use crate::{ use crate::{
hover::hover_for_definition, hover::hover_for_definition,
inlay_hints::AdjustmentHintsMode, inlay_hints::AdjustmentHintsMode,
@ -125,6 +126,7 @@ impl StaticIndex<'_> {
max_length: Some(25), max_length: Some(25),
closure_capture_hints: false, closure_capture_hints: false,
closing_brace_hints_min_lines: Some(25), closing_brace_hints_min_lines: Some(25),
fields_to_resolve: InlayFieldsToResolve::empty(),
}, },
file_id, file_id,
None, None,

View file

@ -15,7 +15,10 @@ use hir_def::{
hir::{ExprId, PatId}, hir::{ExprId, PatId},
}; };
use hir_ty::{Interner, Substitution, TyExt, TypeFlags}; use hir_ty::{Interner, Substitution, TyExt, TypeFlags};
use ide::{Analysis, AnnotationConfig, DiagnosticsConfig, InlayHintsConfig, LineCol, RootDatabase}; use ide::{
Analysis, AnnotationConfig, DiagnosticsConfig, InlayFieldsToResolve, InlayHintsConfig, LineCol,
RootDatabase,
};
use ide_db::{ use ide_db::{
base_db::{ base_db::{
salsa::{self, debug::DebugQueryTable, ParallelDatabase}, salsa::{self, debug::DebugQueryTable, ParallelDatabase},
@ -782,6 +785,7 @@ impl flags::AnalysisStats {
closure_style: hir::ClosureStyle::ImplFn, closure_style: hir::ClosureStyle::ImplFn,
max_length: Some(25), max_length: Some(25),
closing_brace_hints_min_lines: Some(20), closing_brace_hints_min_lines: Some(20),
fields_to_resolve: InlayFieldsToResolve::empty(),
}, },
file_id, file_id,
None, None,

View file

@ -13,8 +13,9 @@ use cfg::{CfgAtom, CfgDiff};
use flycheck::FlycheckConfig; use flycheck::FlycheckConfig;
use ide::{ use ide::{
AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode, AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode,
HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayHintsConfig, HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve,
JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, Snippet, SnippetScope, InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind,
Snippet, SnippetScope,
}; };
use ide_db::{ use ide_db::{
imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind}, imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
@ -1335,6 +1336,18 @@ impl Config {
} }
pub fn inlay_hints(&self) -> InlayHintsConfig { pub fn inlay_hints(&self) -> InlayHintsConfig {
let client_capability_fields = self
.caps
.text_document
.as_ref()
.and_then(|text| text.inlay_hint.as_ref())
.and_then(|inlay_hint_caps| inlay_hint_caps.resolve_support.as_ref())
.map(|inlay_resolve| inlay_resolve.properties.iter())
.into_iter()
.flatten()
.cloned()
.collect::<Vec<_>>();
InlayHintsConfig { InlayHintsConfig {
render_colons: self.data.inlayHints_renderColons, render_colons: self.data.inlayHints_renderColons,
type_hints: self.data.inlayHints_typeHints_enable, type_hints: self.data.inlayHints_typeHints_enable,
@ -1395,6 +1408,7 @@ impl Config {
} else { } else {
None None
}, },
fields_to_resolve: InlayFieldsToResolve { client_capability_fields },
} }
} }

View file

@ -11,8 +11,8 @@ use anyhow::Context;
use ide::{ use ide::{
AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, FilePosition, FileRange, AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, FilePosition, FileRange,
HoverAction, HoverGotoTypeData, Query, RangeInfo, ReferenceCategory, Runnable, RunnableKind, HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, RangeInfo, ReferenceCategory,
SingleResolve, SourceChange, TextEdit, Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit,
}; };
use ide_db::SymbolKind; use ide_db::SymbolKind;
use lsp_server::ErrorCode; use lsp_server::ErrorCode;
@ -30,7 +30,7 @@ use serde_json::json;
use stdx::{format_to, never}; use stdx::{format_to, never};
use syntax::{algo, ast, AstNode, TextRange, TextSize}; use syntax::{algo, ast, AstNode, TextRange, TextSize};
use triomphe::Arc; use triomphe::Arc;
use vfs::{AbsPath, AbsPathBuf, VfsPath}; use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath};
use crate::{ use crate::{
cargo_target_spec::CargoTargetSpec, cargo_target_spec::CargoTargetSpec,
@ -1412,17 +1412,71 @@ pub(crate) fn handle_inlay_hints(
snap.analysis snap.analysis
.inlay_hints(&inlay_hints_config, file_id, Some(range))? .inlay_hints(&inlay_hints_config, file_id, Some(range))?
.into_iter() .into_iter()
.map(|it| to_proto::inlay_hint(&snap, &line_index, it)) .map(|it| {
to_proto::inlay_hint(
&snap,
&inlay_hints_config.fields_to_resolve,
&line_index,
file_id,
it,
)
})
.collect::<Cancellable<Vec<_>>>()?, .collect::<Cancellable<Vec<_>>>()?,
)) ))
} }
pub(crate) fn handle_inlay_hints_resolve( pub(crate) fn handle_inlay_hints_resolve(
_snap: GlobalStateSnapshot, snap: GlobalStateSnapshot,
hint: InlayHint, mut original_hint: InlayHint,
) -> anyhow::Result<InlayHint> { ) -> anyhow::Result<InlayHint> {
let _p = profile::span("handle_inlay_hints_resolve"); let _p = profile::span("handle_inlay_hints_resolve");
Ok(hint)
let data = match original_hint.data.take() {
Some(it) => it,
None => return Ok(original_hint),
};
let resolve_data: lsp_ext::InlayHintResolveData = serde_json::from_value(data)?;
let file_id = FileId(resolve_data.file_id);
let line_index = snap.file_line_index(file_id)?;
let range = from_proto::text_range(
&line_index,
lsp_types::Range { start: original_hint.position, end: original_hint.position },
)?;
let range_start = range.start();
let range_end = range.end();
let large_range = TextRange::new(
range_start.checked_sub(1.into()).unwrap_or(range_start),
range_end.checked_add(1.into()).unwrap_or(range_end),
);
let mut forced_resolve_inlay_hints_config = snap.config.inlay_hints();
forced_resolve_inlay_hints_config.fields_to_resolve = InlayFieldsToResolve::empty();
let resolve_hints = snap.analysis.inlay_hints(
&forced_resolve_inlay_hints_config,
file_id,
Some(large_range),
)?;
let mut resolved_hints = resolve_hints
.into_iter()
.filter_map(|it| {
to_proto::inlay_hint(
&snap,
&forced_resolve_inlay_hints_config.fields_to_resolve,
&line_index,
file_id,
it,
)
.ok()
})
.filter(|hint| hint.position == original_hint.position)
.filter(|hint| hint.kind == original_hint.kind);
if let Some(resolved_hint) = resolved_hints.next() {
if resolved_hints.next().is_none() {
return Ok(resolved_hint);
}
}
Ok(original_hint)
} }
pub(crate) fn handle_call_hierarchy_prepare( pub(crate) fn handle_call_hierarchy_prepare(

View file

@ -682,7 +682,9 @@ pub struct CompletionResolveData {
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct InlayHintResolveData {} pub struct InlayHintResolveData {
pub file_id: u32,
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct CompletionImport { pub struct CompletionImport {

View file

@ -8,10 +8,10 @@ use std::{
use ide::{ use ide::{
Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionItem, Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionItem,
CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit, CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit,
Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint, Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel,
InlayHintLabel, InlayHintLabelPart, InlayKind, Markup, NavigationTarget, ReferenceCategory, InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayKind, Markup,
RenameError, Runnable, Severity, SignatureHelp, SnippetEdit, SourceChange, StructureNodeKind, NavigationTarget, ReferenceCategory, RenameError, Runnable, Severity, SignatureHelp,
SymbolKind, TextEdit, TextRange, TextSize, SnippetEdit, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize,
}; };
use itertools::Itertools; use itertools::Itertools;
use serde_json::to_value; use serde_json::to_value;
@ -437,10 +437,22 @@ pub(crate) fn signature_help(
pub(crate) fn inlay_hint( pub(crate) fn inlay_hint(
snap: &GlobalStateSnapshot, snap: &GlobalStateSnapshot,
fields_to_resolve: &InlayFieldsToResolve,
line_index: &LineIndex, line_index: &LineIndex,
file_id: FileId,
inlay_hint: InlayHint, inlay_hint: InlayHint,
) -> Cancellable<lsp_types::InlayHint> { ) -> Cancellable<lsp_types::InlayHint> {
let (label, tooltip) = inlay_hint_label(snap, inlay_hint.label)?; let (label, tooltip) = inlay_hint_label(snap, fields_to_resolve, inlay_hint.label)?;
let data = if fields_to_resolve.is_empty() {
None
} else {
Some(to_value(lsp_ext::InlayHintResolveData { file_id: file_id.0 }).unwrap())
};
let text_edits = if fields_to_resolve.resolve_text_edits() {
None
} else {
inlay_hint.text_edit.map(|it| text_edit_vec(line_index, it))
};
Ok(lsp_types::InlayHint { Ok(lsp_types::InlayHint {
position: match inlay_hint.position { position: match inlay_hint.position {
@ -454,8 +466,8 @@ pub(crate) fn inlay_hint(
InlayKind::Type | InlayKind::Chaining => Some(lsp_types::InlayHintKind::TYPE), InlayKind::Type | InlayKind::Chaining => Some(lsp_types::InlayHintKind::TYPE),
_ => None, _ => None,
}, },
text_edits: inlay_hint.text_edit.map(|it| text_edit_vec(line_index, it)), text_edits,
data: None, data,
tooltip, tooltip,
label, label,
}) })
@ -463,13 +475,15 @@ pub(crate) fn inlay_hint(
fn inlay_hint_label( fn inlay_hint_label(
snap: &GlobalStateSnapshot, snap: &GlobalStateSnapshot,
fields_to_resolve: &InlayFieldsToResolve,
mut label: InlayHintLabel, mut label: InlayHintLabel,
) -> Cancellable<(lsp_types::InlayHintLabel, Option<lsp_types::InlayHintTooltip>)> { ) -> Cancellable<(lsp_types::InlayHintLabel, Option<lsp_types::InlayHintTooltip>)> {
let res = match &*label.parts { let res = match &*label.parts {
[InlayHintLabelPart { linked_location: None, .. }] => { [InlayHintLabelPart { linked_location: None, .. }] => {
let InlayHintLabelPart { text, tooltip, .. } = label.parts.pop().unwrap(); let InlayHintLabelPart { text, tooltip, .. } = label.parts.pop().unwrap();
( let hint_tooltip = if fields_to_resolve.resolve_hint_tooltip() {
lsp_types::InlayHintLabel::String(text), None
} else {
match tooltip { match tooltip {
Some(ide::InlayTooltip::String(s)) => { Some(ide::InlayTooltip::String(s)) => {
Some(lsp_types::InlayHintTooltip::String(s)) Some(lsp_types::InlayHintTooltip::String(s))
@ -481,35 +495,44 @@ fn inlay_hint_label(
})) }))
} }
None => None, None => None,
}, }
) };
(lsp_types::InlayHintLabel::String(text), hint_tooltip)
} }
_ => { _ => {
let parts = label let parts = label
.parts .parts
.into_iter() .into_iter()
.map(|part| { .map(|part| {
part.linked_location.map(|range| location(snap, range)).transpose().map( let tooltip = if fields_to_resolve.resolve_label_tooltip() {
|location| lsp_types::InlayHintLabelPart { None
value: part.text, } else {
tooltip: match part.tooltip { match part.tooltip {
Some(ide::InlayTooltip::String(s)) => { Some(ide::InlayTooltip::String(s)) => {
Some(lsp_types::InlayHintLabelPartTooltip::String(s)) Some(lsp_types::InlayHintLabelPartTooltip::String(s))
} }
Some(ide::InlayTooltip::Markdown(s)) => { Some(ide::InlayTooltip::Markdown(s)) => {
Some(lsp_types::InlayHintLabelPartTooltip::MarkupContent( Some(lsp_types::InlayHintLabelPartTooltip::MarkupContent(
lsp_types::MarkupContent { lsp_types::MarkupContent {
kind: lsp_types::MarkupKind::Markdown, kind: lsp_types::MarkupKind::Markdown,
value: s, value: s,
}, },
)) ))
} }
None => None, None => None,
}, }
location, };
command: None, let location = if fields_to_resolve.resolve_label_location() {
}, None
) } else {
part.linked_location.map(|range| location(snap, range)).transpose()?
};
Ok(lsp_types::InlayHintLabelPart {
value: part.text,
tooltip,
location,
command: None,
})
}) })
.collect::<Cancellable<_>>()?; .collect::<Cancellable<_>>()?;
(lsp_types::InlayHintLabel::LabelParts(parts), None) (lsp_types::InlayHintLabel::LabelParts(parts), None)

View file

@ -1,5 +1,5 @@
<!--- <!---
lsp/ext.rs hash: 149a5be3c5e469d1 lsp/ext.rs hash: 121482ee911854da
If you need to change the above hash to make the test pass, please check if you If you need to change the above hash to make the test pass, please check if you
need to adjust this doc as well and ping this issue: need to adjust this doc as well and ping this issue:
@ -322,7 +322,7 @@ Position[]
```rust ```rust
fn main() { fn main() {
let x: Vec<()>/*cursor here*/ = vec![] let x: Vec<()>/*cursor here*/ = vec![];
} }
``` ```
@ -362,7 +362,7 @@ interface RunnablesParams {
```typescript ```typescript
interface Runnable { interface Runnable {
label: string; label: string;
/// If this Runnable is associated with a specific function/module, etc, the location of this item /// If this Runnable is associated with a specific function/module, etc., the location of this item
location?: LocationLink; location?: LocationLink;
/// Running things is necessary technology specific, `kind` needs to be advertised via server capabilities, /// Running things is necessary technology specific, `kind` needs to be advertised via server capabilities,
// the type of `args` is specific to `kind`. The actual running is handled by the client. // the type of `args` is specific to `kind`. The actual running is handled by the client.