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

Commit a64626d

Browse files
committed
Highlight closure captures when cursor is on pipe
1 parent 9c0c13e commit a64626d

File tree

10 files changed

+149
-68
lines changed

10 files changed

+149
-68
lines changed

crates/hir-ty/src/infer/closure.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,10 @@ impl InferenceContext<'_> {
115115

116116
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
117117
pub(crate) struct HirPlace {
118-
pub(crate) local: BindingId,
118+
pub local: BindingId,
119119
pub(crate) projections: Vec<ProjectionElem<Infallible, Ty>>,
120120
}
121+
121122
impl HirPlace {
122123
fn ty(&self, ctx: &mut InferenceContext<'_>) -> Ty {
123124
let mut ty = ctx.table.resolve_completely(ctx.result[self.local].clone());
@@ -161,6 +162,10 @@ pub struct CapturedItem {
161162
}
162163

163164
impl CapturedItem {
165+
pub fn local(&self) -> BindingId {
166+
self.place.local
167+
}
168+
164169
pub fn display_kind(&self) -> &'static str {
165170
match self.kind {
166171
CaptureKind::ByRef(k) => match k {

crates/hir/src/lib.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3209,11 +3209,11 @@ impl Closure {
32093209
self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ImplFn).to_string()
32103210
}
32113211

3212-
pub fn captured_items(&self, db: &dyn HirDatabase) -> Vec<hir_ty::CapturedItem> {
3212+
pub fn captured_items(&self, db: &dyn HirDatabase) -> Vec<ClosureCapture> {
32133213
let owner = db.lookup_intern_closure((self.id).into()).0;
32143214
let infer = &db.infer(owner);
32153215
let info = infer.closure_info(&self.id);
3216-
info.0.clone()
3216+
info.0.iter().cloned().map(|capture| ClosureCapture { owner, capture }).collect()
32173217
}
32183218

32193219
pub fn fn_trait(&self, db: &dyn HirDatabase) -> FnTrait {
@@ -3224,6 +3224,26 @@ impl Closure {
32243224
}
32253225
}
32263226

3227+
#[derive(Clone, Debug, PartialEq, Eq)]
3228+
pub struct ClosureCapture {
3229+
owner: DefWithBodyId,
3230+
capture: hir_ty::CapturedItem,
3231+
}
3232+
3233+
impl ClosureCapture {
3234+
pub fn local(&self) -> Local {
3235+
Local { parent: self.owner, binding_id: self.capture.local() }
3236+
}
3237+
3238+
pub fn display_kind(&self) -> &'static str {
3239+
self.capture.display_kind()
3240+
}
3241+
3242+
pub fn display_place(&self, owner: ClosureId, db: &dyn HirDatabase) -> String {
3243+
self.capture.display_place(owner, db)
3244+
}
3245+
}
3246+
32273247
#[derive(Clone, PartialEq, Eq, Debug)]
32283248
pub struct Type {
32293249
env: Arc<TraitEnvironment>,

crates/ide/src/highlight_related.rs

Lines changed: 102 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use hir::Semantics;
22
use ide_db::{
3-
base_db::{FileId, FilePosition},
3+
base_db::{FileId, FilePosition, FileRange},
44
defs::{Definition, IdentClass},
55
helpers::pick_best_token,
66
search::{FileReference, ReferenceCategory, SearchScope},
@@ -30,6 +30,7 @@ pub struct HighlightRelatedConfig {
3030
pub references: bool,
3131
pub exit_points: bool,
3232
pub break_points: bool,
33+
pub closure_captures: bool,
3334
pub yield_points: bool,
3435
}
3536

@@ -53,11 +54,12 @@ pub(crate) fn highlight_related(
5354

5455
let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
5556
T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`
56-
T![->] => 3,
57+
T![->] | T![|] => 3,
5758
kind if kind.is_keyword() => 2,
5859
IDENT | INT_NUMBER => 1,
5960
_ => 0,
6061
})?;
62+
// most if not all of these should be re-implemented with information seeded from hir
6163
match token.kind() {
6264
T![?] if config.exit_points && token.parent().and_then(ast::TryExpr::cast).is_some() => {
6365
highlight_exit_points(sema, token)
@@ -70,11 +72,64 @@ pub(crate) fn highlight_related(
7072
T![break] | T![loop] | T![while] | T![continue] if config.break_points => {
7173
highlight_break_points(token)
7274
}
75+
T![|] if config.closure_captures => highlight_closure_captures(
76+
sema,
77+
token.parent_ancestors().nth(1).and_then(ast::ClosureExpr::cast)?,
78+
file_id,
79+
),
80+
T![move] if config.closure_captures => highlight_closure_captures(
81+
sema,
82+
token.parent().and_then(ast::ClosureExpr::cast)?,
83+
file_id,
84+
),
7385
_ if config.references => highlight_references(sema, &syntax, token, file_id),
7486
_ => None,
7587
}
7688
}
7789

90+
fn highlight_closure_captures(
91+
sema: &Semantics<'_, RootDatabase>,
92+
node: ast::ClosureExpr,
93+
file_id: FileId,
94+
) -> Option<Vec<HighlightedRange>> {
95+
let search_range = node.body()?.syntax().text_range();
96+
let ty = &sema.type_of_expr(&node.into())?.original;
97+
let c = ty.as_closure()?;
98+
Some(
99+
c.captured_items(sema.db)
100+
.into_iter()
101+
.map(|capture| capture.local())
102+
.flat_map(|local| {
103+
let usages = Definition::Local(local)
104+
.usages(sema)
105+
.set_scope(Some(SearchScope::file_range(FileRange {
106+
file_id,
107+
range: search_range,
108+
})))
109+
.include_self_refs()
110+
.all()
111+
.references
112+
.remove(&file_id)
113+
.into_iter()
114+
.flatten()
115+
.map(|FileReference { category, range, .. }| HighlightedRange {
116+
range,
117+
category,
118+
});
119+
let category = local.is_mut(sema.db).then_some(ReferenceCategory::Write);
120+
local
121+
.sources(sema.db)
122+
.into_iter()
123+
.map(|x| x.to_nav(sema.db))
124+
.filter(|decl| decl.file_id == file_id)
125+
.filter_map(|decl| decl.focus_range)
126+
.map(move |range| HighlightedRange { range, category })
127+
.chain(usages)
128+
})
129+
.collect(),
130+
)
131+
}
132+
78133
fn highlight_references(
79134
sema: &Semantics<'_, RootDatabase>,
80135
node: &SyntaxNode,
@@ -93,10 +148,7 @@ fn highlight_references(
93148
.remove(&file_id)
94149
})
95150
.flatten()
96-
.map(|FileReference { category: access, range, .. }| HighlightedRange {
97-
range,
98-
category: access,
99-
});
151+
.map(|FileReference { category, range, .. }| HighlightedRange { range, category });
100152
let mut res = FxHashSet::default();
101153
for &def in &defs {
102154
match def {
@@ -352,16 +404,17 @@ mod tests {
352404

353405
use super::*;
354406

407+
const ENABLED_CONFIG: HighlightRelatedConfig = HighlightRelatedConfig {
408+
break_points: true,
409+
exit_points: true,
410+
references: true,
411+
closure_captures: true,
412+
yield_points: true,
413+
};
414+
355415
#[track_caller]
356416
fn check(ra_fixture: &str) {
357-
let config = HighlightRelatedConfig {
358-
break_points: true,
359-
exit_points: true,
360-
references: true,
361-
yield_points: true,
362-
};
363-
364-
check_with_config(ra_fixture, config);
417+
check_with_config(ra_fixture, ENABLED_CONFIG);
365418
}
366419

367420
#[track_caller]
@@ -1086,12 +1139,7 @@ fn function(field: u32) {
10861139

10871140
#[test]
10881141
fn test_hl_disabled_ref_local() {
1089-
let config = HighlightRelatedConfig {
1090-
references: false,
1091-
break_points: true,
1092-
exit_points: true,
1093-
yield_points: true,
1094-
};
1142+
let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
10951143

10961144
check_with_config(
10971145
r#"
@@ -1106,12 +1154,7 @@ fn foo() {
11061154

11071155
#[test]
11081156
fn test_hl_disabled_ref_local_preserved_break() {
1109-
let config = HighlightRelatedConfig {
1110-
references: false,
1111-
break_points: true,
1112-
exit_points: true,
1113-
yield_points: true,
1114-
};
1157+
let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
11151158

11161159
check_with_config(
11171160
r#"
@@ -1146,12 +1189,7 @@ fn foo() {
11461189

11471190
#[test]
11481191
fn test_hl_disabled_ref_local_preserved_yield() {
1149-
let config = HighlightRelatedConfig {
1150-
references: false,
1151-
break_points: true,
1152-
exit_points: true,
1153-
yield_points: true,
1154-
};
1192+
let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
11551193

11561194
check_with_config(
11571195
r#"
@@ -1182,12 +1220,7 @@ async fn foo() {
11821220

11831221
#[test]
11841222
fn test_hl_disabled_ref_local_preserved_exit() {
1185-
let config = HighlightRelatedConfig {
1186-
references: false,
1187-
break_points: true,
1188-
exit_points: true,
1189-
yield_points: true,
1190-
};
1223+
let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
11911224

11921225
check_with_config(
11931226
r#"
@@ -1225,12 +1258,7 @@ fn foo() ->$0 i32 {
12251258

12261259
#[test]
12271260
fn test_hl_disabled_break() {
1228-
let config = HighlightRelatedConfig {
1229-
references: true,
1230-
break_points: false,
1231-
exit_points: true,
1232-
yield_points: true,
1233-
};
1261+
let config = HighlightRelatedConfig { break_points: false, ..ENABLED_CONFIG };
12341262

12351263
check_with_config(
12361264
r#"
@@ -1246,12 +1274,7 @@ fn foo() {
12461274

12471275
#[test]
12481276
fn test_hl_disabled_yield() {
1249-
let config = HighlightRelatedConfig {
1250-
references: true,
1251-
break_points: true,
1252-
exit_points: true,
1253-
yield_points: false,
1254-
};
1277+
let config = HighlightRelatedConfig { yield_points: false, ..ENABLED_CONFIG };
12551278

12561279
check_with_config(
12571280
r#"
@@ -1265,12 +1288,7 @@ async$0 fn foo() {
12651288

12661289
#[test]
12671290
fn test_hl_disabled_exit() {
1268-
let config = HighlightRelatedConfig {
1269-
references: true,
1270-
break_points: true,
1271-
exit_points: false,
1272-
yield_points: true,
1273-
};
1291+
let config = HighlightRelatedConfig { exit_points: false, ..ENABLED_CONFIG };
12741292

12751293
check_with_config(
12761294
r#"
@@ -1411,6 +1429,34 @@ impl Trait for () {
14111429
type Output$0 = ();
14121430
// ^^^^^^
14131431
}
1432+
"#,
1433+
);
1434+
}
1435+
1436+
#[test]
1437+
fn test_closure_capture_pipe() {
1438+
check(
1439+
r#"
1440+
fn f() {
1441+
let x = 1;
1442+
// ^
1443+
let c = $0|y| x + y;
1444+
// ^ read
1445+
}
1446+
"#,
1447+
);
1448+
}
1449+
1450+
#[test]
1451+
fn test_closure_capture_move() {
1452+
check(
1453+
r#"
1454+
fn f() {
1455+
let x = 1;
1456+
// ^
1457+
let c = move$0 |y| x + y;
1458+
// ^ read
1459+
}
14141460
"#,
14151461
);
14161462
}

crates/rust-analyzer/src/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,8 @@ config_data! {
281281

282282
/// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
283283
highlightRelated_breakPoints_enable: bool = "true",
284+
/// Enables highlighting of all captures of a closure while the cursor is on the `|` or move keyword of a closure.
285+
highlightRelated_closureCaptures_enable: bool = "true",
284286
/// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).
285287
highlightRelated_exitPoints_enable: bool = "true",
286288
/// Enables highlighting of related references while the cursor is on any identifier.
@@ -1554,6 +1556,7 @@ impl Config {
15541556
break_points: self.data.highlightRelated_breakPoints_enable,
15551557
exit_points: self.data.highlightRelated_exitPoints_enable,
15561558
yield_points: self.data.highlightRelated_yieldPoints_enable,
1559+
closure_captures: self.data.highlightRelated_closureCaptures_enable,
15571560
}
15581561
}
15591562

crates/rust-analyzer/src/lsp_ext.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ pub enum HoverRequest {}
434434
impl Request for HoverRequest {
435435
type Params = HoverParams;
436436
type Result = Option<Hover>;
437-
const METHOD: &'static str = "textDocument/hover";
437+
const METHOD: &'static str = lsp_types::request::HoverRequest::METHOD;
438438
}
439439

440440
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]

crates/test-utils/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ fn try_extract_range(text: &str) -> Option<(TextRange, String)> {
9595
Some((TextRange::new(start, end), text))
9696
}
9797

98-
#[derive(Clone, Copy)]
98+
#[derive(Clone, Copy, Debug)]
9999
pub enum RangeOrOffset {
100100
Range(TextRange),
101101
Offset(TextSize),

docs/dev/lsp-extensions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!---
2-
lsp_ext.rs hash: 37ac44a0f507e05a
2+
lsp_ext.rs hash: 31ca513a249753ab
33
44
If you need to change the above hash to make the test pass, please check if you
55
need to adjust this doc as well and ping this issue:

docs/user/generated_config.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,11 @@ Controls file watching implementation.
352352
--
353353
Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
354354
--
355+
[[rust-analyzer.highlightRelated.closureCaptures.enable]]rust-analyzer.highlightRelated.closureCaptures.enable (default: `true`)::
356+
+
357+
--
358+
Enables highlighting of all captures of a closure while the cursor is on the `|` or move keyword of a closure.
359+
--
355360
[[rust-analyzer.highlightRelated.exitPoints.enable]]rust-analyzer.highlightRelated.exitPoints.enable (default: `true`)::
356361
+
357362
--

editors/code/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,11 @@
886886
"default": true,
887887
"type": "boolean"
888888
},
889+
"rust-analyzer.highlightRelated.closureCaptures.enable": {
890+
"markdownDescription": "Enables highlighting of all captures of a closure while the cursor is on the `|` or move keyword of a closure.",
891+
"default": true,
892+
"type": "boolean"
893+
},
889894
"rust-analyzer.highlightRelated.exitPoints.enable": {
890895
"markdownDescription": "Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).",
891896
"default": true,

0 commit comments

Comments
 (0)