use syntect::highlighting::{
Color, FontStyle, ScopeSelectors, StyleModifier, Theme as SyntectTheme, ThemeItem,
ThemeSettings,
};
use crate::color::OpalineColor;
use crate::style::OpalineStyle;
use crate::theme::Theme;
impl From<OpalineColor> for Color {
fn from(c: OpalineColor) -> Self {
Color {
r: c.r,
g: c.g,
b: c.b,
a: 255,
}
}
}
impl From<&OpalineColor> for Color {
fn from(c: &OpalineColor) -> Self {
Color {
r: c.r,
g: c.g,
b: c.b,
a: 255,
}
}
}
impl From<OpalineStyle> for StyleModifier {
fn from(s: OpalineStyle) -> Self {
style_to_modifier(&s)
}
}
impl From<&OpalineStyle> for StyleModifier {
fn from(s: &OpalineStyle) -> Self {
style_to_modifier(s)
}
}
fn style_to_modifier(s: &OpalineStyle) -> StyleModifier {
let mut font_style = FontStyle::empty();
if s.bold {
font_style |= FontStyle::BOLD;
}
if s.italic {
font_style |= FontStyle::ITALIC;
}
if s.underline {
font_style |= FontStyle::UNDERLINE;
}
StyleModifier {
foreground: s.fg.map(Into::into),
background: s.bg.map(Into::into),
font_style: if font_style.is_empty() {
None
} else {
Some(font_style)
},
}
}
const SCOPE_MAPPINGS: &[(&str, &str)] = &[
("code.keyword", "keyword"),
("code.string", "string"),
("code.comment", "comment"),
("code.function", "entity.name.function, support.function"),
("code.number", "constant.numeric"),
("code.type", "entity.name.type, support.type"),
("code.line_number", "constant.numeric.line-number"),
("accent.primary", "variable"),
("accent.secondary", "storage.type, storage.modifier"),
("accent.tertiary", "constant.other, variable.other.constant"),
("success", "markup.inserted"),
("error", "invalid, message.error"),
("warning", "markup.changed"),
("info", "string.other.link, support.constant"),
];
pub fn to_syntect_theme(theme: &Theme) -> SyntectTheme {
let settings = build_settings(theme);
let scopes = build_scopes(theme);
SyntectTheme {
name: Some(theme.meta.name.clone()),
author: theme.meta.author.clone(),
settings,
scopes,
}
}
fn build_settings(theme: &Theme) -> ThemeSettings {
ThemeSettings {
foreground: theme.try_color("text.primary").map(Into::into),
background: theme.try_color("bg.base").map(Into::into),
caret: theme.try_color("accent.primary").map(Into::into),
line_highlight: theme.try_color("bg.highlight").map(Into::into),
selection: theme.try_color("bg.selection").map(Into::into),
selection_foreground: theme.try_color("text.primary").map(Into::into),
gutter: theme.try_color("bg.panel").map(Into::into),
gutter_foreground: theme.try_color("text.dim").map(Into::into),
find_highlight: theme.try_color("warning").map(Into::into),
accent: theme.try_color("accent.primary").map(Into::into),
guide: theme.try_color("border.unfocused").map(Into::into),
active_guide: theme.try_color("border.focused").map(Into::into),
brackets_foreground: theme.try_color("accent.secondary").map(Into::into),
..ThemeSettings::default()
}
}
fn build_scopes(theme: &Theme) -> Vec<ThemeItem> {
let mut items = Vec::new();
for &(token, scope_str) in SCOPE_MAPPINGS {
if let Some(color) = theme.try_color(token) {
let style_name = token.strip_prefix("code.").unwrap_or(token);
let mut style = theme
.try_style(style_name)
.map(StyleModifier::from)
.unwrap_or_default();
style.foreground = Some(color.into());
if let Ok(scope) = scope_str.parse::<ScopeSelectors>() {
items.push(ThemeItem { scope, style });
}
}
}
items
}