]> git.lizzy.rs Git - rust.git/commitdiff
Add ast for plain and raw string literals
authorAleksey Kladov <aleksey.kladov@gmail.com>
Sat, 16 Nov 2019 19:50:41 +0000 (22:50 +0300)
committerAleksey Kladov <aleksey.kladov@gmail.com>
Sat, 16 Nov 2019 19:50:41 +0000 (22:50 +0300)
Cargo.lock
crates/ra_assists/Cargo.toml
crates/ra_assists/src/assists/raw_string.rs
crates/ra_syntax/src/ast/tokens.rs

index 16fbe750202c153e702a9a671149595cb6f37f1a..820cf2acccdf99fe340fcf02e3738a22cd5017ca 100644 (file)
@@ -947,7 +947,6 @@ dependencies = [
  "ra_hir 0.1.0",
  "ra_syntax 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",
 ]
 
index beebccbd9cb6e61b26279a247145afb637e11c4b..125c6222d532b95739f119291fb139d36eceed96 100644 (file)
@@ -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" }
index 58f7157ae21c9f72527784fa48d6adbfda122614..93912a4706686a81cb4cce55358ce3cc03d07105 100644 (file)
@@ -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};
 
 // }
 // ```
 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));
     })
 }
 
@@ -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> {
-    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<impl HirDatabase>) -> Option<Assist> {
         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<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::*;
index 87cca325d3fc9dec956abce06e6908167c8d8203..ed8661faf5db94417cfdb649450cc9ecdf529734 100644 (file)
@@ -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)]
 
 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 {
@@ -94,10 +93,9 @@ fn prefix_by_kind(kind: CommentKind) -> &'static str {
 
 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 {
@@ -111,3 +109,80 @@ pub fn spans_multiple_lines(&self) -> bool {
         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),
+        ))
+    }
+}