aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-03-22 13:08:45 +0000
committerGitHub <[email protected]>2021-03-22 13:08:45 +0000
commitd4fa6721afacec78a750df1bb1f0e7e950eaf73c (patch)
treeefabf84f708868484e0dac7893f77ddfba6d9c21 /crates
parent3af1885bd2c4d3470d203a216488946ee8572970 (diff)
parentd331155f8db056a0f7a406498c96f759f620d2c7 (diff)
Merge #8054
8054: Item movers r=matklad a=ivan770 Closes #6823 https://user-images.githubusercontent.com/14003886/111331579-b4f43480-8679-11eb-9af0-e4dabacc4923.mp4 Implementation issues: - [ ] Most of items are non-movable, since _movability_ of any item has to be determined manually. Common ones are movable though - [x] Cursor should move with the item Co-authored-by: ivan770 <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ide/src/lib.rs10
-rw-r--r--crates/ide/src/move_item.rs620
-rw-r--r--crates/rust-analyzer/src/handlers.rs19
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs22
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1
-rw-r--r--crates/rust-analyzer/src/to_proto.rs12
6 files changed, 684 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;
37mod inlay_hints; 37mod inlay_hints;
38mod join_lines; 38mod join_lines;
39mod matching_brace; 39mod matching_brace;
40mod move_item;
40mod parent_module; 41mod parent_module;
41mod references; 42mod references;
42mod fn_references; 43mod 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..48690b073
--- /dev/null
+++ b/crates/ide/src/move_item.rs
@@ -0,0 +1,620 @@
1use std::iter::once;
2
3use hir::Semantics;
4use ide_db::{base_db::FileRange, RootDatabase};
5use itertools::Itertools;
6use syntax::{
7 algo, ast, match_ast, AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange,
8};
9use text_edit::{TextEdit, TextEditBuilder};
10
11pub enum Direction {
12 Up,
13 Down,
14}
15
16// Feature: Move Item
17//
18// Move item under cursor or selection up and down.
19//
20// |===
21// | Editor | Action Name
22//
23// | VS Code | **Rust Analyzer: Move item up**
24// | VS Code | **Rust Analyzer: Move item down**
25// |===
26pub(crate) fn move_item(
27 db: &RootDatabase,
28 range: FileRange,
29 direction: Direction,
30) -> Option<TextEdit> {
31 let sema = Semantics::new(db);
32 let file = sema.parse(range.file_id);
33
34 let item = file.syntax().covering_element(range.range);
35 find_ancestors(item, direction, range.range)
36}
37
38fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> {
39 let root = match item {
40 NodeOrToken::Node(node) => node,
41 NodeOrToken::Token(token) => token.parent()?,
42 };
43
44 let movable = [
45 SyntaxKind::ARG_LIST,
46 SyntaxKind::GENERIC_PARAM_LIST,
47 SyntaxKind::GENERIC_ARG_LIST,
48 SyntaxKind::VARIANT_LIST,
49 SyntaxKind::TYPE_BOUND_LIST,
50 SyntaxKind::MATCH_ARM,
51 SyntaxKind::PARAM,
52 SyntaxKind::LET_STMT,
53 SyntaxKind::EXPR_STMT,
54 SyntaxKind::MATCH_EXPR,
55 SyntaxKind::MACRO_CALL,
56 SyntaxKind::TYPE_ALIAS,
57 SyntaxKind::TRAIT,
58 SyntaxKind::IMPL,
59 SyntaxKind::MACRO_DEF,
60 SyntaxKind::STRUCT,
61 SyntaxKind::UNION,
62 SyntaxKind::ENUM,
63 SyntaxKind::FN,
64 SyntaxKind::MODULE,
65 SyntaxKind::USE,
66 SyntaxKind::STATIC,
67 SyntaxKind::CONST,
68 SyntaxKind::MACRO_RULES,
69 ];
70
71 let ancestor = once(root.clone())
72 .chain(root.ancestors())
73 .find(|ancestor| movable.contains(&ancestor.kind()))?;
74
75 move_in_direction(&ancestor, direction, range)
76}
77
78fn move_in_direction(
79 node: &SyntaxNode,
80 direction: Direction,
81 range: TextRange,
82) -> Option<TextEdit> {
83 match_ast! {
84 match node {
85 ast::ArgList(it) => swap_sibling_in_list(it.args(), range, direction),
86 ast::GenericParamList(it) => swap_sibling_in_list(it.generic_params(), range, direction),
87 ast::GenericArgList(it) => swap_sibling_in_list(it.generic_args(), range, direction),
88 ast::VariantList(it) => swap_sibling_in_list(it.variants(), range, direction),
89 ast::TypeBoundList(it) => swap_sibling_in_list(it.bounds(), range, direction),
90 _ => Some(replace_nodes(node, &match direction {
91 Direction::Up => node.prev_sibling(),
92 Direction::Down => node.next_sibling(),
93 }?))
94 }
95 }
96}
97
98fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>(
99 list: I,
100 range: TextRange,
101 direction: Direction,
102) -> Option<TextEdit> {
103 let (l, r) = list
104 .tuple_windows()
105 .filter(|(l, r)| match direction {
106 Direction::Up => r.syntax().text_range().contains_range(range),
107 Direction::Down => l.syntax().text_range().contains_range(range),
108 })
109 .next()?;
110
111 Some(replace_nodes(l.syntax(), r.syntax()))
112}
113
114fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit {
115 let mut edit = TextEditBuilder::default();
116
117 algo::diff(first, second).into_text_edit(&mut edit);
118 algo::diff(second, first).into_text_edit(&mut edit);
119
120 edit.finish()
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::fixture;
126 use expect_test::{expect, Expect};
127
128 use crate::Direction;
129
130 fn check(ra_fixture: &str, expect: Expect, direction: Direction) {
131 let (analysis, range) = fixture::range(ra_fixture);
132 let edit = analysis.move_item(range, direction).unwrap().unwrap_or_default();
133 let mut file = analysis.file_text(range.file_id).unwrap().to_string();
134 edit.apply(&mut file);
135 expect.assert_eq(&file);
136 }
137
138 #[test]
139 fn test_moves_match_arm_up() {
140 check(
141 r#"
142fn main() {
143 match true {
144 true => {
145 println!("Hello, world");
146 },
147 false =>$0$0 {
148 println!("Test");
149 }
150 };
151}
152 "#,
153 expect![[r#"
154fn main() {
155 match true {
156 false => {
157 println!("Test");
158 },
159 true => {
160 println!("Hello, world");
161 }
162 };
163}
164 "#]],
165 Direction::Up,
166 );
167 }
168
169 #[test]
170 fn test_moves_match_arm_down() {
171 check(
172 r#"
173fn main() {
174 match true {
175 true =>$0$0 {
176 println!("Hello, world");
177 },
178 false => {
179 println!("Test");
180 }
181 };
182}
183 "#,
184 expect![[r#"
185fn main() {
186 match true {
187 false => {
188 println!("Test");
189 },
190 true => {
191 println!("Hello, world");
192 }
193 };
194}
195 "#]],
196 Direction::Down,
197 );
198 }
199
200 #[test]
201 fn test_nowhere_to_move() {
202 check(
203 r#"
204fn main() {
205 match true {
206 true =>$0$0 {
207 println!("Hello, world");
208 },
209 false => {
210 println!("Test");
211 }
212 };
213}
214 "#,
215 expect![[r#"
216fn main() {
217 match true {
218 true => {
219 println!("Hello, world");
220 },
221 false => {
222 println!("Test");
223 }
224 };
225}
226 "#]],
227 Direction::Up,
228 );
229 }
230
231 #[test]
232 fn test_moves_let_stmt_up() {
233 check(
234 r#"
235fn main() {
236 let test = 123;
237 let test2$0$0 = 456;
238}
239 "#,
240 expect![[r#"
241fn main() {
242 let test2 = 456;
243 let test = 123;
244}
245 "#]],
246 Direction::Up,
247 );
248 }
249
250 #[test]
251 fn test_moves_expr_up() {
252 check(
253 r#"
254fn main() {
255 println!("Hello, world");
256 println!("All I want to say is...");$0$0
257}
258 "#,
259 expect![[r#"
260fn main() {
261 println!("All I want to say is...");
262 println!("Hello, world");
263}
264 "#]],
265 Direction::Up,
266 );
267 }
268
269 #[test]
270 fn test_nowhere_to_move_stmt() {
271 check(
272 r#"
273fn main() {
274 println!("All I want to say is...");$0$0
275 println!("Hello, world");
276}
277 "#,
278 expect![[r#"
279fn main() {
280 println!("All I want to say is...");
281 println!("Hello, world");
282}
283 "#]],
284 Direction::Up,
285 );
286 }
287
288 #[test]
289 fn test_move_item() {
290 check(
291 r#"
292fn main() {}
293
294fn foo() {}$0$0
295 "#,
296 expect![[r#"
297fn foo() {}
298
299fn main() {}
300 "#]],
301 Direction::Up,
302 );
303 }
304
305 #[test]
306 fn test_move_impl_up() {
307 check(
308 r#"
309struct Yay;
310
311trait Wow {}
312
313impl Wow for Yay $0$0{}
314 "#,
315 expect![[r#"
316struct Yay;
317
318impl Wow for Yay {}
319
320trait Wow {}
321 "#]],
322 Direction::Up,
323 );
324 }
325
326 #[test]
327 fn test_move_use_up() {
328 check(
329 r#"
330use std::vec::Vec;
331use std::collections::HashMap$0$0;
332 "#,
333 expect![[r#"
334use std::collections::HashMap;
335use std::vec::Vec;
336 "#]],
337 Direction::Up,
338 );
339 }
340
341 #[test]
342 fn test_moves_match_expr_up() {
343 check(
344 r#"
345fn main() {
346 let test = 123;
347
348 $0match test {
349 456 => {},
350 _ => {}
351 };$0
352}
353 "#,
354 expect![[r#"
355fn main() {
356 match test {
357 456 => {},
358 _ => {}
359 };
360
361 let test = 123;
362}
363 "#]],
364 Direction::Up,
365 );
366 }
367
368 #[test]
369 fn test_moves_param_up() {
370 check(
371 r#"
372fn test(one: i32, two$0$0: u32) {}
373
374fn main() {
375 test(123, 456);
376}
377 "#,
378 expect![[r#"
379fn test(two: u32, one: i32) {}
380
381fn main() {
382 test(123, 456);
383}
384 "#]],
385 Direction::Up,
386 );
387 }
388
389 #[test]
390 fn test_moves_arg_up() {
391 check(
392 r#"
393fn test(one: i32, two: u32) {}
394
395fn main() {
396 test(123, 456$0$0);
397}
398 "#,
399 expect![[r#"
400fn test(one: i32, two: u32) {}
401
402fn main() {
403 test(456, 123);
404}
405 "#]],
406 Direction::Up,
407 );
408 }
409
410 #[test]
411 fn test_moves_arg_down() {
412 check(
413 r#"
414fn test(one: i32, two: u32) {}
415
416fn main() {
417 test(123$0$0, 456);
418}
419 "#,
420 expect![[r#"
421fn test(one: i32, two: u32) {}
422
423fn main() {
424 test(456, 123);
425}
426 "#]],
427 Direction::Down,
428 );
429 }
430
431 #[test]
432 fn test_nowhere_to_move_arg() {
433 check(
434 r#"
435fn test(one: i32, two: u32) {}
436
437fn main() {
438 test(123$0$0, 456);
439}
440 "#,
441 expect![[r#"
442fn test(one: i32, two: u32) {}
443
444fn main() {
445 test(123, 456);
446}
447 "#]],
448 Direction::Up,
449 );
450 }
451
452 #[test]
453 fn test_moves_generic_param_up() {
454 check(
455 r#"
456struct Test<A, B$0$0>(A, B);
457
458fn main() {}
459 "#,
460 expect![[r#"
461struct Test<B, A>(A, B);
462
463fn main() {}
464 "#]],
465 Direction::Up,
466 );
467 }
468
469 #[test]
470 fn test_moves_generic_arg_up() {
471 check(
472 r#"
473struct Test<A, B>(A, B);
474
475fn main() {
476 let t = Test::<i32, &str$0$0>(123, "yay");
477}
478 "#,
479 expect![[r#"
480struct Test<A, B>(A, B);
481
482fn main() {
483 let t = Test::<&str, i32>(123, "yay");
484}
485 "#]],
486 Direction::Up,
487 );
488 }
489
490 #[test]
491 fn test_moves_variant_up() {
492 check(
493 r#"
494enum Hello {
495 One,
496 Two$0$0
497}
498
499fn main() {}
500 "#,
501 expect![[r#"
502enum Hello {
503 Two,
504 One
505}
506
507fn main() {}
508 "#]],
509 Direction::Up,
510 );
511 }
512
513 #[test]
514 fn test_moves_type_bound_up() {
515 check(
516 r#"
517trait One {}
518
519trait Two {}
520
521fn test<T: One + Two$0$0>(t: T) {}
522
523fn main() {}
524 "#,
525 expect![[r#"
526trait One {}
527
528trait Two {}
529
530fn test<T: Two + One>(t: T) {}
531
532fn main() {}
533 "#]],
534 Direction::Up,
535 );
536 }
537
538 #[test]
539 fn test_prioritizes_trait_items() {
540 check(
541 r#"
542struct Test;
543
544trait Yay {
545 type One;
546
547 type Two;
548
549 fn inner();
550}
551
552impl Yay for Test {
553 type One = i32;
554
555 type Two = u32;
556
557 fn inner() {$0$0
558 println!("Mmmm");
559 }
560}
561 "#,
562 expect![[r#"
563struct Test;
564
565trait Yay {
566 type One;
567
568 type Two;
569
570 fn inner();
571}
572
573impl Yay for Test {
574 type One = i32;
575
576 fn inner() {
577 println!("Mmmm");
578 }
579
580 type Two = u32;
581}
582 "#]],
583 Direction::Up,
584 );
585 }
586
587 #[test]
588 fn test_weird_nesting() {
589 check(
590 r#"
591fn test() {
592 mod hello {
593 fn inner() {}
594 }
595
596 mod hi {$0$0
597 fn inner() {}
598 }
599}
600 "#,
601 expect![[r#"
602fn test() {
603 mod hi {
604 fn inner() {}
605 }
606
607 mod hello {
608 fn inner() {}
609 }
610}
611 "#]],
612 Direction::Up,
613 );
614 }
615
616 #[test]
617 fn handles_empty_file() {
618 check(r#"$0$0"#, expect![[r#""#]], Direction::Up);
619 }
620}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 880fea622..85e67554c 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -1427,6 +1427,25 @@ pub(crate) fn handle_open_cargo_toml(
1427 Ok(Some(res)) 1427 Ok(Some(res))
1428} 1428}
1429 1429
1430pub(crate) fn handle_move_item(
1431 snap: GlobalStateSnapshot,
1432 params: lsp_ext::MoveItemParams,
1433) -> Result<Option<lsp_types::TextDocumentEdit>> {
1434 let _p = profile::span("handle_move_item");
1435 let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
1436 let range = from_proto::file_range(&snap, params.text_document, params.range)?;
1437
1438 let direction = match params.direction {
1439 lsp_ext::MoveItemDirection::Up => ide::Direction::Up,
1440 lsp_ext::MoveItemDirection::Down => ide::Direction::Down,
1441 };
1442
1443 match snap.analysis.move_item(range, direction)? {
1444 Some(text_edit) => Ok(Some(to_proto::text_document_edit(&snap, file_id, text_edit)?)),
1445 None => Ok(None),
1446 }
1447}
1448
1430fn to_command_link(command: lsp_types::Command, tooltip: String) -> lsp_ext::CommandLink { 1449fn to_command_link(command: lsp_types::Command, tooltip: String) -> lsp_ext::CommandLink {
1431 lsp_ext::CommandLink { tooltip: Some(tooltip), command } 1450 lsp_ext::CommandLink { tooltip: Some(tooltip), command }
1432} 1451}
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index efcdcd1d9..0e1fec209 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -402,3 +402,25 @@ pub(crate) enum CodeLensResolveData {
402pub fn supports_utf8(caps: &lsp_types::ClientCapabilities) -> bool { 402pub fn supports_utf8(caps: &lsp_types::ClientCapabilities) -> bool {
403 caps.offset_encoding.as_deref().unwrap_or_default().iter().any(|it| it == "utf-8") 403 caps.offset_encoding.as_deref().unwrap_or_default().iter().any(|it| it == "utf-8")
404} 404}
405
406pub enum MoveItem {}
407
408impl Request for MoveItem {
409 type Params = MoveItemParams;
410 type Result = Option<lsp_types::TextDocumentEdit>;
411 const METHOD: &'static str = "experimental/moveItem";
412}
413
414#[derive(Serialize, Deserialize, Debug)]
415#[serde(rename_all = "camelCase")]
416pub struct MoveItemParams {
417 pub direction: MoveItemDirection,
418 pub text_document: TextDocumentIdentifier,
419 pub range: Range,
420}
421
422#[derive(Serialize, Deserialize, Debug)]
423pub enum MoveItemDirection {
424 Up,
425 Down,
426}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index c63a0eaea..e88f16cc1 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -504,6 +504,7 @@ impl GlobalState {
504 .on::<lsp_ext::HoverRequest>(handlers::handle_hover) 504 .on::<lsp_ext::HoverRequest>(handlers::handle_hover)
505 .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs) 505 .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs)
506 .on::<lsp_ext::OpenCargoToml>(handlers::handle_open_cargo_toml) 506 .on::<lsp_ext::OpenCargoToml>(handlers::handle_open_cargo_toml)
507 .on::<lsp_ext::MoveItem>(handlers::handle_move_item)
507 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting) 508 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)
508 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol) 509 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)
509 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol) 510 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index c1ca7ff9b..25169005f 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -658,6 +658,18 @@ pub(crate) fn goto_definition_response(
658 } 658 }
659} 659}
660 660
661pub(crate) fn text_document_edit(
662 snap: &GlobalStateSnapshot,
663 file_id: FileId,
664 edit: TextEdit,
665) -> Result<lsp_types::TextDocumentEdit> {
666 let text_document = optional_versioned_text_document_identifier(snap, file_id);
667 let line_index = snap.file_line_index(file_id)?;
668 let edits =
669 edit.into_iter().map(|it| lsp_types::OneOf::Left(text_edit(&line_index, it))).collect();
670 Ok(lsp_types::TextDocumentEdit { text_document, edits })
671}
672
661pub(crate) fn snippet_text_document_edit( 673pub(crate) fn snippet_text_document_edit(
662 snap: &GlobalStateSnapshot, 674 snap: &GlobalStateSnapshot,
663 is_snippet: bool, 675 is_snippet: bool,