diff options
author | Igor Aleksanov <[email protected]> | 2020-08-14 05:34:07 +0100 |
---|---|---|
committer | Igor Aleksanov <[email protected]> | 2020-08-14 05:34:07 +0100 |
commit | c26c911ec1e6c2ad1dcb7d155a6a1d528839ad1a (patch) | |
tree | 7cff36c38234be0afb65273146d8247083a5cfeb /crates/ide/src/extend_selection.rs | |
parent | 3c018bf84de5c693b5ee1c6bec0fed3b201c2060 (diff) | |
parent | f1f73649a686dc6e6449afc35e0fa6fed00e225d (diff) |
Merge branch 'master' into add-disable-diagnostics
Diffstat (limited to 'crates/ide/src/extend_selection.rs')
-rw-r--r-- | crates/ide/src/extend_selection.rs | 654 |
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 @@ | |||
1 | use std::iter::successors; | ||
2 | |||
3 | use hir::Semantics; | ||
4 | use ide_db::RootDatabase; | ||
5 | use 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 | |||
13 | use 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 | // |=== | ||
25 | pub(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 | |||
31 | fn 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 | |||
118 | fn 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(¯o_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. | ||
181 | fn shallowest_node(node: &SyntaxNode) -> SyntaxNode { | ||
182 | node.ancestors().take_while(|n| n.text_range() == node.text_range()).last().unwrap() | ||
183 | } | ||
184 | |||
185 | fn 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 | |||
212 | fn 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 | |||
235 | fn 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. | ||
247 | fn 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 | |||
290 | fn 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 | |||
300 | fn 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)] | ||
317 | mod 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#" | ||
357 | const FOO: [usize; 2] = [ | ||
358 | 22, | ||
359 | <|>33, | ||
360 | ]"#, | ||
361 | &["33", "33,"], | ||
362 | ); | ||
363 | |||
364 | do_check( | ||
365 | r#" | ||
366 | const 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#" | ||
378 | impl 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#" | ||
391 | struct A; | ||
392 | |||
393 | /// bla | ||
394 | /// bla | ||
395 | struct 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#" | ||
407 | fn 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 | /* | ||
435 | foo | ||
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#" | ||
450 | fn main() { foo<|>+bar;} | ||
451 | "#, | ||
452 | &["foo", "foo+bar"], | ||
453 | ); | ||
454 | do_check( | ||
455 | r#" | ||
456 | fn 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#" | ||
473 | impl S { | ||
474 | fn 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#" | ||
487 | fn 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#" | ||
499 | fn 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#" | ||
557 | fn 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#" | ||
583 | fn 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#" | ||
609 | fn 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 | } | ||