3 use syntax
::{ast, ast::IsString, AstToken, TextRange, TextSize}
;
5 use crate::{AssistContext, AssistId, AssistKind, Assists}
;
7 // Assist: make_raw_string
9 // Adds `r#` to a plain string literal.
19 // r#"Hello, World!"#;
22 pub(crate) fn make_raw_string(acc
: &mut Assists
, ctx
: &AssistContext
<'_
>) -> Option
<()> {
23 let token
= ctx
.find_token_at_offset
::<ast
::String
>()?
;
27 let value
= token
.value()?
;
28 let target
= token
.syntax().text_range();
30 AssistId("make_raw_string", AssistKind
::RefactorRewrite
),
31 "Rewrite as raw string",
34 let hashes
= "#".repeat(required_hashes(&value
).max(1));
35 if matches
!(value
, Cow
::Borrowed(_
)) {
36 // Avoid replacing the whole string to better position the cursor.
37 edit
.insert(token
.syntax().text_range().start(), format
!("r{hashes}"));
38 edit
.insert(token
.syntax().text_range().end(), hashes
);
40 edit
.replace(token
.syntax().text_range(), format
!("r{hashes}\"{value}\"{hashes}"));
46 // Assist: make_usual_string
48 // Turns a raw string into a plain string.
52 // r#"Hello,$0 "World!""#;
58 // "Hello, \"World!\"";
61 pub(crate) fn make_usual_string(acc
: &mut Assists
, ctx
: &AssistContext
<'_
>) -> Option
<()> {
62 let token
= ctx
.find_token_at_offset
::<ast
::String
>()?
;
66 let value
= token
.value()?
;
67 let target
= token
.syntax().text_range();
69 AssistId("make_usual_string", AssistKind
::RefactorRewrite
),
70 "Rewrite as regular string",
73 // parse inside string to escape `"`
74 let escaped
= value
.escape_default().to_string();
75 if let Some(offsets
) = token
.quote_offsets() {
76 if token
.text()[offsets
.contents
- token
.syntax().text_range().start()] == escaped
{
77 edit
.replace(offsets
.quotes
.0, "\"");
78 edit
.replace(offsets
.quotes
.1, "\"");
83 edit
.replace(token
.syntax().text_range(), format
!("\"{escaped}\""));
90 // Adds a hash to a raw string literal.
94 // r#"Hello,$0 World!"#;
100 // r##"Hello, World!"##;
103 pub(crate) fn add_hash(acc
: &mut Assists
, ctx
: &AssistContext
<'_
>) -> Option
<()> {
104 let token
= ctx
.find_token_at_offset
::<ast
::String
>()?
;
108 let text_range
= token
.syntax().text_range();
109 let target
= text_range
;
110 acc
.add(AssistId("add_hash", AssistKind
::Refactor
), "Add #", target
, |edit
| {
111 edit
.insert(text_range
.start() + TextSize
::of('r'
), "#");
112 edit
.insert(text_range
.end(), "#");
116 // Assist: remove_hash
118 // Removes a hash from a raw string literal.
122 // r#"Hello,$0 World!"#;
131 pub(crate) fn remove_hash(acc
: &mut Assists
, ctx
: &AssistContext
<'_
>) -> Option
<()> {
132 let token
= ctx
.find_token_at_offset
::<ast
::String
>()?
;
137 let text
= token
.text();
138 if !text
.starts_with("r#") && text
.ends_with('
#') {
142 let existing_hashes
= text
.chars().skip(1).take_while(|&it
| it
== '
#').count();
144 let text_range
= token
.syntax().text_range();
145 let internal_text
= &text
[token
.text_range_between_quotes()?
- text_range
.start()];
147 if existing_hashes
== required_hashes(internal_text
) {
148 cov_mark
::hit
!(cant_remove_required_hash
);
152 acc
.add(AssistId("remove_hash", AssistKind
::RefactorRewrite
), "Remove #", text_range
, |edit
| {
153 edit
.delete(TextRange
::at(text_range
.start() + TextSize
::of('r'
), TextSize
::of('
#')));
154 edit
.delete(TextRange
::new(text_range
.end() - TextSize
::of('
#'), text_range.end()));
158 fn required_hashes(s
: &str) -> usize {
159 let mut res
= 0usize
;
160 for idx
in s
.match_indices('
"').map(|(i, _)| i) {
161 let (_, sub) = s.split_at(idx + 1);
162 let n_hashes = sub.chars().take_while(|c| *c == '#').count();
163 res = res.max(n_hashes + 1)
170 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
175 fn test_required_hashes() {
176 assert_eq!(0, required_hashes("abc
"));
177 assert_eq!(0, required_hashes("###"));
178 assert_eq!(1, required_hashes("\""));
179 assert_eq!(2, required_hashes("\"#abc"));
180 assert_eq!(0, required_hashes("#abc"));
181 assert_eq!(3, required_hashes("#ab\"##c"));
182 assert_eq!(5, required_hashes("#ab\"##\"####c"));
186 fn make_raw_string_target() {
191 let s
= $
0"random\nstring";
194 r#""random\nstring""#,
199 fn make_raw_string_works() {
204 let s
= $
0"random\nstring";
217 fn make_raw_string_works_inside_macros() {
222 format
!($
0"x = {}", 92)
227 format
!(r
#"x = {}"#, 92)
234 fn make_raw_string_hashes_inside_works() {
239 let s
= $
0"#random##\nstring";
252 fn make_raw_string_closing_hashes_inside_works() {
257 let s
= $
0"#random\"##\nstring";
262 let s
= r
###"#random"##
270 fn make_raw_string_nothing_to_unescape_works() {
275 let s = $0"random string";
280 let s = r#"random string"#;
287 fn make_raw_string_not_works_on_partial_string() {
288 check_assist_not_applicable(
299 fn make_usual_string_not_works_on_partial_string() {
300 check_assist_not_applicable(
311 fn add_hash_target() {
316 let s = $0r"random string";
319 r
#"r"random string""#,
324 fn add_hash_works() {
329 let s = $0r"random string";
334 let s = r#"random string"#;
341 fn add_more_hash_works() {
346 let s = $0r#"random"string"#;
351 let s
= r
##"random"string"##;
358 fn add_hash_not_works() {
359 check_assist_not_applicable(
363 let s = $0"random string";
370 fn remove_hash_target() {
375 let s = $0r#"random string"#;
378 r
##"r#"random string"#"##,
383 fn remove_hash_works() {
386 r
##"fn f() { let s = $0r#"random string"#; }"##,
387 r
#"fn f() { let s = r"random string"; }"#,
392 fn cant_remove_required_hash() {
393 cov_mark
::check
!(cant_remove_required_hash
);
394 check_assist_not_applicable(
398 let s = $0r#"random"str"ing"#;
405 fn remove_more_hash_works() {
410 let s = $0r##"random string"##;
415 let s = r#"random string"#;
422 fn remove_hash_doesnt_work() {
423 check_assist_not_applicable(remove_hash
, r
#"fn f() { let s = $0"random string"; }"#);
427 fn remove_hash_no_hash_doesnt_work() {
428 check_assist_not_applicable(remove_hash
, r
#"fn f() { let s = $0r"random string"; }"#);
432 fn make_usual_string_target() {
437 let s = $0r#"random string"#;
440 r
##"r#"random string"#"##,
445 fn make_usual_string_works() {
450 let s = $0r#"random string"#;
455 let s = "random string";
462 fn make_usual_string_with_quote_works() {
467 let s = $0r#"random"str"ing"#;
472 let s = "random\"str\"ing";
479 fn make_usual_string_more_hash_works() {
484 let s = $0r##"random string"##;
489 let s = "random string";
496 fn make_usual_string_not_works() {
497 check_assist_not_applicable(
501 let s = $0"random string";