Refactor HTML processing into viewer::utils

This patch moves the HTML processing (i. e. syntax highlighting for code
snippets) into the viewer::utils method.  This allows us to use it for
other viewers too.
This commit is contained in:
Robin Krahl 2020-10-04 19:52:23 +02:00
parent ecf810929a
commit f92fc3e9f6
No known key found for this signature in database
GPG Key ID: 8E9B0870524F69D8
2 changed files with 124 additions and 55 deletions

View File

@ -9,9 +9,6 @@ use crate::args;
use crate::doc;
use crate::viewer::utils;
type RichString = text_renderer::TaggedString<Vec<text_renderer::RichAnnotation>>;
type RichLine = text_renderer::TaggedLine<Vec<text_renderer::RichAnnotation>>;
#[derive(Debug)]
pub struct RichTextRenderer {
line_length: usize,
@ -25,41 +22,6 @@ impl RichTextRenderer {
highlighter: utils::get_highlighter(&args)?,
})
}
fn prepare_html<'s>(&self, html: &'s [RichLine]) -> Vec<Vec<text_style::StyledStr<'s>>> {
let mut lines = Vec::new();
let mut highlight_lines = None;
for line in html {
let mut styled_strings = Vec::new();
for ts in line.iter().filter_map(|tle| match tle {
text_renderer::TaggedLineElement::Str(ts) => Some(ts),
_ => None,
}) {
if let Some(highlighter) = &self.highlighter {
if is_pre(ts) {
let h = highlight_lines
.get_or_insert_with(|| highlighter.get_highlight_lines("rs"));
let highlighted_strings = h.highlight(&ts.s, &highlighter.syntax_set);
styled_strings.extend(
highlighted_strings
.iter()
.map(text_style::StyledStr::from)
.map(reset_background),
);
} else {
highlight_lines = None;
styled_strings.push(style_rich_string(ts));
}
} else {
styled_strings.push(style_rich_string(ts));
}
}
lines.push(styled_strings);
}
lines
}
}
impl utils::ManRenderer for RichTextRenderer {
@ -79,9 +41,12 @@ impl utils::ManRenderer for RichTextRenderer {
indent
};
let lines = html2text::from_read_rich(s.html.as_bytes(), self.line_length - indent);
for line in self.prepare_html(&lines) {
for line in utils::highlight_html(&lines, self.highlighter.as_ref()) {
write!(io::stdout(), "{}", " ".repeat(indent))?;
render_iter(line)?;
render_iter(line.into_iter().map(|s| match s {
utils::HighlightedHtmlElement::RichString(s) => style_rich_string(s),
utils::HighlightedHtmlElement::StyledString(s) => utils::reset_background(s),
}))?;
writeln!(io::stdout())?;
}
Ok(())
@ -92,11 +57,10 @@ impl utils::ManRenderer for RichTextRenderer {
if let Some(highlighter) = &self.highlighter {
for line in highlighter.highlight(code.as_ref()) {
write!(io::stdout(), "{}", " ".repeat(indent))?;
// We remove the background as we want to use the terminal background
render_iter(
line.iter()
.map(text_style::StyledStr::from)
.map(reset_background),
.map(utils::reset_background),
)?;
}
writeln!(io::stdout())?;
@ -120,19 +84,7 @@ impl utils::ManRenderer for RichTextRenderer {
}
}
fn reset_background(mut s: text_style::StyledStr<'_>) -> text_style::StyledStr<'_> {
s.style_mut().bg = None;
s
}
fn is_pre(ts: &RichString) -> bool {
ts.tag.iter().any(|annotation| match annotation {
text_renderer::RichAnnotation::Preformat(_) => true,
_ => false,
})
}
fn style_rich_string(ts: &RichString) -> text_style::StyledStr<'_> {
fn style_rich_string(ts: &utils::RichString) -> text_style::StyledStr<'_> {
use text_renderer::RichAnnotation;
let mut s = text_style::StyledStr::plain(&ts.s);

View File

@ -4,10 +4,14 @@
use std::cmp;
use anyhow::Context as _;
use html2text::render::text_renderer;
use crate::args;
use crate::doc;
pub type RichString = text_renderer::TaggedString<Vec<text_renderer::RichAnnotation>>;
pub type RichLine = text_renderer::TaggedLine<Vec<text_renderer::RichAnnotation>>;
/// A helper struct for syntax highlighting using syntect.
#[derive(Clone, Debug)]
pub struct Highlighter {
@ -71,6 +75,114 @@ impl<'s, 'ss, 't, I: Iterator<Item = &'s str>> Iterator for HighlightedLines<'s,
}
}
pub enum HighlightedHtmlElement<'s> {
RichString(&'s RichString),
StyledString(text_style::StyledStr<'s>),
}
impl<'s> From<&'s RichString> for HighlightedHtmlElement<'s> {
fn from(s: &'s RichString) -> HighlightedHtmlElement<'s> {
HighlightedHtmlElement::RichString(s)
}
}
impl<'s> From<text_style::StyledStr<'s>> for HighlightedHtmlElement<'s> {
fn from(s: text_style::StyledStr<'s>) -> HighlightedHtmlElement<'s> {
HighlightedHtmlElement::StyledString(s)
}
}
pub struct HighlightedHtml<'h, 's, I: Iterator<Item = &'s RichLine>> {
iter: I,
highlighter: Option<&'h Highlighter>,
highlight_lines: Option<syntect::easy::HighlightLines<'h>>,
}
impl<'h, 's, I: Iterator<Item = &'s RichLine>> HighlightedHtml<'h, 's, I> {
fn new(iter: I, highlighter: Option<&'h Highlighter>) -> HighlightedHtml<'h, 's, I> {
HighlightedHtml {
iter,
highlighter,
highlight_lines: None,
}
}
fn get_highlighted_line(
&mut self,
highlighter: &'h Highlighter,
line: &'s RichLine,
) -> Vec<HighlightedHtmlElement<'s>> {
let mut elements = Vec::new();
for ts in line.iter().filter_map(|tle| match tle {
text_renderer::TaggedLineElement::Str(ts) => Some(ts),
_ => None,
}) {
if is_pre(ts) {
let h = self
.highlight_lines
.get_or_insert_with(|| highlighter.get_highlight_lines("rs"));
// TODO: syntect expects a newline
let strings = h.highlight(&ts.s, &highlighter.syntax_set);
elements.extend(
strings
.iter()
.map(text_style::StyledStr::from)
.map(HighlightedHtmlElement::from),
);
} else {
self.highlight_lines = None;
elements.push(ts.into());
}
}
elements
}
}
impl<'h, 's, I: Iterator<Item = &'s RichLine>> Iterator for HighlightedHtml<'h, 's, I> {
type Item = Vec<HighlightedHtmlElement<'s>>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(line) = self.iter.next() {
let elements = if let Some(highlighter) = self.highlighter {
self.get_highlighted_line(highlighter, line)
} else {
line.iter()
.filter_map(|tle| match tle {
text_renderer::TaggedLineElement::Str(ts) => Some(ts),
_ => None,
})
.map(From::from)
.collect()
};
Some(elements)
} else {
None
}
}
}
pub fn highlight_html<'h, 's, I, Iter>(
iter: I,
highlighter: Option<&'h Highlighter>,
) -> HighlightedHtml<'h, 's, Iter>
where
I: IntoIterator<Item = Iter::Item, IntoIter = Iter>,
Iter: Iterator<Item = &'s RichLine>,
{
HighlightedHtml::new(iter.into_iter(), highlighter)
}
fn is_pre(ts: &RichString) -> bool {
ts.tag.iter().any(|annotation| match annotation {
text_renderer::RichAnnotation::Preformat(_) => true,
_ => false,
})
}
/// A trait for viewer implementations that display the documentation in a man-like style.
pub trait ManRenderer {
type Error: std::error::Error + Sized + Send;
@ -191,6 +303,11 @@ pub fn get_highlighter(args: &args::ViewerArgs) -> anyhow::Result<Option<Highlig
}
}
pub fn reset_background(mut s: text_style::StyledStr<'_>) -> text_style::StyledStr<'_> {
s.style_mut().bg = None;
s
}
fn get_syntect_theme(args: &args::ViewerArgs) -> anyhow::Result<syntect::highlighting::Theme> {
let mut theme_set = syntect::highlighting::ThemeSet::load_defaults();
let theme_name = args.theme.as_deref().unwrap_or("base16-eighties.dark");