diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 2925916741..f61252d84d 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -52,6 +52,42 @@ pub struct InlayHintsConfig { pub closure_style: ClosureStyle, pub max_length: Option, pub closing_brace_hints_min_lines: Option, + pub fields_to_resolve: InlayFieldsToResolve, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct InlayFieldsToResolve { + pub client_capability_fields: Vec, +} + +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)] @@ -529,6 +565,7 @@ fn closure_has_block_body(closure: &ast::ClosureExpr) -> bool { #[cfg(test)] mod tests { + use expect_test::Expect; use hir::ClosureStyle; use itertools::Itertools; @@ -538,7 +575,7 @@ mod tests { use crate::DiscriminantHints; use crate::{fixture, inlay_hints::InlayHintsConfig, LifetimeElisionHints}; - use super::ClosureReturnTypeHints; + use super::{ClosureReturnTypeHints, InlayFieldsToResolve}; pub(super) const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig { discriminant_hints: DiscriminantHints::Never, @@ -559,6 +596,7 @@ mod tests { param_names_for_lifetime_elision_hints: false, max_length: None, closing_brace_hints_min_lines: None, + fields_to_resolve: InlayFieldsToResolve::empty(), }; pub(super) const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig { type_hints: true, @@ -567,7 +605,19 @@ mod tests { closure_return_type_hints: ClosureReturnTypeHints::WithBlock, binding_mode_hints: true, 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] diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index c9cdbff7d7..2b51a81596 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -91,9 +91,9 @@ pub use crate::{ MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, }, inlay_hints::{ - AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints, InlayHint, - InlayHintLabel, InlayHintLabelPart, InlayHintPosition, InlayHintsConfig, InlayKind, - InlayTooltip, LifetimeElisionHints, + AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints, + InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayHintPosition, + InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints, }, join_lines::JoinLinesConfig, markup::Markup, diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index d8696198d3..aabd26da28 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -12,6 +12,7 @@ use ide_db::{ }; use syntax::{AstNode, SyntaxKind::*, TextRange, T}; +use crate::inlay_hints::InlayFieldsToResolve; use crate::{ hover::hover_for_definition, inlay_hints::AdjustmentHintsMode, @@ -125,6 +126,7 @@ impl StaticIndex<'_> { max_length: Some(25), closure_capture_hints: false, closing_brace_hints_min_lines: Some(25), + fields_to_resolve: InlayFieldsToResolve::empty(), }, file_id, None, diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 4a03be1893..1d99cd9534 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -15,7 +15,10 @@ use hir_def::{ hir::{ExprId, PatId}, }; 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::{ base_db::{ salsa::{self, debug::DebugQueryTable, ParallelDatabase}, @@ -782,6 +785,7 @@ impl flags::AnalysisStats { closure_style: hir::ClosureStyle::ImplFn, max_length: Some(25), closing_brace_hints_min_lines: Some(20), + fields_to_resolve: InlayFieldsToResolve::empty(), }, file_id, None, diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 40c50f6d17..f58de03ec4 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -13,8 +13,9 @@ use cfg::{CfgAtom, CfgDiff}; use flycheck::FlycheckConfig; use ide::{ AssistConfig, CallableSnippets, CompletionConfig, DiagnosticsConfig, ExprFillDefaultMode, - HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayHintsConfig, - JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, Snippet, SnippetScope, + HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve, + InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, + Snippet, SnippetScope, }; use ide_db::{ imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind}, @@ -1335,6 +1336,18 @@ impl Config { } 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::>(); + InlayHintsConfig { render_colons: self.data.inlayHints_renderColons, type_hints: self.data.inlayHints_typeHints_enable, @@ -1395,6 +1408,7 @@ impl Config { } else { None }, + fields_to_resolve: InlayFieldsToResolve { client_capability_fields }, } } diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 36158e8115..dbf3a4792d 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -11,8 +11,8 @@ use anyhow::Context; use ide::{ AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, FilePosition, FileRange, - HoverAction, HoverGotoTypeData, Query, RangeInfo, ReferenceCategory, Runnable, RunnableKind, - SingleResolve, SourceChange, TextEdit, + HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, RangeInfo, ReferenceCategory, + Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit, }; use ide_db::SymbolKind; use lsp_server::ErrorCode; @@ -30,7 +30,7 @@ use serde_json::json; use stdx::{format_to, never}; use syntax::{algo, ast, AstNode, TextRange, TextSize}; use triomphe::Arc; -use vfs::{AbsPath, AbsPathBuf, VfsPath}; +use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath}; use crate::{ cargo_target_spec::CargoTargetSpec, @@ -1412,17 +1412,71 @@ pub(crate) fn handle_inlay_hints( snap.analysis .inlay_hints(&inlay_hints_config, file_id, Some(range))? .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::>>()?, )) } pub(crate) fn handle_inlay_hints_resolve( - _snap: GlobalStateSnapshot, - hint: InlayHint, + snap: GlobalStateSnapshot, + mut original_hint: InlayHint, ) -> anyhow::Result { 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( diff --git a/crates/rust-analyzer/src/lsp/ext.rs b/crates/rust-analyzer/src/lsp/ext.rs index d0989b3230..ad56899163 100644 --- a/crates/rust-analyzer/src/lsp/ext.rs +++ b/crates/rust-analyzer/src/lsp/ext.rs @@ -682,7 +682,9 @@ pub struct CompletionResolveData { } #[derive(Debug, Serialize, Deserialize)] -pub struct InlayHintResolveData {} +pub struct InlayHintResolveData { + pub file_id: u32, +} #[derive(Debug, Serialize, Deserialize)] pub struct CompletionImport { diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs index daa7f5fe19..758dc66b43 100644 --- a/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/crates/rust-analyzer/src/lsp/to_proto.rs @@ -8,10 +8,10 @@ use std::{ use ide::{ Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionItem, CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit, - Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint, - InlayHintLabel, InlayHintLabelPart, InlayKind, Markup, NavigationTarget, ReferenceCategory, - RenameError, Runnable, Severity, SignatureHelp, SnippetEdit, SourceChange, StructureNodeKind, - SymbolKind, TextEdit, TextRange, TextSize, + Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, + InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayKind, Markup, + NavigationTarget, ReferenceCategory, RenameError, Runnable, Severity, SignatureHelp, + SnippetEdit, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize, }; use itertools::Itertools; use serde_json::to_value; @@ -437,10 +437,22 @@ pub(crate) fn signature_help( pub(crate) fn inlay_hint( snap: &GlobalStateSnapshot, + fields_to_resolve: &InlayFieldsToResolve, line_index: &LineIndex, + file_id: FileId, inlay_hint: InlayHint, ) -> Cancellable { - 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 { position: match inlay_hint.position { @@ -454,8 +466,8 @@ pub(crate) fn inlay_hint( InlayKind::Type | InlayKind::Chaining => Some(lsp_types::InlayHintKind::TYPE), _ => None, }, - text_edits: inlay_hint.text_edit.map(|it| text_edit_vec(line_index, it)), - data: None, + text_edits, + data, tooltip, label, }) @@ -463,13 +475,15 @@ pub(crate) fn inlay_hint( fn inlay_hint_label( snap: &GlobalStateSnapshot, + fields_to_resolve: &InlayFieldsToResolve, mut label: InlayHintLabel, ) -> Cancellable<(lsp_types::InlayHintLabel, Option)> { let res = match &*label.parts { [InlayHintLabelPart { linked_location: None, .. }] => { let InlayHintLabelPart { text, tooltip, .. } = label.parts.pop().unwrap(); - ( - lsp_types::InlayHintLabel::String(text), + let hint_tooltip = if fields_to_resolve.resolve_hint_tooltip() { + None + } else { match tooltip { Some(ide::InlayTooltip::String(s)) => { Some(lsp_types::InlayHintTooltip::String(s)) @@ -481,35 +495,44 @@ fn inlay_hint_label( })) } None => None, - }, - ) + } + }; + (lsp_types::InlayHintLabel::String(text), hint_tooltip) } _ => { let parts = label .parts .into_iter() .map(|part| { - part.linked_location.map(|range| location(snap, range)).transpose().map( - |location| lsp_types::InlayHintLabelPart { - value: part.text, - tooltip: match part.tooltip { - Some(ide::InlayTooltip::String(s)) => { - Some(lsp_types::InlayHintLabelPartTooltip::String(s)) - } - Some(ide::InlayTooltip::Markdown(s)) => { - Some(lsp_types::InlayHintLabelPartTooltip::MarkupContent( - lsp_types::MarkupContent { - kind: lsp_types::MarkupKind::Markdown, - value: s, - }, - )) - } - None => None, - }, - location, - command: None, - }, - ) + let tooltip = if fields_to_resolve.resolve_label_tooltip() { + None + } else { + match part.tooltip { + Some(ide::InlayTooltip::String(s)) => { + Some(lsp_types::InlayHintLabelPartTooltip::String(s)) + } + Some(ide::InlayTooltip::Markdown(s)) => { + Some(lsp_types::InlayHintLabelPartTooltip::MarkupContent( + lsp_types::MarkupContent { + kind: lsp_types::MarkupKind::Markdown, + value: s, + }, + )) + } + None => 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::>()?; (lsp_types::InlayHintLabel::LabelParts(parts), None) diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index 67d82a6854..0801e988f5 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@