diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-03-22 13:08:45 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2021-03-22 13:08:45 +0000 |
commit | d4fa6721afacec78a750df1bb1f0e7e950eaf73c (patch) | |
tree | efabf84f708868484e0dac7893f77ddfba6d9c21 /crates/ide | |
parent | 3af1885bd2c4d3470d203a216488946ee8572970 (diff) | |
parent | d331155f8db056a0f7a406498c96f759f620d2c7 (diff) |
Merge #8054
8054: Item movers r=matklad a=ivan770
Closes #6823
https://user-images.githubusercontent.com/14003886/111331579-b4f43480-8679-11eb-9af0-e4dabacc4923.mp4
Implementation issues:
- [ ] Most of items are non-movable, since _movability_ of any item has to be determined manually. Common ones are movable though
- [x] Cursor should move with the item
Co-authored-by: ivan770 <[email protected]>
Diffstat (limited to 'crates/ide')
-rw-r--r-- | crates/ide/src/lib.rs | 10 | ||||
-rw-r--r-- | crates/ide/src/move_item.rs | 620 |
2 files changed, 630 insertions, 0 deletions
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 662da5a96..3f73c0632 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -37,6 +37,7 @@ mod hover; | |||
37 | mod inlay_hints; | 37 | mod inlay_hints; |
38 | mod join_lines; | 38 | mod join_lines; |
39 | mod matching_brace; | 39 | mod matching_brace; |
40 | mod move_item; | ||
40 | mod parent_module; | 41 | mod parent_module; |
41 | mod references; | 42 | mod references; |
42 | mod fn_references; | 43 | mod fn_references; |
@@ -76,6 +77,7 @@ pub use crate::{ | |||
76 | hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult}, | 77 | hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult}, |
77 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, | 78 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, |
78 | markup::Markup, | 79 | markup::Markup, |
80 | move_item::Direction, | ||
79 | prime_caches::PrimeCachesProgress, | 81 | prime_caches::PrimeCachesProgress, |
80 | references::{rename::RenameError, ReferenceSearchResult}, | 82 | references::{rename::RenameError, ReferenceSearchResult}, |
81 | runnables::{Runnable, RunnableKind, TestId}, | 83 | runnables::{Runnable, RunnableKind, TestId}, |
@@ -583,6 +585,14 @@ impl Analysis { | |||
583 | self.with_db(|db| annotations::resolve_annotation(db, annotation)) | 585 | self.with_db(|db| annotations::resolve_annotation(db, annotation)) |
584 | } | 586 | } |
585 | 587 | ||
588 | pub fn move_item( | ||
589 | &self, | ||
590 | range: FileRange, | ||
591 | direction: Direction, | ||
592 | ) -> Cancelable<Option<TextEdit>> { | ||
593 | self.with_db(|db| move_item::move_item(db, range, direction)) | ||
594 | } | ||
595 | |||
586 | /// Performs an operation on that may be Canceled. | 596 | /// Performs an operation on that may be Canceled. |
587 | fn with_db<F, T>(&self, f: F) -> Cancelable<T> | 597 | fn with_db<F, T>(&self, f: F) -> Cancelable<T> |
588 | where | 598 | where |
diff --git a/crates/ide/src/move_item.rs b/crates/ide/src/move_item.rs new file mode 100644 index 000000000..48690b073 --- /dev/null +++ b/crates/ide/src/move_item.rs | |||
@@ -0,0 +1,620 @@ | |||
1 | use std::iter::once; | ||
2 | |||
3 | use hir::Semantics; | ||
4 | use ide_db::{base_db::FileRange, RootDatabase}; | ||
5 | use itertools::Itertools; | ||
6 | use syntax::{ | ||
7 | algo, ast, match_ast, AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, | ||
8 | }; | ||
9 | use text_edit::{TextEdit, TextEditBuilder}; | ||
10 | |||
11 | pub enum Direction { | ||
12 | Up, | ||
13 | Down, | ||
14 | } | ||
15 | |||
16 | // Feature: Move Item | ||
17 | // | ||
18 | // Move item under cursor or selection up and down. | ||
19 | // | ||
20 | // |=== | ||
21 | // | Editor | Action Name | ||
22 | // | ||
23 | // | VS Code | **Rust Analyzer: Move item up** | ||
24 | // | VS Code | **Rust Analyzer: Move item down** | ||
25 | // |=== | ||
26 | pub(crate) fn move_item( | ||
27 | db: &RootDatabase, | ||
28 | range: FileRange, | ||
29 | direction: Direction, | ||
30 | ) -> Option<TextEdit> { | ||
31 | let sema = Semantics::new(db); | ||
32 | let file = sema.parse(range.file_id); | ||
33 | |||
34 | let item = file.syntax().covering_element(range.range); | ||
35 | find_ancestors(item, direction, range.range) | ||
36 | } | ||
37 | |||
38 | fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> { | ||
39 | let root = match item { | ||
40 | NodeOrToken::Node(node) => node, | ||
41 | NodeOrToken::Token(token) => token.parent()?, | ||
42 | }; | ||
43 | |||
44 | let movable = [ | ||
45 | SyntaxKind::ARG_LIST, | ||
46 | SyntaxKind::GENERIC_PARAM_LIST, | ||
47 | SyntaxKind::GENERIC_ARG_LIST, | ||
48 | SyntaxKind::VARIANT_LIST, | ||
49 | SyntaxKind::TYPE_BOUND_LIST, | ||
50 | SyntaxKind::MATCH_ARM, | ||
51 | SyntaxKind::PARAM, | ||
52 | SyntaxKind::LET_STMT, | ||
53 | SyntaxKind::EXPR_STMT, | ||
54 | SyntaxKind::MATCH_EXPR, | ||
55 | SyntaxKind::MACRO_CALL, | ||
56 | SyntaxKind::TYPE_ALIAS, | ||
57 | SyntaxKind::TRAIT, | ||
58 | SyntaxKind::IMPL, | ||
59 | SyntaxKind::MACRO_DEF, | ||
60 | SyntaxKind::STRUCT, | ||
61 | SyntaxKind::UNION, | ||
62 | SyntaxKind::ENUM, | ||
63 | SyntaxKind::FN, | ||
64 | SyntaxKind::MODULE, | ||
65 | SyntaxKind::USE, | ||
66 | SyntaxKind::STATIC, | ||
67 | SyntaxKind::CONST, | ||
68 | SyntaxKind::MACRO_RULES, | ||
69 | ]; | ||
70 | |||
71 | let ancestor = once(root.clone()) | ||
72 | .chain(root.ancestors()) | ||
73 | .find(|ancestor| movable.contains(&ancestor.kind()))?; | ||
74 | |||
75 | move_in_direction(&ancestor, direction, range) | ||
76 | } | ||
77 | |||
78 | fn move_in_direction( | ||
79 | node: &SyntaxNode, | ||
80 | direction: Direction, | ||
81 | range: TextRange, | ||
82 | ) -> Option<TextEdit> { | ||
83 | match_ast! { | ||
84 | match node { | ||
85 | ast::ArgList(it) => swap_sibling_in_list(it.args(), range, direction), | ||
86 | ast::GenericParamList(it) => swap_sibling_in_list(it.generic_params(), range, direction), | ||
87 | ast::GenericArgList(it) => swap_sibling_in_list(it.generic_args(), range, direction), | ||
88 | ast::VariantList(it) => swap_sibling_in_list(it.variants(), range, direction), | ||
89 | ast::TypeBoundList(it) => swap_sibling_in_list(it.bounds(), range, direction), | ||
90 | _ => Some(replace_nodes(node, &match direction { | ||
91 | Direction::Up => node.prev_sibling(), | ||
92 | Direction::Down => node.next_sibling(), | ||
93 | }?)) | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | |||
98 | fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>( | ||
99 | list: I, | ||
100 | range: TextRange, | ||
101 | direction: Direction, | ||
102 | ) -> Option<TextEdit> { | ||
103 | let (l, r) = list | ||
104 | .tuple_windows() | ||
105 | .filter(|(l, r)| match direction { | ||
106 | Direction::Up => r.syntax().text_range().contains_range(range), | ||
107 | Direction::Down => l.syntax().text_range().contains_range(range), | ||
108 | }) | ||
109 | .next()?; | ||
110 | |||
111 | Some(replace_nodes(l.syntax(), r.syntax())) | ||
112 | } | ||
113 | |||
114 | fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit { | ||
115 | let mut edit = TextEditBuilder::default(); | ||
116 | |||
117 | algo::diff(first, second).into_text_edit(&mut edit); | ||
118 | algo::diff(second, first).into_text_edit(&mut edit); | ||
119 | |||
120 | edit.finish() | ||
121 | } | ||
122 | |||
123 | #[cfg(test)] | ||
124 | mod tests { | ||
125 | use crate::fixture; | ||
126 | use expect_test::{expect, Expect}; | ||
127 | |||
128 | use crate::Direction; | ||
129 | |||
130 | fn check(ra_fixture: &str, expect: Expect, direction: Direction) { | ||
131 | let (analysis, range) = fixture::range(ra_fixture); | ||
132 | let edit = analysis.move_item(range, direction).unwrap().unwrap_or_default(); | ||
133 | let mut file = analysis.file_text(range.file_id).unwrap().to_string(); | ||
134 | edit.apply(&mut file); | ||
135 | expect.assert_eq(&file); | ||
136 | } | ||
137 | |||
138 | #[test] | ||
139 | fn test_moves_match_arm_up() { | ||
140 | check( | ||
141 | r#" | ||
142 | fn main() { | ||
143 | match true { | ||
144 | true => { | ||
145 | println!("Hello, world"); | ||
146 | }, | ||
147 | false =>$0$0 { | ||
148 | println!("Test"); | ||
149 | } | ||
150 | }; | ||
151 | } | ||
152 | "#, | ||
153 | expect![[r#" | ||
154 | fn main() { | ||
155 | match true { | ||
156 | false => { | ||
157 | println!("Test"); | ||
158 | }, | ||
159 | true => { | ||
160 | println!("Hello, world"); | ||
161 | } | ||
162 | }; | ||
163 | } | ||
164 | "#]], | ||
165 | Direction::Up, | ||
166 | ); | ||
167 | } | ||
168 | |||
169 | #[test] | ||
170 | fn test_moves_match_arm_down() { | ||
171 | check( | ||
172 | r#" | ||
173 | fn main() { | ||
174 | match true { | ||
175 | true =>$0$0 { | ||
176 | println!("Hello, world"); | ||
177 | }, | ||
178 | false => { | ||
179 | println!("Test"); | ||
180 | } | ||
181 | }; | ||
182 | } | ||
183 | "#, | ||
184 | expect![[r#" | ||
185 | fn main() { | ||
186 | match true { | ||
187 | false => { | ||
188 | println!("Test"); | ||
189 | }, | ||
190 | true => { | ||
191 | println!("Hello, world"); | ||
192 | } | ||
193 | }; | ||
194 | } | ||
195 | "#]], | ||
196 | Direction::Down, | ||
197 | ); | ||
198 | } | ||
199 | |||
200 | #[test] | ||
201 | fn test_nowhere_to_move() { | ||
202 | check( | ||
203 | r#" | ||
204 | fn main() { | ||
205 | match true { | ||
206 | true =>$0$0 { | ||
207 | println!("Hello, world"); | ||
208 | }, | ||
209 | false => { | ||
210 | println!("Test"); | ||
211 | } | ||
212 | }; | ||
213 | } | ||
214 | "#, | ||
215 | expect![[r#" | ||
216 | fn main() { | ||
217 | match true { | ||
218 | true => { | ||
219 | println!("Hello, world"); | ||
220 | }, | ||
221 | false => { | ||
222 | println!("Test"); | ||
223 | } | ||
224 | }; | ||
225 | } | ||
226 | "#]], | ||
227 | Direction::Up, | ||
228 | ); | ||
229 | } | ||
230 | |||
231 | #[test] | ||
232 | fn test_moves_let_stmt_up() { | ||
233 | check( | ||
234 | r#" | ||
235 | fn main() { | ||
236 | let test = 123; | ||
237 | let test2$0$0 = 456; | ||
238 | } | ||
239 | "#, | ||
240 | expect![[r#" | ||
241 | fn main() { | ||
242 | let test2 = 456; | ||
243 | let test = 123; | ||
244 | } | ||
245 | "#]], | ||
246 | Direction::Up, | ||
247 | ); | ||
248 | } | ||
249 | |||
250 | #[test] | ||
251 | fn test_moves_expr_up() { | ||
252 | check( | ||
253 | r#" | ||
254 | fn main() { | ||
255 | println!("Hello, world"); | ||
256 | println!("All I want to say is...");$0$0 | ||
257 | } | ||
258 | "#, | ||
259 | expect![[r#" | ||
260 | fn main() { | ||
261 | println!("All I want to say is..."); | ||
262 | println!("Hello, world"); | ||
263 | } | ||
264 | "#]], | ||
265 | Direction::Up, | ||
266 | ); | ||
267 | } | ||
268 | |||
269 | #[test] | ||
270 | fn test_nowhere_to_move_stmt() { | ||
271 | check( | ||
272 | r#" | ||
273 | fn main() { | ||
274 | println!("All I want to say is...");$0$0 | ||
275 | println!("Hello, world"); | ||
276 | } | ||
277 | "#, | ||
278 | expect![[r#" | ||
279 | fn main() { | ||
280 | println!("All I want to say is..."); | ||
281 | println!("Hello, world"); | ||
282 | } | ||
283 | "#]], | ||
284 | Direction::Up, | ||
285 | ); | ||
286 | } | ||
287 | |||
288 | #[test] | ||
289 | fn test_move_item() { | ||
290 | check( | ||
291 | r#" | ||
292 | fn main() {} | ||
293 | |||
294 | fn foo() {}$0$0 | ||
295 | "#, | ||
296 | expect![[r#" | ||
297 | fn foo() {} | ||
298 | |||
299 | fn main() {} | ||
300 | "#]], | ||
301 | Direction::Up, | ||
302 | ); | ||
303 | } | ||
304 | |||
305 | #[test] | ||
306 | fn test_move_impl_up() { | ||
307 | check( | ||
308 | r#" | ||
309 | struct Yay; | ||
310 | |||
311 | trait Wow {} | ||
312 | |||
313 | impl Wow for Yay $0$0{} | ||
314 | "#, | ||
315 | expect![[r#" | ||
316 | struct Yay; | ||
317 | |||
318 | impl Wow for Yay {} | ||
319 | |||
320 | trait Wow {} | ||
321 | "#]], | ||
322 | Direction::Up, | ||
323 | ); | ||
324 | } | ||
325 | |||
326 | #[test] | ||
327 | fn test_move_use_up() { | ||
328 | check( | ||
329 | r#" | ||
330 | use std::vec::Vec; | ||
331 | use std::collections::HashMap$0$0; | ||
332 | "#, | ||
333 | expect![[r#" | ||
334 | use std::collections::HashMap; | ||
335 | use std::vec::Vec; | ||
336 | "#]], | ||
337 | Direction::Up, | ||
338 | ); | ||
339 | } | ||
340 | |||
341 | #[test] | ||
342 | fn test_moves_match_expr_up() { | ||
343 | check( | ||
344 | r#" | ||
345 | fn main() { | ||
346 | let test = 123; | ||
347 | |||
348 | $0match test { | ||
349 | 456 => {}, | ||
350 | _ => {} | ||
351 | };$0 | ||
352 | } | ||
353 | "#, | ||
354 | expect![[r#" | ||
355 | fn main() { | ||
356 | match test { | ||
357 | 456 => {}, | ||
358 | _ => {} | ||
359 | }; | ||
360 | |||
361 | let test = 123; | ||
362 | } | ||
363 | "#]], | ||
364 | Direction::Up, | ||
365 | ); | ||
366 | } | ||
367 | |||
368 | #[test] | ||
369 | fn test_moves_param_up() { | ||
370 | check( | ||
371 | r#" | ||
372 | fn test(one: i32, two$0$0: u32) {} | ||
373 | |||
374 | fn main() { | ||
375 | test(123, 456); | ||
376 | } | ||
377 | "#, | ||
378 | expect![[r#" | ||
379 | fn test(two: u32, one: i32) {} | ||
380 | |||
381 | fn main() { | ||
382 | test(123, 456); | ||
383 | } | ||
384 | "#]], | ||
385 | Direction::Up, | ||
386 | ); | ||
387 | } | ||
388 | |||
389 | #[test] | ||
390 | fn test_moves_arg_up() { | ||
391 | check( | ||
392 | r#" | ||
393 | fn test(one: i32, two: u32) {} | ||
394 | |||
395 | fn main() { | ||
396 | test(123, 456$0$0); | ||
397 | } | ||
398 | "#, | ||
399 | expect![[r#" | ||
400 | fn test(one: i32, two: u32) {} | ||
401 | |||
402 | fn main() { | ||
403 | test(456, 123); | ||
404 | } | ||
405 | "#]], | ||
406 | Direction::Up, | ||
407 | ); | ||
408 | } | ||
409 | |||
410 | #[test] | ||
411 | fn test_moves_arg_down() { | ||
412 | check( | ||
413 | r#" | ||
414 | fn test(one: i32, two: u32) {} | ||
415 | |||
416 | fn main() { | ||
417 | test(123$0$0, 456); | ||
418 | } | ||
419 | "#, | ||
420 | expect![[r#" | ||
421 | fn test(one: i32, two: u32) {} | ||
422 | |||
423 | fn main() { | ||
424 | test(456, 123); | ||
425 | } | ||
426 | "#]], | ||
427 | Direction::Down, | ||
428 | ); | ||
429 | } | ||
430 | |||
431 | #[test] | ||
432 | fn test_nowhere_to_move_arg() { | ||
433 | check( | ||
434 | r#" | ||
435 | fn test(one: i32, two: u32) {} | ||
436 | |||
437 | fn main() { | ||
438 | test(123$0$0, 456); | ||
439 | } | ||
440 | "#, | ||
441 | expect![[r#" | ||
442 | fn test(one: i32, two: u32) {} | ||
443 | |||
444 | fn main() { | ||
445 | test(123, 456); | ||
446 | } | ||
447 | "#]], | ||
448 | Direction::Up, | ||
449 | ); | ||
450 | } | ||
451 | |||
452 | #[test] | ||
453 | fn test_moves_generic_param_up() { | ||
454 | check( | ||
455 | r#" | ||
456 | struct Test<A, B$0$0>(A, B); | ||
457 | |||
458 | fn main() {} | ||
459 | "#, | ||
460 | expect![[r#" | ||
461 | struct Test<B, A>(A, B); | ||
462 | |||
463 | fn main() {} | ||
464 | "#]], | ||
465 | Direction::Up, | ||
466 | ); | ||
467 | } | ||
468 | |||
469 | #[test] | ||
470 | fn test_moves_generic_arg_up() { | ||
471 | check( | ||
472 | r#" | ||
473 | struct Test<A, B>(A, B); | ||
474 | |||
475 | fn main() { | ||
476 | let t = Test::<i32, &str$0$0>(123, "yay"); | ||
477 | } | ||
478 | "#, | ||
479 | expect![[r#" | ||
480 | struct Test<A, B>(A, B); | ||
481 | |||
482 | fn main() { | ||
483 | let t = Test::<&str, i32>(123, "yay"); | ||
484 | } | ||
485 | "#]], | ||
486 | Direction::Up, | ||
487 | ); | ||
488 | } | ||
489 | |||
490 | #[test] | ||
491 | fn test_moves_variant_up() { | ||
492 | check( | ||
493 | r#" | ||
494 | enum Hello { | ||
495 | One, | ||
496 | Two$0$0 | ||
497 | } | ||
498 | |||
499 | fn main() {} | ||
500 | "#, | ||
501 | expect![[r#" | ||
502 | enum Hello { | ||
503 | Two, | ||
504 | One | ||
505 | } | ||
506 | |||
507 | fn main() {} | ||
508 | "#]], | ||
509 | Direction::Up, | ||
510 | ); | ||
511 | } | ||
512 | |||
513 | #[test] | ||
514 | fn test_moves_type_bound_up() { | ||
515 | check( | ||
516 | r#" | ||
517 | trait One {} | ||
518 | |||
519 | trait Two {} | ||
520 | |||
521 | fn test<T: One + Two$0$0>(t: T) {} | ||
522 | |||
523 | fn main() {} | ||
524 | "#, | ||
525 | expect![[r#" | ||
526 | trait One {} | ||
527 | |||
528 | trait Two {} | ||
529 | |||
530 | fn test<T: Two + One>(t: T) {} | ||
531 | |||
532 | fn main() {} | ||
533 | "#]], | ||
534 | Direction::Up, | ||
535 | ); | ||
536 | } | ||
537 | |||
538 | #[test] | ||
539 | fn test_prioritizes_trait_items() { | ||
540 | check( | ||
541 | r#" | ||
542 | struct Test; | ||
543 | |||
544 | trait Yay { | ||
545 | type One; | ||
546 | |||
547 | type Two; | ||
548 | |||
549 | fn inner(); | ||
550 | } | ||
551 | |||
552 | impl Yay for Test { | ||
553 | type One = i32; | ||
554 | |||
555 | type Two = u32; | ||
556 | |||
557 | fn inner() {$0$0 | ||
558 | println!("Mmmm"); | ||
559 | } | ||
560 | } | ||
561 | "#, | ||
562 | expect![[r#" | ||
563 | struct Test; | ||
564 | |||
565 | trait Yay { | ||
566 | type One; | ||
567 | |||
568 | type Two; | ||
569 | |||
570 | fn inner(); | ||
571 | } | ||
572 | |||
573 | impl Yay for Test { | ||
574 | type One = i32; | ||
575 | |||
576 | fn inner() { | ||
577 | println!("Mmmm"); | ||
578 | } | ||
579 | |||
580 | type Two = u32; | ||
581 | } | ||
582 | "#]], | ||
583 | Direction::Up, | ||
584 | ); | ||
585 | } | ||
586 | |||
587 | #[test] | ||
588 | fn test_weird_nesting() { | ||
589 | check( | ||
590 | r#" | ||
591 | fn test() { | ||
592 | mod hello { | ||
593 | fn inner() {} | ||
594 | } | ||
595 | |||
596 | mod hi {$0$0 | ||
597 | fn inner() {} | ||
598 | } | ||
599 | } | ||
600 | "#, | ||
601 | expect![[r#" | ||
602 | fn test() { | ||
603 | mod hi { | ||
604 | fn inner() {} | ||
605 | } | ||
606 | |||
607 | mod hello { | ||
608 | fn inner() {} | ||
609 | } | ||
610 | } | ||
611 | "#]], | ||
612 | Direction::Up, | ||
613 | ); | ||
614 | } | ||
615 | |||
616 | #[test] | ||
617 | fn handles_empty_file() { | ||
618 | check(r#"$0$0"#, expect![[r#""#]], Direction::Up); | ||
619 | } | ||
620 | } | ||