diff options
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | crates/ra_assists/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_assists/src/assists/raw_string.rs | 59 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/tokens.rs | 95 |
4 files changed, 98 insertions, 58 deletions
diff --git a/Cargo.lock b/Cargo.lock index 16fbe7502..820cf2acc 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -947,7 +947,6 @@ dependencies = [ | |||
947 | "ra_hir 0.1.0", | 947 | "ra_hir 0.1.0", |
948 | "ra_syntax 0.1.0", | 948 | "ra_syntax 0.1.0", |
949 | "ra_text_edit 0.1.0", | 949 | "ra_text_edit 0.1.0", |
950 | "rustc_lexer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||
951 | "test_utils 0.1.0", | 950 | "test_utils 0.1.0", |
952 | ] | 951 | ] |
953 | 952 | ||
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml index beebccbd9..125c6222d 100644 --- a/crates/ra_assists/Cargo.toml +++ b/crates/ra_assists/Cargo.toml | |||
@@ -8,7 +8,6 @@ authors = ["rust-analyzer developers"] | |||
8 | format-buf = "1.0.0" | 8 | format-buf = "1.0.0" |
9 | join_to_string = "0.1.3" | 9 | join_to_string = "0.1.3" |
10 | itertools = "0.8.0" | 10 | itertools = "0.8.0" |
11 | rustc_lexer = "0.1.0" | ||
12 | 11 | ||
13 | ra_syntax = { path = "../ra_syntax" } | 12 | ra_syntax = { path = "../ra_syntax" } |
14 | ra_text_edit = { path = "../ra_text_edit" } | 13 | ra_text_edit = { path = "../ra_text_edit" } |
diff --git a/crates/ra_assists/src/assists/raw_string.rs b/crates/ra_assists/src/assists/raw_string.rs index 58f7157ae..93912a470 100644 --- a/crates/ra_assists/src/assists/raw_string.rs +++ b/crates/ra_assists/src/assists/raw_string.rs | |||
@@ -1,9 +1,9 @@ | |||
1 | use hir::db::HirDatabase; | 1 | use hir::db::HirDatabase; |
2 | use ra_syntax::{ | 2 | use ra_syntax::{ |
3 | ast, AstToken, | ||
3 | SyntaxKind::{RAW_STRING, STRING}, | 4 | SyntaxKind::{RAW_STRING, STRING}, |
4 | TextRange, TextUnit, | 5 | TextUnit, |
5 | }; | 6 | }; |
6 | use rustc_lexer; | ||
7 | 7 | ||
8 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{Assist, AssistCtx, AssistId}; |
9 | 9 | ||
@@ -23,32 +23,16 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
23 | // } | 23 | // } |
24 | // ``` | 24 | // ``` |
25 | pub(crate) fn make_raw_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 25 | pub(crate) fn make_raw_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
26 | let token = ctx.find_token_at_offset(STRING)?; | 26 | let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; |
27 | let text = token.text().as_str(); | 27 | let value = token.value()?; |
28 | let usual_string_range = find_usual_string_range(text)?; | ||
29 | let start_of_inside = usual_string_range.start().to_usize() + 1; | ||
30 | let end_of_inside = usual_string_range.end().to_usize(); | ||
31 | let inside_str = &text[start_of_inside..end_of_inside]; | ||
32 | let mut unescaped = String::with_capacity(inside_str.len()); | ||
33 | let mut error = Ok(()); | ||
34 | rustc_lexer::unescape::unescape_str( | ||
35 | inside_str, | ||
36 | &mut |_, unescaped_char| match unescaped_char { | ||
37 | Ok(c) => unescaped.push(c), | ||
38 | Err(_) => error = Err(()), | ||
39 | }, | ||
40 | ); | ||
41 | if error.is_err() { | ||
42 | return None; | ||
43 | } | ||
44 | ctx.add_assist(AssistId("make_raw_string"), "make raw string", |edit| { | 28 | ctx.add_assist(AssistId("make_raw_string"), "make raw string", |edit| { |
45 | edit.target(token.text_range()); | 29 | edit.target(token.syntax().text_range()); |
46 | let max_hash_streak = count_hashes(&unescaped); | 30 | let max_hash_streak = count_hashes(&value); |
47 | let mut hashes = String::with_capacity(max_hash_streak + 1); | 31 | let mut hashes = String::with_capacity(max_hash_streak + 1); |
48 | for _ in 0..hashes.capacity() { | 32 | for _ in 0..hashes.capacity() { |
49 | hashes.push('#'); | 33 | hashes.push('#'); |
50 | } | 34 | } |
51 | edit.replace(token.text_range(), format!("r{}\"{}\"{}", hashes, unescaped, hashes)); | 35 | edit.replace(token.syntax().text_range(), format!("r{}\"{}\"{}", hashes, value, hashes)); |
52 | }) | 36 | }) |
53 | } | 37 | } |
54 | 38 | ||
@@ -68,17 +52,13 @@ pub(crate) fn make_raw_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist | |||
68 | // } | 52 | // } |
69 | // ``` | 53 | // ``` |
70 | pub(crate) fn make_usual_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 54 | pub(crate) fn make_usual_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
71 | let token = ctx.find_token_at_offset(RAW_STRING)?; | 55 | let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; |
72 | let text = token.text().as_str(); | 56 | let value = token.value()?; |
73 | let usual_string_range = find_usual_string_range(text)?; | ||
74 | ctx.add_assist(AssistId("make_usual_string"), "make usual string", |edit| { | 57 | ctx.add_assist(AssistId("make_usual_string"), "make usual string", |edit| { |
75 | edit.target(token.text_range()); | 58 | edit.target(token.syntax().text_range()); |
76 | // parse inside string to escape `"` | 59 | // parse inside string to escape `"` |
77 | let start_of_inside = usual_string_range.start().to_usize() + 1; | 60 | let escaped = value.escape_default().to_string(); |
78 | let end_of_inside = usual_string_range.end().to_usize(); | 61 | edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); |
79 | let inside_str = &text[start_of_inside..end_of_inside]; | ||
80 | let escaped = inside_str.escape_default().to_string(); | ||
81 | edit.replace(token.text_range(), format!("\"{}\"", escaped)); | ||
82 | }) | 62 | }) |
83 | } | 63 | } |
84 | 64 | ||
@@ -132,6 +112,7 @@ pub(crate) fn remove_hash(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | |||
132 | edit.target(token.text_range()); | 112 | edit.target(token.text_range()); |
133 | let result = &text[2..text.len() - 1]; | 113 | let result = &text[2..text.len() - 1]; |
134 | let result = if result.starts_with('\"') { | 114 | let result = if result.starts_with('\"') { |
115 | // FIXME: this logic is wrong, not only the last has has to handled specially | ||
135 | // no more hash, escape | 116 | // no more hash, escape |
136 | let internal_str = &result[1..result.len() - 1]; | 117 | let internal_str = &result[1..result.len() - 1]; |
137 | format!("\"{}\"", internal_str.escape_default().to_string()) | 118 | format!("\"{}\"", internal_str.escape_default().to_string()) |
@@ -154,20 +135,6 @@ fn count_hashes(s: &str) -> usize { | |||
154 | max_hash_streak | 135 | max_hash_streak |
155 | } | 136 | } |
156 | 137 | ||
157 | fn find_usual_string_range(s: &str) -> Option<TextRange> { | ||
158 | let left_quote = s.find('"')?; | ||
159 | let right_quote = s.rfind('"')?; | ||
160 | if left_quote == right_quote { | ||
161 | // `s` only contains one quote | ||
162 | None | ||
163 | } else { | ||
164 | Some(TextRange::from_to( | ||
165 | TextUnit::from(left_quote as u32), | ||
166 | TextUnit::from(right_quote as u32), | ||
167 | )) | ||
168 | } | ||
169 | } | ||
170 | |||
171 | #[cfg(test)] | 138 | #[cfg(test)] |
172 | mod test { | 139 | mod test { |
173 | use super::*; | 140 | use super::*; |
diff --git a/crates/ra_syntax/src/ast/tokens.rs b/crates/ra_syntax/src/ast/tokens.rs index 87cca325d..ed8661faf 100644 --- a/crates/ra_syntax/src/ast/tokens.rs +++ b/crates/ra_syntax/src/ast/tokens.rs | |||
@@ -2,8 +2,8 @@ | |||
2 | 2 | ||
3 | use crate::{ | 3 | use crate::{ |
4 | ast::AstToken, | 4 | ast::AstToken, |
5 | SyntaxKind::{COMMENT, WHITESPACE}, | 5 | SyntaxKind::{COMMENT, RAW_STRING, STRING, WHITESPACE}, |
6 | SyntaxToken, | 6 | SyntaxToken, TextRange, TextUnit, |
7 | }; | 7 | }; |
8 | 8 | ||
9 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | 9 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
@@ -11,10 +11,9 @@ pub struct Comment(SyntaxToken); | |||
11 | 11 | ||
12 | impl AstToken for Comment { | 12 | impl AstToken for Comment { |
13 | fn cast(token: SyntaxToken) -> Option<Self> { | 13 | fn cast(token: SyntaxToken) -> Option<Self> { |
14 | if token.kind() == COMMENT { | 14 | match token.kind() { |
15 | Some(Comment(token)) | 15 | COMMENT => Some(Comment(token)), |
16 | } else { | 16 | _ => None, |
17 | None | ||
18 | } | 17 | } |
19 | } | 18 | } |
20 | fn syntax(&self) -> &SyntaxToken { | 19 | fn syntax(&self) -> &SyntaxToken { |
@@ -94,10 +93,9 @@ pub struct Whitespace(SyntaxToken); | |||
94 | 93 | ||
95 | impl AstToken for Whitespace { | 94 | impl AstToken for Whitespace { |
96 | fn cast(token: SyntaxToken) -> Option<Self> { | 95 | fn cast(token: SyntaxToken) -> Option<Self> { |
97 | if token.kind() == WHITESPACE { | 96 | match token.kind() { |
98 | Some(Whitespace(token)) | 97 | WHITESPACE => Some(Whitespace(token)), |
99 | } else { | 98 | _ => None, |
100 | None | ||
101 | } | 99 | } |
102 | } | 100 | } |
103 | fn syntax(&self) -> &SyntaxToken { | 101 | fn syntax(&self) -> &SyntaxToken { |
@@ -111,3 +109,80 @@ impl Whitespace { | |||
111 | text.find('\n').map_or(false, |idx| text[idx + 1..].contains('\n')) | 109 | text.find('\n').map_or(false, |idx| text[idx + 1..].contains('\n')) |
112 | } | 110 | } |
113 | } | 111 | } |
112 | |||
113 | pub struct String(SyntaxToken); | ||
114 | |||
115 | impl AstToken for String { | ||
116 | fn cast(token: SyntaxToken) -> Option<Self> { | ||
117 | match token.kind() { | ||
118 | STRING => Some(String(token)), | ||
119 | _ => None, | ||
120 | } | ||
121 | } | ||
122 | fn syntax(&self) -> &SyntaxToken { | ||
123 | &self.0 | ||
124 | } | ||
125 | } | ||
126 | |||
127 | impl String { | ||
128 | pub fn value(&self) -> Option<std::string::String> { | ||
129 | let text = self.text().as_str(); | ||
130 | let usual_string_range = find_usual_string_range(text)?; | ||
131 | let start_of_inside = usual_string_range.start().to_usize() + 1; | ||
132 | let end_of_inside = usual_string_range.end().to_usize(); | ||
133 | let inside_str = &text[start_of_inside..end_of_inside]; | ||
134 | |||
135 | let mut buf = std::string::String::with_capacity(inside_str.len()); | ||
136 | let mut has_error = false; | ||
137 | rustc_lexer::unescape::unescape_str(inside_str, &mut |_, unescaped_char| { | ||
138 | match unescaped_char { | ||
139 | Ok(c) => buf.push(c), | ||
140 | Err(_) => has_error = true, | ||
141 | } | ||
142 | }); | ||
143 | |||
144 | if has_error { | ||
145 | return None; | ||
146 | } | ||
147 | Some(buf) | ||
148 | } | ||
149 | } | ||
150 | |||
151 | pub struct RawString(SyntaxToken); | ||
152 | |||
153 | impl AstToken for RawString { | ||
154 | fn cast(token: SyntaxToken) -> Option<Self> { | ||
155 | match token.kind() { | ||
156 | RAW_STRING => Some(RawString(token)), | ||
157 | _ => None, | ||
158 | } | ||
159 | } | ||
160 | fn syntax(&self) -> &SyntaxToken { | ||
161 | &self.0 | ||
162 | } | ||
163 | } | ||
164 | |||
165 | impl RawString { | ||
166 | pub fn value(&self) -> Option<std::string::String> { | ||
167 | let text = self.text().as_str(); | ||
168 | let usual_string_range = find_usual_string_range(text)?; | ||
169 | let start_of_inside = usual_string_range.start().to_usize() + 1; | ||
170 | let end_of_inside = usual_string_range.end().to_usize(); | ||
171 | let inside_str = &text[start_of_inside..end_of_inside]; | ||
172 | Some(inside_str.to_string()) | ||
173 | } | ||
174 | } | ||
175 | |||
176 | fn find_usual_string_range(s: &str) -> Option<TextRange> { | ||
177 | let left_quote = s.find('"')?; | ||
178 | let right_quote = s.rfind('"')?; | ||
179 | if left_quote == right_quote { | ||
180 | // `s` only contains one quote | ||
181 | None | ||
182 | } else { | ||
183 | Some(TextRange::from_to( | ||
184 | TextUnit::from(left_quote as u32), | ||
185 | TextUnit::from(right_quote as u32), | ||
186 | )) | ||
187 | } | ||
188 | } | ||