]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/matching_brace.rs
Merge #7941
[rust.git] / crates / ide / src / matching_brace.rs
1 use syntax::{
2     ast::{self, AstNode},
3     SourceFile, SyntaxKind, TextSize, T,
4 };
5
6 // Feature: Matching Brace
7 //
8 // If the cursor is on any brace (`<>(){}[]||`) which is a part of a brace-pair,
9 // moves cursor to the matching brace. It uses the actual parser to determine
10 // braces, so it won't confuse generics with comparisons.
11 //
12 // |===
13 // | Editor  | Action Name
14 //
15 // | VS Code | **Rust Analyzer: Find matching brace**
16 // |===
17 pub(crate) fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> {
18     const BRACES: &[SyntaxKind] =
19         &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>], T![|], T![|]];
20     let (brace_token, brace_idx) = file
21         .syntax()
22         .token_at_offset(offset)
23         .filter_map(|node| {
24             let idx = BRACES.iter().position(|&brace| brace == node.kind())?;
25             Some((node, idx))
26         })
27         .next()?;
28     let parent = brace_token.parent();
29     if brace_token.kind() == T![|] && !ast::ParamList::can_cast(parent.kind()) {
30         cov_mark::hit!(pipes_not_braces);
31         return None;
32     }
33     let matching_kind = BRACES[brace_idx ^ 1];
34     let matching_node = parent
35         .children_with_tokens()
36         .filter_map(|it| it.into_token())
37         .find(|node| node.kind() == matching_kind && node != &brace_token)?;
38     Some(matching_node.text_range().start())
39 }
40
41 #[cfg(test)]
42 mod tests {
43     use test_utils::{add_cursor, assert_eq_text, extract_offset};
44
45     use super::*;
46
47     #[test]
48     fn test_matching_brace() {
49         fn do_check(before: &str, after: &str) {
50             let (pos, before) = extract_offset(before);
51             let parse = SourceFile::parse(&before);
52             let new_pos = match matching_brace(&parse.tree(), pos) {
53                 None => pos,
54                 Some(pos) => pos,
55             };
56             let actual = add_cursor(&before, new_pos);
57             assert_eq_text!(after, &actual);
58         }
59
60         do_check("struct Foo { a: i32, }$0", "struct Foo $0{ a: i32, }");
61         do_check("fn main() { |x: i32|$0 x * 2;}", "fn main() { $0|x: i32| x * 2;}");
62         do_check("fn main() { $0|x: i32| x * 2;}", "fn main() { |x: i32$0| x * 2;}");
63
64         {
65             cov_mark::check!(pipes_not_braces);
66             do_check(
67                 "fn main() { match 92 { 1 | 2 |$0 3 => 92 } }",
68                 "fn main() { match 92 { 1 | 2 |$0 3 => 92 } }",
69             );
70         }
71     }
72 }