diff options
Diffstat (limited to 'crates/ra_ide_api/src/extend_selection.rs')
-rw-r--r-- | crates/ra_ide_api/src/extend_selection.rs | 144 |
1 files changed, 73 insertions, 71 deletions
diff --git a/crates/ra_ide_api/src/extend_selection.rs b/crates/ra_ide_api/src/extend_selection.rs index 63879a0b5..e743bf0fe 100644 --- a/crates/ra_ide_api/src/extend_selection.rs +++ b/crates/ra_ide_api/src/extend_selection.rs | |||
@@ -1,8 +1,9 @@ | |||
1 | use ra_db::SourceDatabase; | 1 | use ra_db::SourceDatabase; |
2 | use ra_syntax::{ | 2 | use ra_syntax::{ |
3 | Direction, SyntaxNode, TextRange, TextUnit, AstNode, | 3 | Direction, SyntaxNode, TextRange, TextUnit, AstNode, SyntaxElement, |
4 | algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset}, | 4 | algo::{find_covering_element, find_token_at_offset, TokenAtOffset}, |
5 | SyntaxKind::*, | 5 | SyntaxKind::*, SyntaxToken, |
6 | ast::Comment, | ||
6 | }; | 7 | }; |
7 | 8 | ||
8 | use crate::{FileRange, db::RootDatabase}; | 9 | use crate::{FileRange, db::RootDatabase}; |
@@ -32,53 +33,58 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange | |||
32 | 33 | ||
33 | if range.is_empty() { | 34 | if range.is_empty() { |
34 | let offset = range.start(); | 35 | let offset = range.start(); |
35 | let mut leaves = find_leaf_at_offset(root, offset); | 36 | let mut leaves = find_token_at_offset(root, offset); |
36 | if leaves.clone().all(|it| it.kind() == WHITESPACE) { | 37 | if leaves.clone().all(|it| it.kind() == WHITESPACE) { |
37 | return Some(extend_ws(root, leaves.next()?, offset)); | 38 | return Some(extend_ws(root, leaves.next()?, offset)); |
38 | } | 39 | } |
39 | let leaf_range = match leaves { | 40 | let leaf_range = match leaves { |
40 | LeafAtOffset::None => return None, | 41 | TokenAtOffset::None => return None, |
41 | LeafAtOffset::Single(l) => { | 42 | TokenAtOffset::Single(l) => { |
42 | if string_kinds.contains(&l.kind()) { | 43 | if string_kinds.contains(&l.kind()) { |
43 | extend_single_word_in_comment_or_string(l, offset).unwrap_or_else(|| l.range()) | 44 | extend_single_word_in_comment_or_string(l, offset).unwrap_or_else(|| l.range()) |
44 | } else { | 45 | } else { |
45 | l.range() | 46 | l.range() |
46 | } | 47 | } |
47 | } | 48 | } |
48 | LeafAtOffset::Between(l, r) => pick_best(l, r).range(), | 49 | TokenAtOffset::Between(l, r) => pick_best(l, r).range(), |
49 | }; | 50 | }; |
50 | return Some(leaf_range); | 51 | return Some(leaf_range); |
51 | }; | 52 | }; |
52 | let node = find_covering_node(root, range); | 53 | let node = match find_covering_element(root, range) { |
54 | SyntaxElement::Token(token) => { | ||
55 | if token.range() != range { | ||
56 | return Some(token.range()); | ||
57 | } | ||
58 | if let Some(comment) = Comment::cast(token) { | ||
59 | if let Some(range) = extend_comments(comment) { | ||
60 | return Some(range); | ||
61 | } | ||
62 | } | ||
63 | token.parent() | ||
64 | } | ||
65 | SyntaxElement::Node(node) => node, | ||
66 | }; | ||
67 | if node.range() != range { | ||
68 | return Some(node.range()); | ||
69 | } | ||
53 | 70 | ||
54 | // Using shallowest node with same range allows us to traverse siblings. | 71 | // Using shallowest node with same range allows us to traverse siblings. |
55 | let node = node.ancestors().take_while(|n| n.range() == node.range()).last().unwrap(); | 72 | let node = node.ancestors().take_while(|n| n.range() == node.range()).last().unwrap(); |
56 | 73 | ||
57 | if range == node.range() { | 74 | if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) { |
58 | if string_kinds.contains(&node.kind()) { | 75 | if let Some(range) = extend_list_item(node) { |
59 | if let Some(range) = extend_comments(node) { | 76 | return Some(range); |
60 | return Some(range); | ||
61 | } | ||
62 | } | ||
63 | |||
64 | if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) { | ||
65 | if let Some(range) = extend_list_item(node) { | ||
66 | return Some(range); | ||
67 | } | ||
68 | } | 77 | } |
69 | } | 78 | } |
70 | 79 | ||
71 | match node.ancestors().skip_while(|n| n.range() == range).next() { | 80 | node.parent().map(|it| it.range()) |
72 | None => None, | ||
73 | Some(parent) => Some(parent.range()), | ||
74 | } | ||
75 | } | 81 | } |
76 | 82 | ||
77 | fn extend_single_word_in_comment_or_string( | 83 | fn extend_single_word_in_comment_or_string( |
78 | leaf: &SyntaxNode, | 84 | leaf: SyntaxToken, |
79 | offset: TextUnit, | 85 | offset: TextUnit, |
80 | ) -> Option<TextRange> { | 86 | ) -> Option<TextRange> { |
81 | let text: &str = leaf.leaf_text()?; | 87 | let text: &str = leaf.text(); |
82 | let cursor_position: u32 = (offset - leaf.range().start()).into(); | 88 | let cursor_position: u32 = (offset - leaf.range().start()).into(); |
83 | 89 | ||
84 | let (before, after) = text.split_at(cursor_position as usize); | 90 | let (before, after) = text.split_at(cursor_position as usize); |
@@ -101,14 +107,14 @@ fn extend_single_word_in_comment_or_string( | |||
101 | } | 107 | } |
102 | } | 108 | } |
103 | 109 | ||
104 | fn extend_ws(root: &SyntaxNode, ws: &SyntaxNode, offset: TextUnit) -> TextRange { | 110 | fn extend_ws(root: &SyntaxNode, ws: SyntaxToken, offset: TextUnit) -> TextRange { |
105 | let ws_text = ws.leaf_text().unwrap(); | 111 | let ws_text = ws.text(); |
106 | let suffix = TextRange::from_to(offset, ws.range().end()) - ws.range().start(); | 112 | let suffix = TextRange::from_to(offset, ws.range().end()) - ws.range().start(); |
107 | let prefix = TextRange::from_to(ws.range().start(), offset) - ws.range().start(); | 113 | let prefix = TextRange::from_to(ws.range().start(), offset) - ws.range().start(); |
108 | let ws_suffix = &ws_text.as_str()[suffix]; | 114 | let ws_suffix = &ws_text.as_str()[suffix]; |
109 | let ws_prefix = &ws_text.as_str()[prefix]; | 115 | let ws_prefix = &ws_text.as_str()[prefix]; |
110 | if ws_text.contains('\n') && !ws_suffix.contains('\n') { | 116 | if ws_text.contains('\n') && !ws_suffix.contains('\n') { |
111 | if let Some(node) = ws.next_sibling() { | 117 | if let Some(node) = ws.next_sibling_or_token() { |
112 | let start = match ws_prefix.rfind('\n') { | 118 | let start = match ws_prefix.rfind('\n') { |
113 | Some(idx) => ws.range().start() + TextUnit::from((idx + 1) as u32), | 119 | Some(idx) => ws.range().start() + TextUnit::from((idx + 1) as u32), |
114 | None => node.range().start(), | 120 | None => node.range().start(), |
@@ -124,9 +130,9 @@ fn extend_ws(root: &SyntaxNode, ws: &SyntaxNode, offset: TextUnit) -> TextRange | |||
124 | ws.range() | 130 | ws.range() |
125 | } | 131 | } |
126 | 132 | ||
127 | fn pick_best<'a>(l: &'a SyntaxNode, r: &'a SyntaxNode) -> &'a SyntaxNode { | 133 | fn pick_best<'a>(l: SyntaxToken<'a>, r: SyntaxToken<'a>) -> SyntaxToken<'a> { |
128 | return if priority(r) > priority(l) { r } else { l }; | 134 | return if priority(r) > priority(l) { r } else { l }; |
129 | fn priority(n: &SyntaxNode) -> usize { | 135 | fn priority(n: SyntaxToken) -> usize { |
130 | match n.kind() { | 136 | match n.kind() { |
131 | WHITESPACE => 0, | 137 | WHITESPACE => 0, |
132 | IDENT | SELF_KW | SUPER_KW | CRATE_KW | LIFETIME => 2, | 138 | IDENT | SELF_KW | SUPER_KW | CRATE_KW | LIFETIME => 2, |
@@ -137,54 +143,60 @@ fn pick_best<'a>(l: &'a SyntaxNode, r: &'a SyntaxNode) -> &'a SyntaxNode { | |||
137 | 143 | ||
138 | /// Extend list item selection to include nearby comma and whitespace. | 144 | /// Extend list item selection to include nearby comma and whitespace. |
139 | fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> { | 145 | fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> { |
140 | fn is_single_line_ws(node: &SyntaxNode) -> bool { | 146 | fn is_single_line_ws(node: &SyntaxToken) -> bool { |
141 | node.kind() == WHITESPACE && !node.leaf_text().unwrap().contains('\n') | 147 | node.kind() == WHITESPACE && !node.text().contains('\n') |
142 | } | 148 | } |
143 | 149 | ||
144 | fn nearby_comma(node: &SyntaxNode, dir: Direction) -> Option<&SyntaxNode> { | 150 | fn nearby_comma(node: &SyntaxNode, dir: Direction) -> Option<SyntaxToken> { |
145 | node.siblings(dir) | 151 | node.siblings_with_tokens(dir) |
146 | .skip(1) | 152 | .skip(1) |
147 | .skip_while(|node| is_single_line_ws(node)) | 153 | .skip_while(|node| match node { |
154 | SyntaxElement::Node(_) => false, | ||
155 | SyntaxElement::Token(it) => is_single_line_ws(it), | ||
156 | }) | ||
148 | .next() | 157 | .next() |
158 | .and_then(|it| it.as_token()) | ||
149 | .filter(|node| node.kind() == COMMA) | 159 | .filter(|node| node.kind() == COMMA) |
150 | } | 160 | } |
151 | 161 | ||
152 | if let Some(comma_node) = nearby_comma(node, Direction::Prev) { | 162 | if let Some(comma_node) = nearby_comma(node, Direction::Prev) { |
153 | return Some(TextRange::from_to(comma_node.range().start(), node.range().end())); | 163 | return Some(TextRange::from_to(comma_node.range().start(), node.range().end())); |
154 | } | 164 | } |
155 | |||
156 | if let Some(comma_node) = nearby_comma(node, Direction::Next) { | 165 | if let Some(comma_node) = nearby_comma(node, Direction::Next) { |
157 | // Include any following whitespace when comma if after list item. | 166 | // Include any following whitespace when comma if after list item. |
158 | let final_node = comma_node | 167 | let final_node = comma_node |
159 | .siblings(Direction::Next) | 168 | .next_sibling_or_token() |
160 | .skip(1) | 169 | .and_then(|it| it.as_token()) |
161 | .next() | ||
162 | .filter(|node| is_single_line_ws(node)) | 170 | .filter(|node| is_single_line_ws(node)) |
163 | .unwrap_or(comma_node); | 171 | .unwrap_or(comma_node); |
164 | 172 | ||
165 | return Some(TextRange::from_to(node.range().start(), final_node.range().end())); | 173 | return Some(TextRange::from_to(node.range().start(), final_node.range().end())); |
166 | } | 174 | } |
167 | 175 | ||
168 | return None; | 176 | None |
169 | } | 177 | } |
170 | 178 | ||
171 | fn extend_comments(node: &SyntaxNode) -> Option<TextRange> { | 179 | fn extend_comments(comment: Comment) -> Option<TextRange> { |
172 | let prev = adj_comments(node, Direction::Prev); | 180 | let prev = adj_comments(comment, Direction::Prev); |
173 | let next = adj_comments(node, Direction::Next); | 181 | let next = adj_comments(comment, Direction::Next); |
174 | if prev != next { | 182 | if prev != next { |
175 | Some(TextRange::from_to(prev.range().start(), next.range().end())) | 183 | Some(TextRange::from_to(prev.syntax().range().start(), next.syntax().range().end())) |
176 | } else { | 184 | } else { |
177 | None | 185 | None |
178 | } | 186 | } |
179 | } | 187 | } |
180 | 188 | ||
181 | fn adj_comments(node: &SyntaxNode, dir: Direction) -> &SyntaxNode { | 189 | fn adj_comments(comment: Comment, dir: Direction) -> Comment { |
182 | let mut res = node; | 190 | let mut res = comment; |
183 | for node in node.siblings(dir) { | 191 | for element in comment.syntax().siblings_with_tokens(dir) { |
184 | match node.kind() { | 192 | let token = match element.as_token() { |
185 | COMMENT => res = node, | 193 | None => break, |
186 | WHITESPACE if !node.leaf_text().unwrap().as_str().contains("\n\n") => (), | 194 | Some(token) => token, |
187 | _ => break, | 195 | }; |
196 | if let Some(c) = Comment::cast(token) { | ||
197 | res = c | ||
198 | } else if token.kind() != WHITESPACE || token.text().contains("\n\n") { | ||
199 | break; | ||
188 | } | 200 | } |
189 | } | 201 | } |
190 | res | 202 | res |
@@ -308,23 +320,13 @@ fn bar(){} | |||
308 | /* | 320 | /* |
309 | foo | 321 | foo |
310 | _bar1<|>*/ | 322 | _bar1<|>*/ |
311 | "#, | 323 | "#, |
312 | &["_bar1", "/*\nfoo\n_bar1*/"], | 324 | &["_bar1", "/*\nfoo\n_bar1*/"], |
313 | ); | 325 | ); |
314 | 326 | ||
315 | do_check( | 327 | do_check(r#"//!<|>foo_2 bar"#, &["foo_2", "//!foo_2 bar"]); |
316 | r#" | ||
317 | //!<|>foo_2 bar | ||
318 | "#, | ||
319 | &["foo_2", "//!foo_2 bar"], | ||
320 | ); | ||
321 | 328 | ||
322 | do_check( | 329 | do_check(r#"/<|>/foo bar"#, &["//foo bar"]); |
323 | r#" | ||
324 | /<|>/foo bar | ||
325 | "#, | ||
326 | &["//foo bar"], | ||
327 | ); | ||
328 | } | 330 | } |
329 | 331 | ||
330 | #[test] | 332 | #[test] |
@@ -332,13 +334,13 @@ _bar1<|>*/ | |||
332 | do_check( | 334 | do_check( |
333 | r#" | 335 | r#" |
334 | fn main() { foo<|>+bar;} | 336 | fn main() { foo<|>+bar;} |
335 | "#, | 337 | "#, |
336 | &["foo", "foo+bar"], | 338 | &["foo", "foo+bar"], |
337 | ); | 339 | ); |
338 | do_check( | 340 | do_check( |
339 | r#" | 341 | r#" |
340 | fn main() { foo+<|>bar;} | 342 | fn main() { foo+<|>bar;} |
341 | "#, | 343 | "#, |
342 | &["bar", "foo+bar"], | 344 | &["bar", "foo+bar"], |
343 | ); | 345 | ); |
344 | } | 346 | } |
@@ -355,11 +357,11 @@ fn main() { foo+<|>bar;} | |||
355 | do_check( | 357 | do_check( |
356 | r#" | 358 | r#" |
357 | impl S { | 359 | impl S { |
358 | fn foo() { | 360 | fn foo() { |
359 | // hel<|>lo world | 361 | // hel<|>lo world |
360 | } | ||
361 | } | 362 | } |
362 | "#, | 363 | } |
364 | "#, | ||
363 | &["hello", "// hello world"], | 365 | &["hello", "// hello world"], |
364 | ); | 366 | ); |
365 | } | 367 | } |
@@ -371,7 +373,7 @@ impl S { | |||
371 | fn bar(){} | 373 | fn bar(){} |
372 | 374 | ||
373 | " fn f<|>oo() {" | 375 | " fn f<|>oo() {" |
374 | "#, | 376 | "#, |
375 | &["foo", "\" fn foo() {\""], | 377 | &["foo", "\" fn foo() {\""], |
376 | ); | 378 | ); |
377 | } | 379 | } |