Skip to content

Commit

Permalink
fix: Switch to bat's themes, syntaxes
Browse files Browse the repository at this point in the history
  • Loading branch information
epage committed Oct 13, 2022
1 parent dbb43da commit ac111ec
Show file tree
Hide file tree
Showing 11 changed files with 232 additions and 19 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ concolor-clap = { version = "0.0.12", features = ["api_unstable"] }
proc-exit = "2"
human-panic = "1"
anyhow = "1.0.65"
syntect = { version = "5.0.0", default-features = false, features = ["parsing", "regex-fancy", "default-syntaxes", "default-themes"] }
syntect = { version = "5.0.0", default-features = false, features = ["parsing", "regex-fancy"] }
terminal_size = "0.2.1"
textwrap = "0.15.1"
anstyle = "0.2.2"
Expand All @@ -49,3 +49,7 @@ git-config-env = "0.1.2"
shlex = "1.1.0"
atty = "0.2.14"
anstyle-syntect = "0.1.2"
bincode = "1.3.3"
serde = { version = "1.0.145", features = ["derive"] }
flate2 = "1.0.24"
once_cell = "1.15.0"
Binary file added assets/acknowledgements.bin
Binary file not shown.
Binary file added assets/syntaxes.bin
Binary file not shown.
Binary file added assets/themes.bin
Binary file not shown.
4 changes: 4 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ pub struct Args {
#[arg(long, group = "mode")]
pub list_themes: bool,

/// Display acknowledgements
#[arg(long, hide_short_help = true, group = "mode")]
pub acknowledgements: bool,

/// Run as if git was started in `PATH` instead of the current working directory.
///
/// When multiple -C options are given, each subsequent
Expand Down
11 changes: 0 additions & 11 deletions src/assets.rs

This file was deleted.

104 changes: 104 additions & 0 deletions src/assets/lazy_theme_set.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use super::*;

use std::collections::BTreeMap;
use std::convert::TryFrom;

use serde::Deserialize;
use serde::Serialize;

use once_cell::unsync::OnceCell;

use syntect::highlighting::{Theme, ThemeSet};

/// Same structure as a [`syntect::highlighting::ThemeSet`] but with themes
/// stored in raw serialized form, and deserialized on demand.
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct LazyThemeSet {
/// This is a [`BTreeMap`] because that's what [`syntect::highlighting::ThemeSet`] uses
themes: BTreeMap<String, LazyTheme>,
}

/// Stores raw serialized data for a theme with methods to lazily deserialize
/// (load) the theme.
#[derive(Debug, Serialize, Deserialize)]
struct LazyTheme {
serialized: Vec<u8>,

#[serde(skip, default = "OnceCell::new")]
deserialized: OnceCell<syntect::highlighting::Theme>,
}

impl LazyThemeSet {
/// Lazily load the given theme
pub fn get(&self, name: &str) -> Option<&Theme> {
self.themes.get(name).and_then(|lazy_theme| {
lazy_theme
.deserialized
.get_or_try_init(|| lazy_theme.deserialize())
.ok()
})
}

/// Returns the name of all themes.
pub fn themes(&self) -> impl Iterator<Item = &str> {
self.themes.keys().map(|name| name.as_ref())
}
}

impl LazyTheme {
fn deserialize(&self) -> Result<Theme> {
asset_from_contents(
&self.serialized[..],
"lazy-loaded theme",
COMPRESS_LAZY_THEMES,
)
}
}

impl TryFrom<LazyThemeSet> for ThemeSet {
type Error = Error;

/// Since the user might want to add custom themes to bat, we need a way to
/// convert from a `LazyThemeSet` to a regular [`ThemeSet`] so that more
/// themes can be added. This function does that pretty straight-forward
/// conversion.
fn try_from(lazy_theme_set: LazyThemeSet) -> Result<Self> {
let mut theme_set = ThemeSet::default();

for (name, lazy_theme) in lazy_theme_set.themes {
theme_set.themes.insert(name, lazy_theme.deserialize()?);
}

Ok(theme_set)
}
}

#[cfg(feature = "build-assets")]
impl TryFrom<ThemeSet> for LazyThemeSet {
type Error = Error;

/// To collect themes, a [`ThemeSet`] is needed. Once all desired themes
/// have been added, we need a way to convert that into [`LazyThemeSet`] so
/// that themes can be lazy-loaded later. This function does that
/// conversion.
fn try_from(theme_set: ThemeSet) -> Result<Self> {
let mut lazy_theme_set = LazyThemeSet::default();

for (name, theme) in theme_set.themes {
// All we have to do is to serialize the theme
let lazy_theme = LazyTheme {
serialized: crate::assets::build_assets::asset_to_contents(
&theme,
&format!("theme {}", name),
COMPRESS_LAZY_THEMES,
)?,
deserialized: OnceCell::new(),
};

// Ok done, now we can add it
lazy_theme_set.themes.insert(name, lazy_theme);
}

Ok(lazy_theme_set)
}
}
102 changes: 102 additions & 0 deletions src/assets/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
mod lazy_theme_set;

use anyhow::Error;
use anyhow::Result;

pub use lazy_theme_set::LazyThemeSet;

pub fn load_themes() -> LazyThemeSet {
get_integrated_themeset()
}

pub fn load_syntaxes() -> syntect::parsing::SyntaxSet {
from_binary(get_serialized_integrated_syntaxset(), COMPRESS_SYNTAXES)
}

pub fn to_anstyle_color(color: syntect::highlighting::Color) -> Option<anstyle::Color> {
if color.a == 0 {
// Themes can specify one of the user-configurable terminal colors by
// encoding them as #RRGGBBAA with AA set to 00 (transparent) and RR set
// to the 8-bit color palette number. The built-in themes ansi, base16,
// and base16-256 use this.
Some(match color.r {
// For the first 8 colors, use the Color enum to produce ANSI escape
// sequences using codes 30-37 (foreground) and 40-47 (background).
// For example, red foreground is \x1b[31m. This works on terminals
// without 256-color support.
0x00 => anstyle::AnsiColor::Black.into(),
0x01 => anstyle::AnsiColor::Red.into(),
0x02 => anstyle::AnsiColor::Green.into(),
0x03 => anstyle::AnsiColor::Yellow.into(),
0x04 => anstyle::AnsiColor::Blue.into(),
0x05 => anstyle::AnsiColor::Magenta.into(),
0x06 => anstyle::AnsiColor::Cyan.into(),
0x07 => anstyle::AnsiColor::White.into(),
// For all other colors, use Fixed to produce escape sequences using
// codes 38;5 (foreground) and 48;5 (background). For example,
// bright red foreground is \x1b[38;5;9m. This only works on
// terminals with 256-color support.
//
// TODO: When ansi_term adds support for bright variants using codes
// 90-97 (foreground) and 100-107 (background), we should use those
// for values 0x08 to 0x0f and only use Fixed for 0x10 to 0xff.
n => anstyle::XTermColor(n).into(),
})
} else if color.a == 1 {
// Themes can specify the terminal's default foreground/background color
// (i.e. no escape sequence) using the encoding #RRGGBBAA with AA set to
// 01. The built-in theme ansi uses this.
None
} else {
Some(anstyle::RgbColor(color.r, color.g, color.b).into())
}
}

/// Lazy-loaded syntaxes are already compressed, and we don't want to compress
/// already compressed data.
const COMPRESS_SYNTAXES: bool = false;

/// We don't want to compress our [LazyThemeSet] since the lazy-loaded themes
/// within it are already compressed, and compressing another time just makes
/// performance suffer
const COMPRESS_THEMES: bool = false;

/// Compress for size of ~40 kB instead of ~200 kB without much difference in
/// performance due to lazy-loading
const COMPRESS_LAZY_THEMES: bool = true;

/// Compress for size of ~10 kB instead of ~120 kB
const COMPRESS_ACKNOWLEDGEMENTS: bool = true;

fn get_serialized_integrated_syntaxset() -> &'static [u8] {
include_bytes!("../../assets/syntaxes.bin")
}

