mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-25 11:17:13 +00:00
Merge pull request #19546 from roife/branch-value-highlights
feat: highlighting of return values while the cursor is on `match` / `if` / `=>`
This commit is contained in:
commit
560f235362
@ -291,13 +291,14 @@ fn handle_control_flow_keywords(
|
||||
token: &SyntaxToken,
|
||||
) -> Option<Vec<NavigationTarget>> {
|
||||
match token.kind() {
|
||||
// For `fn` / `loop` / `while` / `for` / `async`, return the keyword it self,
|
||||
// For `fn` / `loop` / `while` / `for` / `async` / `match`, return the keyword it self,
|
||||
// so that VSCode will find the references when using `ctrl + click`
|
||||
T![fn] | T![async] | T![try] | T![return] => nav_for_exit_points(sema, token),
|
||||
T![loop] | T![while] | T![break] | T![continue] => nav_for_break_points(sema, token),
|
||||
T![for] if token.parent().and_then(ast::ForExpr::cast).is_some() => {
|
||||
nav_for_break_points(sema, token)
|
||||
}
|
||||
T![match] | T![=>] | T![if] => nav_for_branch_exit_points(sema, token),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -407,6 +408,91 @@ fn nav_for_exit_points(
|
||||
Some(navs)
|
||||
}
|
||||
|
||||
pub(crate) fn find_branch_root(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
token: &SyntaxToken,
|
||||
) -> Vec<SyntaxNode> {
|
||||
let find_nodes = |node_filter: fn(SyntaxNode) -> Option<SyntaxNode>| {
|
||||
sema.descend_into_macros(token.clone())
|
||||
.into_iter()
|
||||
.filter_map(|token| node_filter(token.parent()?))
|
||||
.collect_vec()
|
||||
};
|
||||
|
||||
match token.kind() {
|
||||
T![match] => find_nodes(|node| Some(ast::MatchExpr::cast(node)?.syntax().clone())),
|
||||
T![=>] => find_nodes(|node| Some(ast::MatchArm::cast(node)?.syntax().clone())),
|
||||
T![if] => find_nodes(|node| {
|
||||
let if_expr = ast::IfExpr::cast(node)?;
|
||||
|
||||
let root_if = iter::successors(Some(if_expr.clone()), |if_expr| {
|
||||
let parent_if = if_expr.syntax().parent().and_then(ast::IfExpr::cast)?;
|
||||
let ast::ElseBranch::IfExpr(else_branch) = parent_if.else_branch()? else {
|
||||
return None;
|
||||
};
|
||||
|
||||
(else_branch.syntax() == if_expr.syntax()).then_some(parent_if)
|
||||
})
|
||||
.last()?;
|
||||
|
||||
Some(root_if.syntax().clone())
|
||||
}),
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn nav_for_branch_exit_points(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
token: &SyntaxToken,
|
||||
) -> Option<Vec<NavigationTarget>> {
|
||||
let db = sema.db;
|
||||
|
||||
let navs = match token.kind() {
|
||||
T![match] => find_branch_root(sema, token)
|
||||
.into_iter()
|
||||
.filter_map(|node| {
|
||||
let file_id = sema.hir_file_for(&node);
|
||||
let match_expr = ast::MatchExpr::cast(node)?;
|
||||
let focus_range = match_expr.match_token()?.text_range();
|
||||
let match_expr_in_file = InFile::new(file_id, match_expr.into());
|
||||
Some(expr_to_nav(db, match_expr_in_file, Some(focus_range)))
|
||||
})
|
||||
.flatten()
|
||||
.collect_vec(),
|
||||
|
||||
T![=>] => find_branch_root(sema, token)
|
||||
.into_iter()
|
||||
.filter_map(|node| {
|
||||
let match_arm = ast::MatchArm::cast(node)?;
|
||||
let match_expr = sema
|
||||
.ancestors_with_macros(match_arm.syntax().clone())
|
||||
.find_map(ast::MatchExpr::cast)?;
|
||||
let file_id = sema.hir_file_for(match_expr.syntax());
|
||||
let focus_range = match_arm.fat_arrow_token()?.text_range();
|
||||
let match_expr_in_file = InFile::new(file_id, match_expr.into());
|
||||
Some(expr_to_nav(db, match_expr_in_file, Some(focus_range)))
|
||||
})
|
||||
.flatten()
|
||||
.collect_vec(),
|
||||
|
||||
T![if] => find_branch_root(sema, token)
|
||||
.into_iter()
|
||||
.filter_map(|node| {
|
||||
let file_id = sema.hir_file_for(&node);
|
||||
let if_expr = ast::IfExpr::cast(node)?;
|
||||
let focus_range = if_expr.if_token()?.text_range();
|
||||
let if_expr_in_file = InFile::new(file_id, if_expr.into());
|
||||
Some(expr_to_nav(db, if_expr_in_file, Some(focus_range)))
|
||||
})
|
||||
.flatten()
|
||||
.collect_vec(),
|
||||
|
||||
_ => return Some(Vec::new()),
|
||||
};
|
||||
|
||||
Some(navs)
|
||||
}
|
||||
|
||||
pub(crate) fn find_loops(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
token: &SyntaxToken,
|
||||
@ -3614,4 +3700,155 @@ fn foo() {
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_for_match_keyword() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
match$0 0 {
|
||||
// ^^^^^
|
||||
0 => {},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_for_match_arm_fat_arrow() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
match 0 {
|
||||
0 =>$0 {},
|
||||
// ^^
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_for_if_keyword() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
if$0 true {
|
||||
// ^^
|
||||
()
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_for_match_nested_in_if() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
if true {
|
||||
match$0 0 {
|
||||
// ^^^^^
|
||||
0 => {},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_for_multiple_match_expressions() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
match 0 {
|
||||
0 => {},
|
||||
_ => {},
|
||||
};
|
||||
|
||||
match$0 1 {
|
||||
// ^^^^^
|
||||
1 => {},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_for_nested_match_expressions() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
match 0 {
|
||||
0 => match$0 1 {
|
||||
// ^^^^^
|
||||
1 => {},
|
||||
_ => {},
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_for_if_else_chains() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
if true {
|
||||
// ^^
|
||||
()
|
||||
} else if$0 false {
|
||||
()
|
||||
} else {
|
||||
()
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_for_match_with_guards() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
match 42 {
|
||||
x if x > 0 =>$0 {},
|
||||
// ^^
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_for_match_with_macro_arm() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! arm {
|
||||
() => { 0 => {} };
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match$0 0 {
|
||||
// ^^^^^
|
||||
arm!(),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -37,8 +37,11 @@ pub struct HighlightRelatedConfig {
|
||||
pub break_points: bool,
|
||||
pub closure_captures: bool,
|
||||
pub yield_points: bool,
|
||||
pub branch_exit_points: bool,
|
||||
}
|
||||
|
||||
type HighlightMap = FxHashMap<EditionedFileId, FxHashSet<HighlightedRange>>;
|
||||
|
||||
// Feature: Highlight Related
|
||||
//
|
||||
// Highlights constructs related to the thing under the cursor:
|
||||
@ -64,7 +67,7 @@ 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![->] => 4,
|
||||
T![->] | T![=>] => 4,
|
||||
kind if kind.is_keyword(file_id.edition(sema.db)) => 3,
|
||||
IDENT | INT_NUMBER => 2,
|
||||
T![|] => 1,
|
||||
@ -78,6 +81,9 @@ pub(crate) fn highlight_related(
|
||||
T![fn] | T![return] | T![->] if config.exit_points => {
|
||||
highlight_exit_points(sema, token).remove(&file_id)
|
||||
}
|
||||
T![match] | T![=>] | T![if] if config.branch_exit_points => {
|
||||
highlight_branch_exit_points(sema, token).remove(&file_id)
|
||||
}
|
||||
T![await] | T![async] if config.yield_points => {
|
||||
highlight_yield_points(sema, token).remove(&file_id)
|
||||
}
|
||||
@ -300,11 +306,93 @@ fn highlight_references(
|
||||
if res.is_empty() { None } else { Some(res.into_iter().collect()) }
|
||||
}
|
||||
|
||||
pub(crate) fn highlight_branch_exit_points(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
token: SyntaxToken,
|
||||
) -> FxHashMap<EditionedFileId, Vec<HighlightedRange>> {
|
||||
let mut highlights: HighlightMap = FxHashMap::default();
|
||||
|
||||
let push_to_highlights = |file_id, range, highlights: &mut HighlightMap| {
|
||||
if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) {
|
||||
let hrange = HighlightedRange { category: ReferenceCategory::empty(), range };
|
||||
highlights.entry(file_id).or_default().insert(hrange);
|
||||
}
|
||||
};
|
||||
|
||||
let push_tail_expr = |tail: Option<ast::Expr>, highlights: &mut HighlightMap| {
|
||||
let Some(tail) = tail else {
|
||||
return;
|
||||
};
|
||||
|
||||
for_each_tail_expr(&tail, &mut |tail| {
|
||||
let file_id = sema.hir_file_for(tail.syntax());
|
||||
let range = tail.syntax().text_range();
|
||||
push_to_highlights(file_id, Some(range), highlights);
|
||||
});
|
||||
};
|
||||
|
||||
let nodes = goto_definition::find_branch_root(sema, &token).into_iter();
|
||||
match token.kind() {
|
||||
T![match] => {
|
||||
for match_expr in nodes.filter_map(ast::MatchExpr::cast) {
|
||||
let file_id = sema.hir_file_for(match_expr.syntax());
|
||||
let range = match_expr.match_token().map(|token| token.text_range());
|
||||
push_to_highlights(file_id, range, &mut highlights);
|
||||
|
||||
let Some(arm_list) = match_expr.match_arm_list() else {
|
||||
continue;
|
||||
};
|
||||
for arm in arm_list.arms() {
|
||||
push_tail_expr(arm.expr(), &mut highlights);
|
||||
}
|
||||
}
|
||||
}
|
||||
T![=>] => {
|
||||
for arm in nodes.filter_map(ast::MatchArm::cast) {
|
||||
let file_id = sema.hir_file_for(arm.syntax());
|
||||
let range = arm.fat_arrow_token().map(|token| token.text_range());
|
||||
push_to_highlights(file_id, range, &mut highlights);
|
||||
|
||||
push_tail_expr(arm.expr(), &mut highlights);
|
||||
}
|
||||
}
|
||||
T![if] => {
|
||||
for mut if_to_process in nodes.map(ast::IfExpr::cast) {
|
||||
while let Some(cur_if) = if_to_process.take() {
|
||||
let file_id = sema.hir_file_for(cur_if.syntax());
|
||||
|
||||
let if_kw_range = cur_if.if_token().map(|token| token.text_range());
|
||||
push_to_highlights(file_id, if_kw_range, &mut highlights);
|
||||
|
||||
if let Some(then_block) = cur_if.then_branch() {
|
||||
push_tail_expr(Some(then_block.into()), &mut highlights);
|
||||
}
|
||||
|
||||
match cur_if.else_branch() {
|
||||
Some(ast::ElseBranch::Block(else_block)) => {
|
||||
push_tail_expr(Some(else_block.into()), &mut highlights);
|
||||
if_to_process = None;
|
||||
}
|
||||
Some(ast::ElseBranch::IfExpr(nested_if)) => if_to_process = Some(nested_if),
|
||||
None => if_to_process = None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
highlights
|
||||
.into_iter()
|
||||
.map(|(file_id, ranges)| (file_id, ranges.into_iter().collect()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn hl_exit_points(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
def_token: Option<SyntaxToken>,
|
||||
body: ast::Expr,
|
||||
) -> Option<FxHashMap<EditionedFileId, FxHashSet<HighlightedRange>>> {
|
||||
) -> Option<HighlightMap> {
|
||||
let mut highlights: FxHashMap<EditionedFileId, FxHashSet<_>> = FxHashMap::default();
|
||||
|
||||
let mut push_to_highlights = |file_id, range| {
|
||||
@ -411,7 +499,7 @@ pub(crate) fn highlight_break_points(
|
||||
loop_token: Option<SyntaxToken>,
|
||||
label: Option<ast::Label>,
|
||||
expr: ast::Expr,
|
||||
) -> Option<FxHashMap<EditionedFileId, FxHashSet<HighlightedRange>>> {
|
||||
) -> Option<HighlightMap> {
|
||||
let mut highlights: FxHashMap<EditionedFileId, FxHashSet<_>> = FxHashMap::default();
|
||||
|
||||
let mut push_to_highlights = |file_id, range| {
|
||||
@ -504,7 +592,7 @@ pub(crate) fn highlight_yield_points(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
async_token: Option<SyntaxToken>,
|
||||
body: Option<ast::Expr>,
|
||||
) -> Option<FxHashMap<EditionedFileId, FxHashSet<HighlightedRange>>> {
|
||||
) -> Option<HighlightMap> {
|
||||
let mut highlights: FxHashMap<EditionedFileId, FxHashSet<_>> = FxHashMap::default();
|
||||
|
||||
let mut push_to_highlights = |file_id, range| {
|
||||
@ -597,10 +685,7 @@ fn original_frange(
|
||||
InFile::new(file_id, text_range?).original_node_file_range_opt(db).map(|(frange, _)| frange)
|
||||
}
|
||||
|
||||
fn merge_map(
|
||||
res: &mut FxHashMap<EditionedFileId, FxHashSet<HighlightedRange>>,
|
||||
new: Option<FxHashMap<EditionedFileId, FxHashSet<HighlightedRange>>>,
|
||||
) {
|
||||
fn merge_map(res: &mut HighlightMap, new: Option<HighlightMap>) {
|
||||
let Some(new) = new else {
|
||||
return;
|
||||
};
|
||||
@ -750,6 +835,7 @@ mod tests {
|
||||
references: true,
|
||||
closure_captures: true,
|
||||
yield_points: true,
|
||||
branch_exit_points: true,
|
||||
};
|
||||
|
||||
#[track_caller]
|
||||
@ -2134,6 +2220,62 @@ fn main() {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_match() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
match$0 0 {
|
||||
// ^^^^^
|
||||
0 => match 1 {
|
||||
1 => 2,
|
||||
// ^
|
||||
_ => 3,
|
||||
// ^
|
||||
},
|
||||
_ => 4,
|
||||
// ^
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_arm_highlight() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
match 0 {
|
||||
0 =>$0 {
|
||||
// ^^
|
||||
let x = 1;
|
||||
x
|
||||
// ^
|
||||
}
|
||||
_ => 2,
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_branches_when_disabled() {
|
||||
let config = HighlightRelatedConfig { branch_exit_points: false, ..ENABLED_CONFIG };
|
||||
check_with_config(
|
||||
r#"
|
||||
fn main() {
|
||||
match$0 0 {
|
||||
0 => 1,
|
||||
_ => 2,
|
||||
}
|
||||
}
|
||||
"#,
|
||||
config,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn asm() {
|
||||
check(
|
||||
@ -2164,6 +2306,200 @@ pub unsafe fn bootstrap() -> ! {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complex_arms_highlight() {
|
||||
check(
|
||||
r#"
|
||||
fn calculate(n: i32) -> i32 { n * 2 }
|
||||
|
||||
fn main() {
|
||||
match$0 Some(1) {
|
||||
// ^^^^^
|
||||
Some(x) => match x {
|
||||
0 => { let y = x; y },
|
||||
// ^
|
||||
1 => calculate(x),
|
||||
//^^^^^^^^^^^^
|
||||
_ => (|| 6)(),
|
||||
// ^^^^^^^^
|
||||
},
|
||||
None => loop {
|
||||
break 5;
|
||||
// ^^^^^^^
|
||||
},
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_in_macro_highlight() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! M {
|
||||
($e:expr) => { $e };
|
||||
}
|
||||
|
||||
fn main() {
|
||||
M!{
|
||||
match$0 Some(1) {
|
||||
// ^^^^^
|
||||
Some(x) => x,
|
||||
// ^
|
||||
None => 0,
|
||||
// ^
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_in_macro_highlight_2() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! match_ast {
|
||||
(match $node:ident { $($tt:tt)* }) => { $crate::match_ast!(match ($node) { $($tt)* }) };
|
||||
|
||||
(match ($node:expr) {
|
||||
$( $( $path:ident )::+ ($it:pat) => $res:expr, )*
|
||||
_ => $catch_all:expr $(,)?
|
||||
}) => {{
|
||||
$( if let Some($it) = $($path::)+cast($node.clone()) { $res } else )*
|
||||
{ $catch_all }
|
||||
}};
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match_ast! {
|
||||
match$0 Some(1) {
|
||||
Some(x) => x,
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_if_else() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
if$0 true {
|
||||
// ^^
|
||||
if false {
|
||||
1
|
||||
// ^
|
||||
} else {
|
||||
2
|
||||
// ^
|
||||
}
|
||||
} else {
|
||||
3
|
||||
// ^
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_else_if_highlight() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
if$0 true {
|
||||
// ^^
|
||||
1
|
||||
// ^
|
||||
} else if false {
|
||||
// ^^
|
||||
2
|
||||
// ^
|
||||
} else {
|
||||
3
|
||||
// ^
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complex_if_branches() {
|
||||
check(
|
||||
r#"
|
||||
fn calculate(n: i32) -> i32 { n * 2 }
|
||||
|
||||
fn main() {
|
||||
if$0 true {
|
||||
// ^^
|
||||
let x = 5;
|
||||
calculate(x)
|
||||
// ^^^^^^^^^^^^
|
||||
} else if false {
|
||||
// ^^
|
||||
(|| 10)()
|
||||
// ^^^^^^^^^
|
||||
} else {
|
||||
loop {
|
||||
break 15;
|
||||
// ^^^^^^^^
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_in_macro_highlight() {
|
||||
check(
|
||||
r#"
|
||||
macro_rules! M {
|
||||
($e:expr) => { $e };
|
||||
}
|
||||
|
||||
fn main() {
|
||||
M!{
|
||||
if$0 true {
|
||||
// ^^
|
||||
5
|
||||
// ^
|
||||
} else {
|
||||
10
|
||||
// ^^
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_in_macro() {
|
||||
// We should not highlight the outer `match` expression.
|
||||
check(
|
||||
r#"
|
||||
macro_rules! M {
|
||||
(match) => { 1 };
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match Some(1) {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
M!(match$0)
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn labeled_block_tail_expr() {
|
||||
check(
|
||||
|
@ -21,6 +21,7 @@ use hir::{PathResolution, Semantics};
|
||||
use ide_db::{
|
||||
FileId, RootDatabase,
|
||||
defs::{Definition, NameClass, NameRefClass},
|
||||
helpers::pick_best_token,
|
||||
search::{ReferenceCategory, SearchScope, UsageSearchResult},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
@ -397,7 +398,11 @@ fn handle_control_flow_keywords(
|
||||
.attach_first_edition(file_id)
|
||||
.map(|it| it.edition(sema.db))
|
||||
.unwrap_or(Edition::CURRENT);
|
||||
let token = file.syntax().token_at_offset(offset).find(|t| t.kind().is_keyword(edition))?;
|
||||
let token = pick_best_token(file.syntax().token_at_offset(offset), |kind| match kind {
|
||||
_ if kind.is_keyword(edition) => 4,
|
||||
T![=>] => 3,
|
||||
_ => 1,
|
||||
})?;
|
||||
|
||||
let references = match token.kind() {
|
||||
T![fn] | T![return] | T![try] => highlight_related::highlight_exit_points(sema, token),
|
||||
@ -408,6 +413,7 @@ fn handle_control_flow_keywords(
|
||||
T![for] if token.parent().and_then(ast::ForExpr::cast).is_some() => {
|
||||
highlight_related::highlight_break_points(sema, token)
|
||||
}
|
||||
T![if] | T![=>] | T![match] => highlight_related::highlight_branch_exit_points(sema, token),
|
||||
_ => return None,
|
||||
}
|
||||
.into_iter()
|
||||
@ -1344,6 +1350,159 @@ impl Foo {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_if_branches() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
let x = if$0 true {
|
||||
1
|
||||
} else if false {
|
||||
2
|
||||
} else {
|
||||
3
|
||||
};
|
||||
|
||||
println!("x: {}", x);
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
FileId(0) 24..26
|
||||
FileId(0) 42..43
|
||||
FileId(0) 55..57
|
||||
FileId(0) 74..75
|
||||
FileId(0) 97..98
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_match_branches() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
$0match Some(42) {
|
||||
Some(x) if x > 0 => println!("positive"),
|
||||
Some(0) => println!("zero"),
|
||||
Some(_) => println!("negative"),
|
||||
None => println!("none"),
|
||||
};
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
FileId(0) 16..21
|
||||
FileId(0) 61..81
|
||||
FileId(0) 102..118
|
||||
FileId(0) 139..159
|
||||
FileId(0) 177..193
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_match_arm_arrow() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
match Some(42) {
|
||||
Some(x) if x > 0 $0=> println!("positive"),
|
||||
Some(0) => println!("zero"),
|
||||
Some(_) => println!("negative"),
|
||||
None => println!("none"),
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
FileId(0) 58..60
|
||||
FileId(0) 61..81
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_nested_branches() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
let x = $0if true {
|
||||
if false {
|
||||
1
|
||||
} else {
|
||||
match Some(42) {
|
||||
Some(_) => 2,
|
||||
None => 3,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
4
|
||||
};
|
||||
|
||||
println!("x: {}", x);
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
FileId(0) 24..26
|
||||
FileId(0) 65..66
|
||||
FileId(0) 140..141
|
||||
FileId(0) 167..168
|
||||
FileId(0) 215..216
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_match_with_complex_guards() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
let x = $0match (x, y) {
|
||||
(a, b) if a > b && a % 2 == 0 => 1,
|
||||
(a, b) if a < b || b % 2 == 1 => 2,
|
||||
(a, _) if a > 40 => 3,
|
||||
_ => 4,
|
||||
};
|
||||
|
||||
println!("x: {}", x);
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
FileId(0) 24..29
|
||||
FileId(0) 80..81
|
||||
FileId(0) 124..125
|
||||
FileId(0) 155..156
|
||||
FileId(0) 171..172
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_mixed_if_match_expressions() {
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
let x = $0if let Some(x) = Some(42) {
|
||||
1
|
||||
} else if let None = None {
|
||||
2
|
||||
} else {
|
||||
match 42 {
|
||||
0 => 3,
|
||||
_ => 4,
|
||||
}
|
||||
};
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
FileId(0) 24..26
|
||||
FileId(0) 60..61
|
||||
FileId(0) 73..75
|
||||
FileId(0) 102..103
|
||||
FileId(0) 153..154
|
||||
FileId(0) 173..174
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
|
||||
check_with_scope(ra_fixture, None, expect)
|
||||
}
|
||||
@ -2867,4 +3026,66 @@ const FOO$0: i32 = 0;
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_if_let_match_combined() {
|
||||
check(
|
||||
r#"
|
||||
enum MyEnum { A(i32), B(String), C }
|
||||
|
||||
fn main() {
|
||||
let val = MyEnum::A(42);
|
||||
|
||||
let x = $0if let MyEnum::A(x) = val {
|
||||
1
|
||||
} else if let MyEnum::B(s) = val {
|
||||
2
|
||||
} else {
|
||||
match val {
|
||||
MyEnum::C => 3,
|
||||
_ => 4,
|
||||
}
|
||||
};
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
FileId(0) 92..94
|
||||
FileId(0) 128..129
|
||||
FileId(0) 141..143
|
||||
FileId(0) 177..178
|
||||
FileId(0) 237..238
|
||||
FileId(0) 257..258
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight_nested_match_expressions() {
|
||||
check(
|
||||
r#"
|
||||
enum Outer { A(Inner), B }
|
||||
enum Inner { X, Y(i32) }
|
||||
|
||||
fn main() {
|
||||
let val = Outer::A(Inner::Y(42));
|
||||
|
||||
$0match val {
|
||||
Outer::A(inner) => match inner {
|
||||
Inner::X => println!("Inner::X"),
|
||||
Inner::Y(n) if n > 0 => println!("Inner::Y positive: {}", n),
|
||||
Inner::Y(_) => println!("Inner::Y non-positive"),
|
||||
},
|
||||
Outer::B => println!("Outer::B"),
|
||||
}
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
FileId(0) 108..113
|
||||
FileId(0) 185..205
|
||||
FileId(0) 243..279
|
||||
FileId(0) 308..341
|
||||
FileId(0) 374..394
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -94,6 +94,8 @@ config_data! {
|
||||
|
||||
|
||||
|
||||
/// Enables highlighting of related return values while the cursor is on any `match`, `if`, or match arm arrow (`=>`).
|
||||
highlightRelated_branchExitPoints_enable: bool = true,
|
||||
/// 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.
|
||||
@ -1629,6 +1631,7 @@ impl Config {
|
||||
exit_points: self.highlightRelated_exitPoints_enable().to_owned(),
|
||||
yield_points: self.highlightRelated_yieldPoints_enable().to_owned(),
|
||||
closure_captures: self.highlightRelated_closureCaptures_enable().to_owned(),
|
||||
branch_exit_points: self.highlightRelated_branchExitPoints_enable().to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -612,6 +612,13 @@ Default: `"client"`
|
||||
Controls file watching implementation.
|
||||
|
||||
|
||||
## rust-analyzer.highlightRelated.branchExitPoints.enable {#highlightRelated.branchExitPoints.enable}
|
||||
|
||||
Default: `true`
|
||||
|
||||
Enables highlighting of related return values while the cursor is on any `match`, `if`, or match arm arrow (`=>`).
|
||||
|
||||
|
||||
## rust-analyzer.highlightRelated.breakPoints.enable {#highlightRelated.breakPoints.enable}
|
||||
|
||||
Default: `true`
|
||||
|
@ -1529,6 +1529,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "highlightRelated",
|
||||
"properties": {
|
||||
"rust-analyzer.highlightRelated.branchExitPoints.enable": {
|
||||
"markdownDescription": "Enables highlighting of related return values while the cursor is on any `match`, `if`, or match arm arrow (`=>`).",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "highlightRelated",
|
||||
"properties": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user