use std::fmt::Write;
use crate::scope::{EMPTY_ATOM_NUMBER, MAX_ATOMS_IN_SCOPE, Scope, lock_global_scope_repo};
use crate::themes::compiled::CompiledTheme;
pub fn generate_css(theme: &CompiledTheme, prefix: &str) -> String {
let mut css = String::new();
writeln!(css, "/*").unwrap();
writeln!(css, " * theme \"{}\" generated by zalo", theme.name).unwrap();
writeln!(css, " */").unwrap();
writeln!(css).unwrap();
writeln!(css, ".{prefix}code {{").unwrap();
writeln!(
css,
" {}",
theme.default_style.foreground.as_css_color_property()
)
.unwrap();
writeln!(
css,
" {}",
theme.default_style.background.as_css_bg_color_property()
)
.unwrap();
writeln!(css, "}}").unwrap();
writeln!(css).unwrap();
if let Some(ref highlight_bg) = theme.highlight_background_color {
writeln!(css, ".{prefix}hl {{").unwrap();
writeln!(css, " {}", highlight_bg.as_css_bg_color_property()).unwrap();
writeln!(css, "}}").unwrap();
writeln!(css).unwrap();
}
if let Some(ref line_number_fg) = theme.line_number_foreground {
writeln!(css, ".z-ln {{").unwrap();
writeln!(css, " {}", line_number_fg.as_css_color_property()).unwrap();
writeln!(css, "}}").unwrap();
writeln!(css).unwrap();
}
for rule in &theme.rules {
if rule.style_modifier.has_properties() {
generate_rule_css(&mut css, rule, prefix);
}
}
css
}
fn generate_rule_css(
css: &mut String,
rule: &crate::themes::compiled::CompiledThemeRule,
prefix: &str,
) {
let css_selector = scope_to_css_selector(rule.selector.target_scope, prefix, false);
write!(css, "{css_selector} {{").unwrap();
if let Some(fg) = rule.style_modifier.foreground {
write!(css, " {}", fg.as_css_color_property()).unwrap();
}
if let Some(bg) = rule.style_modifier.background {
write!(css, " {}", bg.as_css_bg_color_property()).unwrap();
}
if let Some(font_style) = rule.style_modifier.font_style {
write!(css, " {}", font_style.css_attributes().join("")).unwrap();
}
writeln!(css, " }}").unwrap();
}
pub fn scope_to_css_selector(scope: Scope, prefix: &str, as_class: bool) -> String {
let mut selector = String::new();
let repo = lock_global_scope_repo();
for i in 0..MAX_ATOMS_IN_SCOPE {
let atom_number = scope.atom_at(i);
match atom_number {
0 => break, n if n == EMPTY_ATOM_NUMBER => {
continue;
}
n => {
let atom_str = repo.atom_number_to_str(n);
if as_class {
if !selector.is_empty() {
selector.push(' ');
}
} else {
selector.push('.');
}
selector.push_str(prefix);
selector.push_str(&escape_css_identifier(atom_str));
}
}
}
selector
}
fn escape_css_identifier(identifier: &str) -> String {
identifier
.chars()
.fold(String::with_capacity(identifier.len()), |mut output, c| {
if c.is_ascii_alphabetic()
|| c == '-'
|| c == '_'
|| (!output.is_empty() && c.is_ascii_digit())
{
output.push(c);
} else {
write!(output, "\\{:x} ", c as u32).unwrap();
}
output
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scope::Scope;
use crate::themes::RawTheme;
use insta::assert_snapshot;
#[test]
fn test_escape_css_identifier() {
assert_eq!(escape_css_identifier("keyword"), "keyword");
assert_eq!(escape_css_identifier("meta-function"), "meta-function");
assert_eq!(escape_css_identifier("git_gutter"), "git_gutter");
assert_eq!(escape_css_identifier("c++"), "c\\2b \\2b ");
}
#[test]
fn test_scope_to_css_selector() {
let scope = Scope::new("keyword.operator")[0];
let selector = scope_to_css_selector(scope, "g-", false);
assert_eq!(selector, ".g-keyword.g-operator");
let simple_scope = Scope::new("comment")[0];
let simple_selector = scope_to_css_selector(simple_scope, "g-", false);
assert_eq!(simple_selector, ".g-comment");
}
#[test]
fn test_scope_to_css_class() {
let scope = Scope::new("keyword.operator")[0];
let class = scope_to_css_selector(scope, "g-", true);
assert_eq!(class, "g-keyword g-operator");
let simple_scope = Scope::new("comment")[0];
let simple_class = scope_to_css_selector(simple_scope, "g-", true);
assert_eq!(simple_class, "g-comment");
}
#[test]
fn can_generate_css_for_theme() {
let theme = RawTheme::load_from_file(
"grammars-themes/packages/tm-themes/themes/vitesse-black.json",
)
.unwrap()
.compile()
.unwrap();
assert_snapshot!(generate_css(&theme, "g-"));
}
}