1 use std::{iter::once, mem};
4 use ide_db::{base_db::FileRange, helpers::pick_best_token, RootDatabase};
5 use itertools::Itertools;
6 use syntax::{algo, ast, match_ast, AstNode, SyntaxElement, SyntaxKind, SyntaxNode, TextRange};
7 use text_edit::{TextEdit, TextEditBuilder};
9 #[derive(Copy, Clone, Debug)]
17 // Move item under cursor or selection up and down.
20 // | Editor | Action Name
22 // | VS Code | **rust-analyzer: Move item up**
23 // | VS Code | **rust-analyzer: Move item down**
26 // image::https://user-images.githubusercontent.com/48062697/113065576-04298180-91b1-11eb-91ce-4505e99ed598.gif[]
27 pub(crate) fn move_item(
31 ) -> Option<TextEdit> {
32 let sema = Semantics::new(db);
33 let file = sema.parse(range.file_id);
35 let item = if range.range.is_empty() {
36 SyntaxElement::Token(pick_best_token(
37 file.syntax().token_at_offset(range.range.start()),
39 SyntaxKind::IDENT | SyntaxKind::LIFETIME_IDENT => 2,
40 kind if kind.is_trivia() => 0,
45 file.syntax().covering_element(range.range)
48 find_ancestors(item, direction, range.range)
51 fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> {
52 let root = match item {
53 SyntaxElement::Node(node) => node,
54 SyntaxElement::Token(token) => token.parent()?,
59 SyntaxKind::GENERIC_PARAM_LIST,
60 SyntaxKind::GENERIC_ARG_LIST,
61 SyntaxKind::VARIANT_LIST,
62 SyntaxKind::TYPE_BOUND_LIST,
63 SyntaxKind::MATCH_ARM,
66 SyntaxKind::EXPR_STMT,
69 SyntaxKind::LOOP_EXPR,
70 SyntaxKind::WHILE_EXPR,
71 SyntaxKind::RETURN_EXPR,
72 SyntaxKind::MATCH_EXPR,
73 SyntaxKind::MACRO_CALL,
74 SyntaxKind::TYPE_ALIAS,
77 SyntaxKind::MACRO_DEF,
86 SyntaxKind::MACRO_RULES,
87 SyntaxKind::MACRO_DEF,
90 let ancestor = once(root.clone())
91 .chain(root.ancestors())
92 .find(|ancestor| movable.contains(&ancestor.kind()))?;
94 move_in_direction(&ancestor, direction, range)
101 ) -> Option<TextEdit> {
104 ast::ArgList(it) => swap_sibling_in_list(node, it.args(), range, direction),
105 ast::GenericParamList(it) => swap_sibling_in_list(node, it.generic_params(), range, direction),
106 ast::GenericArgList(it) => swap_sibling_in_list(node, it.generic_args(), range, direction),
107 ast::VariantList(it) => swap_sibling_in_list(node, it.variants(), range, direction),
108 ast::TypeBoundList(it) => swap_sibling_in_list(node, it.bounds(), range, direction),
109 _ => Some(replace_nodes(range, node, &match direction {
110 Direction::Up => node.prev_sibling(),
111 Direction::Down => node.next_sibling(),
117 fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>(
121 direction: Direction,
122 ) -> Option<TextEdit> {
123 let list_lookup = list.tuple_windows().find(|(l, r)| match direction {
124 Direction::Up => r.syntax().text_range().contains_range(range),
125 Direction::Down => l.syntax().text_range().contains_range(range),
128 if let Some((l, r)) = list_lookup {
129 Some(replace_nodes(range, l.syntax(), r.syntax()))
131 // Cursor is beyond any movable list item (for example, on curly brace in enum).
132 // It's not necessary, that parent of list is movable (arg list's parent is not, for example),
133 // and we have to continue tree traversal to find suitable node.
134 find_ancestors(SyntaxElement::Node(node.parent()?), direction, range)
138 fn replace_nodes<'a>(
140 mut first: &'a SyntaxNode,
141 mut second: &'a SyntaxNode,
143 let cursor_offset = if range.is_empty() {
144 // FIXME: `applySnippetTextEdits` does not support non-empty selection ranges
145 if first.text_range().contains_range(range) {
146 Some(range.start() - first.text_range().start())
147 } else if second.text_range().contains_range(range) {
148 mem::swap(&mut first, &mut second);
149 Some(range.start() - first.text_range().start())
157 let first_with_cursor = match cursor_offset {
159 let mut item_text = first.text().to_string();
160 item_text.insert_str(offset.into(), "$0");
163 None => first.text().to_string(),
166 let mut edit = TextEditBuilder::default();
168 algo::diff(first, second).into_text_edit(&mut edit);
169 edit.replace(second.text_range(), first_with_cursor);
177 use expect_test::{expect, Expect};
179 use crate::Direction;
181 fn check(ra_fixture: &str, expect: Expect, direction: Direction) {
182 let (analysis, range) = fixture::range(ra_fixture);
183 let edit = analysis.move_item(range, direction).unwrap().unwrap_or_default();
184 let mut file = analysis.file_text(range.file_id).unwrap().to_string();
185 edit.apply(&mut file);
186 expect.assert_eq(&file);
190 fn test_moves_match_arm_up() {
196 println!("Hello, world");
211 println!("Hello, world");
221 fn test_moves_match_arm_down() {
227 println!("Hello, world");
242 println!("Hello, world");
252 fn test_nowhere_to_move() {
258 println!("Hello, world");
270 println!("Hello, world");
283 fn test_moves_let_stmt_up() {
302 fn test_moves_expr_up() {
306 println!("Hello, world");
307 println!("All I want to say is...");$0$0
312 println!("All I want to say is...");$0
313 println!("Hello, world");
321 println!("Hello, world");
334 println!("Hello, world");
342 println!("Hello, world");
355 println!("Hello, world");
363 println!("Hello, world");
376 println!("Hello, world");
384 println!("Hello, world");
397 println!("Hello, world");
405 println!("Hello, world");
414 println!("Hello, world");
422 fn test_nowhere_to_move_stmt() {
426 println!("All I want to say is...");$0$0
427 println!("Hello, world");
432 println!("All I want to say is...");
433 println!("Hello, world");
441 fn test_move_item() {
458 fn test_move_impl_up() {
465 impl Wow for Yay $0$0{}
470 impl Wow for Yay $0{}
479 fn test_move_use_up() {
483 use std::collections::HashMap$0$0;
486 use std::collections::HashMap$0;
494 fn test_moves_match_expr_up() {
521 fn test_moves_param() {
524 fn test(one: i32, two$0$0: u32) {}
531 fn test(two$0: u32, one: i32) {}
541 fn f($0$0arg: u8, arg2: u16) {}
544 fn f(arg2: u16, $0arg: u8) {}
551 fn test_moves_arg_up() {
554 fn test(one: i32, two: u32) {}
561 fn test(one: i32, two: u32) {}
572 fn test_moves_arg_down() {
575 fn test(one: i32, two: u32) {}
582 fn test(one: i32, two: u32) {}
593 fn test_nowhere_to_move_arg() {
596 fn test(one: i32, two: u32) {}
603 fn test(one: i32, two: u32) {}
614 fn test_moves_generic_param_up() {
617 struct Test<A, B$0$0>(A, B);
622 struct Test<B$0, A>(A, B);
631 fn test_moves_generic_arg_up() {
634 struct Test<A, B>(A, B);
637 let t = Test::<i32, &str$0$0>(123, "yay");
641 struct Test<A, B>(A, B);
644 let t = Test::<&str$0, i32>(123, "yay");
652 fn test_moves_variant_up() {
675 fn test_moves_type_bound_up() {
682 fn test<T: One + Two$0$0>(t: T) {}
691 fn test<T: Two$0 + One>(t: T) {}
700 fn test_prioritizes_trait_items() {
749 fn test_weird_nesting() {
778 fn test_cursor_at_item_start() {
825 $0$0impl SomeTrait for Test {}
832 $0impl SomeTrait for Test {}
843 fn test_cursor_at_item_end() {
869 impl SomeTrait for Test {}$0$0
876 impl SomeTrait for Test {}$0
887 fn handles_empty_file() {
888 check(r#"$0$0"#, expect![[r#""#]], Direction::Up);