Skip to content

Commit 8eebf17

Browse files
committed
Migrate replace_derive_with_manual_impl to mutable ast
1 parent 18ea09f commit 8eebf17

File tree

1 file changed

+75
-59
lines changed

1 file changed

+75
-59
lines changed

crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs

Lines changed: 75 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@ use hir::{InFile, MacroFileIdExt, ModuleDef};
22
use ide_db::{helpers::mod_path_to_ast, imports::import_assets::NameToImport, items_locator};
33
use itertools::Itertools;
44
use syntax::{
5-
ast::{self, AstNode, HasName},
5+
ast::{self, make, AstNode, HasName},
6+
ted,
67
SyntaxKind::WHITESPACE,
8+
T,
79
};
810

911
use crate::{
1012
assist_context::{AssistContext, Assists, SourceChangeBuilder},
1113
utils::{
12-
add_trait_assoc_items_to_impl, filter_assoc_items, gen_trait_fn_body,
13-
generate_trait_impl_text, render_snippet, Cursor, DefaultMethods, IgnoreAssocItems,
14+
add_trait_assoc_items_to_impl, filter_assoc_items, gen_trait_fn_body, generate_trait_impl,
15+
DefaultMethods, IgnoreAssocItems,
1416
},
1517
AssistId, AssistKind,
1618
};
@@ -132,35 +134,59 @@ fn add_assist(
132134
label,
133135
target,
134136
|builder| {
135-
let insert_pos = adt.syntax().text_range().end();
137+
let insert_after = ted::Position::after(builder.make_mut(adt.clone()).syntax());
138+
136139
let impl_def_with_items =
137140
impl_def_from_trait(&ctx.sema, adt, &annotated_name, trait_, replace_trait_path);
138141
update_attribute(builder, old_derives, old_tree, old_trait_path, attr);
139-
let trait_path = replace_trait_path.to_string();
142+
143+
let trait_path = make::ty_path(replace_trait_path.clone());
144+
140145
match (ctx.config.snippet_cap, impl_def_with_items) {
141146
(None, _) => {
142-
builder.insert(insert_pos, generate_trait_impl_text(adt, &trait_path, ""))
147+
let impl_def = generate_trait_impl(adt, trait_path);
148+
149+
ted::insert_all(
150+
insert_after,
151+
vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()],
152+
);
153+
}
154+
(Some(cap), None) => {
155+
let impl_def = generate_trait_impl(adt, trait_path);
156+
157+
if let Some(l_curly) =
158+
impl_def.assoc_item_list().and_then(|it| it.l_curly_token())
159+
{
160+
builder.add_tabstop_after_token(cap, l_curly);
161+
}
162+
163+
ted::insert_all(
164+
insert_after,
165+
vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()],
166+
);
143167
}
144-
(Some(cap), None) => builder.insert_snippet(
145-
cap,
146-
insert_pos,
147-
generate_trait_impl_text(adt, &trait_path, " $0"),
148-
),
149168
(Some(cap), Some((impl_def, first_assoc_item))) => {
150-
let mut cursor = Cursor::Before(first_assoc_item.syntax());
151-
let placeholder;
169+
let mut added_snippet = false;
152170
if let ast::AssocItem::Fn(ref func) = first_assoc_item {
153171
if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast)
154172
{
155173
if m.syntax().text() == "todo!()" {
156-
placeholder = m;
157-
cursor = Cursor::Replace(placeholder.syntax());
174+
// Make the `todo!()` a placeholder
175+
builder.add_placeholder_snippet(cap, m);
176+
added_snippet = true;
158177
}
159178
}
160179
}
161180

162-
let rendered = render_snippet(cap, impl_def.syntax(), cursor);
163-
builder.insert_snippet(cap, insert_pos, format!("\n\n{rendered}"))
181+
if !added_snippet {
182+
// If we haven't already added a snippet, add a tabstop before the generated function
183+
builder.add_tabstop_before(cap, first_assoc_item);
184+
}
185+
186+
ted::insert_all(
187+
insert_after,
188+
vec![make::tokens::blank_line().into(), impl_def.syntax().clone().into()],
189+
);
164190
}
165191
};
166192
},
@@ -190,28 +216,7 @@ fn impl_def_from_trait(
190216
if trait_items.is_empty() {
191217
return None;
192218
}
193-
let impl_def = {
194-
use syntax::ast::Impl;
195-
let text = generate_trait_impl_text(adt, trait_path.to_string().as_str(), "");
196-
// FIXME: `generate_trait_impl_text` currently generates two newlines
197-
// at the front, but these leading newlines should really instead be
198-
// inserted at the same time the impl is inserted
199-
assert_eq!(&text[..2], "\n\n", "`generate_trait_impl_text` output changed");
200-
let parse = syntax::SourceFile::parse(&text[2..]);
201-
let node = match parse.tree().syntax().descendants().find_map(Impl::cast) {
202-
Some(it) => it,
203-
None => {
204-
panic!(
205-
"Failed to make ast node `{}` from text {}",
206-
std::any::type_name::<Impl>(),
207-
text
208-
)
209-
}
210-
};
211-
let node = node.clone_for_update();
212-
assert_eq!(node.syntax().text_range().start(), 0.into());
213-
node
214-
};
219+
let impl_def = generate_trait_impl(adt, make::ty_path(trait_path.clone()));
215220

216221
let first_assoc_item =
217222
add_trait_assoc_items_to_impl(sema, &trait_items, trait_, &impl_def, target_scope);
@@ -238,20 +243,37 @@ fn update_attribute(
238243
let has_more_derives = !new_derives.is_empty();
239244

240245
if has_more_derives {
241-
let new_derives = format!("({})", new_derives.iter().format(", "));
242-
builder.replace(old_tree.syntax().text_range(), new_derives);
246+
let old_tree = builder.make_mut(old_tree.clone());
247+
248+
// Make the paths into flat lists of tokens in a vec
249+
let tt = new_derives.iter().map(|path| path.syntax().clone()).map(|node| {
250+
node.descendants_with_tokens()
251+
.filter_map(|element| element.into_token())
252+
.collect::<Vec<_>>()
253+
});
254+
// ...which are interspersed with ", "
255+
let tt = Itertools::intersperse(
256+
tt,
257+
vec![make::token(T![,]).into(), make::tokens::single_space().into()],
258+
);
259+
// ...wrap them into the appropriate `NodeOrToken` variant
260+
let tt = tt.flatten().map(|token| syntax::NodeOrToken::Token(token));
261+
// ...and make them into a flat list of tokens
262+
let tt = tt.collect::<Vec<_>>();
263+
264+
let new_tree = make::token_tree(T!['('], tt).clone_for_update();
265+
ted::replace(old_tree.syntax(), new_tree.syntax());
243266
} else {
244-
let attr_range = attr.syntax().text_range();
245-
builder.delete(attr_range);
246-
247-
if let Some(line_break_range) = attr
248-
.syntax()
249-
.next_sibling_or_token()
250-
.filter(|t| t.kind() == WHITESPACE)
251-
.map(|t| t.text_range())
267+
// Remove the attr and any trailing whitespace
268+
let attr = builder.make_mut(attr.clone());
269+
270+
if let Some(line_break) =
271+
attr.syntax().next_sibling_or_token().filter(|t| t.kind() == WHITESPACE)
252272
{
253-
builder.delete(line_break_range);
273+
ted::remove(line_break)
254274
}
275+
276+
ted::remove(attr.syntax())
255277
}
256278
}
257279

@@ -1168,9 +1190,7 @@ struct Foo {
11681190
bar: String,
11691191
}
11701192
1171-
impl Debug for Foo {
1172-
$0
1173-
}
1193+
impl Debug for Foo {$0}
11741194
"#,
11751195
)
11761196
}
@@ -1191,9 +1211,7 @@ pub struct Foo {
11911211
bar: String,
11921212
}
11931213
1194-
impl Debug for Foo {
1195-
$0
1196-
}
1214+
impl Debug for Foo {$0}
11971215
"#,
11981216
)
11991217
}
@@ -1211,9 +1229,7 @@ struct Foo {}
12111229
#[derive(Display, Serialize)]
12121230
struct Foo {}
12131231
1214-
impl Debug for Foo {
1215-
$0
1216-
}
1232+
impl Debug for Foo {$0}
12171233
"#,
12181234
)
12191235
}

0 commit comments

Comments
 (0)