diff options
-rw-r--r-- | crates/ra_assists/src/assists/raw_string.rs | 107 |
1 files changed, 58 insertions, 49 deletions
diff --git a/crates/ra_assists/src/assists/raw_string.rs b/crates/ra_assists/src/assists/raw_string.rs index 2d2e31e51..9c996c902 100644 --- a/crates/ra_assists/src/assists/raw_string.rs +++ b/crates/ra_assists/src/assists/raw_string.rs | |||
@@ -1,17 +1,19 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use hir::db::HirDatabase; | 3 | use hir::db::HirDatabase; |
4 | use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit}; | 4 | use ra_syntax::{ |
5 | SyntaxKind::{RAW_STRING, STRING}, | ||
6 | SyntaxToken, TextRange, TextUnit, | ||
7 | }; | ||
5 | use rustc_lexer; | 8 | use rustc_lexer; |
6 | 9 | ||
7 | use crate::{Assist, AssistCtx, AssistId}; | 10 | use crate::{Assist, AssistCtx, AssistId}; |
8 | 11 | ||
9 | pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 12 | pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
10 | let literal = ctx.node_at_offset::<Literal>()?; | 13 | let token = ctx.token_at_offset().right_biased()?; |
11 | if literal.token().kind() != ra_syntax::SyntaxKind::STRING { | 14 | if token.kind() != STRING { |
12 | return None; | 15 | return None; |
13 | } | 16 | } |
14 | let token = literal.token(); | ||
15 | let text = token.text().as_str(); | 17 | let text = token.text().as_str(); |
16 | let usual_string_range = find_usual_string_range(text)?; | 18 | let usual_string_range = find_usual_string_range(text)?; |
17 | let start_of_inside = usual_string_range.start().to_usize() + 1; | 19 | let start_of_inside = usual_string_range.start().to_usize() + 1; |
@@ -30,85 +32,52 @@ pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<As | |||
30 | return None; | 32 | return None; |
31 | } | 33 | } |
32 | ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| { | 34 | ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| { |
33 | edit.target(literal.syntax().text_range()); | 35 | edit.target(token.text_range()); |
34 | let max_hash_streak = count_hashes(&unescaped); | 36 | let max_hash_streak = count_hashes(&unescaped); |
35 | let mut hashes = String::with_capacity(max_hash_streak + 1); | 37 | let mut hashes = String::with_capacity(max_hash_streak + 1); |
36 | for _ in 0..hashes.capacity() { | 38 | for _ in 0..hashes.capacity() { |
37 | hashes.push('#'); | 39 | hashes.push('#'); |
38 | } | 40 | } |
39 | edit.replace( | 41 | edit.replace(token.text_range(), format!("r{}\"{}\"{}", hashes, unescaped, hashes)); |
40 | literal.syntax().text_range(), | ||
41 | format!("r{}\"{}\"{}", hashes, unescaped, hashes), | ||
42 | ); | ||
43 | }); | 42 | }); |
44 | ctx.build() | 43 | ctx.build() |
45 | } | 44 | } |
46 | 45 | ||
47 | fn count_hashes(s: &str) -> usize { | ||
48 | let mut max_hash_streak = 0usize; | ||
49 | for idx in s.match_indices("\"#").map(|(i, _)| i) { | ||
50 | let (_, sub) = s.split_at(idx + 1); | ||
51 | let nb_hash = sub.chars().take_while(|c| *c == '#').count(); | ||
52 | if nb_hash > max_hash_streak { | ||
53 | max_hash_streak = nb_hash; | ||
54 | } | ||
55 | } | ||
56 | max_hash_streak | ||
57 | } | ||
58 | |||
59 | fn find_usual_string_range(s: &str) -> Option<TextRange> { | ||
60 | Some(TextRange::from_to( | ||
61 | TextUnit::from(s.find('"')? as u32), | ||
62 | TextUnit::from(s.rfind('"')? as u32), | ||
63 | )) | ||
64 | } | ||
65 | |||
66 | pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 46 | pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
67 | let literal = ctx.node_at_offset::<Literal>()?; | 47 | let token = raw_string_token(&ctx)?; |
68 | if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { | ||
69 | return None; | ||
70 | } | ||
71 | let token = literal.token(); | ||
72 | let text = token.text().as_str(); | 48 | let text = token.text().as_str(); |
73 | let usual_string_range = find_usual_string_range(text)?; | 49 | let usual_string_range = find_usual_string_range(text)?; |
74 | ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| { | 50 | ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| { |
75 | edit.target(literal.syntax().text_range()); | 51 | edit.target(token.text_range()); |
76 | // parse inside string to escape `"` | 52 | // parse inside string to escape `"` |
77 | let start_of_inside = usual_string_range.start().to_usize() + 1; | 53 | let start_of_inside = usual_string_range.start().to_usize() + 1; |
78 | let end_of_inside = usual_string_range.end().to_usize(); | 54 | let end_of_inside = usual_string_range.end().to_usize(); |
79 | let inside_str = &text[start_of_inside..end_of_inside]; | 55 | let inside_str = &text[start_of_inside..end_of_inside]; |
80 | let escaped = inside_str.escape_default().to_string(); | 56 | let escaped = inside_str.escape_default().to_string(); |
81 | edit.replace(literal.syntax().text_range(), format!("\"{}\"", escaped)); | 57 | edit.replace(token.text_range(), format!("\"{}\"", escaped)); |
82 | }); | 58 | }); |
83 | ctx.build() | 59 | ctx.build() |
84 | } | 60 | } |
85 | 61 | ||
86 | pub(crate) fn add_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 62 | pub(crate) fn add_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
87 | let literal = ctx.node_at_offset::<Literal>()?; | 63 | let token = raw_string_token(&ctx)?; |
88 | if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { | ||
89 | return None; | ||
90 | } | ||
91 | ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| { | 64 | ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| { |
92 | edit.target(literal.syntax().text_range()); | 65 | edit.target(token.text_range()); |
93 | edit.insert(literal.syntax().text_range().start() + TextUnit::of_char('r'), "#"); | 66 | edit.insert(token.text_range().start() + TextUnit::of_char('r'), "#"); |
94 | edit.insert(literal.syntax().text_range().end(), "#"); | 67 | edit.insert(token.text_range().end(), "#"); |
95 | }); | 68 | }); |
96 | ctx.build() | 69 | ctx.build() |
97 | } | 70 | } |
98 | 71 | ||
99 | pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 72 | pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
100 | let literal = ctx.node_at_offset::<Literal>()?; | 73 | let token = raw_string_token(&ctx)?; |
101 | if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { | ||
102 | return None; | ||
103 | } | ||
104 | let token = literal.token(); | ||
105 | let text = token.text().as_str(); | 74 | let text = token.text().as_str(); |
106 | if text.starts_with("r\"") { | 75 | if text.starts_with("r\"") { |
107 | // no hash to remove | 76 | // no hash to remove |
108 | return None; | 77 | return None; |
109 | } | 78 | } |
110 | ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| { | 79 | ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| { |
111 | edit.target(literal.syntax().text_range()); | 80 | edit.target(token.text_range()); |
112 | let result = &text[2..text.len() - 1]; | 81 | let result = &text[2..text.len() - 1]; |
113 | let result = if result.starts_with("\"") { | 82 | let result = if result.starts_with("\"") { |
114 | // no more hash, escape | 83 | // no more hash, escape |
@@ -117,11 +86,34 @@ pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist | |||
117 | } else { | 86 | } else { |
118 | result.to_owned() | 87 | result.to_owned() |
119 | }; | 88 | }; |
120 | edit.replace(literal.syntax().text_range(), format!("r{}", result)); | 89 | edit.replace(token.text_range(), format!("r{}", result)); |
121 | }); | 90 | }); |
122 | ctx.build() | 91 | ctx.build() |
123 | } | 92 | } |
124 | 93 | ||
94 | fn raw_string_token(ctx: &AssistCtx<impl HirDatabase>) -> Option<SyntaxToken> { | ||
95 | ctx.token_at_offset().right_biased().filter(|it| it.kind() == RAW_STRING) | ||
96 | } | ||
97 | |||
98 | fn count_hashes(s: &str) -> usize { | ||
99 | let mut max_hash_streak = 0usize; | ||
100 | for idx in s.match_indices("\"#").map(|(i, _)| i) { | ||
101 | let (_, sub) = s.split_at(idx + 1); | ||
102 | let nb_hash = sub.chars().take_while(|c| *c == '#').count(); | ||
103 | if nb_hash > max_hash_streak { | ||
104 | max_hash_streak = nb_hash; | ||
105 | } | ||
106 | } | ||
107 | max_hash_streak | ||
108 | } | ||
109 | |||
110 | fn find_usual_string_range(s: &str) -> Option<TextRange> { | ||
111 | Some(TextRange::from_to( | ||
112 | TextUnit::from(s.find('"')? as u32), | ||
113 | TextUnit::from(s.rfind('"')? as u32), | ||
114 | )) | ||
115 | } | ||
116 | |||
125 | #[cfg(test)] | 117 | #[cfg(test)] |
126 | mod test { | 118 | mod test { |
127 | use super::*; | 119 | use super::*; |
@@ -159,6 +151,23 @@ string"#; | |||
159 | } | 151 | } |
160 | 152 | ||
161 | #[test] | 153 | #[test] |
154 | fn make_raw_string_works_inside_macros() { | ||
155 | check_assist( | ||
156 | make_raw_string, | ||
157 | r#" | ||
158 | fn f() { | ||
159 | format!(<|>"x = {}", 92) | ||
160 | } | ||
161 | "#, | ||
162 | r##" | ||
163 | fn f() { | ||
164 | format!(<|>r#"x = {}"#, 92) | ||
165 | } | ||
166 | "##, | ||
167 | ) | ||
168 | } | ||
169 | |||
170 | #[test] | ||
162 | fn make_raw_string_hashes_inside_works() { | 171 | fn make_raw_string_hashes_inside_works() { |
163 | check_assist( | 172 | check_assist( |
164 | make_raw_string, | 173 | make_raw_string, |