aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/raw_string.rs159
1 files changed, 59 insertions, 100 deletions
diff --git a/crates/ra_assists/src/raw_string.rs b/crates/ra_assists/src/raw_string.rs
index 1cb4297a9..e00267060 100644
--- a/crates/ra_assists/src/raw_string.rs
+++ b/crates/ra_assists/src/raw_string.rs
@@ -1,125 +1,84 @@
1use hir::db::HirDatabase; 1use hir::db::HirDatabase;
2use ra_syntax::{ast::AstNode, ast::Literal, SyntaxText, TextRange, TextUnit}; 2use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit};
3 3
4use crate::{assist_ctx::AssistBuilder, Assist, AssistCtx, AssistId}; 4use crate::{Assist, AssistCtx, AssistId};
5 5
6pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 6pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
7 let literal = ctx.node_at_offset::<Literal>()?; 7 let literal = ctx.node_at_offset::<Literal>()?;
8 if literal.token().kind() == ra_syntax::SyntaxKind::STRING { 8 if literal.token().kind() != ra_syntax::SyntaxKind::STRING {
9 ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| { 9 return None;
10 edit.target(literal.syntax().text_range());
11 edit.insert(literal.syntax().text_range().start(), "r");
12 });
13 ctx.build()
14 } else {
15 None
16 } 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
18fn 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 ))
17} 23}
18 24
19pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 25pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
20 let literal = ctx.node_at_offset::<Literal>()?; 26 let literal = ctx.node_at_offset::<Literal>()?;
21 if literal.token().kind() == ra_syntax::SyntaxKind::RAW_STRING { 27 if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {
22 ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| { 28 return None;
23 let text = literal.syntax().text();
24 let usual_start_pos = text.find_char('"').unwrap(); // we have a RAW_STRING
25 let end = literal.syntax().text_range().end();
26 let mut i = 0;
27 let mut pos = 0;
28 let mut c = text.char_at(end - TextUnit::from(i));
29 while c != Some('"') {
30 if c != None {
31 pos += 1;
32 }
33 i += 1;
34 c = text.char_at(end - TextUnit::from(i));
35 }
36
37 edit.target(literal.syntax().text_range());
38 edit.delete(TextRange::from_to(
39 literal.syntax().text_range().start(),
40 literal.syntax().text_range().start() + usual_start_pos,
41 ));
42 edit.delete(TextRange::from_to(
43 literal.syntax().text_range().end() - TextUnit::from(pos),
44 literal.syntax().text_range().end(),
45 ));
46 // parse inside string to escape `"`
47 let start_of_inside = usual_start_pos + TextUnit::from(1);
48 let end_of_inside = text.len() - usual_start_pos - TextUnit::from(1);
49 let inside_str = text.slice(TextRange::from_to(start_of_inside, end_of_inside));
50 escape_double_quote(
51 edit,
52 &inside_str,
53 literal.syntax().text_range().start() + start_of_inside,
54 );
55 });
56 ctx.build()
57 } else {
58 None
59 } 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()
60} 43}
61 44
62pub(crate) fn add_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 45pub(crate) fn add_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
63 let literal = ctx.node_at_offset::<Literal>()?; 46 let literal = ctx.node_at_offset::<Literal>()?;
64 if literal.token().kind() == ra_syntax::SyntaxKind::RAW_STRING { 47 if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {
65 ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| { 48 return None;
66 edit.target(literal.syntax().text_range());
67 edit.insert(literal.syntax().text_range().start() + TextUnit::from(1), "#");
68 edit.insert(literal.syntax().text_range().end(), "#");
69 });
70 ctx.build()
71 } else {
72 None
73 } 49 }
74} 50 ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| {
75 51 edit.target(literal.syntax().text_range());
76fn escape_double_quote(edit: &mut AssistBuilder, inside_str: &SyntaxText, offset: TextUnit) { 52 edit.insert(literal.syntax().text_range().start() + TextUnit::of_char('r'), "#");
77 let mut start = TextUnit::from(0); 53 edit.insert(literal.syntax().text_range().end(), "#");
78 inside_str.for_each_chunk(|chunk| {
79 let end = start + TextUnit::of_str(chunk);
80 let mut i = 0;
81 for c in chunk.to_string().chars() {
82 if c == '"' {
83 edit.insert(offset + start + TextUnit::from(i), "\\");
84 }
85 i += 1;
86 }
87 start = end;
88 }); 54 });
55 ctx.build()
89} 56}
90 57
91pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 58pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
92 let literal = ctx.node_at_offset::<Literal>()?; 59 let literal = ctx.node_at_offset::<Literal>()?;
93 if literal.token().kind() == ra_syntax::SyntaxKind::RAW_STRING { 60 if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {
94 if !literal.syntax().text().contains_char('#') { 61 return None;
95 return None; 62 }
96 } 63 let token = literal.token();
97 ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| { 64 let text = token.text().as_str();
98 edit.target(literal.syntax().text_range()); 65 if text.starts_with("r\"") {
99 edit.delete(TextRange::from_to( 66 // no hash to remove
100 literal.syntax().text_range().start() + TextUnit::from(1), 67 return None;
101 literal.syntax().text_range().start() + TextUnit::from(2),
102 ));
103 edit.delete(TextRange::from_to(
104 literal.syntax().text_range().end() - TextUnit::from(1),
105 literal.syntax().text_range().end(),
106 ));
107 let text = literal.syntax().text();
108 if text.char_at(TextUnit::from(2)) == Some('"') {
109 // no more hash after assist, need to escape any `"` in the string
110 let inside_str = text
111 .slice(TextRange::from_to(TextUnit::from(3), text.len() - TextUnit::from(2)));
112 escape_double_quote(
113 edit,
114 &inside_str,
115 literal.syntax().text_range().start() + TextUnit::from(3),
116 );
117 }
118 });
119 ctx.build()
120 } else {
121 None
122 } 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()
123} 82}
124 83
125#[cfg(test)] 84#[cfg(test)]