about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLukas Wirth <lukastw97@gmail.com>2023-05-02 08:52:08 +0200
committerLukas Wirth <lukastw97@gmail.com>2023-05-02 08:59:40 +0200
commita64626d99e656cd2b229d9e0dfcefb96e58dd9f2 (patch)
tree8397cd867215b7b96bc82bd803ece6b12a529f97
parent9c0c13ec8e47cfe487f46136b394c0372e957968 (diff)
Highlight closure captures when cursor is on pipe
-rw-r--r--crates/hir-ty/src/infer/closure.rs7
-rw-r--r--crates/hir/src/lib.rs24
-rw-r--r--crates/ide/src/highlight_related.rs158
-rw-r--r--crates/rust-analyzer/src/config.rs3
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs2
-rw-r--r--crates/test-utils/src/lib.rs2
-rw-r--r--docs/dev/lsp-extensions.md2
-rw-r--r--docs/user/generated_config.adoc5
-rw-r--r--editors/code/package.json5
-rw-r--r--editors/code/src/lsp_ext.ts9
10 files changed, 149 insertions, 68 deletions
diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs
index d3acbf7b404..2c3e54bf191 100644
--- a/crates/hir-ty/src/infer/closure.rs
+++ b/crates/hir-ty/src/infer/closure.rs
@@ -115,9 +115,10 @@ impl InferenceContext<'_> {
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub(crate) struct HirPlace {
-    pub(crate) local: BindingId,
+    pub local: BindingId,
     pub(crate) projections: Vec<ProjectionElem<Infallible, Ty>>,
 }
