aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/handlers/raw_string.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/handlers/raw_string.rs')
-rw-r--r--crates/ra_assists/src/handlers/raw_string.rs158
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 @@
1use std::borrow::Cow;
2
1use ra_syntax::{ 3use 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};
9use test_utils::mark;
7 10
8use crate::{AssistContext, AssistId, AssistKind, Assists}; 11use 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
93pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 106pub(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// ```
117pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 130pub(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
144fn count_hashes(s: &str) -> usize { 154fn 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]
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"));
154} 173}
155 174
156#[cfg(test)] 175#[cfg(test)]
157mod test { 176mod 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}