diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-01-10 12:20:00 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-01-10 12:20:00 +0000 |
commit | 9225033cdf16fd9f3f38b3d3482efb0c36698085 (patch) | |
tree | b804cf396e2e6b6d4da6082096f933b445671579 /crates/ra_ide_api_light | |
parent | c2b8aa1ce5e5398d981387079e86ff67a5b7e8c0 (diff) | |
parent | 8384b2cc38acc6e8c1741e8d2d6a66f74b7f02b3 (diff) |
Merge #477
477: Extend selection for list items r=matklad a=hban
First PR, criticism welcome!
There are a few things I'm not so sure about:
* There is now a not-so-small list of "list-like kinds" in extend selection source which maybe should belong somewhere else.
* Preferring left comma doesn't seem right IMO for one reason - trailing commas are usually on the right. For example, when array values are broken across multiple lines extending selected value will cover right trailing comma (because it's the only comma on the same line), but when all values are on the same line it will pick left comma. Anyway, currently with this PR it will pick always extend to left comma when possible since that's what issue specified 😃.
Closes: #444
Co-authored-by: Hrvoje Ban <[email protected]>
Diffstat (limited to 'crates/ra_ide_api_light')
-rw-r--r-- | crates/ra_ide_api_light/src/extend_selection.rs | 131 |
1 files changed, 127 insertions, 4 deletions
diff --git a/crates/ra_ide_api_light/src/extend_selection.rs b/crates/ra_ide_api_light/src/extend_selection.rs index 08cae5a51..db93db208 100644 --- a/crates/ra_ide_api_light/src/extend_selection.rs +++ b/crates/ra_ide_api_light/src/extend_selection.rs | |||
@@ -6,6 +6,21 @@ use ra_syntax::{ | |||
6 | 6 | ||
7 | pub fn extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange> { | 7 | pub fn extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange> { |
8 | let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING]; | 8 | let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING]; |
9 | let list_kinds = [ | ||
10 | FIELD_PAT_LIST, | ||
11 | MATCH_ARM_LIST, | ||
12 | NAMED_FIELD_LIST, | ||
13 | NAMED_FIELD_DEF_LIST, | ||
14 | POS_FIELD_LIST, | ||
15 | ENUM_VARIANT_LIST, | ||
16 | USE_TREE_LIST, | ||
17 | TYPE_PARAM_LIST, | ||
18 | TYPE_ARG_LIST, | ||
19 | PARAM_LIST, | ||
20 | ARG_LIST, | ||
21 | ARRAY_EXPR, | ||
22 | ]; | ||
23 | |||
9 | if range.is_empty() { | 24 | if range.is_empty() { |
10 | let offset = range.start(); | 25 | let offset = range.start(); |
11 | let mut leaves = find_leaf_at_offset(root, offset); | 26 | let mut leaves = find_leaf_at_offset(root, offset); |
@@ -26,9 +41,25 @@ pub fn extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange | |||
26 | return Some(leaf_range); | 41 | return Some(leaf_range); |
27 | }; | 42 | }; |
28 | let node = find_covering_node(root, range); | 43 | let node = find_covering_node(root, range); |
29 | if string_kinds.contains(&node.kind()) && range == node.range() { | 44 | |
30 | if let Some(range) = extend_comments(node) { | 45 | // Using shallowest node with same range allows us to traverse siblings. |
31 | return Some(range); | 46 | let node = node |
47 | .ancestors() | ||
48 | .take_while(|n| n.range() == node.range()) | ||
49 | .last() | ||
50 | .unwrap(); | ||
51 | |||
52 | if range == node.range() { | ||
53 | if string_kinds.contains(&node.kind()) { | ||
54 | if let Some(range) = extend_comments(node) { | ||
55 | return Some(range); | ||
56 | } | ||
57 | } | ||
58 | |||
59 | if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) { | ||
60 | if let Some(range) = extend_list_item(node) { | ||
61 | return Some(range); | ||
62 | } | ||
32 | } | 63 | } |
33 | } | 64 | } |
34 | 65 | ||
@@ -99,6 +130,45 @@ fn pick_best<'a>(l: &'a SyntaxNode, r: &'a SyntaxNode) -> &'a SyntaxNode { | |||
99 | } | 130 | } |
100 | } | 131 | } |
101 | 132 | ||
133 | /// Extend list item selection to include nearby comma and whitespace. | ||
134 | fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> { | ||
135 | fn is_single_line_ws(node: &SyntaxNode) -> bool { | ||
136 | node.kind() == WHITESPACE && !node.leaf_text().unwrap().contains('\n') | ||
137 | } | ||
138 | |||
139 | fn nearby_comma(node: &SyntaxNode, dir: Direction) -> Option<&SyntaxNode> { | ||
140 | node.siblings(dir) | ||
141 | .skip(1) | ||
142 | .skip_while(|node| is_single_line_ws(node)) | ||
143 | .next() | ||
144 | .filter(|node| node.kind() == COMMA) | ||
145 | } | ||
146 | |||
147 | if let Some(comma_node) = nearby_comma(node, Direction::Prev) { | ||
148 | return Some(TextRange::from_to( | ||
149 | comma_node.range().start(), | ||
150 | node.range().end(), | ||
151 | )); | ||
152 | } | ||
153 | |||
154 | if let Some(comma_node) = nearby_comma(node, Direction::Next) { | ||
155 | // Include any following whitespace when comma if after list item. | ||
156 | let final_node = comma_node | ||
157 | .siblings(Direction::Next) | ||
158 | .skip(1) | ||
159 | .next() | ||
160 | .filter(|node| is_single_line_ws(node)) | ||
161 | .unwrap_or(comma_node); | ||
162 | |||
163 | return Some(TextRange::from_to( | ||
164 | node.range().start(), | ||
165 | final_node.range().end(), | ||
166 | )); | ||
167 | } | ||
168 | |||
169 | return None; | ||
170 | } | ||
171 | |||
102 | fn extend_comments(node: &SyntaxNode) -> Option<TextRange> { | 172 | fn extend_comments(node: &SyntaxNode) -> Option<TextRange> { |
103 | let prev = adj_comments(node, Direction::Prev); | 173 | let prev = adj_comments(node, Direction::Prev); |
104 | let next = adj_comments(node, Direction::Next); | 174 | let next = adj_comments(node, Direction::Next); |
@@ -145,7 +215,60 @@ mod tests { | |||
145 | } | 215 | } |
146 | 216 | ||
147 | #[test] | 217 | #[test] |
148 | fn test_extend_selection_start_of_the_lind() { | 218 | fn test_extend_selection_list() { |
219 | do_check(r#"fn foo(<|>x: i32) {}"#, &["x", "x: i32"]); | ||
220 | do_check( | ||
221 | r#"fn foo(<|>x: i32, y: i32) {}"#, | ||
222 | &["x", "x: i32", "x: i32, "], | ||
223 | ); | ||
224 | do_check( | ||
225 | r#"fn foo(<|>x: i32,y: i32) {}"#, | ||
226 | &["x", "x: i32", "x: i32,"], | ||
227 | ); | ||
228 | do_check( | ||
229 | r#"fn foo(x: i32, <|>y: i32) {}"#, | ||
230 | &["y", "y: i32", ", y: i32"], | ||
231 | ); | ||
232 | do_check( | ||
233 | r#"fn foo(x: i32, <|>y: i32, ) {}"#, | ||
234 | &["y", "y: i32", ", y: i32"], | ||
235 | ); | ||
236 | do_check( | ||
237 | r#"fn foo(x: i32,<|>y: i32) {}"#, | ||
238 | &["y", "y: i32", ",y: i32"], | ||
239 | ); | ||
240 | |||
241 | do_check( | ||
242 | r#"const FOO: [usize; 2] = [ 22<|> , 33];"#, | ||
243 | &["22", "22 , "], | ||
244 | ); | ||
245 | do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|>];"#, &["33", ", 33"]); | ||
246 | do_check( | ||
247 | r#"const FOO: [usize; 2] = [ 22 , 33<|> ,];"#, | ||
248 | &["33", ", 33"], | ||
249 | ); | ||
250 | |||
251 | do_check( | ||
252 | r#" | ||
253 | const FOO: [usize; 2] = [ | ||
254 | 22, | ||
255 | <|>33, | ||
256 | ]"#, | ||
257 | &["33", "33,"], | ||
258 | ); | ||
259 | |||
260 | do_check( | ||
261 | r#" | ||
262 | const FOO: [usize; 2] = [ | ||
263 | 22 | ||
264 | , 33<|>, | ||
265 | ]"#, | ||
266 | &["33", ", 33"], | ||
267 | ); | ||
268 | } | ||
269 | |||
270 | #[test] | ||
271 | fn test_extend_selection_start_of_the_line() { | ||
149 | do_check( | 272 | do_check( |
150 | r#" | 273 | r#" |
151 | impl S { | 274 | impl S { |