Refactor syntax highlighting into viewer::utils

This patch introduces the viewer::utils::Highlighter struct that takes
care of syntax highlighting.  This will make it easier to use syntax
highlighting from others viewers once we add them.
This commit is contained in:
Robin Krahl 2020-10-03 15:35:41 +02:00
parent 9458b280d4
commit 970277d32c
No known key found for this signature in database
GPG Key ID: 8E9B0870524F69D8
2 changed files with 77 additions and 14 deletions

View File

@ -14,18 +14,14 @@ type RichString = text_renderer::TaggedString<Vec<text_renderer::RichAnnotation>
#[derive(Debug)]
pub struct RichTextRenderer {
line_length: usize,
highlight: bool,
syntax_set: syntect::parsing::SyntaxSet,
theme: syntect::highlighting::Theme,
highlighter: Option<utils::Highlighter>,
}
impl RichTextRenderer {
pub fn new(args: args::ViewerArgs) -> anyhow::Result<Self> {
Ok(Self {
line_length: utils::get_line_length(&args),
highlight: !args.no_syntax_highlight,
syntax_set: syntect::parsing::SyntaxSet::load_defaults_newlines(),
theme: utils::get_syntect_theme(&args)?,
highlighter: utils::get_highlighter(&args)?,
})
}
}
@ -64,15 +60,11 @@ impl utils::ManRenderer for RichTextRenderer {
fn print_code(&mut self, indent: u8, code: &doc::Code) -> io::Result<()> {
let indent = usize::from(indent);
if self.highlight {
let syntax = self.syntax_set.find_syntax_by_extension("rs").unwrap();
let mut h = syntect::easy::HighlightLines::new(syntax, &self.theme);
for line in syntect::util::LinesWithEndings::from(code.as_ref()) {
let ranges = h.highlight(line, &self.syntax_set);
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(ranges.iter().map(text_style::StyledStr::from).map(|mut s| {
render_iter(line.iter().map(text_style::StyledStr::from).map(|mut s| {
s.style_mut().bg = None;
s
}))?;

View File

@ -8,6 +8,69 @@ use anyhow::Context as _;
use crate::args;
use crate::doc;
/// A helper struct for syntax highlighting using syntect.
#[derive(Clone, Debug)]
pub struct Highlighter {
pub syntax_set: syntect::parsing::SyntaxSet,
pub theme: syntect::highlighting::Theme,
}
impl Highlighter {
pub fn new(args: &args::ViewerArgs) -> anyhow::Result<Highlighter> {
Ok(Highlighter {
syntax_set: syntect::parsing::SyntaxSet::load_defaults_newlines(),
theme: get_syntect_theme(args)?,
})
}
pub fn highlight<'a, 's>(
&'a self,
s: &'s str,
) -> HighlightedLines<'s, 'a, 'a, syntect::util::LinesWithEndings<'s>> {
HighlightedLines::new(
syntect::util::LinesWithEndings::from(s),
self.get_highlight_lines("rs"),
&self.syntax_set,
)
}
pub fn get_highlight_lines(&self, syntax: &str) -> syntect::easy::HighlightLines<'_> {
let syntax = self.syntax_set.find_syntax_by_extension(syntax).unwrap();
syntect::easy::HighlightLines::new(syntax, &self.theme)
}
}
/// An iterator over lines highlighted using syntect.
pub struct HighlightedLines<'s, 'ss, 't, I: Iterator<Item = &'s str>> {
iter: I,
highlighter: syntect::easy::HighlightLines<'t>,
syntax_set: &'ss syntect::parsing::SyntaxSet,
}
impl<'s, 'ss, 't, I: Iterator<Item = &'s str>> HighlightedLines<'s, 'ss, 't, I> {
fn new(
iter: I,
highlighter: syntect::easy::HighlightLines<'t>,
syntax_set: &'ss syntect::parsing::SyntaxSet,
) -> HighlightedLines<'s, 'ss, 't, I> {
HighlightedLines {
iter,
highlighter,
syntax_set,
}
}
}
impl<'s, 'ss, 't, I: Iterator<Item = &'s str>> Iterator for HighlightedLines<'s, 'ss, 't, I> {
type Item = Vec<(syntect::highlighting::Style, &'s str)>;
fn next(&mut self) -> Option<Self::Item> {
self.iter
.next()
.map(|s| self.highlighter.highlight(s, &self.syntax_set))
}
}
/// A trait for viewer implementations that display the documentation in a man-like style.
pub trait ManRenderer {
type Error: std::error::Error + Sized + Send;
@ -120,7 +183,15 @@ pub fn get_line_length(args: &args::ViewerArgs) -> usize {
}
}
pub fn get_syntect_theme(args: &args::ViewerArgs) -> anyhow::Result<syntect::highlighting::Theme> {
pub fn get_highlighter(args: &args::ViewerArgs) -> anyhow::Result<Option<Highlighter>> {
if args.no_syntax_highlight {
Ok(None)
} else {
Highlighter::new(&args).map(Some)
}
}
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");
theme_set