3 use syntax::{ast, AstToken, TextRange, TextSize};
6 use crate::{AssistContext, AssistId, AssistKind, Assists};
8 // Assist: make_raw_string
10 // Adds `r#` to a plain string literal.
20 // r#"Hello, World!"#;
23 pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let token = ctx.find_token_at_offset::<ast::String>()?;
28 let value = token.value()?;
29 let target = token.syntax().text_range();
31 AssistId("make_raw_string", AssistKind::RefactorRewrite),
32 "Rewrite as raw string",
35 let hashes = "#".repeat(required_hashes(&value).max(1));
36 if matches!(value, Cow::Borrowed(_)) {
37 // Avoid replacing the whole string to better position the cursor.
38 edit.insert(token.syntax().text_range().start(), format!("r{}", hashes));
39 edit.insert(token.syntax().text_range().end(), format!("{}", hashes));
42 token.syntax().text_range(),
43 format!("r{}\"{}\"{}", hashes, value, hashes),
50 // Assist: make_usual_string
52 // Turns a raw string into a plain string.
56 // r#"Hello,$0 "World!""#;
62 // "Hello, \"World!\"";
65 pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
66 let token = ctx.find_token_at_offset::<ast::String>()?;
70 let value = token.value()?;
71 let target = token.syntax().text_range();
73 AssistId("make_usual_string", AssistKind::RefactorRewrite),
74 "Rewrite as regular string",
77 // parse inside string to escape `"`
78 let escaped = value.escape_default().to_string();
79 if let Some(offsets) = token.quote_offsets() {
80 if token.text()[offsets.contents - token.syntax().text_range().start()] == escaped {
81 edit.replace(offsets.quotes.0, "\"");
82 edit.replace(offsets.quotes.1, "\"");
87 edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
94 // Adds a hash to a raw string literal.
98 // r#"Hello,$0 World!"#;
104 // r##"Hello, World!"##;
107 pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
108 let token = ctx.find_token_at_offset::<ast::String>()?;
112 let text_range = token.syntax().text_range();
113 let target = text_range;
114 acc.add(AssistId("add_hash", AssistKind::Refactor), "Add #", target, |edit| {
115 edit.insert(text_range.start() + TextSize::of('r'), "#");
116 edit.insert(text_range.end(), "#");
120 // Assist: remove_hash
122 // Removes a hash from a raw string literal.
126 // r#"Hello,$0 World!"#;
135 pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
136 let token = ctx.find_token_at_offset::<ast::String>()?;
141 let text = token.text();
142 if !text.starts_with("r#") && text.ends_with('#') {
146 let existing_hashes = text.chars().skip(1).take_while(|&it| it == '#').count();
148 let text_range = token.syntax().text_range();
149 let internal_text = &text[token.text_range_between_quotes()? - text_range.start()];
151 if existing_hashes == required_hashes(internal_text) {
152 mark::hit!(cant_remove_required_hash);
156 acc.add(AssistId("remove_hash", AssistKind::RefactorRewrite), "Remove #", text_range, |edit| {
157 edit.delete(TextRange::at(text_range.start() + TextSize::of('r'), TextSize::of('#')));
158 edit.delete(TextRange::new(text_range.end() - TextSize::of('#'), text_range.end()));
162 fn required_hashes(s: &str) -> usize {
163 let mut res = 0usize;
164 for idx in s.match_indices('"').map(|(i, _)| i) {
165 let (_, sub) = s.split_at(idx + 1);
166 let n_hashes = sub.chars().take_while(|c| *c == '#').count();
167 res = res.max(n_hashes + 1)
173 fn test_required_hashes() {
174 assert_eq!(0, required_hashes("abc"));
175 assert_eq!(0, required_hashes("###"));
176 assert_eq!(1, required_hashes("\""));
177 assert_eq!(2, required_hashes("\"#abc"));
178 assert_eq!(0, required_hashes("#abc"));
179 assert_eq!(3, required_hashes("#ab\"##c"));
180 assert_eq!(5, required_hashes("#ab\"##\"####c"));
185 use test_utils::mark;
187 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
192 fn make_raw_string_target() {
197 let s = $0"random\nstring";
200 r#""random\nstring""#,
205 fn make_raw_string_works() {
210 let s = $0"random\nstring";
223 fn make_raw_string_works_inside_macros() {
228 format!($0"x = {}", 92)
233 format!(r#"x = {}"#, 92)
240 fn make_raw_string_hashes_inside_works() {
245 let s = $0"#random##\nstring";
258 fn make_raw_string_closing_hashes_inside_works() {
263 let s = $0"#random\"##\nstring";
268 let s = r###"#random"##
276 fn make_raw_string_nothing_to_unescape_works() {
281 let s = $0"random string";
286 let s = r#"random string"#;
293 fn make_raw_string_not_works_on_partial_string() {
294 check_assist_not_applicable(
305 fn make_usual_string_not_works_on_partial_string() {
306 check_assist_not_applicable(
317 fn add_hash_target() {
322 let s = $0r"random string";
325 r#"r"random string""#,
330 fn add_hash_works() {
335 let s = $0r"random string";
340 let s = r#"random string"#;
347 fn add_more_hash_works() {
352 let s = $0r#"random"string"#;
357 let s = r##"random"string"##;
364 fn add_hash_not_works() {
365 check_assist_not_applicable(
369 let s = $0"random string";
376 fn remove_hash_target() {
381 let s = $0r#"random string"#;
384 r##"r#"random string"#"##,
389 fn remove_hash_works() {
392 r##"fn f() { let s = $0r#"random string"#; }"##,
393 r#"fn f() { let s = r"random string"; }"#,
398 fn cant_remove_required_hash() {
399 mark::check!(cant_remove_required_hash);
400 check_assist_not_applicable(
404 let s = $0r#"random"str"ing"#;
411 fn remove_more_hash_works() {
416 let s = $0r##"random string"##;
421 let s = r#"random string"#;
428 fn remove_hash_doesnt_work() {
429 check_assist_not_applicable(remove_hash, r#"fn f() { let s = $0"random string"; }"#);
433 fn remove_hash_no_hash_doesnt_work() {
434 check_assist_not_applicable(remove_hash, r#"fn f() { let s = $0r"random string"; }"#);
438 fn make_usual_string_target() {
443 let s = $0r#"random string"#;
446 r##"r#"random string"#"##,
451 fn make_usual_string_works() {
456 let s = $0r#"random string"#;
461 let s = "random string";
468 fn make_usual_string_with_quote_works() {
473 let s = $0r#"random"str"ing"#;
478 let s = "random\"str\"ing";
485 fn make_usual_string_more_hash_works() {
490 let s = $0r##"random string"##;
495 let s = "random string";
502 fn make_usual_string_not_works() {
503 check_assist_not_applicable(
507 let s = $0"random string";