@@ -4,7 +4,7 @@ use ra_syntax::{
4
4
} ;
5
5
6
6
use crate :: { Assist , AssistCtx , AssistId } ;
7
- use ast:: { edit:: IndentLevel , ArgListOwner , CallExpr , Expr } ;
7
+ use ast:: { edit:: IndentLevel , ArgListOwner , ModuleItemOwner } ;
8
8
use hir:: HirDisplay ;
9
9
use rustc_hash:: { FxHashMap , FxHashSet } ;
10
10
@@ -16,7 +16,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
16
16
// struct Baz;
17
17
// fn baz() -> Baz { Baz }
18
18
// fn foo() {
19
- // bar<|>("", baz());
19
+ // bar<|>("", baz());
20
20
// }
21
21
//
22
22
// ```
@@ -25,7 +25,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
25
25
// struct Baz;
26
26
// fn baz() -> Baz { Baz }
27
27
// fn foo() {
28
- // bar("", baz());
28
+ // bar("", baz());
29
29
// }
30
30
//
31
31
// fn bar(arg: &str, baz: Baz) {
@@ -38,16 +38,24 @@ pub(crate) fn add_function(ctx: AssistCtx) -> Option<Assist> {
38
38
let call = path_expr. syntax ( ) . parent ( ) . and_then ( ast:: CallExpr :: cast) ?;
39
39
let path = path_expr. path ( ) ?;
40
40
41
- if path. qualifier ( ) . is_some ( ) {
42
- return None ;
43
- }
44
-
45
41
if ctx. sema . resolve_path ( & path) . is_some ( ) {
46
42
// The function call already resolves, no need to add a function
47
43
return None ;
48
44
}
49
45
50
- let function_builder = FunctionBuilder :: from_call ( & ctx, & call) ?;
46
+ let target_module = if let Some ( qualifier) = path. qualifier ( ) {
47
+ if let Some ( hir:: PathResolution :: Def ( hir:: ModuleDef :: Module ( resolved) ) ) =
48
+ ctx. sema . resolve_path ( & qualifier)
49
+ {
50
+ Some ( resolved. definition_source ( ctx. sema . db ) . value )
51
+ } else {
52
+ return None ;
53
+ }
54
+ } else {
55
+ None
56
+ } ;
57
+
58
+ let function_builder = FunctionBuilder :: from_call ( & ctx, & call, & path, target_module) ?;
51
59
52
60
ctx. add_assist ( AssistId ( "add_function" ) , "Add function" , |edit| {
53
61
edit. target ( call. syntax ( ) . text_range ( ) ) ;
@@ -66,26 +74,54 @@ struct FunctionTemplate {
66
74
}
67
75
68
76
struct FunctionBuilder {
69
- append_fn_at : SyntaxNode ,
77
+ target : GeneratedFunctionTarget ,
70
78
fn_name : ast:: Name ,
71
79
type_params : Option < ast:: TypeParamList > ,
72
80
params : ast:: ParamList ,
73
81
}
74
82
75
83
impl FunctionBuilder {
76
- fn from_call ( ctx : & AssistCtx , call : & ast:: CallExpr ) -> Option < Self > {
77
- let append_fn_at = next_space_for_fn ( & call) ?;
78
- let fn_name = fn_name ( & call) ?;
84
+ /// Prepares a generated function that matches `call` in `generate_in`
85
+ /// (or as close to `call` as possible, if `generate_in` is `None`)
86
+ fn from_call (
87
+ ctx : & AssistCtx ,
88
+ call : & ast:: CallExpr ,
89
+ path : & ast:: Path ,
90
+ generate_in : Option < hir:: ModuleSource > ,
91
+ ) -> Option < Self > {
92
+ let target = if let Some ( generate_in_module) = generate_in {
93
+ next_space_for_fn_in_module ( generate_in_module) ?
94
+ } else {
95
+ next_space_for_fn_after_call_site ( & call) ?
96
+ } ;
97
+ let fn_name = fn_name ( & path) ?;
79
98
let ( type_params, params) = fn_args ( ctx, & call) ?;
80
- Some ( Self { append_fn_at , fn_name, type_params, params } )
99
+ Some ( Self { target , fn_name, type_params, params } )
81
100
}
82
101
fn render ( self ) -> Option < FunctionTemplate > {
83
102
let placeholder_expr = ast:: make:: expr_todo ( ) ;
84
103
let fn_body = ast:: make:: block_expr ( vec ! [ ] , Some ( placeholder_expr) ) ;
85
104
let fn_def = ast:: make:: fn_def ( self . fn_name , self . type_params , self . params , fn_body) ;
86
- let fn_def = ast:: make:: add_newlines ( 2 , fn_def) ;
87
- let fn_def = IndentLevel :: from_node ( & self . append_fn_at ) . increase_indent ( fn_def) ;
88
- let insert_offset = self . append_fn_at . text_range ( ) . end ( ) ;
105
+
106
+ let ( fn_def, insert_offset) = match self . target {
107
+ GeneratedFunctionTarget :: BehindItem ( it) => {
108
+ let with_leading_blank_line = ast:: make:: add_leading_newlines ( 2 , fn_def) ;
109
+ let indented = IndentLevel :: from_node ( & it) . increase_indent ( with_leading_blank_line) ;
110
+ ( indented, it. text_range ( ) . end ( ) )
111
+ }
112
+ GeneratedFunctionTarget :: InEmptyItemList ( it) => {
113
+ let with_leading_newline = ast:: make:: add_leading_newlines ( 1 , fn_def) ;
114
+ let indent = IndentLevel :: from_node ( it. syntax ( ) ) . indented ( ) ;
115
+ let mut indented = indent. increase_indent ( with_leading_newline) ;
116
+ if !item_list_has_whitespace ( & it) {
117
+ // In this case we want to make sure there's a newline between the closing
118
+ // function brace and the closing module brace (so it doesn't end in `}}`).
119
+ indented = ast:: make:: add_trailing_newlines ( 1 , indented) ;
120
+ }
121
+ ( indented, it. syntax ( ) . text_range ( ) . start ( ) + TextUnit :: from_usize ( 1 ) )
122
+ }
123
+ } ;
124
+
89
125
let cursor_offset_from_fn_start = fn_def
90
126
. syntax ( )
91
127
. descendants ( )
@@ -98,15 +134,25 @@ impl FunctionBuilder {
98
134
}
99
135
}
100
136
101
- fn fn_name ( call : & CallExpr ) -> Option < ast:: Name > {
102
- let name = call. expr ( ) ?. syntax ( ) . to_string ( ) ;
137
+ /// Returns true if the given ItemList contains whitespace.
138
+ fn item_list_has_whitespace ( it : & ast:: ItemList ) -> bool {
139
+ it. syntax ( ) . descendants_with_tokens ( ) . find ( |it| it. kind ( ) == SyntaxKind :: WHITESPACE ) . is_some ( )
140
+ }
141
+
142
+ enum GeneratedFunctionTarget {
143
+ BehindItem ( SyntaxNode ) ,
144
+ InEmptyItemList ( ast:: ItemList ) ,
145
+ }
146
+
147
+ fn fn_name ( call : & ast:: Path ) -> Option < ast:: Name > {
148
+ let name = call. segment ( ) ?. syntax ( ) . to_string ( ) ;
103
149
Some ( ast:: make:: name ( & name) )
104
150
}
105
151
106
152
/// Computes the type variables and arguments required for the generated function
107
153
fn fn_args (
108
154
ctx : & AssistCtx ,
109
- call : & CallExpr ,
155
+ call : & ast :: CallExpr ,
110
156
) -> Option < ( Option < ast:: TypeParamList > , ast:: ParamList ) > {
111
157
let mut arg_names = Vec :: new ( ) ;
112
158
let mut arg_types = Vec :: new ( ) ;
@@ -158,9 +204,9 @@ fn deduplicate_arg_names(arg_names: &mut Vec<String>) {
158
204
}
159
205
}
160
206
161
- fn fn_arg_name ( fn_arg : & Expr ) -> Option < String > {
207
+ fn fn_arg_name ( fn_arg : & ast :: Expr ) -> Option < String > {
162
208
match fn_arg {
163
- Expr :: CastExpr ( cast_expr) => fn_arg_name ( & cast_expr. expr ( ) ?) ,
209
+ ast :: Expr :: CastExpr ( cast_expr) => fn_arg_name ( & cast_expr. expr ( ) ?) ,
164
210
_ => Some (
165
211
fn_arg
166
212
. syntax ( )
@@ -172,7 +218,7 @@ fn fn_arg_name(fn_arg: &Expr) -> Option<String> {
172
218
}
173
219
}
174
220
175
- fn fn_arg_type ( ctx : & AssistCtx , fn_arg : & Expr ) -> Option < String > {
221
+ fn fn_arg_type ( ctx : & AssistCtx , fn_arg : & ast :: Expr ) -> Option < String > {
176
222
let ty = ctx. sema . type_of_expr ( fn_arg) ?;
177
223
if ty. is_unknown ( ) {
178
224
return None ;
@@ -184,7 +230,7 @@ fn fn_arg_type(ctx: &AssistCtx, fn_arg: &Expr) -> Option<String> {
184
230
/// directly after the current block
185
231
/// We want to write the generated function directly after
186
232
/// fns, impls or macro calls, but inside mods
187
- fn next_space_for_fn ( expr : & CallExpr ) -> Option < SyntaxNode > {
233
+ fn next_space_for_fn_after_call_site ( expr : & ast :: CallExpr ) -> Option < GeneratedFunctionTarget > {
188
234
let mut ancestors = expr. syntax ( ) . ancestors ( ) . peekable ( ) ;
189
235
let mut last_ancestor: Option < SyntaxNode > = None ;
190
236
while let Some ( next_ancestor) = ancestors. next ( ) {
@@ -201,7 +247,26 @@ fn next_space_for_fn(expr: &CallExpr) -> Option<SyntaxNode> {
201
247
}
202
248
last_ancestor = Some ( next_ancestor) ;
203
249
}
204
- last_ancestor
250
+ last_ancestor. map ( GeneratedFunctionTarget :: BehindItem )
251
+ }
252
+
253
+ fn next_space_for_fn_in_module ( module : hir:: ModuleSource ) -> Option < GeneratedFunctionTarget > {
254
+ match module {
255
+ hir:: ModuleSource :: SourceFile ( it) => {
256
+ if let Some ( last_item) = it. items ( ) . last ( ) {
257
+ Some ( GeneratedFunctionTarget :: BehindItem ( last_item. syntax ( ) . clone ( ) ) )
258
+ } else {
259
+ Some ( GeneratedFunctionTarget :: BehindItem ( it. syntax ( ) . clone ( ) ) )
260
+ }
261
+ }
262
+ hir:: ModuleSource :: Module ( it) => {
263
+ if let Some ( last_item) = it. item_list ( ) . and_then ( |it| it. items ( ) . last ( ) ) {
264
+ Some ( GeneratedFunctionTarget :: BehindItem ( last_item. syntax ( ) . clone ( ) ) )
265
+ } else {
266
+ it. item_list ( ) . map ( GeneratedFunctionTarget :: InEmptyItemList )
267
+ }
268
+ }
269
+ }
205
270
}
206
271
207
272
#[ cfg( test) ]
@@ -713,6 +778,112 @@ fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) {
713
778
)
714
779
}
715
780
781
+ #[ test]
782
+ fn add_function_in_module ( ) {
783
+ check_assist (
784
+ add_function,
785
+ r"
786
+ mod bar {}
787
+
788
+ fn foo() {
789
+ bar::my_fn<|>()
790
+ }
791
+ " ,
792
+ r"
793
+ mod bar {
794
+ fn my_fn() {
795
+ <|>todo!()
796
+ }
797
+ }
798
+
799
+ fn foo() {
800
+ bar::my_fn()
801
+ }
802
+ " ,
803
+ ) ;
804
+ check_assist (
805
+ add_function,
806
+ r"
807
+ mod bar {
808
+ }
809
+
810
+ fn foo() {
811
+ bar::my_fn<|>()
812
+ }
813
+ " ,
814
+ r"
815
+ mod bar {
816
+ fn my_fn() {
817
+ <|>todo!()
818
+ }
819
+ }
820
+
821
+ fn foo() {
822
+ bar::my_fn()
823
+ }
824
+ " ,
825
+ )
826
+ }
827
+
828
+ #[ test]
829
+ fn add_function_in_module_containing_other_items ( ) {
830
+ check_assist (
831
+ add_function,
832
+ r"
833
+ mod bar {
834
+ fn something_else() {}
835
+ }
836
+
837
+ fn foo() {
838
+ bar::my_fn<|>()
839
+ }
840
+ " ,
841
+ r"
842
+ mod bar {
843
+ fn something_else() {}
844
+
845
+ fn my_fn() {
846
+ <|>todo!()
847
+ }
848
+ }
849
+
850
+ fn foo() {
851
+ bar::my_fn()
852
+ }
853
+ " ,
854
+ )
855
+ }
856
+
857
+ #[ test]
858
+ fn add_function_in_nested_module ( ) {
859
+ check_assist (
860
+ add_function,
861
+ r"
862
+ mod bar {
863
+ mod baz {
864
+ }
865
+ }
866
+
867
+ fn foo() {
868
+ bar::baz::my_fn<|>()
869
+ }
870
+ " ,
871
+ r"
872
+ mod bar {
873
+ mod baz {
874
+ fn my_fn() {
875
+ <|>todo!()
876
+ }
877
+ }
878
+ }
879
+
880
+ fn foo() {
881
+ bar::baz::my_fn()
882
+ }
883
+ " ,
884
+ )
885
+ }
886
+
716
887
#[ test]
717
888
fn add_function_not_applicable_if_function_already_exists ( ) {
718
889
check_assist_not_applicable (
0 commit comments