@@ -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,18 @@ 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
+
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
156
#[ cfg( test) ]
125
157
mod tests {
126
158
use crate :: fixture;
@@ -260,6 +292,107 @@ fn main() {
260
292
expect ! [ [ r#"
261
293
fn main() {
262
294
println!("All I want to say is...");
295
+ println!("Hello, world");
296
+ }
297
+ "# ] ] ,
298
+ Direction :: Up ,
299
+ ) ;
300
+ check (
301
+ r#"
302
+ fn main() {
303
+ println!("Hello, world");
304
+
305
+ if true {
306
+ println!("Test");
307
+ }$0$0
308
+ }
309
+ "# ,
310
+ expect ! [ [ r#"
311
+ fn main() {
312
+ if true {
313
+ println!("Test");
314
+ }
315
+
316
+ println!("Hello, world");
317
+ }
318
+ "# ] ] ,
319
+ Direction :: Up ,
320
+ ) ;
321
+ check (
322
+ r#"
323
+ fn main() {
324
+ println!("Hello, world");
325
+
326
+ for i in 0..10 {
327
+ println!("Test");
328
+ }$0$0
329
+ }
330
+ "# ,
331
+ expect ! [ [ r#"
332
+ fn 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#"
344
+ fn main() {
345
+ println!("Hello, world");
346
+
347
+ loop {
348
+ println!("Test");
349
+ }$0$0
350
+ }
351
+ "# ,
352
+ expect ! [ [ r#"
353
+ fn main() {
354
+ loop {
355
+ println!("Test");
356
+ }
357
+
358
+ println!("Hello, world");
359
+ }
360
+ "# ] ] ,
361
+ Direction :: Up ,
362
+ ) ;
363
+ check (
364
+ r#"
365
+ fn main() {
366
+ println!("Hello, world");
367
+
368
+ while true {
369
+ println!("Test");
370
+ }$0$0
371
+ }
372
+ "# ,
373
+ expect ! [ [ r#"
374
+ fn main() {
375
+ while true {
376
+ println!("Test");
377
+ }
378
+
379
+ println!("Hello, world");
380
+ }
381
+ "# ] ] ,
382
+ Direction :: Up ,
383
+ ) ;
384
+ check (
385
+ r#"
386
+ fn main() {
387
+ println!("Hello, world");
388
+
389
+ return 123;$0$0
390
+ }
391
+ "# ,
392
+ expect ! [ [ r#"
393
+ fn main() {
394
+ return 123;
395
+
263
396
println!("Hello, world");
264
397
}
265
398
"# ] ] ,
@@ -614,6 +747,115 @@ fn test() {
614
747
) ;
615
748
}
616
749
750
+ #[ test]
751
+ fn test_cursor_at_item_start ( ) {
752
+ check (
753
+ r#"
754
+ $0$0#[derive(Debug)]
755
+ enum FooBar {
756
+ Foo,
757
+ Bar,
758
+ }
759
+
760
+ fn main() {}
761
+ "# ,
762
+ expect ! [ [ r#"
763
+ fn main() {}
764
+
765
+ #[derive(Debug)]
766
+ enum 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
+
780
+ fn main() {}
781
+ "# ,
782
+ expect ! [ [ r#"
783
+ fn main() {}
784
+
785
+ enum FooBar {
786
+ Foo,
787
+ Bar,
788
+ }
789
+ "# ] ] ,
790
+ Direction :: Down ,
791
+ ) ;
792
+ check (
793
+ r#"
794
+ struct Test;
795
+
796
+ trait SomeTrait {}
797
+
798
+ $0$0impl SomeTrait for Test {}
799
+
800
+ fn main() {}
801
+ "# ,
802
+ expect ! [ [ r#"
803
+ struct Test;
804
+
805
+ impl SomeTrait for Test {}
806
+
807
+ trait SomeTrait {}
808
+
809
+ fn main() {}
810
+ "# ] ] ,
811
+ Direction :: Up ,
812
+ ) ;
813
+ }
814
+
815
+ #[ test]
816
+ fn test_cursor_at_item_end ( ) {
817
+ check (
818
+ r#"
819
+ enum FooBar {
820
+ Foo,
821
+ Bar,
822
+ }$0$0
823
+
824
+ fn main() {}
825
+ "# ,
826
+ expect ! [ [ r#"
827
+ fn main() {}
828
+
829
+ enum FooBar {
830
+ Foo,
831
+ Bar,
832
+ }
833
+ "# ] ] ,
834
+ Direction :: Down ,
835
+ ) ;
836
+ check (
837
+ r#"
838
+ struct Test;
839
+
840
+ trait SomeTrait {}
841
+
842
+ impl SomeTrait for Test {}$0$0
843
+
844
+ fn main() {}
845
+ "# ,
846
+ expect ! [ [ r#"
847
+ struct Test;
848
+
849
+ impl SomeTrait for Test {}
850
+
851
+ trait SomeTrait {}
852
+
853
+ fn main() {}
854
+ "# ] ] ,
855
+ Direction :: Up ,
856
+ ) ;
857
+ }
858
+
617
859
#[ test]
618
860
fn handles_empty_file ( ) {
619
861
check ( r#"$0$0"# , expect ! [ [ r#""# ] ] , Direction :: Up ) ;
0 commit comments