@@ -4,14 +4,16 @@ use hir::{ModuleSource, Semantics};
4
4
use ra_db:: { RelativePath , RelativePathBuf , SourceDatabaseExt } ;
5
5
use ra_ide_db:: RootDatabase ;
6
6
use ra_syntax:: {
7
- algo:: find_node_at_offset, ast, lex_single_valid_syntax_kind, AstNode , SyntaxKind , SyntaxNode ,
7
+ algo:: find_node_at_offset, ast, ast:: TypeAscriptionOwner , lex_single_valid_syntax_kind,
8
+ AstNode , SyntaxKind , SyntaxNode , SyntaxToken ,
8
9
} ;
9
10
use ra_text_edit:: TextEdit ;
11
+ use std:: convert:: TryInto ;
10
12
use test_utils:: tested_by;
11
13
12
14
use crate :: {
13
15
references:: find_all_refs, FilePosition , FileSystemEdit , RangeInfo , Reference , ReferenceKind ,
14
- SourceChange , SourceFileEdit , TextRange ,
16
+ SourceChange , SourceFileEdit , TextRange , TextSize ,
15
17
} ;
16
18
17
19
pub ( crate ) fn rename (
@@ -21,17 +23,21 @@ pub(crate) fn rename(
21
23
) -> Option < RangeInfo < SourceChange > > {
22
24
match lex_single_valid_syntax_kind ( new_name) ? {
23
25
SyntaxKind :: IDENT | SyntaxKind :: UNDERSCORE => ( ) ,
26
+ SyntaxKind :: SELF_KW => return rename_to_self ( db, position) ,
24
27
_ => return None ,
25
28
}
26
29
27
30
let sema = Semantics :: new ( db) ;
28
31
let source_file = sema. parse ( position. file_id ) ;
29
- if let Some ( ( ast_name, ast_module) ) =
30
- find_name_and_module_at_offset ( source_file. syntax ( ) , position)
31
- {
32
+ let syntax = source_file. syntax ( ) ;
33
+ if let Some ( ( ast_name, ast_module) ) = find_name_and_module_at_offset ( syntax, position) {
32
34
let range = ast_name. syntax ( ) . text_range ( ) ;
33
35
rename_mod ( & sema, & ast_name, & ast_module, position, new_name)
34
36
. map ( |info| RangeInfo :: new ( range, info) )
37
+ } else if let Some ( self_token) =
38
+ syntax. token_at_offset ( position. offset ) . find ( |t| t. kind ( ) == SyntaxKind :: SELF_KW )
39
+ {
40
+ rename_self_to_param ( db, position, self_token, new_name)
35
41
} else {
36
42
rename_reference ( sema. db , position, new_name)
37
43
}
@@ -125,6 +131,112 @@ fn rename_mod(
125
131
Some ( SourceChange :: from_edits ( "Rename" , source_file_edits, file_system_edits) )
126
132
}
127
133
134
+ fn rename_to_self ( db : & RootDatabase , position : FilePosition ) -> Option < RangeInfo < SourceChange > > {
135
+ let sema = Semantics :: new ( db) ;
136
+ let source_file = sema. parse ( position. file_id ) ;
137
+ let syn = source_file. syntax ( ) ;
138
+
139
+ let fn_def = find_node_at_offset :: < ast:: FnDef > ( syn, position. offset ) ?;
140
+ let params = fn_def. param_list ( ) ?;
141
+ if params. self_param ( ) . is_some ( ) {
142
+ return None ; // method already has self param
143
+ }
144
+ let first_param = params. params ( ) . next ( ) ?;
145
+ let mutable = match first_param. ascribed_type ( ) {
146
+ Some ( ast:: TypeRef :: ReferenceType ( rt) ) => rt. mut_token ( ) . is_some ( ) ,
147
+ _ => return None , // not renaming other types
148
+ } ;
149
+
150
+ let RangeInfo { range, info : refs } = find_all_refs ( db, position, None ) ?;
151
+
152
+ let param_range = first_param. syntax ( ) . text_range ( ) ;
153
+ let ( param_ref, usages) : ( Vec < Reference > , Vec < Reference > ) = refs
154
+ . into_iter ( )
155
+ . partition ( |reference| param_range. intersect ( reference. file_range . range ) . is_some ( ) ) ;
156
+
157
+ if param_ref. is_empty ( ) {
158
+ return None ;
159
+ }
160
+
161
+ let mut edits = usages
162
+ . into_iter ( )
163
+ . map ( |reference| source_edit_from_reference ( reference, "self" ) )
164
+ . collect :: < Vec < _ > > ( ) ;
165
+
166
+ edits. push ( SourceFileEdit {
167
+ file_id : position. file_id ,
168
+ edit : TextEdit :: replace (
169
+ param_range,
170
+ String :: from ( if mutable { "&mut self" } else { "&self" } ) ,
171
+ ) ,
172
+ } ) ;
173
+
174
+ Some ( RangeInfo :: new ( range, SourceChange :: source_file_edits ( "Rename" , edits) ) )
175
+ }
176
+
177
+ fn text_edit_from_self_param (
178
+ syn : & SyntaxNode ,
179
+ self_param : & ast:: SelfParam ,
180
+ new_name : & str ,
181
+ ) -> Option < TextEdit > {
182
+ fn target_type_name ( impl_def : & ast:: ImplDef ) -> Option < String > {
183
+ if let Some ( ast:: TypeRef :: PathType ( p) ) = impl_def. target_type ( ) {
184
+ return Some ( p. path ( ) ?. segment ( ) ?. name_ref ( ) ?. text ( ) . to_string ( ) ) ;
185
+ }
186
+ None
187
+ }
188
+
189
+ let impl_def =
190
+ find_node_at_offset :: < ast:: ImplDef > ( syn, self_param. syntax ( ) . text_range ( ) . start ( ) ) ?;
191
+ let type_name = target_type_name ( & impl_def) ?;
192
+
193
+ let mut replacement_text = String :: from ( new_name) ;
194
+ replacement_text. push_str ( ": " ) ;
195
+ replacement_text. push_str ( self_param. mut_token ( ) . map_or ( "&" , |_| "&mut " ) ) ;
196
+ replacement_text. push_str ( type_name. as_str ( ) ) ;
197
+
198
+ Some ( TextEdit :: replace ( self_param. syntax ( ) . text_range ( ) , replacement_text) )
199
+ }
200
+
201
+ fn rename_self_to_param (
202
+ db : & RootDatabase ,
203
+ position : FilePosition ,
204
+ self_token : SyntaxToken ,
205
+ new_name : & str ,
206
+ ) -> Option < RangeInfo < SourceChange > > {
207
+ let sema = Semantics :: new ( db) ;
208
+ let source_file = sema. parse ( position. file_id ) ;
209
+ let syn = source_file. syntax ( ) ;
210
+
211
+ let text = db. file_text ( position. file_id ) ;
212
+ let fn_def = find_node_at_offset :: < ast:: FnDef > ( syn, position. offset ) ?;
213
+ let search_range = fn_def. syntax ( ) . text_range ( ) ;
214
+
215
+ let mut edits: Vec < SourceFileEdit > = vec ! [ ] ;
216
+
217
+ for ( idx, _) in text. match_indices ( "self" ) {
218
+ let offset: TextSize = idx. try_into ( ) . unwrap ( ) ;
219
+ if !search_range. contains_inclusive ( offset) {
220
+ continue ;
221
+ }
222
+ if let Some ( ref usage) =
223
+ syn. token_at_offset ( offset) . find ( |t| t. kind ( ) == SyntaxKind :: SELF_KW )
224
+ {
225
+ let edit = if let Some ( ref self_param) = ast:: SelfParam :: cast ( usage. parent ( ) ) {
226
+ text_edit_from_self_param ( syn, self_param, new_name) ?
227
+ } else {
228
+ TextEdit :: replace ( usage. text_range ( ) , String :: from ( new_name) )
229
+ } ;
230
+ edits. push ( SourceFileEdit { file_id : position. file_id , edit } ) ;
231
+ }
232
+ }
233
+
234
+ let range = ast:: SelfParam :: cast ( self_token. parent ( ) )
235
+ . map_or ( self_token. text_range ( ) , |p| p. syntax ( ) . text_range ( ) ) ;
236
+
237
+ Some ( RangeInfo :: new ( range, SourceChange :: source_file_edits ( "Rename" , edits) ) )
238
+ }
239
+
128
240
fn rename_reference (
129
241
db : & RootDatabase ,
130
242
position : FilePosition ,
@@ -774,6 +886,95 @@ mod tests {
774
886
) ;
775
887
}
776
888
889
+ #[ test]
890
+ fn test_parameter_to_self ( ) {
891
+ test_rename (
892
+ r#"
893
+ struct Foo {
894
+ i: i32,
895
+ }
896
+
897
+ impl Foo {
898
+ fn f(foo<|>: &mut Foo) -> i32 {
899
+ foo.i
900
+ }
901
+ }
902
+ "# ,
903
+ "self" ,
904
+ r#"
905
+ struct Foo {
906
+ i: i32,
907
+ }
908
+
909
+ impl Foo {
910
+ fn f(&mut self) -> i32 {
911
+ self.i
912
+ }
913
+ }
914
+ "# ,
915
+ ) ;
916
+ }
917
+
918
+ #[ test]
919
+ fn test_self_to_parameter ( ) {
920
+ test_rename (
921
+ r#"
922
+ struct Foo {
923
+ i: i32,
924
+ }
925
+
926
+ impl Foo {
927
+ fn f(&mut <|>self) -> i32 {
928
+ self.i
929
+ }
930
+ }
931
+ "# ,
932
+ "foo" ,
933
+ r#"
934
+ struct Foo {
935
+ i: i32,
936
+ }
937
+
938
+ impl Foo {
939
+ fn f(foo: &mut Foo) -> i32 {
940
+ foo.i
941
+ }
942
+ }
943
+ "# ,
944
+ ) ;
945
+ }
946
+
947
+ #[ test]
948
+ fn test_self_in_path_to_parameter ( ) {
949
+ test_rename (
950
+ r#"
951
+ struct Foo {
952
+ i: i32,
953
+ }
954
+
955
+ impl Foo {
956
+ fn f(&self) -> i32 {
957
+ let self_var = 1;
958
+ self<|>.i
959
+ }
960
+ }
961
+ "# ,
962
+ "foo" ,
963
+ r#"
964
+ struct Foo {
965
+ i: i32,
966
+ }
967
+
968
+ impl Foo {
969
+ fn f(foo: &Foo) -> i32 {
970
+ let self_var = 1;
971
+ foo.i
972
+ }
973
+ }
974
+ "# ,
975
+ ) ;
976
+ }
977
+
777
978
fn test_rename ( text : & str , new_name : & str , expected : & str ) {
778
979
let ( analysis, position) = single_file_with_position ( text) ;
779
980
let source_change = analysis. rename ( position, new_name) . unwrap ( ) ;
0 commit comments