Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/hir-ty/src/infer/closure.rs5
-rw-r--r--crates/hir/src/lib.rs24
-rw-r--r--crates/ide/src/highlight_related.rs199
-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, 178 insertions, 78 deletions
diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs
index d3acbf7b40..df2ad7af34 100644
--- a/crates/hir-ty/src/infer/closure.rs
+++ b/crates/hir-ty/src/infer/closure.rs
@@ -118,6 +118,7 @@ pub(crate) struct HirPlace {
pub(crate) 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 145506a89d..f955b74d0e 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 d88ffd25c4..3a519fe65a 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,57 @@ 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, file_id),
+ T![move] if config.closure_captures => highlight_closure_captures(sema, token, file_id),
_ if config.references => highlight_references(sema, &syntax, token, file_id),
_ => None,
}
}
+fn highlight_closure_captures(
+ sema: &Semantics<'_, RootDatabase>,
+ token: SyntaxToken,
+ file_id: FileId,
+) -> Option<Vec<HighlightedRange>> {
+ let closure = token.parent_ancestors().take(2).find_map(ast::ClosureExpr::cast)?;
+ let search_range = closure.body()?.syntax().text_range();
+ let ty = &sema.type_of_expr(&closure.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 +141,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 {
@@ -148,9 +193,16 @@ fn highlight_exit_points(
) -> Option<Vec<HighlightedRange>> {
fn hl(
sema: &Semantics<'_, RootDatabase>,
+ def_ranges: [Option<TextRange>; 2],
body: Option<ast::Expr>,
) -> Option<Vec<HighlightedRange>> {
let mut highlights = Vec::new();
+ highlights.extend(
+ def_ranges
+ .into_iter()
+ .flatten()
+ .map(|range| HighlightedRange { category: None, range }),
+ );
let body = body?;
walk_expr(&body, &mut |expr| match expr {
ast::Expr::ReturnExpr(expr) => {
@@ -194,10 +246,21 @@ fn highlight_exit_points(
for anc in token.parent_ancestors() {
return match_ast! {
match anc {
- ast::Fn(fn_) => hl(sema, fn_.body().map(ast::Expr::BlockExpr)),
- ast::ClosureExpr(closure) => hl(sema, closure.body()),
+ ast::Fn(fn_) => hl(sema, [fn_.fn_token().map(|it| it.text_range()), None], fn_.body().map(ast::Expr::BlockExpr)),
+ ast::ClosureExpr(closure) => hl(
+ sema,
+ closure.param_list().map_or([None; 2], |p| [p.l_paren_token().map(|it| it.text_range()), p.r_paren_token().map(|it| it.text_range())]),
+ closure.body()
+ ),
ast::BlockExpr(block_expr) => if matches!(block_expr.modifier(), Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Try(_)| ast::BlockModifier::Const(_))) {
- hl(sema, Some(block_expr.into()))
+ hl(
+ sema,
+ [block_expr.modifier().and_then(|modifier| match modifier {
+ ast::BlockModifier::Async(t) | ast::BlockModifier::Try(t) | ast::BlockModifier::Const(t) => Some(t.text_range()),
+ _ => None,
+ }), None],
+ Some(block_expr.into())
+ )
} else {
continue;
},
@@ -352,16 +415,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]
@@ -610,7 +674,8 @@ async fn foo() {
fn test_hl_exit_points() {
check(
r#"
-fn foo() -> u32 {
+ fn foo() -> u32 {
+//^^
if true {
return$0 0;
// ^^^^^^
@@ -629,7 +694,8 @@ fn foo() -> u32 {
fn test_hl_exit_points2() {
check(
r#"
-fn foo() ->$0 u32 {
+ fn foo() ->$0 u32 {
+//^^
if true {
return 0;
// ^^^^^^
@@ -648,7 +714,8 @@ fn foo() ->$0 u32 {
fn test_hl_exit_points3() {
check(
r#"
-fn$0 foo() -> u32 {
+ fn$0 foo() -> u32 {
+//^^
if true {
return 0;
// ^^^^^^
@@ -694,7 +761,8 @@ macro_rules! never {
() => { never() }
}
fn never() -> ! { loop {} }
-fn foo() ->$0 u32 {
+ fn foo() ->$0 u32 {
+//^^
never();
// ^^^^^^^
never!();
@@ -714,7 +782,8 @@ fn foo() ->$0 u32 {
fn test_hl_inner_tail_exit_points() {
check(
r#"
-fn foo() ->$0 u32 {
+ fn foo() ->$0 u32 {
+//^^
if true {
unsafe {
return 5;
@@ -755,7 +824,8 @@ fn foo() ->$0 u32 {
fn test_hl_inner_tail_exit_points_labeled_block() {
check(
r#"
-fn foo() ->$0 u32 {
+ fn foo() ->$0 u32 {
+//^^
'foo: {
break 'foo 0;
// ^^^^^
@@ -776,7 +846,8 @@ fn foo() ->$0 u32 {
fn test_hl_inner_tail_exit_points_loops() {
check(
r#"
-fn foo() ->$0 u32 {
+ fn foo() ->$0 u32 {
+//^^
'foo: while { return 0; true } {
// ^^^^^^
break 'foo 0;
@@ -1086,12 +1157,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 +1172,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 +1207,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 +1238,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#"
@@ -1207,7 +1258,8 @@ fn foo() -> i32 {
check_with_config(
r#"
-fn foo() ->$0 i32 {
+ fn foo() ->$0 i32 {
+//^^
let x = 5;
let y = x * 2;
@@ -1225,12 +1277,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 +1293,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 +1307,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 +1451,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 f9288f01b5..aa6beb6351 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 90c9b16ec2..625ffe0763 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 5abadaad62..fd3e68e2d2 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 76080eca4e..42f58fee30 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 e9b3d45a37..aad6969671 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 d46c248cd4..f36e34b6a1 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 754d43618a..82955acf25 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.