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:
parent
ecf810929a
commit
f92fc3e9f6
@ -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);
|
||||
|
@ -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");
|
||||
|
Loading…
Reference in New Issue
Block a user