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