From 241807dbf9785748a76cb61358a68214fc24e013 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Wed, 31 Aug 2022 18:34:10 +0200 Subject: [PATCH 1/3] Allow multi-part inlay hint labels with location links --- crates/ide/src/inlay_hints.rs | 162 ++++++++++++++++++++++----- crates/ide/src/lib.rs | 4 +- crates/rust-analyzer/src/handlers.rs | 2 +- crates/rust-analyzer/src/to_proto.rs | 57 +++++++--- 4 files changed, 179 insertions(+), 46 deletions(-) diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index e9034daefa8d..4ad6aa0e0497 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -1,3 +1,5 @@ +use std::fmt; + use either::Either; use hir::{known, Callable, HasVisibility, HirDisplay, Mutability, Semantics, TypeInfo}; use ide_db::{ @@ -69,7 +71,7 @@ pub enum InlayKind { pub struct InlayHint { pub range: TextRange, pub kind: InlayKind, - pub label: String, + pub label: InlayHintLabel, pub tooltip: Option, } @@ -80,6 +82,78 @@ pub enum InlayTooltip { HoverOffset(FileId, TextSize), } +pub struct InlayHintLabel { + pub parts: Vec, +} + +impl InlayHintLabel { + pub fn as_simple_str(&self) -> Option<&str> { + match &*self.parts { + [part] => part.as_simple_str(), + _ => None, + } + } + + pub fn prepend_str(&mut self, s: &str) { + match &mut *self.parts { + [part, ..] if part.as_simple_str().is_some() => part.text = format!("{s}{}", part.text), + _ => self.parts.insert(0, InlayHintLabelPart { text: s.into(), linked_location: None }), + } + } + + pub fn append_str(&mut self, s: &str) { + match &mut *self.parts { + [.., part] if part.as_simple_str().is_some() => part.text.push_str(s), + _ => self.parts.push(InlayHintLabelPart { text: s.into(), linked_location: None }), + } + } +} + +impl From for InlayHintLabel { + fn from(s: String) -> Self { + Self { parts: vec![InlayHintLabelPart { text: s, linked_location: None }] } + } +} + +impl fmt::Display for InlayHintLabel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.parts.iter().map(|part| &part.text).format("")) + } +} + +impl fmt::Debug for InlayHintLabel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(&self.parts).finish() + } +} + +pub struct InlayHintLabelPart { + pub text: String, + pub linked_location: Option, +} + +impl InlayHintLabelPart { + pub fn as_simple_str(&self) -> Option<&str> { + match self { + Self { text, linked_location: None } => Some(text), + _ => None, + } + } +} + +impl fmt::Debug for InlayHintLabelPart { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.as_simple_str() { + Some(string) => string.fmt(f), + None => f + .debug_struct("InlayHintLabelPart") + .field("text", &self.text) + .field("linked_location", &self.linked_location) + .finish(), + } + } +} + // Feature: Inlay Hints // // rust-analyzer shows additional information inline with the source code. @@ -281,7 +355,7 @@ fn closing_brace_hints( acc.push(InlayHint { range: closing_token.text_range(), kind: InlayKind::ClosingBraceHint, - label, + label: label.into(), tooltip: name_offset.map(|it| InlayTooltip::HoverOffset(file_id, it)), }); @@ -311,7 +385,7 @@ fn implicit_static_hints( acc.push(InlayHint { range: t.text_range(), kind: InlayKind::LifetimeHint, - label: "'static".to_owned(), + label: "'static".to_owned().into(), tooltip: Some(InlayTooltip::String("Elided static lifetime".into())), }); } @@ -329,10 +403,10 @@ fn fn_lifetime_fn_hints( return None; } - let mk_lt_hint = |t: SyntaxToken, label| InlayHint { + let mk_lt_hint = |t: SyntaxToken, label: String| InlayHint { range: t.text_range(), kind: InlayKind::LifetimeHint, - label, + label: label.into(), tooltip: Some(InlayTooltip::String("Elided lifetime".into())), }; @@ -486,7 +560,8 @@ fn fn_lifetime_fn_hints( "{}{}", allocated_lifetimes.iter().format(", "), if is_empty { "" } else { ", " } - ), + ) + .into(), tooltip: Some(InlayTooltip::String("Elided lifetimes".into())), }); } @@ -535,7 +610,8 @@ fn closure_ret_hints( range: param_list.syntax().text_range(), kind: InlayKind::ClosureReturnTypeHint, label: hint_iterator(sema, &famous_defs, config, &ty) - .unwrap_or_else(|| ty.display_truncated(sema.db, config.max_length).to_string()), + .unwrap_or_else(|| ty.display_truncated(sema.db, config.max_length).to_string()) + .into(), tooltip: Some(InlayTooltip::HoverRanged(file_id, param_list.syntax().text_range())), }); Some(()) @@ -562,7 +638,7 @@ fn reborrow_hints( acc.push(InlayHint { range: expr.syntax().text_range(), kind: InlayKind::ImplicitReborrowHint, - label: label.to_string(), + label: label.to_string().into(), tooltip: Some(InlayTooltip::String("Compiler inserted reborrow".into())), }); Some(()) @@ -620,9 +696,9 @@ fn chaining_hints( acc.push(InlayHint { range: expr.syntax().text_range(), kind: InlayKind::ChainingHint, - label: hint_iterator(sema, &famous_defs, config, &ty).unwrap_or_else(|| { - ty.display_truncated(sema.db, config.max_length).to_string() - }), + label: hint_iterator(sema, &famous_defs, config, &ty) + .unwrap_or_else(|| ty.display_truncated(sema.db, config.max_length).to_string()) + .into(), tooltip: Some(InlayTooltip::HoverRanged(file_id, expr.syntax().text_range())), }); } @@ -674,7 +750,7 @@ fn param_name_hints( InlayHint { range, kind: InlayKind::ParameterHint, - label: param_name, + label: param_name.into(), tooltip: tooltip.map(|it| InlayTooltip::HoverOffset(it.file_id, it.range.start())), } }); @@ -705,7 +781,7 @@ fn binding_mode_hints( acc.push(InlayHint { range, kind: InlayKind::BindingModeHint, - label: r.to_string(), + label: r.to_string().into(), tooltip: Some(InlayTooltip::String("Inferred binding mode".into())), }); }); @@ -720,7 +796,7 @@ fn binding_mode_hints( acc.push(InlayHint { range, kind: InlayKind::BindingModeHint, - label: bm.to_string(), + label: bm.to_string().into(), tooltip: Some(InlayTooltip::String("Inferred binding mode".into())), }); } @@ -772,7 +848,7 @@ fn bind_pat_hints( None => pat.syntax().text_range(), }, kind: InlayKind::TypeHint, - label, + label: label.into(), tooltip: pat .name() .map(|it| it.syntax().text_range()) @@ -2223,7 +2299,9 @@ fn main() { InlayHint { range: 147..172, kind: ChainingHint, - label: "B", + label: [ + "B", + ], tooltip: Some( HoverRanged( FileId( @@ -2236,7 +2314,9 @@ fn main() { InlayHint { range: 147..154, kind: ChainingHint, - label: "A", + label: [ + "A", + ], tooltip: Some( HoverRanged( FileId( @@ -2294,7 +2374,9 @@ fn main() { InlayHint { range: 143..190, kind: ChainingHint, - label: "C", + label: [ + "C", + ], tooltip: Some( HoverRanged( FileId( @@ -2307,7 +2389,9 @@ fn main() { InlayHint { range: 143..179, kind: ChainingHint, - label: "B", + label: [ + "B", + ], tooltip: Some( HoverRanged( FileId( @@ -2350,7 +2434,9 @@ fn main() { InlayHint { range: 246..283, kind: ChainingHint, - label: "B>", + label: [ + "B>", + ], tooltip: Some( HoverRanged( FileId( @@ -2363,7 +2449,9 @@ fn main() { InlayHint { range: 246..265, kind: ChainingHint, - label: "A>", + label: [ + "A>", + ], tooltip: Some( HoverRanged( FileId( @@ -2408,7 +2496,9 @@ fn main() { InlayHint { range: 174..241, kind: ChainingHint, - label: "impl Iterator", + label: [ + "impl Iterator", + ], tooltip: Some( HoverRanged( FileId( @@ -2421,7 +2511,9 @@ fn main() { InlayHint { range: 174..224, kind: ChainingHint, - label: "impl Iterator", + label: [ + "impl Iterator", + ], tooltip: Some( HoverRanged( FileId( @@ -2434,7 +2526,9 @@ fn main() { InlayHint { range: 174..206, kind: ChainingHint, - label: "impl Iterator", + label: [ + "impl Iterator", + ], tooltip: Some( HoverRanged( FileId( @@ -2447,7 +2541,9 @@ fn main() { InlayHint { range: 174..189, kind: ChainingHint, - label: "&mut MyIter", + label: [ + "&mut MyIter", + ], tooltip: Some( HoverRanged( FileId( @@ -2489,7 +2585,9 @@ fn main() { InlayHint { range: 124..130, kind: TypeHint, - label: "Struct", + label: [ + "Struct", + ], tooltip: Some( HoverRanged( FileId( @@ -2502,7 +2600,9 @@ fn main() { InlayHint { range: 145..185, kind: ChainingHint, - label: "Struct", + label: [ + "Struct", + ], tooltip: Some( HoverRanged( FileId( @@ -2515,7 +2615,9 @@ fn main() { InlayHint { range: 145..168, kind: ChainingHint, - label: "Struct", + label: [ + "Struct", + ], tooltip: Some( HoverRanged( FileId( @@ -2528,7 +2630,9 @@ fn main() { InlayHint { range: 222..228, kind: ParameterHint, - label: "self", + label: [ + "self", + ], tooltip: Some( HoverOffset( FileId( diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index d61d69a090b3..0552330814aa 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -82,8 +82,8 @@ pub use crate::{ highlight_related::{HighlightRelatedConfig, HighlightedRange}, hover::{HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult}, inlay_hints::{ - ClosureReturnTypeHints, InlayHint, InlayHintsConfig, InlayKind, InlayTooltip, - LifetimeElisionHints, ReborrowHints, + ClosureReturnTypeHints, InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind, + InlayTooltip, LifetimeElisionHints, ReborrowHints, }, join_lines::JoinLinesConfig, markup::Markup, diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index d89f0f5a3cf4..3a661ddf063c 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -1362,7 +1362,7 @@ pub(crate) fn handle_inlay_hints( .map(|it| { to_proto::inlay_hint(&snap, &line_index, inlay_hints_config.render_colons, it) }) - .collect(), + .collect::>>()?, )) } diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 102cd6029504..de151f0d92c1 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -9,8 +9,9 @@ use ide::{ Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionItem, CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, InlayHint, - InlayKind, Markup, NavigationTarget, ReferenceCategory, RenameError, Runnable, Severity, - SignatureHelp, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize, + InlayHintLabel, InlayKind, Markup, NavigationTarget, ReferenceCategory, RenameError, Runnable, + Severity, SignatureHelp, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, + TextSize, }; use itertools::Itertools; use serde_json::to_value; @@ -426,9 +427,16 @@ pub(crate) fn inlay_hint( snap: &GlobalStateSnapshot, line_index: &LineIndex, render_colons: bool, - inlay_hint: InlayHint, -) -> lsp_types::InlayHint { - lsp_types::InlayHint { + mut inlay_hint: InlayHint, +) -> Result { + match inlay_hint.kind { + InlayKind::ParameterHint if render_colons => inlay_hint.label.append_str(":"), + InlayKind::TypeHint if render_colons => inlay_hint.label.prepend_str(": "), + InlayKind::ClosureReturnTypeHint => inlay_hint.label.prepend_str(" -> "), + _ => {} + } + + Ok(lsp_types::InlayHint { position: match inlay_hint.kind { // before annotated thing InlayKind::ParameterHint @@ -459,15 +467,9 @@ pub(crate) fn inlay_hint( | InlayKind::ImplicitReborrowHint | InlayKind::TypeHint | InlayKind::ClosingBraceHint => false, - InlayKind::BindingModeHint => inlay_hint.label != "&", + InlayKind::BindingModeHint => inlay_hint.label.to_string() != "&", InlayKind::ParameterHint | InlayKind::LifetimeHint => true, }), - label: lsp_types::InlayHintLabel::String(match inlay_hint.kind { - InlayKind::ParameterHint if render_colons => format!("{}:", inlay_hint.label), - InlayKind::TypeHint if render_colons => format!(": {}", inlay_hint.label), - InlayKind::ClosureReturnTypeHint => format!(" -> {}", inlay_hint.label), - _ => inlay_hint.label.clone(), - }), kind: match inlay_hint.kind { InlayKind::ParameterHint => Some(lsp_types::InlayHintKind::PARAMETER), InlayKind::ClosureReturnTypeHint | InlayKind::TypeHint | InlayKind::ChainingHint => { @@ -506,9 +508,36 @@ pub(crate) fn inlay_hint( })(), tooltip: Some(match inlay_hint.tooltip { Some(ide::InlayTooltip::String(s)) => lsp_types::InlayHintTooltip::String(s), - _ => lsp_types::InlayHintTooltip::String(inlay_hint.label), + _ => lsp_types::InlayHintTooltip::String(inlay_hint.label.to_string()), }), - } + label: inlay_hint_label(snap, inlay_hint.label)?, + }) +} + +fn inlay_hint_label( + snap: &GlobalStateSnapshot, + label: InlayHintLabel, +) -> Result { + Ok(match label.as_simple_str() { + Some(s) => lsp_types::InlayHintLabel::String(s.into()), + None => lsp_types::InlayHintLabel::LabelParts( + label + .parts + .into_iter() + .map(|part| { + Ok(lsp_types::InlayHintLabelPart { + value: part.text, + tooltip: None, + location: part + .linked_location + .map(|range| location(snap, range)) + .transpose()?, + command: None, + }) + }) + .collect::>>()?, + ), + }) } static TOKEN_RESULT_COUNTER: AtomicU32 = AtomicU32::new(1); From 064c9ef9e27ed9c427dedd6b4a1a042cb5cdc3e7 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Wed, 31 Aug 2022 18:54:19 +0200 Subject: [PATCH 2/3] Make clicking closing brace hint go to the opening brace --- crates/ide/src/inlay_hints.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 4ad6aa0e0497..d1b1d2c331a5 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -129,6 +129,11 @@ impl fmt::Debug for InlayHintLabel { pub struct InlayHintLabelPart { pub text: String, + /// Source location represented by this label part. The client will use this to fetch the part's + /// hover tooltip, and Ctrl+Clicking the label part will navigate to the definition the location + /// refers to (not necessarily the location itself). + /// When setting this, no tooltip must be set on the containing hint, or VS Code will display + /// them both. pub linked_location: Option, } @@ -266,10 +271,10 @@ fn closing_brace_hints( ) -> Option<()> { let min_lines = config.closing_brace_hints_min_lines?; - let name = |it: ast::Name| it.syntax().text_range().start(); + let name = |it: ast::Name| it.syntax().text_range(); let mut closing_token; - let (label, name_offset) = if let Some(item_list) = ast::AssocItemList::cast(node.clone()) { + let (label, name_range) = if let Some(item_list) = ast::AssocItemList::cast(node.clone()) { closing_token = item_list.r_curly_token()?; let parent = item_list.syntax().parent()?; @@ -279,11 +284,11 @@ fn closing_brace_hints( let imp = sema.to_def(&imp)?; let ty = imp.self_ty(sema.db); let trait_ = imp.trait_(sema.db); - - (match trait_ { + let hint_text = match trait_ { Some(tr) => format!("impl {} for {}", tr.name(sema.db), ty.display_truncated(sema.db, config.max_length)), None => format!("impl {}", ty.display_truncated(sema.db, config.max_length)), - }, None) + }; + (hint_text, None) }, ast::Trait(tr) => { (format!("trait {}", tr.name()?), tr.name().map(name)) @@ -327,7 +332,7 @@ fn closing_brace_hints( ( format!("{}!", mac.path()?), - mac.path().and_then(|it| it.segment()).map(|it| it.syntax().text_range().start()), + mac.path().and_then(|it| it.segment()).map(|it| it.syntax().text_range()), ) } else { return None; @@ -352,11 +357,12 @@ fn closing_brace_hints( return None; } + let linked_location = name_range.map(|range| FileRange { file_id, range }); acc.push(InlayHint { range: closing_token.text_range(), kind: InlayKind::ClosingBraceHint, - label: label.into(), - tooltip: name_offset.map(|it| InlayTooltip::HoverOffset(file_id, it)), + label: InlayHintLabel { parts: vec![InlayHintLabelPart { text: label, linked_location }] }, + tooltip: None, // provided by label part location }); None From c4eadab016cf16bd32b90ba7ecc71361592a4f28 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Thu, 8 Sep 2022 18:33:53 +0200 Subject: [PATCH 3/3] Update crates/rust-analyzer/src/to_proto.rs Co-authored-by: Lukas Wirth --- crates/rust-analyzer/src/to_proto.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index de151f0d92c1..e083b9d0e337 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -467,7 +467,7 @@ pub(crate) fn inlay_hint( | InlayKind::ImplicitReborrowHint | InlayKind::TypeHint | InlayKind::ClosingBraceHint => false, - InlayKind::BindingModeHint => inlay_hint.label.to_string() != "&", + InlayKind::BindingModeHint => inlay_hint.label.as_simple_str() != Some("&"), InlayKind::ParameterHint | InlayKind::LifetimeHint => true, }), kind: match inlay_hint.kind {