2 ast::{self, HasStringValue},
4 SyntaxKind::{RAW_STRING, STRING},
8 use crate::{Assist, AssistCtx, AssistId};
10 // Assist: make_raw_string
12 // Adds `r#` to a plain string literal.
16 // "Hello,<|> World!";
22 // r#"Hello, World!"#;
25 pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> {
26 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
27 let value = token.value()?;
28 let target = token.syntax().text_range();
29 ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", target, |edit| {
30 let max_hash_streak = count_hashes(&value);
31 let mut hashes = String::with_capacity(max_hash_streak + 1);
32 for _ in 0..hashes.capacity() {
35 edit.replace(token.syntax().text_range(), format!("r{}\"{}\"{}", hashes, value, hashes));
39 // Assist: make_usual_string
41 // Turns a raw string into a plain string.
45 // r#"Hello,<|> "World!""#;
51 // "Hello, \"World!\"";
54 pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> {
55 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
56 let value = token.value()?;
57 let target = token.syntax().text_range();
58 ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", target, |edit| {
59 // parse inside string to escape `"`
60 let escaped = value.escape_default().to_string();
61 edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
67 // Adds a hash to a raw string literal.
71 // r#"Hello,<|> World!"#;
77 // r##"Hello, World!"##;
80 pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> {
81 let token = ctx.find_token_at_offset(RAW_STRING)?;
82 let target = token.text_range();
83 ctx.add_assist(AssistId("add_hash"), "Add # to raw string", target, |edit| {
84 edit.insert(token.text_range().start() + TextSize::of('r'), "#");
85 edit.insert(token.text_range().end(), "#");
89 // Assist: remove_hash
91 // Removes a hash from a raw string literal.
95 // r#"Hello,<|> World!"#;
104 pub(crate) fn remove_hash(ctx: AssistCtx) -> Option<Assist> {
105 let token = ctx.find_token_at_offset(RAW_STRING)?;
106 let text = token.text().as_str();
107 if text.starts_with("r\"") {
111 let target = token.text_range();
112 ctx.add_assist(AssistId("remove_hash"), "Remove hash from raw string", target, |edit| {
113 let result = &text[2..text.len() - 1];
114 let result = if result.starts_with('\"') {
115 // FIXME: this logic is wrong, not only the last has has to handled specially
116 // no more hash, escape
117 let internal_str = &result[1..result.len() - 1];
118 format!("\"{}\"", internal_str.escape_default().to_string())
122 edit.replace(token.text_range(), format!("r{}", result));
126 fn count_hashes(s: &str) -> usize {
127 let mut max_hash_streak = 0usize;
128 for idx in s.match_indices("\"#").map(|(i, _)| i) {
129 let (_, sub) = s.split_at(idx + 1);
130 let nb_hash = sub.chars().take_while(|c| *c == '#').count();
131 if nb_hash > max_hash_streak {
132 max_hash_streak = nb_hash;
141 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
144 fn make_raw_string_target() {
149 let s = <|>"random\nstring";
152 r#""random\nstring""#,
157 fn make_raw_string_works() {
162 let s = <|>"random\nstring";
175 fn make_raw_string_works_inside_macros() {
180 format!(<|>"x = {}", 92)
185 format!(<|>r#"x = {}"#, 92)
192 fn make_raw_string_hashes_inside_works() {
197 let s = <|>"#random##\nstring";
202 let s = <|>r#"#random##
210 fn make_raw_string_closing_hashes_inside_works() {
215 let s = <|>"#random\"##\nstring";
220 let s = <|>r###"#random"##
228 fn make_raw_string_nothing_to_unescape_works() {
233 let s = <|>"random string";
238 let s = <|>r#"random string"#;
245 fn make_raw_string_not_works_on_partial_string() {
246 check_assist_not_applicable(
257 fn make_usual_string_not_works_on_partial_string() {
258 check_assist_not_applicable(
269 fn add_hash_target() {
274 let s = <|>r"random string";
277 r#"r"random string""#,
282 fn add_hash_works() {
287 let s = <|>r"random string";
292 let s = <|>r#"random string"#;
299 fn add_more_hash_works() {
304 let s = <|>r#"random"string"#;
309 let s = <|>r##"random"string"##;
316 fn add_hash_not_works() {
317 check_assist_not_applicable(
321 let s = <|>"random string";
328 fn remove_hash_target() {
333 let s = <|>r#"random string"#;
336 r##"r#"random string"#"##,
341 fn remove_hash_works() {
346 let s = <|>r#"random string"#;
351 let s = <|>r"random string";
358 fn remove_hash_with_quote_works() {
363 let s = <|>r#"random"str"ing"#;
368 let s = <|>r"random\"str\"ing";
375 fn remove_more_hash_works() {
380 let s = <|>r##"random string"##;
385 let s = <|>r#"random string"#;
392 fn remove_hash_not_works() {
393 check_assist_not_applicable(
397 let s = <|>"random string";
404 fn remove_hash_no_hash_not_works() {
405 check_assist_not_applicable(
409 let s = <|>r"random string";
416 fn make_usual_string_target() {
421 let s = <|>r#"random string"#;
424 r##"r#"random string"#"##,
429 fn make_usual_string_works() {
434 let s = <|>r#"random string"#;
439 let s = <|>"random string";
446 fn make_usual_string_with_quote_works() {
451 let s = <|>r#"random"str"ing"#;
456 let s = <|>"random\"str\"ing";
463 fn make_usual_string_more_hash_works() {
468 let s = <|>r##"random string"##;
473 let s = <|>"random string";
480 fn make_usual_string_not_works() {
481 check_assist_not_applicable(
485 let s = <|>"random string";
492 fn count_hashes_test() {
493 assert_eq!(0, count_hashes("abc"));
494 assert_eq!(0, count_hashes("###"));
495 assert_eq!(1, count_hashes("\"#abc"));
496 assert_eq!(0, count_hashes("#abc"));
497 assert_eq!(2, count_hashes("#ab\"##c"));
498 assert_eq!(4, count_hashes("#ab\"##\"####c"));