fn get_integrated_themeset() -> LazyThemeSet {
from_binary(include_bytes!("../../assets/themes.bin"), COMPRESS_THEMES)
}

pub fn get_acknowledgements() -> String {
from_binary(
include_bytes!("../../assets/acknowledgements.bin"),
COMPRESS_ACKNOWLEDGEMENTS,
)
}

fn from_binary<T: serde::de::DeserializeOwned>(v: &[u8], compressed: bool) -> T {
asset_from_contents(v, "n/a", compressed)
.expect("data integrated in binary is never faulty, but make sure `compressed` is in sync!")
}

fn asset_from_contents<T: serde::de::DeserializeOwned>(
contents: &[u8],
description: &str,
compressed: bool,
) -> Result<T, anyhow::Error> {
if compressed {
bincode::deserialize_from(flate2::read::ZlibDecoder::new(contents))
} else {
bincode::deserialize_from(contents)
}
.map_err(|_| anyhow::format_err!("Could not parse {}", description))
}
9 changes: 4 additions & 5 deletions src/blame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,8 @@ pub fn blame(
let syntax_set = crate::assets::load_syntaxes();
let theme_set = crate::assets::load_themes();
let theme = theme_set
.themes
.get(&theme)
.or_else(|| theme_set.themes.get(THEME_DEFAULT))
.or_else(|| theme_set.get(THEME_DEFAULT))
.expect("default theme is present");

let syntax = syntax_set
Expand Down Expand Up @@ -433,11 +432,11 @@ fn gutter_style(theme: &syntect::highlighting::Theme) -> anstyle::Style {
.settings
.gutter_foreground
.map(crate::assets::to_anstyle_color)
.unwrap_or_else(|| anstyle::XTermColor(DEFAULT_GUTTER_COLOR).into());
.unwrap_or_else(|| Some(anstyle::XTermColor(DEFAULT_GUTTER_COLOR).into()));

fg_color.into()
anstyle::Style::new().fg_color(fg_color)
}

const THEME_DEFAULT: &str = "base16-ocean.dark";
const THEME_DEFAULT: &str = "Monokai Extended";
pub const THEME: DefaultField<String> =
RawField::<String>::new("dive.theme").default_value(|| THEME_DEFAULT.to_owned());
8 changes: 6 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ fn run() -> proc_exit::ExitResult {
list_languages(&mut config, colored_stdout)?;
} else if args.list_themes {
list_themes(&mut config, colored_stdout)?;
} else if args.acknowledgements {
use std::io::Write;
let _ = writeln!(std::io::stdout(), "{}", assets::get_acknowledgements());
} else if let Some(file_path) = args.file.as_deref() {
blame::blame(
file_path,
Expand Down Expand Up @@ -137,7 +140,8 @@ fn list_themes(config: &mut Config, colored_stdout: bool) -> proc_exit::ExitResu
let syntax = syntax_set
.find_syntax_by_name("Rust")
.expect("always included");
for (name, theme) in theme_set.themes.iter() {
for name in theme_set.themes() {
let theme = theme_set.get(name).unwrap();
let mut highlighter = blame::Highlighter::enabled(syntax, theme);
let _ = writeln!(
pager,
Expand All @@ -158,7 +162,7 @@ fn list_themes(config: &mut Config, colored_stdout: bool) -> proc_exit::ExitResu
let _ = writeln!(pager);
}
} else {
for name in theme_set.themes.keys() {
for name in theme_set.themes() {
let _ = writeln!(pager, "{}", name);
}
}
Expand Down

0 comments on commit ac111ec

Please sign in to comment.