use crate::styled_string::SpanStyle;
use syntect::highlighting::{Color, Highlighter, Theme, ThemeSet};
use syntect::parsing::{Scope, ScopeStack};
#[derive(Debug, Clone)]
pub struct ColorScheme {
colors: std::collections::HashMap<SpanStyle, Color>,
default_foreground: Color,
default_background: Color,
}
impl ColorScheme {
pub fn from_syntect_theme(theme: &Theme) -> Self {
let highlighter = Highlighter::new(theme);
let default_style = highlighter.get_default();
let mut colors = std::collections::HashMap::new();
colors.insert(
SpanStyle::Keyword,
Self::color_for_scope(&highlighter, "keyword.control"),
);
colors.insert(
SpanStyle::TypeName,
Self::color_for_scope(&highlighter, "entity.name.type"),
);
colors.insert(
SpanStyle::FunctionName,
Self::color_for_scope(&highlighter, "entity.name.function"),
);
colors.insert(
SpanStyle::FieldName,
Self::color_for_scope(&highlighter, "variable.other.member"),
);
colors.insert(
SpanStyle::Lifetime,
Self::color_for_scope(&highlighter, "storage.modifier.lifetime"),
);
colors.insert(
SpanStyle::Generic,
Self::color_for_scope(&highlighter, "entity.name.type.parameter"),
);
colors.insert(
SpanStyle::Operator,
Self::color_for_scope(&highlighter, "keyword.operator"),
);
colors.insert(
SpanStyle::Comment,
Self::color_for_scope(&highlighter, "comment.line"),
);
Self {
colors,
default_foreground: default_style.foreground,
default_background: default_style.background,
}
}
pub fn from_theme_name(name: &str) -> Result<Self, String> {
let theme_set = ThemeSet::load_defaults();
theme_set
.themes
.get(name)
.map(Self::from_syntect_theme)
.ok_or_else(|| format!("Theme '{}' not found", name))
}
pub fn available_themes() -> Vec<String> {
let theme_set = ThemeSet::load_defaults();
theme_set.themes.keys().cloned().collect()
}
pub fn color_for(&self, style: SpanStyle) -> Color {
self.colors
.get(&style)
.copied()
.unwrap_or(self.default_foreground)
}
pub fn default_foreground(&self) -> Color {
self.default_foreground
}
pub fn default_background(&self) -> Color {
self.default_background
}
fn color_for_scope(highlighter: &Highlighter, scope_str: &str) -> Color {
let scope = Scope::new(scope_str).unwrap_or_else(|_| {
Scope::new("").unwrap()
});
let mut stack = ScopeStack::new();
stack.push(scope);
let style = highlighter.style_for_stack(stack.as_slice());
style.foreground
}
}
impl Default for ColorScheme {
fn default() -> Self {
Self::from_theme_name("base16-ocean.dark").unwrap_or_else(|_| {
Self {
colors: std::collections::HashMap::new(),
default_foreground: Color {
r: 200,
g: 200,
b: 200,
a: 255,
},
default_background: Color {
r: 0,
g: 0,
b: 0,
a: 255,
},
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_load_theme() {
let scheme = ColorScheme::from_theme_name("base16-ocean.dark");
assert!(scheme.is_ok());
}
#[test]
fn test_available_themes() {
let themes = ColorScheme::available_themes();
assert!(!themes.is_empty());
assert!(themes.contains(&"base16-ocean.dark".to_string()));
}
#[test]
fn test_color_for_style() {
let scheme = ColorScheme::default();
let keyword_color = scheme.color_for(SpanStyle::Keyword);
let type_color = scheme.color_for(SpanStyle::TypeName);
assert!(keyword_color.r != 0 || keyword_color.g != 0 || keyword_color.b != 0);
assert!(type_color.r != 0 || type_color.g != 0 || type_color.b != 0);
}
#[test]
fn test_default_colors() {
let scheme = ColorScheme::default();
let fg = scheme.default_foreground();
let bg = scheme.default_background();
assert!(fg.a == 255);
assert!(bg.a == 255);
}
}