Skip to content

Commit 2f6d545

Browse files
committed
Auto merge of rust-lang#15231 - DropDemBits:structured-snippet-migrate-2, r=lowr
internal: Migrate more assists to use the structured snippet API Continuing from rust-lang#14979 Migrates the following assists: - `generate_derive` - `wrap_return_type_in_result` - `generate_delegate_methods` As a bonus, `generate_delegate_methods` now generates the function and impl block at the correct indentation 🎉.
2 parents 9b2d4ce + 5fddf3e commit 2f6d545

File tree

6 files changed

+297
-124
lines changed

6 files changed

+297
-124
lines changed

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

Lines changed: 116 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
use std::collections::HashSet;
22

33
use hir::{self, HasCrate, HasSource, HasVisibility};
4-
use syntax::ast::{self, make, AstNode, HasGenericParams, HasName, HasVisibility as _};
4+
use syntax::{
5+
ast::{
6+
self, edit_in_place::Indent, make, AstNode, HasGenericParams, HasName, HasVisibility as _,
7+
},
8+
ted,
9+
};
510

611
use crate::{
7-
utils::{convert_param_list_to_arg_list, find_struct_impl, render_snippet, Cursor},
12+
utils::{convert_param_list_to_arg_list, find_struct_impl},
813
AssistContext, AssistId, AssistKind, Assists, GroupLabel,
914
};
10-
use syntax::ast::edit::AstNodeEdit;
1115

1216
// Assist: generate_delegate_methods
1317
//
@@ -96,7 +100,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
96100
AssistId("generate_delegate_methods", AssistKind::Generate),
97101
format!("Generate delegate for `{field_name}.{name}()`",),
98102
target,
99-
|builder| {
103+
|edit| {
100104
// Create the function
101105
let method_source = match method.source(ctx.db()) {
102106
Some(source) => source.value,
@@ -135,36 +139,12 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
135139
is_const,
136140
is_unsafe,
137141
)
138-
.indent(ast::edit::IndentLevel(1))
139142
.clone_for_update();
140143

141-
let cursor = Cursor::Before(f.syntax());
142-
143-
// Create or update an impl block, attach the function to it,
144-
// then insert into our code.
145-
match impl_def {
146-
Some(impl_def) => {
147-
// Remember where in our source our `impl` block lives.
148-
let impl_def = impl_def.clone_for_update();
149-
let old_range = impl_def.syntax().text_range();
150-
151-
// Attach the function to the impl block
152-
let assoc_items = impl_def.get_or_create_assoc_item_list();
153-
assoc_items.add_item(f.clone().into());
154-
155-
// Update the impl block.
156-
match ctx.config.snippet_cap {
157-
Some(cap) => {
158-
let snippet = render_snippet(cap, impl_def.syntax(), cursor);
159-
builder.replace_snippet(cap, old_range, snippet);
160-
}
161-
None => {
162-
builder.replace(old_range, impl_def.syntax().to_string());
163-
}
164-
}
165-
}
144+
// Get the impl to update, or create one if we need to.
145+
let impl_def = match impl_def {
146+
Some(impl_def) => edit.make_mut(impl_def),
166147
None => {
167-
// Attach the function to the impl block
168148
let name = &strukt_name.to_string();
169149
let params = strukt.generic_param_list();
170150
let ty_params = params.clone();
@@ -178,24 +158,34 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
178158
None,
179159
)
180160
.clone_for_update();
181-
let assoc_items = impl_def.get_or_create_assoc_item_list();
182-
assoc_items.add_item(f.clone().into());
161+
162+
// Fixup impl_def indentation
163+
let indent = strukt.indent_level();
164+
impl_def.reindent_to(indent);
183165

184166
// Insert the impl block.
185-
match ctx.config.snippet_cap {
186-
Some(cap) => {
187-
let offset = strukt.syntax().text_range().end();
188-
let snippet = render_snippet(cap, impl_def.syntax(), cursor);
189-
let snippet = format!("\n\n{snippet}");
190-
builder.insert_snippet(cap, offset, snippet);
191-
}
192-
None => {
193-
let offset = strukt.syntax().text_range().end();
194-
let snippet = format!("\n\n{}", impl_def.syntax());
195-
builder.insert(offset, snippet);
196-
}
197-
}
167+
let strukt = edit.make_mut(strukt.clone());
168+
ted::insert_all(
169+
ted::Position::after(strukt.syntax()),
170+
vec![
171+
make::tokens::whitespace(&format!("\n\n{indent}")).into(),
172+
impl_def.syntax().clone().into(),
173+
],
174+
);
175+
176+
impl_def
198177
}
178+
};
179+
180+
// Fixup function indentation.
181+
// FIXME: Should really be handled by `AssocItemList::add_item`
182+
f.reindent_to(impl_def.indent_level() + 1);
183+
184+
let assoc_items = impl_def.get_or_create_assoc_item_list();
185+
assoc_items.add_item(f.clone().into());
186+
187+
if let Some(cap) = ctx.config.snippet_cap {
188+
edit.add_tabstop_before(cap, f)
199189
}
200190
},
201191
)?;
@@ -244,6 +234,45 @@ impl Person {
244234
);
245235
}
246236

