Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 867a7dc

Browse files
author
Jonas Schievink
committed
Show inlay hints after a } to indicate the closed item
1 parent 58234c6 commit 867a7dc

File tree

6 files changed

+185
-7
lines changed

6 files changed

+185
-7
lines changed

crates/ide/src/inlay_hints.rs

Lines changed: 145 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub struct InlayHintsConfig {
2626
pub param_names_for_lifetime_elision_hints: bool,
2727
pub hide_named_constructor_hints: bool,
2828
pub max_length: Option<usize>,
29+
pub closing_brace_hints_min_lines: Option<usize>,
2930
}
3031

3132
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -52,6 +53,7 @@ pub enum InlayKind {
5253
LifetimeHint,
5354
ParameterHint,
5455
TypeHint,
56+
ClosingBraceHint,
5557
}
5658

5759
#[derive(Debug)]
@@ -104,7 +106,7 @@ pub(crate) fn inlay_hints(
104106
NodeOrToken::Token(_) => return acc,
105107
NodeOrToken::Node(n) => n
106108
.descendants()
107-
.filter(|descendant| range.contains_range(descendant.text_range()))
109+
.filter(|descendant| range.intersect(descendant.text_range()).is_some())
108110
.for_each(hints),
109111
},
110112
None => file.descendants().for_each(hints),
@@ -124,6 +126,8 @@ fn hints(
124126
None => return,
125127
};
126128

