Add ast for plain and raw string literals

This commit is contained in:
Aleksey Kladov 2019-11-16 22:50:41 +03:00
parent 42604c673d
commit 5b54a93fe7
4 changed files with 98 additions and 58 deletions

1
Cargo.lock generated
View file

@ -947,7 +947,6 @@ dependencies = [
"ra_hir 0.1.0", "ra_hir 0.1.0",
"ra_syntax 0.1.0", "ra_syntax 0.1.0",
"ra_text_edit 0.1.0", "ra_text_edit 0.1.0",
"rustc_lexer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"test_utils 0.1.0", "test_utils 0.1.0",
] ]

View file

@ -8,7 +8,6 @@ authors = ["rust-analyzer developers"]
format-buf = "1.0.0" format-buf = "1.0.0"
join_to_string = "0.1.3" join_to_string = "0.1.3"
itertools = "0.8.0" itertools = "0.8.0"
rustc_lexer = "0.1.0"
ra_syntax = { path = "../ra_syntax" } ra_syntax = { path = "../ra_syntax" }
ra_text_edit = { path = "../ra_text_edit" } ra_text_edit = { path = "../ra_text_edit" }

View file

@ -1,9 +1,9 @@
use hir::db::HirDatabase; use hir::db::HirDatabase;
use ra_syntax::{ use ra_syntax::{
ast, AstToken,
SyntaxKind::{RAW_STRING, STRING}, SyntaxKind::{RAW_STRING, STRING},
TextRange, TextUnit, TextUnit,
}; };
use rustc_lexer;
use crate::{Assist, AssistCtx, AssistId}; use crate::{Assist, AssistCtx, AssistId};
@ -23,32 +23,16 @@ use crate::{Assist, AssistCtx, AssistId};
// } // }
// ``` // ```
pub(crate) fn make_raw_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { pub(crate) fn make_raw_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
let token = ctx.find_token_at_offset(STRING)?; let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
let text = token.text().as_str(); let value = token.value()?;
let usual_string_range = find_usual_string_range(text)?;
let start_of_inside = usual_string_range.start().to_usize() + 1;
let end_of_inside = usual_string_range.end().to_usize();
let inside_str = &text[start_of_inside..end_of_inside];
let mut unescaped = String::with_capacity(inside_str.len());
let mut error = Ok(());
rustc_lexer::unescape::unescape_str(
inside_str,
&mut |_, unescaped_char| match unescaped_char {
Ok(c) => unescaped.push(c),
Err(_) => error = Err(()),
},
);
if error.is_err() {
return None;
}
ctx.add_assist(AssistId("make_raw_string"), "make raw string", |edit| { ctx.add_assist(AssistId("make_raw_string"), "make raw string", |edit| {
edit.target(token.text_range()); edit.target(token.syntax().text_range());
let max_hash_streak = count_hashes(&unescaped); let max_hash_streak = count_hashes(&value);
let mut hashes = String::with_capacity(max_hash_streak + 1); let mut hashes = String::with_capacity(max_hash_streak + 1);
for _ in 0..hashes.capacity() { for _ in 0..hashes.capacity() {
hashes.push('#'); hashes.push('#');
} }
edit.replace(token.text_range(), format!("r{}\"{}\"{}", hashes, unescaped, hashes)); edit.replace(token.syntax().text_range(), format!("r{}\"{}\"{}", hashes, value, hashes));
}) })
} }
@ -68,17 +52,13 @@ pub(crate) fn make_raw_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist
// } // }
// ``` // ```
pub(crate) fn make_usual_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { pub(crate) fn make_usual_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
let token = ctx.find_token_at_offset(RAW_STRING)?; let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
let text = token.text().as_str(); let value = token.value()?;
let usual_string_range = find_usual_string_range(text)?;
ctx.add_assist(AssistId("make_usual_string"), "make usual string", |edit| { ctx.add_assist(AssistId("make_usual_string"), "make usual string", |edit| {
edit.target(token.text_range()); edit.target(token.syntax().text_range());
// parse inside string to escape `"` // parse inside string to escape `"`
let start_of_inside = usual_string_range.start().to_usize() + 1; let escaped = value.escape_default().to_string();
let end_of_inside = usual_string_range.end().to_usize(); edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
let inside_str = &text[start_of_inside..end_of_inside];
let escaped = inside_str.escape_default().to_string();
edit.replace(token.text_range(), format!("\"{}\"", escaped));
}) })
} }
@ -132,6 +112,7 @@ pub(crate) fn remove_hash(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
edit.target(token.text_range()); edit.target(token.text_range());
let result = &text[2..text.len() - 1]; let result = &text[2..text.len() - 1];
let result = if result.starts_with('\"') { let result = if result.starts_with('\"') {
// FIXME: this logic is wrong, not only the last has has to handled specially
// no more hash, escape // no more hash, escape
let internal_str = &result[1..result.len() - 1]; let internal_str = &result[1..result.len() - 1];
format!("\"{}\"", internal_str.escape_default().to_string()) format!("\"{}\"", internal_str.escape_default().to_string())
@ -154,20 +135,6 @@ fn count_hashes(s: &str) -> usize {
max_hash_streak max_hash_streak
} }
fn find_usual_string_range(s: &str) -> Option<TextRange> {
let left_quote = s.find('"')?;
let right_quote = s.rfind('"')?;
if left_quote == right_quote {
// `s` only contains one quote
None
} else {
Some(TextRange::from_to(
TextUnit::from(left_quote as u32),
TextUnit::from(right_quote as u32),
))
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;

View file

@ -2,8 +2,8 @@
use crate::{ use crate::{
ast::AstToken, ast::AstToken,
SyntaxKind::{COMMENT, WHITESPACE}, SyntaxKind::{COMMENT, RAW_STRING, STRING, WHITESPACE},
SyntaxToken, SyntaxToken, TextRange, TextUnit,
}; };
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
@ -11,10 +11,9 @@ pub struct Comment(SyntaxToken);
impl AstToken for Comment { impl AstToken for Comment {
fn cast(token: SyntaxToken) -> Option<Self> { fn cast(token: SyntaxToken) -> Option<Self> {
if token.kind() == COMMENT { match token.kind() {
Some(Comment(token)) COMMENT => Some(Comment(token)),
} else { _ => None,
None
} }
} }
fn syntax(&self) -> &SyntaxToken { fn syntax(&self) -> &SyntaxToken {
@ -94,10 +93,9 @@ pub struct Whitespace(SyntaxToken);
impl AstToken for Whitespace { impl AstToken for Whitespace {
fn cast(token: SyntaxToken) -> Option<Self> { fn cast(token: SyntaxToken) -> Option<Self> {
if token.kind() == WHITESPACE { match token.kind() {
Some(Whitespace(token)) WHITESPACE => Some(Whitespace(token)),
} else { _ => None,
None
} }
} }
fn syntax(&self) -> &SyntaxToken { fn syntax(&self) -> &SyntaxToken {
@ -111,3 +109,80 @@ impl Whitespace {
text.find('\n').map_or(false, |idx| text[idx + 1..].contains('\n')) text.find('\n').map_or(false, |idx| text[idx + 1..].contains('\n'))
} }
} }
pub struct String(SyntaxToken);
impl AstToken for String {
fn cast(token: SyntaxToken) -> Option<Self> {
match token.kind() {
STRING => Some(String(token)),
_ => None,
}
}
fn syntax(&self) -> &SyntaxToken {
&self.0
}
}
impl String {
pub fn value(&self) -> Option<std::string::String> {
let text = self.text().as_str();
let usual_string_range = find_usual_string_range(text)?;
let start_of_inside = usual_string_range.start().to_usize() + 1;
let end_of_inside = usual_string_range.end().to_usize();
let inside_str = &text[start_of_inside..end_of_inside];
let mut buf = std::string::String::with_capacity(inside_str.len());
let mut has_error = false;
rustc_lexer::unescape::unescape_str(inside_str, &mut |_, unescaped_char| {
match unescaped_char {
Ok(c) => buf.push(c),
Err(_) => has_error = true,
}
});
if has_error {
return None;
}
Some(buf)
}
}
pub struct RawString(SyntaxToken);
impl AstToken for RawString {
fn cast(token: SyntaxToken) -> Option<Self> {
match token.kind() {
RAW_STRING => Some(RawString(token)),
_ => None,
}
}
fn syntax(&self) -> &SyntaxToken {
&self.0
}
}
impl RawString {
pub fn value(&self) -> Option<std::string::String> {
let text = self.text().as_str();
let usual_string_range = find_usual_string_range(text)?;
let start_of_inside = usual_string_range.start().to_usize() + 1;
let end_of_inside = usual_string_range.end().to_usize();
let inside_str = &text[start_of_inside..end_of_inside];
Some(inside_str.to_string())
}
}
fn find_usual_string_range(s: &str) -> Option<TextRange> {
let left_quote = s.find('"')?;
let right_quote = s.rfind('"')?;
if left_quote == right_quote {
// `s` only contains one quote
None
} else {
Some(TextRange::from_to(
TextUnit::from(left_quote as u32),
TextUnit::from(right_quote as u32),
))
}
}