use super::event::EventRenderer;
use super::syntax_set::load_full_syntax_set;
use super::syntax_theme::{CodeHighlightTheme, build_syntect_theme, default_theme_set};
use crate::config::Config;
use crate::theme::{Theme, ThemeManager, apply_custom_code_theme, apply_custom_theme};
use crate::user_themes;
use anyhow::Result;
use pulldown_cmark::Event;
use syntect::highlighting::ThemeSet;
use syntect::parsing::SyntaxSet;
pub struct TerminalRenderer {
config: Config,
theme: Theme,
syntax_set: &'static SyntaxSet,
code_theme: CodeHighlightTheme,
}
impl TerminalRenderer {
pub fn new(config: &Config) -> Result<Self> {
let theme_manager = build_theme_manager(config);
let mut theme = theme_manager.get_theme(&config.theme)?.clone();
if let Some(overrides) = &config.custom_theme {
apply_custom_theme(&mut theme, overrides)?;
}
if let Some(overrides) = &config.custom_code_theme {
apply_custom_code_theme(&mut theme, overrides)?;
}
if (config.custom_theme.is_some() || config.custom_code_theme.is_some())
&& !theme.name.ends_with("+custom")
{
theme.name = format!("{}+custom", theme.name);
}
let syntax_set = load_full_syntax_set();
let theme_set = default_theme_set();
let code_theme = if config.custom_code_theme.is_some() {
if config.code_theme.is_some() {
log::info!(
"Ignoring '--code-theme' because '--custom-code-theme' overrides are applied."
);
}
build_syntect_theme(&theme)
} else {
match config.code_theme.as_ref() {
Some(requested_theme) => {
resolve_code_theme(requested_theme, &theme, &theme_manager, theme_set)
}
None => build_syntect_theme(&theme),
}
};
Ok(Self {
config: config.clone(),
theme,
syntax_set,
code_theme,
})
}
pub fn render(&self, events: Vec<Event<'static>>) -> Result<String> {
let mut renderer =
EventRenderer::new(&self.config, &self.theme, self.syntax_set, &self.code_theme);
renderer.render_events(events)
}
pub fn to_html(&self, events: Vec<Event<'static>>) -> Result<String> {
let events = events.into_iter().map(|event| match event {
Event::Html(html) if html.as_ref().trim() == crate::markdown::BLANK_LINE_MARKER => {
Event::HardBreak
}
Event::InlineHtml(html)
if html.as_ref().trim() == crate::markdown::BLANK_LINE_MARKER =>
{
Event::HardBreak
}
other => other,
});
let mut html_output = String::new();
pulldown_cmark::html::push_html(&mut html_output, events);
Ok(html_output)
}
}
fn resolve_code_theme(
requested_theme: &str,
main_theme: &Theme,
theme_manager: &ThemeManager,
theme_set: &ThemeSet,
) -> CodeHighlightTheme {
if let Some(theme) = theme_set.themes.get(requested_theme) {
return CodeHighlightTheme::syntect_only(theme.clone());
}
if let Some((actual_name, theme)) = theme_set
.themes
.iter()
.find(|(name, _)| name.eq_ignore_ascii_case(requested_theme))
{
if actual_name.as_str() != requested_theme {
log::info!(
"Using syntax theme '{}' for '--code-theme {}'.",
actual_name,
requested_theme
);
}
return CodeHighlightTheme::syntect_only(theme.clone());
}
if let Some(builtin_theme) = find_builtin_theme(theme_manager, requested_theme) {
if !builtin_theme.name.eq_ignore_ascii_case(requested_theme) {
log::info!(
"Using built-in theme '{}' for '--code-theme {}'.",
builtin_theme.name,
requested_theme
);
}
return build_syntect_theme(builtin_theme);
}
log::warn!(
"Code theme '{}' not found; falling back to '{}'.",
requested_theme,
main_theme.name
);
build_syntect_theme(main_theme)
}
fn find_builtin_theme<'a>(
theme_manager: &'a ThemeManager,
requested_theme: &str,
) -> Option<&'a Theme> {
if let Ok(theme) = theme_manager.get_theme(requested_theme) {
return Some(theme);
}
let requested_lower = requested_theme.to_ascii_lowercase();
theme_manager
.list_themes()
.into_iter()
.find(|name| name.to_ascii_lowercase() == requested_lower)
.and_then(|matched_name| theme_manager.get_theme(matched_name).ok())
}
pub(crate) fn build_theme_manager(config: &Config) -> ThemeManager {
let mut manager = ThemeManager::new();
if let Some(config_dir) = &config.config_dir {
match user_themes::load_user_themes(config_dir, &manager) {
Ok(themes) => {
for theme in themes {
manager.add_theme(theme);
}
}
Err(err) => {
log::warn!(
"Failed to load user themes from '{}': {}",
config_dir.display(),
err
);
}
}
}
manager
}