use std::io::Cursor;
use std::sync::LazyLock;
use syntect::highlighting::{Theme, ThemeSet};
use super::{Appearance, Palette};
pub const SYNTECT_DEFAULT_KEYS: &[&str] = &[
"InspiredGitHub",
"Solarized (dark)",
"Solarized (light)",
"base16-eighties.dark",
"base16-mocha.dark",
"base16-ocean.dark",
"base16-ocean.light",
];
pub const SYNTECT_EXTRA_KEYS: &[&str] = &[
"shadow-index",
"archival-simulacra",
"oblivion-ink",
"garden-unseen",
"burning-glyph",
"golden-delirium",
"tangerine-memory",
"purple-haze",
"frozen-phrase",
"babel-blend",
"resin-record",
"silent-sheet",
"obdurate-noon",
"faded-echo",
"parched-page",
"pale-mirror",
"ochre-thread",
"cryptic-chai",
"asterion-code",
"infinite-rose",
"barley-bound",
"verglas-trace",
];
#[derive(Clone, Copy, Debug)]
pub struct CodeThemeKeys {
pub dark: &'static str,
pub light: &'static str,
pub fallback: &'static str,
}
pub const SYNTECT_THEME_KEYS: CodeThemeKeys = CodeThemeKeys {
dark: "oblivion-ink",
light: "silent-sheet",
fallback: "oblivion-ink",
};
static THEME_SET: LazyLock<ThemeSet> = LazyLock::new(load_theme_set);
fn load_theme_set() -> ThemeSet {
let mut set = ThemeSet::load_defaults();
for (key, bytes) in EMBEDDED_THEMES {
let mut reader = Cursor::new(bytes);
match ThemeSet::load_from_reader(&mut reader) {
Ok(theme) => {
set.themes.insert((*key).to_owned(), theme);
}
Err(e) => {
eprintln!("ublx: failed to load embedded syntect theme {key:?}: {e}");
}
}
}
set
}
const EMBEDDED_THEMES: &[(&str, &[u8])] = &[
(
"shadow-index",
include_bytes!("../../assets/syntect-themes/shadow-index.tmTheme"),
),
(
"archival-simulacra",
include_bytes!("../../assets/syntect-themes/archival-simulacra.tmTheme"),
),
(
"oblivion-ink",
include_bytes!("../../assets/syntect-themes/oblivion-ink.tmTheme"),
),
(
"garden-unseen",
include_bytes!("../../assets/syntect-themes/garden-unseen.tmTheme"),
),
(
"burning-glyph",
include_bytes!("../../assets/syntect-themes/burning-glyph.tmTheme"),
),
(
"golden-delirium",
include_bytes!("../../assets/syntect-themes/golden-delirium.tmTheme"),
),
(
"tangerine-memory",
include_bytes!("../../assets/syntect-themes/tangerine-memory.tmTheme"),
),
(
"purple-haze",
include_bytes!("../../assets/syntect-themes/purple-haze.tmTheme"),
),
(
"frozen-phrase",
include_bytes!("../../assets/syntect-themes/frozen-phrase.tmTheme"),
),
(
"babel-blend",
include_bytes!("../../assets/syntect-themes/babel-blend.tmTheme"),
),
(
"resin-record",
include_bytes!("../../assets/syntect-themes/resin-record.tmTheme"),
),
(
"silent-sheet",
include_bytes!("../../assets/syntect-themes/silent-sheet.tmTheme"),
),
(
"obdurate-noon",
include_bytes!("../../assets/syntect-themes/obdurate-noon.tmTheme"),
),
(
"faded-echo",
include_bytes!("../../assets/syntect-themes/faded-echo.tmTheme"),
),
(
"parched-page",
include_bytes!("../../assets/syntect-themes/parched-page.tmTheme"),
),
(
"pale-mirror",
include_bytes!("../../assets/syntect-themes/pale-mirror.tmTheme"),
),
(
"ochre-thread",
include_bytes!("../../assets/syntect-themes/ochre-thread.tmTheme"),
),
(
"cryptic-chai",
include_bytes!("../../assets/syntect-themes/cryptic-chai.tmTheme"),
),
(
"asterion-code",
include_bytes!("../../assets/syntect-themes/asterion-code.tmTheme"),
),
(
"infinite-rose",
include_bytes!("../../assets/syntect-themes/infinite-rose.tmTheme"),
),
(
"barley-bound",
include_bytes!("../../assets/syntect-themes/barley-bound.tmTheme"),
),
(
"verglas-trace",
include_bytes!("../../assets/syntect-themes/verglas-trace.tmTheme"),
),
];
fn appearance_fallback_key(appearance: Appearance) -> &'static str {
let k = &SYNTECT_THEME_KEYS;
match appearance {
Appearance::Dark => k.dark,
Appearance::Light => k.light,
}
}
#[must_use]
pub fn resolve_syntect_key(key: &str, appearance: Appearance) -> &'static str {
if THEME_SET.themes.contains_key(key) {
SYNTECT_EXTRA_KEYS
.iter()
.chain(SYNTECT_DEFAULT_KEYS.iter())
.copied()
.find(|&k| k == key)
.unwrap_or_else(|| appearance_fallback_key(appearance))
} else {
appearance_fallback_key(appearance)
}
}
#[must_use]
pub fn theme_for_palette(palette: &Palette) -> &'static Theme {
theme_for_key(palette.syntect, palette.appearance)
}
#[must_use]
pub fn theme_for_key(key: &str, appearance: Appearance) -> &'static Theme {
if let Some(t) = THEME_SET.themes.get(key) {
return t;
}
let resolved = appearance_fallback_key(appearance);
THEME_SET.themes.get(resolved).unwrap_or_else(|| {
THEME_SET
.themes
.get(SYNTECT_THEME_KEYS.fallback)
.expect("syntect fallback theme")
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::themes::palettes::theme_ordered_list;
#[test]
fn embedded_extra_themes_load() {
for key in SYNTECT_EXTRA_KEYS {
assert!(
THEME_SET.themes.contains_key(*key),
"missing embedded syntect theme {key}"
);
}
}
#[test]
fn every_palette_syntect_key_resolves() {
for p in theme_ordered_list() {
assert!(
THEME_SET.themes.contains_key(p.syntect),
"palette {} references unknown syntect key {}",
p.name,
p.syntect
);
let _ = theme_for_palette(p);
}
}
}