use hikari_core::{HlClass, Rgb as HlRgb, Theme};
use crate::chrome::ChromePalette;
#[derive(Debug, Clone, Copy)]
pub struct ChromeSyntax {
chrome: ChromePalette,
}
impl ChromeSyntax {
#[must_use]
pub const fn new(chrome: ChromePalette) -> Self {
Self { chrome }
}
#[must_use]
pub fn for_theme(theme: crate::chrome::FleetTheme) -> Self {
Self::new(ChromePalette::for_theme(theme))
}
#[must_use]
pub const fn chrome(&self) -> &ChromePalette {
&self.chrome
}
}
impl Theme for ChromeSyntax {
fn color(&self, class: HlClass) -> HlRgb {
let c = &self.chrome;
let rgb = match class {
HlClass::Comment { .. } => c.text_dim,
HlClass::Keyword | HlClass::Operator | HlClass::Info => c.link,
HlClass::KeywordArg | HlClass::Attribute | HlClass::Numeric { .. } => c.agent,
HlClass::Type | HlClass::Namespace => c.info,
HlClass::Function => c.primary,
HlClass::Str | HlClass::Added => c.success,
HlClass::Escape | HlClass::Special | HlClass::Warning => c.search,
HlClass::Boolean | HlClass::Constant => c.warning,
HlClass::Punctuation => c.text_bright,
HlClass::Hyperlink | HlClass::Hint => c.structural,
HlClass::Error | HlClass::Removed => c.error,
HlClass::Variable | HlClass::Whitespace | HlClass::Unchanged | HlClass::Plain => {
c.text_muted
}
};
HlRgb::new(rgb.r, rgb.g, rgb.b)
}
}
pub const ALL_CLASSES: &[HlClass] = &[
HlClass::Comment { multiline: false },
HlClass::Comment { multiline: true },
HlClass::Keyword,
HlClass::KeywordArg,
HlClass::Type,
HlClass::Function,
HlClass::Namespace,
HlClass::Variable,
HlClass::Constant,
HlClass::Str,
HlClass::Escape,
HlClass::Numeric { float: false },
HlClass::Numeric { float: true },
HlClass::Boolean,
HlClass::Punctuation,
HlClass::Operator,
HlClass::Attribute,
HlClass::Special,
HlClass::Hyperlink,
HlClass::Whitespace,
HlClass::Error,
HlClass::Warning,
HlClass::Info,
HlClass::Hint,
HlClass::Added,
HlClass::Removed,
HlClass::Unchanged,
HlClass::Plain,
];
#[cfg(test)]
mod tests {
use super::*;
use crate::chrome::FleetTheme;
fn hex(c: HlRgb) -> String {
let mut s = String::with_capacity(7);
s.push('#');
for b in [c.r, c.g, c.b] {
s.push(char::from_digit(u32::from(b >> 4), 16).unwrap_or('0'));
s.push(char::from_digit(u32::from(b & 0xF), 16).unwrap_or('0'));
}
s.to_uppercase()
}
#[test]
fn themes_reproduce_nord_on_the_fleet_default() {
let ours = ChromeSyntax::for_theme(FleetTheme::prescribed_default());
let nord = hikari_core::NordTheme;
let mut diffs = Vec::new();
for class in ALL_CLASSES {
let a = hex(ours.color(*class));
let b = hex(nord.color(*class));
if a != b {
diffs.push((format!("{class:?}"), a, b));
}
}
for (class, ours, nord) in &diffs {
assert!(
class.starts_with("Comment"),
"{class} drifted off Nord: {ours} (ours) != {nord} (NordTheme)",
);
}
assert_eq!(
diffs.len(),
2,
"expected exactly the two Comment variants to differ, got {diffs:?}",
);
}
#[test]
fn a_different_theme_paints_code_differently() {
let nordish = ChromeSyntax::for_theme(FleetTheme::PlemeDark);
let vellum = ChromeSyntax::for_theme(FleetTheme::Vellum);
let differing = ALL_CLASSES
.iter()
.filter(|c| hex(nordish.color(**c)) != hex(vellum.color(**c)))
.count();
assert!(
differing > ALL_CLASSES.len() / 2,
"picking Vellum must recolour the CODE, not just the frame: only \
{differing}/{} classes moved",
ALL_CLASSES.len(),
);
}
#[test]
fn no_class_collapses_onto_the_background_in_any_theme() {
for theme in [
FleetTheme::PlemeDark,
FleetTheme::Vellum,
FleetTheme::PolarVeil,
FleetTheme::Bare,
] {
let syn = ChromeSyntax::for_theme(theme);
let bg = syn.chrome().background.hex();
for class in ALL_CLASSES {
assert_ne!(
hex(syn.color(*class)),
bg,
"{theme:?}: {class:?} is the same colour as the ground",
);
}
}
}
#[test]
fn the_load_bearing_classes_stay_distinguishable_in_every_theme() {
let key = [
HlClass::Comment { multiline: false },
HlClass::Keyword,
HlClass::Str,
HlClass::Function,
HlClass::Numeric { float: false },
];
for theme in [
FleetTheme::PlemeDark,
FleetTheme::Vellum,
FleetTheme::PolarVeil,
FleetTheme::Bare,
] {
let syn = ChromeSyntax::for_theme(theme);
let mut seen = std::collections::BTreeSet::new();
for class in key {
assert!(
seen.insert(hex(syn.color(class))),
"{theme:?}: {class:?} duplicates another load-bearing class",
);
}
}
}
#[test]
fn every_class_is_in_the_roster() {
let mut seen = std::collections::BTreeSet::new();
for c in ALL_CLASSES {
assert!(seen.insert(format!("{c:?}")), "{c:?} listed twice");
}
assert_eq!(seen.len(), ALL_CLASSES.len());
}
}