aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/hover.rs1
-rw-r--r--crates/ide/src/move_item.rs268
2 files changed, 256 insertions, 13 deletions
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 3c951c507..02a1a5b37 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -470,6 +470,7 @@ fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option<hir::Module>
470 470
471fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { 471fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
472 return tokens.max_by_key(priority); 472 return tokens.max_by_key(priority);
473
473 fn priority(n: &SyntaxToken) -> usize { 474 fn priority(n: &SyntaxToken) -> usize {
474 match n.kind() { 475 match n.kind() {
475 IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3, 476 IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
diff --git a/crates/ide/src/move_item.rs b/crates/ide/src/move_item.rs
index 05fa8fc13..d36dcd4e4 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,18 @@ 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
147 fn priority(n: &SyntaxToken) -> usize {
148 match n.kind() {
149 SyntaxKind::IDENT | SyntaxKind::LIFETIME_IDENT => 2,
150 kind if kind.is_trivia() => 0,
151 _ => 1,
152 }
153 }
154}
155
124#[cfg(test)] 156#[cfg(test)]
125mod tests { 157mod tests {
126 use crate::fixture; 158 use crate::fixture;
@@ -265,6 +297,107 @@ fn main() {
265 "#]], 297 "#]],
266 Direction::Up, 298 Direction::Up,
267 ); 299 );
300 check(
301 r#"
302fn main() {
303 println!("Hello, world");
304
305 if true {
306 println!("Test");
307 }$0$0
308}
309 "#,
310 expect![[r#"
311fn main() {
312 if true {
313 println!("Test");
314 }
315
316 println!("Hello, world");
317}
318 "#]],
319 Direction::Up,
320 );
321 check(
322 r#"
323fn main() {
324 println!("Hello, world");
325
326 for i in 0..10 {
327 println!("Test");
328 }$0$0
329}
330 "#,
331 expect![[r#"
332fn main() {
333 for i in 0..10 {
334 println!("Test");
335 }
336
337 println!("Hello, world");
338}
339 "#]],
340 Direction::Up,
341 );
342 check(
343 r#"
344fn main() {
345 println!("Hello, world");
346
347 loop {
348 println!("Test");
349 }$0$0
350}
351 "#,
352 expect![[r#"
353fn main() {
354 loop {
355 println!("Test");
356 }
357
358 println!("Hello, world");
359}
360 "#]],
361 Direction::Up,
362 );
363 check(
364 r#"
365fn main() {
366 println!("Hello, world");
367
368 while true {
369 println!("Test");
370 }$0$0
371}
372 "#,
373 expect![[r#"
374fn main() {
375 while true {
376 println!("Test");
377 }
378
379 println!("Hello, world");
380}
381 "#]],
382 Direction::Up,
383 );
384 check(
385 r#"
386fn main() {
387 println!("Hello, world");
388
389 return 123;$0$0
390}
391 "#,
392 expect![[r#"
393fn main() {
394 return 123;
395
396 println!("Hello, world");
397}
398 "#]],
399 Direction::Up,
400 );
268 } 401 }
269 402
270 #[test] 403 #[test]
@@ -615,6 +748,115 @@ fn test() {
615 } 748 }
616 749
617 #[test] 750 #[test]
751 fn test_cursor_at_item_start() {
752 check(
753 r#"
754$0$0#[derive(Debug)]
755enum FooBar {
756 Foo,
757 Bar,
758}
759
760fn main() {}
761 "#,
762 expect![[r#"
763fn main() {}
764
765#[derive(Debug)]
766enum FooBar {
767 Foo,
768 Bar,
769}
770 "#]],
771 Direction::Down,
772 );
773 check(
774 r#"
775$0$0enum FooBar {
776 Foo,
777 Bar,
778}
779
780fn main() {}
781 "#,
782 expect![[r#"
783fn main() {}
784
785enum FooBar {
786 Foo,
787 Bar,
788}
789 "#]],
790 Direction::Down,
791 );
792 check(
793 r#"
794struct Test;
795
796trait SomeTrait {}
797
798$0$0impl SomeTrait for Test {}
799
800fn main() {}
801 "#,
802 expect![[r#"
803struct Test;
804
805impl SomeTrait for Test {}
806
807trait SomeTrait {}
808
809fn main() {}
810 "#]],
811 Direction::Up,
812 );
813 }
814
815 #[test]
816 fn test_cursor_at_item_end() {
817 check(
818 r#"
819enum FooBar {
820 Foo,
821 Bar,
822}$0$0
823
824fn main() {}
825 "#,
826 expect![[r#"
827fn main() {}
828
829enum FooBar {
830 Foo,
831 Bar,
832}
833 "#]],
834 Direction::Down,
835 );
836 check(
837 r#"
838struct Test;
839
840trait SomeTrait {}
841
842impl SomeTrait for Test {}$0$0
843
844fn main() {}
845 "#,
846 expect![[r#"
847struct Test;
848
849impl SomeTrait for Test {}
850
851trait SomeTrait {}
852
853fn main() {}
854 "#]],
855 Direction::Up,
856 );
857 }
858
859 #[test]
618 fn handles_empty_file() { 860 fn handles_empty_file() {
619 check(r#"$0$0"#, expect![[r#""#]], Direction::Up); 861 check(r#"$0$0"#, expect![[r#""#]], Direction::Up);
620 } 862 }