diff options
-rw-r--r-- | crates/ra_assists/src/handlers/raw_string.rs | 131 |
1 files changed, 52 insertions, 79 deletions
diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs index 6d77dff13..ba1dcb610 100644 --- a/crates/ra_assists/src/handlers/raw_string.rs +++ b/crates/ra_assists/src/handlers/raw_string.rs | |||
@@ -4,8 +4,9 @@ use ra_syntax::{ | |||
4 | ast::{self, HasQuotes, HasStringValue}, | 4 | ast::{self, HasQuotes, HasStringValue}, |
5 | AstToken, | 5 | AstToken, |
6 | SyntaxKind::{RAW_STRING, STRING}, | 6 | SyntaxKind::{RAW_STRING, STRING}, |
7 | TextSize, | 7 | TextRange, TextSize, |
8 | }; | 8 | }; |
9 | use test_utils::mark; | ||
9 | 10 | ||
10 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 11 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
11 | 12 | ||
@@ -33,8 +34,7 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
33 | "Rewrite as raw string", | 34 | "Rewrite as raw string", |
34 | target, | 35 | target, |
35 | |edit| { | 36 | |edit| { |
36 | let max_hash_streak = count_hashes(&value); | 37 | let hashes = "#".repeat(required_hashes(&value).max(1)); |
37 | let hashes = "#".repeat(max_hash_streak + 1); | ||
38 | if matches!(value, Cow::Borrowed(_)) { | 38 | if matches!(value, Cow::Borrowed(_)) { |
39 | // Avoid replacing the whole string to better position the cursor. | 39 | // Avoid replacing the whole string to better position the cursor. |
40 | edit.insert(token.syntax().text_range().start(), format!("r{}", hashes)); | 40 | edit.insert(token.syntax().text_range().start(), format!("r{}", hashes)); |
@@ -106,7 +106,7 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Optio | |||
106 | pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 106 | pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
107 | let token = ctx.find_token_at_offset(RAW_STRING)?; | 107 | let token = ctx.find_token_at_offset(RAW_STRING)?; |
108 | let target = token.text_range(); | 108 | let target = token.text_range(); |
109 | acc.add(AssistId("add_hash", AssistKind::Refactor), "Add # to raw string", target, |edit| { | 109 | acc.add(AssistId("add_hash", AssistKind::Refactor), "Add #", target, |edit| { |
110 | edit.insert(token.text_range().start() + TextSize::of('r'), "#"); | 110 | edit.insert(token.text_range().start() + TextSize::of('r'), "#"); |
111 | edit.insert(token.text_range().end(), "#"); | 111 | edit.insert(token.text_range().end(), "#"); |
112 | }) | 112 | }) |
@@ -128,49 +128,58 @@ pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
128 | // } | 128 | // } |
129 | // ``` | 129 | // ``` |
130 | pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 130 | pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
131 | let token = ctx.find_token_at_offset(RAW_STRING)?; | 131 | let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; |
132 | |||
132 | let text = token.text().as_str(); | 133 | let text = token.text().as_str(); |
133 | if text.starts_with("r\"") { | 134 | if !text.starts_with("r#") && text.ends_with("#") { |
134 | // no hash to remove | ||
135 | return None; | 135 | return None; |
136 | } | 136 | } |
137 | let target = token.text_range(); | 137 | |
138 | acc.add( | 138 | let existing_hashes = text.chars().skip(1).take_while(|&it| it == '#').count(); |
139 | AssistId("remove_hash", AssistKind::RefactorRewrite), | 139 | |
140 | "Remove hash from raw string", | 140 | let text_range = token.syntax().text_range(); |
141 | target, | 141 | let internal_text = &text[token.text_range_between_quotes()? - text_range.start()]; |
142 | |edit| { | 142 | |
143 | let result = &text[2..text.len() - 1]; | 143 | if existing_hashes == required_hashes(internal_text) { |
144 | let result = if result.starts_with('\"') { | 144 | mark::hit!(cant_remove_required_hash); |
145 | // FIXME: this logic is wrong, not only the last has has to handled specially | 145 | return None; |
146 | // no more hash, escape | 146 | } |
147 | let internal_str = &result[1..result.len() - 1]; | 147 | |
148 | format!("\"{}\"", internal_str.escape_default().to_string()) | 148 | acc.add(AssistId("remove_hash", AssistKind::RefactorRewrite), "Remove #", text_range, |edit| { |
149 | } else { | 149 | edit.delete(TextRange::at(text_range.start() + TextSize::of('r'), TextSize::of('#'))); |
150 | result.to_owned() | 150 | edit.delete(TextRange::new(text_range.end() - TextSize::of('#'), text_range.end())); |
151 | }; | 151 | }) |
152 | edit.replace(token.text_range(), format!("r{}", result)); | ||
153 | }, | ||
154 | ) | ||
155 | } | 152 | } |
156 | 153 | ||
157 | fn count_hashes(s: &str) -> usize { | 154 | fn required_hashes(s: &str) -> usize { |
158 | let mut max_hash_streak = 0usize; | 155 | let mut res = 0usize; |
159 | for idx in s.match_indices("\"#").map(|(i, _)| i) { | 156 | for idx in s.match_indices('"').map(|(i, _)| i) { |
160 | let (_, sub) = s.split_at(idx + 1); | 157 | let (_, sub) = s.split_at(idx + 1); |
161 | let nb_hash = sub.chars().take_while(|c| *c == '#').count(); | 158 | let n_hashes = sub.chars().take_while(|c| *c == '#').count(); |
162 | if nb_hash > max_hash_streak { | 159 | res = res.max(n_hashes + 1) |
163 | max_hash_streak = nb_hash; | ||
164 | } | ||
165 | } | 160 | } |
166 | max_hash_streak | 161 | res |
162 | } | ||
163 | |||
164 | #[test] | ||
165 | fn test_required_hashes() { | ||
166 | assert_eq!(0, required_hashes("abc")); | ||
167 | assert_eq!(0, required_hashes("###")); | ||
168 | assert_eq!(1, required_hashes("\"")); | ||
169 | assert_eq!(2, required_hashes("\"#abc")); | ||
170 | assert_eq!(0, required_hashes("#abc")); | ||
171 | assert_eq!(3, required_hashes("#ab\"##c")); | ||
172 | assert_eq!(5, required_hashes("#ab\"##\"####c")); | ||
167 | } | 173 | } |
168 | 174 | ||
169 | #[cfg(test)] | 175 | #[cfg(test)] |
170 | mod test { | 176 | mod test { |
171 | use super::*; | 177 | use test_utils::mark; |
178 | |||
172 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; | 179 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; |
173 | 180 | ||
181 | use super::*; | ||
182 | |||
174 | #[test] | 183 | #[test] |
175 | fn make_raw_string_target() { | 184 | fn make_raw_string_target() { |
176 | check_assist_target( | 185 | check_assist_target( |
@@ -372,33 +381,21 @@ string"###; | |||
372 | fn remove_hash_works() { | 381 | fn remove_hash_works() { |
373 | check_assist( | 382 | check_assist( |
374 | remove_hash, | 383 | remove_hash, |
375 | r##" | 384 | r##"fn f() { let s = <|>r#"random string"#; }"##, |
376 | fn f() { | 385 | r#"fn f() { let s = r"random string"; }"#, |
377 | let s = <|>r#"random string"#; | ||
378 | } | ||
379 | "##, | ||
380 | r#" | ||
381 | fn f() { | ||
382 | let s = r"random string"; | ||
383 | } | ||
384 | "#, | ||
385 | ) | 386 | ) |
386 | } | 387 | } |
387 | 388 | ||
388 | #[test] | 389 | #[test] |
389 | fn remove_hash_with_quote_works() { | 390 | fn cant_remove_required_hash() { |
390 | check_assist( | 391 | mark::check!(cant_remove_required_hash); |
392 | check_assist_not_applicable( | ||
391 | remove_hash, | 393 | remove_hash, |
392 | r##" | 394 | r##" |
393 | fn f() { | 395 | fn f() { |
394 | let s = <|>r#"random"str"ing"#; | 396 | let s = <|>r#"random"str"ing"#; |
395 | } | 397 | } |
396 | "##, | 398 | "##, |
397 | r#" | ||
398 | fn f() { | ||
399 | let s = r"random\"str\"ing"; | ||
400 | } | ||
401 | "#, | ||
402 | ) | 399 | ) |
403 | } | 400 | } |
404 | 401 | ||
@@ -420,27 +417,13 @@ string"###; | |||
420 | } | 417 | } |
421 | 418 | ||
422 | #[test] | 419 | #[test] |
423 | fn remove_hash_not_works() { | 420 | fn remove_hash_doesnt_work() { |
424 | check_assist_not_applicable( | 421 | check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>"random string"; }"#); |
425 | remove_hash, | ||
426 | r#" | ||
427 | fn f() { | ||
428 | let s = <|>"random string"; | ||
429 | } | ||
430 | "#, | ||
431 | ); | ||
432 | } | 422 | } |
433 | 423 | ||
434 | #[test] | 424 | #[test] |
435 | fn remove_hash_no_hash_not_works() { | 425 | fn remove_hash_no_hash_doesnt_work() { |
436 | check_assist_not_applicable( | 426 | check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>r"random string"; }"#); |
437 | remove_hash, | ||
438 | r#" | ||
439 | fn f() { | ||
440 | let s = <|>r"random string"; | ||
441 | } | ||
442 | "#, | ||
443 | ); | ||
444 | } | 427 | } |
445 | 428 | ||
446 | #[test] | 429 | #[test] |
@@ -518,14 +501,4 @@ string"###; | |||
518 | "#, | 501 | "#, |
519 | ); | 502 | ); |
520 | } | 503 | } |
521 | |||
522 | #[test] | ||
523 | fn count_hashes_test() { | ||
524 | assert_eq!(0, count_hashes("abc")); | ||
525 | assert_eq!(0, count_hashes("###")); | ||
526 | assert_eq!(1, count_hashes("\"#abc")); | ||
527 | assert_eq!(0, count_hashes("#abc")); | ||
528 | assert_eq!(2, count_hashes("#ab\"##c")); | ||
529 | assert_eq!(4, count_hashes("#ab\"##\"####c")); | ||
530 | } | ||
531 | } | 504 | } |