about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs142
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/utils.rs14
2 files changed, 148 insertions, 8 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs
index ed86380a56c..94b49c5df09 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs
@@ -2,7 +2,10 @@ use std::borrow::Cow;
 
 use syntax::{AstToken, TextRange, TextSize, ast, ast::IsString};
 
-use crate::{AssistContext, AssistId, Assists, utils::required_hashes};
+use crate::{
+    AssistContext, AssistId, Assists,
+    utils::{required_hashes, string_suffix},
+};
 
 // Assist: make_raw_string
 //
@@ -33,12 +36,15 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt
         target,
         |edit| {
             let hashes = "#".repeat(required_hashes(&value).max(1));
+            let range = token.syntax().text_range();
+            let suffix = string_suffix(token.text()).unwrap_or_default();
+            let range = TextRange::new(range.start(), range.end() - TextSize::of(suffix));
             if matches!(value, Cow::Borrowed(_)) {
                 // Avoid replacing the whole string to better position the cursor.
-                edit.insert(token.syntax().text_range().start(), format!("r{hashes}"));
-                edit.insert(token.syntax().text_range().end(), hashes);
+                edit.insert(range.start(), format!("r{hashes}"));
+                edit.insert(range.end(), hashes);
             } else {
-                edit.replace(token.syntax().text_range(), format!("r{hashes}\"{value}\"{hashes}"));
+                edit.replace(range, format!("r{hashes}\"{value}\"{hashes}"));
             }
         },
     )
@@ -73,15 +79,19 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
         |edit| {
             // parse inside string to escape `"`
             let escaped = value.escape_default().to_string();
+            let suffix = string_suffix(token.text()).unwrap_or_default();
             if let Some(offsets) = token.quote_offsets() {
                 if token.text()[offsets.contents - token.syntax().text_range().start()] == escaped {
+                    let end_quote = offsets.quotes.1;
+                    let end_quote =
+                        TextRange::new(end_quote.start(), end_quote.end() - TextSize::of(suffix));
                     edit.replace(offsets.quotes.0, "\"");
-                    edit.replace(offsets.quotes.1, "\"");
+                    edit.replace(end_quote, "\"");
                     return;
                 }
             }
 
-            edit.replace(token.syntax().text_range(), format!("\"{escaped}\""));
+            edit.replace(token.syntax().text_range(), format!("\"{escaped}\"{suffix}"));
         },
     )
 }
@@ -109,8 +119,9 @@ pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()>
     let text_range = token.syntax().text_range();
     let target = text_range;
     acc.add(AssistId::refactor("add_hash"), "Add #", target, |edit| {
+        let suffix = string_suffix(token.text()).unwrap_or_default();
         edit.insert(text_range.start() + TextSize::of('r'), "#");
-        edit.insert(text_range.end(), "#");
+        edit.insert(text_range.end() - TextSize::of(suffix), "#");
     })
 }
 
@@ -151,8 +162,12 @@ pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
     }
 
     acc.add(AssistId::refactor_rewrite("remove_hash"), "Remove #", text_range, |edit| {
+        let suffix = string_suffix(text).unwrap_or_default();
         edit.delete(TextRange::at(text_range.start() + TextSize::of('r'), TextSize::of('#')));
-        edit.delete(TextRange::new(text_range.end() - TextSize::of('#'), text_range.end()));
+        edit.delete(
+            TextRange::new(text_range.end() - TextSize::of('#'), text_range.end())
+                - TextSize::of(suffix),
+        );
     })
 }
 
@@ -263,6 +278,23 @@ string"###;
     }
 
     #[test]
