@@ -4,14 +4,16 @@ use hir::{ModuleSource, Semantics};
44use ra_db:: { RelativePath , RelativePathBuf , SourceDatabaseExt } ;
55use ra_ide_db:: RootDatabase ;
66use 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 ,
89} ;
910use ra_text_edit:: TextEdit ;
11+ use std:: convert:: TryInto ;
1012use test_utils:: tested_by;
1113
1214use crate :: {
1315 references:: find_all_refs, FilePosition , FileSystemEdit , RangeInfo , Reference , ReferenceKind ,
14- SourceChange , SourceFileEdit , TextRange ,
16+ SourceChange , SourceFileEdit , TextRange , TextSize ,
1517} ;
1618
1719pub ( crate ) fn rename (
@@ -21,17 +23,21 @@ pub(crate) fn rename(
2123) -> Option < RangeInfo < SourceChange > > {
2224 match lex_single_valid_syntax_kind ( new_name) ? {
2325 SyntaxKind :: IDENT | SyntaxKind :: UNDERSCORE => ( ) ,
26+ SyntaxKind :: SELF_KW => return rename_to_self ( db, position) ,
2427 _ => return None ,
2528 }
2629
2730 let sema = Semantics :: new ( db) ;
2831 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) {
3234 let range = ast_name. syntax ( ) . text_range ( ) ;
3335 rename_mod ( & sema, & ast_name, & ast_module, position, new_name)
3436 . 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)
3541 } else {
3642 rename_reference ( sema. db , position, new_name)
3743 }
@@ -125,6 +131,112 @@ fn rename_mod(
125131 Some ( SourceChange :: from_edits ( "Rename" , source_file_edits, file_system_edits) )
126132}
127133
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+
128240fn rename_reference (
129241 db : & RootDatabase ,
130242 position : FilePosition ,
@@ -774,6 +886,95 @@ mod tests {
774886 ) ;
775887 }
776888
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+
777978 fn test_rename ( text : & str , new_name : & str , expected : & str ) {
778979 let ( analysis, position) = single_file_with_position ( text) ;
779980 let source_change = analysis. rename ( position, new_name) . unwrap ( ) ;
0 commit comments