mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-28 04:44:57 +00:00
Generalise syntax rewriting infrastructure to allow removal of nodes
This commit is contained in:
parent
3bd119a4c1
commit
062f6e3bbe
7 changed files with 253 additions and 122 deletions
|
@ -11,6 +11,7 @@ use ra_syntax::{
|
||||||
use ra_text_edit::TextEditBuilder;
|
use ra_text_edit::TextEditBuilder;
|
||||||
|
|
||||||
use crate::{AssistAction, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
|
use crate::{AssistAction, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
|
||||||
|
use algo::SyntaxRewriter;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct Assist(pub(crate) Vec<AssistInfo>);
|
pub(crate) struct Assist(pub(crate) Vec<AssistInfo>);
|
||||||
|
@ -234,6 +235,11 @@ impl ActionBuilder {
|
||||||
pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
|
pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
|
||||||
algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
|
algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
|
||||||
}
|
}
|
||||||
|
pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
|
||||||
|
let node = rewriter.rewrite_root().unwrap();
|
||||||
|
let new = rewriter.rewrite(&node);
|
||||||
|
algo::diff(&node, &new).into_text_edit(&mut self.edit)
|
||||||
|
}
|
||||||
|
|
||||||
fn build(self) -> AssistAction {
|
fn build(self) -> AssistAction {
|
||||||
AssistAction {
|
AssistAction {
|
||||||
|
|
|
@ -3,7 +3,10 @@ use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use hir::{PathResolution, SemanticsScope};
|
use hir::{PathResolution, SemanticsScope};
|
||||||
use ra_ide_db::RootDatabase;
|
use ra_ide_db::RootDatabase;
|
||||||
use ra_syntax::ast::{self, AstNode};
|
use ra_syntax::{
|
||||||
|
algo::SyntaxRewriter,
|
||||||
|
ast::{self, AstNode},
|
||||||
|
};
|
||||||
|
|
||||||
pub trait AstTransform<'a> {
|
pub trait AstTransform<'a> {
|
||||||
fn get_substitution(&self, node: &ra_syntax::SyntaxNode) -> Option<ra_syntax::SyntaxNode>;
|
fn get_substitution(&self, node: &ra_syntax::SyntaxNode) -> Option<ra_syntax::SyntaxNode>;
|
||||||
|
@ -153,15 +156,14 @@ impl<'a> QualifyPaths<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
|
pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
|
||||||
let syntax = node.syntax();
|
SyntaxRewriter::from_fn(|element| match element {
|
||||||
let result = ra_syntax::algo::replace_descendants(syntax, |element| match element {
|
|
||||||
ra_syntax::SyntaxElement::Node(n) => {
|
ra_syntax::SyntaxElement::Node(n) => {
|
||||||
let replacement = transformer.get_substitution(&n)?;
|
let replacement = transformer.get_substitution(&n)?;
|
||||||
Some(replacement.into())
|
Some(replacement.into())
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
})
|
||||||
N::cast(result).unwrap()
|
.rewrite_ast(&node)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> AstTransform<'a> for QualifyPaths<'a> {
|
impl<'a> AstTransform<'a> for QualifyPaths<'a> {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use std::iter::successors;
|
use std::iter::successors;
|
||||||
|
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
algo::neighbor,
|
algo::{neighbor, SyntaxRewriter},
|
||||||
ast::{self, edit::AstNodeEdit, make},
|
ast::{self, edit::AstNodeEdit, make},
|
||||||
AstNode, AstToken, Direction, InsertPosition, SyntaxElement, TextRange, T,
|
AstNode, Direction, InsertPosition, SyntaxElement, T,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Assist, AssistCtx, AssistId};
|
use crate::{Assist, AssistCtx, AssistId};
|
||||||
|
@ -22,9 +22,10 @@ use crate::{Assist, AssistCtx, AssistId};
|
||||||
// ```
|
// ```
|
||||||
pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> {
|
pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> {
|
||||||
let tree: ast::UseTree = ctx.find_node_at_offset()?;
|
let tree: ast::UseTree = ctx.find_node_at_offset()?;
|
||||||
let (new_tree, to_delete) = if let Some(use_item) =
|
let mut rewriter = SyntaxRewriter::default();
|
||||||
tree.syntax().parent().and_then(ast::UseItem::cast)
|
let mut offset = ctx.frange.range.start();
|
||||||
{
|
|
||||||
|
if let Some(use_item) = tree.syntax().parent().and_then(ast::UseItem::cast) {
|
||||||
let (merged, to_delete) = next_prev()
|
let (merged, to_delete) = next_prev()
|
||||||
.filter_map(|dir| neighbor(&use_item, dir))
|
.filter_map(|dir| neighbor(&use_item, dir))
|
||||||
.filter_map(|it| Some((it.clone(), it.use_tree()?)))
|
.filter_map(|it| Some((it.clone(), it.use_tree()?)))
|
||||||
|
@ -32,42 +33,28 @@ pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> {
|
||||||
Some((try_merge_trees(&tree, &use_tree)?, use_item.clone()))
|
Some((try_merge_trees(&tree, &use_tree)?, use_item.clone()))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut range = to_delete.syntax().text_range();
|
rewriter.replace_ast(&tree, &merged);
|
||||||
let next_ws = to_delete
|
rewriter += to_delete.remove();
|
||||||
.syntax()
|
|
||||||
.next_sibling_or_token()
|
if to_delete.syntax().text_range().end() < offset {
|
||||||
.and_then(|it| it.into_token())
|
offset -= to_delete.syntax().text_range().len();
|
||||||
.and_then(ast::Whitespace::cast);
|
|
||||||
if let Some(ws) = next_ws {
|
|
||||||
range = range.extend_to(&ws.syntax().text_range())
|
|
||||||
}
|
}
|
||||||
(merged, range)
|
|
||||||
} else {
|
} else {
|
||||||
let (merged, to_delete) = next_prev()
|
let (merged, to_delete) = next_prev()
|
||||||
.filter_map(|dir| neighbor(&tree, dir))
|
.filter_map(|dir| neighbor(&tree, dir))
|
||||||
.find_map(|use_tree| Some((try_merge_trees(&tree, &use_tree)?, use_tree.clone())))?;
|
.find_map(|use_tree| Some((try_merge_trees(&tree, &use_tree)?, use_tree.clone())))?;
|
||||||
|
|
||||||
let mut range = to_delete.syntax().text_range();
|
rewriter.replace_ast(&tree, &merged);
|
||||||
if let Some((dir, nb)) = next_prev().find_map(|dir| Some((dir, neighbor(&to_delete, dir)?)))
|
rewriter += to_delete.remove();
|
||||||
{
|
|
||||||
let nb_range = nb.syntax().text_range();
|
if to_delete.syntax().text_range().end() < offset {
|
||||||
if dir == Direction::Prev {
|
offset -= to_delete.syntax().text_range().len();
|
||||||
range = TextRange::from_to(nb_range.end(), range.end());
|
|
||||||
} else {
|
|
||||||
range = TextRange::from_to(range.start(), nb_range.start());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
(merged, range)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut offset = ctx.frange.range.start();
|
|
||||||
ctx.add_assist(AssistId("merge_imports"), "Merge imports", |edit| {
|
ctx.add_assist(AssistId("merge_imports"), "Merge imports", |edit| {
|
||||||
edit.replace_ast(tree, new_tree);
|
edit.rewrite(rewriter);
|
||||||
edit.delete(to_delete);
|
// FIXME: we only need because our diff is imprecise
|
||||||
|
|
||||||
if to_delete.end() <= offset {
|
|
||||||
offset -= to_delete.len();
|
|
||||||
}
|
|
||||||
edit.set_cursor(offset);
|
edit.set_cursor(offset);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -156,7 +143,7 @@ use std::fmt::Debug;
|
||||||
use std::fmt<|>::Display;
|
use std::fmt<|>::Display;
|
||||||
",
|
",
|
||||||
r"
|
r"
|
||||||
use std::fmt<|>::{Display, Debug};
|
use std::fmt:<|>:{Display, Debug};
|
||||||
",
|
",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -178,7 +165,7 @@ use std::{fmt<|>::{Debug, Display}};
|
||||||
use std::{fmt::Debug, fmt<|>::Display};
|
use std::{fmt::Debug, fmt<|>::Display};
|
||||||
",
|
",
|
||||||
r"
|
r"
|
||||||
use std::{fmt<|>::{Display, Debug}};
|
use std::{fmt::<|>{Display, Debug}};
|
||||||
",
|
",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -197,6 +184,38 @@ use foo::baz;
|
||||||
use foo<|>::{bar, baz};
|
use foo<|>::{bar, baz};
|
||||||
|
|
||||||
/// Doc comment
|
/// Doc comment
|
||||||
|
",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn works_with_trailing_comma() {
|
||||||
|
check_assist(
|
||||||
|
merge_imports,
|
||||||
|
r"
|
||||||
|
use {
|
||||||
|
foo<|>::bar,
|
||||||
|
foo::baz,
|
||||||
|
};
|
||||||
|
",
|
||||||
|
r"
|
||||||
|
use {
|
||||||
|
foo<|>::{bar, baz},
|
||||||
|
};
|
||||||
|
",
|
||||||
|
);
|
||||||
|
check_assist(
|
||||||
|
merge_imports,
|
||||||
|
r"
|
||||||
|
use {
|
||||||
|
foo::baz,
|
||||||
|
foo<|>::bar,
|
||||||
|
};
|
||||||
|
",
|
||||||
|
r"
|
||||||
|
use {
|
||||||
|
foo::{bar<|>, baz},
|
||||||
|
};
|
||||||
",
|
",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,8 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use ra_parser::FragmentKind;
|
use ra_parser::FragmentKind;
|
||||||
use ra_syntax::{algo::replace_descendants, SyntaxElement, SyntaxNode};
|
use ra_syntax::{algo::SyntaxRewriter, SyntaxNode};
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub fn expand_eager_macro(
|
pub fn expand_eager_macro(
|
||||||
db: &dyn AstDatabase,
|
db: &dyn AstDatabase,
|
||||||
|
@ -95,10 +95,10 @@ fn eager_macro_recur(
|
||||||
curr: InFile<SyntaxNode>,
|
curr: InFile<SyntaxNode>,
|
||||||
macro_resolver: &dyn Fn(ast::Path) -> Option<MacroDefId>,
|
macro_resolver: &dyn Fn(ast::Path) -> Option<MacroDefId>,
|
||||||
) -> Option<SyntaxNode> {
|
) -> Option<SyntaxNode> {
|
||||||
let mut original = curr.value.clone();
|
let original = curr.value.clone();
|
||||||
|
|
||||||
let children = curr.value.descendants().filter_map(ast::MacroCall::cast);
|
let children = curr.value.descendants().filter_map(ast::MacroCall::cast);
|
||||||
let mut replaces: HashMap<SyntaxElement, SyntaxElement> = HashMap::default();
|
let mut rewriter = SyntaxRewriter::default();
|
||||||
|
|
||||||
// Collect replacement
|
// Collect replacement
|
||||||
for child in children {
|
for child in children {
|
||||||
|
@ -119,12 +119,9 @@ fn eager_macro_recur(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
replaces.insert(child.syntax().clone().into(), insert.into());
|
rewriter.replace(child.syntax(), &insert);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !replaces.is_empty() {
|
let res = rewriter.rewrite(&original);
|
||||||
original = replace_descendants(&original, |n| replaces.get(n).cloned());
|
Some(res)
|
||||||
}
|
|
||||||
|
|
||||||
Some(original)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,9 @@
|
||||||
use hir::Semantics;
|
use hir::Semantics;
|
||||||
use ra_ide_db::RootDatabase;
|
use ra_ide_db::RootDatabase;
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
algo::{find_node_at_offset, replace_descendants},
|
algo::{find_node_at_offset, SyntaxRewriter},
|
||||||
ast, AstNode, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, WalkEvent, T,
|
ast, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent, T,
|
||||||
};
|
};
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
|
|
||||||
use crate::FilePosition;
|
use crate::FilePosition;
|
||||||
|
|
||||||
|
@ -37,7 +36,7 @@ fn expand_macro_recur(
|
||||||
let mut expanded = sema.expand(macro_call)?;
|
let mut expanded = sema.expand(macro_call)?;
|
||||||
|
|
||||||
let children = expanded.descendants().filter_map(ast::MacroCall::cast);
|
let children = expanded.descendants().filter_map(ast::MacroCall::cast);
|
||||||
let mut replaces: FxHashMap<SyntaxElement, SyntaxElement> = FxHashMap::default();
|
let mut rewriter = SyntaxRewriter::default();
|
||||||
|
|
||||||
for child in children.into_iter() {
|
for child in children.into_iter() {
|
||||||
if let Some(new_node) = expand_macro_recur(sema, &child) {
|
if let Some(new_node) = expand_macro_recur(sema, &child) {
|
||||||
|
@ -47,12 +46,13 @@ fn expand_macro_recur(
|
||||||
if expanded == *child.syntax() {
|
if expanded == *child.syntax() {
|
||||||
expanded = new_node;
|
expanded = new_node;
|
||||||
} else {
|
} else {
|
||||||
replaces.insert(child.syntax().clone().into(), new_node.into());
|
rewriter.replace(child.syntax(), &new_node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(replace_descendants(&expanded, |n| replaces.get(n).cloned()))
|
let res = rewriter.rewrite(&expanded);
|
||||||
|
Some(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: It would also be cool to share logic here and in the mbe tests,
|
// FIXME: It would also be cool to share logic here and in the mbe tests,
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
//! FIXME: write short doc here
|
//! FIXME: write short doc here
|
||||||
|
|
||||||
use std::ops::RangeInclusive;
|
use std::{
|
||||||
|
fmt,
|
||||||
|
ops::{self, RangeInclusive},
|
||||||
|
};
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ra_text_edit::TextEditBuilder;
|
use ra_text_edit::TextEditBuilder;
|
||||||
|
@ -222,42 +225,119 @@ fn _replace_children(
|
||||||
with_children(parent, new_children)
|
with_children(parent, new_children)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct SyntaxRewriter<'a> {
|
||||||
|
f: Option<Box<dyn Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a>>,
|
||||||
|
//FIXME: add debug_assertions that all elements are in fact from the same file.
|
||||||
|
replacements: FxHashMap<SyntaxElement, Replacement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for SyntaxRewriter<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("SyntaxRewriter").field("replacements", &self.replacements).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SyntaxRewriter<'a> {
|
||||||
|
pub fn from_fn(f: impl Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a) -> SyntaxRewriter<'a> {
|
||||||
|
SyntaxRewriter { f: Some(Box::new(f)), replacements: FxHashMap::default() }
|
||||||
|
}
|
||||||
|
pub fn delete<T: Clone + Into<SyntaxElement>>(&mut self, what: &T) {
|
||||||
|
let what = what.clone().into();
|
||||||
|
let replacement = Replacement::Delete;
|
||||||
|
self.replacements.insert(what, replacement);
|
||||||
|
}
|
||||||
|
pub fn replace<T: Clone + Into<SyntaxElement>>(&mut self, what: &T, with: &T) {
|
||||||
|
let what = what.clone().into();
|
||||||
|
let replacement = Replacement::Single(with.clone().into());
|
||||||
|
self.replacements.insert(what, replacement);
|
||||||
|
}
|
||||||
|
pub fn replace_ast<T: AstNode>(&mut self, what: &T, with: &T) {
|
||||||
|
self.replace(what.syntax(), with.syntax())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rewrite(&self, node: &SyntaxNode) -> SyntaxNode {
|
||||||
|
if self.f.is_none() && self.replacements.is_empty() {
|
||||||
|
return node.clone();
|
||||||
|
}
|
||||||
|
self.rewrite_children(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rewrite_ast<N: AstNode>(self, node: &N) -> N {
|
||||||
|
N::cast(self.rewrite(node.syntax())).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rewrite_root(&self) -> Option<SyntaxNode> {
|
||||||
|
assert!(self.f.is_none());
|
||||||
|
self.replacements
|
||||||
|
.keys()
|
||||||
|
.map(|element| match element {
|
||||||
|
SyntaxElement::Node(it) => it.clone(),
|
||||||
|
SyntaxElement::Token(it) => it.parent(),
|
||||||
|
})
|
||||||
|
.fold1(|a, b| least_common_ancestor(&a, &b).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replacement(&self, element: &SyntaxElement) -> Option<Replacement> {
|
||||||
|
if let Some(f) = &self.f {
|
||||||
|
assert!(self.replacements.is_empty());
|
||||||
|
return f(element).map(Replacement::Single);
|
||||||
|
}
|
||||||
|
self.replacements.get(element).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rewrite_children(&self, node: &SyntaxNode) -> SyntaxNode {
|
||||||
|
// FIXME: this could be made much faster.
|
||||||
|
let new_children =
|
||||||
|
node.children_with_tokens().flat_map(|it| self.rewrite_self(&it)).collect::<Vec<_>>();
|
||||||
|
with_children(node, new_children)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rewrite_self(
|
||||||
|
&self,
|
||||||
|
element: &SyntaxElement,
|
||||||
|
) -> Option<NodeOrToken<rowan::GreenNode, rowan::GreenToken>> {
|
||||||
|
if let Some(replacement) = self.replacement(&element) {
|
||||||
|
return match replacement {
|
||||||
|
Replacement::Single(NodeOrToken::Node(it)) => {
|
||||||
|
Some(NodeOrToken::Node(it.green().clone()))
|
||||||
|
}
|
||||||
|
Replacement::Single(NodeOrToken::Token(it)) => {
|
||||||
|
Some(NodeOrToken::Token(it.green().clone()))
|
||||||
|
}
|
||||||
|
Replacement::Delete => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let res = match element {
|
||||||
|
NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()),
|
||||||
|
NodeOrToken::Node(it) => NodeOrToken::Node(self.rewrite_children(it).green().clone()),
|
||||||
|
};
|
||||||
|
Some(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ops::AddAssign for SyntaxRewriter<'_> {
|
||||||
|
fn add_assign(&mut self, rhs: SyntaxRewriter) {
|
||||||
|
assert!(rhs.f.is_none());
|
||||||
|
self.replacements.extend(rhs.replacements)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum Replacement {
|
||||||
|
Delete,
|
||||||
|
Single(SyntaxElement),
|
||||||
|
}
|
||||||
|
|
||||||
/// Replaces descendants in the node, according to the mapping.
|
/// Replaces descendants in the node, according to the mapping.
|
||||||
///
|
///
|
||||||
/// This is a type-unsafe low-level editing API, if you need to use it, prefer
|
/// This is a type-unsafe low-level editing API, if you need to use it, prefer
|
||||||
/// to create a type-safe abstraction on top of it instead.
|
/// to create a type-safe abstraction on top of it instead.
|
||||||
pub fn replace_descendants(
|
pub fn _replace_descendants(
|
||||||
parent: &SyntaxNode,
|
parent: &SyntaxNode,
|
||||||
map: impl Fn(&SyntaxElement) -> Option<SyntaxElement>,
|
map: impl Fn(&SyntaxElement) -> Option<SyntaxElement>,
|
||||||
) -> SyntaxNode {
|
) -> SyntaxNode {
|
||||||
_replace_descendants(parent, &map)
|
SyntaxRewriter::from_fn(map).rewrite(parent)
|
||||||
}
|
|
||||||
|
|
||||||
fn _replace_descendants(
|
|
||||||
parent: &SyntaxNode,
|
|
||||||
map: &dyn Fn(&SyntaxElement) -> Option<SyntaxElement>,
|
|
||||||
) -> SyntaxNode {
|
|
||||||
// FIXME: this could be made much faster.
|
|
||||||
let new_children = parent.children_with_tokens().map(|it| go(map, it)).collect::<Vec<_>>();
|
|
||||||
return with_children(parent, new_children);
|
|
||||||
|
|
||||||
fn go(
|
|
||||||
map: &dyn Fn(&SyntaxElement) -> Option<SyntaxElement>,
|
|
||||||
element: SyntaxElement,
|
|
||||||
) -> NodeOrToken<rowan::GreenNode, rowan::GreenToken> {
|
|
||||||
if let Some(replacement) = map(&element) {
|
|
||||||
return match replacement {
|
|
||||||
NodeOrToken::Node(it) => NodeOrToken::Node(it.green().clone()),
|
|
||||||
NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
match element {
|
|
||||||
NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()),
|
|
||||||
NodeOrToken::Node(it) => {
|
|
||||||
NodeOrToken::Node(_replace_descendants(&it, map).green().clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_children(
|
fn with_children(
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
use std::{iter, ops::RangeInclusive};
|
use std::{iter, ops::RangeInclusive};
|
||||||
|
|
||||||
use arrayvec::ArrayVec;
|
use arrayvec::ArrayVec;
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
algo,
|
algo,
|
||||||
|
@ -17,6 +16,7 @@ use crate::{
|
||||||
SyntaxKind::{ATTR, COMMENT, WHITESPACE},
|
SyntaxKind::{ATTR, COMMENT, WHITESPACE},
|
||||||
SyntaxNode, SyntaxToken, T,
|
SyntaxNode, SyntaxToken, T,
|
||||||
};
|
};
|
||||||
|
use algo::{neighbor, SyntaxRewriter};
|
||||||
|
|
||||||
impl ast::BinExpr {
|
impl ast::BinExpr {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
@ -255,6 +255,28 @@ impl ast::UseItem {
|
||||||
}
|
}
|
||||||
self.clone()
|
self.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remove(&self) -> SyntaxRewriter<'static> {
|
||||||
|
let mut res = SyntaxRewriter::default();
|
||||||
|
res.delete(self.syntax());
|
||||||
|
let next_ws = self
|
||||||
|
.syntax()
|
||||||
|
.next_sibling_or_token()
|
||||||
|
.and_then(|it| it.into_token())
|
||||||
|
.and_then(ast::Whitespace::cast);
|
||||||
|
if let Some(next_ws) = next_ws {
|
||||||
|
let ws_text = next_ws.syntax().text();
|
||||||
|
if ws_text.starts_with('\n') {
|
||||||
|
let rest = &ws_text[1..];
|
||||||
|
if rest.is_empty() {
|
||||||
|
res.delete(next_ws.syntax())
|
||||||
|
} else {
|
||||||
|
res.replace(next_ws.syntax(), &make::tokens::whitespace(rest));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ast::UseTree {
|
impl ast::UseTree {
|
||||||
|
@ -293,6 +315,22 @@ impl ast::UseTree {
|
||||||
Some(res)
|
Some(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remove(&self) -> SyntaxRewriter<'static> {
|
||||||
|
let mut res = SyntaxRewriter::default();
|
||||||
|
res.delete(self.syntax());
|
||||||
|
for &dir in [Direction::Next, Direction::Prev].iter() {
|
||||||
|
if let Some(nb) = neighbor(self, dir) {
|
||||||
|
self.syntax()
|
||||||
|
.siblings_with_tokens(dir)
|
||||||
|
.skip(1)
|
||||||
|
.take_while(|it| it.as_node() != Some(nb.syntax()))
|
||||||
|
.for_each(|el| res.delete(&el));
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
@ -343,28 +381,24 @@ impl IndentLevel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _increase_indent(self, node: SyntaxNode) -> SyntaxNode {
|
fn _increase_indent(self, node: SyntaxNode) -> SyntaxNode {
|
||||||
let replacements: FxHashMap<SyntaxElement, SyntaxElement> = node
|
let mut rewriter = SyntaxRewriter::default();
|
||||||
.descendants_with_tokens()
|
node.descendants_with_tokens()
|
||||||
.filter_map(|el| el.into_token())
|
.filter_map(|el| el.into_token())
|
||||||
.filter_map(ast::Whitespace::cast)
|
.filter_map(ast::Whitespace::cast)
|
||||||
.filter(|ws| {
|
.filter(|ws| {
|
||||||
let text = ws.syntax().text();
|
let text = ws.syntax().text();
|
||||||
text.contains('\n')
|
text.contains('\n')
|
||||||
})
|
})
|
||||||
.map(|ws| {
|
.for_each(|ws| {
|
||||||
(
|
let new_ws = make::tokens::whitespace(&format!(
|
||||||
ws.syntax().clone().into(),
|
"{}{:width$}",
|
||||||
make::tokens::whitespace(&format!(
|
ws.syntax().text(),
|
||||||
"{}{:width$}",
|
"",
|
||||||
ws.syntax().text(),
|
width = self.0 as usize * 4
|
||||||
"",
|
));
|
||||||
width = self.0 as usize * 4
|
rewriter.replace(ws.syntax(), &new_ws)
|
||||||
))
|
});
|
||||||
.into(),
|
rewriter.rewrite(&node)
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
algo::replace_descendants(&node, |n| replacements.get(n).cloned())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decrease_indent<N: AstNode>(self, node: N) -> N {
|
pub fn decrease_indent<N: AstNode>(self, node: N) -> N {
|
||||||
|
@ -372,27 +406,21 @@ impl IndentLevel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _decrease_indent(self, node: SyntaxNode) -> SyntaxNode {
|
fn _decrease_indent(self, node: SyntaxNode) -> SyntaxNode {
|
||||||
let replacements: FxHashMap<SyntaxElement, SyntaxElement> = node
|
let mut rewriter = SyntaxRewriter::default();
|
||||||
.descendants_with_tokens()
|
node.descendants_with_tokens()
|
||||||
.filter_map(|el| el.into_token())
|
.filter_map(|el| el.into_token())
|
||||||
.filter_map(ast::Whitespace::cast)
|
.filter_map(ast::Whitespace::cast)
|
||||||
.filter(|ws| {
|
.filter(|ws| {
|
||||||
let text = ws.syntax().text();
|
let text = ws.syntax().text();
|
||||||
text.contains('\n')
|
text.contains('\n')
|
||||||
})
|
})
|
||||||
.map(|ws| {
|
.for_each(|ws| {
|
||||||
(
|
let new_ws = make::tokens::whitespace(
|
||||||
ws.syntax().clone().into(),
|
&ws.syntax().text().replace(&format!("\n{:1$}", "", self.0 as usize * 4), "\n"),
|
||||||
make::tokens::whitespace(
|
);
|
||||||
&ws.syntax()
|
rewriter.replace(ws.syntax(), &new_ws)
|
||||||
.text()
|
});
|
||||||
.replace(&format!("\n{:1$}", "", self.0 as usize * 4), "\n"),
|
rewriter.rewrite(&node)
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
algo::replace_descendants(&node, |n| replacements.get(n).cloned())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,12 +470,11 @@ pub trait AstNodeEdit: AstNode + Sized {
|
||||||
&self,
|
&self,
|
||||||
replacement_map: impl IntoIterator<Item = (D, D)>,
|
replacement_map: impl IntoIterator<Item = (D, D)>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let map = replacement_map
|
let mut rewriter = SyntaxRewriter::default();
|
||||||
.into_iter()
|
for (from, to) in replacement_map {
|
||||||
.map(|(from, to)| (from.syntax().clone().into(), to.syntax().clone().into()))
|
rewriter.replace(from.syntax(), to.syntax())
|
||||||
.collect::<FxHashMap<SyntaxElement, _>>();
|
}
|
||||||
let new_syntax = algo::replace_descendants(self.syntax(), |n| map.get(n).cloned());
|
rewriter.rewrite_ast(self)
|
||||||
Self::cast(new_syntax).unwrap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue