mirror of
https://github.com/mfontanini/presenterm.git
synced 2025-05-05 15:32:58 +00:00
Pull in all of bat's syntect themes
This commit is contained in:
parent
45b2043a43
commit
9d17aba0df
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -903,6 +903,7 @@ dependencies = [
|
||||
"clap",
|
||||
"comrak",
|
||||
"crossterm",
|
||||
"flate2",
|
||||
"hex",
|
||||
"image",
|
||||
"itertools",
|
||||
|
@ -13,6 +13,7 @@ clap = { version = "4.4", features = ["derive", "string"] }
|
||||
comrak = { version = "0.19", default-features = false }
|
||||
crossterm = { version = "0.27", features = ["serde"] }
|
||||
hex = "0.4"
|
||||
flate2 = "1.0"
|
||||
image = "0.24"
|
||||
merge-struct = "0.1.0"
|
||||
itertools = "0.11"
|
||||
|
BIN
bat/themes.bin
Normal file
BIN
bat/themes.bin
Normal file
Binary file not shown.
@ -19,6 +19,7 @@ cd $clone_path
|
||||
git reset --hard $git_hash
|
||||
|
||||
cp assets/syntaxes.bin "$script_dir"
|
||||
cp assets/themes.bin "$script_dir"
|
||||
|
||||
acknowledgements_file="$script_dir/acknowledgements.txt"
|
||||
cp LICENSE-MIT "$acknowledgements_file"
|
||||
|
@ -490,7 +490,7 @@ impl<'a> PresentationBuilder<'a> {
|
||||
let mut code_highlighter = self.highlighter.language_highlighter(&code.language);
|
||||
let padding_style = {
|
||||
let mut highlighter = self.highlighter.language_highlighter(&CodeLanguage::Rust);
|
||||
highlighter.style_line("//").first().expect("no styles").style
|
||||
highlighter.style_line("//").next().expect("no styles").style
|
||||
};
|
||||
let groups = match self.options.allow_mutations {
|
||||
true => code.attributes.highlight_groups.clone(),
|
||||
@ -669,8 +669,6 @@ impl CodeLine {
|
||||
fn highlight(&self, padding_style: &Style, code_highlighter: &mut LanguageHighlighter) -> String {
|
||||
let mut output = StyledTokens { style: *padding_style, tokens: &self.prefix }.apply_style();
|
||||
output.push_str(&code_highlighter.highlight_line(&self.code));
|
||||
// Remove newline
|
||||
output.pop();
|
||||
output.push_str(&StyledTokens { style: *padding_style, tokens: &self.suffix }.apply_style());
|
||||
output
|
||||
}
|
||||
@ -1147,7 +1145,7 @@ mod test {
|
||||
}
|
||||
|
||||
fn try_build_presentation(elements: Vec<MarkdownElement>) -> Result<Presentation, BuildError> {
|
||||
let highlighter = CodeHighlighter::new("base16-ocean.dark").unwrap();
|
||||
let highlighter = CodeHighlighter::default();
|
||||
let theme = PresentationTheme::default();
|
||||
let mut resources = Resources::new("/tmp");
|
||||
let options = PresentationBuilderOptions::default();
|
||||
|
@ -172,7 +172,7 @@ mod test {
|
||||
let arena = Arena::new();
|
||||
let parser = MarkdownParser::new(&arena);
|
||||
let theme = Default::default();
|
||||
let highlighter = CodeHighlighter::new("base16-ocean.dark").unwrap();
|
||||
let highlighter = CodeHighlighter::default();
|
||||
let resources = Resources::new("examples");
|
||||
let mut exporter = Exporter::new(parser, &theme, highlighter, resources);
|
||||
exporter.extract_metadata(content, Path::new(path)).expect("metadata extraction failed")
|
||||
|
@ -74,7 +74,7 @@ fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
|
||||
};
|
||||
let arena = Arena::new();
|
||||
let parser = MarkdownParser::new(&arena);
|
||||
let default_highlighter = CodeHighlighter::new("base16-ocean.dark")?;
|
||||
let default_highlighter = CodeHighlighter::default();
|
||||
if cli.acknowledgements {
|
||||
display_acknowledgements();
|
||||
return Ok(());
|
||||
|
@ -1,28 +1,76 @@
|
||||
use crate::markdown::elements::CodeLanguage;
|
||||
use crossterm::{
|
||||
style::{SetBackgroundColor, SetForegroundColor},
|
||||
QueueableCommand,
|
||||
};
|
||||
use flate2::read::ZlibDecoder;
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
io::{self, Write},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use syntect::{
|
||||
easy::HighlightLines,
|
||||
highlighting::{Style, Theme, ThemeSet},
|
||||
parsing::SyntaxSet,
|
||||
util::as_24_bit_terminal_escaped,
|
||||
};
|
||||
|
||||
static SYNTAX_SET: Lazy<SyntaxSet> = Lazy::new(|| {
|
||||
let contents = include_bytes!("../../bat/syntaxes.bin");
|
||||
bincode::deserialize(contents).expect("syntaxes are broken")
|
||||
});
|
||||
static THEMES: Lazy<ThemeSet> = Lazy::new(ThemeSet::load_defaults);
|
||||
|
||||
static THEMES: Lazy<LazyThemeSet> = Lazy::new(|| {
|
||||
let contents = include_bytes!("../../bat/themes.bin");
|
||||
let theme_set: LazyThemeSet = bincode::deserialize(contents).expect("syntaxes are broken");
|
||||
let default_themes = ThemeSet::load_defaults();
|
||||
theme_set.merge(default_themes);
|
||||
theme_set
|
||||
});
|
||||
|
||||
// This structure mimic's `bat`'s serialized theme set's.
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct LazyThemeSet {
|
||||
serialized_themes: BTreeMap<String, Vec<u8>>,
|
||||
#[serde(skip)]
|
||||
themes: Mutex<BTreeMap<String, Arc<Theme>>>,
|
||||
}
|
||||
|
||||
impl LazyThemeSet {
|
||||
fn merge(&self, themes: ThemeSet) {
|
||||
let mut all_themes = self.themes.lock().unwrap();
|
||||
for (name, theme) in themes.themes {
|
||||
if !self.serialized_themes.contains_key(&name) {
|
||||
all_themes.insert(name, theme.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, theme_name: &str) -> Option<Arc<Theme>> {
|
||||
let mut themes = self.themes.lock().unwrap();
|
||||
if let Some(theme) = themes.get(theme_name) {
|
||||
return Some(theme.clone());
|
||||
}
|
||||
let serialized = self.serialized_themes.get(theme_name)?;
|
||||
let decoded: Theme = bincode::deserialize_from(ZlibDecoder::new(serialized.as_slice())).ok()?;
|
||||
let decoded = Arc::new(decoded);
|
||||
themes.insert(theme_name.to_string(), decoded);
|
||||
themes.get(theme_name).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
/// A code highlighter.
|
||||
#[derive(Clone)]
|
||||
pub struct CodeHighlighter {
|
||||
theme: &'static Theme,
|
||||
theme: Arc<Theme>,
|
||||
}
|
||||
|
||||
impl CodeHighlighter {
|
||||
/// Construct a new highlighted using the given [syntect] theme name.
|
||||
pub fn new(theme: &str) -> Result<Self, ThemeNotFound> {
|
||||
let theme = THEMES.themes.get(theme).ok_or(ThemeNotFound)?;
|
||||
let theme = THEMES.get(theme).ok_or(ThemeNotFound)?;
|
||||
Ok(Self { theme })
|
||||
}
|
||||
|
||||
@ -30,7 +78,7 @@ impl CodeHighlighter {
|
||||
pub(crate) fn language_highlighter(&self, language: &CodeLanguage) -> LanguageHighlighter {
|
||||
let extension = Self::language_extension(language);
|
||||
let syntax = SYNTAX_SET.find_syntax_by_extension(extension).unwrap();
|
||||
let highlighter = HighlightLines::new(syntax, self.theme);
|
||||
let highlighter = HighlightLines::new(syntax, &self.theme);
|
||||
LanguageHighlighter { highlighter }
|
||||
}
|
||||
|
||||
@ -90,23 +138,28 @@ impl CodeHighlighter {
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) struct LanguageHighlighter {
|
||||
highlighter: HighlightLines<'static>,
|
||||
|
||||
impl Default for CodeHighlighter {
|
||||
fn default() -> Self {
|
||||
Self::new("base16-eighties.dark").expect("default theme not found")
|
||||
}
|
||||
}
|
||||
|
||||
impl LanguageHighlighter {
|
||||
pub(crate) struct LanguageHighlighter<'a> {
|
||||
highlighter: HighlightLines<'a>,
|
||||
}
|
||||
|
||||
impl<'a> LanguageHighlighter<'a> {
|
||||
pub(crate) fn highlight_line(&mut self, line: &str) -> String {
|
||||
let ranges = self.highlighter.highlight_line(line, &SYNTAX_SET).unwrap();
|
||||
as_24_bit_terminal_escaped(&ranges, true)
|
||||
self.style_line(line).map(|s| s.apply_style()).collect()
|
||||
}
|
||||
|
||||
pub(crate) fn style_line<'a>(&mut self, line: &'a str) -> Vec<StyledTokens<'a>> {
|
||||
pub(crate) fn style_line<'b>(&mut self, line: &'b str) -> impl Iterator<Item = StyledTokens<'b>> {
|
||||
self.highlighter
|
||||
.highlight_line(line, &SYNTAX_SET)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|(style, tokens)| StyledTokens { style, tokens })
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,7 +170,29 @@ pub(crate) struct StyledTokens<'a> {
|
||||
|
||||
impl<'a> StyledTokens<'a> {
|
||||
pub(crate) fn apply_style(&self) -> String {
|
||||
as_24_bit_terminal_escaped(&[(self.style, self.tokens)], true)
|
||||
let background = to_ansi_color(self.style.background);
|
||||
let foreground = to_ansi_color(self.style.foreground);
|
||||
|
||||
// We do this conversion manually as crossterm will reset the color after styling, and we
|
||||
// want to "keep it open" so that padding also uses this background color.
|
||||
//
|
||||
// Note: these unwraps shouldn't happen as this is an in-memory writer so there's no
|
||||
// fallible IO here.
|
||||
let mut cursor = io::BufWriter::new(Vec::new());
|
||||
if let Some(color) = background {
|
||||
cursor.queue(SetBackgroundColor(color)).unwrap();
|
||||
}
|
||||
if let Some(color) = foreground {
|
||||
cursor.queue(SetForegroundColor(color)).unwrap();
|
||||
}
|
||||
// syntect likes its input to contain \n but we don't want them as we pad text with extra
|
||||
// " " at the end so we get rid of them here.
|
||||
for chunk in self.tokens.split('\n') {
|
||||
cursor.write_all(chunk.as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
cursor.flush().unwrap();
|
||||
String::from_utf8(cursor.into_inner().unwrap()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,6 +201,28 @@ impl<'a> StyledTokens<'a> {
|
||||
#[error("theme not found")]
|
||||
pub struct ThemeNotFound;
|
||||
|
||||
// This code has been adapted from bat's: https://github.com/sharkdp/bat
|
||||
fn to_ansi_color(color: syntect::highlighting::Color) -> Option<crossterm::style::Color> {
|
||||
use crossterm::style::Color;
|
||||
if color.a == 0 {
|
||||
Some(match color.r {
|
||||
0x00 => Color::Black,
|
||||
0x01 => Color::DarkRed,
|
||||
0x02 => Color::DarkGreen,
|
||||
0x03 => Color::DarkYellow,
|
||||
0x04 => Color::DarkBlue,
|
||||
0x05 => Color::DarkMagenta,
|
||||
0x06 => Color::DarkCyan,
|
||||
0x07 => Color::Grey,
|
||||
n => Color::AnsiValue(n),
|
||||
})
|
||||
} else if color.a == 1 {
|
||||
None
|
||||
} else {
|
||||
Some(Color::Rgb { r: color.r, g: color.g, b: color.b })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
@ -139,4 +236,9 @@ mod test {
|
||||
assert!(syntax.is_some(), "extension {extension} for {language:?} not found");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_highlighter() {
|
||||
CodeHighlighter::default();
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ code:
|
||||
minimum_size: 50
|
||||
minimum_margin:
|
||||
percent: 8
|
||||
theme_name: InspiredGitHub
|
||||
theme_name: GitHub
|
||||
padding:
|
||||
horizontal: 2
|
||||
vertical: 1
|
||||
|
Loading…
x
Reference in New Issue
Block a user