129+
closing_brace_hints(hints, sema, config, node.clone());
130+
127131
if let Some(expr) = ast::Expr::cast(node.clone()) {
128132
chaining_hints(hints, sema, &famous_defs, config, &expr);
129133
match expr {
@@ -147,6 +151,96 @@ fn hints(
147151
}
148152
}
149153

154+
fn closing_brace_hints(
155+
acc: &mut Vec<InlayHint>,
156+
sema: &Semantics<RootDatabase>,
157+
config: &InlayHintsConfig,
158+
node: SyntaxNode,
159+
) -> Option<()> {
160+
let min_lines = config.closing_brace_hints_min_lines?;
161+
162+
let mut closing_token;
163+
let label = if let Some(item_list) = ast::AssocItemList::cast(node.clone()) {
164+
closing_token = item_list.r_curly_token()?;
165+
166+
let parent = item_list.syntax().parent()?;
167+
match_ast! {
168+
match parent {
169+
ast::Impl(imp) => {
170+
let imp = sema.to_def(&imp)?;
171+
let ty = imp.self_ty(sema.db);
172+
let trait_ = imp.trait_(sema.db);
173+
174+
match trait_ {
175+
Some(tr) => format!("impl {} for {}", tr.name(sema.db), ty.display_truncated(sema.db, config.max_length)),
176+
None => format!("impl {}", ty.display_truncated(sema.db, config.max_length)),
177+
}
178+
},
179+
ast::Trait(tr) => {
180+
format!("trait {}", tr.name()?)
181+
},
182+
_ => return None,
183+
}
184+
}
185+
} else if let Some(list) = ast::ItemList::cast(node.clone()) {
186+
closing_token = list.r_curly_token()?;
187+
188+
let module = ast::Module::cast(list.syntax().parent()?)?;
189+
format!("mod {}", module.name()?)
190+
} else if let Some(block) = ast::BlockExpr::cast(node.clone()) {
191+
closing_token = block.stmt_list()?.r_curly_token()?;
192+
193+
let parent = block.syntax().parent()?;
194+
match_ast! {
195+
match parent {
196+
ast::Fn(it) => {
197+
// FIXME: this could include parameters, but `HirDisplay` prints too much info
198+
// and doesn't respect the max length either, so the hints end up way too long
199+
format!("fn {}", it.name()?)
200+
},
201+
ast::Static(it) => format!("static {}", it.name()?),
202+
ast::Const(it) => {
203+
if it.underscore_token().is_some() {
204+
"const _".into()
205+
} else {
206+
format!("const {}", it.name()?)
207+
}
208+
},
209+
_ => return None,
210+
}
211+
}
212+
} else {
213+
return None;
214+
};
215+
216+
if let Some(mut next) = closing_token.next_token() {
217+
if next.kind() == T![;] {
218+
if let Some(tok) = next.next_token() {
219+
closing_token = next;
220+
next = tok;
221+
}
222+
}
223+
if !(next.kind() == SyntaxKind::WHITESPACE && next.text().contains('\n')) {
224+
// Only display the hint if the `}` is the last token on the line
225+
return None;
226+
}
227+
}
228+
229+
let mut lines = 1;
230+
node.text().for_each_chunk(|s| lines += s.matches('\n').count());
231+
if lines < min_lines {
232+
return None;
233+
}
234+
235+
acc.push(InlayHint {
236+
range: closing_token.text_range(),
237+
kind: InlayKind::ClosingBraceHint,
238+
label: label.into(),
239+
});
240+
241+
None
242+
}
243+
150244
fn lifetime_hints(
151245
acc: &mut Vec<InlayHint>,
152246
config: &InlayHintsConfig,
@@ -925,6 +1019,7 @@ mod tests {
9251019
hide_named_constructor_hints: false,
9261020
param_names_for_lifetime_elision_hints: false,
9271021
max_length: None,
1022+
closing_brace_hints_min_lines: None,
9281023
};
9291024
const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig {
9301025
type_hints: true,
@@ -1422,10 +1517,10 @@ fn main() {
14221517
let foo = foo();
14231518
let foo = foo1();
14241519
let foo = foo2();
1520+
// ^^^ impl Fn(f64, f64)
14251521
let foo = foo3();
14261522
// ^^^ impl Fn(f64, f64) -> u32
14271523
let foo = foo4();
1428-
// ^^^ &dyn Fn(f64, f64) -> u32
14291524
let foo = foo5();
14301525
let foo = foo6();
14311526
let foo = foo7();
@@ -2290,7 +2385,54 @@ fn __(
22902385
//^^^^ &mut
22912386
//^ ref mut
22922387
}
2293-
}
2388+
}"#,
2389+
);
2390+
}
2391+
2392+
#[test]
2393+
fn hints_closing_brace() {
2394+
check_with_config(
2395+
InlayHintsConfig { closing_brace_hints_min_lines: Some(2), ..DISABLED_CONFIG },
2396+
r#"
2397+
fn a() {}
2398+
2399+
fn f() {
2400+
} // no hint unless `}` is the last token on the line
2401+
2402+
fn g() {
2403+
}
2404+
//^ fn g
2405+
2406+
fn h<T>(with: T, arguments: u8, ...) {
2407+
}
2408+
//^ fn h
2409+
2410+
trait Tr {
2411+
fn f();
2412+
fn g() {
2413+
}
2414+
//^ fn g
2415+
}
2416+
//^ trait Tr
2417+
impl Tr for () {
2418+
}
2419+
//^ impl Tr for ()
2420+
impl dyn Tr {
2421+
}
2422+
//^ impl dyn Tr
2423+
2424+
static S0: () = 0;
2425+
static S1: () = {};
2426+
static S2: () = {
2427+
};
2428+
//^ static S2
2429+
const _: () = {
2430+
};
2431+
//^ const _
2432+
2433+
mod m {
2434+
}
2435+
//^ mod m
22942436
"#,
22952437
);
22962438
}

crates/ide/src/static_index.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ impl StaticIndex<'_> {
116116
param_names_for_lifetime_elision_hints: false,
117117
binding_mode_hints: false,
118118
max_length: Some(25),
119+
closing_brace_hints_min_lines: Some(25),
119120
},
120121
file_id,
121122
None,

crates/rust-analyzer/src/config.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,11 @@ config_data! {
259259
inlayHints_bindingModeHints_enable: bool = "false",
260260
/// Whether to show inlay type hints for method chains.
261261
inlayHints_chainingHints_enable: bool = "true",
262+
/// Whether to show inlay hints after a closing `}` to indicate what item it belongs to.
263+
inlayHints_closingBraceHints_enable: bool = "true",
264+
/// Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1
265+
/// to always show them).
266+
inlayHints_closingBraceHints_minLines: usize = "25",
262267
/// Whether to show inlay type hints for return types of closures with blocks.
263268
inlayHints_closureReturnTypeHints_enable: bool = "false",
264269
/// Whether to show inlay type hints for elided lifetimes in function signatures.
@@ -1005,6 +1010,11 @@ impl Config {
10051010
.data
10061011
.inlayHints_lifetimeElisionHints_useParameterNames,
10071012
max_length: self.data.inlayHints_maxLength,
1013+
closing_brace_hints_min_lines: if self.data.inlayHints_closingBraceHints_enable {
1014+
Some(self.data.inlayHints_closingBraceHints_minLines)
1015+
} else {
1016+
None
1017+
},
10081018
}
10091019
}
10101020

