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.rs499
1 files changed, 499 insertions, 0 deletions
diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs
new file mode 100644
index 000000000..2c0a1e126
--- /dev/null
+++ b/crates/ra_assists/src/handlers/raw_string.rs
@@ -0,0 +1,499 @@
1use ra_syntax::{
2 ast, AstToken,
3 SyntaxKind::{RAW_STRING, STRING},
4 TextUnit,
5};
6
7use crate::{Assist, AssistCtx, AssistId};
8
9// Assist: make_raw_string
10//
11// Adds `r#` to a plain string literal.
12//
13// ```
14// fn main() {
15// "Hello,<|> World!";
16// }
17// ```
18// ->
19// ```
20// fn main() {
21// r#"Hello, World!"#;
22// }
23// ```
24pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> {
25 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
26 let value = token.value()?;
27 ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", |edit| {
28 edit.target(token.syntax().text_range());
29 let max_hash_streak = count_hashes(&value);
30 let mut hashes = String::with_capacity(max_hash_streak + 1);
31 for _ in 0..hashes.capacity() {
32 hashes.push('#');
33 }
34 edit.replace(token.syntax().text_range(), format!("r{}\"{}\"{}", hashes, value, hashes));
35 })
36}
37
38// Assist: make_usual_string
39//
40// Turns a raw string into a plain string.
41//
42// ```
43// fn main() {
44// r#"Hello,<|> "World!""#;
45// }
46// ```
47// ->
48// ```
49// fn main() {
50// "Hello, \"World!\"";
51// }
52// ```
53pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> {
54 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
55 let value = token.value()?;
56 ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", |edit| {
57 edit.target(token.syntax().text_range());
58 // parse inside string to escape `"`
59 let escaped = value.escape_default().to_string();
60 edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
61 })
62}
63
64// Assist: add_hash
65//
66// Adds a hash to a raw string literal.
67//
68// ```
69// fn main() {
70// r#"Hello,<|> World!"#;
71// }
72// ```
73// ->
74// ```
75// fn main() {
76// r##"Hello, World!"##;
77// }
78// ```
79pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> {
80 let token = ctx.find_token_at_offset(RAW_STRING)?;
81 ctx.add_assist(AssistId("add_hash"), "Add # to raw string", |edit| {
82 edit.target(token.text_range());
83 edit.insert(token.text_range().start() + TextUnit::of_char('r'), "#");
84 edit.insert(token.text_range().end(), "#");
85 })
86}
87
88// Assist: remove_hash
89//
90// Removes a hash from a raw string literal.
91//
92// ```
93// fn main() {
94// r#"Hello,<|> World!"#;
95// }
96// ```
97// ->
98// ```
99// fn main() {
100// r"Hello, World!";
101// }
102// ```
103pub(crate) fn remove_hash(ctx: AssistCtx) -> Option<Assist> {
104 let token = ctx.find_token_at_offset(RAW_STRING)?;
105 let text = token.text().as_str();
106 if text.starts_with("r\"") {
107 // no hash to remove
108 return None;
109 }
110 ctx.add_assist(AssistId("remove_hash"), "Remove hash from raw string", |edit| {
111 edit.target(token.text_range());
112 let result = &text[2..text.len() - 1];
113 let result = if result.starts_with('\"') {
114 // FIXME: this logic is wrong, not only the last has has to handled specially
115 // no more hash, escape
116 let internal_str = &result[1..result.len() - 1];
117 format!("\"{}\"", internal_str.escape_default().to_string())
118 } else {
119 result.to_owned()
120 };
121 edit.replace(token.text_range(), format!("r{}", result));
122 })
123}
124
125fn count_hashes(s: &str) -> usize {
126 let mut max_hash_streak = 0usize;
127 for idx in s.match_indices("\"#").map(|(i, _)| i) {
128 let (_, sub) = s.split_at(idx + 1);
129 let nb_hash = sub.chars().take_while(|c| *c == '#').count();
130 if nb_hash > max_hash_streak {
131 max_hash_streak = nb_hash;
132 }
133 }
134 max_hash_streak
135}
136
137#[cfg(test)]
138mod test {
139 use super::*;
140 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
141
142 #[test]
143 fn make_raw_string_target() {
144 check_assist_target(
145 make_raw_string,
146 r#"
147 fn f() {
148 let s = <|>"random\nstring";
149 }
150 "#,
151 r#""random\nstring""#,
152 );
153 }
154
155 #[test]
156 fn make_raw_string_works() {
157 check_assist(
158 make_raw_string,
159 r#"
160 fn f() {
161 let s = <|>"random\nstring";
162 }
163 "#,
164 r##"
165 fn f() {
166 let s = <|>r#"random
167string"#;
168 }
169 "##,
170 )
171 }
172
173 #[test]
174 fn make_raw_string_works_inside_macros() {
175 check_assist(
176 make_raw_string,
177 r#"
178 fn f() {
179 format!(<|>"x = {}", 92)
180 }
181 "#,
182 r##"
183 fn f() {
184 format!(<|>r#"x = {}"#, 92)
185 }
186 "##,
187 )
188 }
189
190 #[test]
191 fn make_raw_string_hashes_inside_works() {
192 check_assist(
193 make_raw_string,
194 r###"
195 fn f() {
196 let s = <|>"#random##\nstring";
197 }
198 "###,
199 r####"
200 fn f() {
201 let s = <|>r#"#random##
202string"#;
203 }
204 "####,
205 )
206 }
207
208 #[test]
209 fn make_raw_string_closing_hashes_inside_works() {
210 check_assist(
211 make_raw_string,
212 r###"
213 fn f() {
214 let s = <|>"#random\"##\nstring";
215 }
216 "###,
217 r####"
218 fn f() {
219 let s = <|>r###"#random"##
220string"###;
221 }
222 "####,
223 )
224 }
225
226 #[test]
227 fn make_raw_string_nothing_to_unescape_works() {
228 check_assist(
229 make_raw_string,
230 r#"
231 fn f() {
232 let s = <|>"random string";
233 }
234 "#,
235 r##"
236 fn f() {
237 let s = <|>r#"random string"#;
238 }
239 "##,
240 )
241 }
242
243 #[test]
244 fn make_raw_string_not_works_on_partial_string() {
245 check_assist_not_applicable(
246 make_raw_string,
247 r#"
248 fn f() {
249 let s = "foo<|>
250 }
251 "#,
252 )
253 }
254
255 #[test]
256 fn make_usual_string_not_works_on_partial_string() {
257 check_assist_not_applicable(
258 make_usual_string,
259 r#"
260 fn main() {
261 let s = r#"bar<|>
262 }
263 "#,
264 )
265 }
266
267 #[test]
268 fn add_hash_target() {
269 check_assist_target(
270 add_hash,
271 r#"
272 fn f() {
273 let s = <|>r"random string";
274 }
275 "#,
276 r#"r"random string""#,
277 );
278 }
279
280 #[test]
281 fn add_hash_works() {
282 check_assist(
283 add_hash,
284 r#"
285 fn f() {
286 let s = <|>r"random string";
287 }
288 "#,
289 r##"
290 fn f() {
291 let s = <|>r#"random string"#;
292 }
293 "##,
294 )
295 }
296
297 #[test]
298 fn add_more_hash_works() {
299 check_assist(
300 add_hash,
301 r##"
302 fn f() {
303 let s = <|>r#"random"string"#;
304 }
305 "##,
306 r###"
307 fn f() {
308 let s = <|>r##"random"string"##;
309 }
310 "###,
311 )
312 }
313
314 #[test]
315 fn add_hash_not_works() {
316 check_assist_not_applicable(
317 add_hash,
318 r#"
319 fn f() {
320 let s = <|>"random string";
321 }
322 "#,
323 );
324 }
325
326 #[test]
327 fn remove_hash_target() {
328 check_assist_target(
329 remove_hash,
330 r##"
331 fn f() {
332 let s = <|>r#"random string"#;
333 }
334 "##,
335 r##"r#"random string"#"##,
336 );
337 }
338
339 #[test]
340 fn remove_hash_works() {
341 check_assist(
342 remove_hash,
343 r##"
344 fn f() {
345 let s = <|>r#"random string"#;
346 }
347 "##,
348 r#"
349 fn f() {
350 let s = <|>r"random string";
351 }
352 "#,
353 )
354 }
355
356 #[test]
357 fn remove_hash_with_quote_works() {
358 check_assist(
359 remove_hash,
360 r##"
361 fn f() {
362 let s = <|>r#"random"str"ing"#;
363 }
364 "##,
365 r#"
366 fn f() {
367 let s = <|>r"random\"str\"ing";
368 }
369 "#,
370 )
371 }
372
373 #[test]
374 fn remove_more_hash_works() {
375 check_assist(
376 remove_hash,
377 r###"
378 fn f() {
379 let s = <|>r##"random string"##;
380 }
381 "###,
382 r##"
383 fn f() {
384 let s = <|>r#"random string"#;
385 }
386 "##,
387 )
388 }
389
390 #[test]
391 fn remove_hash_not_works() {
392 check_assist_not_applicable(
393 remove_hash,
394 r#"
395 fn f() {
396 let s = <|>"random string";
397 }
398 "#,
399 );
400 }
401
402 #[test]
403 fn remove_hash_no_hash_not_works() {
404 check_assist_not_applicable(
405 remove_hash,
406 r#"
407 fn f() {
408 let s = <|>r"random string";
409 }
410 "#,
411 );
412 }
413
414 #[test]
415 fn make_usual_string_target() {
416 check_assist_target(
417 make_usual_string,
418 r##"
419 fn f() {
420 let s = <|>r#"random string"#;
421 }
422 "##,
423 r##"r#"random string"#"##,
424 );
425 }
426
427 #[test]
428 fn make_usual_string_works() {
429 check_assist(
430 make_usual_string,
431 r##"
432 fn f() {
433 let s = <|>r#"random string"#;
434 }
435 "##,
436 r#"
437 fn f() {
438 let s = <|>"random string";
439 }
440 "#,
441 )
442 }
443
444 #[test]
445 fn make_usual_string_with_quote_works() {
446 check_assist(
447 make_usual_string,
448 r##"
449 fn f() {
450 let s = <|>r#"random"str"ing"#;
451 }
452 "##,
453 r#"
454 fn f() {
455 let s = <|>"random\"str\"ing";
456 }
457 "#,
458 )
459 }
460
461 #[test]
462 fn make_usual_string_more_hash_works() {
463 check_assist(
464 make_usual_string,
465 r###"
466 fn f() {
467 let s = <|>r##"random string"##;
468 }
469 "###,
470 r##"
471 fn f() {
472 let s = <|>"random string";
473 }
474 "##,
475 )
476 }
477
478 #[test]
479 fn make_usual_string_not_works() {
480 check_assist_not_applicable(
481 make_usual_string,
482 r#"
483 fn f() {
484 let s = <|>"random string";
485 }
486 "#,
487 );
488 }
489
490 #[test]
491 fn count_hashes_test() {
492 assert_eq!(0, count_hashes("abc"));
493 assert_eq!(0, count_hashes("###"));
494 assert_eq!(1, count_hashes("\"#abc"));
495 assert_eq!(0, count_hashes("#abc"));
496 assert_eq!(2, count_hashes("#ab\"##c"));
497 assert_eq!(4, count_hashes("#ab\"##\"####c"));
498 }
499}