@@ -30,7 +30,7 @@ use syntax::{
3030 ast:: { self , NameOwner } ,
3131 lex_single_syntax_kind, AstNode , SyntaxKind , TextRange , T ,
3232} ;
33- use text_edit:: TextEdit ;
33+ use text_edit:: { TextEdit , TextEditBuilder } ;
3434
3535use crate :: {
3636 defs:: Definition ,
@@ -303,141 +303,187 @@ pub fn source_edit_from_references(
303303) -> TextEdit {
304304 let mut edit = TextEdit :: builder ( ) ;
305305 for reference in references {
306- let ( range , replacement ) = match & reference. name {
306+ let has_emitted_edit = match & reference. name {
307307 // if the ranges differ then the node is inside a macro call, we can't really attempt
308308 // to make special rewrites like shorthand syntax and such, so just rename the node in
309309 // the macro input
310310 ast:: NameLike :: NameRef ( name_ref)
311311 if name_ref. syntax ( ) . text_range ( ) == reference. range =>
312312 {
313- source_edit_from_name_ref ( name_ref, new_name, def)
313+ source_edit_from_name_ref ( & mut edit , name_ref, new_name, def)
314314 }
315315 ast:: NameLike :: Name ( name) if name. syntax ( ) . text_range ( ) == reference. range => {
316- source_edit_from_name ( name, new_name)
316+ source_edit_from_name ( & mut edit , name, new_name)
317317 }
318- _ => None ,
318+ _ => false ,
319+ } ;
320+ if !has_emitted_edit {
321+ edit. replace ( reference. range , new_name. to_string ( ) ) ;
319322 }
320- . unwrap_or_else ( || ( reference. range , new_name. to_string ( ) ) ) ;
321- edit. replace ( range, replacement) ;
322323 }
324+
323325 edit. finish ( )
324326}
325327
326- fn source_edit_from_name ( name : & ast:: Name , new_name : & str ) -> Option < ( TextRange , String ) > {
328+ fn source_edit_from_name ( edit : & mut TextEditBuilder , name : & ast:: Name , new_name : & str ) -> bool {
327329 if let Some ( _) = ast:: RecordPatField :: for_field_name ( name) {
328- // FIXME: instead of splitting the shorthand, recursively trigger a rename of the
329- // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547
330330 if let Some ( ident_pat) = name. syntax ( ) . parent ( ) . and_then ( ast:: IdentPat :: cast) {
331- return Some ( (
332- TextRange :: empty ( ident_pat. syntax ( ) . text_range ( ) . start ( ) ) ,
333- [ new_name, ": " ] . concat ( ) ,
334- ) ) ;
331+ cov_mark:: hit!( rename_record_pat_field_name_split) ;
332+ // Foo { ref mut field } -> Foo { new_name: ref mut field }
333+ // ^ insert `new_name: `
334+
335+ // FIXME: instead of splitting the shorthand, recursively trigger a rename of the
336+ // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547
337+ edit. insert ( ident_pat. syntax ( ) . text_range ( ) . start ( ) , format ! ( "{}: " , new_name) ) ;
338+ return true ;
335339 }
336340 }
337- None
341+
342+ false
338343}
339344
340345fn source_edit_from_name_ref (
346+ edit : & mut TextEditBuilder ,
341347 name_ref : & ast:: NameRef ,
342348 new_name : & str ,
343349 def : Definition ,
344- ) -> Option < ( TextRange , String ) > {
350+ ) -> bool {
345351 if let Some ( record_field) = ast:: RecordExprField :: for_name_ref ( name_ref) {
346352 let rcf_name_ref = record_field. name_ref ( ) ;
347353 let rcf_expr = record_field. expr ( ) ;
348- match ( rcf_name_ref, rcf_expr. and_then ( |it| it. name_ref ( ) ) ) {
354+ match & ( rcf_name_ref, rcf_expr. and_then ( |it| it. name_ref ( ) ) ) {
349355 // field: init-expr, check if we can use a field init shorthand
350356 ( Some ( field_name) , Some ( init) ) => {
351- if field_name == * name_ref {
357+ if field_name == name_ref {
352358 if init. text ( ) == new_name {
353359 cov_mark:: hit!( test_rename_field_put_init_shorthand) ;
360+ // Foo { field: local } -> Foo { local }
361+ // ^^^^^^^ delete this
362+
354363 // same names, we can use a shorthand here instead.
355364 // we do not want to erase attributes hence this range start
356365 let s = field_name. syntax ( ) . text_range ( ) . start ( ) ;
357- let e = record_field. syntax ( ) . text_range ( ) . end ( ) ;
358- return Some ( ( TextRange :: new ( s, e) , new_name. to_owned ( ) ) ) ;
366+ let e = init. syntax ( ) . text_range ( ) . start ( ) ;
367+ edit. delete ( TextRange :: new ( s, e) ) ;
368+ return true ;
359369 }
360- } else if init == * name_ref {
370+ } else if init == name_ref {
361371 if field_name. text ( ) == new_name {
362372 cov_mark:: hit!( test_rename_local_put_init_shorthand) ;
373+ // Foo { field: local } -> Foo { field }
374+ // ^^^^^^^ delete this
375+
363376 // same names, we can use a shorthand here instead.
364377 // we do not want to erase attributes hence this range start
365- let s = field_name. syntax ( ) . text_range ( ) . start ( ) ;
366- let e = record_field. syntax ( ) . text_range ( ) . end ( ) ;
367- return Some ( ( TextRange :: new ( s, e) , new_name. to_owned ( ) ) ) ;
378+ let s = field_name. syntax ( ) . text_range ( ) . end ( ) ;
379+ let e = init. syntax ( ) . text_range ( ) . end ( ) ;
380+ edit. delete ( TextRange :: new ( s, e) ) ;
381+ return true ;
368382 }
369383 }
370- None
371384 }
372385 // init shorthand
373386 ( None , Some ( _) ) if matches ! ( def, Definition :: Field ( _) ) => {
374387 cov_mark:: hit!( test_rename_field_in_field_shorthand) ;
375- let s = name_ref. syntax ( ) . text_range ( ) . start ( ) ;
376- Some ( ( TextRange :: empty ( s) , format ! ( "{}: " , new_name) ) )
388+ // Foo { field } -> Foo { new_name: field }
389+ // ^ insert `new_name: `
390+ let offset = name_ref. syntax ( ) . text_range ( ) . start ( ) ;
391+ edit. insert ( offset, format ! ( "{}: " , new_name) ) ;
392+ return true ;
377393 }
378394 ( None , Some ( _) ) if matches ! ( def, Definition :: Local ( _) ) => {
379395 cov_mark:: hit!( test_rename_local_in_field_shorthand) ;
380- let s = name_ref. syntax ( ) . text_range ( ) . end ( ) ;
381- Some ( ( TextRange :: empty ( s) , format ! ( ": {}" , new_name) ) )
396+ // Foo { field } -> Foo { field: new_name }
397+ // ^ insert `: new_name`
398+ let offset = name_ref. syntax ( ) . text_range ( ) . end ( ) ;
399+ edit. insert ( offset, format ! ( ": {}" , new_name) ) ;
400+ return true ;
382401 }
383- _ => None ,
402+ _ => ( ) ,
384403 }
385404 } else if let Some ( record_field) = ast:: RecordPatField :: for_field_name_ref ( name_ref) {
386405 let rcf_name_ref = record_field. name_ref ( ) ;
387406 let rcf_pat = record_field. pat ( ) ;
388407 match ( rcf_name_ref, rcf_pat) {
389408 // field: rename
390- ( Some ( field_name) , Some ( ast:: Pat :: IdentPat ( pat) ) ) if field_name == * name_ref => {
409+ ( Some ( field_name) , Some ( ast:: Pat :: IdentPat ( pat) ) )
410+ if field_name == * name_ref && pat. at_token ( ) . is_none ( ) =>
411+ {
391412 // field name is being renamed
392- if pat. name ( ) . map_or ( false , |it| it. text ( ) == new_name) {
393- cov_mark:: hit!( test_rename_field_put_init_shorthand_pat) ;
394- // same names, we can use a shorthand here instead/
395- // we do not want to erase attributes hence this range start
396- let s = field_name. syntax ( ) . text_range ( ) . start ( ) ;
397- let e = record_field. syntax ( ) . text_range ( ) . end ( ) ;
398- Some ( ( TextRange :: new ( s, e) , pat. to_string ( ) ) )
399- } else {
400- None
413+ if let Some ( name) = pat. name ( ) {
414+ if name. text ( ) == new_name {
415+ cov_mark:: hit!( test_rename_field_put_init_shorthand_pat) ;
416+ // Foo { field: ref mut local } -> Foo { ref mut field }
417+ // ^^^^^^^ delete this
418+ // ^^^^^ replace this with `field`
419+
420+ // same names, we can use a shorthand here instead/
421+ // we do not want to erase attributes hence this range start
422+ let s = field_name. syntax ( ) . text_range ( ) . start ( ) ;
423+ let e = pat. syntax ( ) . text_range ( ) . start ( ) ;
424+ edit. delete ( TextRange :: new ( s, e) ) ;
425+ edit. replace ( name. syntax ( ) . text_range ( ) , new_name. to_string ( ) ) ;
426+ return true ;
427+ }
401428 }
402429 }
403- _ => None ,
430+ _ => ( ) ,
404431 }
405- } else {
406- None
407432 }
433+ false
408434}
409435
410436fn source_edit_from_def (
411437 sema : & Semantics < RootDatabase > ,
412438 def : Definition ,
413439 new_name : & str ,
414440) -> Result < ( FileId , TextEdit ) > {
415- let frange = def
441+ let FileRange { file_id , range } = def
416442 . range_for_rename ( sema)
417443 . ok_or_else ( || format_err ! ( "No identifier available to rename" ) ) ?;
418444
419- let mut replacement_text = String :: new ( ) ;
420- let mut repl_range = frange. range ;
445+ let mut edit = TextEdit :: builder ( ) ;
421446 if let Definition :: Local ( local) = def {
422447 if let Either :: Left ( pat) = local. source ( sema. db ) . value {
423- if matches ! (
424- pat. syntax( ) . parent( ) . and_then( ast:: RecordPatField :: cast) ,
425- Some ( pat_field) if pat_field. name_ref( ) . is_none( )
426- ) {
427- replacement_text. push_str ( ": " ) ;
428- replacement_text. push_str ( new_name) ;
429- repl_range = TextRange :: new (
430- pat. syntax ( ) . text_range ( ) . end ( ) ,
431- pat. syntax ( ) . text_range ( ) . end ( ) ,
432- ) ;
448+ // special cases required for renaming fields/locals in Record patterns
449+ if let Some ( pat_field) = pat. syntax ( ) . parent ( ) . and_then ( ast:: RecordPatField :: cast) {
450+ let name_range = pat. name ( ) . unwrap ( ) . syntax ( ) . text_range ( ) ;
451+ if let Some ( name_ref) = pat_field. name_ref ( ) {
452+ if new_name == name_ref. text ( ) && pat. at_token ( ) . is_none ( ) {
453+ // Foo { field: ref mut local } -> Foo { ref mut field }
454+ // ^^^^^^ delete this
455+ // ^^^^^ replace this with `field`
456+ cov_mark:: hit!( test_rename_local_put_init_shorthand_pat) ;
457+ edit. delete (
458+ name_ref
459+ . syntax ( )
460+ . text_range ( )
461+ . cover_offset ( pat. syntax ( ) . text_range ( ) . start ( ) ) ,
462+ ) ;
463+ edit. replace ( name_range, name_ref. text ( ) . to_string ( ) ) ;
464+ } else {
465+ // Foo { field: ref mut local @ local 2} -> Foo { field: ref mut new_name @ local2 }
466+ // Foo { field: ref mut local } -> Foo { field: ref mut new_name }
467+ // ^^^^^ replace this with `new_name`
468+ edit. replace ( name_range, new_name. to_string ( ) ) ;
469+ }
470+ } else {
471+ // Foo { ref mut field } -> Foo { field: ref mut new_name }
472+ // ^ insert `field: `
473+ // ^^^^^ replace this with `new_name`
474+ edit. insert (
475+ pat. syntax ( ) . text_range ( ) . start ( ) ,
476+ format ! ( "{}: " , pat_field. field_name( ) . unwrap( ) ) ,
477+ ) ;
478+ edit. replace ( name_range, new_name. to_string ( ) ) ;
479+ }
433480 }
434481 }
435482 }
436- if replacement_text . is_empty ( ) {
437- replacement_text . push_str ( new_name) ;
483+ if edit . is_empty ( ) {
484+ edit . replace ( range , new_name. to_string ( ) ) ;
438485 }
439- let edit = TextEdit :: replace ( repl_range, replacement_text) ;
440- Ok ( ( frange. file_id , edit) )
486+ Ok ( ( file_id, edit. finish ( ) ) )
441487}
442488
443489#[ derive( Copy , Clone , Debug , PartialEq ) ]
0 commit comments