From 5b54a93fe71137606674ff11dc57bc6e7eaa37f9 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 16 Nov 2019 22:50:41 +0300 Subject: Add ast for plain and raw string literals --- crates/ra_assists/Cargo.toml | 1 - crates/ra_assists/src/assists/raw_string.rs | 59 ++++-------------- crates/ra_syntax/src/ast/tokens.rs | 95 ++++++++++++++++++++++++++--- 3 files changed, 98 insertions(+), 57 deletions(-) (limited to 'crates') 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"] format-buf = "1.0.0" join_to_string = "0.1.3" itertools = "0.8.0" -rustc_lexer = "0.1.0" ra_syntax = { path = "../ra_syntax" } 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 @@ use hir::db::HirDatabase; use ra_syntax::{ + ast, AstToken, SyntaxKind::{RAW_STRING, STRING}, - TextRange, TextUnit, + TextUnit, }; -use rustc_lexer; use crate::{Assist, AssistCtx, AssistId}; @@ -23,32 +23,16 @@ use crate::{Assist, AssistCtx, AssistId}; // } // ``` pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option { - let token = ctx.find_token_at_offset(STRING)?; - let text = token.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 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; - } + let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; + let value = token.value()?; ctx.add_assist(AssistId("make_raw_string"), "make raw string", |edit| { - edit.target(token.text_range()); - let max_hash_streak = count_hashes(&unescaped); + edit.target(token.syntax().text_range()); + let max_hash_streak = count_hashes(&value); let mut hashes = String::with_capacity(max_hash_streak + 1); for _ in 0..hashes.capacity() { 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) -> Option) -> Option { - let token = ctx.find_token_at_offset(RAW_STRING)?; - let text = token.text().as_str(); - let usual_string_range = find_usual_string_range(text)?; + let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; + let value = token.value()?; 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 `"` - 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 escaped = inside_str.escape_default().to_string(); - edit.replace(token.text_range(), format!("\"{}\"", escaped)); + let escaped = value.escape_default().to_string(); + edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); }) } @@ -132,6 +112,7 @@ pub(crate) fn remove_hash(ctx: AssistCtx) -> Option { edit.target(token.text_range()); let result = &text[2..text.len() - 1]; let result = if result.starts_with('\"') { + // FIXME: this logic is wrong, not only the last has has to handled specially // no more hash, escape let internal_str = &result[1..result.len() - 1]; format!("\"{}\"", internal_str.escape_default().to_string()) @@ -154,20 +135,6 @@ fn count_hashes(s: &str) -> usize { max_hash_streak } -fn find_usual_string_range(s: &str) -> Option { - 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)] mod test { 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 @@ use crate::{ ast::AstToken, - SyntaxKind::{COMMENT, WHITESPACE}, - SyntaxToken, + SyntaxKind::{COMMENT, RAW_STRING, STRING, WHITESPACE}, + SyntaxToken, TextRange, TextUnit, }; #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -11,10 +11,9 @@ pub struct Comment(SyntaxToken); impl AstToken for Comment { fn cast(token: SyntaxToken) -> Option { - if token.kind() == COMMENT { - Some(Comment(token)) - } else { - None + match token.kind() { + COMMENT => Some(Comment(token)), + _ => None, } } fn syntax(&self) -> &SyntaxToken { @@ -94,10 +93,9 @@ pub struct Whitespace(SyntaxToken); impl AstToken for Whitespace { fn cast(token: SyntaxToken) -> Option { - if token.kind() == WHITESPACE { - Some(Whitespace(token)) - } else { - None + match token.kind() { + WHITESPACE => Some(Whitespace(token)), + _ => None, } } fn syntax(&self) -> &SyntaxToken { @@ -111,3 +109,80 @@ impl Whitespace { 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 { + match token.kind() { + STRING => Some(String(token)), + _ => None, + } + } + fn syntax(&self) -> &SyntaxToken { + &self.0 + } +} + +impl String { + pub fn value(&self) -> Option { + 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 { + match token.kind() { + RAW_STRING => Some(RawString(token)), + _ => None, + } + } + fn syntax(&self) -> &SyntaxToken { + &self.0 + } +} + +impl RawString { + pub fn value(&self) -> Option { + 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 { + 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), + )) + } +} -- cgit v1.2.3