aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/extend_selection.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/extend_selection.rs')
-rw-r--r--crates/ide/src/extend_selection.rs654
1 files changed, 654 insertions, 0 deletions
diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs
new file mode 100644
index 000000000..34563a026
--- /dev/null
+++ b/crates/ide/src/extend_selection.rs
@@ -0,0 +1,654 @@
1use std::iter::successors;
2
3use hir::Semantics;
4use ide_db::RootDatabase;
5use syntax::{
6 algo::{self, find_covering_element, skip_trivia_token},
7 ast::{self, AstNode, AstToken},
8 Direction, NodeOrToken,
9 SyntaxKind::{self, *},
10 SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset, T,
11};
12
13use crate::FileRange;
14
15// Feature: Extend Selection
16//
17// Extends the current selection to the encompassing syntactic construct
18// (expression, statement, item, module, etc). It works with multiple cursors.
19//
20// |===
21// | Editor | Shortcut
22//
23// | VS Code | kbd:[Ctrl+Shift+→]
24// |===
25pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
26 let sema = Semantics::new(db);
27 let src = sema.parse(frange.file_id);
28 try_extend_selection(&sema, src.syntax(), frange).unwrap_or(frange.range)
29}
30
31fn try_extend_selection(
32 sema: &Semantics<RootDatabase>,
33 root: &SyntaxNode,
34 frange: FileRange,
35) -> Option<TextRange> {
36 let range = frange.range;
37
38 let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING];
39 let list_kinds = [
40 RECORD_PAT_FIELD_LIST,
41 MATCH_ARM_LIST,
42 RECORD_FIELD_LIST,
43 TUPLE_FIELD_LIST,
44 RECORD_EXPR_FIELD_LIST,
45 VARIANT_LIST,
46 USE_TREE_LIST,
47 GENERIC_PARAM_LIST,
48 GENERIC_ARG_LIST,
49 TYPE_BOUND_LIST,
50 PARAM_LIST,
51 ARG_LIST,
52 ARRAY_EXPR,
53 TUPLE_EXPR,
54 TUPLE_TYPE,
55 TUPLE_PAT,
56 WHERE_CLAUSE,
57 ];
58
59 if range.is_empty() {
60 let offset = range.start();
61 let mut leaves = root.token_at_offset(offset);
62 if leaves.clone().all(|it| it.kind() == WHITESPACE) {
63 return Some(extend_ws(root, leaves.next()?, offset));
64 }
65 let leaf_range = match leaves {
66 TokenAtOffset::None => return None,
67 TokenAtOffset::Single(l) => {
68 if string_kinds.contains(&l.kind()) {
69 extend_single_word_in_comment_or_string(&l, offset)
70 .unwrap_or_else(|| l.text_range())
71 } else {
72 l.text_range()
73 }
74 }
75 TokenAtOffset::Between(l, r) => pick_best(l, r).text_range(),
76 };
77 return Some(leaf_range);
78 };
79 let node = match find_covering_element(root, range) {
80 NodeOrToken::Token(token) => {
81 if token.text_range() != range {
82 return Some(token.text_range());
83 }
84 if let Some(comment) = ast::Comment::cast(token.clone()) {
85 if let Some(range) = extend_comments(comment) {
86 return Some(range);
87 }
88 }
89 token.parent()
90 }
91 NodeOrToken::Node(node) => node,
92 };
93
94 // if we are in single token_tree, we maybe live in macro or attr
95 if node.kind() == TOKEN_TREE {
96 if let Some(macro_call) = node.ancestors().find_map(ast::MacroCall::cast) {
97 if let Some(range) = extend_tokens_from_range(sema, macro_call, range) {
98 return Some(range);
99 }
100 }
101 }
102
103 if node.text_range() != range {
104 return Some(node.text_range());
105 }
106
107 let node = shallowest_node(&node);
108
109 if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) {
110 if let Some(range) = extend_list_item(&node) {
111 return Some(range);
112 }
113 }
114
115 node.parent().map(|it| it.text_range())
116}
117
118fn extend_tokens_from_range(
119 sema: &Semantics<RootDatabase>,
120 macro_call: ast::MacroCall,
121 original_range: TextRange,
122) -> Option<TextRange> {
123 let src = find_covering_element(&macro_call.syntax(), original_range);
124 let (first_token, last_token) = match src {
125 NodeOrToken::Node(it) => (it.first_token()?, it.last_token()?),
126 NodeOrToken::Token(it) => (it.clone(), it),
127 };
128
129 let mut first_token = skip_trivia_token(first_token, Direction::Next)?;
130 let mut last_token = skip_trivia_token(last_token, Direction::Prev)?;
131
132 while !original_range.contains_range(first_token.text_range()) {
133 first_token = skip_trivia_token(first_token.next_token()?, Direction::Next)?;
134 }
135 while !original_range.contains_range(last_token.text_range()) {
136 last_token = skip_trivia_token(last_token.prev_token()?, Direction::Prev)?;
137 }
138
139 // compute original mapped token range
140 let extended = {
141 let fst_expanded = sema.descend_into_macros(first_token.clone());
142 let lst_expanded = sema.descend_into_macros(last_token.clone());
143 let mut lca = algo::least_common_ancestor(&fst_expanded.parent(), &lst_expanded.parent())?;
144 lca = shallowest_node(&lca);
145 if lca.first_token() == Some(fst_expanded) && lca.last_token() == Some(lst_expanded) {
146 lca = lca.parent()?;
147 }
148 lca
149 };
150
151 // Compute parent node range
152 let validate = |token: &SyntaxToken| {
153 let expanded = sema.descend_into_macros(token.clone());
154 algo::least_common_ancestor(&extended, &expanded.parent()).as_ref() == Some(&extended)
155 };
156
157 // Find the first and last text range under expanded parent
158 let first = successors(Some(first_token), |token| {
159 let token = token.prev_token()?;
160 skip_trivia_token(token, Direction::Prev)
161 })
162 .take_while(validate)
163 .last()?;
164
165 let last = successors(Some(last_token), |token| {
166 let token = token.next_token()?;
167 skip_trivia_token(token, Direction::Next)
168 })
169 .take_while(validate)
170 .last()?;
171
172 let range = first.text_range().cover(last.text_range());
173 if range.contains_range(original_range) && original_range != range {
174 Some(range)
175 } else {
176 None
177 }
178}
179
180/// Find the shallowest node with same range, which allows us to traverse siblings.
181fn shallowest_node(node: &SyntaxNode) -> SyntaxNode {
182 node.ancestors().take_while(|n| n.text_range() == node.text_range()).last().unwrap()
183}
184
185fn extend_single_word_in_comment_or_string(
186 leaf: &SyntaxToken,
187 offset: TextSize,
188) -> Option<TextRange> {
189 let text: &str = leaf.text();
190 let cursor_position: u32 = (offset - leaf.text_range().start()).into();
191
192 let (before, after) = text.split_at(cursor_position as usize);
193
194 fn non_word_char(c: char) -> bool {
195 !(c.is_alphanumeric() || c == '_')
196 }
197
198 let start_idx = before.rfind(non_word_char)? as u32;
199 let end_idx = after.find(non_word_char).unwrap_or_else(|| after.len()) as u32;
200
201 let from: TextSize = (start_idx + 1).into();
202 let to: TextSize = (cursor_position + end_idx).into();
203
204 let range = TextRange::new(from, to);
205 if range.is_empty() {
206 None
207 } else {
208 Some(range + leaf.text_range().start())
209 }
210}
211
212fn extend_ws(root: &SyntaxNode, ws: SyntaxToken, offset: TextSize) -> TextRange {
213 let ws_text = ws.text();
214 let suffix = TextRange::new(offset, ws.text_range().end()) - ws.text_range().start();
215 let prefix = TextRange::new(ws.text_range().start(), offset) - ws.text_range().start();
216 let ws_suffix = &ws_text.as_str()[suffix];
217 let ws_prefix = &ws_text.as_str()[prefix];
218 if ws_text.contains('\n') && !ws_suffix.contains('\n') {
219 if let Some(node) = ws.next_sibling_or_token() {
220 let start = match ws_prefix.rfind('\n') {
221 Some(idx) => ws.text_range().start() + TextSize::from((idx + 1) as u32),
222 None => node.text_range().start(),
223 };
224 let end = if root.text().char_at(node.text_range().end()) == Some('\n') {
225 node.text_range().end() + TextSize::of('\n')
226 } else {
227 node.text_range().end()
228 };
229 return TextRange::new(start, end);
230 }
231 }
232 ws.text_range()
233}
234
235fn pick_best(l: SyntaxToken, r: SyntaxToken) -> SyntaxToken {
236 return if priority(&r) > priority(&l) { r } else { l };
237 fn priority(n: &SyntaxToken) -> usize {
238 match n.kind() {
239 WHITESPACE => 0,
240 IDENT | T![self] | T![super] | T![crate] | LIFETIME => 2,
241 _ => 1,
242 }
243 }
244}
245
246/// Extend list item selection to include nearby delimiter and whitespace.
247fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
248 fn is_single_line_ws(node: &SyntaxToken) -> bool {
249 node.kind() == WHITESPACE && !node.text().contains('\n')
250 }
251
252 fn nearby_delimiter(
253 delimiter_kind: SyntaxKind,
254 node: &SyntaxNode,
255 dir: Direction,
256 ) -> Option<SyntaxToken> {
257 node.siblings_with_tokens(dir)
258 .skip(1)
259 .skip_while(|node| match node {
260 NodeOrToken::Node(_) => false,
261 NodeOrToken::Token(it) => is_single_line_ws(it),
262 })
263 .next()
264 .and_then(|it| it.into_token())
265 .filter(|node| node.kind() == delimiter_kind)
266 }
267
268 let delimiter = match node.kind() {
269 TYPE_BOUND => T![+],
270 _ => T![,],
271 };
272
273 if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Next) {
274 // Include any following whitespace when delimiter is after list item.
275 let final_node = delimiter_node
276 .next_sibling_or_token()
277 .and_then(|it| it.into_token())
278 .filter(|node| is_single_line_ws(node))
279 .unwrap_or(delimiter_node);
280
281 return Some(TextRange::new(node.text_range().start(), final_node.text_range().end()));
282 }
283 if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Prev) {
284 return Some(TextRange::new(delimiter_node.text_range().start(), node.text_range().end()));
285 }
286
287 None
288}
289
290fn extend_comments(comment: ast::Comment) -> Option<TextRange> {
291 let prev = adj_comments(&comment, Direction::Prev);
292 let next = adj_comments(&comment, Direction::Next);
293 if prev != next {
294 Some(TextRange::new(prev.syntax().text_range().start(), next.syntax().text_range().end()))
295 } else {
296 None
297 }
298}
299
300fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment {
301 let mut res = comment.clone();
302 for element in comment.syntax().siblings_with_tokens(dir) {
303 let token = match element.as_token() {
304 None => break,
305 Some(token) => token,
306 };
307 if let Some(c) = ast::Comment::cast(token.clone()) {
308 res = c
309 } else if token.kind() != WHITESPACE || token.text().contains("\n\n") {
310 break;
311 }
312 }
313 res
314}
315
316#[cfg(test)]
317mod tests {
318 use crate::mock_analysis::analysis_and_position;
319
320 use super::*;
321
322 fn do_check(before: &str, afters: &[&str]) {
323 let (analysis, position) = analysis_and_position(&before);
324 let before = analysis.file_text(position.file_id).unwrap();
325 let range = TextRange::empty(position.offset);
326 let mut frange = FileRange { file_id: position.file_id, range };
327
328 for &after in afters {
329 frange.range = analysis.extend_selection(frange).unwrap();
330 let actual = &before[frange.range];
331 assert_eq!(after, actual);
332 }
333 }
334
335 #[test]
336 fn test_extend_selection_arith() {
337 do_check(r#"fn foo() { <|>1 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]);
338 }
339
340 #[test]
341 fn test_extend_selection_list() {
342 do_check(r#"fn foo(<|>x: i32) {}"#, &["x", "x: i32"]);
343 do_check(r#"fn foo(<|>x: i32, y: i32) {}"#, &["x", "x: i32", "x: i32, "]);
344 do_check(r#"fn foo(<|>x: i32,y: i32) {}"#, &["x", "x: i32", "x: i32,", "(x: i32,y: i32)"]);
345 do_check(r#"fn foo(x: i32, <|>y: i32) {}"#, &["y", "y: i32", ", y: i32"]);
346 do_check(r#"fn foo(x: i32, <|>y: i32, ) {}"#, &["y", "y: i32", "y: i32, "]);
347 do_check(r#"fn foo(x: i32,<|>y: i32) {}"#, &["y", "y: i32", ",y: i32"]);
348
349 do_check(r#"const FOO: [usize; 2] = [ 22<|> , 33];"#, &["22", "22 , "]);
350 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|>];"#, &["33", ", 33"]);
351 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|> ,];"#, &["33", "33 ,", "[ 22 , 33 ,]"]);
352
353 do_check(r#"fn main() { (1, 2<|>) }"#, &["2", ", 2", "(1, 2)"]);
354
355 do_check(
356 r#"
357const FOO: [usize; 2] = [
358 22,
359 <|>33,
360]"#,
361 &["33", "33,"],
362 );
363
364 do_check(
365 r#"
366const FOO: [usize; 2] = [
367 22
368 , 33<|>,
369]"#,
370 &["33", "33,"],
371 );
372 }
373
374 #[test]
375 fn test_extend_selection_start_of_the_line() {
376 do_check(
377 r#"
378impl S {
379<|> fn foo() {
380
381 }
382}"#,
383 &[" fn foo() {\n\n }\n"],
384 );
385 }
386
387 #[test]
388 fn test_extend_selection_doc_comments() {
389 do_check(
390 r#"
391struct A;
392
393/// bla
394/// bla
395struct B {
396 <|>
397}
398 "#,
399 &["\n \n", "{\n \n}", "/// bla\n/// bla\nstruct B {\n \n}"],
400 )
401 }
402
403 #[test]
404 fn test_extend_selection_comments() {
405 do_check(
406 r#"
407fn bar(){}
408
409// fn foo() {
410// 1 + <|>1
411// }
412
413// fn foo(){}
414 "#,
415 &["1", "// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"],
416 );
417
418 do_check(
419 r#"
420// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
421// pub enum Direction {
422// <|> Next,
423// Prev
424// }
425"#,
426 &[
427 "// Next,",
428 "// #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n// pub enum Direction {\n// Next,\n// Prev\n// }",
429 ],
430 );
431
432 do_check(
433 r#"
434/*
435foo
436_bar1<|>*/
437"#,
438 &["_bar1", "/*\nfoo\n_bar1*/"],
439 );
440
441 do_check(r#"//!<|>foo_2 bar"#, &["foo_2", "//!foo_2 bar"]);
442
443 do_check(r#"/<|>/foo bar"#, &["//foo bar"]);
444 }
445
446 #[test]
447 fn test_extend_selection_prefer_idents() {
448 do_check(
449 r#"
450fn main() { foo<|>+bar;}
451"#,
452 &["foo", "foo+bar"],
453 );
454 do_check(
455 r#"
456fn main() { foo+<|>bar;}
457"#,
458 &["bar", "foo+bar"],
459 );
460 }
461
462 #[test]
463 fn test_extend_selection_prefer_lifetimes() {
464 do_check(r#"fn foo<<|>'a>() {}"#, &["'a", "<'a>"]);
465 do_check(r#"fn foo<'a<|>>() {}"#, &["'a", "<'a>"]);
466 }
467
468 #[test]
469 fn test_extend_selection_select_first_word() {
470 do_check(r#"// foo bar b<|>az quxx"#, &["baz", "// foo bar baz quxx"]);
471 do_check(
472 r#"
473impl S {
474fn foo() {
475// hel<|>lo world
476}
477}
478"#,
479 &["hello", "// hello world"],
480 );
481 }
482
483 #[test]
484 fn test_extend_selection_string() {
485 do_check(
486 r#"
487fn bar(){}
488
489" fn f<|>oo() {"
490"#,
491 &["foo", "\" fn foo() {\""],
492 );
493 }
494
495 #[test]
496 fn test_extend_trait_bounds_list_in_where_clause() {
497 do_check(
498 r#"
499fn foo<R>()
500 where
501 R: req::Request + 'static,
502 R::Params: DeserializeOwned<|> + panic::UnwindSafe + 'static,
503 R::Result: Serialize + 'static,
504"#,
505 &[
506 "DeserializeOwned",
507 "DeserializeOwned + ",
508 "DeserializeOwned + panic::UnwindSafe + 'static",
509 "R::Params: DeserializeOwned + panic::UnwindSafe + 'static",
510 "R::Params: DeserializeOwned + panic::UnwindSafe + 'static,",
511 ],
512 );
513 do_check(r#"fn foo<T>() where T: <|>Copy"#, &["Copy"]);
514 do_check(r#"fn foo<T>() where T: <|>Copy + Display"#, &["Copy", "Copy + "]);
515 do_check(r#"fn foo<T>() where T: <|>Copy +Display"#, &["Copy", "Copy +"]);
516 do_check(r#"fn foo<T>() where T: <|>Copy+Display"#, &["Copy", "Copy+"]);
517 do_check(r#"fn foo<T>() where T: Copy + <|>Display"#, &["Display", "+ Display"]);
518 do_check(r#"fn foo<T>() where T: Copy + <|>Display + Sync"#, &["Display", "Display + "]);
519 do_check(r#"fn foo<T>() where T: Copy +<|>Display"#, &["Display", "+Display"]);
520 }
521
522 #[test]
523 fn test_extend_trait_bounds_list_inline() {
524 do_check(r#"fn foo<T: <|>Copy>() {}"#, &["Copy"]);
525 do_check(r#"fn foo<T: <|>Copy + Display>() {}"#, &["Copy", "Copy + "]);
526 do_check(r#"fn foo<T: <|>Copy +Display>() {}"#, &["Copy", "Copy +"]);
527 do_check(r#"fn foo<T: <|>Copy+Display>() {}"#, &["Copy", "Copy+"]);
528 do_check(r#"fn foo<T: Copy + <|>Display>() {}"#, &["Display", "+ Display"]);
529 do_check(r#"fn foo<T: Copy + <|>Display + Sync>() {}"#, &["Display", "Display + "]);
530 do_check(r#"fn foo<T: Copy +<|>Display>() {}"#, &["Display", "+Display"]);
531 do_check(
532 r#"fn foo<T: Copy<|> + Display, U: Copy>() {}"#,
533 &[
534 "Copy",
535 "Copy + ",
536 "Copy + Display",
537 "T: Copy + Display",
538 "T: Copy + Display, ",
539 "<T: Copy + Display, U: Copy>",
540 ],
541 );
542 }
543
544 #[test]
545 fn test_extend_selection_on_tuple_in_type() {
546 do_check(
547 r#"fn main() { let _: (krate, <|>_crate_def_map, module_id) = (); }"#,
548 &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
549 );
550 // white space variations
551 do_check(
552 r#"fn main() { let _: (krate,<|>_crate_def_map,module_id) = (); }"#,
553 &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
554 );
555 do_check(
556 r#"
557fn main() { let _: (
558 krate,
559 _crate<|>_def_map,
560 module_id
561) = (); }"#,
562 &[
563 "_crate_def_map",
564 "_crate_def_map,",
565 "(\n krate,\n _crate_def_map,\n module_id\n)",
566 ],
567 );
568 }
569
570 #[test]
571 fn test_extend_selection_on_tuple_in_rvalue() {
572 do_check(
573 r#"fn main() { let var = (krate, _crate_def_map<|>, module_id); }"#,
574 &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
575 );
576 // white space variations
577 do_check(
578 r#"fn main() { let var = (krate,_crate<|>_def_map,module_id); }"#,
579 &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
580 );
581 do_check(
582 r#"
583fn main() { let var = (
584 krate,
585 _crate_def_map<|>,
586 module_id
587); }"#,
588 &[
589 "_crate_def_map",
590 "_crate_def_map,",
591 "(\n krate,\n _crate_def_map,\n module_id\n)",
592 ],
593 );
594 }
595
596 #[test]
597 fn test_extend_selection_on_tuple_pat() {
598 do_check(
599 r#"fn main() { let (krate, _crate_def_map<|>, module_id) = var; }"#,
600 &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
601 );
602 // white space variations
603 do_check(
604 r#"fn main() { let (krate,_crate<|>_def_map,module_id) = var; }"#,
605 &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
606 );
607 do_check(
608 r#"
609fn main() { let (
610 krate,
611 _crate_def_map<|>,
612 module_id
613) = var; }"#,
614 &[
615 "_crate_def_map",
616 "_crate_def_map,",
617 "(\n krate,\n _crate_def_map,\n module_id\n)",
618 ],
619 );
620 }
621
622 #[test]
623 fn extend_selection_inside_macros() {
624 do_check(
625 r#"macro_rules! foo { ($item:item) => {$item} }
626 foo!{fn hello(na<|>me:usize){}}"#,
627 &[
628 "name",
629 "name:usize",
630 "(name:usize)",
631 "fn hello(name:usize){}",
632 "{fn hello(name:usize){}}",
633 "foo!{fn hello(name:usize){}}",
634 ],
635 );
636 }
637
638 #[test]
639 fn extend_selection_inside_recur_macros() {
640 do_check(
641 r#" macro_rules! foo2 { ($item:item) => {$item} }
642 macro_rules! foo { ($item:item) => {foo2!($item);} }
643 foo!{fn hello(na<|>me:usize){}}"#,
644 &[
645 "name",
646 "name:usize",
647 "(name:usize)",
648 "fn hello(name:usize){}",
649 "{fn hello(name:usize){}}",
650 "foo!{fn hello(name:usize){}}",
651 ],
652 );
653 }
654}