aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/move_item.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/move_item.rs')
-rw-r--r--crates/ide/src/move_item.rs392
1 files changed, 392 insertions, 0 deletions
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 @@
1use std::iter::once;
2
3use hir::Semantics;
4use ide_db::{base_db::FileRange, RootDatabase};
5use syntax::{algo, AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode};
6use text_edit::{TextEdit, TextEditBuilder};
7
8pub 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// |===
23pub(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
35fn 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
69fn 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
92fn 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
101fn 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)]
111mod 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#"
129fn 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#"
141fn 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#"
160fn 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#"
172fn 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#"
191fn 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#"
203fn 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#"
222fn main() {
223 let test = 123;
224 let test2$0$0 = 456;
225}
226 "#,
227 expect![[r#"
228fn 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#"
241fn 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#"
254fn 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#"
274fn main() {
275 println!("Hello, world");
276 println!("All I want to say is...");$0$0
277}
278 "#,
279 expect![[r#"
280fn 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#"
293fn main() {
294 println!("All I want to say is...");$0$0
295 println!("Hello, world");
296}
297 "#,
298 expect![[r#"
299fn 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#"
312fn main() {}
313
314fn foo() {}$0$0
315 "#,
316 expect![[r#"
317fn foo() {}
318
319fn main() {}
320 "#]],
321 Direction::Up,
322 );
323 }
324
325 #[test]
326 fn test_move_impl_up() {
327 check(
328 r#"
329struct Yay;
330
331trait Wow {}
332
333impl Wow for Yay {}$0$0
334 "#,
335 expect![[r#"
336struct Yay;
337
338impl Wow for Yay {}
339
340trait Wow {}
341 "#]],
342 Direction::Up,
343 );
344 }
345
346 #[test]
347 fn test_move_use_up() {
348 check(
349 r#"
350use std::vec::Vec;
351use std::collections::HashMap$0$0;
352 "#,
353 expect![[r#"
354use std::collections::HashMap;
355use std::vec::Vec;
356 "#]],
357 Direction::Up,
358 );
359 }
360
361 #[test]
362 fn moves_match_expr_up() {
363 check(
364 r#"
365fn main() {
366 let test = 123;
367
368 $0match test {
369 456 => {},
370 _ => {}
371 }$0;
372}
373 "#,
374 expect![[r#"
375fn 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}