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