Skip to content

Commit 9cbf09e

Browse files
committed
rewrite merge use trees assist to use muatable syntax trees
changelog internal
1 parent d834306 commit 9cbf09e

File tree

7 files changed

+118
-85
lines changed

7 files changed

+118
-85
lines changed

crates/ide_assists/src/handlers/merge_imports.rs

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
use ide_db::helpers::insert_use::{try_merge_imports, try_merge_trees, MergeBehavior};
2-
use syntax::{
3-
algo::{neighbor, SyntaxRewriter},
4-
ast, AstNode,
5-
};
2+
use syntax::{algo::neighbor, ast, ted, AstNode};
63

74
use crate::{
85
assist_context::{AssistContext, Assists},
@@ -24,33 +21,29 @@ use crate::{
2421
// ```
2522
pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
2623
let tree: ast::UseTree = ctx.find_node_at_offset()?;
27-
let mut rewriter = SyntaxRewriter::default();
24+
let original_parent = tree.syntax().ancestors().last()?;
25+
26+
let tree = tree.clone_for_update();
27+
let new_parent = tree.syntax().ancestors().last()?;
28+
2829
let mut offset = ctx.offset();
2930

31+
let mut imports = None;
32+
let mut uses = None;
3033
if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) {
31-
let (merged, to_delete) =
34+
let (merged, to_remove) =
3235
next_prev().filter_map(|dir| neighbor(&use_item, dir)).find_map(|use_item2| {
3336
try_merge_imports(&use_item, &use_item2, MergeBehavior::Full).zip(Some(use_item2))
3437
})?;
3538

36-
rewriter.replace_ast(&use_item, &merged);
37-
rewriter += to_delete.remove();
38-
39-
if to_delete.syntax().text_range().end() < offset {
40-
offset -= to_delete.syntax().text_range().len();
41-
}
39+
imports = Some((use_item, merged, to_remove));
4240
} else {
43-
let (merged, to_delete) =
41+
let (merged, to_remove) =
4442
next_prev().filter_map(|dir| neighbor(&tree, dir)).find_map(|use_tree| {
4543
try_merge_trees(&tree, &use_tree, MergeBehavior::Full).zip(Some(use_tree))
4644
})?;
4745

48-
rewriter.replace_ast(&tree, &merged);
49-
rewriter += to_delete.remove();
50-
51-
if to_delete.syntax().text_range().end() < offset {
52-
offset -= to_delete.syntax().text_range().len();
53-
}
46+
uses = Some((tree.clone(), merged, to_remove))
5447
};
5548

5649
let target = tree.syntax().text_range();
@@ -59,7 +52,23 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
5952
"Merge imports",
6053
target,
6154
|builder| {
62-
builder.rewrite(rewriter);
55+
if let Some((to_replace, replacement, to_remove)) = imports {
56+
if to_remove.syntax().text_range().end() < offset {
57+
offset -= to_remove.syntax().text_range().len();
58+
}
59+
ted::replace(to_replace.syntax().clone(), replacement.syntax().clone());
60+
to_remove.remove();
61+
}
62+
63+
if let Some((to_replace, replacement, to_remove)) = uses {
64+
if to_remove.syntax().text_range().end() < offset {
65+
offset -= to_remove.syntax().text_range().len();
66+
}
67+
ted::replace(to_replace.syntax().clone(), replacement.syntax().clone());
68+
to_remove.remove()
69+
}
70+
71+
builder.replace(original_parent.text_range(), new_parent.to_string())
6372
},
6473
)
6574
}

crates/ide_assists/src/handlers/unmerge_use.rs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use syntax::{
2-
algo::SyntaxRewriter,
3-
ast::{self, edit::AstNodeEdit, VisibilityOwner},
2+
ast::{self, VisibilityOwner},
3+
ted::{self, Position},
44
AstNode, SyntaxKind,
55
};
66

