Hide lines in code block (#1453)

* hide lines

* test hide ines

* add documentation

* fix test

* factor shared code with hl_lines
This commit is contained in:
François 2021-05-14 20:25:13 +02:00 committed by GitHub
parent 5365efebb3
commit b244bcdfbb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 163 additions and 9 deletions

View file

@ -17,6 +17,8 @@ pub struct CodeBlock<'config> {
/// List of ranges of lines to highlight.
highlight_lines: Vec<Range>,
/// List of ranges of lines to hide.
hide_lines: Vec<Range>,
/// 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<usize> {
self.ranges_to_lines(&self.highlight_lines)
}
fn get_hidden_lines(&self) -> HashSet<usize> {
self.ranges_to_lines(&self.hide_lines)
}
fn ranges_to_lines(&self, range: &Vec<Range>) -> HashSet<usize> {
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<usize>, b
}
}
}
fn hide_hidden_lines<'a>(
data: Vec<(Style, &'a str)>,
lines: &HashSet<usize>,
) -> 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(&current_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
}

View file

@ -28,16 +28,23 @@ pub struct FenceSettings<'a> {
pub language: Option<&'a str>,
pub line_numbers: bool,
pub highlight_lines: Vec<Range>,
pub hide_lines: Vec<Range>,
}
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<Range>),
HideLines(Vec<Range>),
}
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<Range> {
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));
}

View file

@ -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 = "<span style=\"color:#c0c5ce;\">".to_string();
result.push_str($s);
result.push_str("\n</span>");
result
}};
}
macro_rules! colored_html {
( $($s:expr),* $(,)* ) => {{
let mut result = "<pre style=\"background-color:#2b303b;\">\n<code>".to_string();
$(
result.push_str(colored_html_line!($s).as_str());
)*
result.push_str("</code></pre>");
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",
)
);
}

View file

@ -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);
```
````