about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-05-16 13:25:04 +0000
committerbors <bors@rust-lang.org>2022-05-16 13:25:04 +0000
commitee2cbe0ae810cd8621662ac38f2c7a020d06620c (patch)
tree8f00d8bc0d7a0f2a4fed1eea668a3148962f16d6
parent58234c64da2ab535c1028728f422be5dd94f8e4c (diff)
parentfccc12982e6c05b49cb1ddd70ce2fc0b4600ed8d (diff)
downloadrust-ee2cbe0ae810cd8621662ac38f2c7a020d06620c.tar.gz
rust-ee2cbe0ae810cd8621662ac38f2c7a020d06620c.zip
Auto merge of #12244 - jonas-schievink:closing-brace-hints, r=jonas-schievink
feat: Show inlay hints after a `}` to indicate the closed item

Closes https://github.com/rust-lang/rust-analyzer/issues/7315

![screenshot-2022-05-13-19:42:00](https://user-images.githubusercontent.com/1786438/168338713-4cedef50-3611-4667-aa6a-49e154ec16a7.png)
-rw-r--r--crates/ide/src/inlay_hints.rs172
-rw-r--r--crates/ide/src/static_index.rs1
-rw-r--r--crates/rust-analyzer/src/config.rs10
-rw-r--r--crates/rust-analyzer/src/to_proto.rs11
-rw-r--r--docs/user/generated_config.adoc11
-rw-r--r--editors/code/package.json11
6 files changed, 209 insertions, 7 deletions
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 23a46c02762..d8769aacfc5 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<usize>,
+    pub closing_brace_hints_min_lines: Option<usize>,
 }
 
 #[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<InlayHint>,
+    sema: &Semantics<RootDatabase>,
+    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<InlayHint>,
     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<T>(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 53820860f5d..01d7213630e 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 623f5ec9437..304254c3e26 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 cd9226d03da..8669d5de37d 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 57f04067a3d..d8585ffb1de 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 f34f9354e62..d7978bab473 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,