@@ -22,7 +22,7 @@ use crate::{
2222
// use std::fmt::Display;
2323
// ```
2424
pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25-
let tree: ast::UseTree = ctx.find_node_at_offset()?;
25+
let tree: ast::UseTree = ctx.find_node_at_offset::<ast::UseTree>()?.clone_for_update();
2626

2727
let tree_list = tree.syntax().parent().and_then(ast::UseTreeList::cast)?;
2828
if tree_list.use_trees().count() < 2 {
@@ -33,6 +33,9 @@ pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
3333
let use_: ast::Use = tree_list.syntax().ancestors().find_map(ast::Use::cast)?;
3434
let path = resolve_full_path(&tree)?;
3535

36+
let old_parent_range = use_.syntax().parent()?.text_range();
37+
let new_parent = use_.syntax().parent()?;
38+
3639
let target = tree.syntax().text_range();
3740
acc.add(
3841
AssistId("unmerge_use", AssistKind::RefactorRewrite),
@@ -47,20 +50,13 @@ pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
4750
tree.rename(),
4851
tree.star_token().is_some(),
4952
),
50-
);
51-
52-
let mut rewriter = SyntaxRewriter::default();
53-
rewriter += tree.remove();
54-
rewriter.insert_after(use_.syntax(), &ast::make::tokens::single_newline());
55-
if let ident_level @ 1..=usize::MAX = use_.indent_level().0 as usize {
56-
rewriter.insert_after(
57-
use_.syntax(),
58-
&ast::make::tokens::whitespace(&" ".repeat(4 * ident_level)),
59-
);
60-
}
61-
rewriter.insert_after(use_.syntax(), new_use.syntax());
62-
63-
builder.rewrite(rewriter);
53+
)
54+
.clone_for_update();
55+
56+
tree.remove();
57+
ted::insert(Position::after(use_.syntax()), new_use.syntax());
58+
59+
builder.replace(old_parent_range, new_parent.to_string());
6460
},
6561
)
6662
}

crates/ide_db/src/helpers/insert_use.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ pub fn try_merge_imports(
213213
let lhs_tree = lhs.use_tree()?;
214214
let rhs_tree = rhs.use_tree()?;
215215
let merged = try_merge_trees(&lhs_tree, &rhs_tree, merge_behavior)?;
216-
Some(lhs.with_use_tree(merged))
216+
Some(lhs.with_use_tree(merged).clone_for_update())
217217
}
218218

219219
pub fn try_merge_trees(
@@ -234,7 +234,7 @@ pub fn try_merge_trees(
234234
} else {
235235
(lhs.split_prefix(&lhs_prefix), rhs.split_prefix(&rhs_prefix))
236236
};
237-
recursive_merge(&lhs, &rhs, merge)
237+
recursive_merge(&lhs, &rhs, merge).map(|it| it.clone_for_update())
238238
}
239239

240240
/// Recursively "zips" together lhs and rhs.

crates/syntax/src/ast/edit.rs

Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::{
99
use arrayvec::ArrayVec;
1010

1111
use crate::{
12-
algo::{self, neighbor, SyntaxRewriter},
12+
algo::{self, SyntaxRewriter},
1313
ast::{
1414
self,
1515
make::{self, tokens},
@@ -322,27 +322,6 @@ impl ast::Use {
322322
}
323323
self.clone()
324324
}
325-
326-
pub fn remove(&self) -> SyntaxRewriter<'static> {
327-
let mut res = SyntaxRewriter::default();
328-
res.delete(self.syntax());
329-
let next_ws = self
330-
.syntax()
331-
.next_sibling_or_token()
332-
.and_then(|it| it.into_token())
333-
.and_then(ast::Whitespace::cast);
334-
if let Some(next_ws) = next_ws {
335-
let ws_text = next_ws.syntax().text();
336-
if let Some(rest) = ws_text.strip_prefix('\n') {
337-
if rest.is_empty() {
338-
res.delete(next_ws.syntax())
339-
} else {
340-
res.replace(next_ws.syntax(), &make::tokens::whitespace(rest));
341-
}
342-
}
343-
}
344-
res
345-
}
346325
}
347326

348327
impl ast::UseTree {
@@ -396,22 +375,6 @@ impl ast::UseTree {
396375
Some(res)
397376
}
398377
}
399-
400-
pub fn remove(&self) -> SyntaxRewriter<'static> {
401-
let mut res = SyntaxRewriter::default();
402-
res.delete(self.syntax());
403-
for &dir in [Direction::Next, Direction::Prev].iter() {
404-
if let Some(nb) = neighbor(self, dir) {
405-
self.syntax()
406-
.siblings_with_tokens(dir)
407-
.skip(1)
408-
.take_while(|it| it.as_node() != Some(nb.syntax()))
409-
.for_each(|el| res.delete(&el));
410-
return res;
411-
}
412-
}
413-
res
414-
}
415378
}
416379

