diff options
Diffstat (limited to 'crates/ra_assists/src/handlers/raw_string.rs')
-rw-r--r-- | crates/ra_assists/src/handlers/raw_string.rs | 499 |
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 @@ | |||
1 | use ra_syntax::{ | ||
2 | ast, AstToken, | ||
3 | SyntaxKind::{RAW_STRING, STRING}, | ||
4 | TextUnit, | ||
5 | }; | ||
6 | |||
7 | use 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 | // ``` | ||
24 | pub(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 | // ``` | ||
53 | pub(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 | // ``` | ||
79 | pub(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 | // ``` | ||
103 | pub(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 | |||
125 | fn 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)] | ||
138 | mod 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 | ||
167 | string"#; | ||
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## | ||
202 | string"#; | ||
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"## | ||
220 | string"###; | ||
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 | } | ||