Skip to content

Commit 0342311

Browse files
committed
Generate trait from impl v2
1 parent 20c877a commit 0342311

File tree

2 files changed

+79
-31
lines changed

2 files changed

+79
-31
lines changed

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

Lines changed: 78 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use crate::assist_context::{AssistContext, Assists};
2-
use ide_db::{assists::AssistId, SnippetCap};
2+
use ide_db::assists::AssistId;
33
use syntax::{
4-
ast::{self, HasGenericParams, HasVisibility},
5-
AstNode,
4+
ast::{self, edit::IndentLevel, make, HasGenericParams, HasVisibility},
5+
ted, AstNode, SyntaxKind,
66
};
77

88
// NOTES :
@@ -68,6 +68,16 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_
6868
// Get AST Node
6969
let impl_ast = ctx.find_node_at_offset::<ast::Impl>()?;
7070

71+
// Check if cursor is to the left of assoc item list's L_CURLY.
72+
// if no L_CURLY then return.
73+
let l_curly = impl_ast.assoc_item_list()?.l_curly_token()?;
74+
75+
let cursor_offset = ctx.offset();
76+
let l_curly_offset = l_curly.text_range();
77+
if cursor_offset >= l_curly_offset.start() {
78+
return None;
79+
}
80+
7181
// If impl is not inherent then we don't really need to go any further.
7282
if impl_ast.for_token().is_some() {
7383
return None;
@@ -80,9 +90,11 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_
8090
return None;
8191
}
8292

93+
let impl_name = impl_ast.self_ty()?;
94+
8395
acc.add(
8496
AssistId("generate_trait_from_impl", ide_db::assists::AssistKind::Generate),
85-
"Generate trait from impl".to_owned(),
97+
"Generate trait from impl",
8698
impl_ast.syntax().text_range(),
8799
|builder| {
88100
let trait_items = assoc_items.clone_for_update();
@@ -93,45 +105,43 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_
93105
remove_items_visibility(&item);
94106
});
95107

96-
syntax::ted::replace(assoc_items.clone_for_update().syntax(), impl_items.syntax());
108+
ted::replace(assoc_items.clone_for_update().syntax(), impl_items.syntax());
97109

98110
impl_items.assoc_items().for_each(|item| {
99111
remove_items_visibility(&item);
100112
});
101113