417380
impl ast::MatchArmList {
@@ -592,6 +555,13 @@ impl ops::Add<u8> for IndentLevel {
592555
}
593556

594557
impl IndentLevel {
558+
pub fn from_element(element: &SyntaxElement) -> IndentLevel {
559+
match element {
560+
rowan::NodeOrToken::Node(it) => IndentLevel::from_node(it),
561+
rowan::NodeOrToken::Token(it) => IndentLevel::from_token(it),
562+
}
563+
}
564+
595565
pub fn from_node(node: &SyntaxNode) -> IndentLevel {
596566
match node.first_token() {
597567
Some(it) => Self::from_token(&it),

crates/syntax/src/ast/edit_in_place.rs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
33
use std::iter::empty;
44

5-
use ast::{edit::AstNodeEdit, make, GenericParamsOwner, WhereClause};
65
use parser::T;
76

87
use crate::{
9-
ast,
8+
algo::neighbor,
9+
ast::{self, edit::AstNodeEdit, make, GenericParamsOwner, WhereClause},
1010
ted::{self, Position},
11-
AstNode, Direction,
11+
AstNode, AstToken, Direction,
1212
};
1313

1414
use super::NameOwner;
@@ -126,3 +126,41 @@ impl ast::TypeBoundList {
126126
}
127127
}
128128
}
129+
130+
impl ast::UseTree {
131+
pub fn remove(&self) {
132+
for &dir in [Direction::Next, Direction::Prev].iter() {
133+
if let Some(next_use_tree) = neighbor(self, dir) {
134+
let separators = self
135+
.syntax()
136+
.siblings_with_tokens(dir)
137+
.skip(1)
138+
.take_while(|it| it.as_node() != Some(next_use_tree.syntax()));
139+
ted::remove_all_iter(separators);
140+
break;
141+
}
142+
}
143+
ted::remove(self.syntax())
144+
}
145+
}
146+
147+
impl ast::Use {
148+
pub fn remove(&self) {
149+
let next_ws = self
150+
.syntax()
151+
.next_sibling_or_token()
152+
.and_then(|it| it.into_token())
153+
.and_then(ast::Whitespace::cast);
154+
if let Some(next_ws) = next_ws {
155+
let ws_text = next_ws.syntax().text();
156+
if let Some(rest) = ws_text.strip_prefix('\n') {
157+
if rest.is_empty() {
158+
ted::remove(next_ws.syntax())
159+
} else {
160+
ted::replace(next_ws.syntax(), make::tokens::whitespace(rest))
161+
}
162+
}
163+
}
164+
ted::remove(self.syntax())
165+
}
166+
}

crates/syntax/src/ast/make.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ pub mod tokens {
560560
pub fn whitespace(text: &str) -> SyntaxToken {
561561
assert!(text.trim().is_empty());
562562
let sf = SourceFile::parse(text).ok().unwrap();
563-
sf.syntax().first_child_or_token().unwrap().into_token().unwrap()
563+
sf.syntax().clone_for_update().first_child_or_token().unwrap().into_token().unwrap()
564564
}
565565

566566
pub fn doc_comment(text: &str) -> SyntaxToken {

crates/syntax/src/ted.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
//!
33
//! The `_raw`-suffixed functions insert elements as is, unsuffixed versions fix
44
//! up elements around the edges.
5-
use std::ops::RangeInclusive;
5+
use std::{mem, ops::RangeInclusive};
66

77
use parser::T;
88

9-
use crate::{ast::make, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken};
9+
use crate::{
10+
ast::{edit::IndentLevel, make},
11+
SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken,
12+
};
1013

1114
/// Utility trait to allow calling `ted` functions with references or owned
1215
/// nodes. Do not use outside of this module.
@@ -101,12 +104,25 @@ pub fn insert_all_raw(position: Position, elements: Vec<SyntaxElement>) {
101104
}
102105

103106
pub fn remove(elem: impl Element) {
104-
let elem = elem.syntax_element();
105-
remove_all(elem.clone()..=elem)
107+
elem.syntax_element().detach()
106108
}
107109
pub fn remove_all(range: RangeInclusive<SyntaxElement>) {
108110
replace_all(range, Vec::new())
109111
}
112+
pub fn remove_all_iter(range: impl IntoIterator<Item = SyntaxElement>) {
113+
let mut it = range.into_iter();
114+
if let Some(mut first) = it.next() {
115+
match it.last() {
116+
Some(mut last) => {
117+
if first.index() > last.index() {
118+
mem::swap(&mut first, &mut last)
119+
}
120+
remove_all(first..=last)
121+
}
122+
None => remove(first),
123+
}
124+
}
125+
}
110126

111127
pub fn replace(old: impl Element, new: impl Element) {
112128
let old = old.syntax_element();
@@ -149,5 +165,9 @@ fn ws_between(left: &SyntaxElement, right: &SyntaxElement) -> Option<SyntaxToken
149165
if right.kind() == T![;] || right.kind() == T![,] {
150166
return None;
151167
}
168+
if right.kind() == SyntaxKind::USE {
169+
let indent = IndentLevel::from_element(left);
170+
return Some(make::tokens::whitespace(&format!("\n{}", indent)));
171+
}
152172
Some(make::tokens::single_space())
153173
}

0 commit comments

Comments
 (0)