237+
#[test]
238+
fn test_generate_delegate_create_impl_block_match_indent() {
239+
check_assist(
240+
generate_delegate_methods,
241+
r#"
242+
mod indent {
243+
struct Age(u8);
244+
impl Age {
245+
fn age(&self) -> u8 {
246+
self.0
247+
}
248+
}
249+
250+
struct Person {
251+
ag$0e: Age,
252+
}
253+
}"#,
254+
r#"
255+
mod indent {
256+
struct Age(u8);
257+
impl Age {
258+
fn age(&self) -> u8 {
259+
self.0
260+
}
261+
}
262+
263+
struct Person {
264+
age: Age,
265+
}
266+
267+
impl Person {
268+
$0fn age(&self) -> u8 {
269+
self.age.age()
270+
}
271+
}
272+
}"#,
273+
);
274+
}
275+
247276
#[test]
248277
fn test_generate_delegate_update_impl_block() {
249278
check_assist(
@@ -281,6 +310,47 @@ impl Person {
281310
);
282311
}
283312

313+
#[test]
314+
fn test_generate_delegate_update_impl_block_match_indent() {
315+
check_assist(
316+
generate_delegate_methods,
317+
r#"
318+
mod indent {
319+
struct Age(u8);
320+
impl Age {
321+
fn age(&self) -> u8 {
322+
self.0
323+
}
324+
}
325+
326+
struct Person {
327+
ag$0e: Age,
328+
}
329+
330+
impl Person {}
331+
}"#,
332+
r#"
333+
mod indent {
334+
struct Age(u8);
335+
impl Age {
336+
fn age(&self) -> u8 {
337+
self.0
338+
}
339+
}
340+
341+
struct Person {
342+
age: Age,
343+
}
344+
345+
impl Person {
346+
$0fn age(&self) -> u8 {
347+
self.age.age()
348+
}
349+
}
350+
}"#,
351+
);
352+
}
353+
284354
#[test]
285355
fn test_generate_delegate_tuple_struct() {
286356
check_assist(

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

Lines changed: 63 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use syntax::{
2-
ast::{self, edit::IndentLevel, AstNode, HasAttrs},
3-
SyntaxKind::{COMMENT, WHITESPACE},
4-
TextSize,
2+
ast::{self, edit_in_place::AttrsOwnerEdit, make, AstNode, HasAttrs},
3+
T,
54
};
65

76
use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -27,48 +26,37 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
2726
pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
2827
let cap = ctx.config.snippet_cap?;
2928
let nominal = ctx.find_node_at_offset::<ast::Adt>()?;
30-
let node_start = derive_insertion_offset(&nominal)?;
3129
let target = nominal.syntax().text_range();
32-
acc.add(
33-
AssistId("generate_derive", AssistKind::Generate),
34-
"Add `#[derive]`",
35-
target,
36-
|builder| {
37-
let derive_attr = nominal
38-
.attrs()
39-
.filter_map(|x| x.as_simple_call())
40-
.filter(|(name, _arg)| name == "derive")
41-
.map(|(_name, arg)| arg)
42-
.next();
43-
match derive_attr {
44-
None => {
45-
let indent_level = IndentLevel::from_node(nominal.syntax());
46-
builder.insert_snippet(
47-
cap,
48-
node_start,
49-
format!("#[derive($0)]\n{indent_level}"),
50-
);
51-
}
52-
Some(tt) => {
53-
// Just move the cursor.
54-
builder.insert_snippet(
55-
cap,
56-
tt.syntax().text_range().end() - TextSize::of(')'),
57-
"$0",
58-
)
59-
}
60-
};
61-
},
62-
)
63-
}
30+
acc.add(AssistId("generate_derive", AssistKind::Generate), "Add `#[derive]`", target, |edit| {
31+
let derive_attr = nominal
32+
.attrs()
33+
.filter_map(|x| x.as_simple_call())
34+
.filter(|(name, _arg)| name == "derive")
35+
.map(|(_name, arg)| arg)
36+
.next();
37+
match derive_attr {
38+
None => {
39+
let derive = make::attr_outer(make::meta_token_tree(
40+
make::ext::ident_path("derive"),
41+
make::token_tree(T!['('], vec![]).clone_for_update(),
42+
))
43+
.clone_for_update();
44+
45+
let nominal = edit.make_mut(nominal);
46+
nominal.add_attr(derive.clone());
6447

65-
// Insert `derive` after doc comments.
66-
fn derive_insertion_offset(nominal: &ast::Adt) -> Option<TextSize> {
67-
let non_ws_child = nominal
68-
.syntax()
69-
.children_with_tokens()
70-
.find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
71-
Some(non_ws_child.text_range().start())
48+
edit.add_tabstop_before_token(
49+
cap,
50+
derive.meta().unwrap().token_tree().unwrap().r_paren_token().unwrap(),
51+
);
52+
}
53+
Some(tt) => {
54+
// Just move the cursor.
55+
let tt = edit.make_mut(tt);
56+
edit.add_tabstop_before_token(cap, tt.right_delimiter_token().unwrap());
57+
}
58+
};
59+
})
7260
}
7361

7462
#[cfg(test)]
@@ -114,6 +102,38 @@ mod m {
114102
);
115103
}
116104

105+
#[test]
106+
fn add_derive_existing_with_brackets() {
107+
check_assist(
108+
generate_derive,
109+
"
110+
#[derive[Clone]]
111+
struct Foo { a: i32$0, }
112+
",
113+
"
114+
#[derive[Clone$0]]
115+
struct Foo { a: i32, }
116+
",
117+
);
118+
}
119+
120+
#[test]
121+
fn add_derive_existing_missing_delimiter() {
122+
// since `#[derive]` isn't a simple attr call (i.e. `#[derive()]`)
123+
// we don't consider it as a proper derive attr and generate a new
124+
// one instead
125+
check_assist(
126+
generate_derive,
127+
"
128+
#[derive]
129+
struct Foo { a: i32$0, }",
130+
"
131+
#[derive]
132+
#[derive($0)]
133+
struct Foo { a: i32, }",
134+
);
135+
}
136+
117137
#[test]
118138
fn add_derive_new_with_doc_comment() {
119139
check_assist(

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

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use ide_db::{
66
};
77
use syntax::{
88
ast::{self, make, Expr},
9-
match_ast, AstNode,
9+
match_ast, ted, AstNode,
1010
};
1111

1212
use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -52,8 +52,8 @@ pub(crate) fn wrap_return_type_in_result(acc: &mut Assists, ctx: &AssistContext<
5252
AssistId("wrap_return_type_in_result", AssistKind::RefactorRewrite),
5353
"Wrap return type in Result",
5454
type_ref.syntax().text_range(),
55-
|builder| {
56-
let body = ast::Expr::BlockExpr(body);
55+
|edit| {
56+
let body = edit.make_mut(ast::Expr::BlockExpr(body));
5757

5858
let mut exprs_to_wrap = Vec::new();
5959
let tail_cb = &mut |e: &_| tail_cb_impl(&mut exprs_to_wrap, e);
@@ -70,17 +70,24 @@ pub(crate) fn wrap_return_type_in_result(acc: &mut Assists, ctx: &AssistContext<
7070
let ok_wrapped = make::expr_call(
7171
make::expr_path(make::ext::ident_path("Ok")),
7272
make::arg_list(iter::once(ret_expr_arg.clone())),
73-
);
74-
builder.replace_ast(ret_expr_arg, ok_wrapped);
73+
)
74+
.clone_for_update();
75+
ted::replace(ret_expr_arg.syntax(), ok_wrapped.syntax());
7576
}
7677

77-
match ctx.config.snippet_cap {
78-
Some(cap) => {
79-
let snippet = format!("Result<{type_ref}, ${{0:_}}>");
80-
builder.replace_snippet(cap, type_ref.syntax().text_range(), snippet)
81-
}
82-
None => builder
83-
.replace(type_ref.syntax().text_range(), format!("Result<{type_ref}, _>")),
78+
let new_result_ty =
79+
make::ext::ty_result(type_ref.clone(), make::ty_placeholder()).clone_for_update();
80+
let old_result_ty = edit.make_mut(type_ref.clone());
81+
82+
ted::replace(old_result_ty.syntax(), new_result_ty.syntax());
83+
84+
if let Some(cap) = ctx.config.snippet_cap {
85+
let generic_args = new_result_ty
86+
.syntax()
87+
.descendants()
88+
.find_map(ast::GenericArgList::cast)
89+
.unwrap();
90+
edit.add_placeholder_snippet(cap, generic_args.generic_args().last().unwrap());
8491
}
8592
},
8693
)

0 commit comments

Comments
 (0)