]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/matching_brace.rs
Merge #11579
[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 //
18 // image::https://user-images.githubusercontent.com/48062697/113065573-04298180-91b1-11eb-8dec-d4e2a202f304.gif[]
19 pub(crate) fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> {
20     const BRACES: &[SyntaxKind] =
21         &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>], T![|], T![|]];
22     let (brace_token, brace_idx) = file
23         .syntax()
24         .token_at_offset(offset)
25         .filter_map(|node| {
26             let idx = BRACES.iter().position(|&brace| brace == node.kind())?;
27             Some((node, idx))
28         })
29         .last()?;
30     let parent = brace_token.parent()?;
31     if brace_token.kind() == T![|] && !ast::ParamList::can_cast(parent.kind()) {
32         cov_mark::hit!(pipes_not_braces);
33         return None;
34     }
35     let matching_kind = BRACES[brace_idx ^ 1];
36     let matching_node = parent
37         .children_with_tokens()
38         .filter_map(|it| it.into_token())
39         .find(|node| node.kind() == matching_kind && node != &brace_token)?;
40     Some(matching_node.text_range().start())
41 }
42
43 #[cfg(test)]
44 mod tests {
45     use test_utils::{add_cursor, assert_eq_text, extract_offset};
46
47     use super::*;
48
49     #[test]
50     fn test_matching_brace() {
51         fn do_check(before: &str, after: &str) {
52             let (pos, before) = extract_offset(before);
53             let parse = SourceFile::parse(&before);
54             let new_pos = match matching_brace(&parse.tree(), pos) {
55                 None => pos,
56                 Some(pos) => pos,
57             };
58             let actual = add_cursor(&before, new_pos);
59             assert_eq_text!(after, &actual);
60         }
61
62         do_check("struct Foo { a: i32, }$0", "struct Foo $0{ a: i32, }");
63         do_check("fn main() { |x: i32|$0 x * 2;}", "fn main() { $0|x: i32| x * 2;}");
64         do_check("fn main() { $0|x: i32| x * 2;}", "fn main() { |x: i32$0| x * 2;}");
65         do_check(
66             "fn func(x) { return (2 * (x + 3)$0) + 5;}",
67             "fn func(x) { return $0(2 * (x + 3)) + 5;}",
68         );
69
70         {
71             cov_mark::check!(pipes_not_braces);
72             do_check(
73                 "fn main() { match 92 { 1 | 2 |$0 3 => 92 } }",
74                 "fn main() { match 92 { 1 | 2 |$0 3 => 92 } }",
75             );
76         }
77     }
78 }