mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-28 17:53:56 +00:00
Improved node lookup algorithm, added more movable nodes
This commit is contained in:
parent
904bdff224
commit
6ef6e4dee1
@ -4,10 +4,12 @@ use hir::Semantics;
|
|||||||
use ide_db::{base_db::FileRange, RootDatabase};
|
use ide_db::{base_db::FileRange, RootDatabase};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
algo, ast, match_ast, AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange,
|
algo, ast, match_ast, AstNode, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange,
|
||||||
|
TokenAtOffset,
|
||||||
};
|
};
|
||||||
use text_edit::{TextEdit, TextEditBuilder};
|
use text_edit::{TextEdit, TextEditBuilder};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
Up,
|
Up,
|
||||||
Down,
|
Down,
|
||||||
@ -31,14 +33,19 @@ pub(crate) fn move_item(
|
|||||||
let sema = Semantics::new(db);
|
let sema = Semantics::new(db);
|
||||||
let file = sema.parse(range.file_id);
|
let file = sema.parse(range.file_id);
|
||||||
|
|
||||||
let item = file.syntax().covering_element(range.range);
|
let item = if range.range.is_empty() {
|
||||||
|
SyntaxElement::Token(pick_best(file.syntax().token_at_offset(range.range.start()))?)
|
||||||
|
} else {
|
||||||
|
file.syntax().covering_element(range.range)
|
||||||
|
};
|
||||||
|
|
||||||
find_ancestors(item, direction, range.range)
|
find_ancestors(item, direction, range.range)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> {
|
fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -> Option<TextEdit> {
|
||||||
let root = match item {
|
let root = match item {
|
||||||
NodeOrToken::Node(node) => node,
|
SyntaxElement::Node(node) => node,
|
||||||
NodeOrToken::Token(token) => token.parent()?,
|
SyntaxElement::Token(token) => token.parent()?,
|
||||||
};
|
};
|
||||||
|
|
||||||
let movable = [
|
let movable = [
|
||||||
@ -51,6 +58,11 @@ fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) -
|
|||||||
SyntaxKind::PARAM,
|
SyntaxKind::PARAM,
|
||||||
SyntaxKind::LET_STMT,
|
SyntaxKind::LET_STMT,
|
||||||
SyntaxKind::EXPR_STMT,
|
SyntaxKind::EXPR_STMT,
|
||||||
|
SyntaxKind::IF_EXPR,
|
||||||
|
SyntaxKind::FOR_EXPR,
|
||||||
|
SyntaxKind::LOOP_EXPR,
|
||||||
|
SyntaxKind::WHILE_EXPR,
|
||||||
|
SyntaxKind::RETURN_EXPR,
|
||||||
SyntaxKind::MATCH_EXPR,
|
SyntaxKind::MATCH_EXPR,
|
||||||
SyntaxKind::MACRO_CALL,
|
SyntaxKind::MACRO_CALL,
|
||||||
SyntaxKind::TYPE_ALIAS,
|
SyntaxKind::TYPE_ALIAS,
|
||||||
@ -83,11 +95,11 @@ fn move_in_direction(
|
|||||||
) -> Option<TextEdit> {
|
) -> Option<TextEdit> {
|
||||||
match_ast! {
|
match_ast! {
|
||||||
match node {
|
match node {
|
||||||
ast::ArgList(it) => swap_sibling_in_list(it.args(), range, direction),
|
ast::ArgList(it) => swap_sibling_in_list(node, it.args(), range, direction),
|
||||||
ast::GenericParamList(it) => swap_sibling_in_list(it.generic_params(), range, direction),
|
ast::GenericParamList(it) => swap_sibling_in_list(node, it.generic_params(), range, direction),
|
||||||
ast::GenericArgList(it) => swap_sibling_in_list(it.generic_args(), range, direction),
|
ast::GenericArgList(it) => swap_sibling_in_list(node, it.generic_args(), range, direction),
|
||||||
ast::VariantList(it) => swap_sibling_in_list(it.variants(), range, direction),
|
ast::VariantList(it) => swap_sibling_in_list(node, it.variants(), range, direction),
|
||||||
ast::TypeBoundList(it) => swap_sibling_in_list(it.bounds(), range, direction),
|
ast::TypeBoundList(it) => swap_sibling_in_list(node, it.bounds(), range, direction),
|
||||||
_ => Some(replace_nodes(node, &match direction {
|
_ => Some(replace_nodes(node, &match direction {
|
||||||
Direction::Up => node.prev_sibling(),
|
Direction::Up => node.prev_sibling(),
|
||||||
Direction::Down => node.next_sibling(),
|
Direction::Down => node.next_sibling(),
|
||||||
@ -97,19 +109,27 @@ fn move_in_direction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>(
|
fn swap_sibling_in_list<A: AstNode + Clone, I: Iterator<Item = A>>(
|
||||||
|
node: &SyntaxNode,
|
||||||
list: I,
|
list: I,
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
) -> Option<TextEdit> {
|
) -> Option<TextEdit> {
|
||||||
let (l, r) = list
|
let list_lookup = list
|
||||||
.tuple_windows()
|
.tuple_windows()
|
||||||
.filter(|(l, r)| match direction {
|
.filter(|(l, r)| match direction {
|
||||||
Direction::Up => r.syntax().text_range().contains_range(range),
|
Direction::Up => r.syntax().text_range().contains_range(range),
|
||||||
Direction::Down => l.syntax().text_range().contains_range(range),
|
Direction::Down => l.syntax().text_range().contains_range(range),
|
||||||
})
|
})
|
||||||
.next()?;
|
.next();
|
||||||
|
|
||||||
|
if let Some((l, r)) = list_lookup {
|
||||||
Some(replace_nodes(l.syntax(), r.syntax()))
|
Some(replace_nodes(l.syntax(), r.syntax()))
|
||||||
|
} else {
|
||||||
|
// Cursor is beyond any movable list item (for example, on curly brace in enum).
|
||||||
|
// It's not necessary, that parent of list is movable (arg list's parent is not, for example),
|
||||||
|
// and we have to continue tree traversal to find suitable node.
|
||||||
|
find_ancestors(SyntaxElement::Node(node.parent()?), direction, range)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit {
|
fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit {
|
||||||
@ -121,6 +141,17 @@ fn replace_nodes(first: &SyntaxNode, second: &SyntaxNode) -> TextEdit {
|
|||||||
edit.finish()
|
edit.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
|
||||||
|
return tokens.max_by_key(priority);
|
||||||
|
fn priority(n: &SyntaxToken) -> usize {
|
||||||
|
match n.kind() {
|
||||||
|
SyntaxKind::IDENT | SyntaxKind::LIFETIME_IDENT => 2,
|
||||||
|
kind if kind.is_trivia() => 0,
|
||||||
|
_ => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::fixture;
|
use crate::fixture;
|
||||||
@ -260,6 +291,107 @@ fn main() {
|
|||||||
expect![[r#"
|
expect![[r#"
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("All I want to say is...");
|
println!("All I want to say is...");
|
||||||
|
println!("Hello, world");
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world");
|
||||||
|
|
||||||
|
if true {
|
||||||
|
println!("Test");
|
||||||
|
}$0$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn main() {
|
||||||
|
if true {
|
||||||
|
println!("Test");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Hello, world");
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world");
|
||||||
|
|
||||||
|
for i in 0..10 {
|
||||||
|
println!("Test");
|
||||||
|
}$0$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn main() {
|
||||||
|
for i in 0..10 {
|
||||||
|
println!("Test");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Hello, world");
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
println!("Test");
|
||||||
|
}$0$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn main() {
|
||||||
|
loop {
|
||||||
|
println!("Test");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Hello, world");
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world");
|
||||||
|
|
||||||
|
while true {
|
||||||
|
println!("Test");
|
||||||
|
}$0$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn main() {
|
||||||
|
while true {
|
||||||
|
println!("Test");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Hello, world");
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world");
|
||||||
|
|
||||||
|
return 123;$0$0
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn main() {
|
||||||
|
return 123;
|
||||||
|
|
||||||
println!("Hello, world");
|
println!("Hello, world");
|
||||||
}
|
}
|
||||||
"#]],
|
"#]],
|
||||||
@ -614,6 +746,115 @@ fn test() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cursor_at_item_start() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
$0$0#[derive(Debug)]
|
||||||
|
enum FooBar {
|
||||||
|
Foo,
|
||||||
|
Bar,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum FooBar {
|
||||||
|
Foo,
|
||||||
|
Bar,
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Down,
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
$0$0enum FooBar {
|
||||||
|
Foo,
|
||||||
|
Bar,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
enum FooBar {
|
||||||
|
Foo,
|
||||||
|
Bar,
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Down,
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
struct Test;
|
||||||
|
|
||||||
|
trait SomeTrait {}
|
||||||
|
|
||||||
|
$0$0impl SomeTrait for Test {}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
struct Test;
|
||||||
|
|
||||||
|
impl SomeTrait for Test {}
|
||||||
|
|
||||||
|
trait SomeTrait {}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cursor_at_item_end() {
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
enum FooBar {
|
||||||
|
Foo,
|
||||||
|
Bar,
|
||||||
|
}$0$0
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
|
enum FooBar {
|
||||||
|
Foo,
|
||||||
|
Bar,
|
||||||
|
}
|
||||||
|
"#]],
|
||||||
|
Direction::Down,
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
r#"
|
||||||
|
struct Test;
|
||||||
|
|
||||||
|
trait SomeTrait {}
|
||||||
|
|
||||||
|
impl SomeTrait for Test {}$0$0
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
struct Test;
|
||||||
|
|
||||||
|
impl SomeTrait for Test {}
|
||||||
|
|
||||||
|
trait SomeTrait {}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
"#]],
|
||||||
|
Direction::Up,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn handles_empty_file() {
|
fn handles_empty_file() {
|
||||||
check(r#"$0$0"#, expect![[r#""#]], Direction::Up);
|
check(r#"$0$0"#, expect![[r#""#]], Direction::Up);
|
||||||
|
Loading…
Reference in New Issue
Block a user