aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/handlers/raw_string.rs131
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};
9use test_utils::mark;
9 10
10use crate::{AssistContext, AssistId, AssistKind, Assists}; 11use 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
106pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 106pub(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// ```
130pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 130pub(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
157fn count_hashes(s: &str) -> usize { 154fn 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]
165fn 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)]
170mod test { 176mod 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}