]> git.lizzy.rs Git - rust.git/blob - crates/ra_ide/src/extend_selection.rs
c096ca6ae44949033e7d58b410285bf3ca1550a9
[rust.git] / crates / ra_ide / src / extend_selection.rs
1 //! FIXME: write short doc here
2
3 use ra_db::SourceDatabase;
4 use ra_syntax::{
5     algo::find_covering_element,
6     ast::{self, AstNode, AstToken},
7     Direction, NodeOrToken,
8     SyntaxKind::{self, *},
9     SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T,
10 };
11
12 use crate::{db::RootDatabase, FileRange};
13
14 // FIXME: restore macro support
15 pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
16     let parse = db.parse(frange.file_id);
17     try_extend_selection(parse.tree().syntax(), frange.range).unwrap_or(frange.range)
18 }
19
20 fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange> {
21     let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING];
22     let list_kinds = [
23         RECORD_FIELD_PAT_LIST,
24         MATCH_ARM_LIST,
25         RECORD_FIELD_DEF_LIST,
26         TUPLE_FIELD_DEF_LIST,
27         RECORD_FIELD_LIST,
28         ENUM_VARIANT_LIST,
29         USE_TREE_LIST,
30         TYPE_PARAM_LIST,
31         TYPE_ARG_LIST,
32         TYPE_BOUND_LIST,
33         PARAM_LIST,
34         ARG_LIST,
35         ARRAY_EXPR,
36         TUPLE_EXPR,
37         TUPLE_TYPE,
38         WHERE_CLAUSE,
39     ];
40
41     if range.is_empty() {
42         let offset = range.start();
43         let mut leaves = root.token_at_offset(offset);
44         if leaves.clone().all(|it| it.kind() == WHITESPACE) {
45             return Some(extend_ws(root, leaves.next()?, offset));
46         }
47         let leaf_range = match leaves {
48             TokenAtOffset::None => return None,
49             TokenAtOffset::Single(l) => {
50                 if string_kinds.contains(&l.kind()) {
51                     extend_single_word_in_comment_or_string(&l, offset)
52                         .unwrap_or_else(|| l.text_range())
53                 } else {
54                     l.text_range()
55                 }
56             }
57             TokenAtOffset::Between(l, r) => pick_best(l, r).text_range(),
58         };
59         return Some(leaf_range);
60     };
61     let node = match find_covering_element(root, range) {
62         NodeOrToken::Token(token) => {
63             if token.text_range() != range {
64                 return Some(token.text_range());
65             }
66             if let Some(comment) = ast::Comment::cast(token.clone()) {
67                 if let Some(range) = extend_comments(comment) {
68                     return Some(range);
69                 }
70             }
71             token.parent()
72         }
73         NodeOrToken::Node(node) => node,
74     };
75     if node.text_range() != range {
76         return Some(node.text_range());
77     }
78
79     // Using shallowest node with same range allows us to traverse siblings.
80     let node = node.ancestors().take_while(|n| n.text_range() == node.text_range()).last().unwrap();
81
82     if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) {
83         if let Some(range) = extend_list_item(&node) {
84             return Some(range);
85         }
86     }
87
88     node.parent().map(|it| it.text_range())
89 }
90
91 fn extend_single_word_in_comment_or_string(
92     leaf: &SyntaxToken,
93     offset: TextUnit,
94 ) -> Option<TextRange> {
95     let text: &str = leaf.text();
96     let cursor_position: u32 = (offset - leaf.text_range().start()).into();
97
98     let (before, after) = text.split_at(cursor_position as usize);
99
100     fn non_word_char(c: char) -> bool {
101         !(c.is_alphanumeric() || c == '_')
102     }
103
104     let start_idx = before.rfind(non_word_char)? as u32;
105     let end_idx = after.find(non_word_char).unwrap_or_else(|| after.len()) as u32;
106
107     let from: TextUnit = (start_idx + 1).into();
108     let to: TextUnit = (cursor_position + end_idx).into();
109
110     let range = TextRange::from_to(from, to);
111     if range.is_empty() {
112         None
113     } else {
114         Some(range + leaf.text_range().start())
115     }
116 }
117
118 fn extend_ws(root: &SyntaxNode, ws: SyntaxToken, offset: TextUnit) -> TextRange {
119     let ws_text = ws.text();
120     let suffix = TextRange::from_to(offset, ws.text_range().end()) - ws.text_range().start();
121     let prefix = TextRange::from_to(ws.text_range().start(), offset) - ws.text_range().start();
122     let ws_suffix = &ws_text.as_str()[suffix];
123     let ws_prefix = &ws_text.as_str()[prefix];
124     if ws_text.contains('\n') && !ws_suffix.contains('\n') {
125         if let Some(node) = ws.next_sibling_or_token() {
126             let start = match ws_prefix.rfind('\n') {
127                 Some(idx) => ws.text_range().start() + TextUnit::from((idx + 1) as u32),
128                 None => node.text_range().start(),
129             };
130             let end = if root.text().char_at(node.text_range().end()) == Some('\n') {
131                 node.text_range().end() + TextUnit::of_char('\n')
132             } else {
133                 node.text_range().end()
134             };
135             return TextRange::from_to(start, end);
136         }
137     }
138     ws.text_range()
139 }
140
141 fn pick_best<'a>(l: SyntaxToken, r: SyntaxToken) -> SyntaxToken {
142     return if priority(&r) > priority(&l) { r } else { l };
143     fn priority(n: &SyntaxToken) -> usize {
144         match n.kind() {
145             WHITESPACE => 0,
146             IDENT | T![self] | T![super] | T![crate] | LIFETIME => 2,
147             _ => 1,
148         }
149     }
150 }
151
152 /// Extend list item selection to include nearby delimiter and whitespace.
153 fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
154     fn is_single_line_ws(node: &SyntaxToken) -> bool {
155         node.kind() == WHITESPACE && !node.text().contains('\n')
156     }
157
158     fn nearby_delimiter(
159         delimiter_kind: SyntaxKind,
160         node: &SyntaxNode,
161         dir: Direction,
162     ) -> Option<SyntaxToken> {
163         node.siblings_with_tokens(dir)
164             .skip(1)
165             .skip_while(|node| match node {
166                 NodeOrToken::Node(_) => false,
167                 NodeOrToken::Token(it) => is_single_line_ws(it),
168             })
169             .next()
170             .and_then(|it| it.into_token())
171             .filter(|node| node.kind() == delimiter_kind)
172     }
173
174     let delimiter = match node.kind() {
175         TYPE_BOUND => T![+],
176         _ => T![,],
177     };
178
179     if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Next) {
180         // Include any following whitespace when delimiter is after list item.
181         let final_node = delimiter_node
182             .next_sibling_or_token()
183             .and_then(|it| it.into_token())
184             .filter(|node| is_single_line_ws(node))
185             .unwrap_or(delimiter_node);
186
187         return Some(TextRange::from_to(node.text_range().start(), final_node.text_range().end()));
188     }
189     if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Prev) {
190         return Some(TextRange::from_to(
191             delimiter_node.text_range().start(),
192             node.text_range().end(),
193         ));
194     }
195
196     None
197 }
198
199 fn extend_comments(comment: ast::Comment) -> Option<TextRange> {
200     let prev = adj_comments(&comment, Direction::Prev);
201     let next = adj_comments(&comment, Direction::Next);
202     if prev != next {
203         Some(TextRange::from_to(
204             prev.syntax().text_range().start(),
205             next.syntax().text_range().end(),
206         ))
207     } else {
208         None
209     }
210 }
211
212 fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment {
213     let mut res = comment.clone();
214     for element in comment.syntax().siblings_with_tokens(dir) {
215         let token = match element.as_token() {
216             None => break,
217             Some(token) => token,
218         };
219         if let Some(c) = ast::Comment::cast(token.clone()) {
220             res = c
221         } else if token.kind() != WHITESPACE || token.text().contains("\n\n") {
222             break;
223         }
224     }
225     res
226 }
227
228 #[cfg(test)]
229 mod tests {
230     use ra_syntax::{AstNode, SourceFile};
231     use test_utils::extract_offset;
232
233     use super::*;
234
235     fn do_check(before: &str, afters: &[&str]) {
236         let (cursor, before) = extract_offset(before);
237         let parse = SourceFile::parse(&before);
238         let mut range = TextRange::offset_len(cursor, 0.into());
239         for &after in afters {
240             range = try_extend_selection(parse.tree().syntax(), range).unwrap();
241             let actual = &before[range];
242             assert_eq!(after, actual);
243         }
244     }
245
246     #[test]
247     fn test_extend_selection_arith() {
248         do_check(r#"fn foo() { <|>1 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]);
249     }
250
251     #[test]
252     fn test_extend_selection_list() {
253         do_check(r#"fn foo(<|>x: i32) {}"#, &["x", "x: i32"]);
254         do_check(r#"fn foo(<|>x: i32, y: i32) {}"#, &["x", "x: i32", "x: i32, "]);
255         do_check(r#"fn foo(<|>x: i32,y: i32) {}"#, &["x", "x: i32", "x: i32,", "(x: i32,y: i32)"]);
256         do_check(r#"fn foo(x: i32, <|>y: i32) {}"#, &["y", "y: i32", ", y: i32"]);
257         do_check(r#"fn foo(x: i32, <|>y: i32, ) {}"#, &["y", "y: i32", "y: i32, "]);
258         do_check(r#"fn foo(x: i32,<|>y: i32) {}"#, &["y", "y: i32", ",y: i32"]);
259
260         do_check(r#"const FOO: [usize; 2] = [ 22<|> , 33];"#, &["22", "22 , "]);
261         do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|>];"#, &["33", ", 33"]);
262         do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|> ,];"#, &["33", "33 ,", "[ 22 , 33 ,]"]);
263
264         do_check(r#"fn main() { (1, 2<|>) }"#, &["2", ", 2", "(1, 2)"]);
265
266         do_check(
267             r#"
268 const FOO: [usize; 2] = [
269     22,
270     <|>33,
271 ]"#,
272             &["33", "33,"],
273         );
274
275         do_check(
276             r#"
277 const FOO: [usize; 2] = [
278     22
279     , 33<|>,
280 ]"#,
281             &["33", "33,"],
282         );
283     }
284
285     #[test]
286     fn test_extend_selection_start_of_the_line() {
287         do_check(
288             r#"
289 impl S {
290 <|>    fn foo() {
291
292     }
293 }"#,
294             &["    fn foo() {\n\n    }\n"],
295         );
296     }
297
298     #[test]
299     fn test_extend_selection_doc_comments() {
300         do_check(
301             r#"
302 struct A;
303
304 /// bla
305 /// bla
306 struct B {
307     <|>
308 }
309             "#,
310             &["\n    \n", "{\n    \n}", "/// bla\n/// bla\nstruct B {\n    \n}"],
311         )
312     }
313
314     #[test]
315     fn test_extend_selection_comments() {
316         do_check(
317             r#"
318 fn bar(){}
319
320 // fn foo() {
321 // 1 + <|>1
322 // }
323
324 // fn foo(){}
325     "#,
326             &["1", "// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"],
327         );
328
329         do_check(
330             r#"
331 // #[derive(Debug, Clone, Copy, PartialEq, Eq)]
332 // pub enum Direction {
333 //  <|>   Next,
334 //     Prev
335 // }
336 "#,
337             &[
338                 "//     Next,",
339                 "// #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n// pub enum Direction {\n//     Next,\n//     Prev\n// }",
340             ],
341         );
342
343         do_check(
344             r#"
345 /*
346 foo
347 _bar1<|>*/
348 "#,
349             &["_bar1", "/*\nfoo\n_bar1*/"],
350         );
351
352         do_check(r#"//!<|>foo_2 bar"#, &["foo_2", "//!foo_2 bar"]);
353
354         do_check(r#"/<|>/foo bar"#, &["//foo bar"]);
355     }
356
357     #[test]
358     fn test_extend_selection_prefer_idents() {
359         do_check(
360             r#"
361 fn main() { foo<|>+bar;}
362 "#,
363             &["foo", "foo+bar"],
364         );
365         do_check(
366             r#"
367 fn main() { foo+<|>bar;}
368 "#,
369             &["bar", "foo+bar"],
370         );
371     }
372
373     #[test]
374     fn test_extend_selection_prefer_lifetimes() {
375         do_check(r#"fn foo<<|>'a>() {}"#, &["'a", "<'a>"]);
376         do_check(r#"fn foo<'a<|>>() {}"#, &["'a", "<'a>"]);
377     }
378
379     #[test]
380     fn test_extend_selection_select_first_word() {
381         do_check(r#"// foo bar b<|>az quxx"#, &["baz", "// foo bar baz quxx"]);
382         do_check(
383             r#"
384 impl S {
385 fn foo() {
386 // hel<|>lo world
387 }
388 }
389 "#,
390             &["hello", "// hello world"],
391         );
392     }
393
394     #[test]
395     fn test_extend_selection_string() {
396         do_check(
397             r#"
398 fn bar(){}
399
400 " fn f<|>oo() {"
401 "#,
402             &["foo", "\" fn foo() {\""],
403         );
404     }
405
406     #[test]
407     fn test_extend_trait_bounds_list_in_where_clause() {
408         do_check(
409             r#"
410 fn foo<R>() 
411     where 
412         R: req::Request + 'static,
413         R::Params: DeserializeOwned<|> + panic::UnwindSafe + 'static,
414         R::Result: Serialize + 'static,
415 "#,
416             &[
417                 "DeserializeOwned",
418                 "DeserializeOwned + ",
419                 "DeserializeOwned + panic::UnwindSafe + 'static",
420                 "R::Params: DeserializeOwned + panic::UnwindSafe + 'static",
421                 "R::Params: DeserializeOwned + panic::UnwindSafe + 'static,",
422             ],
423         );
424         do_check(r#"fn foo<T>() where T: <|>Copy"#, &["Copy"]);
425         do_check(r#"fn foo<T>() where T: <|>Copy + Display"#, &["Copy", "Copy + "]);
426         do_check(r#"fn foo<T>() where T: <|>Copy +Display"#, &["Copy", "Copy +"]);
427         do_check(r#"fn foo<T>() where T: <|>Copy+Display"#, &["Copy", "Copy+"]);
428         do_check(r#"fn foo<T>() where T: Copy + <|>Display"#, &["Display", "+ Display"]);
429         do_check(r#"fn foo<T>() where T: Copy + <|>Display + Sync"#, &["Display", "Display + "]);
430         do_check(r#"fn foo<T>() where T: Copy +<|>Display"#, &["Display", "+Display"]);
431     }
432
433     #[test]
434     fn test_extend_trait_bounds_list_inline() {
435         do_check(r#"fn foo<T: <|>Copy>() {}"#, &["Copy"]);
436         do_check(r#"fn foo<T: <|>Copy + Display>() {}"#, &["Copy", "Copy + "]);
437         do_check(r#"fn foo<T: <|>Copy +Display>() {}"#, &["Copy", "Copy +"]);
438         do_check(r#"fn foo<T: <|>Copy+Display>() {}"#, &["Copy", "Copy+"]);
439         do_check(r#"fn foo<T: Copy + <|>Display>() {}"#, &["Display", "+ Display"]);
440         do_check(r#"fn foo<T: Copy + <|>Display + Sync>() {}"#, &["Display", "Display + "]);
441         do_check(r#"fn foo<T: Copy +<|>Display>() {}"#, &["Display", "+Display"]);
442         do_check(
443             r#"fn foo<T: Copy<|> + Display, U: Copy>() {}"#,
444             &[
445                 "Copy",
446                 "Copy + ",
447                 "Copy + Display",
448                 "T: Copy + Display",
449                 "T: Copy + Display, ",
450                 "<T: Copy + Display, U: Copy>",
451             ],
452         );
453     }
454
455     #[test]
456     fn test_extend_selection_on_tuple_in_type() {
457         do_check(
458             r#"fn main() { let _: (krate, <|>_crate_def_map, module_id) = (); }"#,
459             &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
460         );
461         // white space variations
462         do_check(
463             r#"fn main() { let _: (krate,<|>_crate_def_map,module_id) = (); }"#,
464             &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
465         );
466         do_check(
467             r#"
468 fn main() { let _: (
469     krate,
470     _crate<|>_def_map,
471     module_id
472 ) = (); }"#,
473             &[
474                 "_crate_def_map",
475                 "_crate_def_map,",
476                 "(\n    krate,\n    _crate_def_map,\n    module_id\n)",
477             ],
478         );
479     }
480
481     #[test]
482     fn test_extend_selection_on_tuple_in_rvalue() {
483         do_check(
484             r#"fn main() { let var = (krate, _crate_def_map<|>, module_id); }"#,
485             &["_crate_def_map", "_crate_def_map, ", "(krate, _crate_def_map, module_id)"],
486         );
487         // white space variations
488         do_check(
489             r#"fn main() { let var = (krate,_crate<|>_def_map,module_id); }"#,
490             &["_crate_def_map", "_crate_def_map,", "(krate,_crate_def_map,module_id)"],
491         );
492         do_check(
493             r#"
494 fn main() { let var = (
495     krate,
496     _crate_def_map<|>,
497     module_id
498 ); }"#,
499             &[
500                 "_crate_def_map",
501                 "_crate_def_map,",
502                 "(\n    krate,\n    _crate_def_map,\n    module_id\n)",
503             ],
504         );
505     }
506 }