diff --git a/components/markdown/src/codeblock/highlight.rs b/components/markdown/src/codeblock/highlight.rs index 04d4dddd5..ca9cd624d 100644 --- a/components/markdown/src/codeblock/highlight.rs +++ b/components/markdown/src/codeblock/highlight.rs @@ -6,7 +6,9 @@ use libs::syntect::highlighting::{Color, Theme}; use libs::syntect::html::{ line_tokens_to_classed_spans, styled_line_to_highlighted_html, ClassStyle, IncludeBackground, }; -use libs::syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet}; +use libs::syntect::parsing::{ + ParseState, Scope, ScopeStack, SyntaxReference, SyntaxSet, SCOPE_REPO, +}; use libs::tera::escape_html; /// Not public, but from syntect::html @@ -18,9 +20,28 @@ fn write_css_color(s: &mut String, c: Color) { } } +/// Not public, but from syntect::html +fn scope_to_classes(s: &mut String, scope: Scope, style: ClassStyle) { + let repo = SCOPE_REPO.lock().unwrap(); + for i in 0..(scope.len()) { + let atom = scope.atom_at(i as usize); + let atom_s = repo.atom_str(atom); + if i != 0 { + s.push(' ') + } + match style { + ClassStyle::Spaced => {} + ClassStyle::SpacedPrefixed { prefix } => { + s.push_str(prefix); + } + _ => {} // Non-exhaustive + } + s.push_str(atom_s); + } +} + pub(crate) struct ClassHighlighter<'config> { syntax_set: &'config SyntaxSet, - open_spans: isize, parse_state: ParseState, scope_stack: ScopeStack, } @@ -28,7 +49,7 @@ pub(crate) struct ClassHighlighter<'config> { impl<'config> ClassHighlighter<'config> { pub fn new(syntax: &SyntaxReference, syntax_set: &'config SyntaxSet) -> Self { let parse_state = ParseState::new(syntax); - Self { syntax_set, open_spans: 0, parse_state, scope_stack: ScopeStack::new() } + Self { syntax_set, parse_state, scope_stack: ScopeStack::new() } } /// Parse the line of code and update the internal HTML buffer with tagged HTML @@ -39,24 +60,28 @@ impl<'config> ClassHighlighter<'config> { debug_assert!(line.ends_with('\n')); let parsed_line = self.parse_state.parse_line(line, self.syntax_set).expect("failed to parse line"); - let (formatted_line, delta) = line_tokens_to_classed_spans( + + let mut formatted_line = String::with_capacity(line.len() + self.scope_stack.len() * 8); // A guess + for scope in self.scope_stack.as_slice() { + formatted_line.push_str(""); + } + + let (formatted_contents, _) = line_tokens_to_classed_spans( line, parsed_line.as_slice(), CLASS_STYLE, &mut self.scope_stack, ) .expect("line_tokens_to_classed_spans should not fail"); - self.open_spans += delta; - formatted_line - } + formatted_line.push_str(&formatted_contents); - /// Close all open `` tags and return the finished HTML string - pub fn finalize(&mut self) -> String { - let mut html = String::with_capacity((self.open_spans * 7) as usize); - for _ in 0..self.open_spans { - html.push_str(""); + for _ in 0..self.scope_stack.len() { + formatted_line.push_str(""); } - html + + formatted_line } } @@ -130,15 +155,6 @@ impl<'config> SyntaxHighlighter<'config> { } } - pub fn finalize(&mut self) -> Option { - use SyntaxHighlighter::*; - - match self { - Inlined(_) | NoHighlight => None, - Classed(h) => Some(h.finalize()), - } - } - /// Inlined needs to set the background/foreground colour on
     pub fn pre_style(&self) -> Option {
         use SyntaxHighlighter::*;
@@ -210,7 +226,6 @@ mod tests {
         for line in LinesWithEndings::from(code) {
             out.push_str(&highlighter.highlight_line(line));
         }
-        out.push_str(&highlighter.finalize());
 
         assert!(out.starts_with(""));
diff --git a/components/markdown/src/codeblock/mod.rs b/components/markdown/src/codeblock/mod.rs
index 5c9a46314..01af5f019 100644
--- a/components/markdown/src/codeblock/mod.rs
+++ b/components/markdown/src/codeblock/mod.rs
@@ -168,10 +168,6 @@ impl<'config> CodeBlock<'config> {
             }
         }
 
-        if let Some(rest) = self.highlighter.finalize() {
-            buffer.push_str(&rest);
-        }
-
         if self.line_numbers {
             buffer.push_str("");
         }
diff --git a/components/markdown/tests/codeblocks.rs b/components/markdown/tests/codeblocks.rs
index 4b27d2966..a565a6a2f 100644
--- a/components/markdown/tests/codeblocks.rs
+++ b/components/markdown/tests/codeblocks.rs
@@ -2,9 +2,24 @@ use config::Config;
 
 mod common;
 
-fn render_codeblock(content: &str, highlight_code: bool) -> String {
+enum HighlightMode {
+    None,
+    Inlined,
+    Classed,
+}
+
+fn render_codeblock(content: &str, highlight_mode: HighlightMode) -> String {
     let mut config = Config::default_for_test();
-    config.markdown.highlight_code = highlight_code;
+    match highlight_mode {
+        HighlightMode::None => {}
+        HighlightMode::Inlined => {
+            config.markdown.highlight_code = true;
+        }
+        HighlightMode::Classed => {
+            config.markdown.highlight_code = true;
+            config.markdown.highlight_theme = "css".to_owned();
+        }
+    }
     common::render_with_config(content, config).unwrap().body
 }
 
@@ -17,7 +32,7 @@ foo
 bar
 ```
     "#,
-        false,
+        HighlightMode::None,
     );
     insta::assert_snapshot!(body);
 }
@@ -33,7 +48,7 @@ baz
 bat
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
     );
     insta::assert_snapshot!(body);
 }
@@ -49,7 +64,7 @@ bar
 baz
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
     );
     insta::assert_snapshot!(body);
 }
@@ -65,7 +80,7 @@ bar
 baz
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
     );
     insta::assert_snapshot!(body);
 }
@@ -81,7 +96,7 @@ bar
 baz
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
     );
     insta::assert_snapshot!(body);
 }
@@ -97,7 +112,7 @@ bar
 baz
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
     );
     let body2 = render_codeblock(
         r#"
@@ -108,7 +123,7 @@ bar
 baz
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
     );
     assert_eq!(body, body2);
 }
@@ -124,7 +139,7 @@ bar
 baz
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
     );
     insta::assert_snapshot!(body);
 }
@@ -140,7 +155,7 @@ bar
 baz
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
     );
     insta::assert_snapshot!(body);
 }
@@ -156,7 +171,7 @@ bar
 baz
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
     );
     insta::assert_snapshot!(body);
 }
@@ -172,7 +187,7 @@ bar
 baz
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
     );
     insta::assert_snapshot!(body);
 }
@@ -188,7 +203,7 @@ bar
 baz
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
     );
     insta::assert_snapshot!(body);
 }
@@ -204,7 +219,7 @@ bar
 baz
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
     );
     insta::assert_snapshot!(body);
 }
@@ -220,7 +235,24 @@ bar
 baz
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
+    );
+    insta::assert_snapshot!(body);
+}
+
+#[test]
+fn can_highlight_with_classes() {
+    let body = render_codeblock(
+        r#"
+```html,hl_lines=3-4
+
+```
+    "#,
+        HighlightMode::Classed,
     );
     insta::assert_snapshot!(body);
 }
@@ -234,14 +266,14 @@ foo
 bar
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
     );
     insta::assert_snapshot!(body);
 }
 
 #[test]
 fn can_add_line_numbers_windows_eol() {
-    let body = render_codeblock("```linenos\r\nfoo\r\nbar\r\n```\r\n", true);
+    let body = render_codeblock("```linenos\r\nfoo\r\nbar\r\n```\r\n", HighlightMode::Inlined);
     insta::assert_snapshot!(body);
 }
 
@@ -254,7 +286,7 @@ foo
 bar
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
     );
     insta::assert_snapshot!(body);
 }
@@ -268,7 +300,24 @@ foo
 bar
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
+    );
+    insta::assert_snapshot!(body);
+}
+
+#[test]
+fn can_add_line_numbers_with_classes() {
+    let body = render_codeblock(
+        r#"
+```html,linenos
+
+```
+    "#,
+        HighlightMode::Classed,
     );
     insta::assert_snapshot!(body);
 }
@@ -283,7 +332,7 @@ fn can_render_shortcode_in_codeblock() {
 
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
     );
     insta::assert_snapshot!(body);
 }
@@ -300,7 +349,7 @@ text2
 text3
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
     );
     insta::assert_snapshot!(body);
 }
@@ -323,7 +372,7 @@ A quote
 
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
     );
     insta::assert_snapshot!(body);
 }
@@ -337,7 +386,7 @@ foo
 bar
 ```
     "#,
-        true,
+        HighlightMode::Inlined,
     );
     insta::assert_snapshot!(body);
 }
diff --git a/components/markdown/tests/snapshots/codeblocks__can_add_line_numbers_with_classes.snap b/components/markdown/tests/snapshots/codeblocks__can_add_line_numbers_with_classes.snap
new file mode 100644
index 000000000..4d2268935
--- /dev/null
+++ b/components/markdown/tests/snapshots/codeblocks__can_add_line_numbers_with_classes.snap
@@ -0,0 +1,11 @@
+---
+source: components/markdown/tests/codeblocks.rs
+expression: body
+---
+
1<link +
2 rel="stylesheet" +
3 type="text/css" +
4 href="main.css" +
5/> +
+ diff --git a/components/markdown/tests/snapshots/codeblocks__can_highlight_with_classes.snap b/components/markdown/tests/snapshots/codeblocks__can_highlight_with_classes.snap new file mode 100644 index 000000000..e4feb60b3 --- /dev/null +++ b/components/markdown/tests/snapshots/codeblocks__can_highlight_with_classes.snap @@ -0,0 +1,11 @@ +--- +source: components/markdown/tests/codeblocks.rs +expression: body +--- +
<link
+    rel="stylesheet"
+    type="text/css"
+    href="main.css"
+/>
+
+