aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api/src/extend_selection.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api/src/extend_selection.rs')
-rw-r--r--crates/ra_ide_api/src/extend_selection.rs144
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..7293ba359 100644
--- a/crates/ra_ide_api/src/extend_selection.rs
+++ b/crates/ra_ide_api/src/extend_selection.rs
@@ -1,8 +1,9 @@
1use ra_db::SourceDatabase; 1use ra_db::SourceDatabase;
2use ra_syntax::{ 2use ra_syntax::{
3 Direction, SyntaxNode, TextRange, TextUnit, AstNode, 3 Direction, SyntaxNode, TextRange, TextUnit, 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::{self, AstNode, AstToken},
6}; 7};
7 8
8use crate::{FileRange, db::RootDatabase}; 9use 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) = ast::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
77fn extend_single_word_in_comment_or_string( 83fn 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
104fn extend_ws(root: &SyntaxNode, ws: &SyntaxNode, offset: TextUnit) -> TextRange { 110fn 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
127fn pick_best<'a>(l: &'a SyntaxNode, r: &'a SyntaxNode) -> &'a SyntaxNode { 133fn 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.
139fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> { 145fn 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
171fn extend_comments(node: &SyntaxNode) -> Option<TextRange> { 179fn extend_comments(comment: ast::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
181fn adj_comments(node: &SyntaxNode, dir: Direction) -> &SyntaxNode { 189fn adj_comments(comment: ast::Comment, dir: Direction) -> ast::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) = ast::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/*
309foo 321foo
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#"
334fn main() { foo<|>+bar;} 336fn 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#"
340fn main() { foo+<|>bar;} 342fn 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#"
357impl S { 359impl S {
358 fn foo() { 360fn 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 {
371fn bar(){} 373fn 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 }