1- use std:: borrow:: Cow ;
2-
3- use syntax:: { AstToken , TextRange , TextSize , ast, ast:: IsString } ;
1+ use ide_db:: source_change:: SourceChangeBuilder ;
2+ use syntax:: {
3+ AstToken ,
4+ ast:: { self , IsString , make:: tokens:: literal} ,
5+ } ;
46
57use crate :: {
68 AssistContext , AssistId , Assists ,
7- utils:: { required_hashes, string_suffix} ,
9+ utils:: { required_hashes, string_prefix , string_suffix} ,
810} ;
911
1012// Assist: make_raw_string
@@ -23,8 +25,7 @@ use crate::{
2325// }
2426// ```
2527pub ( crate ) fn make_raw_string ( acc : & mut Assists , ctx : & AssistContext < ' _ > ) -> Option < ( ) > {
26- // FIXME: This should support byte and c strings as well.
27- let token = ctx. find_token_at_offset :: < ast:: String > ( ) ?;
28+ let token = ctx. find_token_at_offset :: < ast:: AnyString > ( ) ?;
2829 if token. is_raw ( ) {
2930 return None ;
3031 }
@@ -36,16 +37,10 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt
3637 target,
3738 |edit| {
3839 let hashes = "#" . repeat ( required_hashes ( & value) . max ( 1 ) ) ;
39- let range = token. syntax ( ) . text_range ( ) ;
40+ let raw_prefix = token. raw_prefix ( ) ;
4041 let suffix = string_suffix ( token. text ( ) ) . unwrap_or_default ( ) ;
41- let range = TextRange :: new ( range. start ( ) , range. end ( ) - TextSize :: of ( suffix) ) ;
42- if matches ! ( value, Cow :: Borrowed ( _) ) {
43- // Avoid replacing the whole string to better position the cursor.
44- edit. insert ( range. start ( ) , format ! ( "r{hashes}" ) ) ;
45- edit. insert ( range. end ( ) , hashes) ;
46- } else {
47- edit. replace ( range, format ! ( "r{hashes}\" {value}\" {hashes}" ) ) ;
48- }
42+ let new_str = format ! ( "{raw_prefix}{hashes}\" {value}\" {hashes}{suffix}" ) ;
43+ replace_literal ( & token, & new_str, edit, ctx) ;
4944 } ,
5045 )
5146}
@@ -66,7 +61,7 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt
6661// }
6762// ```
6863pub ( crate ) fn make_usual_string ( acc : & mut Assists , ctx : & AssistContext < ' _ > ) -> Option < ( ) > {
69- let token = ctx. find_token_at_offset :: < ast:: String > ( ) ?;
64+ let token = ctx. find_token_at_offset :: < ast:: AnyString > ( ) ?;
7065 if !token. is_raw ( ) {
7166 return None ;
7267 }
@@ -80,18 +75,9 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
8075 // parse inside string to escape `"`
8176 let escaped = value. escape_default ( ) . to_string ( ) ;
8277 let suffix = string_suffix ( token. text ( ) ) . unwrap_or_default ( ) ;
83- if let Some ( offsets) = token. quote_offsets ( )
84- && token. text ( ) [ offsets. contents - token. syntax ( ) . text_range ( ) . start ( ) ] == escaped
85- {
86- let end_quote = offsets. quotes . 1 ;
87- let end_quote =
88- TextRange :: new ( end_quote. start ( ) , end_quote. end ( ) - TextSize :: of ( suffix) ) ;
89- edit. replace ( offsets. quotes . 0 , "\" " ) ;
90- edit. replace ( end_quote, "\" " ) ;
91- return ;
92- }
93-
94- edit. replace ( token. syntax ( ) . text_range ( ) , format ! ( "\" {escaped}\" {suffix}" ) ) ;
78+ let prefix = string_prefix ( token. text ( ) ) . map_or ( "" , |s| s. trim_end_matches ( 'r' ) ) ;
79+ let new_str = format ! ( "{prefix}\" {escaped}\" {suffix}" ) ;
80+ replace_literal ( & token, & new_str, edit, ctx) ;
9581 } ,
9682 )
9783}
@@ -112,16 +98,18 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
11298// }
11399// ```
114100pub ( crate ) fn add_hash ( acc : & mut Assists , ctx : & AssistContext < ' _ > ) -> Option < ( ) > {
115- let token = ctx. find_token_at_offset :: < ast:: String > ( ) ?;
101+ let token = ctx. find_token_at_offset :: < ast:: AnyString > ( ) ?;
116102 if !token. is_raw ( ) {
117103 return None ;
118104 }
119- let text_range = token. syntax ( ) . text_range ( ) ;
120- let target = text_range;
105+ let target = token. syntax ( ) . text_range ( ) ;
121106 acc. add ( AssistId :: refactor ( "add_hash" ) , "Add #" , target, |edit| {
122- let suffix = string_suffix ( token. text ( ) ) . unwrap_or_default ( ) ;
123- edit. insert ( text_range. start ( ) + TextSize :: of ( 'r' ) , "#" ) ;
124- edit. insert ( text_range. end ( ) - TextSize :: of ( suffix) , "#" ) ;
107+ let str = token. text ( ) ;
108+ let suffix = string_suffix ( str) . unwrap_or_default ( ) ;
109+ let raw_prefix = token. raw_prefix ( ) ;
110+ let wrap_range = raw_prefix. len ( ) ..str. len ( ) - suffix. len ( ) ;
111+ let new_str = [ raw_prefix, "#" , & str[ wrap_range] , "#" , suffix] . concat ( ) ;
112+ replace_literal ( & token, & new_str, edit, ctx) ;
125113 } )
126114}
127115
@@ -141,17 +129,15 @@ pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()>
141129// }
142130// ```
143131pub ( crate ) fn remove_hash ( acc : & mut Assists , ctx : & AssistContext < ' _ > ) -> Option < ( ) > {
144- let token = ctx. find_token_at_offset :: < ast:: String > ( ) ?;
132+ let token = ctx. find_token_at_offset :: < ast:: AnyString > ( ) ?;
145133 if !token. is_raw ( ) {
146134 return None ;
147135 }
148136
149137 let text = token. text ( ) ;
150- if !text. starts_with ( "r#" ) && text. ends_with ( '#' ) {
151- return None ;
152- }
153138
154- let existing_hashes = text. chars ( ) . skip ( 1 ) . take_while ( |& it| it == '#' ) . count ( ) ;
139+ let existing_hashes =
140+ text. chars ( ) . skip ( token. raw_prefix ( ) . len ( ) ) . take_while ( |& it| it == '#' ) . count ( ) ;
155141
156142 let text_range = token. syntax ( ) . text_range ( ) ;
157143 let internal_text = & text[ token. text_range_between_quotes ( ) ? - text_range. start ( ) ] ;
@@ -163,14 +149,38 @@ pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
163149
164150 acc. add ( AssistId :: refactor_rewrite ( "remove_hash" ) , "Remove #" , text_range, |edit| {
165151 let suffix = string_suffix ( text) . unwrap_or_default ( ) ;
166- edit. delete ( TextRange :: at ( text_range. start ( ) + TextSize :: of ( 'r' ) , TextSize :: of ( '#' ) ) ) ;
167- edit. delete (
168- TextRange :: new ( text_range. end ( ) - TextSize :: of ( '#' ) , text_range. end ( ) )
169- - TextSize :: of ( suffix) ,
170- ) ;
152+ let prefix = token. raw_prefix ( ) ;
153+ let wrap_range = prefix. len ( ) + 1 ..text. len ( ) - suffix. len ( ) - 1 ;
154+ let new_str = [ prefix, & text[ wrap_range] , suffix] . concat ( ) ;
155+ replace_literal ( & token, & new_str, edit, ctx) ;
171156 } )
172157}
173158
159+ fn replace_literal (
160+ token : & impl AstToken ,
161+ new : & str ,
162+ builder : & mut SourceChangeBuilder ,
163+ ctx : & AssistContext < ' _ > ,
164+ ) {
165+ let token = token. syntax ( ) ;
166+ let node = token. parent ( ) . expect ( "no parent token" ) ;
167+ let mut edit = builder. make_editor ( & node) ;
168+ let new_literal = literal ( new) ;
169+
170+ edit. replace ( token, mut_token ( new_literal) ) ;
171+
172+ builder. add_file_edits ( ctx. vfs_file_id ( ) , edit) ;
173+ }
174+
175+ fn mut_token ( token : syntax:: SyntaxToken ) -> syntax:: SyntaxToken {
176+ let node = token. parent ( ) . expect ( "no parent token" ) ;
177+ node. clone_for_update ( )
178+ . children_with_tokens ( )
179+ . filter_map ( |it| it. into_token ( ) )
180+ . find ( |it| it. text_range ( ) == token. text_range ( ) && it. text ( ) == token. text ( ) )
181+ . unwrap ( )
182+ }
183+
174184#[ cfg( test) ]
175185mod tests {
176186 use super :: * ;
@@ -224,6 +234,42 @@ string"#;
224234 )
225235 }
226236
237+ #[ test]
238+ fn make_raw_byte_string_works ( ) {
239+ check_assist (
240+ make_raw_string,
241+ r#"
242+ fn f() {
243+ let s = $0b"random\nstring";
244+ }
245+ "# ,
246+ r##"
247+ fn f() {
248+ let s = br#"random
249+ string"#;
250+ }
251+ "## ,
252+ )
253+ }
254+
255+ #[ test]
256+ fn make_raw_c_string_works ( ) {
257+ check_assist (
258+ make_raw_string,
259+ r#"
260+ fn f() {
261+ let s = $0c"random\nstring";
262+ }
263+ "# ,
264+ r##"
265+ fn f() {
266+ let s = cr#"random
267+ string"#;
268+ }
269+ "## ,
270+ )
271+ }
272+
227273 #[ test]
228274 fn make_raw_string_hashes_inside_works ( ) {
229275 check_assist (
@@ -348,6 +394,23 @@ string"###;
348394 )
349395 }
350396
397+ #[ test]
398+ fn add_hash_works_for_c_str ( ) {
399+ check_assist (
400+ add_hash,
401+ r#"
402+ fn f() {
403+ let s = $0cr"random string";
404+ }
405+ "# ,
406+ r##"
407+ fn f() {
408+ let s = cr#"random string"#;
409+ }
410+ "## ,
411+ )
412+ }
413+
351414 #[ test]
352415 fn add_hash_has_suffix_works ( ) {
353416 check_assist (
@@ -433,6 +496,15 @@ string"###;
433496 )
434497 }
435498
499+ #[ test]
500+ fn remove_hash_works_for_c_str ( ) {
501+ check_assist (
502+ remove_hash,
503+ r##"fn f() { let s = $0cr#"random string"#; }"## ,
504+ r#"fn f() { let s = cr"random string"; }"# ,
505+ )
506+ }
507+
436508 #[ test]
437509 fn remove_hash_has_suffix_works ( ) {
438510 check_assist (
@@ -529,6 +601,23 @@ string"###;
529601 )
530602 }
531603
604+ #[ test]
605+ fn make_usual_string_for_c_str ( ) {
606+ check_assist (
607+ make_usual_string,
608+ r##"
609+ fn f() {
610+ let s = $0cr#"random string"#;
611+ }
612+ "## ,
613+ r#"
614+ fn f() {
615+ let s = c"random string";
616+ }
617+ "# ,
618+ )
619+ }
620+
532621 #[ test]
533622 fn make_usual_string_has_suffix_works ( ) {
534623 check_assist (
0 commit comments