mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-29 06:23:25 +00:00
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.
This commit is contained in:
commit
8a0a09a368
6 changed files with 105 additions and 107 deletions
|
@ -79,7 +79,7 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext<'_>)
|
||||||
edit.edit_file(target_file);
|
edit.edit_file(target_file);
|
||||||
|
|
||||||
let vis_owner = edit.make_mut(vis_owner);
|
let vis_owner = edit.make_mut(vis_owner);
|
||||||
vis_owner.set_visibility(missing_visibility.clone_for_update());
|
vis_owner.set_visibility(Some(missing_visibility.clone_for_update()));
|
||||||
|
|
||||||
if let Some((cap, vis)) = ctx.config.snippet_cap.zip(vis_owner.visibility()) {
|
if let Some((cap, vis)) = ctx.config.snippet_cap.zip(vis_owner.visibility()) {
|
||||||
edit.add_tabstop_before(cap, vis);
|
edit.add_tabstop_before(cap, vis);
|
||||||
|
@ -131,7 +131,7 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext<'_>
|
||||||
edit.edit_file(target_file);
|
edit.edit_file(target_file);
|
||||||
|
|
||||||
let vis_owner = edit.make_mut(vis_owner);
|
let vis_owner = edit.make_mut(vis_owner);
|
||||||
vis_owner.set_visibility(missing_visibility.clone_for_update());
|
vis_owner.set_visibility(Some(missing_visibility.clone_for_update()));
|
||||||
|
|
||||||
if let Some((cap, vis)) = ctx.config.snippet_cap.zip(vis_owner.visibility()) {
|
if let Some((cap, vis)) = ctx.config.snippet_cap.zip(vis_owner.visibility()) {
|
||||||
edit.add_tabstop_before(cap, vis);
|
edit.add_tabstop_before(cap, vis);
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
use crate::assist_context::{AssistContext, Assists};
|
use crate::assist_context::{AssistContext, Assists};
|
||||||
use ide_db::assists::AssistId;
|
use ide_db::assists::AssistId;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
ast::{self, edit::IndentLevel, make, HasGenericParams, HasVisibility},
|
ast::{
|
||||||
ted, AstNode, SyntaxKind,
|
self,
|
||||||
|
edit_in_place::{HasVisibilityEdit, Indent},
|
||||||
|
make, HasGenericParams, HasName,
|
||||||
|
},
|
||||||
|
ted::{self, Position},
|
||||||
|
AstNode, SyntaxKind, T,
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOTES :
|
// NOTES :
|
||||||
|
@ -44,7 +49,7 @@ use syntax::{
|
||||||
// };
|
// };
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// trait ${0:TraitName}<const N: usize> {
|
// trait ${0:NewTrait}<const N: usize> {
|
||||||
// // Used as an associated constant.
|
// // Used as an associated constant.
|
||||||
// const CONST_ASSOC: usize = N * 4;
|
// const CONST_ASSOC: usize = N * 4;
|
||||||
//
|
//
|
||||||
|
@ -53,7 +58,7 @@ use syntax::{
|
||||||
// const_maker! {i32, 7}
|
// const_maker! {i32, 7}
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// impl<const N: usize> ${0:TraitName}<N> for Foo<N> {
|
// impl<const N: usize> ${0:NewTrait}<N> for Foo<N> {
|
||||||
// // Used as an associated constant.
|
// // Used as an associated constant.
|
||||||
// const CONST_ASSOC: usize = N * 4;
|
// const CONST_ASSOC: usize = N * 4;
|
||||||
//
|
//
|
||||||
|
@ -94,8 +99,10 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_
|
||||||
"Generate trait from impl",
|
"Generate trait from impl",
|
||||||
impl_ast.syntax().text_range(),
|
impl_ast.syntax().text_range(),
|
||||||
|builder| {
|
|builder| {
|
||||||
|
let impl_ast = builder.make_mut(impl_ast);
|
||||||
let trait_items = assoc_items.clone_for_update();
|
let trait_items = assoc_items.clone_for_update();
|
||||||
let impl_items = assoc_items.clone_for_update();
|
let impl_items = builder.make_mut(assoc_items);
|
||||||
|
let impl_name = builder.make_mut(impl_name);
|
||||||
|
|
||||||
trait_items.assoc_items().for_each(|item| {
|
trait_items.assoc_items().for_each(|item| {
|
||||||
strip_body(&item);
|
strip_body(&item);
|
||||||
|
@ -112,46 +119,42 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_
|
||||||
impl_ast.generic_param_list(),
|
impl_ast.generic_param_list(),
|
||||||
impl_ast.where_clause(),
|
impl_ast.where_clause(),
|
||||||
trait_items,
|
trait_items,
|
||||||
);
|
)
|
||||||
|
.clone_for_update();
|
||||||
|
|
||||||
|
let trait_name = trait_ast.name().expect("new trait should have a name");
|
||||||
|
let trait_name_ref = make::name_ref(&trait_name.to_string()).clone_for_update();
|
||||||
|
|
||||||
// Change `impl Foo` to `impl NewTrait for Foo`
|
// Change `impl Foo` to `impl NewTrait for Foo`
|
||||||
let arg_list = if let Some(genpars) = impl_ast.generic_param_list() {
|
let mut elements = vec![
|
||||||
genpars.to_generic_args().to_string()
|
trait_name_ref.syntax().clone().into(),
|
||||||
} else {
|
make::tokens::single_space().into(),
|
||||||
"".to_owned()
|
make::token(T![for]).into(),
|
||||||
};
|
];
|
||||||
|
|
||||||
if let Some(snippet_cap) = ctx.config.snippet_cap {
|
if let Some(params) = impl_ast.generic_param_list() {
|
||||||
builder.replace_snippet(
|
let gen_args = ¶ms.to_generic_args().clone_for_update();
|
||||||
snippet_cap,
|
elements.insert(1, gen_args.syntax().clone().into());
|
||||||
impl_name.syntax().text_range(),
|
|
||||||
format!("${{0:TraitName}}{} for {}", arg_list, impl_name),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Insert trait before TraitImpl
|
|
||||||
builder.insert_snippet(
|
|
||||||
snippet_cap,
|
|
||||||
impl_ast.syntax().text_range().start(),
|
|
||||||
format!(
|
|
||||||
"{}\n\n{}",
|
|
||||||
trait_ast.to_string().replace("NewTrait", "${0:TraitName}"),
|
|
||||||
IndentLevel::from_node(impl_ast.syntax())
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
builder.replace(
|
|
||||||
impl_name.syntax().text_range(),
|
|
||||||
format!("NewTrait{} for {}", arg_list, impl_name),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Insert trait before TraitImpl
|
|
||||||
builder.insert(
|
|
||||||
impl_ast.syntax().text_range().start(),
|
|
||||||
format!("{}\n\n{}", trait_ast, IndentLevel::from_node(impl_ast.syntax())),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.replace(assoc_items.syntax().text_range(), impl_items.to_string());
|
ted::insert_all(Position::before(impl_name.syntax()), elements);
|
||||||
|
|
||||||
|
// Insert trait before TraitImpl
|
||||||
|
ted::insert_all_raw(
|
||||||
|
Position::before(impl_ast.syntax()),
|
||||||
|
vec![
|
||||||
|
trait_ast.syntax().clone().into(),
|
||||||
|
make::tokens::whitespace(&format!("\n\n{}", impl_ast.indent_level())).into(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Link the trait name & trait ref names together as a placeholder snippet group
|
||||||
|
if let Some(cap) = ctx.config.snippet_cap {
|
||||||
|
builder.add_placeholder_snippet_group(
|
||||||
|
cap,
|
||||||
|
vec![trait_name.syntax().clone(), trait_name_ref.syntax().clone()],
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -160,23 +163,8 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_
|
||||||
|
|
||||||
/// `E0449` Trait items always share the visibility of their trait
|
/// `E0449` Trait items always share the visibility of their trait
|
||||||
fn remove_items_visibility(item: &ast::AssocItem) {
|
fn remove_items_visibility(item: &ast::AssocItem) {
|
||||||
match item {
|
if let Some(has_vis) = ast::AnyHasVisibility::cast(item.syntax().clone()) {
|
||||||
ast::AssocItem::Const(c) => {
|
has_vis.set_visibility(None);
|
||||||
if let Some(vis) = c.visibility() {
|
|
||||||
ted::remove(vis.syntax());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ast::AssocItem::Fn(f) => {
|
|
||||||
if let Some(vis) = f.visibility() {
|
|
||||||
ted::remove(vis.syntax());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ast::AssocItem::TypeAlias(t) => {
|
|
||||||
if let Some(vis) = t.visibility() {
|
|
||||||
ted::remove(vis.syntax());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,12 +392,12 @@ impl<const N: usize> F$0oo<N> {
|
||||||
r#"
|
r#"
|
||||||
struct Foo<const N: usize>([i32; N]);
|
struct Foo<const N: usize>([i32; N]);
|
||||||
|
|
||||||
trait ${0:TraitName}<const N: usize> {
|
trait ${0:NewTrait}<const N: usize> {
|
||||||
// Used as an associated constant.
|
// Used as an associated constant.
|
||||||
const CONST: usize = N * 4;
|
const CONST: usize = N * 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> ${0:TraitName}<N> for Foo<N> {
|
impl<const N: usize> ${0:NewTrait}<N> for Foo<N> {
|
||||||
// Used as an associated constant.
|
// Used as an associated constant.
|
||||||
const CONST: usize = N * 4;
|
const CONST: usize = N * 4;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1665,7 +1665,7 @@ macro_rules! const_maker {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
trait ${0:TraitName}<const N: usize> {
|
trait ${0:NewTrait}<const N: usize> {
|
||||||
// Used as an associated constant.
|
// Used as an associated constant.
|
||||||
const CONST_ASSOC: usize = N * 4;
|
const CONST_ASSOC: usize = N * 4;
|
||||||
|
|
||||||
|
@ -1674,7 +1674,7 @@ trait ${0:TraitName}<const N: usize> {
|
||||||
const_maker! {i32, 7}
|
const_maker! {i32, 7}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> ${0:TraitName}<N> for Foo<N> {
|
impl<const N: usize> ${0:NewTrait}<N> for Foo<N> {
|
||||||
// Used as an associated constant.
|
// Used as an associated constant.
|
||||||
const CONST_ASSOC: usize = N * 4;
|
const CONST_ASSOC: usize = N * 4;
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,7 @@ impl SnippetEdit {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(1..)
|
.zip(1..)
|
||||||
.with_position()
|
.with_position()
|
||||||
.map(|pos| {
|
.flat_map(|pos| {
|
||||||
let (snippet, index) = match pos {
|
let (snippet, index) = match pos {
|
||||||
(itertools::Position::First, it) | (itertools::Position::Middle, it) => it,
|
(itertools::Position::First, it) | (itertools::Position::Middle, it) => it,
|
||||||
// last/only snippet gets index 0
|
// last/only snippet gets index 0
|
||||||
|
@ -146,11 +146,13 @@ impl SnippetEdit {
|
||||||
| (itertools::Position::Only, (snippet, _)) => (snippet, 0),
|
| (itertools::Position::Only, (snippet, _)) => (snippet, 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
let range = match snippet {
|
match snippet {
|
||||||
Snippet::Tabstop(pos) => TextRange::empty(pos),
|
Snippet::Tabstop(pos) => vec![(index, TextRange::empty(pos))],
|
||||||
Snippet::Placeholder(range) => range,
|
Snippet::Placeholder(range) => vec![(index, range)],
|
||||||
};
|
Snippet::PlaceholderGroup(ranges) => {
|
||||||
(index, range)
|
ranges.into_iter().map(|range| (index, range)).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
|
|
||||||
|
@ -248,7 +250,7 @@ impl SourceChangeBuilder {
|
||||||
fn commit(&mut self) {
|
fn commit(&mut self) {
|
||||||
let snippet_edit = self.snippet_builder.take().map(|builder| {
|
let snippet_edit = self.snippet_builder.take().map(|builder| {
|
||||||
SnippetEdit::new(
|
SnippetEdit::new(
|
||||||
builder.places.into_iter().map(PlaceSnippet::finalize_position).collect_vec(),
|
builder.places.into_iter().flat_map(PlaceSnippet::finalize_position).collect(),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -287,30 +289,10 @@ impl SourceChangeBuilder {
|
||||||
pub fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
|
pub fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
|
||||||
self.edit.insert(offset, text.into())
|
self.edit.insert(offset, text.into())
|
||||||
}
|
}
|
||||||
/// Append specified `snippet` at the given `offset`
|
|
||||||
pub fn insert_snippet(
|
|
||||||
&mut self,
|
|
||||||
_cap: SnippetCap,
|
|
||||||
offset: TextSize,
|
|
||||||
snippet: impl Into<String>,
|
|
||||||
) {
|
|
||||||
self.source_change.is_snippet = true;
|
|
||||||
self.insert(offset, snippet);
|
|
||||||
}
|
|
||||||
/// Replaces specified `range` of text with a given string.
|
/// Replaces specified `range` of text with a given string.
|
||||||
pub fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
|
pub fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
|
||||||
self.edit.replace(range, replace_with.into())
|
self.edit.replace(range, replace_with.into())
|
||||||
}
|
}
|
||||||
/// Replaces specified `range` of text with a given `snippet`.
|
|
||||||
pub fn replace_snippet(
|
|
||||||
&mut self,
|
|
||||||
_cap: SnippetCap,
|
|
||||||
range: TextRange,
|
|
||||||
snippet: impl Into<String>,
|
|
||||||
) {
|
|
||||||
self.source_change.is_snippet = true;
|
|
||||||
self.replace(range, snippet);
|
|
||||||
}
|
|
||||||
pub fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
|
pub 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)
|
||||||
}
|
}
|
||||||
|
@ -356,6 +338,17 @@ impl SourceChangeBuilder {
|
||||||
self.add_snippet(PlaceSnippet::Over(node.syntax().clone().into()))
|
self.add_snippet(PlaceSnippet::Over(node.syntax().clone().into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a snippet to move the cursor selected over `nodes`
|
||||||
|
///
|
||||||
|
/// This allows for renaming newly generated items without having to go
|
||||||
|
/// through a separate rename step.
|
||||||
|
pub fn add_placeholder_snippet_group(&mut self, _cap: SnippetCap, nodes: Vec<SyntaxNode>) {
|
||||||
|
assert!(nodes.iter().all(|node| node.parent().is_some()));
|
||||||
|
self.add_snippet(PlaceSnippet::OverGroup(
|
||||||
|
nodes.into_iter().map(|node| node.into()).collect(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
fn add_snippet(&mut self, snippet: PlaceSnippet) {
|
fn add_snippet(&mut self, snippet: PlaceSnippet) {
|
||||||
let snippet_builder = self.snippet_builder.get_or_insert(SnippetBuilder { places: vec![] });
|
let snippet_builder = self.snippet_builder.get_or_insert(SnippetBuilder { places: vec![] });
|
||||||
snippet_builder.places.push(snippet);
|
snippet_builder.places.push(snippet);
|
||||||
|
@ -400,6 +393,13 @@ pub enum Snippet {
|
||||||
Tabstop(TextSize),
|
Tabstop(TextSize),
|
||||||
/// A placeholder snippet (e.g. `${0:placeholder}`).
|
/// A placeholder snippet (e.g. `${0:placeholder}`).
|
||||||
Placeholder(TextRange),
|
Placeholder(TextRange),
|
||||||
|
/// A group of placeholder snippets, e.g.
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// let ${0:new_var} = 4;
|
||||||
|
/// fun(1, 2, 3, ${0:new_var});
|
||||||
|
/// ```
|
||||||
|
PlaceholderGroup(Vec<TextRange>),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PlaceSnippet {
|
enum PlaceSnippet {
|
||||||
|
@ -409,14 +409,20 @@ enum PlaceSnippet {
|
||||||
After(SyntaxElement),
|
After(SyntaxElement),
|
||||||
/// Place a placeholder snippet in place of the element
|
/// Place a placeholder snippet in place of the element
|
||||||
Over(SyntaxElement),
|
Over(SyntaxElement),
|
||||||
|
/// Place a group of placeholder snippets which are linked together
|
||||||
|
/// in place of the elements
|
||||||
|
OverGroup(Vec<SyntaxElement>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlaceSnippet {
|
impl PlaceSnippet {
|
||||||
fn finalize_position(self) -> Snippet {
|
fn finalize_position(self) -> Vec<Snippet> {
|
||||||
match self {
|
match self {
|
||||||
PlaceSnippet::Before(it) => Snippet::Tabstop(it.text_range().start()),
|
PlaceSnippet::Before(it) => vec![Snippet::Tabstop(it.text_range().start())],
|
||||||
PlaceSnippet::After(it) => Snippet::Tabstop(it.text_range().end()),
|
PlaceSnippet::After(it) => vec![Snippet::Tabstop(it.text_range().end())],
|
||||||
PlaceSnippet::Over(it) => Snippet::Placeholder(it.text_range()),
|
PlaceSnippet::Over(it) => vec![Snippet::Placeholder(it.text_range())],
|
||||||
|
PlaceSnippet::OverGroup(it) => {
|
||||||
|
vec![Snippet::PlaceholderGroup(it.into_iter().map(|it| it.text_range()).collect())]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1007,7 +1007,8 @@ impl ast::IdentPat {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait HasVisibilityEdit: ast::HasVisibility {
|
pub trait HasVisibilityEdit: ast::HasVisibility {
|
||||||
fn set_visibility(&self, visibility: ast::Visibility) {
|
fn set_visibility(&self, visibility: Option<ast::Visibility>) {
|
||||||
|
if let Some(visibility) = visibility {
|
||||||
match self.visibility() {
|
match self.visibility() {
|
||||||
Some(current_visibility) => {
|
Some(current_visibility) => {
|
||||||
ted::replace(current_visibility.syntax(), visibility.syntax())
|
ted::replace(current_visibility.syntax(), visibility.syntax())
|
||||||
|
@ -1022,6 +1023,9 @@ pub trait HasVisibilityEdit: ast::HasVisibility {
|
||||||
ted::insert(ted::Position::before(vis_before), visibility.syntax());
|
ted::insert(ted::Position::before(vis_before), visibility.syntax());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if let Some(visibility) = self.visibility() {
|
||||||
|
ted::remove(visibility.syntax());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1147,7 +1147,7 @@ pub mod tokens {
|
||||||
|
|
||||||
pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> = Lazy::new(|| {
|
pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> = Lazy::new(|| {
|
||||||
SourceFile::parse(
|
SourceFile::parse(
|
||||||
"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",
|
"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: {}",
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue