diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 23a46c0276..d8769aacfc 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -26,6 +26,7 @@ pub struct InlayHintsConfig { pub param_names_for_lifetime_elision_hints: bool, pub hide_named_constructor_hints: bool, pub max_length: Option, + pub closing_brace_hints_min_lines: Option, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -52,6 +53,7 @@ pub enum InlayKind { LifetimeHint, ParameterHint, TypeHint, + ClosingBraceHint, } #[derive(Debug)] @@ -104,7 +106,7 @@ pub(crate) fn inlay_hints( NodeOrToken::Token(_) => return acc, NodeOrToken::Node(n) => n .descendants() - .filter(|descendant| range.contains_range(descendant.text_range())) + .filter(|descendant| range.intersect(descendant.text_range()).is_some()) .for_each(hints), }, None => file.descendants().for_each(hints), @@ -124,6 +126,8 @@ fn hints( None => return, }; + closing_brace_hints(hints, sema, config, node.clone()); + if let Some(expr) = ast::Expr::cast(node.clone()) { chaining_hints(hints, sema, &famous_defs, config, &expr); match expr { @@ -147,6 +151,104 @@ fn hints( } } +fn closing_brace_hints( + acc: &mut Vec, + sema: &Semantics, + config: &InlayHintsConfig, + node: SyntaxNode, +) -> Option<()> { + let min_lines = config.closing_brace_hints_min_lines?; + + let mut closing_token; + let label = if let Some(item_list) = ast::AssocItemList::cast(node.clone()) { + closing_token = item_list.r_curly_token()?; + + let parent = item_list.syntax().parent()?; + match_ast! { + match parent { + ast::Impl(imp) => { + let imp = sema.to_def(&imp)?; + let ty = imp.self_ty(sema.db); + let trait_ = imp.trait_(sema.db); + + 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)), + } + }, + ast::Trait(tr) => { + format!("trait {}", tr.name()?) + }, + _ => return None, + } + } + } else if let Some(list) = ast::ItemList::cast(node.clone()) { + closing_token = list.r_curly_token()?; + + let module = ast::Module::cast(list.syntax().parent()?)?; + format!("mod {}", module.name()?) + } else if let Some(block) = ast::BlockExpr::cast(node.clone()) { + closing_token = block.stmt_list()?.r_curly_token()?; + + let parent = block.syntax().parent()?; + match_ast! { + match parent { + 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()?) + }, + ast::Static(it) => format!("static {}", it.name()?), + ast::Const(it) => { + if it.underscore_token().is_some() { + "const _".into() + } else { + format!("const {}", it.name()?) + } + }, + _ => return None, + } + } + } else if let Some(mac) = ast::MacroCall::cast(node.clone()) { + let last_token = mac.syntax().last_token()?; + if last_token.kind() != T![;] && last_token.kind() != SyntaxKind::R_CURLY { + return None; + } + closing_token = last_token; + + format!("{}!", mac.path()?) + } else { + return None; + }; + + if let Some(mut next) = closing_token.next_token() { + if next.kind() == T![;] { + if let Some(tok) = next.next_token() { + closing_token = next; + next = tok; + } + } + if !(next.kind() == SyntaxKind::WHITESPACE && next.text().contains('\n')) { + // Only display the hint if the `}` is the last token on the line + return None; + } + } + + let mut lines = 1; + node.text().for_each_chunk(|s| lines += s.matches('\n').count()); + if lines < min_lines { + return None; + } + + acc.push(InlayHint { + range: closing_token.text_range(), + kind: InlayKind::ClosingBraceHint, + label: label.into(), + }); + + None +} + fn lifetime_hints( acc: &mut Vec, config: &InlayHintsConfig, @@ -925,6 +1027,7 @@ mod tests { hide_named_constructor_hints: false, param_names_for_lifetime_elision_hints: false, max_length: None, + closing_brace_hints_min_lines: None, }; const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig { type_hints: true, @@ -1422,10 +1525,10 @@ fn main() { let foo = foo(); let foo = foo1(); let foo = foo2(); + // ^^^ impl Fn(f64, f64) let foo = foo3(); // ^^^ impl Fn(f64, f64) -> u32 let foo = foo4(); - // ^^^ &dyn Fn(f64, f64) -> u32 let foo = foo5(); let foo = foo6(); let foo = foo7(); @@ -2290,7 +2393,70 @@ fn __( //^^^^ &mut //^ ref mut } -} +}"#, + ); + } + + #[test] + fn hints_closing_brace() { + check_with_config( + InlayHintsConfig { closing_brace_hints_min_lines: Some(2), ..DISABLED_CONFIG }, + r#" +fn a() {} + +fn f() { +} // no hint unless `}` is the last token on the line + +fn g() { + } +//^ fn g + +fn h(with: T, arguments: u8, ...) { + } +//^ fn h + +trait Tr { + fn f(); + fn g() { + } + //^ fn g + } +//^ trait Tr +impl Tr for () { + } +//^ impl Tr for () +impl dyn Tr { + } +//^ impl dyn Tr + +static S0: () = 0; +static S1: () = {}; +static S2: () = { + }; +//^ static S2 +const _: () = { + }; +//^ const _ + +mod m { + } +//^ mod m + +m! {} +m!(); +m!( + ); +//^ m! + +m! { + } +//^ m! + +fn f() { + let v = vec![ + ]; + } +//^ fn f "#, ); } diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index 53820860f5..01d7213630 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -116,6 +116,7 @@ impl StaticIndex<'_> { param_names_for_lifetime_elision_hints: false, binding_mode_hints: false, max_length: Some(25), + closing_brace_hints_min_lines: Some(25), }, file_id, None, diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 623f5ec943..304254c3e2 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -259,6 +259,11 @@ config_data! { inlayHints_bindingModeHints_enable: bool = "false", /// Whether to show inlay type hints for method chains. inlayHints_chainingHints_enable: bool = "true", + /// Whether to show inlay hints after a closing `}` to indicate what item it belongs to. + inlayHints_closingBraceHints_enable: bool = "true", + /// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1 + /// to always show them). + inlayHints_closingBraceHints_minLines: usize = "25", /// Whether to show inlay type hints for return types of closures with blocks. inlayHints_closureReturnTypeHints_enable: bool = "false", /// Whether to show inlay type hints for elided lifetimes in function signatures. @@ -1005,6 +1010,11 @@ impl Config { .data .inlayHints_lifetimeElisionHints_useParameterNames, max_length: self.data.inlayHints_maxLength, + closing_brace_hints_min_lines: if self.data.inlayHints_closingBraceHints_enable { + Some(self.data.inlayHints_closingBraceHints_minLines) + } else { + None + }, } } diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index cd9226d03d..8669d5de37 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -426,7 +426,8 @@ pub(crate) fn inlay_hint( | InlayKind::TypeHint | InlayKind::ChainingHint | InlayKind::GenericParamListHint - | InlayKind::LifetimeHint => position(line_index, inlay_hint.range.end()), + | 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), @@ -442,12 +443,13 @@ pub(crate) fn inlay_hint( InlayKind::BindingModeHint | InlayKind::GenericParamListHint | InlayKind::LifetimeHint - | InlayKind::ImplicitReborrowHint => None, + | InlayKind::ImplicitReborrowHint + | InlayKind::ClosingBraceHint => None, }, tooltip: None, padding_left: Some(match inlay_hint.kind { InlayKind::TypeHint => !render_colons, - InlayKind::ChainingHint => true, + InlayKind::ChainingHint | InlayKind::ClosingBraceHint => true, InlayKind::BindingModeHint | InlayKind::ClosureReturnTypeHint | InlayKind::GenericParamListHint @@ -460,7 +462,8 @@ pub(crate) fn inlay_hint( | InlayKind::ClosureReturnTypeHint | InlayKind::GenericParamListHint | InlayKind::ImplicitReborrowHint - | InlayKind::TypeHint => false, + | InlayKind::TypeHint + | InlayKind::ClosingBraceHint => false, InlayKind::BindingModeHint => inlay_hint.label != "&", InlayKind::ParameterHint | InlayKind::LifetimeHint => true, }), diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 57f04067a3..d8585ffb1d 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -355,6 +355,17 @@ Whether to show inlay type hints for binding modes. -- Whether to show inlay type hints for method chains. -- +[[rust-analyzer.inlayHints.closingBraceHints.enable]]rust-analyzer.inlayHints.closingBraceHints.enable (default: `true`):: ++ +-- +Whether to show inlay hints after a closing `}` to indicate what item it belongs to. +-- +[[rust-analyzer.inlayHints.closingBraceHints.minLines]]rust-analyzer.inlayHints.closingBraceHints.minLines (default: `25`):: ++ +-- +Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1 +to always show them). +-- [[rust-analyzer.inlayHints.closureReturnTypeHints.enable]]rust-analyzer.inlayHints.closureReturnTypeHints.enable (default: `false`):: + -- diff --git a/editors/code/package.json b/editors/code/package.json index f34f9354e6..d7978bab47 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -792,6 +792,17 @@ "default": true, "type": "boolean" }, + "rust-analyzer.inlayHints.closingBraceHints.enable": { + "markdownDescription": "Whether to show inlay hints after a closing `}` to indicate what item it belongs to.", + "default": true, + "type": "boolean" + }, + "rust-analyzer.inlayHints.closingBraceHints.minLines": { + "markdownDescription": "Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1\nto always show them).", + "default": 25, + "type": "integer", + "minimum": 0 + }, "rust-analyzer.inlayHints.closureReturnTypeHints.enable": { "markdownDescription": "Whether to show inlay type hints for return types of closures with blocks.", "default": false,