1use syntax::{
2 SourceFile, SyntaxKind, T, TextSize,
3 ast::{self, AstNode},
4};
5
6pub(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 current = file.syntax().token_at_offset(offset);
21 if let Some((brace_token, brace_idx)) = current
22 .clone()
23 .filter_map(|node| {
24 let idx = BRACES.iter().position(|&brace| brace == node.kind())?;
25 Some((node, idx))
26 })
27 .last()
28 {
29 let parent = brace_token.parent()?;
30 if brace_token.kind() == T![|] && !ast::ParamList::can_cast(parent.kind()) {
31 cov_mark::hit!(pipes_not_braces);
32 return None;
33 }
34 let matching_kind = BRACES[brace_idx ^ 1];
35 let matching_node = parent
36 .children_with_tokens()
37 .filter_map(|it| it.into_token())
38 .find(|node| node.kind() == matching_kind && node != &brace_token)?;
39 Some(matching_node.text_range().start())
40 } else {
41 current.last()?.parent_ancestors().find_map(|x| {
43 x.children_with_tokens()
44 .filter_map(|it| it.into_token())
45 .filter(|node| BRACES.contains(&node.kind()))
47 .last()
48 .map(|x| x.text_range().start())
49 })
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use test_utils::{add_cursor, assert_eq_text, extract_offset};
56
57 use super::*;
58
59 #[test]
60 fn test_matching_brace() {
61 fn do_check(before: &str, after: &str) {
62 let (pos, before) = extract_offset(before);
63 let parse = SourceFile::parse(&before, span::Edition::CURRENT);
64 let new_pos = match matching_brace(&parse.tree(), pos) {
65 None => pos,
66 Some(pos) => pos,
67 };
68 let actual = add_cursor(&before, new_pos);
69 assert_eq_text!(after, &actual);
70 }
71
72 do_check("struct Foo { a: i32, }$0", "struct Foo $0{ a: i32, }");
73 do_check("fn main() { |x: i32|$0 x * 2;}", "fn main() { $0|x: i32| x * 2;}");
74 do_check("fn main() { $0|x: i32| x * 2;}", "fn main() { |x: i32$0| x * 2;}");
75 do_check(
76 "fn func(x) { return (2 * (x + 3)$0) + 5;}",
77 "fn func(x) { return $0(2 * (x + 3)) + 5;}",
78 );
79 do_check(
80 "fn func(x) { return (2 * (x $0+ 3)) + 5;}",
81 "fn func(x) { return (2 * (x + 3$0)) + 5;}",
82 );
83 do_check(
84 "fn func(x) { re$0turn (2 * (x + 3)) + 5;}",
85 "fn func(x) { return (2 * (x + 3)) + 5;$0}",
86 );
87
88 {
89 cov_mark::check!(pipes_not_braces);
90 do_check(
91 "fn main() { match 92 { 1 | 2 |$0 3 => 92 } }",
92 "fn main() { match 92 { 1 | 2 |$0 3 => 92 } }",
93 );
94 }
95 }
96}