瀏覽代碼

Implement markdown code block coloring

This will allow a future parser to query whether a certain position in
the input text is within code (and as such should not be interpreted as
command start).
Mark Rousskov 6 年之前
父節點
當前提交
c5a133c828
共有 3 個文件被更改,包括 198 次插入0 次删除
  1. 28 0
      Cargo.lock
  2. 1 0
      parser/Cargo.toml
  3. 169 0
      parser/src/code_block.rs

+ 28 - 0
Cargo.lock

@@ -379,6 +379,14 @@ dependencies = [
  "num_cpus 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "getopts"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "h2"
 version = "0.1.16"
@@ -807,6 +815,9 @@ dependencies = [
 [[package]]
 name = "parser"
 version = "0.1.0"
+dependencies = [
+ "pulldown-cmark 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
 
 [[package]]
 name = "pear"
@@ -881,6 +892,15 @@ dependencies = [
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "pulldown-cmark"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "quick-error"
 version = "1.2.2"
@@ -1543,6 +1563,11 @@ dependencies = [
  "smallvec 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "unicode-width"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "unicode-xid"
 version = "0.1.0"
@@ -1731,6 +1756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
 "checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b"
 "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4"
+"checksum getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797"
 "checksum h2 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "ddb2b25a33e231484694267af28fec74ac63b5ccf51ee2065a5e313b834d836e"
 "checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
 "checksum http 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "1a10e5b573b9a0146545010f50772b9e8b1dd0a256564cc4307694c68832a2f5"
@@ -1784,6 +1810,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0"
 "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c"
 "checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915"
+"checksum pulldown-cmark 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eef52fac62d0ea7b9b4dc7da092aa64ea7ec3d90af6679422d3d7e0e14b6ee15"
 "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
 "checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1"
 "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
@@ -1853,6 +1880,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum unicase 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d3218ea14b4edcaccfa0df0a64a3792a2c32cc706f1b336e48867f9d3147f90"
 "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
 "checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426"
+"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
 "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
 "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
 "checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f"

+ 1 - 0
parser/Cargo.toml

@@ -5,3 +5,4 @@ authors = ["Mark Rousskov <mark.simulacrum@gmail.com>"]
 edition = "2018"
 
 [dependencies]
+pulldown-cmark = "0.2.0"

+ 169 - 0
parser/src/code_block.rs

@@ -0,0 +1,169 @@
+use pulldown_cmark::{Event, Parser, Tag};
+use std::ops::Range;
+
+pub struct ColorCodeBlocks {
+    code: Vec<Range<usize>>,
+}
+
+impl ColorCodeBlocks {
+    pub fn new(s: &str) -> ColorCodeBlocks {
+        let mut code = Vec::new();
+        let mut parser = Parser::new(s);
+        let mut before_event = parser.get_offset();
+        'outer: while let Some(event) = parser.next() {
+            if let Event::Start(Tag::Code) | Event::Start(Tag::CodeBlock(_)) = event {
+                let start = before_event;
+                loop {
+                    match parser.next() {
+                        Some(Event::End(Tag::Code)) | Some(Event::End(Tag::CodeBlock(_))) => {
+                            let end = parser.get_offset();
+                            code.push(start..end);
+                            break;
+                        }
+                        Some(_) => {}
+                        None => break 'outer,
+                    }
+                }
+            }
+            before_event = parser.get_offset();
+        }
+
+        ColorCodeBlocks { code }
+    }
+
+    pub fn is_in_code(&self, pos: usize) -> bool {
+        for range in &self.code {
+            if range.start <= pos && pos <= range.end {
+                return true;
+            }
+        }
+
+        false
+    }
+}
+
+#[cfg(test)]
+#[derive(Debug, PartialEq, Eq)]
+enum Code<'a> {
+    Yes(&'a str),
+    No(&'a str),
+}
+
+#[cfg(test)]
+fn bodies(s: &str) -> Vec<Code<'_>> {
+    let mut bodies = Vec::new();
+    let cbs = ColorCodeBlocks::new(s);
+    let mut previous = 0..0;
+    for range in &cbs.code {
+        let range = range.clone();
+        if previous.end != range.start {
+            bodies.push(Code::No(&s[previous.end..range.start]));
+        }
+        bodies.push(Code::Yes(&s[range.clone()]));
+        previous = range.clone();
+    }
+    if let Some(range) = cbs.code.last() {
+        if range.end != s.len() {
+            bodies.push(Code::No(&s[range.end..]));
+        }
+    }
+    bodies
+}
+
+#[test]
+fn cbs_1() {
+    assert_eq!(
+        bodies("`hey you`bar me too"),
+        [Code::Yes("`hey you`"), Code::No("bar me too")]
+    );
+}
+
+#[test]
+fn cbs_2() {
+    assert_eq!(
+        bodies("`hey you` <b>me too</b>"),
+        [Code::Yes("`hey you`"), Code::No(" <b>me too</b>")]
+    );
+}
+
+#[test]
+fn cbs_3() {
+    assert_eq!(
+        bodies(r"`hey you\` <b>`me too</b>"),
+        [Code::Yes(r"`hey you\`"), Code::No(" <b>`me too</b>")]
+    );
+}
+
+#[test]
+fn cbs_4() {
+    assert_eq!(
+        bodies(
+            "
+```language_spec
+testing
+```
+
+nope
+"
+        ),
+        [
+            Code::No("\n"),
+            Code::Yes("```language_spec\ntesting\n```\n"),
+            Code::No("\nnope\n")
+        ],
+    );
+}
+
+#[test]
+fn cbs_5() {
+    assert_eq!(
+        bodies(concat!(
+            "
+```     tag_after_space
+testing
+```",
+            "           "
+        )),
+        [
+            Code::No("\n"),
+            Code::Yes("```     tag_after_space\ntesting\n```           "),
+        ],
+    );
+}
+
+#[test]
+fn cbs_6() {
+    assert_eq!(
+        bodies(
+            "
+    this is indented
+    this is indented too
+"
+        ),
+        [
+            Code::No("\n"),
+            Code::Yes("    this is indented\n    this is indented too\n"),
+        ],
+    );
+}
+
+#[test]
+fn cbs_7() {
+    assert_eq!(
+        bodies(
+            "
+```
+testing unclosed
+"
+        ),
+        [Code::No("\n"), Code::Yes("```\ntesting unclosed\n"),],
+    );
+}
+
+#[test]
+fn cbs_8() {
+    assert_eq!(
+        bodies("`one` not `two`"),
+        [Code::Yes("`one`"), Code::No(" not "), Code::Yes("`two`")]
+    );
+}