diff options
Diffstat (limited to 'crates/ra_assists/src/handlers/raw_string.rs')
-rw-r--r-- | crates/ra_assists/src/handlers/raw_string.rs | 158 |
1 files changed, 72 insertions, 86 deletions
diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs index 96679e160..4e8a0c2db 100644 --- a/crates/ra_assists/src/handlers/raw_string.rs +++ b/crates/ra_assists/src/handlers/raw_string.rs | |||
@@ -1,9 +1,12 @@ | |||
1 | use std::borrow::Cow; | ||
2 | |||
1 | use ra_syntax::{ | 3 | use ra_syntax::{ |
2 | ast::{self, HasStringValue}, | 4 | ast::{self, HasQuotes, HasStringValue}, |
3 | AstToken, | 5 | AstToken, |
4 | SyntaxKind::{RAW_STRING, STRING}, | 6 | SyntaxKind::{RAW_STRING, STRING}, |
5 | TextSize, | 7 | TextRange, TextSize, |
6 | }; | 8 | }; |
9 | use test_utils::mark; | ||
7 | 10 | ||
8 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 11 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
9 | 12 | ||
@@ -31,15 +34,17 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
31 | "Rewrite as raw string", | 34 | "Rewrite as raw string", |
32 | target, | 35 | target, |
33 | |edit| { | 36 | |edit| { |
34 | let max_hash_streak = count_hashes(&value); | 37 | let hashes = "#".repeat(required_hashes(&value).max(1)); |
35 | let mut hashes = String::with_capacity(max_hash_streak + 1); | 38 | if matches!(value, Cow::Borrowed(_)) { |
36 | for _ in 0..hashes.capacity() { | 39 | // Avoid replacing the whole string to better position the cursor. |
37 | hashes.push('#'); | 40 | edit.insert(token.syntax().text_range().start(), format!("r{}", hashes)); |
41 | edit.insert(token.syntax().text_range().end(), format!("{}", hashes)); | ||
42 | } else { | ||
43 | edit.replace( | ||
44 | token.syntax().text_range(), | ||
45 | format!("r{}\"{}\"{}", hashes, value, hashes), | ||
46 | ); | ||
38 | } | 47 | } |
39 | edit.replace( | ||
40 | token.syntax().text_range(), | ||
41 | format!("r{}\"{}\"{}", hashes, value, hashes), | ||
42 | ); | ||
43 | }, | 48 | }, |
44 | ) | 49 | ) |
45 | } | 50 | } |
@@ -70,6 +75,14 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Optio | |||
70 | |edit| { | 75 | |edit| { |
71 | // parse inside string to escape `"` | 76 | // parse inside string to escape `"` |
72 | let escaped = value.escape_default().to_string(); | 77 | let escaped = value.escape_default().to_string(); |
78 | if let Some(offsets) = token.quote_offsets() { | ||
79 | if token.text()[offsets.contents - token.syntax().text_range().start()] == escaped { | ||
80 | edit.replace(offsets.quotes.0, "\""); | ||
81 | edit.replace(offsets.quotes.1, "\""); | ||
82 | return; | ||
83 | } | ||
84 | } | ||
85 | |||
73 | edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); | 86 | edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); |
74 | }, | 87 | }, |
75 | ) | 88 | ) |
@@ -93,7 +106,7 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Optio | |||
93 | pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 106 | pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
94 | let token = ctx.find_token_at_offset(RAW_STRING)?; | 107 | let token = ctx.find_token_at_offset(RAW_STRING)?; |
95 | let target = token.text_range(); | 108 | let target = token.text_range(); |
96 | acc.add(AssistId("add_hash", AssistKind::Refactor), "Add # to raw string", target, |edit| { | 109 | acc.add(AssistId("add_hash", AssistKind::Refactor), "Add #", target, |edit| { |
97 | edit.insert(token.text_range().start() + TextSize::of('r'), "#"); | 110 | edit.insert(token.text_range().start() + TextSize::of('r'), "#"); |
98 | edit.insert(token.text_range().end(), "#"); | 111 | edit.insert(token.text_range().end(), "#"); |
99 | }) | 112 | }) |
@@ -115,49 +128,58 @@ pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
115 | // } | 128 | // } |
116 | // ``` | 129 | // ``` |
117 | pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 130 | pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
118 | 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 | |||
119 | let text = token.text().as_str(); | 133 | let text = token.text().as_str(); |
120 | if text.starts_with("r\"") { | 134 | if !text.starts_with("r#") && text.ends_with('#') { |
121 | // no hash to remove | ||
122 | return None; | 135 | return None; |
123 | } | 136 | } |
124 | let target = token.text_range(); | 137 | |
125 | acc.add( | 138 | let existing_hashes = text.chars().skip(1).take_while(|&it| it == '#').count(); |
126 | AssistId("remove_hash", AssistKind::RefactorRewrite), | 139 | |
127 | "Remove hash from raw string", | 140 | let text_range = token.syntax().text_range(); |
128 | target, | 141 | let internal_text = &text[token.text_range_between_quotes()? - text_range.start()]; |
129 | |edit| { | 142 | |
130 | let result = &text[2..text.len() - 1]; | 143 | if existing_hashes == required_hashes(internal_text) { |
131 | let result = if result.starts_with('\"') { | 144 | mark::hit!(cant_remove_required_hash); |
132 | // FIXME: this logic is wrong, not only the last has has to handled specially | 145 | return None; |
133 | // no more hash, escape | 146 | } |
134 | let internal_str = &result[1..result.len() - 1]; | 147 | |
135 | format!("\"{}\"", internal_str.escape_default().to_string()) | 148 | acc.add(AssistId("remove_hash", AssistKind::RefactorRewrite), "Remove #", text_range, |edit| { |
136 | } else { | 149 | edit.delete(TextRange::at(text_range.start() + TextSize::of('r'), TextSize::of('#'))); |
137 | result.to_owned() | 150 | edit.delete(TextRange::new(text_range.end() - TextSize::of('#'), text_range.end())); |
138 | }; | 151 | }) |
139 | edit.replace(token.text_range(), format!("r{}", result)); | ||
140 | }, | ||
141 | ) | ||
142 | } | 152 | } |
143 | 153 | ||
144 | fn count_hashes(s: &str) -> usize { | 154 | fn required_hashes(s: &str) -> usize { |
145 | let mut max_hash_streak = 0usize; | 155 | let mut res = 0usize; |
146 | for idx in s.match_indices("\"#").map(|(i, _)| i) { | 156 | for idx in s.match_indices('"').map(|(i, _)| i) { |
147 | let (_, sub) = s.split_at(idx + 1); | 157 | let (_, sub) = s.split_at(idx + 1); |
148 | let nb_hash = sub.chars().take_while(|c| *c == '#').count(); | 158 | let n_hashes = sub.chars().take_while(|c| *c == '#').count(); |
149 | if nb_hash > max_hash_streak { | 159 | res = res.max(n_hashes + 1) |
150 | max_hash_streak = nb_hash; | ||
151 | } | ||
152 | } | 160 | } |
153 | 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")); | ||
154 | } | 173 | } |
155 | 174 | ||
156 | #[cfg(test)] | 175 | #[cfg(test)] |
157 | mod test { | 176 | mod test { |
158 | use super::*; | 177 | use test_utils::mark; |
178 | |||
159 | 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}; |
160 | 180 | ||
181 | use super::*; | ||
182 | |||
161 | #[test] | 183 | #[test] |
162 | fn make_raw_string_target() { | 184 | fn make_raw_string_target() { |
163 | check_assist_target( | 185 | check_assist_target( |
@@ -359,33 +381,21 @@ string"###; | |||
359 | fn remove_hash_works() { | 381 | fn remove_hash_works() { |
360 | check_assist( | 382 | check_assist( |
361 | remove_hash, | 383 | remove_hash, |
362 | r##" | 384 | r##"fn f() { let s = <|>r#"random string"#; }"##, |
363 | fn f() { | 385 | r#"fn f() { let s = r"random string"; }"#, |
364 | let s = <|>r#"random string"#; | ||
365 | } | ||
366 | "##, | ||
367 | r#" | ||
368 | fn f() { | ||
369 | let s = r"random string"; | ||
370 | } | ||
371 | "#, | ||
372 | ) | 386 | ) |
373 | } | 387 | } |
374 | 388 | ||
375 | #[test] | 389 | #[test] |
376 | fn remove_hash_with_quote_works() { | 390 | fn cant_remove_required_hash() { |
377 | check_assist( | 391 | mark::check!(cant_remove_required_hash); |
392 | check_assist_not_applicable( | ||
378 | remove_hash, | 393 | remove_hash, |
379 | r##" | 394 | r##" |
380 | fn f() { | 395 | fn f() { |
381 | let s = <|>r#"random"str"ing"#; | 396 | let s = <|>r#"random"str"ing"#; |
382 | } | 397 | } |
383 | "##, | 398 | "##, |
384 | r#" | ||
385 | fn f() { | ||
386 | let s = r"random\"str\"ing"; | ||
387 | } | ||
388 | "#, | ||
389 | ) | 399 | ) |
390 | } | 400 | } |
391 | 401 | ||
@@ -407,27 +417,13 @@ string"###; | |||
407 | } | 417 | } |
408 | 418 | ||
409 | #[test] | 419 | #[test] |
410 | fn remove_hash_not_works() { | 420 | fn remove_hash_doesnt_work() { |
411 | check_assist_not_applicable( | 421 | check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>"random string"; }"#); |
412 | remove_hash, | ||
413 | r#" | ||
414 | fn f() { | ||
415 | let s = <|>"random string"; | ||
416 | } | ||
417 | "#, | ||
418 | ); | ||
419 | } | 422 | } |
420 | 423 | ||
421 | #[test] | 424 | #[test] |
422 | fn remove_hash_no_hash_not_works() { | 425 | fn remove_hash_no_hash_doesnt_work() { |
423 | check_assist_not_applicable( | 426 | check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>r"random string"; }"#); |
424 | remove_hash, | ||
425 | r#" | ||
426 | fn f() { | ||
427 | let s = <|>r"random string"; | ||
428 | } | ||
429 | "#, | ||
430 | ); | ||
431 | } | 427 | } |
432 | 428 | ||
433 | #[test] | 429 | #[test] |
@@ -505,14 +501,4 @@ string"###; | |||
505 | "#, | 501 | "#, |
506 | ); | 502 | ); |
507 | } | 503 | } |
508 | |||
509 | #[test] | ||
510 | fn count_hashes_test() { | ||
511 | assert_eq!(0, count_hashes("abc")); | ||
512 | assert_eq!(0, count_hashes("###")); | ||
513 | assert_eq!(1, count_hashes("\"#abc")); | ||
514 | assert_eq!(0, count_hashes("#abc")); | ||
515 | assert_eq!(2, count_hashes("#ab\"##c")); | ||
516 | assert_eq!(4, count_hashes("#ab\"##\"####c")); | ||
517 | } | ||
518 | } | 504 | } |