aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2019-11-16 19:50:41 +0000
committerAleksey Kladov <[email protected]>2019-11-16 19:50:41 +0000
commit5b54a93fe71137606674ff11dc57bc6e7eaa37f9 (patch)
treee3766c348d90f933b0f374b0a528e2ffc67d58fd /crates
parent42604c673d159edc1571732c6be1dc00a365b7be (diff)
Add ast for plain and raw string literals
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/Cargo.toml1
-rw-r--r--crates/ra_assists/src/assists/raw_string.rs59
-rw-r--r--crates/ra_syntax/src/ast/tokens.rs95
3 files changed, 98 insertions, 57 deletions
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"]
8format-buf = "1.0.0" 8format-buf = "1.0.0"
9join_to_string = "0.1.3" 9join_to_string = "0.1.3"
10itertools = "0.8.0" 10itertools = "0.8.0"
11rustc_lexer = "0.1.0"
12 11
13ra_syntax = { path = "../ra_syntax" } 12ra_syntax = { path = "../ra_syntax" }
14ra_text_edit = { path = "../ra_text_edit" } 13ra_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 @@
1use hir::db::HirDatabase; 1use hir::db::HirDatabase;
2use ra_syntax::{ 2use ra_syntax::{
3 ast, AstToken,
3 SyntaxKind::{RAW_STRING, STRING}, 4 SyntaxKind::{RAW_STRING, STRING},
4 TextRange, TextUnit, 5 TextUnit,
5}; 6};
6use rustc_lexer;
7 7
8use crate::{Assist, AssistCtx, AssistId}; 8use crate::{Assist, AssistCtx, AssistId};
9 9
@@ -23,32 +23,16 @@ use crate::{Assist, AssistCtx, AssistId};
23// } 23// }
24// ``` 24// ```
25pub(crate) fn make_raw_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 25pub(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// ```
70pub(crate) fn make_usual_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 54pub(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
157fn 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)]
172mod test { 139mod 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
3use crate::{ 3use 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
12impl AstToken for Comment { 12impl 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
95impl AstToken for Whitespace { 94impl 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
113pub struct String(SyntaxToken);
114
115impl 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
127impl 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
151pub struct RawString(SyntaxToken);
152
153impl 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
165impl 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
176fn 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}