@@ -4,10 +4,12 @@ use hir::Semantics;
4
4
use ide_db:: { base_db:: FileRange , RootDatabase } ;
5
5
use itertools:: Itertools ;
6
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
10
use text_edit:: { TextEdit , TextEditBuilder } ;
10
11
12
+ #[ derive( Copy , Clone , Debug ) ]
11
13
pub enum Direction {
12
14
Up ,
13
15
Down ,
@@ -31,14 +33,19 @@ pub(crate) fn move_item(
31
33
let sema = Semantics :: new ( db) ;
32
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
42
find_ancestors ( item, direction, range. range )
36
43
}
37
44
38
45
fn find_ancestors ( item : SyntaxElement , direction : Direction , range : TextRange ) -> Option < TextEdit > {
39
46
let root = match item {
40
- NodeOrToken :: Node ( node) => node,
41
- NodeOrToken :: Token ( token) => token. parent ( ) ?,
47
+ SyntaxElement :: Node ( node) => node,
48
+ SyntaxElement :: Token ( token) => token. parent ( ) ?,
42
49
} ;
43
50
44
51
let movable = [
@@ -51,6 +58,11 @@ fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -
51
58
SyntaxKind :: PARAM ,
52
59
SyntaxKind :: LET_STMT ,
53
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
66
SyntaxKind :: MATCH_EXPR ,
55
67
SyntaxKind :: MACRO_CALL ,
56
68
SyntaxKind :: TYPE_ALIAS ,
@@ -83,11 +95,11 @@ fn move_in_direction(
83
95
) -> Option < TextEdit > {
84
96
match_ast ! {
85
97
match node {
86
- ast:: ArgList ( it) => swap_sibling_in_list( it. args( ) , range, direction) ,
87
- ast:: GenericParamList ( it) => swap_sibling_in_list( it. generic_params( ) , range, direction) ,
88
- ast:: GenericArgList ( it) => swap_sibling_in_list( it. generic_args( ) , range, direction) ,
89
- ast:: VariantList ( it) => swap_sibling_in_list( it. variants( ) , range, direction) ,
90
- ast:: TypeBoundList ( it) => swap_sibling_in_list( it. bounds( ) , range, direction) ,
98
+ ast:: ArgList ( it) => swap_sibling_in_list( node , it. args( ) , range, direction) ,
99
+ ast:: GenericParamList ( it) => swap_sibling_in_list( node , it. generic_params( ) , range, direction) ,
100
+ ast:: GenericArgList ( it) => swap_sibling_in_list( node , it. generic_args( ) , range, direction) ,
101
+ ast:: VariantList ( it) => swap_sibling_in_list( node , it. variants( ) , range, direction) ,
102
+ ast:: TypeBoundList ( it) => swap_sibling_in_list( node , it. bounds( ) , range, direction) ,
91
103
_ => Some ( replace_nodes( node, & match direction {
92
104
Direction :: Up => node. prev_sibling( ) ,
93
105
Direction :: Down => node. next_sibling( ) ,
@@ -97,19 +109,27 @@ fn move_in_direction(
97
109
}
98
110
99
111
fn swap_sibling_in_list < A : AstNode + Clone , I : Iterator < Item = A > > (
112
+ node : & SyntaxNode ,
100
113
list : I ,
101
114
range : TextRange ,
102
115
direction : Direction ,
103
116
) -> Option < TextEdit > {
104
- let ( l , r ) = list
117
+ let list_lookup = list
105
118
. tuple_windows ( )
106
119
. filter ( |( l, r) | match direction {
107
120
Direction :: Up => r. syntax ( ) . text_range ( ) . contains_range ( range) ,
108
121
Direction :: Down => l. syntax ( ) . text_range ( ) . contains_range ( range) ,
109
122
} )
110
- . next ( ) ?;
111
-
112
- Some ( replace_nodes ( l. syntax ( ) , r. syntax ( ) ) )
123
+ . next ( ) ;
124
+
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
135
fn replace_nodes ( first : & SyntaxNode , second : & SyntaxNode ) -> TextEdit {
@@ -121,6 +141,17 @@ fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit {
121
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
155
#[ cfg( test) ]
125
156
mod tests {
126
157
use crate :: fixture;
@@ -260,6 +291,107 @@ fn main() {
260
291
expect ! [ [ r#"
261
292
fn main() {
262
293
println!("All I want to say is...");
294
+ println!("Hello, world");
295
+ }
296
+ "# ] ] ,
297
+ Direction :: Up ,
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
+
263
395
println!("Hello, world");
264
396
}
265
397
"# ] ] ,
@@ -614,6 +746,115 @@ fn test() {
614
746
) ;
615
747
}
616
748
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
+
617
858
#[ test]
618
859
fn handles_empty_file ( ) {
619
860
check ( r#"$0$0"# , expect ! [ [ r#""# ] ] , Direction :: Up ) ;
0 commit comments