diff options
Diffstat (limited to 'crates/ra_ide/src/matching_brace.rs')
-rw-r--r-- | crates/ra_ide/src/matching_brace.rs | 43 |
1 files changed, 43 insertions, 0 deletions
diff --git a/crates/ra_ide/src/matching_brace.rs b/crates/ra_ide/src/matching_brace.rs new file mode 100644 index 000000000..d1204fac0 --- /dev/null +++ b/crates/ra_ide/src/matching_brace.rs | |||
@@ -0,0 +1,43 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use ra_syntax::{ast::AstNode, SourceFile, SyntaxKind, TextUnit, T}; | ||
4 | |||
5 | pub fn matching_brace(file: &SourceFile, offset: TextUnit) -> Option<TextUnit> { | ||
6 | const BRACES: &[SyntaxKind] = | ||
7 | &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>]]; | ||
8 | let (brace_node, brace_idx) = file | ||
9 | .syntax() | ||
10 | .token_at_offset(offset) | ||
11 | .filter_map(|node| { | ||
12 | let idx = BRACES.iter().position(|&brace| brace == node.kind())?; | ||
13 | Some((node, idx)) | ||
14 | }) | ||
15 | .next()?; | ||
16 | let parent = brace_node.parent(); | ||
17 | let matching_kind = BRACES[brace_idx ^ 1]; | ||
18 | let matching_node = parent.children_with_tokens().find(|node| node.kind() == matching_kind)?; | ||
19 | Some(matching_node.text_range().start()) | ||
20 | } | ||
21 | |||
22 | #[cfg(test)] | ||
23 | mod tests { | ||
24 | use test_utils::{add_cursor, assert_eq_text, extract_offset}; | ||
25 | |||
26 | use super::*; | ||
27 | |||
28 | #[test] | ||
29 | fn test_matching_brace() { | ||
30 | fn do_check(before: &str, after: &str) { | ||
31 | let (pos, before) = extract_offset(before); | ||
32 | let parse = SourceFile::parse(&before); | ||
33 | let new_pos = match matching_brace(&parse.tree(), pos) { | ||
34 | None => pos, | ||
35 | Some(pos) => pos, | ||
36 | }; | ||
37 | let actual = add_cursor(&before, new_pos); | ||
38 | assert_eq_text!(after, &actual); | ||
39 | } | ||
40 | |||
41 | do_check("struct Foo { a: i32, }<|>", "struct Foo <|>{ a: i32, }"); | ||
42 | } | ||
43 | } | ||