aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/move_item.rs267
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;
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,
@@ -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
38fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> { 45fn 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
99fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>( 111fn 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
115fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit { 135fn 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
144fn 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)]
125mod tests { 156mod 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#"
301fn main() {
302 println!("Hello, world");
303
304 if true {
305 println!("Test");
306 }$0$0
307}
308 "#,
309 expect![[r#"
310fn main() {
311 if true {
312 println!("Test");
313 }
314
315 println!("Hello, world");
316}
317 "#]],
318 Direction::Up,
319 );
320 check(
321 r#"
322fn main() {
323 println!("Hello, world");
324
325 for i in 0..10 {
326 println!("Test");
327 }$0$0
328}
329 "#,
330 expect![[r#"
331fn 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#"
343fn main() {
344 println!("Hello, world");
345
346 loop {
347 println!("Test");
348 }$0$0
349}
350 "#,
351 expect![[r#"
352fn main() {
353 loop {
354 println!("Test");
355 }
356
357 println!("Hello, world");
358}
359 "#]],
360 Direction::Up,
361 );
362 check(
363 r#"
364fn main() {
365 println!("Hello, world");
366
367 while true {
368 println!("Test");
369 }$0$0
370}
371 "#,
372 expect![[r#"
373fn main() {
374 while true {
375 println!("Test");
376 }
377
378 println!("Hello, world");
379}
380 "#]],
381 Direction::Up,
382 );
383 check(
384 r#"
385fn main() {
386 println!("Hello, world");
387
388 return 123;$0$0
389}
390 "#,
391 expect![[r#"
392fn 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)]
754enum FooBar {
755 Foo,
756 Bar,
757}
758
759fn main() {}
760 "#,
761 expect![[r#"
762fn main() {}
763
764#[derive(Debug)]
765enum 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
779fn main() {}
780 "#,
781 expect![[r#"
782fn main() {}
783
784enum FooBar {
785 Foo,
786 Bar,
787}
788 "#]],
789 Direction::Down,
790 );
791 check(
792 r#"
793struct Test;
794
795trait SomeTrait {}
796
797$0$0impl SomeTrait for Test {}
798
799fn main() {}
800 "#,
801 expect![[r#"
802struct Test;
803
804impl SomeTrait for Test {}
805
806trait SomeTrait {}
807
808fn main() {}
809 "#]],
810 Direction::Up,
811 );
812 }
813
814 #[test]
815 fn test_cursor_at_item_end() {
816 check(
817 r#"
818enum FooBar {
819 Foo,
820 Bar,
821}$0$0
822
823fn main() {}
824 "#,
825 expect![[r#"
826fn main() {}
827
828enum FooBar {
829 Foo,
830 Bar,
831}
832 "#]],
833 Direction::Down,
834 );
835 check(
836 r#"
837struct Test;
838
839trait SomeTrait {}
840
841impl SomeTrait for Test {}$0$0
842
843fn main() {}
844 "#,
845 expect![[r#"
846struct Test;
847
848impl SomeTrait for Test {}
849
850trait SomeTrait {}
851
852fn 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 }