From b244bcdfbbb0abf982504a6aee7ed630f77acaf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Fri, 14 May 2021 20:25:13 +0200 Subject: [PATCH] Hide lines in code block (#1453) * hide lines * test hide ines * add documentation * fix test * factor shared code with hl_lines --- .../rendering/src/markdown/codeblock.rs | 42 ++++++++++++- components/rendering/src/markdown/fence.rs | 31 +++++++--- .../rendering/tests/codeblock_hide_lines.rs | 61 +++++++++++++++++++ .../content/syntax-highlighting.md | 38 +++++++++++- 4 files changed, 163 insertions(+), 9 deletions(-) create mode 100644 components/rendering/tests/codeblock_hide_lines.rs diff --git a/components/rendering/src/markdown/codeblock.rs b/components/rendering/src/markdown/codeblock.rs index 3c3a194c..a4dec6a9 100644 --- a/components/rendering/src/markdown/codeblock.rs +++ b/components/rendering/src/markdown/codeblock.rs @@ -17,6 +17,8 @@ pub struct CodeBlock<'config> { /// List of ranges of lines to highlight. highlight_lines: Vec, + /// List of ranges of lines to hide. + hide_lines: Vec, /// The number of lines in the code block being processed. num_lines: usize, } @@ -52,6 +54,7 @@ impl<'config> CodeBlock<'config> { theme, highlight_lines: fence_info.highlight_lines, + hide_lines: fence_info.hide_lines, num_lines: 0, } } @@ -77,6 +80,9 @@ impl<'config> CodeBlock<'config> { let hl_lines = self.get_highlighted_lines(); color_highlighted_lines(&mut highlighted, &hl_lines, hl_background); + let hide_lines = self.get_hidden_lines(); + let highlighted = hide_hidden_lines(highlighted, &hide_lines); + styled_line_to_highlighted_html(&highlighted, self.background) } @@ -94,8 +100,16 @@ impl<'config> CodeBlock<'config> { } fn get_highlighted_lines(&self) -> HashSet { + self.ranges_to_lines(&self.highlight_lines) + } + + fn get_hidden_lines(&self) -> HashSet { + self.ranges_to_lines(&self.hide_lines) + } + + fn ranges_to_lines(&self, range: &Vec) -> HashSet { let mut lines = HashSet::new(); - for range in &self.highlight_lines { + for range in range { for line in range.from..=min(range.to, self.num_lines) { // Ranges are one-indexed lines.insert(line.saturating_sub(1)); @@ -196,3 +210,29 @@ fn color_highlighted_lines(data: &mut [(Style, &str)], lines: &HashSet, b } } } + +fn hide_hidden_lines<'a>( + data: Vec<(Style, &'a str)>, + lines: &HashSet, +) -> Vec<(Style, &'a str)> { + if lines.is_empty() { + return data; + } + + let mut current_line = 0; + + let mut to_keep = Vec::new(); + + for item in data { + if !lines.contains(¤t_line) { + to_keep.push(item); + } + + // We split the lines such that every newline is at the end of an item. + if item.1.ends_with('\n') { + current_line += 1; + } + } + + to_keep +} diff --git a/components/rendering/src/markdown/fence.rs b/components/rendering/src/markdown/fence.rs index e41b73a0..23bd8040 100644 --- a/components/rendering/src/markdown/fence.rs +++ b/components/rendering/src/markdown/fence.rs @@ -28,16 +28,23 @@ pub struct FenceSettings<'a> { pub language: Option<&'a str>, pub line_numbers: bool, pub highlight_lines: Vec, + pub hide_lines: Vec, } impl<'a> FenceSettings<'a> { pub fn new(fence_info: &'a str) -> Self { - let mut me = Self { language: None, line_numbers: false, highlight_lines: Vec::new() }; + let mut me = Self { + language: None, + line_numbers: false, + highlight_lines: Vec::new(), + hide_lines: Vec::new(), + }; for token in FenceIter::new(fence_info) { match token { FenceToken::Language(lang) => me.language = Some(lang), FenceToken::EnableLineNumbers => me.line_numbers = true, FenceToken::HighlightLines(lines) => me.highlight_lines.extend(lines), + FenceToken::HideLines(lines) => me.hide_lines.extend(lines), } } @@ -50,6 +57,7 @@ enum FenceToken<'a> { Language(&'a str), EnableLineNumbers, HighlightLines(Vec), + HideLines(Vec), } struct FenceIter<'a> { @@ -59,6 +67,16 @@ impl<'a> FenceIter<'a> { fn new(fence_info: &'a str) -> Self { Self { split: fence_info.split(',') } } + + fn parse_ranges(token: Option<&str>) -> Vec { + let mut ranges = Vec::new(); + for range in token.unwrap_or("").split(' ') { + if let Some(range) = Range::parse(range) { + ranges.push(range); + } + } + ranges + } } impl<'a> Iterator for FenceIter<'a> { @@ -73,14 +91,13 @@ impl<'a> Iterator for FenceIter<'a> { "" => continue, "linenos" => return Some(FenceToken::EnableLineNumbers), "hl_lines" => { - let mut ranges = Vec::new(); - for range in tok_split.next().unwrap_or("").split(' ') { - if let Some(range) = Range::parse(range) { - ranges.push(range); - } - } + let ranges = Self::parse_ranges(tok_split.next()); return Some(FenceToken::HighlightLines(ranges)); } + "hide_lines" => { + let ranges = Self::parse_ranges(tok_split.next()); + return Some(FenceToken::HideLines(ranges)); + } lang => { return Some(FenceToken::Language(lang)); } diff --git a/components/rendering/tests/codeblock_hide_lines.rs b/components/rendering/tests/codeblock_hide_lines.rs new file mode 100644 index 00000000..bd8412a1 --- /dev/null +++ b/components/rendering/tests/codeblock_hide_lines.rs @@ -0,0 +1,61 @@ +use std::collections::HashMap; + +use tera::Tera; + +use config::Config; +use front_matter::InsertAnchor; +use rendering::{render_content, RenderContext}; + +macro_rules! colored_html_line { + ( $s:expr ) => {{ + let mut result = "".to_string(); + result.push_str($s); + result.push_str("\n"); + result + }}; +} + +macro_rules! colored_html { + ( $($s:expr),* $(,)* ) => {{ + let mut result = "
\n".to_string();
+        $(
+            result.push_str(colored_html_line!($s).as_str());
+        )*
+        result.push_str("
"); + result + }}; +} + +#[test] +fn hide_lines_simple() { + let tera_ctx = Tera::default(); + let permalinks_ctx = HashMap::new(); + let mut config = Config::default(); + config.markdown.highlight_code = true; + let context = RenderContext::new( + &tera_ctx, + &config, + &config.default_language, + "", + &permalinks_ctx, + InsertAnchor::None, + ); + let res = render_content( + r#" +```hide_lines=2 +foo +bar +baz +bat +``` + "#, + &context, + ) + .unwrap(); + assert_eq!( + res.body, + colored_html!( + "foo\nbaz\nbat", + ) + ); +} diff --git a/docs/content/documentation/content/syntax-highlighting.md b/docs/content/documentation/content/syntax-highlighting.md index 6e0e5fa6..2e63f206 100644 --- a/docs/content/documentation/content/syntax-highlighting.md +++ b/docs/content/documentation/content/syntax-highlighting.md @@ -150,7 +150,6 @@ Here is a full list of supported languages and their short names: ``` Note: due to some issues with the JavaScript syntax, the TypeScript syntax will be used instead. -If If you want to highlight a language not on this list, please open an issue or a pull request on the [Zola repo](https://github.com/getzola/zola). Alternatively, the `extra_syntaxes` configuration option can be used to add additional syntax files. @@ -173,3 +172,40 @@ If your site source is laid out as follows: ``` you would set your `extra_syntaxes` to `["syntaxes", "syntaxes/Sublime-Language1"]` to load `lang1.sublime-syntax` and `lang2.sublime-syntax`. + +## Annotations + +You can use additional annotations to customize how code blocks are displayed: + +- `linenos` to enable line numbering. +````md + +```rust,linenos +use highlighter::highlight; +let code = "..."; +highlight(code); +``` + +```` +- `hl_lines` to highlight lines. You must specify a list of ranges of lines to highlight, +separated by ` `. Ranges are 1-indexed. +````md + +```rust,hl_lines=3 +use highlighter::highlight; +let code = "..."; +highlight(code); +``` + +```` +- `hide_lines` to hide lines. You must specify a list of ranges of lines to hide, +separated by ` `. Ranges are 1-indexed. +````md + +```rust,hide_lines=1-2 +use highlighter::highlight; +let code = "..."; +highlight(code); +``` + +````