aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/assists')
-rw-r--r--crates/ra_assists/src/assists/raw_string.rs107
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
3use hir::db::HirDatabase; 3use hir::db::HirDatabase;
4use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit}; 4use ra_syntax::{
5 SyntaxKind::{RAW_STRING, STRING},
6 SyntaxToken, TextRange, TextUnit,
7};
5use rustc_lexer; 8use rustc_lexer;
6 9
7use crate::{Assist, AssistCtx, AssistId}; 10use crate::{Assist, AssistCtx, AssistId};
8 11
9pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 12pub(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
47fn 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
59fn 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
66pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 46pub(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
86pub(crate) fn add_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 62pub(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
99pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 72pub(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
94fn raw_string_token(ctx: &AssistCtx<impl HirDatabase>) -> Option<SyntaxToken> {
95 ctx.token_at_offset().right_biased().filter(|it| it.kind() == RAW_STRING)
96}
97
98fn 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
110fn 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)]
126mod test { 118mod 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,