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",
|
"clap",
|
||||||
"comrak",
|
"comrak",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
"flate2",
|
||||||
"hex",
|
"hex",
|
||||||
"image",
|
"image",
|
||||||
"itertools",
|
"itertools",
|
||||||
|
@ -13,6 +13,7 @@ clap = { version = "4.4", features = ["derive", "string"] }
|
|||||||
comrak = { version = "0.19", default-features = false }
|
comrak = { version = "0.19", default-features = false }
|
||||||
crossterm = { version = "0.27", features = ["serde"] }
|
crossterm = { version = "0.27", features = ["serde"] }
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
flate2 = "1.0"
|
||||||
image = "0.24"
|
image = "0.24"
|
||||||
merge-struct = "0.1.0"
|
merge-struct = "0.1.0"
|
||||||
itertools = "0.11"
|
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
|
git reset --hard $git_hash
|
||||||
|
|
||||||
cp assets/syntaxes.bin "$script_dir"
|
cp assets/syntaxes.bin "$script_dir"
|
||||||
|
cp assets/themes.bin "$script_dir"
|
||||||
|
|
||||||
acknowledgements_file="$script_dir/acknowledgements.txt"
|
acknowledgements_file="$script_dir/acknowledgements.txt"
|
||||||
cp LICENSE-MIT "$acknowledgements_file"
|
cp LICENSE-MIT "$acknowledgements_file"
|
||||||
|
@ -490,7 +490,7 @@ impl<'a> PresentationBuilder<'a> {
|
|||||||
let mut code_highlighter = self.highlighter.language_highlighter(&code.language);
|
let mut code_highlighter = self.highlighter.language_highlighter(&code.language);
|
||||||
let padding_style = {
|
let padding_style = {
|
||||||
let mut highlighter = self.highlighter.language_highlighter(&CodeLanguage::Rust);
|
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 {
|
let groups = match self.options.allow_mutations {
|
||||||
true => code.attributes.highlight_groups.clone(),
|
true => code.attributes.highlight_groups.clone(),
|
||||||
@ -669,8 +669,6 @@ impl CodeLine {
|
|||||||
fn highlight(&self, padding_style: &Style, code_highlighter: &mut LanguageHighlighter) -> String {
|
fn highlight(&self, padding_style: &Style, code_highlighter: &mut LanguageHighlighter) -> String {
|
||||||
let mut output = StyledTokens { style: *padding_style, tokens: &self.prefix }.apply_style();
|
let mut output = StyledTokens { style: *padding_style, tokens: &self.prefix }.apply_style();
|
||||||
output.push_str(&code_highlighter.highlight_line(&self.code));
|
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.push_str(&StyledTokens { style: *padding_style, tokens: &self.suffix }.apply_style());
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
@ -1147,7 +1145,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn try_build_presentation(elements: Vec<MarkdownElement>) -> Result<Presentation, BuildError> {
|
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 theme = PresentationTheme::default();
|
||||||
let mut resources = Resources::new("/tmp");
|
let mut resources = Resources::new("/tmp");
|
||||||
let options = PresentationBuilderOptions::default();
|
let options = PresentationBuilderOptions::default();
|
||||||
|
@ -172,7 +172,7 @@ mod test {
|
|||||||
let arena = Arena::new();
|
let arena = Arena::new();
|
||||||
let parser = MarkdownParser::new(&arena);
|
let parser = MarkdownParser::new(&arena);
|
||||||
let theme = Default::default();
|
let theme = Default::default();
|
||||||
let highlighter = CodeHighlighter::new("base16-ocean.dark").unwrap();
|
let highlighter = CodeHighlighter::default();
|
||||||
let resources = Resources::new("examples");
|
let resources = Resources::new("examples");
|
||||||
let mut exporter = Exporter::new(parser, &theme, highlighter, resources);
|
let mut exporter = Exporter::new(parser, &theme, highlighter, resources);
|
||||||
exporter.extract_metadata(content, Path::new(path)).expect("metadata extraction failed")
|
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 arena = Arena::new();
|
||||||
let parser = MarkdownParser::new(&arena);
|
let parser = MarkdownParser::new(&arena);
|
||||||
let default_highlighter = CodeHighlighter::new("base16-ocean.dark")?;
|
let default_highlighter = CodeHighlighter::default();
|
||||||
if cli.acknowledgements {
|
if cli.acknowledgements {
|
||||||
display_acknowledgements();
|
display_acknowledgements();
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@ -1,28 +1,76 @@
|
|||||||
use crate::markdown::elements::CodeLanguage;
|
use crate::markdown::elements::CodeLanguage;
|
||||||
|
use crossterm::{
|
||||||
|
style::{SetBackgroundColor, SetForegroundColor},
|
||||||
|
QueueableCommand,
|
||||||
|
};
|
||||||
|
use flate2::read::ZlibDecoder;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
io::{self, Write},
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
use syntect::{
|
use syntect::{
|
||||||
easy::HighlightLines,
|
easy::HighlightLines,
|
||||||
highlighting::{Style, Theme, ThemeSet},
|
highlighting::{Style, Theme, ThemeSet},
|
||||||
parsing::SyntaxSet,
|
parsing::SyntaxSet,
|
||||||
util::as_24_bit_terminal_escaped,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static SYNTAX_SET: Lazy<SyntaxSet> = Lazy::new(|| {
|
static SYNTAX_SET: Lazy<SyntaxSet> = Lazy::new(|| {
|
||||||
let contents = include_bytes!("../../bat/syntaxes.bin");
|
let contents = include_bytes!("../../bat/syntaxes.bin");
|
||||||
bincode::deserialize(contents).expect("syntaxes are broken")
|
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.
|
/// A code highlighter.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CodeHighlighter {
|
pub struct CodeHighlighter {
|
||||||
theme: &'static Theme,
|
theme: Arc<Theme>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CodeHighlighter {
|
impl CodeHighlighter {
|
||||||
/// Construct a new highlighted using the given [syntect] theme name.
|
/// Construct a new highlighted using the given [syntect] theme name.
|
||||||
pub fn new(theme: &str) -> Result<Self, ThemeNotFound> {
|
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 })
|
Ok(Self { theme })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +78,7 @@ impl CodeHighlighter {
|
|||||||
pub(crate) fn language_highlighter(&self, language: &CodeLanguage) -> LanguageHighlighter {
|
pub(crate) fn language_highlighter(&self, language: &CodeLanguage) -> LanguageHighlighter {
|
||||||
let extension = Self::language_extension(language);
|
let extension = Self::language_extension(language);
|
||||||
let syntax = SYNTAX_SET.find_syntax_by_extension(extension).unwrap();
|
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 }
|
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 {
|
pub(crate) fn highlight_line(&mut self, line: &str) -> String {
|
||||||
let ranges = self.highlighter.highlight_line(line, &SYNTAX_SET).unwrap();
|
self.style_line(line).map(|s| s.apply_style()).collect()
|
||||||
as_24_bit_terminal_escaped(&ranges, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
self.highlighter
|
||||||
.highlight_line(line, &SYNTAX_SET)
|
.highlight_line(line, &SYNTAX_SET)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(style, tokens)| StyledTokens { style, tokens })
|
.map(|(style, tokens)| StyledTokens { style, tokens })
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +170,29 @@ pub(crate) struct StyledTokens<'a> {
|
|||||||
|
|
||||||
impl<'a> StyledTokens<'a> {
|
impl<'a> StyledTokens<'a> {
|
||||||
pub(crate) fn apply_style(&self) -> String {
|
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")]
|
#[error("theme not found")]
|
||||||
pub struct ThemeNotFound;
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -139,4 +236,9 @@ mod test {
|
|||||||
assert!(syntax.is_some(), "extension {extension} for {language:?} not found");
|
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_size: 50
|
||||||
minimum_margin:
|
minimum_margin:
|
||||||
percent: 8
|
percent: 8
|
||||||
theme_name: InspiredGitHub
|
theme_name: GitHub
|
||||||
padding:
|
padding:
|
||||||
horizontal: 2
|
horizontal: 2
|
||||||
vertical: 1
|
vertical: 1
|
||||||
|
Loading…
x
Reference in New Issue
Block a user