Highlight closure captures when cursor is on pipe

This commit is contained in:
Lukas Wirth 2023-05-02 08:52:08 +02:00
parent 9c0c13ec8e
commit a64626d99e
10 changed files with 149 additions and 68 deletions

View file

@ -115,9 +115,10 @@ impl InferenceContext<'_> {
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct HirPlace { pub(crate) struct HirPlace {
pub(crate) local: BindingId, pub local: BindingId,
pub(crate) projections: Vec<ProjectionElem<Infallible, Ty>>, pub(crate) projections: Vec<ProjectionElem<Infallible, Ty>>,
} }
impl HirPlace { impl HirPlace {
fn ty(&self, ctx: &mut InferenceContext<'_>) -> Ty { fn ty(&self, ctx: &mut InferenceContext<'_>) -> Ty {
let mut ty = ctx.table.resolve_completely(ctx.result[self.local].clone()); let mut ty = ctx.table.resolve_completely(ctx.result[self.local].clone());
@ -161,6 +162,10 @@ pub struct CapturedItem {
} }
impl CapturedItem { impl CapturedItem {
pub fn local(&self) -> BindingId {
self.place.local
}
pub fn display_kind(&self) -> &'static str { pub fn display_kind(&self) -> &'static str {
match self.kind { match self.kind {
CaptureKind::ByRef(k) => match k { CaptureKind::ByRef(k) => match k {

View file

@ -3209,11 +3209,11 @@ impl Closure {
self.clone().as_ty().display(db).with_closure_style(ClosureStyle::ImplFn).to_string() 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 owner = db.lookup_intern_closure((self.id).into()).0;
let infer = &db.infer(owner); let infer = &db.infer(owner);
let info = infer.closure_info(&self.id); 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 { 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)] #[derive(Clone, PartialEq, Eq, Debug)]
pub struct Type { pub struct Type {
env: Arc<TraitEnvironment>, env: Arc<TraitEnvironment>,

View file

@ -1,6 +1,6 @@
use hir::Semantics; use hir::Semantics;
use ide_db::{ use ide_db::{
base_db::{FileId, FilePosition}, base_db::{FileId, FilePosition, FileRange},
defs::{Definition, IdentClass}, defs::{Definition, IdentClass},
helpers::pick_best_token, helpers::pick_best_token,
search::{FileReference, ReferenceCategory, SearchScope}, search::{FileReference, ReferenceCategory, SearchScope},
@ -30,6 +30,7 @@ pub struct HighlightRelatedConfig {
pub references: bool, pub references: bool,
pub exit_points: bool, pub exit_points: bool,
pub break_points: bool, pub break_points: bool,
pub closure_captures: bool,
pub yield_points: 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 { 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![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?`
T![->] => 3, T![->] | T![|] => 3,
kind if kind.is_keyword() => 2, kind if kind.is_keyword() => 2,
IDENT | INT_NUMBER => 1, IDENT | INT_NUMBER => 1,
_ => 0, _ => 0,
})?; })?;
// most if not all of these should be re-implemented with information seeded from hir
match token.kind() { match token.kind() {
T![?] if config.exit_points && token.parent().and_then(ast::TryExpr::cast).is_some() => { T![?] if config.exit_points && token.parent().and_then(ast::TryExpr::cast).is_some() => {
highlight_exit_points(sema, token) 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 => { T![break] | T![loop] | T![while] | T![continue] if config.break_points => {
highlight_break_points(token) 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), _ if config.references => highlight_references(sema, &syntax, token, file_id),
_ => None, _ => 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( fn highlight_references(
sema: &Semantics<'_, RootDatabase>, sema: &Semantics<'_, RootDatabase>,
node: &SyntaxNode, node: &SyntaxNode,
@ -93,10 +148,7 @@ fn highlight_references(
.remove(&file_id) .remove(&file_id)
}) })
.flatten() .flatten()
.map(|FileReference { category: access, range, .. }| HighlightedRange { .map(|FileReference { category, range, .. }| HighlightedRange { range, category });
range,
category: access,
});
let mut res = FxHashSet::default(); let mut res = FxHashSet::default();
for &def in &defs { for &def in &defs {
match def { match def {
@ -352,16 +404,17 @@ mod tests {
use super::*; use super::*;
const ENABLED_CONFIG: HighlightRelatedConfig = HighlightRelatedConfig {
break_points: true,
exit_points: true,
references: true,
closure_captures: true,
yield_points: true,
};
#[track_caller] #[track_caller]
fn check(ra_fixture: &str) { fn check(ra_fixture: &str) {
let config = HighlightRelatedConfig { check_with_config(ra_fixture, ENABLED_CONFIG);
break_points: true,
exit_points: true,
references: true,
yield_points: true,
};
check_with_config(ra_fixture, config);
} }
#[track_caller] #[track_caller]
@ -1086,12 +1139,7 @@ fn function(field: u32) {
#[test] #[test]
fn test_hl_disabled_ref_local() { fn test_hl_disabled_ref_local() {
let config = HighlightRelatedConfig { let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
references: false,
break_points: true,
exit_points: true,
yield_points: true,
};
check_with_config( check_with_config(
r#" r#"
@ -1106,12 +1154,7 @@ fn foo() {
#[test] #[test]
fn test_hl_disabled_ref_local_preserved_break() { fn test_hl_disabled_ref_local_preserved_break() {
let config = HighlightRelatedConfig { let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
references: false,
break_points: true,
exit_points: true,
yield_points: true,
};
check_with_config( check_with_config(
r#" r#"
@ -1146,12 +1189,7 @@ fn foo() {
#[test] #[test]
fn test_hl_disabled_ref_local_preserved_yield() { fn test_hl_disabled_ref_local_preserved_yield() {
let config = HighlightRelatedConfig { let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
references: false,
break_points: true,
exit_points: true,
yield_points: true,
};
check_with_config( check_with_config(
r#" r#"
@ -1182,12 +1220,7 @@ async fn foo() {
#[test] #[test]
fn test_hl_disabled_ref_local_preserved_exit() { fn test_hl_disabled_ref_local_preserved_exit() {
let config = HighlightRelatedConfig { let config = HighlightRelatedConfig { references: false, ..ENABLED_CONFIG };
references: false,
break_points: true,
exit_points: true,
yield_points: true,
};
check_with_config( check_with_config(
r#" r#"
@ -1225,12 +1258,7 @@ fn foo() ->$0 i32 {
#[test] #[test]
fn test_hl_disabled_break() { fn test_hl_disabled_break() {
let config = HighlightRelatedConfig { let config = HighlightRelatedConfig { break_points: false, ..ENABLED_CONFIG };
references: true,
break_points: false,
exit_points: true,
yield_points: true,
};
check_with_config( check_with_config(
r#" r#"
@ -1246,12 +1274,7 @@ fn foo() {
#[test] #[test]
fn test_hl_disabled_yield() { fn test_hl_disabled_yield() {
let config = HighlightRelatedConfig { let config = HighlightRelatedConfig { yield_points: false, ..ENABLED_CONFIG };
references: true,
break_points: true,
exit_points: true,
yield_points: false,
};
check_with_config( check_with_config(
r#" r#"
@ -1265,12 +1288,7 @@ async$0 fn foo() {
#[test] #[test]
fn test_hl_disabled_exit() { fn test_hl_disabled_exit() {
let config = HighlightRelatedConfig { let config = HighlightRelatedConfig { exit_points: false, ..ENABLED_CONFIG };
references: true,
break_points: true,
exit_points: false,
yield_points: true,
};
check_with_config( check_with_config(
r#" r#"
@ -1411,6 +1429,34 @@ impl Trait for () {
type Output$0 = (); type Output$0 = ();
// ^^^^^^ // ^^^^^^
} }
"#,
);
}
#[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
}
"#, "#,
); );
} }

View file

@ -281,6 +281,8 @@ config_data! {
/// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords. /// Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords.
highlightRelated_breakPoints_enable: bool = "true", 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 (`->`). /// Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).
highlightRelated_exitPoints_enable: bool = "true", highlightRelated_exitPoints_enable: bool = "true",
/// Enables highlighting of related references while the cursor is on any identifier. /// 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, break_points: self.data.highlightRelated_breakPoints_enable,
exit_points: self.data.highlightRelated_exitPoints_enable, exit_points: self.data.highlightRelated_exitPoints_enable,
yield_points: self.data.highlightRelated_yieldPoints_enable, yield_points: self.data.highlightRelated_yieldPoints_enable,
closure_captures: self.data.highlightRelated_closureCaptures_enable,
} }
} }

View file

@ -434,7 +434,7 @@ pub enum HoverRequest {}
impl Request for HoverRequest { impl Request for HoverRequest {
type Params = HoverParams; type Params = HoverParams;
type Result = Option<Hover>; 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)] #[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]

View file

@ -95,7 +95,7 @@ fn try_extract_range(text: &str) -> Option<(TextRange, String)> {
Some((TextRange::new(start, end), text)) Some((TextRange::new(start, end), text))
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy, Debug)]
pub enum RangeOrOffset { pub enum RangeOrOffset {
Range(TextRange), Range(TextRange),
Offset(TextSize), Offset(TextSize),

View file

@ -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 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: need to adjust this doc as well and ping this issue:

View file

@ -352,6 +352,11 @@ Controls file watching implementation.
-- --
Enables highlighting of related references while the cursor is on `break`, `loop`, `while`, or `for` keywords. 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`):: [[rust-analyzer.highlightRelated.exitPoints.enable]]rust-analyzer.highlightRelated.exitPoints.enable (default: `true`)::
+ +
-- --

View file

@ -886,6 +886,11 @@
"default": true, "default": true,
"type": "boolean" "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": { "rust-analyzer.highlightRelated.exitPoints.enable": {
"markdownDescription": "Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).", "markdownDescription": "Enables highlighting of all exit points while the cursor is on any `return`, `?`, `fn`, or return type arrow (`->`).",
"default": true, "default": true,

View file

@ -10,12 +10,9 @@ export const hover = new lc.RequestType<
HoverParams, HoverParams,
(lc.Hover & { actions: CommandLinkGroup[] }) | null, (lc.Hover & { actions: CommandLinkGroup[] }) | null,
void void
>("textDocument/hover"); >(lc.HoverRequest.method);
export type HoverParams = { position: lc.Position | lc.Range } & Omit< export type HoverParams = { position: lc.Position | lc.Range } & Omit<lc.HoverParams, "position">;
lc.TextDocumentPositionParams,
"position"
> &
lc.WorkDoneProgressParams;
export type CommandLink = { export type CommandLink = {
/** /**
* A tooltip for the command, when represented in the UI. * A tooltip for the command, when represented in the UI.