Skip to content

Commit 8a0a09a

Browse files
committed
Auto merge of #16577 - DropDemBits:structured-snippet-migrate-7, r=Veykril
internal: Migrate assists to the structured snippet API, part 7/7 Continuing from #16467 Migrates the following assists: - `generate_trait_from_impl` This adds `add_placeholder_snippet_group`, which adds a group of placeholder snippets which are linked together and allows for renaming generated items without going through a separate rename step. This also removes the last usages of `SourceChangeBuilder::{insert,replace}_snippet`, as all assists have finally been migrated to the structured snippet versions of those methods.
2 parents b9b0d29 + 4af075d commit 8a0a09a

File tree

6 files changed

+105
-107
lines changed

6 files changed

+105
-107
lines changed

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>)
7979
edit.edit_file(target_file);
8080

8181
let vis_owner = edit.make_mut(vis_owner);
82-
vis_owner.set_visibility(missing_visibility.clone_for_update());
82+
vis_owner.set_visibility(Some(missing_visibility.clone_for_update()));
8383

8484
if let Some((cap, vis)) = ctx.config.snippet_cap.zip(vis_owner.visibility()) {
8585
edit.add_tabstop_before(cap, vis);
@@ -131,7 +131,7 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext<'_>
131131
edit.edit_file(target_file);
132132

133133
let vis_owner = edit.make_mut(vis_owner);
134-
vis_owner.set_visibility(missing_visibility.clone_for_update());
134+
vis_owner.set_visibility(Some(missing_visibility.clone_for_update()));
135135

136136
if let Some((cap, vis)) = ctx.config.snippet_cap.zip(vis_owner.visibility()) {
137137
edit.add_tabstop_before(cap, vis);

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

+46-58
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
use crate::assist_context::{AssistContext, Assists};
22
use ide_db::assists::AssistId;
33
use syntax::{
4-
ast::{self, edit::IndentLevel, make, HasGenericParams, HasVisibility},
5-
ted, AstNode, SyntaxKind,
4+
ast::{
5+
self,
6+
edit_in_place::{HasVisibilityEdit, Indent},
7+
make, HasGenericParams, HasName,
8+
},
9+
ted::{self, Position},
10+
AstNode, SyntaxKind, T,
611
};
712

813
// NOTES :
@@ -44,7 +49,7 @@ use syntax::{
4449
// };
4550
// }
4651
//
47-
// trait ${0:TraitName}<const N: usize> {
52+
// trait ${0:NewTrait}<const N: usize> {
4853
// // Used as an associated constant.
4954
// const CONST_ASSOC: usize = N * 4;
5055
//
@@ -53,7 +58,7 @@ use syntax::{
5358
// const_maker! {i32, 7}
5459
// }
5560
//
56-
// impl<const N: usize> ${0:TraitName}<N> for Foo<N> {
61+
// impl<const N: usize> ${0:NewTrait}<N> for Foo<N> {
5762
// // Used as an associated constant.
5863
// const CONST_ASSOC: usize = N * 4;
5964
//
@@ -94,8 +99,10 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_
9499
"Generate trait from impl",
95100
impl_ast.syntax().text_range(),
96101
|builder| {
102+
let impl_ast = builder.make_mut(impl_ast);
97103
let trait_items = assoc_items.clone_for_update();
98-
let impl_items = assoc_items.clone_for_update();
104+
let impl_items = builder.make_mut(assoc_items);
105+
let impl_name = builder.make_mut(impl_name);
99106

100107
trait_items.assoc_items().for_each(|item| {
101108
strip_body(&item);
@@ -112,46 +119,42 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_
112119
impl_ast.generic_param_list(),
113120
impl_ast.where_clause(),
114121
trait_items,
115-
);
122+
)
123+
.clone_for_update();
124+
125+
let trait_name = trait_ast.name().expect("new trait should have a name");
126+
let trait_name_ref = make::name_ref(&trait_name.to_string()).clone_for_update();
116127

117128
// Change `impl Foo` to `impl NewTrait for Foo`
118-
let arg_list = if let Some(genpars) = impl_ast.generic_param_list() {
119-
genpars.to_generic_args().to_string()
120-
} else {
121-
"".to_owned()
122-
};
123-
124-
if let Some(snippet_cap) = ctx.config.snippet_cap {
125-
builder.replace_snippet(
126-
snippet_cap,
127-
impl_name.syntax().text_range(),
128-
format!("${{0:TraitName}}{} for {}", arg_list, impl_name),
129-
);
129+
let mut elements = vec![
130+
trait_name_ref.syntax().clone().into(),
131+
make::tokens::single_space().into(),
132+
make::token(T![for]).into(),
133+
];
134+
135+
if let Some(params) = impl_ast.generic_param_list() {
136+
let gen_args = &params.to_generic_args().clone_for_update();
137+
elements.insert(1, gen_args.syntax().clone().into());
138+
}
130139

131-
// Insert trait before TraitImpl
132-
builder.insert_snippet(
133-
snippet_cap,
134-
impl_ast.syntax().text_range().start(),
135-
format!(
136-
"{}\n\n{}",
137-
trait_ast.to_string().replace("NewTrait", "${0:TraitName}"),
138-
IndentLevel::from_node(impl_ast.syntax())
139-
),
140-
);
141-
} else {
142-
builder.replace(
143-
impl_name.syntax().text_range(),
144-
format!("NewTrait{} for {}", arg_list, impl_name),
145-
);
140+
ted::insert_all(Position::before(impl_name.syntax()), elements);
141+
142+
// Insert trait before TraitImpl
143+
ted::insert_all_raw(
144+
Position::before(impl_ast.syntax()),
145+
vec![
146+
trait_ast.syntax().clone().into(),
147+
make::tokens::whitespace(&format!("\n\n{}", impl_ast.indent_level())).into(),
148+
],
149+
);
146150

147-
// Insert trait before TraitImpl
148-
builder.insert(
149-
impl_ast.syntax().text_range().start(),
150-
format!("{}\n\n{}", trait_ast, IndentLevel::from_node(impl_ast.syntax())),
151+
// Link the trait name & trait ref names together as a placeholder snippet group
152+
if let Some(cap) = ctx.config.snippet_cap {
153+
builder.add_placeholder_snippet_group(
154+
cap,
155+
vec![trait_name.syntax().clone(), trait_name_ref.syntax().clone()],
151156
);
152157
}
153-
154-
builder.replace(assoc_items.syntax().text_range(), impl_items.to_string());
155158
},
156159
);
157160

@@ -160,23 +163,8 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_
160163

161164
/// `E0449` Trait items always share the visibility of their trait
162165
fn remove_items_visibility(item: &ast::AssocItem) {
163-
match item {
164-
ast::AssocItem::Const(c) => {
165-
if let Some(vis) = c.visibility() {
166-
ted::remove(vis.syntax());
167-
}
168-
}
169-
ast::AssocItem::Fn(f) => {
170-
if let Some(vis) = f.visibility() {
171-
ted::remove(vis.syntax());
172-
}
173-
}
174-
ast::AssocItem::TypeAlias(t) => {
175-
if let Some(vis) = t.visibility() {
176-
ted::remove(vis.syntax());
177-
}
178-
}
179-
_ => (),
166+
if let Some(has_vis) = ast::AnyHasVisibility::cast(item.syntax().clone()) {
167+
has_vis.set_visibility(None);
180168
}
181169
}
182170

@@ -404,12 +392,12 @@ impl<const N: usize> F$0oo<N> {
404392
r#"
405393
struct Foo<const N: usize>([i32; N]);
406394
407-
trait ${0:TraitName}<const N: usize> {
395+
trait ${0:NewTrait}<const N: usize> {
408396
// Used as an associated constant.
409397
const CONST: usize = N * 4;
410398
}
411399
412-
impl<const N: usize> ${0:TraitName}<N> for Foo<N> {
400+
impl<const N: usize> ${0:NewTrait}<N> for Foo<N> {
413401
// Used as an associated constant.
414402
const CONST: usize = N * 4;
415403
}

crates/ide-assists/src/tests/generated.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1665,7 +1665,7 @@ macro_rules! const_maker {
16651665
};
16661666
}
16671667
1668-
trait ${0:TraitName}<const N: usize> {
1668+
trait ${0:NewTrait}<const N: usize> {
16691669
// Used as an associated constant.
16701670
const CONST_ASSOC: usize = N * 4;
16711671
@@ -1674,7 +1674,7 @@ trait ${0:TraitName}<const N: usize> {
16741674
const_maker! {i32, 7}
16751675
}
16761676
1677-
impl<const N: usize> ${0:TraitName}<N> for Foo<N> {
1677+
impl<const N: usize> ${0:NewTrait}<N> for Foo<N> {
16781678
// Used as an associated constant.
16791679
const CONST_ASSOC: usize = N * 4;
16801680

crates/ide-db/src/source_change.rs

+37-31
Original file line numberDiff line numberDiff line change
@@ -138,19 +138,21 @@ impl SnippetEdit {
138138
.into_iter()
139139
.zip(1..)
140140
.with_position()
141-
.map(|pos| {
141+
.flat_map(|pos| {
142142
let (snippet, index) = match pos {
143143
(itertools::Position::First, it) | (itertools::Position::Middle, it) => it,
144144
// last/only snippet gets index 0
145145
(itertools::Position::Last, (snippet, _))
146146
| (itertools::Position::Only, (snippet, _)) => (snippet, 0),
147147
};
148148

149-
let range = match snippet {
150-
Snippet::Tabstop(pos) => TextRange::empty(pos),
151-
Snippet::Placeholder(range) => range,
152-
};
153-
(index, range)
149+
match snippet {
150+
Snippet::Tabstop(pos) => vec![(index, TextRange::empty(pos))],
151+
Snippet::Placeholder(range) => vec![(index, range)],
152+
Snippet::PlaceholderGroup(ranges) => {
153+
ranges.into_iter().map(|range| (index, range)).collect()
154+
}
155+
}
154156
})
155157
.collect_vec();
156158

@@ -248,7 +250,7 @@ impl SourceChangeBuilder {
248250
fn commit(&mut self) {
249251
let snippet_edit = self.snippet_builder.take().map(|builder| {
250252
SnippetEdit::new(
251-
builder.places.into_iter().map(PlaceSnippet::finalize_position).collect_vec(),
253+
builder.places.into_iter().flat_map(PlaceSnippet::finalize_position).collect(),
252254
)
253255
});
254256

@@ -287,30 +289,10 @@ impl SourceChangeBuilder {
287289
pub fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
288290
self.edit.insert(offset, text.into())
289291
}
290-
/// Append specified `snippet` at the given `offset`
291-
pub fn insert_snippet(
292-
&mut self,
293-
_cap: SnippetCap,
294-
offset: TextSize,
295-
snippet: impl Into<String>,
296-
) {
297-
self.source_change.is_snippet = true;
298-
self.insert(offset, snippet);
299-
}
300292
/// Replaces specified `range` of text with a given string.
301293
pub fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
302294
self.edit.replace(range, replace_with.into())
303295
}
304-
/// Replaces specified `range` of text with a given `snippet`.
305-
pub fn replace_snippet(
306-
&mut self,
307-
_cap: SnippetCap,
308-
range: TextRange,
309-
snippet: impl Into<String>,
310-
) {
311-
self.source_change.is_snippet = true;
312-
self.replace(range, snippet);
313-
}
314296
pub fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
315297
algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
316298
}
@@ -356,6 +338,17 @@ impl SourceChangeBuilder {
356338
self.add_snippet(PlaceSnippet::Over(node.syntax().clone().into()))
357339
}
358340

341+
/// Adds a snippet to move the cursor selected over `nodes`
342+
///
343+
/// This allows for renaming newly generated items without having to go
344+
/// through a separate rename step.
345+
pub fn add_placeholder_snippet_group(&mut self, _cap: SnippetCap, nodes: Vec<SyntaxNode>) {
346+
assert!(nodes.iter().all(|node| node.parent().is_some()));
347+
self.add_snippet(PlaceSnippet::OverGroup(
348+
nodes.into_iter().map(|node| node.into()).collect(),
349+
))
350+
}
351+
359352
fn add_snippet(&mut self, snippet: PlaceSnippet) {
360353
let snippet_builder = self.snippet_builder.get_or_insert(SnippetBuilder { places: vec![] });
361354
snippet_builder.places.push(snippet);
@@ -400,6 +393,13 @@ pub enum Snippet {
400393
Tabstop(TextSize),
401394
/// A placeholder snippet (e.g. `${0:placeholder}`).
402395
Placeholder(TextRange),
396+
/// A group of placeholder snippets, e.g.
397+
///
398+
/// ```no_run
399+
/// let ${0:new_var} = 4;
400+
/// fun(1, 2, 3, ${0:new_var});
401+
/// ```
402+
PlaceholderGroup(Vec<TextRange>),
403403
}
404404

405405
enum PlaceSnippet {
@@ -409,14 +409,20 @@ enum PlaceSnippet {
409409
After(SyntaxElement),
410410
/// Place a placeholder snippet in place of the element
411411
Over(SyntaxElement),
412+
/// Place a group of placeholder snippets which are linked together
413+
/// in place of the elements
414+
OverGroup(Vec<SyntaxElement>),
412415
}
413416

414417
impl PlaceSnippet {
415-
fn finalize_position(self) -> Snippet {
418+
fn finalize_position(self) -> Vec<Snippet> {
416419
match self {
417-
PlaceSnippet::Before(it) => Snippet::Tabstop(it.text_range().start()),
418-
PlaceSnippet::After(it) => Snippet::Tabstop(it.text_range().end()),
419-
PlaceSnippet::Over(it) => Snippet::Placeholder(it.text_range()),
420+
PlaceSnippet::Before(it) => vec![Snippet::Tabstop(it.text_range().start())],
421+
PlaceSnippet::After(it) => vec![Snippet::Tabstop(it.text_range().end())],
422+
PlaceSnippet::Over(it) => vec![Snippet::Placeholder(it.text_range())],
423+
PlaceSnippet::OverGroup(it) => {
424+
vec![Snippet::PlaceholderGroup(it.into_iter().map(|it| it.text_range()).collect())]
425+
}
420426
}
421427
}
422428
}

crates/syntax/src/ast/edit_in_place.rs

+17-13
Original file line numberDiff line numberDiff line change
@@ -1007,20 +1007,24 @@ impl ast::IdentPat {
10071007
}
10081008

10091009
pub trait HasVisibilityEdit: ast::HasVisibility {
1010-
fn set_visibility(&self, visibility: ast::Visibility) {
1011-
match self.visibility() {
1012-
Some(current_visibility) => {
1013-
ted::replace(current_visibility.syntax(), visibility.syntax())
1014-
}
1015-
None => {
1016-
let vis_before = self
1017-
.syntax()
1018-
.children_with_tokens()
1019-
.find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR))
1020-
.unwrap_or_else(|| self.syntax().first_child_or_token().unwrap());
1021-
1022-
ted::insert(ted::Position::before(vis_before), visibility.syntax());
1010+
fn set_visibility(&self, visibility: Option<ast::Visibility>) {
1011+
if let Some(visibility) = visibility {
1012+
match self.visibility() {
1013+
Some(current_visibility) => {
1014+
ted::replace(current_visibility.syntax(), visibility.syntax())
1015+
}
1016+
None => {
1017+
let vis_before = self
1018+
.syntax()
1019+
.children_with_tokens()
1020+
.find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR))
1021+
.unwrap_or_else(|| self.syntax().first_child_or_token().unwrap());
1022+
1023+
ted::insert(ted::Position::before(vis_before), visibility.syntax());
1024+
}
10231025
}
1026+
} else if let Some(visibility) = self.visibility() {
1027+
ted::remove(visibility.syntax());
10241028
}
10251029
}
10261030
}

crates/syntax/src/ast/make.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1147,7 +1147,7 @@ pub mod tokens {
11471147

11481148
pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> = Lazy::new(|| {
11491149
SourceFile::parse(
1150-
"const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, { let a @ [] })\n;\n\n",
1150+
"const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, { let a @ [] })\n;\n\nimpl A for B where: {}",
11511151
)
11521152
});
11531153

0 commit comments

Comments
 (0)