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