102-
let trait_ast = ast::make::trait_(
114+
let trait_ast = make::trait_(
103115
false,
104-
"NewTrait".to_string(),
105-
HasGenericParams::generic_param_list(&impl_ast),
106-
HasGenericParams::where_clause(&impl_ast),
116+
"NewTrait",
117+
impl_ast.generic_param_list(),
118+
impl_ast.where_clause(),
107119
trait_items,
108120
);
109121

110122
// Change `impl Foo` to `impl NewTrait for Foo`
111-
// First find the PATH_TYPE which is what Foo is.
112-
let impl_name = impl_ast.self_ty().unwrap();
113-
let trait_name = if let Some(genpars) = impl_ast.generic_param_list() {
114-
format!("NewTrait{}", genpars.to_generic_args())
123+
let arg_list = if let Some(genpars) = impl_ast.generic_param_list() {
124+
genpars.to_generic_args().to_string()
115125
} else {
116-
format!("NewTrait")
126+
"".to_string()
117127
};
118128

119129
// // Then replace
120130
builder.replace(
121-
impl_name.clone().syntax().text_range(),
122-
format!("{} for {}", trait_name, impl_name.to_string()),
131+
impl_name.syntax().text_range(),
132+
format!("NewTrait{} for {}", arg_list, impl_name.to_string()),
123133
);
124134

125-
builder.replace(
126-
impl_ast.assoc_item_list().unwrap().syntax().text_range(),
127-
impl_items.to_string(),
128-
);
135+
builder.replace(assoc_items.syntax().text_range(), impl_items.to_string());
129136

130137
// Insert trait before TraitImpl
131-
builder.insert_snippet(
132-
SnippetCap::new(true).unwrap(),
138+
builder.insert(
133139
impl_ast.syntax().text_range().start(),
134-
format!("{}\n\n", trait_ast.to_string()),
140+
format!(
141+
"{}\n\n{}",
142+
trait_ast.to_string(),
143+
IndentLevel::from_node(impl_ast.syntax())
144+
),
135145
);
136146
},
137147
);
@@ -144,17 +154,17 @@ fn remove_items_visibility(item: &ast::AssocItem) {
144154
match item {
145155
ast::AssocItem::Const(c) => {
146156
if let Some(vis) = c.visibility() {
147-
syntax::ted::remove(vis.syntax());
157+
ted::remove(vis.syntax());
148158
}
149159
}
150160
ast::AssocItem::Fn(f) => {
151161
if let Some(vis) = f.visibility() {
152-
syntax::ted::remove(vis.syntax());
162+
ted::remove(vis.syntax());
153163
}
154164
}
155165
ast::AssocItem::TypeAlias(t) => {
156166
if let Some(vis) = t.visibility() {
157-
syntax::ted::remove(vis.syntax());
167+
ted::remove(vis.syntax());
158168
}
159169
}
160170
_ => (),
@@ -168,12 +178,12 @@ fn strip_body(item: &ast::AssocItem) {
168178
// In constrast to function bodies, we want to see no ws before a semicolon.
169179
// So let's remove them if we see any.
170180
if let Some(prev) = body.syntax().prev_sibling_or_token() {
171-
if prev.kind() == syntax::SyntaxKind::WHITESPACE {
172-
syntax::ted::remove(prev);
181+
if prev.kind() == SyntaxKind::WHITESPACE {
182+
ted::remove(prev);
173183
}
174184
}
175185

176-
syntax::ted::replace(body.syntax(), ast::make::tokens::semicolon());
186+
ted::replace(body.syntax(), ast::make::tokens::semicolon());
177187
}
178188
}
179189
_ => (),
@@ -185,6 +195,21 @@ mod tests {
185195
use super::*;
186196
use crate::tests::{check_assist, check_assist_not_applicable};
187197

198+
#[test]
199+
fn test_trigger_when_cursor_on_header() {
200+
check_assist_not_applicable(
201+
generate_trait_from_impl,
202+
r#"
203+
struct Foo(f64);
204+
205+
impl Foo { $0
206+
fn add(&mut self, x: f64) {
207+
self.0 += x;
208+
}
209+
}"#,
210+
);
211+
}
212+
188213
#[test]
189214
fn test_assoc_item_fn() {
190215
check_assist(
@@ -299,7 +324,7 @@ impl<const N: usize> NewTrait<N> for Foo<N> {
299324
}
300325

301326
#[test]
302-
fn test_e0449_avoided() {
327+
fn test_trait_items_should_not_have_vis() {
303328
check_assist(
304329
generate_trait_from_impl,
305330
r#"
@@ -334,4 +359,27 @@ impl Emp$0tyImpl{}
334359
"#,
335360
)
336361
}
362+
363+
#[test]
364+
fn test_not_top_level_impl() {
365+
check_assist(
366+
generate_trait_from_impl,
367+
r#"
368+
mod a {
369+
impl S$0 {
370+
fn foo() {}
371+
}
372+
}"#,
373+
r#"
374+
mod a {
375+
trait NewTrait {
376+
fn foo();
377+
}
378+
379+
impl NewTrait for S {
380+
fn foo() {}
381+
}
382+
}"#,
383+
)
384+
}
337385
}

crates/syntax/src/ast/make.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -865,7 +865,7 @@ pub fn param_list(
865865

866866
pub fn trait_(
867867
is_unsafe: bool,
868-
ident: String,
868+
ident: &str,
869869
gen_params: Option<ast::GenericParamList>,
870870
where_clause: Option<ast::WhereClause>,
871871
assoc_items: ast::AssocItemList,

0 commit comments

Comments
 (0)