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