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:
parent
5365efebb3
commit
b244bcdfbb
|
@ -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(¤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
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
61
components/rendering/tests/codeblock_hide_lines.rs
Normal file
61
components/rendering/tests/codeblock_hide_lines.rs
Normal 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",
|
||||
)
|
||||
);
|
||||
}
|
|
@ -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);
|
||||
```
|
||||
|
||||
````
|
||||
|
|
Loading…
Reference in a new issue