Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #12285 - Veykril:inlay-hints, r=Veykril
feat: Implement inlay hint tooltips Currently this just delegates to hover request for some things, and otherwise fallsback to the label. ![image](https://user-images.githubusercontent.com/3757771/168816520-e015726f-53e1-4dac-a76e-8f1312f145d7.png) ![image](https://user-images.githubusercontent.com/3757771/168802753-43749b75-866c-40db-b106-aeaa542b87a3.png) ![image](https://user-images.githubusercontent.com/3757771/168816579-da2050f0-f198-4b3a-a9a1-53199fcaab61.png) ![image](https://user-images.githubusercontent.com/3757771/168818804-d4400ffe-0e6c-48a0-a872-f054e5f550fa.png)
bors 2022-05-17
parent f8fc0ea · parent 58a2411 · commit c5c442e
-rw-r--r--crates/ide/src/inlay_hints.rs162
-rw-r--r--crates/ide/src/lib.rs4
-rw-r--r--crates/rust-analyzer/src/caps.rs10
-rw-r--r--crates/rust-analyzer/src/handlers.rs45
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs6
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1
-rw-r--r--crates/rust-analyzer/src/to_proto.rs54
-rw-r--r--docs/dev/lsp-extensions.md2
8 files changed, 224 insertions, 60 deletions
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index d3ce350f9b..bf82e83822 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -8,7 +8,8 @@ use itertools::Itertools;
use stdx::to_lower_snake_case;
use syntax::{
ast::{self, AstNode, HasArgList, HasGenericParams, HasName, UnaryOp},
- match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, SyntaxNode, TextRange, T,
+ match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken, TextRange,
+ TextSize, T,
};
use crate::FileId;
@@ -56,11 +57,19 @@ pub enum InlayKind {
TypeHint,
}
+// FIXME: This should live somewhere more general
+#[derive(Debug)]
+pub enum RangeOrOffset {
+ Range(TextRange),
+ Offset(TextSize),
+}
+
#[derive(Debug)]
pub struct InlayHint {
pub range: TextRange,
pub kind: InlayKind,
- pub label: SmolStr,
+ pub label: String,
+ pub hover_trigger: Option<RangeOrOffset>,
}
// Feature: Inlay Hints
@@ -164,8 +173,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 mut closing_token;
- let label = if let Some(item_list) = ast::AssocItemList::cast(node.clone()) {
+ let (label, name_offset) = if let Some(item_list) = ast::AssocItemList::cast(node.clone()) {
closing_token = item_list.r_curly_token()?;
let parent = item_list.syntax().parent()?;
@@ -176,13 +187,13 @@ fn closing_brace_hints(
let ty = imp.self_ty(sema.db);
let trait_ = imp.trait_(sema.db);
- match trait_ {
+ (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)
},
ast::Trait(tr) => {
- format!("trait {}", tr.name()?)
+ (format!("trait {}", tr.name()?), tr.name().map(name))
},
_ => return None,
}
@@ -191,7 +202,7 @@ fn closing_brace_hints(
closing_token = list.r_curly_token()?;
let module = ast::Module::cast(list.syntax().parent()?)?;
- format!("mod {}", module.name()?)
+ (format!("mod {}", module.name()?), module.name().map(name))
} else if let Some(block) = ast::BlockExpr::cast(node.clone()) {
closing_token = block.stmt_list()?.r_curly_token()?;
@@ -201,14 +212,14 @@ fn closing_brace_hints(
ast::Fn(it) => {
// FIXME: this could include parameters, but `HirDisplay` prints too much info
// and doesn't respect the max length either, so the hints end up way too long
- format!("fn {}", it.name()?)
+ (format!("fn {}", it.name()?), it.name().map(name))
},
- ast::Static(it) => format!("static {}", it.name()?),
+ ast::Static(it) => (format!("static {}", it.name()?), it.name().map(name)),
ast::Const(it) => {
if it.underscore_token().is_some() {
- "const _".into()
+ ("const _".into(), None)
} else {
- format!("const {}", it.name()?)
+ (format!("const {}", it.name()?), it.name().map(name))
}
},
_ => return None,
@@ -221,7 +232,10 @@ fn closing_brace_hints(
}
closing_token = last_token;
- format!("{}!", mac.path()?)
+ (
+ format!("{}!", mac.path()?),
+ mac.path().and_then(|it| it.segment()).map(|it| it.syntax().text_range().start()),
+ )
} else {
return None;
};
@@ -248,7 +262,8 @@ fn closing_brace_hints(
acc.push(InlayHint {
range: closing_token.text_range(),
kind: InlayKind::ClosingBraceHint,
- label: label.into(),
+ label,
+ hover_trigger: name_offset.map(RangeOrOffset::Offset),
});
None
@@ -262,6 +277,14 @@ fn lifetime_fn_hints(
if config.lifetime_elision_hints == LifetimeElisionHints::Never {
return None;
}
+
+ let mk_lt_hint = |t: SyntaxToken, label| InlayHint {
+ range: t.text_range(),
+ kind: InlayKind::LifetimeHint,
+ label,
+ hover_trigger: None,
+ };
+
let param_list = func.param_list()?;
let generic_param_list = func.generic_param_list();
let ret_type = func.ret_type();
@@ -378,11 +401,7 @@ fn lifetime_fn_hints(
ast::Type::RefType(ty) if ty.lifetime().is_none() => {
if let Some(amp) = ty.amp_token() {
is_trivial = false;
- acc.push(InlayHint {
- range: amp.text_range(),
- kind: InlayKind::LifetimeHint,
- label: output_lt.clone(),
- });
+ acc.push(mk_lt_hint(amp, output_lt.to_string()));
}
}
_ => (),
@@ -398,8 +417,8 @@ fn lifetime_fn_hints(
for (_, amp_token, _, is_elided) in potential_lt_refs {
if is_elided {
let t = amp_token?;
- let lt = a.next()?.clone();
- acc.push(InlayHint { range: t.text_range(), kind: InlayKind::LifetimeHint, label: lt });
+ let lt = a.next()?;
+ acc.push(mk_lt_hint(t, lt.to_string()));
}
}
@@ -409,21 +428,20 @@ fn lifetime_fn_hints(
(Some(gpl), allocated_lifetimes) => {
let angle_tok = gpl.l_angle_token()?;
let is_empty = gpl.generic_params().next().is_none();
- acc.push(InlayHint {
- range: angle_tok.text_range(),
- kind: InlayKind::GenericParamListHint,
- label: format!(
+ acc.push(mk_lt_hint(
+ angle_tok,
+ format!(
"{}{}",
allocated_lifetimes.iter().format(", "),
if is_empty { "" } else { ", " }
- )
- .into(),
- });
+ ),
+ ));
}
(None, allocated_lifetimes) => acc.push(InlayHint {
range: func.name()?.syntax().text_range(),
kind: InlayKind::GenericParamListHint,
label: format!("<{}>", allocated_lifetimes.iter().format(", "),).into(),
+ hover_trigger: None,
}),
}
Some(())
@@ -456,7 +474,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().into()),
+ .unwrap_or_else(|| ty.display_truncated(sema.db, config.max_length).to_string()),
+ hover_trigger: None,
});
Some(())
}
@@ -482,7 +501,8 @@ fn reborrow_hints(
acc.push(InlayHint {
range: expr.syntax().text_range(),
kind: InlayKind::ImplicitReborrowHint,
- label: SmolStr::new_inline(label),
+ label: label.to_string(),
+ hover_trigger: None,
});
Some(())
}
@@ -539,8 +559,9 @@ fn chaining_hints(
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().into()
+ ty.display_truncated(sema.db, config.max_length).to_string()
}),
+ hover_trigger: Some(RangeOrOffset::Range(expr.syntax().text_range())),
});
}
}
@@ -581,6 +602,8 @@ fn param_name_hints(
range,
kind: InlayKind::ParameterHint,
label: param_name.into(),
+ // FIXME: Show hover for parameter
+ hover_trigger: None,
});
acc.extend(hints);
@@ -609,7 +632,8 @@ fn binding_mode_hints(
acc.push(InlayHint {
range,
kind: InlayKind::BindingModeHint,
- label: SmolStr::new_inline(r),
+ label: r.to_string(),
+ hover_trigger: None,
});
});
match pat {
@@ -623,7 +647,8 @@ fn binding_mode_hints(
acc.push(InlayHint {
range,
kind: InlayKind::BindingModeHint,
- label: SmolStr::new_inline(bm),
+ label: bm.to_string(),
+ hover_trigger: None,
});
}
_ => (),
@@ -663,7 +688,7 @@ fn bind_pat_hints(
{
return None;
}
- ty_name.into()
+ ty_name
}
};
@@ -674,6 +699,7 @@ fn bind_pat_hints(
},
kind: InlayKind::TypeHint,
label,
+ hover_trigger: pat.name().map(|it| it.syntax().text_range()).map(RangeOrOffset::Range),
});
Some(())
@@ -738,7 +764,7 @@ fn hint_iterator(
famous_defs: &FamousDefs,
config: &InlayHintsConfig,
ty: &hir::Type,
-) -> Option<SmolStr> {
+) -> Option<String> {
let db = sema.db;
let strukt = ty.strip_references().as_adt()?;
let krate = strukt.module(db).krate();
@@ -775,7 +801,7 @@ fn hint_iterator(
)
.to_string()
});
- return Some(format!("{}{}{}", LABEL_START, ty_display, LABEL_END).into());
+ return Some(format!("{}{}{}", LABEL_START, ty_display, LABEL_END));
}
}
@@ -1986,11 +2012,21 @@ fn main() {
range: 147..172,
kind: ChainingHint,
label: "B",
+ hover_trigger: Some(
+ Range(
+ 147..172,
+ ),
+ ),
},
InlayHint {
range: 147..154,
kind: ChainingHint,
label: "A",
+ hover_trigger: Some(
+ Range(
+ 147..154,
+ ),
+ ),
},
]
"#]],
@@ -2041,11 +2077,21 @@ fn main() {
range: 143..190,
kind: ChainingHint,
label: "C",
+ hover_trigger: Some(
+ Range(
+ 143..190,
+ ),
+ ),
},
InlayHint {
range: 143..179,
kind: ChainingHint,
label: "B",
+ hover_trigger: Some(
+ Range(
+ 143..179,
+ ),
+ ),
},
]
"#]],
@@ -2081,11 +2127,21 @@ fn main() {
range: 246..283,
kind: ChainingHint,
label: "B<X<i32, bool>>",
+ hover_trigger: Some(
+ Range(
+ 246..283,
+ ),
+ ),
},
InlayHint {
range: 246..265,
kind: ChainingHint,
label: "A<X<i32, bool>>",
+ hover_trigger: Some(
+ Range(
+ 246..265,
+ ),
+ ),
},
]
"#]],
@@ -2123,21 +2179,41 @@ fn main() {
range: 174..241,
kind: ChainingHint,
label: "impl Iterator<Item = ()>",
+ hover_trigger: Some(
+ Range(
+ 174..241,
+ ),
+ ),
},
InlayHint {
range: 174..224,
kind: ChainingHint,
label: "impl Iterator<Item = ()>",
+ hover_trigger: Some(
+ Range(
+ 174..224,
+ ),
+ ),
},
InlayHint {
range: 174..206,
kind: ChainingHint,
label: "impl Iterator<Item = ()>",
+ hover_trigger: Some(
+ Range(
+ 174..206,
+ ),
+ ),
},
InlayHint {
range: 174..189,
kind: ChainingHint,
label: "&mut MyIter",
+ hover_trigger: Some(
+ Range(
+ 174..189,
+ ),
+ ),
},
]
"#]],
@@ -2172,21 +2248,37 @@ fn main() {
range: 124..130,
kind: TypeHint,
label: "Struct",
+ hover_trigger: Some(
+ Range(
+ 124..130,
+ ),
+ ),
},
InlayHint {
range: 145..185,
kind: ChainingHint,
label: "Struct",
+ hover_trigger: Some(
+ Range(
+ 145..185,
+ ),
+ ),
},
InlayHint {
range: 145..168,
kind: ChainingHint,
label: "Struct",
+ hover_trigger: Some(
+ Range(
+ 145..168,
+ ),
+ ),
},
InlayHint {
range: 222..228,
kind: ParameterHint,
label: "self",
+ hover_trigger: None,
},
]
"#]],
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index e5160f99f3..071da8097c 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -80,7 +80,9 @@ pub use crate::{
folding_ranges::{Fold, FoldKind},
highlight_related::{HighlightRelatedConfig, HighlightedRange},
hover::{HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult},
- inlay_hints::{InlayHint, InlayHintsConfig, InlayKind, LifetimeElisionHints, ReborrowHints},
+ inlay_hints::{
+ InlayHint, InlayHintsConfig, InlayKind, LifetimeElisionHints, RangeOrOffset, ReborrowHints,
+ },
join_lines::JoinLinesConfig,
markup::Markup,
moniker::{MonikerKind, MonikerResult, PackageInformation},
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs
index 85e3d50053..a653ec289b 100644
--- a/crates/rust-analyzer/src/caps.rs
+++ b/crates/rust-analyzer/src/caps.rs
@@ -4,7 +4,8 @@ use lsp_types::{
CodeActionProviderCapability, CodeLensOptions, CompletionOptions, DeclarationCapability,
DocumentOnTypeFormattingOptions, FileOperationFilter, FileOperationPattern,
FileOperationPatternKind, FileOperationRegistrationOptions, FoldingRangeProviderCapability,
- HoverProviderCapability, ImplementationProviderCapability, OneOf, RenameOptions, SaveOptions,
+ HoverProviderCapability, ImplementationProviderCapability, InlayHintOptions,
+ InlayHintServerCapabilities, OneOf, RenameOptions, SaveOptions,
SelectionRangeProviderCapability, SemanticTokensFullOptions, SemanticTokensLegend,
SemanticTokensOptions, ServerCapabilities, SignatureHelpOptions, TextDocumentSyncCapability,
TextDocumentSyncKind, TextDocumentSyncOptions, TypeDefinitionProviderCapability,
@@ -112,7 +113,12 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities {
.into(),
),
moniker_provider: None,
- inlay_hint_provider: Some(OneOf::Left(true)),
+ inlay_hint_provider: Some(OneOf::Right(InlayHintServerCapabilities::Options(
+ InlayHintOptions {
+ work_done_progress_options: Default::default(),
+ resolve_provider: Some(true),
+ },
+ ))),
experimental: Some(json!({
"externalDocs": true,
"hoverRange": true,
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index ec897ec9d9..510b37bb6f 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -1343,11 +1343,54 @@ 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(inlay_hints_config.render_colons, &line_index, it))
+ .map(|it| {
+ to_proto::inlay_hint(
+ &line_index,
+ &params.text_document,
+ inlay_hints_config.render_colons,
+ it,
+ )
+ })
.collect(),
))
}
+pub(crate) fn handle_inlay_hints_resolve(
+ snap: GlobalStateSnapshot,
+ mut hint: InlayHint,
+) -> Result<InlayHint> {
+ let _p = profile::span("handle_inlay_hints_resolve");
+ let data = match hint.data.take() {
+ Some(it) => it,
+ None => return Ok(hint),
+ };
+
+ let resolve_data: lsp_ext::InlayHintResolveData = serde_json::from_value(data)?;
+
+ let file_range = from_proto::file_range(
+ &snap,
+ resolve_data.text_document,
+ match resolve_data.position {
+ PositionOrRange::Position(pos) => Range::new(pos, pos),
+ PositionOrRange::Range(range) => range,
+ },
+ )?;
+ let info = match snap.analysis.hover(&snap.config.hover(), file_range)? {
+ None => return Ok(hint),
+ Some(info) => info,
+ };
+
+ let markup_kind =
+ snap.config.hover().documentation.map_or(ide::HoverDocFormat::Markdown, |kind| kind);
+
+ // FIXME: hover actions?
+ hint.tooltip = Some(lsp_types::InlayHintTooltip::MarkupContent(to_proto::markup_content(
+ info.info.markup,
+ markup_kind,
+ )));
+ Ok(hint)
+}
+
pub(crate) fn handle_call_hierarchy_prepare(
snap: GlobalStateSnapshot,
params: CallHierarchyPrepareParams,
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index 1cfaa13327..c1b230bd9d 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -519,6 +519,12 @@ pub struct CompletionResolveData {
}
#[derive(Debug, Serialize, Deserialize)]
+pub struct InlayHintResolveData {
+ pub text_document: TextDocumentIdentifier,
+ pub position: PositionOrRange,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
pub struct CompletionImport {
pub full_import_path: String,
pub imported_name: String,
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 856948a012..b5ac55e60d 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -612,6 +612,7 @@ impl GlobalState {
.on::<lsp_types::request::GotoImplementation>(handlers::handle_goto_implementation)
.on::<lsp_types::request::GotoTypeDefinition>(handlers::handle_goto_type_definition)
.on::<lsp_types::request::InlayHintRequest>(handlers::handle_inlay_hints)
+ .on::<lsp_types::request::InlayHintResolveRequest>(handlers::handle_inlay_hints_resolve)
.on::<lsp_types::request::Completion>(handlers::handle_completion)
.on::<lsp_types::request::ResolveCompletionItem>(handlers::handle_completion_resolve)
.on::<lsp_types::request::CodeLensRequest>(handlers::handle_code_lens)
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index c0609f5187..6817090a8b 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -415,8 +415,9 @@ pub(crate) fn signature_help(
}
pub(crate) fn inlay_hint(
- render_colons: bool,
line_index: &LineIndex,
+ text_document: &lsp_types::TextDocumentIdentifier,
+ render_colons: bool,
inlay_hint: InlayHint,
) -> lsp_types::InlayHint {
lsp_types::InlayHint {
@@ -433,24 +434,6 @@ pub(crate) fn inlay_hint(
| InlayKind::LifetimeHint
| InlayKind::ClosingBraceHint => position(line_index, inlay_hint.range.end()),
},
- 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.to_string(),
- }),
- kind: match inlay_hint.kind {
- InlayKind::ParameterHint => Some(lsp_types::InlayHintKind::PARAMETER),
- InlayKind::ClosureReturnTypeHint | InlayKind::TypeHint | InlayKind::ChainingHint => {
- Some(lsp_types::InlayHintKind::TYPE)
- }
- InlayKind::BindingModeHint
- | InlayKind::GenericParamListHint
- | InlayKind::LifetimeHint
- | InlayKind::ImplicitReborrowHint
- | InlayKind::ClosingBraceHint => None,
- },
- tooltip: None,
padding_left: Some(match inlay_hint.kind {
InlayKind::TypeHint => !render_colons,
InlayKind::ChainingHint | InlayKind::ClosingBraceHint => true,
@@ -471,8 +454,39 @@ pub(crate) fn inlay_hint(
InlayKind::BindingModeHint => inlay_hint.label != "&",
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 => {
+ Some(lsp_types::InlayHintKind::TYPE)
+ }
+ InlayKind::BindingModeHint
+ | InlayKind::GenericParamListHint
+ | InlayKind::LifetimeHint
+ | InlayKind::ImplicitReborrowHint
+ | InlayKind::ClosingBraceHint => None,
+ },
text_edits: None,
- data: None,
+ tooltip: Some(lsp_types::InlayHintTooltip::String(inlay_hint.label)),
+ data: inlay_hint.hover_trigger.map(|range_or_offset| {
+ to_value(lsp_ext::InlayHintResolveData {
+ text_document: text_document.clone(),
+ position: match range_or_offset {
+ ide::RangeOrOffset::Offset(offset) => {
+ lsp_ext::PositionOrRange::Position(position(line_index, offset))
+ }
+ ide::RangeOrOffset::Range(text_range) => {
+ lsp_ext::PositionOrRange::Range(range(line_index, text_range))
+ }
+ },
+ })
+ .unwrap()
+ }),
}
}
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 2e1cee8955..875561608d 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
<!---
-lsp_ext.rs hash: 7a34bc3f38e2a7d8
+lsp_ext.rs hash: 44e8238e4fbd4128
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: