diff options
Diffstat (limited to 'crates/libeditor')
-rw-r--r-- | crates/libeditor/src/lib.rs | 26 | ||||
-rw-r--r-- | crates/libeditor/tests/test.rs | 43 |
2 files changed, 58 insertions, 11 deletions
diff --git a/crates/libeditor/src/lib.rs b/crates/libeditor/src/lib.rs index 9e44f5d92..28da457d1 100644 --- a/crates/libeditor/src/lib.rs +++ b/crates/libeditor/src/lib.rs | |||
@@ -12,8 +12,8 @@ mod code_actions; | |||
12 | use libsyntax2::{ | 12 | use libsyntax2::{ |
13 | ast::{self, NameOwner}, | 13 | ast::{self, NameOwner}, |
14 | AstNode, | 14 | AstNode, |
15 | algo::walk, | 15 | algo::{walk, find_leaf_at_offset}, |
16 | SyntaxKind::*, | 16 | SyntaxKind::{self, *}, |
17 | }; | 17 | }; |
18 | pub use libsyntax2::{File, TextRange, TextUnit}; | 18 | pub use libsyntax2::{File, TextRange, TextUnit}; |
19 | pub use self::{ | 19 | pub use self::{ |
@@ -52,6 +52,28 @@ pub fn parse(text: &str) -> ast::File { | |||
52 | ast::File::parse(text) | 52 | ast::File::parse(text) |
53 | } | 53 | } |
54 | 54 | ||
55 | pub fn matching_brace(file: &ast::File, offset: TextUnit) -> Option<TextUnit> { | ||
56 | const BRACES: &[SyntaxKind] = &[ | ||
57 | L_CURLY, R_CURLY, | ||
58 | L_BRACK, R_BRACK, | ||
59 | L_PAREN, R_PAREN, | ||
60 | L_ANGLE, R_ANGLE, | ||
61 | ]; | ||
62 | let syntax = file.syntax(); | ||
63 | let syntax = syntax.as_ref(); | ||
64 | let (brace_node, brace_idx) = find_leaf_at_offset(syntax, offset) | ||
65 | .filter_map(|node| { | ||
66 | let idx = BRACES.iter().position(|&brace| brace == node.kind())?; | ||
67 | Some((node, idx)) | ||
68 | }) | ||
69 | .next()?; | ||
70 | let parent = brace_node.parent()?; | ||
71 | let matching_kind = BRACES[brace_idx ^ 1]; | ||
72 | let matching_node = parent.children() | ||
73 | .find(|node| node.kind() == matching_kind)?; | ||
74 | Some(matching_node.range().start()) | ||
75 | } | ||
76 | |||
55 | pub fn highlight(file: &ast::File) -> Vec<HighlightedRange> { | 77 | pub fn highlight(file: &ast::File) -> Vec<HighlightedRange> { |
56 | let syntax = file.syntax(); | 78 | let syntax = file.syntax(); |
57 | let mut res = Vec::new(); | 79 | let mut res = Vec::new(); |
diff --git a/crates/libeditor/tests/test.rs b/crates/libeditor/tests/test.rs index 7063425ce..d5df9d0cc 100644 --- a/crates/libeditor/tests/test.rs +++ b/crates/libeditor/tests/test.rs | |||
@@ -9,7 +9,7 @@ use itertools::Itertools; | |||
9 | use libeditor::{ | 9 | use libeditor::{ |
10 | File, TextUnit, TextRange, ActionResult, CursorPosition, | 10 | File, TextUnit, TextRange, ActionResult, CursorPosition, |
11 | highlight, runnables, extend_selection, file_structure, | 11 | highlight, runnables, extend_selection, file_structure, |
12 | flip_comma, add_derive, | 12 | flip_comma, add_derive, matching_brace, |
13 | }; | 13 | }; |
14 | 14 | ||
15 | #[test] | 15 | #[test] |
@@ -119,6 +119,25 @@ fn test_add_derive() { | |||
119 | ) | 119 | ) |
120 | } | 120 | } |
121 | 121 | ||
122 | #[test] | ||
123 | fn test_matching_brace() { | ||
124 | fn do_check(before: &str, after: &str) { | ||
125 | let (pos, before) = extract_cursor(before); | ||
126 | let file = file(&before); | ||
127 | let new_pos = match matching_brace(&file, pos) { | ||
128 | None => pos, | ||
129 | Some(pos) => pos, | ||
130 | }; | ||
131 | let actual = add_cursor(&before, new_pos); | ||
132 | assert_eq_text!(after, &actual); | ||
133 | } | ||
134 | |||
135 | do_check( | ||
136 | "struct Foo { a: i32, }<|>", | ||
137 | "struct Foo <|>{ a: i32, }", | ||
138 | ); | ||
139 | } | ||
140 | |||
122 | fn file(text: &str) -> File { | 141 | fn file(text: &str) -> File { |
123 | File::parse(text) | 142 | File::parse(text) |
124 | } | 143 | } |
@@ -138,16 +157,12 @@ fn check_action<F: Fn(&File, TextUnit) -> Option<ActionResult>>( | |||
138 | let file = file(&before); | 157 | let file = file(&before); |
139 | let result = f(&file, before_cursor_pos).expect("code action is not applicable"); | 158 | let result = f(&file, before_cursor_pos).expect("code action is not applicable"); |
140 | let actual = result.edit.apply(&before); | 159 | let actual = result.edit.apply(&before); |
141 | let actual_cursor_pos: u32 = match result.cursor_position { | 160 | let actual_cursor_pos = match result.cursor_position { |
142 | CursorPosition::Same => result.edit.apply_to_offset(before_cursor_pos).unwrap(), | 161 | CursorPosition::Same => result.edit.apply_to_offset(before_cursor_pos).unwrap(), |
143 | CursorPosition::Offset(off) => off, | 162 | CursorPosition::Offset(off) => off, |
144 | }.into(); | 163 | }; |
145 | let actual_cursor_pos = actual_cursor_pos as usize; | 164 | let actual = add_cursor(&actual, actual_cursor_pos); |
146 | let mut actual_with_cursor = String::new(); | 165 | assert_eq_text!(after, &actual); |
147 | actual_with_cursor.push_str(&actual[..actual_cursor_pos]); | ||
148 | actual_with_cursor.push_str("<|>"); | ||
149 | actual_with_cursor.push_str(&actual[actual_cursor_pos..]); | ||
150 | assert_eq_text!(after, &actual_with_cursor); | ||
151 | } | 166 | } |
152 | 167 | ||
153 | fn extract_cursor(text: &str) -> (TextUnit, String) { | 168 | fn extract_cursor(text: &str) -> (TextUnit, String) { |
@@ -162,3 +177,13 @@ fn extract_cursor(text: &str) -> (TextUnit, String) { | |||
162 | let cursor_pos = TextUnit::from(cursor_pos as u32); | 177 | let cursor_pos = TextUnit::from(cursor_pos as u32); |
163 | (cursor_pos, new_text) | 178 | (cursor_pos, new_text) |
164 | } | 179 | } |
180 | |||
181 | fn add_cursor(text: &str, offset: TextUnit) -> String { | ||
182 | let offset: u32 = offset.into(); | ||
183 | let offset: usize = offset as usize; | ||
184 | let mut res = String::new(); | ||
185 | res.push_str(&text[..offset]); | ||
186 | res.push_str("<|>"); | ||
187 | res.push_str(&text[offset..]); | ||
188 | res | ||
189 | } | ||