crates/rust-analyzer/src/to_proto.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,8 @@ pub(crate) fn inlay_hint(
426426
| InlayKind::TypeHint
427427
| InlayKind::ChainingHint
428428
| InlayKind::GenericParamListHint
429-
| InlayKind::LifetimeHint => position(line_index, inlay_hint.range.end()),
429+
| InlayKind::LifetimeHint
430+
| InlayKind::ClosingBraceHint => position(line_index, inlay_hint.range.end()),
430431
},
431432
label: lsp_types::InlayHintLabel::String(match inlay_hint.kind {
432433
InlayKind::ParameterHint if render_colons => format!("{}:", inlay_hint.label),
@@ -442,12 +443,13 @@ pub(crate) fn inlay_hint(
442443
InlayKind::BindingModeHint
443444
| InlayKind::GenericParamListHint
444445
| InlayKind::LifetimeHint
445-
| InlayKind::ImplicitReborrowHint => None,
446+
| InlayKind::ImplicitReborrowHint
447+
| InlayKind::ClosingBraceHint => None,
446448
},
447449
tooltip: None,
448450
padding_left: Some(match inlay_hint.kind {
449451
InlayKind::TypeHint => !render_colons,
450-
InlayKind::ChainingHint => true,
452+
InlayKind::ChainingHint | InlayKind::ClosingBraceHint => true,
451453
InlayKind::BindingModeHint
452454
| InlayKind::ClosureReturnTypeHint
453455
| InlayKind::GenericParamListHint
@@ -460,7 +462,8 @@ pub(crate) fn inlay_hint(
460462
| InlayKind::ClosureReturnTypeHint
461463
| InlayKind::GenericParamListHint
462464
| InlayKind::ImplicitReborrowHint
463-
| InlayKind::TypeHint => false,
465+
| InlayKind::TypeHint
466+
| InlayKind::ClosingBraceHint => false,
464467
InlayKind::BindingModeHint => inlay_hint.label != "&",
465468
InlayKind::ParameterHint | InlayKind::LifetimeHint => true,
466469
}),

docs/user/generated_config.adoc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,17 @@ Whether to show inlay type hints for binding modes.
355355
--
356356
Whether to show inlay type hints for method chains.
357357
--
358+
[[rust-analyzer.inlayHints.closingBraceHints.enable]]rust-analyzer.inlayHints.closingBraceHints.enable (default: `true`)::
359+
+
360+
--
361+
Whether to show inlay hints after a closing `}` to indicate what item it belongs to.
362+
--
363+
[[rust-analyzer.inlayHints.closingBraceHints.minLines]]rust-analyzer.inlayHints.closingBraceHints.minLines (default: `25`)::
364+
+
365+
--
366+
Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1
367+
to always show them).
368+
--
358369
[[rust-analyzer.inlayHints.closureReturnTypeHints.enable]]rust-analyzer.inlayHints.closureReturnTypeHints.enable (default: `false`)::
359370
+
360371
--

editors/code/package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,17 @@
792792
"default": true,
793793
"type": "boolean"
794794
},
795+
"rust-analyzer.inlayHints.closingBraceHints.enable": {
796+
"markdownDescription": "Whether to show inlay hints after a closing `}` to indicate what item it belongs to.",
797+
"default": true,
798+
"type": "boolean"
799+
},
800+
"rust-analyzer.inlayHints.closingBraceHints.minLines": {
801+
"markdownDescription": "Minimum number of lines required before the `}` until the hint is shown (set to 0 or 1\nto always show them).",
802+
"default": 25,
803+
"type": "integer",
804+
"minimum": 0
805+
},
795806
"rust-analyzer.inlayHints.closureReturnTypeHints.enable": {
796807
"markdownDescription": "Whether to show inlay type hints for return types of closures with blocks.",
797808
"default": false,

0 commit comments

Comments
 (0)