@@ -4,13 +4,15 @@ use syntax::{
4
4
ast:: { self , make, AstNode } ,
5
5
Direction , SmolStr ,
6
6
SyntaxKind :: { IDENT , WHITESPACE } ,
7
- TextRange , TextSize ,
7
+ TextSize ,
8
8
} ;
9
9
10
10
use crate :: {
11
- assist_config:: SnippetCap ,
12
11
assist_context:: { AssistBuilder , AssistContext , Assists } ,
13
- utils:: mod_path_to_ast,
12
+ utils:: {
13
+ add_trait_assoc_items_to_impl, filter_assoc_items, mod_path_to_ast, render_snippet, Cursor ,
14
+ DefaultMethods ,
15
+ } ,
14
16
AssistId , AssistKind ,
15
17
} ;
16
18
@@ -47,11 +49,10 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
47
49
ctx. token_at_offset ( ) . find ( |t| t. kind ( ) == IDENT && * t. text ( ) != attr_name) ?;
48
50
let trait_path = make:: path_unqualified ( make:: path_segment ( make:: name_ref ( trait_token. text ( ) ) ) ) ;
49
51
50
- let annotated = attr. syntax ( ) . siblings ( Direction :: Next ) . find_map ( ast:: Name :: cast) ?;
51
- let annotated_name = annotated. syntax ( ) . text ( ) . to_string ( ) ;
52
- let insert_pos = annotated. syntax ( ) . parent ( ) ?. text_range ( ) . end ( ) ;
52
+ let annotated_name = attr. syntax ( ) . siblings ( Direction :: Next ) . find_map ( ast:: Name :: cast) ?;
53
+ let insert_pos = annotated_name. syntax ( ) . parent ( ) ?. text_range ( ) . end ( ) ;
53
54
54
- let current_module = ctx. sema . scope ( annotated . syntax ( ) ) . module ( ) ?;
55
+ let current_module = ctx. sema . scope ( annotated_name . syntax ( ) ) . module ( ) ?;
55
56
let current_crate = current_module. krate ( ) ;
56
57
57
58
let found_traits = imports_locator:: find_imports ( & ctx. sema , current_crate, trait_token. text ( ) )
@@ -69,21 +70,22 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
69
70
} ) ;
70
71
71
72
let mut no_traits_found = true ;
72
- for ( trait_path, _trait ) in found_traits. inspect ( |_| no_traits_found = false ) {
73
- add_assist ( acc, ctx. config . snippet_cap , & attr, & trait_path, & annotated_name, insert_pos) ?;
73
+ for ( trait_path, trait_ ) in found_traits. inspect ( |_| no_traits_found = false ) {
74
+ add_assist ( acc, ctx, & attr, & trait_path, Some ( trait_ ) , & annotated_name, insert_pos) ?;
74
75
}
75
76
if no_traits_found {
76
- add_assist ( acc, ctx. config . snippet_cap , & attr, & trait_path, & annotated_name, insert_pos) ?;
77
+ add_assist ( acc, ctx, & attr, & trait_path, None , & annotated_name, insert_pos) ?;
77
78
}
78
79
Some ( ( ) )
79
80
}
80
81
81
82
fn add_assist (
82
83
acc : & mut Assists ,
83
- snippet_cap : Option < SnippetCap > ,
84
+ ctx : & AssistContext ,
84
85
attr : & ast:: Attr ,
85
86
trait_path : & ast:: Path ,
86
- annotated_name : & str ,
87
+ trait_ : Option < hir:: Trait > ,
88
+ annotated_name : & ast:: Name ,
87
89
insert_pos : TextSize ,
88
90
) -> Option < ( ) > {
89
91
let target = attr. syntax ( ) . text_range ( ) ;
@@ -92,25 +94,62 @@ fn add_assist(
92
94
let trait_name = trait_path. segment ( ) . and_then ( |seg| seg. name_ref ( ) ) ?;
93
95
94
96
acc. add ( AssistId ( "add_custom_impl" , AssistKind :: Refactor ) , label, target, |builder| {
97
+ let impl_def_with_items =
98
+ impl_def_from_trait ( & ctx. sema , annotated_name, trait_, trait_path) ;
95
99
update_attribute ( builder, & input, & trait_name, & attr) ;
96
- match snippet_cap {
97
- Some ( cap) => {
100
+ match ( ctx. config . snippet_cap , impl_def_with_items) {
101
+ ( None , _) => builder. insert (
102
+ insert_pos,
103
+ format ! ( "\n \n impl {} for {} {{\n \n }}" , trait_path, annotated_name) ,
104
+ ) ,
105
+ ( Some ( cap) , None ) => builder. insert_snippet (
106
+ cap,
107
+ insert_pos,
108
+ format ! ( "\n \n impl {} for {} {{\n $0\n }}" , trait_path, annotated_name) ,
109
+ ) ,
110
+ ( Some ( cap) , Some ( ( impl_def, first_assoc_item) ) ) => {
111
+ let mut cursor = Cursor :: Before ( first_assoc_item. syntax ( ) ) ;
112
+ let placeholder;
113
+ if let ast:: AssocItem :: Fn ( ref func) = first_assoc_item {
114
+ if let Some ( m) = func. syntax ( ) . descendants ( ) . find_map ( ast:: MacroCall :: cast) {
115
+ if m. syntax ( ) . text ( ) == "todo!()" {
116
+ placeholder = m;
117
+ cursor = Cursor :: Replace ( placeholder. syntax ( ) ) ;
118
+ }
119
+ }
120
+ }
121
+
98
122
builder. insert_snippet (
99
123
cap,
100
124
insert_pos,
101
- format ! ( "\n \n impl {} for {} {{\n $0\n }}" , trait_path, annotated_name) ,
102
- ) ;
103
- }
104
- None => {
105
- builder. insert (
106
- insert_pos,
107
- format ! ( "\n \n impl {} for {} {{\n \n }}" , trait_path, annotated_name) ,
108
- ) ;
125
+ format ! ( "\n \n {}" , render_snippet( cap, impl_def. syntax( ) , cursor) ) ,
126
+ )
109
127
}
110
- }
128
+ } ;
111
129
} )
112
130
}
113
131
132
+ fn impl_def_from_trait (
133
+ sema : & hir:: Semantics < ide_db:: RootDatabase > ,
134
+ annotated_name : & ast:: Name ,
135
+ trait_ : Option < hir:: Trait > ,
136
+ trait_path : & ast:: Path ,
137
+ ) -> Option < ( ast:: Impl , ast:: AssocItem ) > {
138
+ let trait_ = trait_?;
139
+ let target_scope = sema. scope ( annotated_name. syntax ( ) ) ;
140
+ let trait_items = filter_assoc_items ( sema. db , & trait_. items ( sema. db ) , DefaultMethods :: No ) ;
141
+ if trait_items. is_empty ( ) {
142
+ return None ;
143
+ }
144
+ let impl_def = make:: impl_trait (
145
+ trait_path. clone ( ) ,
146
+ make:: path_unqualified ( make:: path_segment ( make:: name_ref ( annotated_name. text ( ) ) ) ) ,
147
+ ) ;
148
+ let ( impl_def, first_assoc_item) =
149
+ add_trait_assoc_items_to_impl ( sema, trait_items, trait_, impl_def, target_scope) ;
150
+ Some ( ( impl_def, first_assoc_item) )
151
+ }
152
+
114
153
fn update_attribute (
115
154
builder : & mut AssistBuilder ,
116
155
input : & ast:: TokenTree ,
@@ -133,13 +172,14 @@ fn update_attribute(
133
172
let attr_range = attr. syntax ( ) . text_range ( ) ;
134
173
builder. delete ( attr_range) ;
135
174
136
- let line_break_range = attr
175
+ if let Some ( line_break_range) = attr
137
176
. syntax ( )
138
177
. next_sibling_or_token ( )
139
178
. filter ( |t| t. kind ( ) == WHITESPACE )
140
179
. map ( |t| t. text_range ( ) )
141
- . unwrap_or_else ( || TextRange :: new ( TextSize :: from ( 0 ) , TextSize :: from ( 0 ) ) ) ;
142
- builder. delete ( line_break_range) ;
180
+ {
181
+ builder. delete ( line_break_range) ;
182
+ }
143
183
}
144
184
}
145
185
@@ -150,12 +190,17 @@ mod tests {
150
190
use super :: * ;
151
191
152
192
#[ test]
153
- fn add_custom_impl_qualified ( ) {
193
+ fn add_custom_impl_debug ( ) {
154
194
check_assist (
155
195
add_custom_impl,
156
196
"
157
197
mod fmt {
158
- pub trait Debug {}
198
+ pub struct Error;
199
+ pub type Result = Result<(), Error>;
200
+ pub struct Formatter<'a>;
201
+ pub trait Debug {
202
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result;
203
+ }
159
204
}
160
205
161
206
#[derive(Debu<|>g)]
@@ -165,15 +210,71 @@ struct Foo {
165
210
" ,
166
211
"
167
212
mod fmt {
168
- pub trait Debug {}
213
+ pub struct Error;
214
+ pub type Result = Result<(), Error>;
215
+ pub struct Formatter<'a>;
216
+ pub trait Debug {
217
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result;
218
+ }
169
219
}
170
220
171
221
struct Foo {
172
222
bar: String,
173
223
}
174
224
175
225
impl fmt::Debug for Foo {
176
- $0
226
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227
+ ${0:todo!()}
228
+ }
229
+ }
230
+ " ,
231
+ )
232
+ }
233
+ #[ test]
234
+ fn add_custom_impl_all ( ) {
235
+ check_assist (
236
+ add_custom_impl,
237
+ "
238
+ mod foo {
239
+ pub trait Bar {
240
+ type Qux;
241
+ const Baz: usize = 42;
242
+ const Fez: usize;
243
+ fn foo();
244
+ fn bar() {}
245
+ }
246
+ }
247
+
248
+ #[derive(<|>Bar)]
249
+ struct Foo {
250
+ bar: String,
251
+ }
252
+ " ,
253
+ "
254
+ mod foo {
255
+ pub trait Bar {
256
+ type Qux;
257
+ const Baz: usize = 42;
258
+ const Fez: usize;
259
+ fn foo();
260
+ fn bar() {}
261
+ }
262
+ }
263
+
264
+ struct Foo {
265
+ bar: String,
266
+ }
267
+
268
+ impl foo::Bar for Foo {
269
+ $0type Qux;
270
+
271
+ const Baz: usize = 42;
272
+
273
+ const Fez: usize;
274
+
275
+ fn foo() {
276
+ todo!()
277
+ }
177
278
}
178
279
" ,
179
280
)
0 commit comments