Skip to content

Commit b19da13

Browse files
bors[bot]Geobert Quach
and
Geobert Quach
authored
Merge #1850
1850: feat(assists): raw string <-> usual string manipulation r=matklad a=Geobert Fixes #1730 Co-authored-by: Geobert Quach <[email protected]>
2 parents 4101c15 + ce4d843 commit b19da13

File tree

3 files changed

+431
-0
lines changed

3 files changed

+431
-0
lines changed

crates/ra_assists/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ mod fill_match_arms;
9696
mod merge_match_arms;
9797
mod introduce_variable;
9898
mod inline_local_variable;
99+
mod raw_string;
99100
mod replace_if_let_with_match;
100101
mod split_import;
101102
mod remove_dbg;
@@ -125,6 +126,10 @@ fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assis
125126
move_guard::move_guard_to_arm_body,
126127
move_guard::move_arm_cond_to_match_guard,
127128
move_bounds::move_bounds_to_where_clause,
129+
raw_string::add_hash,
130+
raw_string::make_raw_string,
131+
raw_string::make_usual_string,
132+
raw_string::remove_hash,
128133
]
129134
}
130135

crates/ra_assists/src/raw_string.rs

+370
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
use hir::db::HirDatabase;
2+
use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit};
3+
4+
use crate::{Assist, AssistCtx, AssistId};
5+
6+
pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
7+
let literal = ctx.node_at_offset::<Literal>()?;
8+
if literal.token().kind() != ra_syntax::SyntaxKind::STRING {
9+
return None;
10+
}
11+
ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| {
12+
edit.target(literal.syntax().text_range());
13+
edit.insert(literal.syntax().text_range().start(), "r");
14+
});
15+
ctx.build()
16+
}
17+
18+
fn find_usual_string_range(s: &str) -> Option<TextRange> {
19+
Some(TextRange::from_to(
20+
TextUnit::from(s.find('"')? as u32),
21+
TextUnit::from(s.rfind('"')? as u32),
22+
))
23+
}
24+
25+
pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
26+
let literal = ctx.node_at_offset::<Literal>()?;
27+
if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {
28+
return None;
29+
}
30+
let token = literal.token();
31+
let text = token.text().as_str();
32+
let usual_string_range = find_usual_string_range(text)?;
33+
ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| {
34+
edit.target(literal.syntax().text_range());
35+
// parse inside string to escape `"`
36+
let start_of_inside = usual_string_range.start().to_usize() + 1;
37+
let end_of_inside = usual_string_range.end().to_usize();
38+
let inside_str = &text[start_of_inside..end_of_inside];
39+
let escaped = inside_str.escape_default().to_string();
40+
edit.replace(literal.syntax().text_range(), format!("\"{}\"", escaped));
41+
});
42+
ctx.build()
43+
}
44+
45+
pub(crate) fn add_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
46+
let literal = ctx.node_at_offset::<Literal>()?;
47+
if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {
48+
return None;
49+
}
50+
ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| {
51+
edit.target(literal.syntax().text_range());
52+
edit.insert(literal.syntax().text_range().start() + TextUnit::of_char('r'), "#");
53+
edit.insert(literal.syntax().text_range().end(), "#");
54+
});
55+
ctx.build()
56+
}
57+
58+
pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
59+
let literal = ctx.node_at_offset::<Literal>()?;
60+
if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {
61+
return None;
62+
}
63+
let token = literal.token();
64+
let text = token.text().as_str();
65+
if text.starts_with("r\"") {
66+
// no hash to remove
67+
return None;
68+
}
69+
ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| {
70+
edit.target(literal.syntax().text_range());
71+
let result = &text[2..text.len() - 1];
72+
let result = if result.starts_with("\"") {
73+
// no more hash, escape
74+
let internal_str = &result[1..result.len() - 1];
75+
format!("\"{}\"", internal_str.escape_default().to_string())
76+
} else {
77+
result.to_owned()
78+
};
79+
edit.replace(literal.syntax().text_range(), format!("r{}", result));
80+
});
81+
ctx.build()
82+
}
83+
84+
#[cfg(test)]
85+
mod test {
86+
use super::*;
87+
use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
88+
89+
#[test]
90+
fn make_raw_string_target() {
91+
check_assist_target(
92+
make_raw_string,
93+
r#"
94+
fn f() {
95+
let s = <|>"random string";
96+
}
97+
"#,
98+
r#""random string""#,
99+
);
100+
}
101+
102+
#[test]
103+
fn make_raw_string_works() {
104+
check_assist(
105+
make_raw_string,
106+
r#"
107+
fn f() {
108+
let s = <|>"random string";
109+
}
110+
"#,
111+
r#"
112+
fn f() {
113+
let s = <|>r"random string";
114+
}
115+
"#,
116+
)
117+
}
118+
119+
#[test]
120+
fn make_raw_string_with_escaped_works() {
121+
check_assist(
122+
make_raw_string,
123+
r#"
124+
fn f() {
125+
let s = <|>"random\nstring";
126+
}
127+
"#,
128+
r#"
129+
fn f() {
130+
let s = <|>r"random\nstring";
131+
}
132+
"#,
133+
)
134+
}
135+
136+
#[test]
137+
fn make_raw_string_not_works() {
138+
check_assist_not_applicable(
139+
make_raw_string,
140+
r#"
141+
fn f() {
142+
let s = <|>r"random string";
143+
}
144+
"#,
145+
);
146+
}
147+
148+
#[test]
149+
fn add_hash_target() {
150+
check_assist_target(
151+
add_hash,
152+
r#"
153+
fn f() {
154+
let s = <|>r"random string";
155+
}
156+
"#,
157+
r#"r"random string""#,
158+
);
159+
}
160+
161+
#[test]
162+
fn add_hash_works() {
163+
check_assist(
164+
add_hash,
165+
r#"
166+
fn f() {
167+
let s = <|>r"random string";
168+
}
169+
"#,
170+
r##"
171+
fn f() {
172+
let s = <|>r#"random string"#;
173+
}
174+
"##,
175+
)
176+
}
177+
178+
#[test]
179+
fn add_more_hash_works() {
180+
check_assist(
181+
add_hash,
182+
r##"
183+
fn f() {
184+
let s = <|>r#"random"string"#;
185+
}
186+
"##,
187+
r###"
188+
fn f() {
189+
let s = <|>r##"random"string"##;
190+
}
191+
"###,
192+
)
193+
}
194+
195+
#[test]
196+
fn add_hash_not_works() {
197+
check_assist_not_applicable(
198+
add_hash,
199+
r#"
200+
fn f() {
201+
let s = <|>"random string";
202+
}
203+
"#,
204+
);
205+
}
206+
207+
#[test]
208+
fn remove_hash_target() {
209+
check_assist_target(
210+
remove_hash,
211+
r##"
212+
fn f() {
213+
let s = <|>r#"random string"#;
214+
}
215+
"##,
216+
r##"r#"random string"#"##,
217+
);
218+
}
219+
220+
#[test]
221+
fn remove_hash_works() {
222+
check_assist(
223+
remove_hash,
224+
r##"
225+
fn f() {
226+
let s = <|>r#"random string"#;
227+
}
228+
"##,
229+
r#"
230+
fn f() {
231+
let s = <|>r"random string";
232+
}
233+
"#,
234+
)
235+
}
236+
237+
#[test]
238+
fn remove_hash_with_quote_works() {
239+
check_assist(
240+
remove_hash,
241+
r##"
242+
fn f() {
243+
let s = <|>r#"random"str"ing"#;
244+
}
245+
"##,
246+
r#"
247+
fn f() {
248+
let s = <|>r"random\"str\"ing";
249+
}
250+
"#,
251+
)
252+
}
253+
254+
#[test]
255+
fn remove_more_hash_works() {
256+
check_assist(
257+
remove_hash,
258+
r###"
259+
fn f() {
260+
let s = <|>r##"random string"##;
261+
}
262+
"###,
263+
r##"
264+
fn f() {
265+
let s = <|>r#"random string"#;
266+
}
267+
"##,
268+
)
269+
}
270+
271+
#[test]
272+
fn remove_hash_not_works() {
273+
check_assist_not_applicable(
274+
remove_hash,
275+
r#"
276+
fn f() {
277+
let s = <|>"random string";
278+
}
279+
"#,
280+
);
281+
}
282+
283+
#[test]
284+
fn remove_hash_no_hash_not_works() {
285+
check_assist_not_applicable(
286+
remove_hash,
287+
r#"
288+
fn f() {
289+
let s = <|>r"random string";
290+
}
291+
"#,
292+
);
293+
}
294+
295+
#[test]
296+
fn make_usual_string_target() {
297+
check_assist_target(
298+
make_usual_string,
299+
r##"
300+
fn f() {
301+
let s = <|>r#"random string"#;
302+
}
303+
"##,
304+
r##"r#"random string"#"##,
305+
);
306+
}
307+
308+
#[test]
309+
fn make_usual_string_works() {
310+
check_assist(
311+
make_usual_string,
312+
r##"
313+
fn f() {
314+
let s = <|>r#"random string"#;
315+
}
316+
"##,
317+
r#"
318+
fn f() {
319+
let s = <|>"random string";
320+
}
321+
"#,
322+
)
323+
}
324+
325+
#[test]
326+
fn make_usual_string_with_quote_works() {
327+
check_assist(
328+
make_usual_string,
329+
r##"
330+
fn f() {
331+
let s = <|>r#"random"str"ing"#;
332+
}
333+
"##,
334+
r#"
335+
fn f() {
336+
let s = <|>"random\"str\"ing";
337+
}
338+
"#,
339+
)
340+
}
341+
342+
#[test]
343+
fn make_usual_string_more_hash_works() {
344+
check_assist(
345+
make_usual_string,
346+
r###"
347+
fn f() {
348+
let s = <|>r##"random string"##;
349+
}
350+
"###,
351+
r##"
352+
fn f() {
353+
let s = <|>"random string";
354+
}
355+
"##,
356+
)
357+
}
358+
359+
#[test]
360+
fn make_usual_string_not_works() {
361+
check_assist_not_applicable(
362+
make_usual_string,
363+
r#"
364+
fn f() {
365+
let s = <|>"random string";
366+
}
367+
"#,
368+
);
369+
}
370+
}

0 commit comments

Comments
 (0)