+
 impl HirPlace {
     fn ty(&self, ctx: &mut InferenceContext<'_>) -> Ty {
         let mut ty = ctx.table.resolve_completely(ctx.result[self.local].clone());
@@ -161,6 +162,10 @@ pub struct CapturedItem {
 }
 
 impl CapturedItem {
+    pub fn local(&self) -> BindingId {
+        self.place.local
+    }
+
     pub fn display_kind(&self) -> &'static str {
         match self.kind {
             CaptureKind::ByRef(k) => match k {
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 145506a89df..f955b74d0e5 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -3209,11 +3209,11 @@ impl Closure {
         self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ImplFn).to_string()
     }
 
-    pub fn captured_items(&self, db: &dyn HirDatabase) -> Vec<hir_ty::CapturedItem> {
+    pub fn captured_items(&self, db: &dyn HirDatabase) -> Vec<ClosureCapture> {
         let owner = db.lookup_intern_closure((self.id).into()).0;
         let infer = &db.infer(owner);
         let info = infer.closure_info(&self.id);
-        info.0.clone()
+        info.0.iter().cloned().map(|capture| ClosureCapture { owner, capture }).collect()
     }
 
     pub fn fn_trait(&self, db: &dyn HirDatabase) -> FnTrait {
@@ -3224,6 +3224,26 @@ impl Closure {
     }
 }
 
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct ClosureCapture {
+    owner: DefWithBodyId,
+    capture: hir_ty::CapturedItem,
+}
+
+impl ClosureCapture {
+    pub fn local(&self) -> Local {
+        Local { parent: self.owner, binding_id: self.capture.local() }
+    }
+
+    pub fn display_kind(&self) -> &'static str {
+        self.capture.display_kind()
+    }
+
+    pub fn display_place(&self, owner: ClosureId, db: &dyn HirDatabase) -> String {
+        self.capture.display_place(owner, db)
+    }
+}
+
 #[derive(Clone, PartialEq, Eq, Debug)]
 pub struct Type {
     env: Arc<TraitEnvironment>,
diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs
index d88ffd25c40..b71bdf8c0f3 100644
--- a/crates/ide/src/highlight_related.rs
+++ b/crates/ide/src/highlight_related.rs
@@ -1,6 +1,6 @@
 use hir::Semantics;
 use ide_db::{
-    base_db::{FileId, FilePosition},
+    base_db::{FileId, FilePosition, FileRange},
     defs::{Definition, IdentClass},
     helpers::pick_best_token,
     search::{FileReference, ReferenceCategory, SearchScope},
@@ -30,6 +30,7 @@ pub struct HighlightRelatedConfig {
     pub references: bool,
     pub exit_points: bool,
     pub break_points: bool,
+    pub closure_captures: bool,
     pub yield_points: bool,
 }
 
@@ -53,11 +54,12 @@ pub(crate) fn highlight_related(
 
     let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
         T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`
-        T![->] => 3,
+        T![->] | T![|] => 3,
         kind if kind.is_keyword() => 2,
         IDENT | INT_NUMBER => 1,
         _ => 0,
     })?;
+    // most if not all of these should be re-implemented with information seeded from hir
     match token.kind() {
         T![?] if config.exit_points && token.parent().and_then(ast::TryExpr::cast).is_some() => {
             highlight_exit_points(sema, token)
@@ -70,11 +72,64 @@ pub(crate) fn highlight_related(
         T![break] | T![loop] | T![while] | T![continue] if config.break_points => {
             highlight_break_points(token)
         }
+        T![|] if config.closure_captures => highlight_closure_captures(
+            sema,
+            token.parent_ancestors().nth(1).and_then(ast::ClosureExpr::cast)?,
+            file_id,
+        ),
+        T![move] if config.closure_captures => highlight_closure_captures(
+            sema,
+            token.parent().and_then(ast::ClosureExpr::cast)?,
+            file_id,
+        ),
         _ if config.references => highlight_references(sema, &syntax, token, file_id),
         _ => None,
     }
 }
 
+fn highlight_closure_captures(
+    sema: &Semantics<'_, RootDatabase>,
+    node: ast::ClosureExpr,
+    file_id: FileId,
+) -> Option<Vec<HighlightedRange>> {
+    let search_range = node.body()?.syntax().text_range();
+    let ty = &sema.type_of_expr(&node.into())?.original;
+    let c = ty.as_closure()?;
+    Some(
+        c.captured_items(sema.db)
+            .into_iter()
+            .map(|capture| capture.local())
+            .flat_map(|local| {
+                let usages = Definition::Local(local)
+                    .usages(sema)
+                    .set_scope(Some(SearchScope::file_range(FileRange {
+                        file_id,
+                        range: search_range,
+                    })))
+                    .include_self_refs()
+                    .all()
+                    .references
+                    .remove(&file_id)
+                    .into_iter()
+                    .flatten()
+                    .map(|FileReference { category, range, .. }| HighlightedRange {
+                        range,
+                        category,
+                    });
+                let category = local.is_mut(sema.db).then_some(ReferenceCategory::Write);
+                local
+                    .sources(sema.db)
+                    .into_iter()
+                    .map(|x| x.to_nav(sema.db))
+                    .filter(|decl| decl.file_id == file_id)
+                    .filter_map(|decl| decl.focus_range)
+                    .map(move |range| HighlightedRange { range, category })
+                    .chain(usages)
+            })
+            .collect(),
+    )
+}
+
 fn highlight_references(
     sema: &Semantics<'_, RootDatabase>,
     node: &SyntaxNode,
@@ -93,10 +148,7 @@ fn highlight_references(
                 .remove(&file_id)
         })
         .flatten()
-        .map(|FileReference { category: access, range, .. }| HighlightedRange {
-            range,
-            category: access,
-        });
+        .map(|FileReference { category, range, .. }| HighlightedRange { range, category });
     let mut res = FxHashSet::default();
     for &def in &defs {
         match def {
@@ -352,16 +404,17 @@ mod tests {
 
     use super::*;
 
+    const ENABLED_CONFIG: HighlightRelatedConfig = HighlightRelatedConfig {
+        break_points: true,
+        exit_points: true,
+        references: true,
+        closure_captures: true,
+        yield_points: true,
+    };
+
     #[track_caller]
     fn check(ra_fixture: &str) {
-        let config = HighlightRelatedConfig {
-            break_points: true,
-            exit_points: true,
-            references: true,
-            yield_points: true,
-        };
-
-        check_with_config(ra_fixture, config);
+        check_with_config(ra_fixture, ENABLED_CONFIG);
     }
 
     #[track_caller]
@@ -1086,12 +1139,7 @@ fn function(field: u32) {
 
     #[test]
     fn test_hl_disabled_ref_local() {
-        let config = HighlightRelatedConfig {
-            references: false,
-            break_points: true,
-            exit_points: true,
-            yield_points: true,
-        };
+        let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
 
         check_with_config(
             r#"
@@ -1106,12 +1154,7 @@ fn foo() {
 
     #[test]
     fn test_hl_disabled_ref_local_preserved_break() {
-        let config = HighlightRelatedConfig {
-            references: false,
-            break_points: true,
-            exit_points: true,
-            yield_points: true,
-        };
+        let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
 
         check_with_config(
             r#"
@@ -1146,12 +1189,7 @@ fn foo() {
 
     #[test]
     fn test_hl_disabled_ref_local_preserved_yield() {
-        let config = HighlightRelatedConfig {
-            references: false,
-            break_points: true,
-            exit_points: true,
-            yield_points: true,
-        };
+        let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
 
         check_with_config(
             r#"
@@ -1182,12 +1220,7 @@ async fn foo() {
 
     #[test]
     fn test_hl_disabled_ref_local_preserved_exit() {
-        let config = HighlightRelatedConfig {
-            references: false,
-            break_points: true,
-            exit_points: true,
-            yield_points: true,
-        };
+        let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
 
         check_with_config(
             r#"
@@ -1225,12 +1258,7 @@ fn foo() ->$0 i32 {
 
     #[test]
     fn test_hl_disabled_break() {
-        let config = HighlightRelatedConfig {
-            references: true,
-            break_points: false,
-            exit_points: true,
-            yield_points: true,
-        };
+        let config = HighlightRelatedConfig { break_points: false, ..ENABLED_CONFIG };
 
         check_with_config(
             r#"
@@ -1246,12 +1274,7 @@ fn foo() {
 
     #[test]
     fn test_hl_disabled_yield() {
-        let config = HighlightRelatedConfig {
-            references: true,
-            break_points: true,
-            exit_points: true,
-            yield_points: false,
-        };
+        let config = HighlightRelatedConfig { yield_points: false, ..ENABLED_CONFIG };
 
         check_with_config(
             r#"
@@ -1265,12 +1288,7 @@ async$0 fn foo() {
 
     #[test]
     fn test_hl_disabled_exit() {
-        let config = HighlightRelatedConfig {
-            references: true,
-            break_points: true,
-            exit_points: false,
-            yield_points: true,
-        };
+        let config = HighlightRelatedConfig { exit_points: false, ..ENABLED_CONFIG };
 
         check_with_config(
             r#"
@@ -1414,4 +1432,32 @@ impl Trait for () {
 "#,
         );
     }
+
+    #[test]
+    fn test_closure_capture_pipe() {
+        check(
+            r#"
+fn f() {
+    let x = 1;
+    //  ^
+    let c = $0|y| x + y;
+    //          ^ read
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn test_closure_capture_move() {
+        check(
+            r#"
+fn f() {
+    let x = 1;
+    //  ^
+    let c = move$0 |y| x + y;
+    //               ^ read
+}
+"#,
+        );
+    }
 }
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index f9288f01b53..aa6beb6351a 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -281,6 +281,8 @@ config_data! {
 
         /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
         highlightRelated_breakPoints_enable: bool = "true",
+        /// Enables highlighting of all captures of a closure while the cursor is on the `|` or move keyword of a closure.
+        highlightRelated_closureCaptures_enable: bool = "true",
         /// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).
         highlightRelated_exitPoints_enable: bool = "true",
         /// Enables highlighting of related references while the cursor is on any identifier.
@@ -1554,6 +1556,7 @@ impl Config {
             break_points: self.data.highlightRelated_breakPoints_enable,
             exit_points: self.data.highlightRelated_exitPoints_enable,
             yield_points: self.data.highlightRelated_yieldPoints_enable,
+            closure_captures: self.data.highlightRelated_closureCaptures_enable,
         }
     }
 
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index 90c9b16ec2b..625ffe0763c 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -434,7 +434,7 @@ pub enum HoverRequest {}
 impl Request for HoverRequest {
     type Params = HoverParams;
     type Result = Option<Hover>;
-    const METHOD: &'static str = "textDocument/hover";
+    const METHOD: &'static str = lsp_types::request::HoverRequest::METHOD;
 }
 
 #[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs
index 5abadaad629..fd3e68e2d2c 100644
--- a/crates/test-utils/src/lib.rs
+++ b/crates/test-utils/src/lib.rs
@@ -95,7 +95,7 @@ fn try_extract_range(text: &str) -> Option<(TextRange, String)> {
     Some((TextRange::new(start, end), text))
 }
 
-#[derive(Clone, Copy)]
+#[derive(Clone, Copy, Debug)]
 pub enum RangeOrOffset {
     Range(TextRange),
     Offset(TextSize),
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 76080eca4e0..42f58fee30e 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -1,5 +1,5 @@
 <!---
-lsp_ext.rs hash: 37ac44a0f507e05a
+lsp_ext.rs hash: 31ca513a249753ab
 
 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:
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index e9b3d45a373..aad6969671c 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -352,6 +352,11 @@ Controls file watching implementation.
 --
 Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
 --
+[[rust-analyzer.highlightRelated.closureCaptures.enable]]rust-analyzer.highlightRelated.closureCaptures.enable (default: `true`)::
++
+--
+Enables highlighting of all captures of a closure while the cursor is on the `|` or move keyword of a closure.
+--
 [[rust-analyzer.highlightRelated.exitPoints.enable]]rust-analyzer.highlightRelated.exitPoints.enable (default: `true`)::
 +
 --
diff --git a/editors/code/package.json b/editors/code/package.json
index d46c248cd49..f36e34b6a1b 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -886,6 +886,11 @@
                     "default": true,
                     "type": "boolean"
                 },
+                "rust-analyzer.highlightRelated.closureCaptures.enable": {
+                    "markdownDescription": "Enables highlighting of all captures of a closure while the cursor is on the `|` or move keyword of a closure.",
+                    "default": true,
+                    "type": "boolean"
+                },
                 "rust-analyzer.highlightRelated.exitPoints.enable": {
                     "markdownDescription": "Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).",
                     "default": true,
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index 754d43618a4..82955acf25e 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -10,12 +10,9 @@ export const hover = new lc.RequestType<
     HoverParams,
     (lc.Hover & { actions: CommandLinkGroup[] }) | null,
     void
->("textDocument/hover");
-export type HoverParams = { position: lc.Position | lc.Range } & Omit<
-    lc.TextDocumentPositionParams,
-    "position"
-> &
-    lc.WorkDoneProgressParams;
+>(lc.HoverRequest.method);
+export type HoverParams = { position: lc.Position | lc.Range } & Omit<lc.HoverParams, "position">;
+
 export type CommandLink = {
     /**
      * A tooltip for the command, when represented in the UI.