diff options
author | ivan770 <[email protected]> | 2021-03-16 12:37:00 +0000 |
---|---|---|
committer | ivan770 <[email protected]> | 2021-03-18 09:22:27 +0000 |
commit | 7d604584954660d255ad0929d3be8ce03f879d0c (patch) | |
tree | 613fdfdfd7eeb170082800533fb8b669dc35d25b /crates/ide | |
parent | d704750ba982153d92ccff90cf236121641b9da3 (diff) |
Item up and down movers
Diffstat (limited to 'crates/ide')
-rw-r--r-- | crates/ide/src/lib.rs | 10 | ||||
-rw-r--r-- | crates/ide/src/move_item.rs | 392 |
2 files changed, 402 insertions, 0 deletions
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 662da5a96..3f73c0632 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -37,6 +37,7 @@ mod hover; | |||
37 | mod inlay_hints; | 37 | mod inlay_hints; |
38 | mod join_lines; | 38 | mod join_lines; |
39 | mod matching_brace; | 39 | mod matching_brace; |
40 | mod move_item; | ||
40 | mod parent_module; | 41 | mod parent_module; |
41 | mod references; | 42 | mod references; |
42 | mod fn_references; | 43 | mod fn_references; |
@@ -76,6 +77,7 @@ pub use crate::{ | |||
76 | hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult}, | 77 | hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult}, |
77 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, | 78 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, |
78 | markup::Markup, | 79 | markup::Markup, |
80 | move_item::Direction, | ||
79 | prime_caches::PrimeCachesProgress, | 81 | prime_caches::PrimeCachesProgress, |
80 | references::{rename::RenameError, ReferenceSearchResult}, | 82 | references::{rename::RenameError, ReferenceSearchResult}, |
81 | runnables::{Runnable, RunnableKind, TestId}, | 83 | runnables::{Runnable, RunnableKind, TestId}, |
@@ -583,6 +585,14 @@ impl Analysis { | |||
583 | self.with_db(|db| annotations::resolve_annotation(db, annotation)) | 585 | self.with_db(|db| annotations::resolve_annotation(db, annotation)) |
584 | } | 586 | } |
585 | 587 | ||
588 | pub fn move_item( | ||
589 | &self, | ||
590 | range: FileRange, | ||
591 | direction: Direction, | ||
592 | ) -> Cancelable<Option<TextEdit>> { | ||
593 | self.with_db(|db| move_item::move_item(db, range, direction)) | ||
594 | } | ||
595 | |||
586 | /// Performs an operation on that may be Canceled. | 596 | /// Performs an operation on that may be Canceled. |
587 | fn with_db<F, T>(&self, f: F) -> Cancelable<T> | 597 | fn with_db<F, T>(&self, f: F) -> Cancelable<T> |
588 | where | 598 | where |
diff --git a/crates/ide/src/move_item.rs b/crates/ide/src/move_item.rs new file mode 100644 index 000000000..be62d008d --- /dev/null +++ b/crates/ide/src/move_item.rs | |||
@@ -0,0 +1,392 @@ | |||
1 | use std::iter::once; | ||
2 | |||
3 | use hir::Semantics; | ||
4 | use ide_db::{base_db::FileRange, RootDatabase}; | ||
5 | use syntax::{algo, AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode}; | ||
6 | use text_edit::{TextEdit, TextEditBuilder}; | ||
7 | |||
8 | pub enum Direction { | ||
9 | Up, | ||
10 | Down, | ||
11 | } | ||
12 | |||
13 | // Feature: Move Item | ||
14 | // | ||
15 | // Move item under cursor or selection up and down. | ||
16 | // | ||
17 | // |=== | ||
18 | // | Editor | Action Name | ||
19 | // | ||
20 | // | VS Code | **Rust Analyzer: Move item up** | ||
21 | // | VS Code | **Rust Analyzer: Move item down** | ||
22 | // |=== | ||
23 | pub(crate) fn move_item( | ||
24 | db: &RootDatabase, | ||
25 | range: FileRange, | ||
26 | direction: Direction, | ||
27 | ) -> Option<TextEdit> { | ||
28 | let sema = Semantics::new(db); | ||
29 | let file = sema.parse(range.file_id); | ||
30 | |||
31 | let item = file.syntax().covering_element(range.range); | ||
32 | find_ancestors(item, direction) | ||
33 | } | ||
34 | |||
35 | fn find_ancestors(item: SyntaxElement, direction: Direction) -> Option<TextEdit> { | ||
36 | let movable = [ | ||
37 | SyntaxKind::MATCH_ARM, | ||
38 | // https://github.com/intellij-rust/intellij-rust/blob/master/src/main/kotlin/org/rust/ide/actions/mover/RsStatementUpDownMover.kt | ||
39 | SyntaxKind::LET_STMT, | ||
40 | SyntaxKind::EXPR_STMT, | ||
41 | SyntaxKind::MATCH_EXPR, | ||
42 | // https://github.com/intellij-rust/intellij-rust/blob/master/src/main/kotlin/org/rust/ide/actions/mover/RsItemUpDownMover.kt | ||
43 | SyntaxKind::TRAIT, | ||
44 | SyntaxKind::IMPL, | ||
45 | SyntaxKind::MACRO_CALL, | ||
46 | SyntaxKind::MACRO_DEF, | ||
47 | SyntaxKind::STRUCT, | ||
48 | SyntaxKind::ENUM, | ||
49 | SyntaxKind::MODULE, | ||
50 | SyntaxKind::USE, | ||
51 | SyntaxKind::FN, | ||
52 | SyntaxKind::CONST, | ||
53 | SyntaxKind::TYPE_ALIAS, | ||
54 | ]; | ||
55 | |||
56 | let root = match item { | ||
57 | NodeOrToken::Node(node) => node, | ||
58 | NodeOrToken::Token(token) => token.parent(), | ||
59 | }; | ||
60 | |||
61 | let ancestor = once(root.clone()) | ||
62 | .chain(root.ancestors()) | ||
63 | .filter(|ancestor| movable.contains(&ancestor.kind())) | ||
64 | .max_by_key(|ancestor| kind_priority(ancestor.kind()))?; | ||
65 | |||
66 | move_in_direction(&ancestor, direction) | ||
67 | } | ||
68 | |||
69 | fn kind_priority(kind: SyntaxKind) -> i32 { | ||
70 | match kind { | ||
71 | SyntaxKind::MATCH_ARM => 4, | ||
72 | |||
73 | SyntaxKind::LET_STMT | SyntaxKind::EXPR_STMT | SyntaxKind::MATCH_EXPR => 3, | ||
74 | |||
75 | SyntaxKind::TRAIT | ||
76 | | SyntaxKind::IMPL | ||
77 | | SyntaxKind::MACRO_CALL | ||
78 | | SyntaxKind::MACRO_DEF | ||
79 | | SyntaxKind::STRUCT | ||
80 | | SyntaxKind::ENUM | ||
81 | | SyntaxKind::MODULE | ||
82 | | SyntaxKind::USE | ||
83 | | SyntaxKind::FN | ||
84 | | SyntaxKind::CONST | ||
85 | | SyntaxKind::TYPE_ALIAS => 2, | ||
86 | |||
87 | // Placeholder for items, that are non-movable, and filtered even before kind_priority call | ||
88 | _ => 1, | ||
89 | } | ||
90 | } | ||
91 | |||
92 | fn move_in_direction(node: &SyntaxNode, direction: Direction) -> Option<TextEdit> { | ||
93 | let sibling = match direction { | ||
94 | Direction::Up => node.prev_sibling(), | ||
95 | Direction::Down => node.next_sibling(), | ||
96 | }?; | ||
97 | |||
98 | Some(replace_nodes(&sibling, node)) | ||
99 | } | ||
100 | |||
101 | fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit { | ||
102 | let mut edit = TextEditBuilder::default(); | ||
103 | |||
104 | algo::diff(first, second).into_text_edit(&mut edit); | ||
105 | algo::diff(second, first).into_text_edit(&mut edit); | ||
106 | |||
107 | edit.finish() | ||
108 | } | ||
109 | |||
110 | #[cfg(test)] | ||
111 | mod tests { | ||
112 | use crate::fixture; | ||
113 | use expect_test::{expect, Expect}; | ||
114 | |||
115 | use crate::Direction; | ||
116 | |||
117 | fn check(ra_fixture: &str, expect: Expect, direction: Direction) { | ||
118 | let (analysis, range) = fixture::range(ra_fixture); | ||
119 | let edit = analysis.move_item(range, direction).unwrap().unwrap_or_default(); | ||
120 | let mut file = analysis.file_text(range.file_id).unwrap().to_string(); | ||
121 | edit.apply(&mut file); | ||
122 | expect.assert_eq(&file); | ||
123 | } | ||
124 | |||
125 | #[test] | ||
126 | fn test_moves_match_arm_up() { | ||
127 | check( | ||
128 | r#" | ||
129 | fn main() { | ||
130 | match true { | ||
131 | true => { | ||
132 | println!("Hello, world"); | ||
133 | }, | ||
134 | false =>$0$0 { | ||
135 | println!("Test"); | ||
136 | } | ||
137 | }; | ||
138 | } | ||
139 | "#, | ||
140 | expect![[r#" | ||
141 | fn main() { | ||
142 | match true { | ||
143 | false => { | ||
144 | println!("Test"); | ||
145 | }, | ||
146 | true => { | ||
147 | println!("Hello, world"); | ||
148 | } | ||
149 | }; | ||
150 | } | ||
151 | "#]], | ||
152 | Direction::Up, | ||
153 | ); | ||
154 | } | ||
155 | |||
156 | #[test] | ||
157 | fn test_moves_match_arm_down() { | ||
158 | check( | ||
159 | r#" | ||
160 | fn main() { | ||
161 | match true { | ||
162 | true =>$0$0 { | ||
163 | println!("Hello, world"); | ||
164 | }, | ||
165 | false => { | ||
166 | println!("Test"); | ||
167 | } | ||
168 | }; | ||
169 | } | ||
170 | "#, | ||
171 | expect![[r#" | ||
172 | fn main() { | ||
173 | match true { | ||
174 | false => { | ||
175 | println!("Test"); | ||
176 | }, | ||
177 | true => { | ||
178 | println!("Hello, world"); | ||
179 | } | ||
180 | }; | ||
181 | } | ||
182 | "#]], | ||
183 | Direction::Down, | ||
184 | ); | ||
185 | } | ||
186 | |||
187 | #[test] | ||
188 | fn test_nowhere_to_move() { | ||
189 | check( | ||
190 | r#" | ||
191 | fn main() { | ||
192 | match true { | ||
193 | true =>$0$0 { | ||
194 | println!("Hello, world"); | ||
195 | }, | ||
196 | false => { | ||
197 | println!("Test"); | ||
198 | } | ||
199 | }; | ||
200 | } | ||
201 | "#, | ||
202 | expect![[r#" | ||
203 | fn main() { | ||
204 | match true { | ||
205 | true => { | ||
206 | println!("Hello, world"); | ||
207 | }, | ||
208 | false => { | ||
209 | println!("Test"); | ||
210 | } | ||
211 | }; | ||
212 | } | ||
213 | "#]], | ||
214 | Direction::Up, | ||
215 | ); | ||
216 | } | ||
217 | |||
218 | #[test] | ||
219 | fn test_moves_let_stmt_up() { | ||
220 | check( | ||
221 | r#" | ||
222 | fn main() { | ||
223 | let test = 123; | ||
224 | let test2$0$0 = 456; | ||
225 | } | ||
226 | "#, | ||
227 | expect![[r#" | ||
228 | fn main() { | ||
229 | let test2 = 456; | ||
230 | let test = 123; | ||
231 | } | ||
232 | "#]], | ||
233 | Direction::Up, | ||
234 | ); | ||
235 | } | ||
236 | |||
237 | #[test] | ||
238 | fn test_prioritizes_match_arm() { | ||
239 | check( | ||
240 | r#" | ||
241 | fn main() { | ||
242 | match true { | ||
243 | true => { | ||
244 | let test = 123;$0$0 | ||
245 | let test2 = 456; | ||
246 | }, | ||
247 | false => { | ||
248 | println!("Test"); | ||
249 | } | ||
250 | }; | ||
251 | } | ||
252 | "#, | ||
253 | expect![[r#" | ||
254 | fn main() { | ||
255 | match true { | ||
256 | false => { | ||
257 | println!("Test"); | ||
258 | }, | ||
259 | true => { | ||
260 | let test = 123; | ||
261 | let test2 = 456; | ||
262 | } | ||
263 | }; | ||
264 | } | ||
265 | "#]], | ||
266 | Direction::Down, | ||
267 | ); | ||
268 | } | ||
269 | |||
270 | #[test] | ||
271 | fn test_moves_expr_up() { | ||
272 | check( | ||
273 | r#" | ||
274 | fn main() { | ||
275 | println!("Hello, world"); | ||
276 | println!("All I want to say is...");$0$0 | ||
277 | } | ||
278 | "#, | ||
279 | expect![[r#" | ||
280 | fn main() { | ||
281 | println!("All I want to say is..."); | ||
282 | println!("Hello, world"); | ||
283 | } | ||
284 | "#]], | ||
285 | Direction::Up, | ||
286 | ); | ||
287 | } | ||
288 | |||
289 | #[test] | ||
290 | fn test_nowhere_to_move_stmt() { | ||
291 | check( | ||
292 | r#" | ||
293 | fn main() { | ||
294 | println!("All I want to say is...");$0$0 | ||
295 | println!("Hello, world"); | ||
296 | } | ||
297 | "#, | ||
298 | expect![[r#" | ||
299 | fn main() { | ||
300 | println!("All I want to say is..."); | ||
301 | println!("Hello, world"); | ||
302 | } | ||
303 | "#]], | ||
304 | Direction::Up, | ||
305 | ); | ||
306 | } | ||
307 | |||
308 | #[test] | ||
309 | fn test_move_item() { | ||
310 | check( | ||
311 | r#" | ||
312 | fn main() {} | ||
313 | |||
314 | fn foo() {}$0$0 | ||
315 | "#, | ||
316 | expect![[r#" | ||
317 | fn foo() {} | ||
318 | |||
319 | fn main() {} | ||
320 | "#]], | ||
321 | Direction::Up, | ||
322 | ); | ||
323 | } | ||
324 | |||
325 | #[test] | ||
326 | fn test_move_impl_up() { | ||
327 | check( | ||
328 | r#" | ||
329 | struct Yay; | ||
330 | |||
331 | trait Wow {} | ||
332 | |||
333 | impl Wow for Yay {}$0$0 | ||
334 | "#, | ||
335 | expect![[r#" | ||
336 | struct Yay; | ||
337 | |||
338 | impl Wow for Yay {} | ||
339 | |||
340 | trait Wow {} | ||
341 | "#]], | ||
342 | Direction::Up, | ||
343 | ); | ||
344 | } | ||
345 | |||
346 | #[test] | ||
347 | fn test_move_use_up() { | ||
348 | check( | ||
349 | r#" | ||
350 | use std::vec::Vec; | ||
351 | use std::collections::HashMap$0$0; | ||
352 | "#, | ||
353 | expect![[r#" | ||
354 | use std::collections::HashMap; | ||
355 | use std::vec::Vec; | ||
356 | "#]], | ||
357 | Direction::Up, | ||
358 | ); | ||
359 | } | ||
360 | |||
361 | #[test] | ||
362 | fn moves_match_expr_up() { | ||
363 | check( | ||
364 | r#" | ||
365 | fn main() { | ||
366 | let test = 123; | ||
367 | |||
368 | $0match test { | ||
369 | 456 => {}, | ||
370 | _ => {} | ||
371 | }$0; | ||
372 | } | ||
373 | "#, | ||
374 | expect![[r#" | ||
375 | fn main() { | ||
376 | match test { | ||
377 | 456 => {}, | ||
378 | _ => {} | ||
379 | }; | ||
380 | |||
381 | let test = 123; | ||
382 | } | ||
383 | "#]], | ||
384 | Direction::Up, | ||
385 | ); | ||
386 | } | ||
387 | |||
388 | #[test] | ||
389 | fn handles_empty_file() { | ||
390 | check(r#"$0$0"#, expect![[r#""#]], Direction::Up); | ||
391 | } | ||
392 | } | ||