mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
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 <leshenko.ivan770@gmail.com>
This commit is contained in:
commit
d4fa6721af
11 changed files with 781 additions and 1 deletions
|
@ -37,6 +37,7 @@ mod hover;
|
||||||
mod inlay_hints;
|
mod inlay_hints;
|
||||||
mod join_lines;
|
mod join_lines;
|
||||||
mod matching_brace;
|
mod matching_brace;
|
||||||
|
mod move_item;
|
||||||
mod parent_module;
|
mod parent_module;
|
||||||
mod references;
|
mod references;
|
||||||
mod fn_references;
|
mod fn_references;
|
||||||
|
@ -76,6 +77,7 @@ pub use crate::{
|
||||||
hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult},
|
hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult},
|
||||||
inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
|
inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
|
||||||
markup::Markup,
|
markup::Markup,
|
||||||
|
move_item::Direction,
|
||||||
prime_caches::PrimeCachesProgress,
|
prime_caches::PrimeCachesProgress,
|
||||||
references::{rename::RenameError, ReferenceSearchResult},
|
references::{rename::RenameError, ReferenceSearchResult},
|
||||||
runnables::{Runnable, RunnableKind, TestId},
|
runnables::{Runnable, RunnableKind, TestId},
|
||||||
|
@ -583,6 +585,14 @@ impl Analysis {
|
||||||
self.with_db(|db| annotations::resolve_annotation(db, annotation))
|
self.with_db(|db| annotations::resolve_annotation(db, annotation))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn move_item(
|
||||||
|
&self,
|
||||||
|
range: FileRange,
|
||||||
|
direction: Direction,
|
||||||
|
) -> Cancelable<Option<TextEdit>> {
|
||||||
|
self.with_db(|db| move_item::move_item(db, range, direction))
|
||||||
|
}
|
||||||
|
|
||||||
/// Performs an operation on that may be Canceled.
|
/// Performs an operation on that may be Canceled.
|
||||||
fn with_db<F, T>(&self, f: F) -> Cancelable<T>
|
fn with_db<F, T>(&self, f: F) -> Cancelable<T>
|
||||||
where
|
where
|
||||||
|
|
620
crates/ide/src/move_item.rs
Normal file
620
crates/ide/src/move_item.rs
Normal file
|
@ -0,0 +1,620 @@
|
||||||
|
use std::iter::once;
|
||||||
|
|
||||||
|
use hir::Semantics;
|
||||||
|
use ide_db::{base_db::FileRange, RootDatabase};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use syntax::{
|
||||||
|
algo, ast, match_ast, AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange,
|
||||||
|
};
|
||||||
|
use text_edit::{TextEdit, TextEditBuilder};
|
||||||
|
|
||||||
|
pub enum Direction {
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feature: Move Item
|
||||||
|
//
|
||||||
|
// Move item under cursor or selection up and down.
|
||||||
|
//
|
||||||
|
// |===
|
||||||
|
// | Editor | Action Name
|
||||||
|
//
|
||||||
|
// | VS Code | **Rust Analyzer: Move item up**
|
||||||
|
// | VS Code | **Rust Analyzer: Move item down**
|
||||||
|
// |===
|
||||||
|
pub(crate) fn move_item(
|
||||||
|
db: &RootDatabase,
|
||||||
|
range: FileRange,
|
||||||
|
direction: Direction,
|
||||||
|
) -> Option<TextEdit> {
|
||||||
|
let sema = Semantics::new(db);
|
||||||
|
let file = sema.parse(range.file_id);
|
||||||
|
|
||||||
|
let item = file.syntax().covering_element(range.range);
|
||||||
|
find_ancestors(item, direction, range.range)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> {
|
||||||
|
let root = match item {
|
||||||
|
NodeOrToken::Node(node) => node,
|
||||||
|
NodeOrToken::Token(token) => token.parent()?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let movable = [
|
||||||
|
SyntaxKind::ARG_LIST,
|
||||||
|
SyntaxKind::GENERIC_PARAM_LIST,
|
||||||
|
SyntaxKind::GENERIC_ARG_LIST,
|
||||||
|
SyntaxKind::VARIANT_LIST,
|
||||||
|
SyntaxKind::TYPE_BOUND_LIST,
|
||||||
|
SyntaxKind::MATCH_ARM,
|
||||||
|
SyntaxKind::PARAM,
|
||||||
|
SyntaxKind::LET_STMT,
|
||||||
|
SyntaxKind::EXPR_STMT,
|
||||||
|
SyntaxKind::MATCH_EXPR,
|
||||||
|
SyntaxKind::MACRO_CALL,
|
||||||
|
SyntaxKind::TYPE_ALIAS,
|
||||||
|
SyntaxKind::TRAIT,
|
||||||
|
SyntaxKind::IMPL,
|
||||||
|
SyntaxKind::MACRO_DEF,
|
||||||
|
SyntaxKind::STRUCT,
|
||||||
|
SyntaxKind::UNION,
|
||||||
|
SyntaxKind::ENUM,
|
||||||
|
SyntaxKind::FN,
|
||||||
|
SyntaxKind::MODULE,
|
||||||
|
SyntaxKind::USE,
|
||||||
|
SyntaxKind::STATIC,
|
||||||
|
SyntaxKind::CONST,
|
||||||
|
SyntaxKind::MACRO_RULES,
|
||||||
|
];
|
||||||
|
|
||||||
|
let ancestor = once(root.clone())
|
||||||
|
.chain(root.ancestors())
|
||||||
|
.find(|ancestor| movable.contains(&ancestor.kind()))?;
|
||||||
|
|
||||||
|
move_in_direction(&ancestor, direction, range)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_in_direction(
|
||||||
|
node: &SyntaxNode,
|
||||||
|
direction: Direction,
|
||||||
|
range: TextRange,
|
||||||
|
) -> Option<TextEdit> {
|
||||||
|
match_ast! {
|
||||||
|
match node {
|
||||||
|
ast::ArgList(it) => swap_sibling_in_list(it.args(), range, direction),
|
||||||
|
ast::GenericParamList(it) => swap_sibling_in_list(it.generic_params(), range, direction),
|
||||||
|
ast::GenericArgList(it) => swap_sibling_in_list(it.generic_args(), range, direction),
|
||||||
|
ast::VariantList(it) => swap_sibling_in_list(it.variants(), range, direction),
|
||||||
|
ast::TypeBoundList(it) => swap_sibling_in_list(it.bounds(), range, direction),
|
||||||
|
_ => Some(replace_nodes(node, &match direction {
|
||||||
|
Direction::Up => node.prev_sibling(),
|
||||||
|
Direction::Down => node.next_sibling(),
|
||||||
|
}?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>(
|
||||||
|
list: I,
|
||||||
|
range: TextRange,
|
||||||
|
direction: Direction,
|
||||||
|
) -> Option<TextEdit> {
|
||||||
|
let (l, r) = list
|
||||||
|
.tuple_windows()
|
||||||
|
.filter(|(l, r)| match direction {
|
||||||
|
Direction::Up => r.syntax().text_range().contains_range(range),
|
||||||
|
Direction::Down => l.syntax().text_range().contains_range(range),
|
||||||
|
})
|
||||||
|
.next()?;
|
||||||
|
|
||||||
|
Some(replace_nodes(l.syntax(), r.syntax()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit {
|
||||||
|
let mut edit = TextEditBuilder::default();
|
||||||
|
|
||||||
|
algo::diff(first, second).into_text_edit(&mut edit);
|
||||||
|
algo::diff(second, first).into_text_edit(&mut edit);
|
||||||
|
|
||||||
|
edit.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::fixture;
|
||||||
|
use expect_test::{expect, Expect};
|
||||||
|
|
||||||
|
use crate::Direction;
|
||||||
|
|
||||||
|
fn check(ra_fixture: &str, expect: Expect, direction: Direction) {
|
||||||
|
let (analysis, range) = fixture::range(ra_fixture);
|
||||||
|
let edit = analysis.move_item(range, direction).unwrap().unwrap_or_default();
|
||||||
|
let mut file = analysis.file_text(range.file_id).unwrap().to_string();
|
||||||
|
edit.apply(&mut file);
|
||||||
|
expect.assert_eq(&file);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_moves_match_arm_up() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
match true {
|
||||||
|
true => {
|
||||||
|
println!("Hello, world");
|
||||||
|
},
|
||||||
|
false =>$0$0 {
|
||||||
|
println!("Test");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn main() {
|
||||||
|
match true {
|
||||||
|
false => {
|
||||||
|
println!("Test");
|
||||||
|
},
|
||||||
|
true => {
|
||||||
|
println!("Hello, world");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_moves_match_arm_down() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
match true {
|
||||||
|
true =>$0$0 {
|
||||||
|
println!("Hello, world");
|
||||||
|
},
|
||||||
|
false => {
|
||||||
|
println!("Test");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn main() {
|
||||||
|
match true {
|
||||||
|
false => {
|
||||||
|
println!("Test");
|
||||||
|
},
|
||||||
|
true => {
|
||||||
|
println!("Hello, world");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Down,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nowhere_to_move() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
match true {
|
||||||
|
true =>$0$0 {
|
||||||
|
println!("Hello, world");
|
||||||
|
},
|
||||||
|
false => {
|
||||||
|
println!("Test");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn main() {
|
||||||
|
match true {
|
||||||
|
true => {
|
||||||
|
println!("Hello, world");
|
||||||
|
},
|
||||||
|
false => {
|
||||||
|
println!("Test");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_moves_let_stmt_up() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
let test = 123;
|
||||||
|
let test2$0$0 = 456;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn main() {
|
||||||
|
let test2 = 456;
|
||||||
|
let test = 123;
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_moves_expr_up() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world");
|
||||||
|
println!("All I want to say is...");$0$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn main() {
|
||||||
|
println!("All I want to say is...");
|
||||||
|
println!("Hello, world");
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nowhere_to_move_stmt() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
println!("All I want to say is...");$0$0
|
||||||
|
println!("Hello, world");
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn main() {
|
||||||
|
println!("All I want to say is...");
|
||||||
|
println!("Hello, world");
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_move_item() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
fn foo() {}$0$0
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn foo() {}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_move_impl_up() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
struct Yay;
|
||||||
|
|
||||||
|
trait Wow {}
|
||||||
|
|
||||||
|
impl Wow for Yay $0$0{}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
struct Yay;
|
||||||
|
|
||||||
|
impl Wow for Yay {}
|
||||||
|
|
||||||
|
trait Wow {}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_move_use_up() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
use std::vec::Vec;
|
||||||
|
use std::collections::HashMap$0$0;
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::vec::Vec;
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_moves_match_expr_up() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
let test = 123;
|
||||||
|
|
||||||
|
$0match test {
|
||||||
|
456 => {},
|
||||||
|
_ => {}
|
||||||
|
};$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn main() {
|
||||||
|
match test {
|
||||||
|
456 => {},
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
let test = 123;
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_moves_param_up() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn test(one: i32, two$0$0: u32) {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
test(123, 456);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn test(two: u32, one: i32) {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
test(123, 456);
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_moves_arg_up() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn test(one: i32, two: u32) {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
test(123, 456$0$0);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn test(one: i32, two: u32) {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
test(456, 123);
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_moves_arg_down() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn test(one: i32, two: u32) {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
test(123$0$0, 456);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn test(one: i32, two: u32) {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
test(456, 123);
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Down,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nowhere_to_move_arg() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn test(one: i32, two: u32) {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
test(123$0$0, 456);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn test(one: i32, two: u32) {}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
test(123, 456);
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_moves_generic_param_up() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
struct Test<A, B$0$0>(A, B);
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
struct Test<B, A>(A, B);
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_moves_generic_arg_up() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
struct Test<A, B>(A, B);
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let t = Test::<i32, &str$0$0>(123, "yay");
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
struct Test<A, B>(A, B);
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let t = Test::<&str, i32>(123, "yay");
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_moves_variant_up() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
enum Hello {
|
||||||
|
One,
|
||||||
|
Two$0$0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
enum Hello {
|
||||||
|
Two,
|
||||||
|
One
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_moves_type_bound_up() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
trait One {}
|
||||||
|
|
||||||
|
trait Two {}
|
||||||
|
|
||||||
|
fn test<T: One + Two$0$0>(t: T) {}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
trait One {}
|
||||||
|
|
||||||
|
trait Two {}
|
||||||
|
|
||||||
|
fn test<T: Two + One>(t: T) {}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_prioritizes_trait_items() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
struct Test;
|
||||||
|
|
||||||
|
trait Yay {
|
||||||
|
type One;
|
||||||
|
|
||||||
|
type Two;
|
||||||
|
|
||||||
|
fn inner();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Yay for Test {
|
||||||
|
type One = i32;
|
||||||
|
|
||||||
|
type Two = u32;
|
||||||
|
|
||||||
|
fn inner() {$0$0
|
||||||
|
println!("Mmmm");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
struct Test;
|
||||||
|
|
||||||
|
trait Yay {
|
||||||
|
type One;
|
||||||
|
|
||||||
|
type Two;
|
||||||
|
|
||||||
|
fn inner();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Yay for Test {
|
||||||
|
type One = i32;
|
||||||
|
|
||||||
|
fn inner() {
|
||||||
|
println!("Mmmm");
|
||||||
|
}
|
||||||
|
|
||||||
|
type Two = u32;
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_weird_nesting() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn test() {
|
||||||
|
mod hello {
|
||||||
|
fn inner() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod hi {$0$0
|
||||||
|
fn inner() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn test() {
|
||||||
|
mod hi {
|
||||||
|
fn inner() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod hello {
|
||||||
|
fn inner() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn handles_empty_file() {
|
||||||
|
check(r#"$0$0"#, expect![[r#""#]], Direction::Up);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1427,6 +1427,25 @@ pub(crate) fn handle_open_cargo_toml(
|
||||||
Ok(Some(res))
|
Ok(Some(res))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_move_item(
|
||||||
|
snap: GlobalStateSnapshot,
|
||||||
|
params: lsp_ext::MoveItemParams,
|
||||||
|
) -> Result<Option<lsp_types::TextDocumentEdit>> {
|
||||||
|
let _p = profile::span("handle_move_item");
|
||||||
|
let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?;
|
||||||
|
let range = from_proto::file_range(&snap, params.text_document, params.range)?;
|
||||||
|
|
||||||
|
let direction = match params.direction {
|
||||||
|
lsp_ext::MoveItemDirection::Up => ide::Direction::Up,
|
||||||
|
lsp_ext::MoveItemDirection::Down => ide::Direction::Down,
|
||||||
|
};
|
||||||
|
|
||||||
|
match snap.analysis.move_item(range, direction)? {
|
||||||
|
Some(text_edit) => Ok(Some(to_proto::text_document_edit(&snap, file_id, text_edit)?)),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn to_command_link(command: lsp_types::Command, tooltip: String) -> lsp_ext::CommandLink {
|
fn to_command_link(command: lsp_types::Command, tooltip: String) -> lsp_ext::CommandLink {
|
||||||
lsp_ext::CommandLink { tooltip: Some(tooltip), command }
|
lsp_ext::CommandLink { tooltip: Some(tooltip), command }
|
||||||
}
|
}
|
||||||
|
|
|
@ -402,3 +402,25 @@ pub(crate) enum CodeLensResolveData {
|
||||||
pub fn supports_utf8(caps: &lsp_types::ClientCapabilities) -> bool {
|
pub fn supports_utf8(caps: &lsp_types::ClientCapabilities) -> bool {
|
||||||
caps.offset_encoding.as_deref().unwrap_or_default().iter().any(|it| it == "utf-8")
|
caps.offset_encoding.as_deref().unwrap_or_default().iter().any(|it| it == "utf-8")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum MoveItem {}
|
||||||
|
|
||||||
|
impl Request for MoveItem {
|
||||||
|
type Params = MoveItemParams;
|
||||||
|
type Result = Option<lsp_types::TextDocumentEdit>;
|
||||||
|
const METHOD: &'static str = "experimental/moveItem";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct MoveItemParams {
|
||||||
|
pub direction: MoveItemDirection,
|
||||||
|
pub text_document: TextDocumentIdentifier,
|
||||||
|
pub range: Range,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub enum MoveItemDirection {
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
}
|
||||||
|
|
|
@ -504,6 +504,7 @@ impl GlobalState {
|
||||||
.on::<lsp_ext::HoverRequest>(handlers::handle_hover)
|
.on::<lsp_ext::HoverRequest>(handlers::handle_hover)
|
||||||
.on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs)
|
.on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs)
|
||||||
.on::<lsp_ext::OpenCargoToml>(handlers::handle_open_cargo_toml)
|
.on::<lsp_ext::OpenCargoToml>(handlers::handle_open_cargo_toml)
|
||||||
|
.on::<lsp_ext::MoveItem>(handlers::handle_move_item)
|
||||||
.on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)
|
.on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)
|
||||||
.on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)
|
.on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)
|
||||||
.on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)
|
.on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)
|
||||||
|
|
|
@ -658,6 +658,18 @@ pub(crate) fn goto_definition_response(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn text_document_edit(
|
||||||
|
snap: &GlobalStateSnapshot,
|
||||||
|
file_id: FileId,
|
||||||
|
edit: TextEdit,
|
||||||
|
) -> Result<lsp_types::TextDocumentEdit> {
|
||||||
|
let text_document = optional_versioned_text_document_identifier(snap, file_id);
|
||||||
|
let line_index = snap.file_line_index(file_id)?;
|
||||||
|
let edits =
|
||||||
|
edit.into_iter().map(|it| lsp_types::OneOf::Left(text_edit(&line_index, it))).collect();
|
||||||
|
Ok(lsp_types::TextDocumentEdit { text_document, edits })
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn snippet_text_document_edit(
|
pub(crate) fn snippet_text_document_edit(
|
||||||
snap: &GlobalStateSnapshot,
|
snap: &GlobalStateSnapshot,
|
||||||
is_snippet: bool,
|
is_snippet: bool,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!---
|
<!---
|
||||||
lsp_ext.rs hash: 4dfa8d7035f4aee7
|
lsp_ext.rs hash: e8a7502bd2b2c2f5
|
||||||
|
|
||||||
If you need to change the above hash to make the test pass, please check if you
|
If you need to change the above hash to make the test pass, please check if you
|
||||||
need to adjust this doc as well and ping this issue:
|
need to adjust this doc as well and ping this issue:
|
||||||
|
@ -595,3 +595,29 @@ interface TestInfo {
|
||||||
runnable: Runnable;
|
runnable: Runnable;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Hover Actions
|
||||||
|
|
||||||
|
**Issue:** https://github.com/rust-analyzer/rust-analyzer/issues/6823
|
||||||
|
|
||||||
|
This request is sent from client to server to move item under cursor or selection in some direction.
|
||||||
|
|
||||||
|
**Method:** `experimental/moveItemUp`
|
||||||
|
**Method:** `experimental/moveItemDown`
|
||||||
|
|
||||||
|
**Request:** `MoveItemParams`
|
||||||
|
|
||||||
|
**Response:** `TextDocumentEdit | null`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface MoveItemParams {
|
||||||
|
textDocument: lc.TextDocumentIdentifier,
|
||||||
|
range: lc.Range,
|
||||||
|
direction: Direction
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum Direction {
|
||||||
|
Up = "Up",
|
||||||
|
Down = "Down"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -208,6 +208,16 @@
|
||||||
"command": "rust-analyzer.peekTests",
|
"command": "rust-analyzer.peekTests",
|
||||||
"title": "Peek related tests",
|
"title": "Peek related tests",
|
||||||
"category": "Rust Analyzer"
|
"category": "Rust Analyzer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "rust-analyzer.moveItemUp",
|
||||||
|
"title": "Move item up",
|
||||||
|
"category": "Rust Analyzer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "rust-analyzer.moveItemDown",
|
||||||
|
"title": "Move item down",
|
||||||
|
"category": "Rust Analyzer"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"keybindings": [
|
"keybindings": [
|
||||||
|
|
|
@ -134,6 +134,51 @@ export function joinLines(ctx: Ctx): Cmd {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function moveItemUp(ctx: Ctx): Cmd {
|
||||||
|
return moveItem(ctx, ra.Direction.Up);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function moveItemDown(ctx: Ctx): Cmd {
|
||||||
|
return moveItem(ctx, ra.Direction.Down);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function moveItem(ctx: Ctx, direction: ra.Direction): Cmd {
|
||||||
|
return async () => {
|
||||||
|
const editor = ctx.activeRustEditor;
|
||||||
|
const client = ctx.client;
|
||||||
|
if (!editor || !client) return;
|
||||||
|
|
||||||
|
const edit = await client.sendRequest(ra.moveItem, {
|
||||||
|
range: client.code2ProtocolConverter.asRange(editor.selection),
|
||||||
|
textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
|
||||||
|
direction
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!edit) return;
|
||||||
|
|
||||||
|
let cursor: vscode.Position | null = null;
|
||||||
|
|
||||||
|
await editor.edit((builder) => {
|
||||||
|
client.protocol2CodeConverter.asTextEdits(edit.edits).forEach((edit: any) => {
|
||||||
|
builder.replace(edit.range, edit.newText);
|
||||||
|
|
||||||
|
if (direction === ra.Direction.Up) {
|
||||||
|
if (!cursor || edit.range.end.isBeforeOrEqual(cursor)) {
|
||||||
|
cursor = edit.range.end;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!cursor || edit.range.end.isAfterOrEqual(cursor)) {
|
||||||
|
cursor = edit.range.end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).then(() => {
|
||||||
|
const newPosition = cursor ?? editor.selection.start;
|
||||||
|
editor.selection = new vscode.Selection(newPosition, newPosition);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function onEnter(ctx: Ctx): Cmd {
|
export function onEnter(ctx: Ctx): Cmd {
|
||||||
async function handleKeypress() {
|
async function handleKeypress() {
|
||||||
const editor = ctx.activeRustEditor;
|
const editor = ctx.activeRustEditor;
|
||||||
|
|
|
@ -127,3 +127,16 @@ export const openCargoToml = new lc.RequestType<OpenCargoTomlParams, lc.Location
|
||||||
export interface OpenCargoTomlParams {
|
export interface OpenCargoTomlParams {
|
||||||
textDocument: lc.TextDocumentIdentifier;
|
textDocument: lc.TextDocumentIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const moveItem = new lc.RequestType<MoveItemParams, lc.TextDocumentEdit | void, void>("experimental/moveItem");
|
||||||
|
|
||||||
|
export interface MoveItemParams {
|
||||||
|
textDocument: lc.TextDocumentIdentifier;
|
||||||
|
range: lc.Range;
|
||||||
|
direction: Direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enum Direction {
|
||||||
|
Up = "Up",
|
||||||
|
Down = "Down"
|
||||||
|
}
|
||||||
|
|
|
@ -114,6 +114,8 @@ async function tryActivate(context: vscode.ExtensionContext) {
|
||||||
ctx.registerCommand('openDocs', commands.openDocs);
|
ctx.registerCommand('openDocs', commands.openDocs);
|
||||||
ctx.registerCommand('openCargoToml', commands.openCargoToml);
|
ctx.registerCommand('openCargoToml', commands.openCargoToml);
|
||||||
ctx.registerCommand('peekTests', commands.peekTests);
|
ctx.registerCommand('peekTests', commands.peekTests);
|
||||||
|
ctx.registerCommand('moveItemUp', commands.moveItemUp);
|
||||||
|
ctx.registerCommand('moveItemDown', commands.moveItemDown);
|
||||||
|
|
||||||
defaultOnEnter.dispose();
|
defaultOnEnter.dispose();
|
||||||
ctx.registerCommand('onEnter', commands.onEnter);
|
ctx.registerCommand('onEnter', commands.onEnter);
|
||||||
|
|
Loading…
Reference in a new issue