use hir::db::HirDatabase;
use ra_syntax::{
+ ast, AstToken,
SyntaxKind::{RAW_STRING, STRING},
- TextRange, TextUnit,
+ TextUnit,
};
-use rustc_lexer;
use crate::{Assist, AssistCtx, AssistId};
// }
// ```
pub(crate) fn make_raw_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
- 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));
})
}
// }
// ```
pub(crate) fn make_usual_string(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
- 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));
})
}
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())
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)]
mod test {
use super::*;
use crate::{
ast::AstToken,
- SyntaxKind::{COMMENT, WHITESPACE},
- SyntaxToken,
+ SyntaxKind::{COMMENT, RAW_STRING, STRING, WHITESPACE},
+ SyntaxToken, TextRange, TextUnit,
};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
impl AstToken for Comment {
fn cast(token: SyntaxToken) -> Option<Self> {
- if token.kind() == COMMENT {
- Some(Comment(token))
- } else {
- None
+ match token.kind() {
+ COMMENT => Some(Comment(token)),
+ _ => None,
}
}
fn syntax(&self) -> &SyntaxToken {
impl AstToken for Whitespace {
fn cast(token: SyntaxToken) -> Option<Self> {
- if token.kind() == WHITESPACE {
- Some(Whitespace(token))
- } else {
- None
+ match token.kind() {
+ WHITESPACE => Some(Whitespace(token)),
+ _ => None,
}
}
fn syntax(&self) -> &SyntaxToken {
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),
+ ))
+ }
+}