aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/move_item.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/move_item.rs')
-rw-r--r--crates/ide/src/move_item.rs271
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;
4use ide_db::{base_db::FileRange, RootDatabase}; 4use ide_db::{base_db::FileRange, RootDatabase};
5use itertools::Itertools; 5use itertools::Itertools;
6use syntax::{ 6use 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};
9use text_edit::{TextEdit, TextEditBuilder}; 10use text_edit::{TextEdit, TextEditBuilder};
10 11
12#[derive(Copy, Clone, Debug)]
11pub enum Direction { 13pub 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[]
26pub(crate) fn move_item( 30pub(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
38fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> { 47fn 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
98fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>( 113fn 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
114fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit { 137fn 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
146fn 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)]
124mod tests { 159mod 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#"
304fn main() {
305 println!("Hello, world");
306
307 if true {
308 println!("Test");
309 }$0$0
310}
311 "#,
312 expect![[r#"
313fn main() {
314 if true {
315 println!("Test");
316 }
317
318 println!("Hello, world");
319}
320 "#]],
321 Direction::Up,
322 );
323 check(
324 r#"
325fn main() {
326 println!("Hello, world");
327
328 for i in 0..10 {
329 println!("Test");
330 }$0$0
331}
332 "#,
333 expect![[r#"
334fn 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#"
346fn main() {
347 println!("Hello, world");
348
349 loop {
350 println!("Test");
351 }$0$0
352}
353 "#,
354 expect![[r#"
355fn main() {
356 loop {
357 println!("Test");
358 }
359
360 println!("Hello, world");
361}
362 "#]],
363 Direction::Up,
364 );
365 check(
366 r#"
367fn main() {
368 println!("Hello, world");
369
370 while true {
371 println!("Test");
372 }$0$0
373}
374 "#,
375 expect![[r#"
376fn main() {
377 while true {
378 println!("Test");
379 }
380
381 println!("Hello, world");
382}
383 "#]],
384 Direction::Up,
385 );
386 check(
387 r#"
388fn main() {
389 println!("Hello, world");
390
391 return 123;$0$0
392}
393 "#,
394 expect![[r#"
395fn 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)]
757enum FooBar {
758 Foo,
759 Bar,
760}
761
762fn main() {}
763 "#,
764 expect![[r#"
765fn main() {}
766
767#[derive(Debug)]
768enum 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
782fn main() {}
783 "#,
784 expect![[r#"
785fn main() {}
786
787enum FooBar {
788 Foo,
789 Bar,
790}
791 "#]],
792 Direction::Down,
793 );
794 check(
795 r#"
796struct Test;
797
798trait SomeTrait {}
799
800$0$0impl SomeTrait for Test {}
801
802fn main() {}
803 "#,
804 expect![[r#"
805struct Test;
806
807impl SomeTrait for Test {}
808
809trait SomeTrait {}
810
811fn main() {}
812 "#]],
813 Direction::Up,
814 );
815 }
816
817 #[test]
818 fn test_cursor_at_item_end() {
819 check(
820 r#"
821enum FooBar {
822 Foo,
823 Bar,
824}$0$0
825
826fn main() {}
827 "#,
828 expect![[r#"
829fn main() {}
830
831enum FooBar {
832 Foo,
833 Bar,
834}
835 "#]],
836 Direction::Down,
837 );
838 check(
839 r#"
840struct Test;
841
842trait SomeTrait {}
843
844impl SomeTrait for Test {}$0$0
845
846fn main() {}
847 "#,
848 expect![[r#"
849struct Test;
850
851impl SomeTrait for Test {}
852
853trait SomeTrait {}
854
855fn 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 }