use crate::config::colors::{AnsiColor, NamedColor};
use bitflags::bitflags;
use rustc_hash::FxHashMap;
pub type StyleId = u16;
pub const DEFAULT_STYLE_ID: StyleId = 0;
bitflags! {
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct StyleFlags: u16 {
const INVERSE = 1 << 0;
const BOLD = 1 << 1;
const ITALIC = 1 << 2;
const DIM = 1 << 3;
const HIDDEN = 1 << 4;
const STRIKEOUT = 1 << 5;
const UNDERLINE = 1 << 6;
const DOUBLE_UNDERLINE = 1 << 7;
const UNDERCURL = 1 << 8;
const DOTTED_UNDERLINE = 1 << 9;
const DASHED_UNDERLINE = 1 << 10;
const ALL_UNDERLINES = Self::UNDERLINE.bits()
| Self::DOUBLE_UNDERLINE.bits()
| Self::UNDERCURL.bits()
| Self::DOTTED_UNDERLINE.bits()
| Self::DASHED_UNDERLINE.bits();
const DIM_BOLD = Self::DIM.bits() | Self::BOLD.bits();
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Style {
pub fg: AnsiColor,
pub bg: AnsiColor,
pub underline_color: Option<AnsiColor>,
pub flags: StyleFlags,
}
impl Default for Style {
#[inline]
fn default() -> Self {
Self {
fg: AnsiColor::Named(NamedColor::Foreground),
bg: AnsiColor::Named(NamedColor::Background),
underline_color: None,
flags: StyleFlags::empty(),
}
}
}
#[derive(Clone, Debug)]
pub struct StyleSet {
styles: Vec<Style>,
lookup: FxHashMap<Style, StyleId>,
}
impl PartialEq for StyleSet {
fn eq(&self, other: &Self) -> bool {
self.styles == other.styles
}
}
impl StyleSet {
pub fn new() -> Self {
let default_style = Style::default();
let mut lookup = FxHashMap::default();
lookup.insert(default_style, DEFAULT_STYLE_ID);
Self {
styles: vec![default_style],
lookup,
}
}
#[inline(always)]
pub fn get(&self, id: StyleId) -> Style {
if id == DEFAULT_STYLE_ID {
return Style::default();
}
self.styles
.get(id as usize)
.copied()
.unwrap_or_else(Style::default)
}
#[inline(always)]
pub unsafe fn get_unchecked(&self, id: StyleId) -> Style {
debug_assert!(
(id as usize) < self.styles.len(),
"StyleSet::get_unchecked called with out-of-range id {} (len {})",
id,
self.styles.len(),
);
*self.styles.get_unchecked(id as usize)
}
pub fn intern(&mut self, style: Style) -> StyleId {
if let Some(&id) = self.lookup.get(&style) {
return id;
}
if self.styles.len() >= u16::MAX as usize {
tracing::warn!(
"StyleSet hit u16::MAX styles ({}); falling back to default",
self.styles.len()
);
return DEFAULT_STYLE_ID;
}
let id = self.styles.len() as StyleId;
self.styles.push(style);
self.lookup.insert(style, id);
id
}
#[inline]
pub fn len(&self) -> usize {
self.styles.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.styles.is_empty()
}
}
impl Default for StyleSet {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_style_at_id_zero() {
let set = StyleSet::new();
assert_eq!(set.get(DEFAULT_STYLE_ID), Style::default());
assert_eq!(set.len(), 1);
}
#[test]
fn intern_returns_existing_id() {
let mut set = StyleSet::new();
let s = Style {
fg: AnsiColor::Named(NamedColor::Red),
..Style::default()
};
let id1 = set.intern(s);
let id2 = set.intern(s);
assert_eq!(id1, id2);
assert_ne!(id1, DEFAULT_STYLE_ID);
assert_eq!(set.len(), 2);
}
#[test]
fn distinct_styles_get_distinct_ids() {
let mut set = StyleSet::new();
let red = Style {
fg: AnsiColor::Named(NamedColor::Red),
..Style::default()
};
let blue = Style {
fg: AnsiColor::Named(NamedColor::Blue),
..Style::default()
};
assert_ne!(set.intern(red), set.intern(blue));
assert_eq!(set.len(), 3);
}
}