diff options
Diffstat (limited to 'crates/ide/src/move_item.rs')
-rw-r--r-- | crates/ide/src/move_item.rs | 271 |
1 files changed, 258 insertions, 13 deletions
diff --git a/crates/ide/src/move_item.rs b/crates/ide/src/move_item.rs index 48690b073..8d37f4f92 100644 --- a/crates/ide/src/move_item.rs +++ b/crates/ide/src/move_item.rs | |||
@@ -4,10 +4,12 @@ use hir::Semantics; | |||
4 | use ide_db::{base_db::FileRange, RootDatabase}; | 4 | use ide_db::{base_db::FileRange, RootDatabase}; |
5 | use itertools::Itertools; | 5 | use itertools::Itertools; |
6 | use syntax::{ | 6 | use syntax::{ |
7 | algo, ast, match_ast, AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, | 7 | algo, ast, match_ast, AstNode, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, |
8 | TokenAtOffset, | ||
8 | }; | 9 | }; |
9 | use text_edit::{TextEdit, TextEditBuilder}; | 10 | use text_edit::{TextEdit, TextEditBuilder}; |
10 | 11 | ||
12 | #[derive(Copy, Clone, Debug)] | ||
11 | pub enum Direction { | 13 | pub enum Direction { |
12 | Up, | 14 | Up, |
13 | Down, | 15 | Down, |
@@ -23,6 +25,8 @@ pub enum Direction { | |||
23 | // | VS Code | **Rust Analyzer: Move item up** | 25 | // | VS Code | **Rust Analyzer: Move item up** |
24 | // | VS Code | **Rust Analyzer: Move item down** | 26 | // | VS Code | **Rust Analyzer: Move item down** |
25 | // |=== | 27 | // |=== |
28 | // | ||
29 | // image::https://user-images.githubusercontent.com/48062697/113065576-04298180-91b1-11eb-91ce-4505e99ed598.gif[] | ||
26 | pub(crate) fn move_item( | 30 | pub(crate) fn move_item( |
27 | db: &RootDatabase, | 31 | db: &RootDatabase, |
28 | range: FileRange, | 32 | range: FileRange, |
@@ -31,14 +35,19 @@ pub(crate) fn move_item( | |||
31 | let sema = Semantics::new(db); | 35 | let sema = Semantics::new(db); |
32 | let file = sema.parse(range.file_id); | 36 | let file = sema.parse(range.file_id); |
33 | 37 | ||
34 | let item = file.syntax().covering_element(range.range); | 38 | let item = if range.range.is_empty() { |
39 | SyntaxElement::Token(pick_best(file.syntax().token_at_offset(range.range.start()))?) | ||
40 | } else { | ||
41 | file.syntax().covering_element(range.range) | ||
42 | }; | ||
43 | |||
35 | find_ancestors(item, direction, range.range) | 44 | find_ancestors(item, direction, range.range) |
36 | } | 45 | } |
37 | 46 | ||
38 | fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> { | 47 | fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> { |
39 | let root = match item { | 48 | let root = match item { |
40 | NodeOrToken::Node(node) => node, | 49 | SyntaxElement::Node(node) => node, |
41 | NodeOrToken::Token(token) => token.parent()?, | 50 | SyntaxElement::Token(token) => token.parent()?, |
42 | }; | 51 | }; |
43 | 52 | ||
44 | let movable = [ | 53 | let movable = [ |
@@ -51,6 +60,11 @@ fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) - | |||
51 | SyntaxKind::PARAM, | 60 | SyntaxKind::PARAM, |
52 | SyntaxKind::LET_STMT, | 61 | SyntaxKind::LET_STMT, |
53 | SyntaxKind::EXPR_STMT, | 62 | SyntaxKind::EXPR_STMT, |
63 | SyntaxKind::IF_EXPR, | ||
64 | SyntaxKind::FOR_EXPR, | ||
65 | SyntaxKind::LOOP_EXPR, | ||
66 | SyntaxKind::WHILE_EXPR, | ||
67 | SyntaxKind::RETURN_EXPR, | ||
54 | SyntaxKind::MATCH_EXPR, | 68 | SyntaxKind::MATCH_EXPR, |
55 | SyntaxKind::MACRO_CALL, | 69 | SyntaxKind::MACRO_CALL, |
56 | SyntaxKind::TYPE_ALIAS, | 70 | SyntaxKind::TYPE_ALIAS, |
@@ -66,6 +80,7 @@ fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) - | |||
66 | SyntaxKind::STATIC, | 80 | SyntaxKind::STATIC, |
67 | SyntaxKind::CONST, | 81 | SyntaxKind::CONST, |
68 | SyntaxKind::MACRO_RULES, | 82 | SyntaxKind::MACRO_RULES, |
83 | SyntaxKind::MACRO_DEF, | ||
69 | ]; | 84 | ]; |
70 | 85 | ||
71 | let ancestor = once(root.clone()) | 86 | let ancestor = once(root.clone()) |
@@ -82,11 +97,11 @@ fn move_in_direction( | |||
82 | ) -> Option<TextEdit> { | 97 | ) -> Option<TextEdit> { |
83 | match_ast! { | 98 | match_ast! { |
84 | match node { | 99 | match node { |
85 | ast::ArgList(it) => swap_sibling_in_list(it.args(), range, direction), | 100 | ast::ArgList(it) => swap_sibling_in_list(node, it.args(), range, direction), |
86 | ast::GenericParamList(it) => swap_sibling_in_list(it.generic_params(), range, direction), | 101 | ast::GenericParamList(it) => swap_sibling_in_list(node, it.generic_params(), range, direction), |
87 | ast::GenericArgList(it) => swap_sibling_in_list(it.generic_args(), range, direction), | 102 | ast::GenericArgList(it) => swap_sibling_in_list(node, it.generic_args(), range, direction), |
88 | ast::VariantList(it) => swap_sibling_in_list(it.variants(), range, direction), | 103 | ast::VariantList(it) => swap_sibling_in_list(node, it.variants(), range, direction), |
89 | ast::TypeBoundList(it) => swap_sibling_in_list(it.bounds(), range, direction), | 104 | ast::TypeBoundList(it) => swap_sibling_in_list(node, it.bounds(), range, direction), |
90 | _ => Some(replace_nodes(node, &match direction { | 105 | _ => Some(replace_nodes(node, &match direction { |
91 | Direction::Up => node.prev_sibling(), | 106 | Direction::Up => node.prev_sibling(), |
92 | Direction::Down => node.next_sibling(), | 107 | Direction::Down => node.next_sibling(), |
@@ -96,19 +111,27 @@ fn move_in_direction( | |||
96 | } | 111 | } |
97 | 112 | ||
98 | fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>( | 113 | fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>( |
114 | node: &SyntaxNode, | ||
99 | list: I, | 115 | list: I, |
100 | range: TextRange, | 116 | range: TextRange, |
101 | direction: Direction, | 117 | direction: Direction, |
102 | ) -> Option<TextEdit> { | 118 | ) -> Option<TextEdit> { |
103 | let (l, r) = list | 119 | let list_lookup = list |
104 | .tuple_windows() | 120 | .tuple_windows() |
105 | .filter(|(l, r)| match direction { | 121 | .filter(|(l, r)| match direction { |
106 | Direction::Up => r.syntax().text_range().contains_range(range), | 122 | Direction::Up => r.syntax().text_range().contains_range(range), |
107 | Direction::Down => l.syntax().text_range().contains_range(range), | 123 | Direction::Down => l.syntax().text_range().contains_range(range), |
108 | }) | 124 | }) |
109 | .next()?; | 125 | .next(); |
110 | 126 | ||
111 | Some(replace_nodes(l.syntax(), r.syntax())) | 127 | if let Some((l, r)) = list_lookup { |
128 | Some(replace_nodes(l.syntax(), r.syntax())) | ||
129 | } else { | ||
130 | // Cursor is beyond any movable list item (for example, on curly brace in enum). | ||
131 | // It's not necessary, that parent of list is movable (arg list's parent is not, for example), | ||
132 | // and we have to continue tree traversal to find suitable node. | ||
133 | find_ancestors(SyntaxElement::Node(node.parent()?), direction, range) | ||
134 | } | ||
112 | } | 135 | } |
113 | 136 | ||
114 | fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit { | 137 | fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit { |
@@ -120,6 +143,18 @@ fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit { | |||
120 | edit.finish() | 143 | edit.finish() |
121 | } | 144 | } |
122 | 145 | ||
146 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | ||
147 | return tokens.max_by_key(priority); | ||
148 | |||
149 | fn priority(n: &SyntaxToken) -> usize { | ||
150 | match n.kind() { | ||
151 | SyntaxKind::IDENT | SyntaxKind::LIFETIME_IDENT => 2, | ||
152 | kind if kind.is_trivia() => 0, | ||
153 | _ => 1, | ||
154 | } | ||
155 | } | ||
156 | } | ||
157 | |||
123 | #[cfg(test)] | 158 | #[cfg(test)] |
124 | mod tests { | 159 | mod tests { |
125 | use crate::fixture; | 160 | use crate::fixture; |
@@ -264,6 +299,107 @@ fn main() { | |||
264 | "#]], | 299 | "#]], |
265 | Direction::Up, | 300 | Direction::Up, |
266 | ); | 301 | ); |
302 | check( | ||
303 | r#" | ||
304 | fn main() { | ||
305 | println!("Hello, world"); | ||
306 | |||
307 | if true { | ||
308 | println!("Test"); | ||
309 | }$0$0 | ||
310 | } | ||
311 | "#, | ||
312 | expect![[r#" | ||
313 | fn main() { | ||
314 | if true { | ||
315 | println!("Test"); | ||
316 | } | ||
317 | |||
318 | println!("Hello, world"); | ||
319 | } | ||
320 | "#]], | ||
321 | Direction::Up, | ||
322 | ); | ||
323 | check( | ||
324 | r#" | ||
325 | fn main() { | ||
326 | println!("Hello, world"); | ||
327 | |||
328 | for i in 0..10 { | ||
329 | println!("Test"); | ||
330 | }$0$0 | ||
331 | } | ||
332 | "#, | ||
333 | expect![[r#" | ||
334 | fn main() { | ||
335 | for i in 0..10 { | ||
336 | println!("Test"); | ||
337 | } | ||
338 | |||
339 | println!("Hello, world"); | ||
340 | } | ||
341 | "#]], | ||
342 | Direction::Up, | ||
343 | ); | ||
344 | check( | ||
345 | r#" | ||
346 | fn main() { | ||
347 | println!("Hello, world"); | ||
348 | |||
349 | loop { | ||
350 | println!("Test"); | ||
351 | }$0$0 | ||
352 | } | ||
353 | "#, | ||
354 | expect![[r#" | ||
355 | fn main() { | ||
356 | loop { | ||
357 | println!("Test"); | ||
358 | } | ||
359 | |||
360 | println!("Hello, world"); | ||
361 | } | ||
362 | "#]], | ||
363 | Direction::Up, | ||
364 | ); | ||
365 | check( | ||
366 | r#" | ||
367 | fn main() { | ||
368 | println!("Hello, world"); | ||
369 | |||
370 | while true { | ||
371 | println!("Test"); | ||
372 | }$0$0 | ||
373 | } | ||
374 | "#, | ||
375 | expect![[r#" | ||
376 | fn main() { | ||
377 | while true { | ||
378 | println!("Test"); | ||
379 | } | ||
380 | |||
381 | println!("Hello, world"); | ||
382 | } | ||
383 | "#]], | ||
384 | Direction::Up, | ||
385 | ); | ||
386 | check( | ||
387 | r#" | ||
388 | fn main() { | ||
389 | println!("Hello, world"); | ||
390 | |||
391 | return 123;$0$0 | ||
392 | } | ||
393 | "#, | ||
394 | expect![[r#" | ||
395 | fn main() { | ||
396 | return 123; | ||
397 | |||
398 | println!("Hello, world"); | ||
399 | } | ||
400 | "#]], | ||
401 | Direction::Up, | ||
402 | ); | ||
267 | } | 403 | } |
268 | 404 | ||
269 | #[test] | 405 | #[test] |
@@ -614,6 +750,115 @@ fn test() { | |||
614 | } | 750 | } |
615 | 751 | ||
616 | #[test] | 752 | #[test] |
753 | fn test_cursor_at_item_start() { | ||
754 | check( | ||
755 | r#" | ||
756 | $0$0#[derive(Debug)] | ||
757 | enum FooBar { | ||
758 | Foo, | ||
759 | Bar, | ||
760 | } | ||
761 | |||
762 | fn main() {} | ||
763 | "#, | ||
764 | expect![[r#" | ||
765 | fn main() {} | ||
766 | |||
767 | #[derive(Debug)] | ||
768 | enum FooBar { | ||
769 | Foo, | ||
770 | Bar, | ||
771 | } | ||
772 | "#]], | ||
773 | Direction::Down, | ||
774 | ); | ||
775 | check( | ||
776 | r#" | ||
777 | $0$0enum FooBar { | ||
778 | Foo, | ||
779 | Bar, | ||
780 | } | ||
781 | |||
782 | fn main() {} | ||
783 | "#, | ||
784 | expect![[r#" | ||
785 | fn main() {} | ||
786 | |||
787 | enum FooBar { | ||
788 | Foo, | ||
789 | Bar, | ||
790 | } | ||
791 | "#]], | ||
792 | Direction::Down, | ||
793 | ); | ||
794 | check( | ||
795 | r#" | ||
796 | struct Test; | ||
797 | |||
798 | trait SomeTrait {} | ||
799 | |||
800 | $0$0impl SomeTrait for Test {} | ||
801 | |||
802 | fn main() {} | ||
803 | "#, | ||
804 | expect![[r#" | ||
805 | struct Test; | ||
806 | |||
807 | impl SomeTrait for Test {} | ||
808 | |||
809 | trait SomeTrait {} | ||
810 | |||
811 | fn main() {} | ||
812 | "#]], | ||
813 | Direction::Up, | ||
814 | ); | ||
815 | } | ||
816 | |||
817 | #[test] | ||
818 | fn test_cursor_at_item_end() { | ||
819 | check( | ||
820 | r#" | ||
821 | enum FooBar { | ||
822 | Foo, | ||
823 | Bar, | ||
824 | }$0$0 | ||
825 | |||
826 | fn main() {} | ||
827 | "#, | ||
828 | expect![[r#" | ||
829 | fn main() {} | ||
830 | |||
831 | enum FooBar { | ||
832 | Foo, | ||
833 | Bar, | ||
834 | } | ||
835 | "#]], | ||
836 | Direction::Down, | ||
837 | ); | ||
838 | check( | ||
839 | r#" | ||
840 | struct Test; | ||
841 | |||
842 | trait SomeTrait {} | ||
843 | |||
844 | impl SomeTrait for Test {}$0$0 | ||
845 | |||
846 | fn main() {} | ||
847 | "#, | ||
848 | expect![[r#" | ||
849 | struct Test; | ||
850 | |||
851 | impl SomeTrait for Test {} | ||
852 | |||
853 | trait SomeTrait {} | ||
854 | |||
855 | fn main() {} | ||
856 | "#]], | ||
857 | Direction::Up, | ||
858 | ); | ||
859 | } | ||
860 | |||
861 | #[test] | ||
617 | fn handles_empty_file() { | 862 | fn handles_empty_file() { |
618 | check(r#"$0$0"#, expect![[r#""#]], Direction::Up); | 863 | check(r#"$0$0"#, expect![[r#""#]], Direction::Up); |
619 | } | 864 | } |