+    fn make_raw_string_has_suffix() {
+        check_assist(
+            make_raw_string,
+            r#"
+            fn f() {
+                let s = $0"random string"i32;
+            }
+            "#,
+            r##"
+            fn f() {
+                let s = r#"random string"#i32;
+            }
+            "##,
+        )
+    }
+
+    #[test]
     fn make_raw_string_not_works_on_partial_string() {
         check_assist_not_applicable(
             make_raw_string,
@@ -317,6 +349,23 @@ string"###;
     }
 
     #[test]
+    fn add_hash_has_suffix_works() {
+        check_assist(
+            add_hash,
+            r#"
+            fn f() {
+                let s = $0r"random string"i32;
+            }
+            "#,
+            r##"
+            fn f() {
+                let s = r#"random string"#i32;
+            }
+            "##,
+        )
+    }
+
+    #[test]
     fn add_more_hash_works() {
         check_assist(
             add_hash,
@@ -334,6 +383,23 @@ string"###;
     }
 
     #[test]
+    fn add_more_hash_has_suffix_works() {
+        check_assist(
+            add_hash,
+            r##"
+            fn f() {
+                let s = $0r#"random"string"#i32;
+            }
+            "##,
+            r###"
+            fn f() {
+                let s = r##"random"string"##i32;
+            }
+            "###,
+        )
+    }
+
+    #[test]
     fn add_hash_not_works() {
         check_assist_not_applicable(
             add_hash,
@@ -368,6 +434,15 @@ string"###;
     }
 
     #[test]
+    fn remove_hash_has_suffix_works() {
+        check_assist(
+            remove_hash,
+            r##"fn f() { let s = $0r#"random string"#i32; }"##,
+            r#"fn f() { let s = r"random string"i32; }"#,
+        )
+    }
+
+    #[test]
     fn cant_remove_required_hash() {
         cov_mark::check!(cant_remove_required_hash);
         check_assist_not_applicable(
@@ -398,6 +473,23 @@ string"###;
     }
 
     #[test]
+    fn remove_more_hash_has_suffix_works() {
+        check_assist(
+            remove_hash,
+            r###"
+            fn f() {
+                let s = $0r##"random string"##i32;
+            }
+            "###,
+            r##"
+            fn f() {
+                let s = r#"random string"#i32;
+            }
+            "##,
+        )
+    }
+
+    #[test]
     fn remove_hash_does_not_work() {
         check_assist_not_applicable(remove_hash, r#"fn f() { let s = $0"random string"; }"#);
     }
@@ -438,6 +530,23 @@ string"###;
     }
 
     #[test]
+    fn make_usual_string_has_suffix_works() {
+        check_assist(
+            make_usual_string,
+            r##"
+            fn f() {
+                let s = $0r#"random string"#i32;
+            }
+            "##,
+            r#"
+            fn f() {
+                let s = "random string"i32;
+            }
+            "#,
+        )
+    }
+
+    #[test]
     fn make_usual_string_with_quote_works() {
         check_assist(
             make_usual_string,
@@ -472,6 +581,23 @@ string"###;
     }
 
     #[test]
+    fn make_usual_string_more_hash_has_suffix_works() {
+        check_assist(
+            make_usual_string,
+            r###"
+            fn f() {
+                let s = $0r##"random string"##i32;
+            }
+            "###,
+            r##"
+            fn f() {
+                let s = "random string"i32;
+            }
+            "##,
+        )
+    }
+
+    #[test]
     fn make_usual_string_not_works() {
         check_assist_not_applicable(
             make_usual_string,
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
index fdc5dd13eb6..0471998f0b1 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/utils.rs
@@ -1026,6 +1026,20 @@ fn test_required_hashes() {
     assert_eq!(5, required_hashes("#ab\"##\"####c"));
 }
 
+/// Calculate the string literal suffix length
+pub(crate) fn string_suffix(s: &str) -> Option<&str> {
+    s.rfind(['"', '\'', '#']).map(|i| &s[i + 1..])
+}
+#[test]
+fn test_string_suffix() {
+    assert_eq!(Some(""), string_suffix(r#""abc""#));
+    assert_eq!(Some(""), string_suffix(r#""""#));
+    assert_eq!(Some("a"), string_suffix(r#"""a"#));
+    assert_eq!(Some("i32"), string_suffix(r#"""i32"#));
+    assert_eq!(Some("i32"), string_suffix(r#"r""i32"#));
+    assert_eq!(Some("i32"), string_suffix(r##"r#""#i32"##));
+}
+
 /// Replaces the record expression, handling field shorthands including inside macros.
 pub(crate) fn replace_record_field_expr(
     ctx: &AssistContext<'_>,