From 1ae8e8083a961934a41658d1bed3cc2c0fdfbb5c Mon Sep 17 00:00:00 2001 From: rhysd Date: Thu, 28 Nov 2024 19:04:53 +0900 Subject: [PATCH] separate generators into each modules --- alacritty/spring_night.toml | 2 +- autoload/airline/themes/spring_night.vim | 2 +- colors/spring-night.vim | 2 +- gen/.rustfmt.toml | 1 + gen/Cargo.lock | 6 +- gen/Cargo.toml | 2 +- gen/README.md | 22 +- gen/src/airline.rs | 329 +++++++ gen/src/alacritty.rs | 292 ++++++ gen/src/colorscheme.rs | 762 ++++++++++++++++ gen/src/main.rs | 1047 +--------------------- gen/src/palette.rs | 130 +++ gen/src/test.rs | 430 --------- 13 files changed, 1542 insertions(+), 1485 deletions(-) create mode 100644 gen/src/airline.rs create mode 100644 gen/src/alacritty.rs create mode 100644 gen/src/colorscheme.rs create mode 100644 gen/src/palette.rs delete mode 100644 gen/src/test.rs diff --git a/alacritty/spring_night.toml b/alacritty/spring_night.toml index a74b535..f08be7f 100644 --- a/alacritty/spring_night.toml +++ b/alacritty/spring_night.toml @@ -5,7 +5,7 @@ # Copyright (c) 2016 rhysd # # PLEASE DO NOT MODIFY THIS FILE DIRECTLY! -# Generated by script vim-color-spring-night/gen/src/main.rs +# Generated by script vim-color-spring-night/gen/src/alacritty.rs [colors.primary] background = "#132132" diff --git a/autoload/airline/themes/spring_night.vim b/autoload/airline/themes/spring_night.vim index 8b4b59d..e2b77fa 100644 --- a/autoload/airline/themes/spring_night.vim +++ b/autoload/airline/themes/spring_night.vim @@ -5,7 +5,7 @@ " Copyright (c) 2016 rhysd " " PLEASE DO NOT MODIFY THIS FILE DIRECTLY! -" Generated by script vim-color-spring-night/gen/src/main.rs +" Generated by script vim-color-spring-night/gen/src/airline.rs let g:airline#themes#spring_night#palette = {} diff --git a/colors/spring-night.vim b/colors/spring-night.vim index a58e90f..d5ddc91 100644 --- a/colors/spring-night.vim +++ b/colors/spring-night.vim @@ -5,7 +5,7 @@ " Copyright (c) 2016 rhysd " " PLEASE DO NOT MODIFY THIS FILE DIRECTLY! -" Generated by script vim-color-spring-night/gen/src/main.rs +" Generated by script vim-color-spring-night/gen/src/colorscheme.rs " Optimization: " `:set background=dark` has some side effects which takes a time. diff --git a/gen/.rustfmt.toml b/gen/.rustfmt.toml index 3a26366..55797ef 100644 --- a/gen/.rustfmt.toml +++ b/gen/.rustfmt.toml @@ -1 +1,2 @@ edition = "2021" +use_field_init_shorthand = true diff --git a/gen/Cargo.lock b/gen/Cargo.lock index dd070f0..3fff5bd 100644 --- a/gen/Cargo.lock +++ b/gen/Cargo.lock @@ -24,7 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "gen-color-spring-night" +name = "gen-spring-night-theme" version = "0.0.0" dependencies = [ "anyhow", @@ -44,9 +44,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "indexmap" diff --git a/gen/Cargo.toml b/gen/Cargo.toml index 1125448..ef5436f 100644 --- a/gen/Cargo.toml +++ b/gen/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "gen-color-spring-night" +name = "gen-spring-night-theme" version = "0.0.0" authors = ["rhysd "] publish = false diff --git a/gen/README.md b/gen/README.md index 1f80866..461fa19 100644 --- a/gen/README.md +++ b/gen/README.md @@ -2,8 +2,9 @@ The following files are generated by this script. - [`colors/spring-night.vim`](../colors/sprint-night.vim) - [`autoload/airline/themes/spring_night.vim`](../autoload/airline/themes/spring_night.vim) +- [`alacritty/sprint_night.toml`](../alacritty/sprint_night.toml) -This script requires Rust toolchain. Install it by following [the official steps](https://www.rust-lang.org/en-US/install.html) +This script requires Rust toolchain. Install it by following the [official instruction](https://www.rust-lang.org/en-US/install.html) and confirm that `cargo` command is available for package management. To generate files: @@ -24,17 +25,18 @@ To build a release binary: ``` $ cargo build --release -$ ./target/release/gen-color-spring-night --dir .. +$ ./target/release/gen-spring-night-theme --dir .. ``` To modify colors or highlights, you can update tables and lists in `src/main.rs`. -- `Palette` is a struct to define colors. `Palette::default` method constructs the instance. You - can find the color palette inside the function body. -- `Colorscheme` is a struct to generate Vim colorscheme file. `ColorschemeWriter::new` method - constructs the definition of highlights. Read the function body to know/modify the highlights for - each syntax items. The terminal colors used in `:terminal` are also defined here. -- `AirlineTheme` is a struct to generate [vim-airline](https://github.com/vim-airline/vim-airline) +- `Palette` in [`palette` module](./src/palette.rs) is a struct to define colors. `Palette::default` + method constructs the instance. You can find the color palette inside the function body. +- `Colorscheme` in [`colorscheme` module](./src/colorscheme.rs) is a struct to generate Vim + colorscheme file. `ColorschemeWriter::new` method constructs the definition of highlights. Read + the function body to know/modify the highlights for each syntax items. The terminal colors used in + `:terminal` are also defined here. +- `AirlineTheme` in [`airline` module](./src/airline.rs) is a struct to generate [vim-airline](https://github.com/vim-airline/vim-airline) theme file. `AirlineThemeWriter::new` method defines color palettes for each modes. -- `AlacrittyTheme` is a struct to generate [Alacritty](https://alacritty.org/) theme file. - `AlacrittyTheme::new` method defines the terminal ANSI colors. +- `AlacrittyTheme` in [`alacritty` module](./src/alacritty.rs) is a struct to generate [Alacritty](https://alacritty.org/) + theme file. `AlacrittyTheme::new` method defines the terminal ANSI colors. diff --git a/gen/src/airline.rs b/gen/src/airline.rs new file mode 100644 index 0000000..1fbf490 --- /dev/null +++ b/gen/src/airline.rs @@ -0,0 +1,329 @@ +use crate::palette::Palette; +use std::collections::HashMap; +use std::io::{Result, Write}; + +#[derive(Debug, PartialEq, Default)] +struct ModeColors<'a> { + label: (&'a str, &'a str), + info: (&'a str, &'a str), + main: (&'a str, &'a str), + modified: Option<&'a str>, + modified_main: Option<&'a str>, +} + +#[derive(Debug)] +pub struct AirlineTheme<'a> { + palette: &'a Palette, + modes: HashMap<&'a str, ModeColors<'a>>, + paste: &'a str, + info_mod: &'a str, + error: (&'a str, &'a str), + warning: (&'a str, &'a str), +} + +impl<'a> AirlineTheme<'a> { + pub fn new(palette: &'a Palette) -> Self { + // Note: Pairs of strings are color names of (fg, bg) + Self { + palette, + modes: { + let mut m = HashMap::new(); + + macro_rules! mode_colors { + ($name:ident { $($n:ident: $e:expr,)+ }) => { + assert_eq!(m.insert(stringify!($name), ModeColors { $($n: $e,)+ }), None) + }; + } + + mode_colors!(normal { + label: ("bg", "gold"), + info: ("gold", "hiddenfg"), + main: ("yellow", "bglight"), + modified: Some("green"), + modified_main: Some("whitegreen"), + }); + + mode_colors!(insert { + label: ("bg", "skyblue"), + info: ("skyblue", "hiddenfg"), + main: ("whiteblue", "bglight"), + modified: None, + modified_main: None, + }); + + mode_colors!(visual { + label: ("bg", "kakezakura"), + info: ("kakezakura", "hiddenfg"), + main: ("whitepink", "bglight"), + modified: Some("sakura"), + modified_main: None, + }); + + mode_colors!(replace { + label: ("bg", "red"), + info: ("red", "hiddenfg"), + main: ("whitered", "bglight"), + modified: Some("crimson"), + modified_main: None, + }); + + mode_colors!(inactive { + label: ("weakfg", "bglight"), + info: ("weakfg", "bglight"), + main: ("weakfg", "bglight"), + modified: None, + modified_main: None, + }); + + m + }, + paste: "mikan", + info_mod: "hiddenfg", + error: ("bg", "red"), + warning: ("bg", "mikan"), + } + } + + fn write_header(&self, w: &mut impl Write) -> Result<()> { + let red = &self.palette["red"]; + // Header + write!( + w, + r#"" vim-airline theme for spring-night colorscheme +" +" Author: rhysd +" License: MIT +" Copyright (c) 2016 rhysd +" +" PLEASE DO NOT MODIFY THIS FILE DIRECTLY! +" Generated by script vim-color-spring-night/gen/{source} + +let g:airline#themes#spring_night#palette = {{}} + +let g:airline#themes#spring_night#palette.accents = {{ +\ 'red': ['{guifg}', '', {ctermfg}, '', ''], +\ }} + +"#, + source = file!(), + guifg = red.gui.normal(), + ctermfg = red.cterm.normal(), + ) + } + + fn write_section_color( + &self, + w: &mut impl Write, + name: &str, + (fg, bg): (&'a str, &'a str), + ) -> Result<()> { + let fg = &self.palette[fg]; + let bg = &self.palette[bg]; + writeln!( + w, + "\\ 'airline_{name}': ['{gui_fg}', '{gui_bg}', {cterm_fg}, {cterm_bg}, ''],", + gui_fg = fg.gui.normal(), + gui_bg = bg.gui.normal(), + cterm_fg = fg.cterm.normal(), + cterm_bg = bg.cterm.normal(), + ) + } + + fn write_error_warning(&self, w: &mut impl Write) -> Result<()> { + self.write_section_color(w, "error", self.error)?; + self.write_section_color(w, "warning", self.warning) + } + + fn write_mode_colors(&self, w: &mut impl Write, name: &str) -> Result<()> { + let mode = &self.modes[name]; + + writeln!(w, "let g:airline#themes#spring_night#palette.{name} = {{")?; + self.write_section_color(w, "a", mode.label)?; + self.write_section_color(w, "b", mode.info)?; + self.write_section_color(w, "c", mode.main)?; + self.write_section_color(w, "x", mode.main)?; + self.write_section_color(w, "y", mode.info)?; + self.write_section_color(w, "z", mode.label)?; + self.write_error_warning(w)?; + writeln!(w, "\\ }}")?; + + if let Some(modified) = mode.modified { + let main_fg = mode.modified_main.unwrap_or(modified); + writeln!( + w, + "let g:airline#themes#spring_night#palette.{name}_modified = {{", + )?; + self.write_section_color(w, "a", (mode.label.0, modified))?; + self.write_section_color(w, "b", (modified, self.info_mod))?; + self.write_section_color(w, "c", (main_fg, mode.main.1))?; + self.write_error_warning(w)?; + writeln!(w, "\\ }}")?; + } + + writeln!(w) + } + + pub fn write_to(&self, w: &mut impl Write) -> Result<()> { + self.write_header(w)?; + + for mode in &["normal", "insert", "visual", "replace", "inactive"] { + self.write_mode_colors(w, mode)?; + } + + let normal_map = &self.modes["normal"]; + let insert_map = &self.modes["insert"]; + + // Insert Paste + writeln!( + w, + "let g:airline#themes#spring_night#palette.insert_paste = {{", + )?; + self.write_section_color(w, "a", (insert_map.label.0, self.paste))?; + self.write_section_color(w, "b", (self.paste, self.info_mod))?; + self.write_section_color(w, "c", (self.paste, normal_map.main.1))?; + self.write_error_warning(w)?; + writeln!(w, "\\ }}\n")?; + + // Inactive Modified is a special case + writeln!( + w, + "let g:airline#themes#spring_night#palette.inactive_modified = {{", + )?; + let modified = &self.palette[normal_map.modified.unwrap()]; + let guifg = modified.gui.normal(); + let ctermfg = modified.cterm.normal(); + writeln!(w, "\\ 'airline_c': ['{guifg}', '', {ctermfg}, '', ''],")?; + self.write_error_warning(w)?; + writeln!(w, "\\ }}") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::palette::{Color, ColorCode}; + use regex::Regex; + use std::str; + + #[test] + fn test_write_theme() { + let mut m = HashMap::new(); + m.insert( + "color1", + Color { + gui: ColorCode::Normal("#123456"), + cterm: ColorCode::Normal(123), + }, + ); + m.insert( + "color2", + Color { + gui: ColorCode::Contrast("#000000", "#ffffff"), + cterm: ColorCode::Contrast(1, 2), + }, + ); + // Userd for accents + m.insert( + "red", + Color { + gui: ColorCode::Normal("#ff0000"), + cterm: ColorCode::Normal(123), + }, + ); + + let palette = Palette::from(m); + let w = AirlineTheme { + palette: &palette, + modes: { + let mut m = HashMap::new(); + m.insert( + "normal", + ModeColors { + label: ("color1", "color2"), + info: ("color2", "color1"), + main: ("color2", "color2"), + modified: Some("color1"), + modified_main: Some("color1"), + }, + ); + m.insert( + "insert", + ModeColors { + label: ("color2", "color1"), + info: ("color1", "color2"), + main: ("color1", "color1"), + modified: Some("color1"), + modified_main: None, + }, + ); + m.insert( + "visual", + ModeColors { + label: ("color1", "color1"), + info: ("color2", "color2"), + main: ("color1", "color1"), + modified: None, + modified_main: None, + }, + ); + m.insert( + "replace", + ModeColors { + label: ("color1", "color1"), + info: ("color2", "color2"), + main: ("color1", "color1"), + modified: Some("color1"), + modified_main: None, + }, + ); + m.insert( + "inactive", + ModeColors { + label: ("color1", "color1"), + info: ("color2", "color2"), + main: ("color1", "color1"), + modified: Some("color2"), + modified_main: Some("color2"), + }, + ); + m + }, + paste: "color1", + info_mod: "color2", + error: ("color1", "color2"), + warning: ("color2", "color1"), + }; + + let mut out = vec![]; + w.write_to(&mut out).unwrap(); + let rendered = str::from_utf8(&out).unwrap(); + + let re_var = Regex::new(r"^let g:airline#themes#spring_night#palette\.(\w+) =").unwrap(); + let re_palette = + Regex::new(r"^\\\s+'(red|airline_(a|b|c|x|y|z|error|warning))': \[('(#[[:xdigit:]]{6})?',\s*){2}((\d{1,3}|''),\s*){2}''\]").unwrap(); + for line in rendered.lines() { + if line.starts_with("let g:") { + match re_var.captures(line) { + Some(found) => { + let mode = &found[1]; + assert!( + w.modes.keys().any(|m| *m == mode + || format!("{}_modified", m) == mode + || format!("{}_paste", m) == mode + || "accents" == mode), + "Unknown mode: {}", + mode + ); + } + None => assert!( + line == "let g:airline#themes#spring_night#palette = {}", + "Invalid variable definition: {}", + line + ), + } + } else if line.starts_with("\\ ") { + assert!(re_palette.is_match(line), "Invalid color palette: {}", line); + } + } + } +} diff --git a/gen/src/alacritty.rs b/gen/src/alacritty.rs new file mode 100644 index 0000000..7a75619 --- /dev/null +++ b/gen/src/alacritty.rs @@ -0,0 +1,292 @@ +use crate::palette::Palette; +use std::fmt; +use std::io::{Result, Write}; + +#[derive(Clone, Copy, Debug)] +enum Brightness { + Dim, + Normal, + Bright, +} + +impl fmt::Display for Brightness { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Dim => write!(f, "dim"), + Self::Normal => write!(f, "normal"), + Self::Bright => write!(f, "bright"), + } + } +} + +#[derive(Debug)] +struct AnsiColors<'a> { + brightness: Brightness, + black: &'a str, + red: &'a str, + green: &'a str, + yellow: &'a str, + blue: &'a str, + magenta: &'a str, + cyan: &'a str, + white: &'a str, +} + +#[derive(Debug)] +struct ForegroundColors<'a> { + dim: &'a str, + normal: &'a str, + bright: &'a str, + hint_head: &'a str, + hint_tail: &'a str, +} + +#[derive(Debug)] +struct BackgroundColors<'a> { + normal: &'a str, + search: &'a str, + search_focus: &'a str, + footer_bar: &'a str, + line_indicator: &'a str, + hint_head: &'a str, + hint_tail: &'a str, +} + +#[derive(Debug)] +pub struct AlacrittyTheme<'a> { + palette: &'a Palette, + fg: ForegroundColors<'a>, + bg: BackgroundColors<'a>, + dim: AnsiColors<'a>, + normal: AnsiColors<'a>, + bright: AnsiColors<'a>, +} + +impl<'a> AlacrittyTheme<'a> { + pub fn new(palette: &'a Palette) -> Self { + Self { + palette, + fg: ForegroundColors { + dim: "yellow", + normal: "fg", + bright: "fg", + hint_head: "bg", + hint_tail: "bg", + }, + bg: BackgroundColors { + normal: "bg", + search: "sakura", + search_focus: "kakezakura", + footer_bar: "bgstrong", + line_indicator: "yaezakura", + hint_head: "mikan", + hint_tail: "orange", + }, + dim: AnsiColors { + brightness: Brightness::Dim, + black: "black", + red: "mildred", + green: "darkgreen", + yellow: "dullgold", + blue: "blue", + magenta: "fuchsia", + cyan: "cloudy", + white: "gray", + }, + normal: AnsiColors { + brightness: Brightness::Normal, + black: "black", + red: "crimson", + green: "green", + yellow: "gold", + blue: "blue", + magenta: "purple", + cyan: "skyblue", + white: "white", + }, + bright: AnsiColors { + brightness: Brightness::Bright, + black: "gray", + red: "red", + green: "lime", + yellow: "yellow", + blue: "paleblue", + magenta: "purple", + cyan: "sunny", + white: "white", + }, + } + } + + fn color(&self, name: &str) -> &'_ str { + self.palette[name].gui.normal() + } + + fn write_header_comment(&self, w: &mut impl Write) -> Result<()> { + writeln!( + w, + r#"# Alacritty theme for spring-night colorscheme +# +# Author: rhysd +# License: MIT +# Copyright (c) 2016 rhysd +# +# PLEASE DO NOT MODIFY THIS FILE DIRECTLY! +# Generated by script vim-color-spring-night/gen/{file}"#, + file = file!(), + ) + } + + #[rustfmt::skip] + fn write_primary_section(&self, w: &mut impl Write) -> Result<()> { + writeln!(w)?; + writeln!(w, "[colors.primary]")?; + writeln!(w, "background = \"{}\"", self.color(self.bg.normal))?; + writeln!(w, "foreground = \"{}\"", self.color(self.fg.normal))?; + writeln!(w, "dim_foreground = \"{}\"", self.color(self.fg.dim))?; + writeln!(w, "bright_foreground = \"{}\"", self.color(self.fg.bright)) + } + + #[rustfmt::skip] + fn write_ansi_colors_section(&self, w: &mut impl Write, colors: &AnsiColors<'a>) -> Result<()> { + writeln!(w)?; + writeln!(w, "[colors.{}]", colors.brightness)?; + writeln!(w, "black = \"{}\"", self.color(colors.black))?; + writeln!(w, "red = \"{}\"", self.color(colors.red))?; + writeln!(w, "green = \"{}\"", self.color(colors.green))?; + writeln!(w, "yellow = \"{}\"", self.color(colors.yellow))?; + writeln!(w, "blue = \"{}\"", self.color(colors.blue))?; + writeln!(w, "magenta = \"{}\"", self.color(colors.magenta))?; + writeln!(w, "cyan = \"{}\"", self.color(colors.cyan))?; + writeln!(w, "white = \"{}\"", self.color(colors.white)) + } + + fn write_search_section(&self, w: &mut impl Write) -> Result<()> { + writeln!(w)?; + writeln!(w, "[colors.search]")?; + writeln!( + w, + r#"matches = {{ foreground = "{fg}", background = "{bg}" }}"#, + fg = self.color(self.fg.normal), + bg = self.color(self.bg.search), + )?; + writeln!( + w, + r#"focused_match = {{ foreground = "{fg}", background = "{bg}" }}"#, + fg = self.color(self.fg.bright), + bg = self.color(self.bg.search_focus), + ) + } + + fn write_single_color_section( + &self, + w: &mut impl Write, + name: &str, + fg: &str, + bg: &str, + ) -> Result<()> { + writeln!(w)?; + writeln!(w, "[colors.{name}]")?; + writeln!(w, "foreground = \"{}\"", &self.color(fg))?; + writeln!(w, "background = \"{}\"", &self.color(bg)) + } + + fn write_footer_bar_section(&self, w: &mut impl Write) -> Result<()> { + self.write_single_color_section(w, "footer_bar", self.fg.normal, self.bg.footer_bar) + } + + fn write_line_indicator_section(&self, w: &mut impl Write) -> Result<()> { + self.write_single_color_section(w, "line_indicator", self.fg.normal, self.bg.line_indicator) + } + + fn write_hints_section(&self, w: &mut impl Write) -> Result<()> { + writeln!(w)?; + writeln!(w, "[colors.hints]")?; + writeln!( + w, + r#"start = {{ foreground = "{fg}", background = "{bg}" }}"#, + fg = self.color(self.fg.hint_head), + bg = self.color(self.bg.hint_head), + )?; + writeln!( + w, + r#"end = {{ foreground = "{fg}", background = "{bg}" }}"#, + fg = self.color(self.fg.hint_tail), + bg = self.color(self.bg.hint_tail), + ) + } + + pub fn write_to(&self, w: &mut impl Write) -> Result<()> { + self.write_header_comment(w)?; + self.write_primary_section(w)?; + for colors in [&self.dim, &self.normal, &self.bright] { + self.write_ansi_colors_section(w, colors)?; + } + self.write_search_section(w)?; + self.write_footer_bar_section(w)?; + self.write_line_indicator_section(w)?; + self.write_hints_section(w) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use regex::Regex; + use std::str; + use toml_edit::{DocumentMut, Item as TomlItem, Value as TomlValue}; + + #[test] + fn test_default_alacritty_theme() { + let p = Palette::default(); + let w = AlacrittyTheme::new(&p); + let mut out = vec![]; + w.write_to(&mut out).unwrap(); + let src = str::from_utf8(&out).unwrap(); + let doc: DocumentMut = src.parse().expect(src); + + let hex_color = Regex::new(r"^#[[:xdigit:]]{6}$").unwrap(); + let assert_color = move |path: &str| { + let mut item = doc.as_item(); + for idx in path.split('.') { + item = item.get(idx).expect(path); + } + let TomlItem::Value(v) = item else { + panic!("{path} is not a primitive: {item:?}"); + }; + let TomlValue::String(s) = v else { + panic!("{path} is not a string: {v:?}"); + }; + let s = s.value().as_str(); + assert!(hex_color.is_match(s), "{path} is not a hex color: {s:?}"); + }; + + for path in [ + "foreground", + "background", + "dim_foreground", + "bright_foreground", + ] { + assert_color(&format!("colors.primary.{path}")); + } + + for mode in ["dim", "normal", "bright"] { + for color in [ + "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", + ] { + assert_color(&format!("colors.{mode}.{color}")); + } + } + + for section in [ + "search.matches", + "search.focused_match", + "footer_bar", + "line_indicator", + ] { + for color in ["foreground", "background"] { + assert_color(&format!("colors.{section}.{color}")); + } + } + } +} diff --git a/gen/src/colorscheme.rs b/gen/src/colorscheme.rs new file mode 100644 index 0000000..b8eeb60 --- /dev/null +++ b/gen/src/colorscheme.rs @@ -0,0 +1,762 @@ +use crate::palette::{ColorCode, Palette}; +use std::fmt::Display; +use std::io::{Result, Write}; + +const NONE_COLOR: ColorCode<&'static str> = ColorCode::Normal("NONE"); + +type ColorName = Option<&'static str>; + +#[derive(Debug, PartialEq)] +enum HiAttr { + Nothing, + Bold, + Italic, + Underline, + Reverse, + None, + CommentItalic, + Undercurl, +} + +#[derive(Debug)] +struct HiCommand { + name: &'static str, + fg: ColorName, + bg: ColorName, + sp: ColorName, + attr: HiAttr, +} + +#[derive(Debug)] +enum Highlight { + Fixed(HiCommand), + Dynamic { gui: HiCommand, term: HiCommand }, // Use different highlights for GUI and CUI +} + +fn indent(level: u8) -> &'static str { + &" "[..level as usize * 4] +} + +#[derive(Debug)] +pub struct Colorscheme<'a> { + palette: &'a Palette, + highlights: &'a [Highlight], + term_colors: [&'static str; 16], +} + +impl<'a> Colorscheme<'a> { + pub fn new(palette: &'a Palette) -> Self { + macro_rules! color { + (-) => { + None // '-' means don't care + }; + ($name:ident) => { + Some(stringify!($name)) + }; + } + + macro_rules! hi { + ($name:ident, $fg:tt, $bg:tt , $sp:tt, $attr:ident) => { + HiCommand { + name: stringify!($name), + fg: color!($fg), + bg: color!($bg), + sp: color!($sp), + attr: HiAttr::$attr, + } + }; + } + + use Highlight::{Dynamic, Fixed}; + + #[rustfmt::skip] + let highlights = &[ + // NAME FG BG SP ATTRIBUTES + //--------------------------------------------------------------------------------- + // Normal colors + Fixed(hi!(Boolean, red, -, -, Nothing)), + Fixed(hi!(Character, green, -, -, Nothing)), + Fixed(hi!(ColorColumn, -, bgstrong, -, Nothing)), + Fixed(hi!(Comment, weakfg, -, -, CommentItalic)), + Fixed(hi!(Conceal, mikan, bg, -, Nothing)), + Fixed(hi!(Conditional, skyblue, -, -, Nothing)), + Fixed(hi!(Constant, red, -, -, Nothing)), + Fixed(hi!(Cursor, bg, fg, -, Nothing)), + Fixed(hi!(lCursor, bg, fg, -, Nothing)), + Fixed(hi!(CursorColumn, -, bgemphasis, -, Nothing)), + Fixed(hi!(CursorLine, -, bgemphasis, -, None)), + Fixed(hi!(CursorLineNr, purple, bgstrong, -, Nothing)), + Fixed(hi!(Define, orange, -, -, Nothing)), + Fixed(hi!(Directory, green, -, -, Nothing)), + Fixed(hi!(EndOfBuffer, bgstrong, -, -, Nothing)), + Fixed(hi!(Error, red, bgemphasis, -, Bold)), + Fixed(hi!(ErrorMsg, red, bg, -, Bold)), + Fixed(hi!(Float, red, -, -, Nothing)), + Fixed(hi!(NormalFloat, fg, bgweaker, -, Nothing)), + Fixed(hi!(FloatBorder, weakfg, bgweaker, -, Nothing)), + Fixed(hi!(FoldColumn, purple, bgemphasis, -, Nothing)), + Fixed(hi!(Folded, purple, light, -, Nothing)), + Fixed(hi!(Function, orange, -, -, Nothing)), + Fixed(hi!(Identifier, gold, -, -, Italic)), + Fixed(hi!(IncSearch, NONE, sakura, -, Underline)), + Fixed(hi!(Keyword, yellow, -, -, Bold)), + Fixed(hi!(Label, skyblue, -, -, Nothing)), + Fixed(hi!(LineNr, weakerfg, bgemphasis, -, Nothing)), + Fixed(hi!(MatchParen, bg, gold, -, Bold)), + Fixed(hi!(ModeMsg, gold, -, -, Nothing)), + Fixed(hi!(MoreMsg, green, -, -, Nothing)), + Fixed(hi!(NonText, light, -, -, Nothing)), + Fixed(hi!(Normal, fg, bg, -, Nothing)), + Fixed(hi!(Number, red, -, -, Nothing)), + Fixed(hi!(Operater, orange, -, -, Nothing)), + Fixed(hi!(Pmenu, purple, bgemphasis, -, Nothing)), + Fixed(hi!(PmenuSbar, gold, bgstrong, -, Nothing)), + Fixed(hi!(PmenuSel, gold, bgstrong, -, Nothing)), + Fixed(hi!(PmenuThumb, gold, weakfg, -, Nothing)), + Fixed(hi!(PreProc, orange, -, -, Nothing)), + Fixed(hi!(Question, skyblue, -, -, Nothing)), + Fixed(hi!(Search, NONE, nasu, -, Underline)), + Fixed(hi!(SignColumn, fg, bgemphasis, -, Nothing)), + Fixed(hi!(Special, yellow, -, -, Bold)), + Fixed(hi!(SpecialKey, hiddenfg, -, -, Nothing)), + Fixed(hi!(SpecialComment, palepink, -, -, Nothing)), + Dynamic { + gui: hi!(SpellBad, red, -, red, Undercurl), + term: hi!(SpellBad, red, NONE, red, Undercurl), + }, + Dynamic { + gui: hi!(SpellCap, purple, -, purple, Undercurl), + term: hi!(SpellCap, purple, NONE, purple, Undercurl), + }, + Dynamic { + gui: hi!(SpellLocal, red, -, red, Undercurl), + term: hi!(SpellLocal, red, NONE, red, Undercurl), + }, + Dynamic { + gui: hi!(SpellRare, yellow, -, yellow, Undercurl), + term: hi!(SpellRare, yellow, NONE, yellow, Undercurl), + }, + Fixed(hi!(Statement, skyblue, -, -, Nothing)), + Fixed(hi!(StatusLine, fg, bgstrong, -, Bold)), + Fixed(hi!(StatusLineNC, weakfg, bgemphasis, -, None)), + Fixed(hi!(StatusLineTerm, fg, bgstrong, -, Bold)), + Fixed(hi!(StatusLineTermNC, weakfg, bgemphasis, -, None)), + Fixed(hi!(StorageClass, gold, -, -, Italic)), + Fixed(hi!(String, green, -, -, Nothing)), + Fixed(hi!(TabLine, weakfg, bgstrong, -, Nothing)), + Fixed(hi!(TabLineFill, bgemphasis, -, -, Nothing)), + Fixed(hi!(TabLineSel, gold, bg, -, Bold)), + Fixed(hi!(Tag, orange, -, -, Nothing)), + Fixed(hi!(Title, gold, -, -, Bold)), + Fixed(hi!(Todo, bg, red, -, Bold)), + Fixed(hi!(ToolbarButton, gold, bg, -, Bold)), + Fixed(hi!(ToolbarLine, weakfg, bgstrong, -, Nothing)), + Fixed(hi!(Type, gold, -, -, Nothing)), + Fixed(hi!(Underlined, skyblue, -, -, Underline)), + Fixed(hi!(VertSplit, bgemphasis, bg, -, Nothing)), + Fixed(hi!(Visual, -, yaezakura, -, Nothing)), + Fixed(hi!(WarningMsg, mikan, bgemphasis, -, Nothing)), + Fixed(hi!(WildMenu, bg, gold, -, Nothing)), + // + // File type specific + // + // Markdown is highlighted with H TML highlights in gVim but link text doesn't + // have a color. So define it her e. + Fixed(hi!(cmakeArguments, yellow, -, -, Nothing)), + Fixed(hi!(cmakeOperators, red, -, -, Nothing)), + Fixed(hi!(cStorageClass, yellow, -, -, Nothing)), + Fixed(hi!(cTypedef, yellow, -, -, Nothing)), + Fixed(hi!(DiffAdd, -, darkgreen, -, Bold)), + Fixed(hi!(DiffChange, -, darkgold, -, Bold)), + Fixed(hi!(DiffDelete, fg, mildred, -, Bold)), + Fixed(hi!(DiffText, -, bg, -, Nothing)), + Fixed(hi!(diffAdded, green, -, -, Nothing)), + Fixed(hi!(diffFile, yellow, -, -, Nothing)), + Fixed(hi!(diffIndexLine, gold, -, -, Nothing)), + Fixed(hi!(diffNewFile, yellow, -, -, Nothing)), + Fixed(hi!(diffRemoved, red, -, -, Nothing)), + Fixed(hi!(gitCommitOverflow, -, mildred, -, Nothing)), + Fixed(hi!(gitCommitSummary, yellow, -, -, Nothing)), + Fixed(hi!(gitCommitSelectedFile, skyblue, -, -, Nothing)), + Fixed(hi!(gitconfigSection, skyblue, -, -, Bold)), + Fixed(hi!(goBuiltins, red, -, -, Nothing)), + Fixed(hi!(helpExample, skyblue, -, -, Nothing)), + Fixed(hi!(helpCommand, purple, -, -, Nothing)), + Fixed(hi!(htmlBold, -, bgemphasis, -, Nothing)), + Fixed(hi!(htmlLinkText, skyblue, -, -, Nothing)), + Fixed(hi!(htmlTagName, orange, -, -, Nothing)), + Fixed(hi!(javaScriptBraces, fg, -, -, Nothing)), + Fixed(hi!(makeCommands, yellow, -, -, Nothing)), + Fixed(hi!(markdownCode, yellow, -, -, Nothing)), + Fixed(hi!(markdownUrl, weakfg, -, -, Nothing)), + Fixed(hi!(ocamlConstructor, gold, -, -, Nothing)), + Fixed(hi!(ocamlKeyChar, skyblue, -, -, Nothing)), + Fixed(hi!(ocamlKeyword, gold , -, -, Nothing)), + Fixed(hi!(ocamlFunDef, skyblue, -, -, Nothing)), + Fixed(hi!(plantumlColonLine, skyblue, -, -, Nothing)), + Fixed(hi!(pythonBuiltin, red, -, -, Nothing)), + Fixed(hi!(qfFileName, gold, -, -, Nothing)), + Fixed(hi!(qfLineNr, skyblue, -, -, Nothing)), + Fixed(hi!(rstEmphasis, -, bgemphasis, -, Italic)), + Fixed(hi!(rstStrongEmphasis, -, bgstrong, -, Bold)), + Fixed(hi!(rubyFunction, yellow, -, -, Nothing)), + Fixed(hi!(rubyIdentifier, yellow, -, -, Nothing)), + Fixed(hi!(rustEnumVariant, gold, -, -, Nothing)), + Fixed(hi!(rustFuncCall, fg, -, -, Nothing)), + Fixed(hi!(rustCommentLineDoc, palepink, -, -, Nothing)), + Fixed(hi!(scalaInstanceDeclaration, gold, -, -, Nothing)), + Fixed(hi!(tomlTable, skyblue, -, -, Nothing)), + Fixed(hi!(tomlTableArray, skyblue, -, -, Nothing)), + Fixed(hi!(tomlKey, gold, -, -, Nothing)), + Fixed(hi!(tmuxCommands, skyblue, -, -, Nothing)), + Fixed(hi!(tmuxFlags, gold, -, -, Nothing)), + Fixed(hi!(tmuxFormatString, yellow, -, -, Nothing)), + Fixed(hi!(typescriptBraces, fg, -, -, Nothing)), + Fixed(hi!(typescriptAsyncFuncKeyword, skyblue, -, -, Nothing)), + Fixed(hi!(typescriptKeywordOp, yellow, -, -, Bold)), + Fixed(hi!(vimfilerColumn__SizeLine, weakfg, -, -, Nothing)), + Fixed(hi!(vimfilerClosedFile, green, -, -, Nothing)), + Fixed(hi!(vimCommand, skyblue, -, -, Nothing)), + Fixed(hi!(watListDelimiter, fg, -, -, Nothing)), + Fixed(hi!(watInstGeneral, yellow, -, -, Nothing)), + Fixed(hi!(watInstGetSet, yellow, -, -, Nothing)), + Fixed(hi!(watInstWithType, yellow, -, -, Nothing)), + Fixed(hi!(watUnnamedVar, purple, -, -, Nothing)), + Fixed(hi!(zshDelimiter, skyblue, -, -, Nothing)), + Fixed(hi!(zshPrecommand, red, -, -, Nothing)), + Fixed(hi!(debugPC, bg, skyblue, -, Nothing)), + Fixed(hi!(debugBreakPoint, bg, gold, -, Nothing)), + Fixed(hi!(zigMultilineStringDelimiter, yellow, -, -, Nothing)), + // + // Plugin specific + // + // Some plugins introduce its own highlight definitions. Adjust them for + // working fine with this colorscheme. + Fixed(hi!(ALEWarningSign, orange, bgemphasis, -, Bold)), + Fixed(hi!(ALEErrorSign, bgemphasis, mildred, -, Bold)), + Fixed(hi!(ALEInfoSign, -, light, -, Nothing)), + Fixed(hi!(ALEError, -, mildred, -, Nothing)), + Fixed(hi!(ALEWarning, -, darkgold, -, Nothing)), + Fixed(hi!(Flake8_Error, red, bgemphasis, -, Nothing)), + Fixed(hi!(Flake8_Warning, yellow, bgemphasis, -, Nothing)), + Fixed(hi!(Flake8_PyFlake, skyblue, bgemphasis, -, Nothing)), + Fixed(hi!(Flake8_Complexity, skyblue, bgemphasis, -, Nothing)), + Fixed(hi!(Flake8_Naming, skyblue, bgemphasis, -, Nothing)), + Fixed(hi!(SignifySignAdd, green, bgemphasis, -, Nothing)), + Fixed(hi!(SignifySignChange, yellow, bgemphasis, -, Nothing)), + Fixed(hi!(SignifySignChangeDelete, gold, bgemphasis, -, Nothing)), + Fixed(hi!(SignifySignDelete, red, bgemphasis, -, Nothing)), + Fixed(hi!(CleverFChar, bg, red, -, Nothing)), + Fixed(hi!(CleverFDirect, bg, red, -, Nothing)), + Fixed(hi!(DirvishArg, yellow, -, -, Bold)), + Fixed(hi!(EasyMotionTarget, red, -, -, Bold)), + Fixed(hi!(EasyMotionShade, weakfg, bg, -, Nothing)), + Fixed(hi!(GitGutterAdd, green, bgemphasis, -, Nothing)), + Fixed(hi!(GitGutterChange, yellow, bgemphasis, -, Nothing)), + Fixed(hi!(GitGutterChangeDelete, gold, bgemphasis, -, Nothing)), + Fixed(hi!(GitGutterDelete, red, bgemphasis, -, Nothing)), + Fixed(hi!(HighlightedyankRegion, -, bgemphasis, -, Nothing)), + Dynamic { + gui: hi!(EasyMotionIncCursor, bg, fg, -, Nothing), + term: hi!(EasyMotionIncCursor, -, -, -, Reverse), + }, + Fixed(hi!(plugDeleted, weakfg, -, -, Nothing)), + Fixed(hi!(ConflictMarker, -, mildred, -, Nothing)), + Fixed(hi!(IndentGuidesOdd, -, bgweaker, -, Nothing)), + Fixed(hi!(IndentGuidesEven, -, bgemphasis, -, Nothing)), + ]; + + let term_colors = [ + "bg", // 0: black + "crimson", // 1: red + "green", // 2: green + "gold", // 3: yellow + "blue", // 4: blue + "purple", // 5: magenta + "skyblue", // 6: cyan + "fg", // 7: white + "weakerfg", // 8: bright black (gray) + "red", // 9: bright red + "lime", // 10: bright green + "yellow", // 11: bright yellow + "paleblue", // 12: bright blue + "purple", // 13: bright magenta + "sunny", // 14: bright cyan + "white", // 15: bright white + ]; + + Self { + palette, + highlights, + term_colors, + } + } + + fn write_header(&self, w: &mut impl Write) -> Result<()> { + write!( + w, + r#"" spring-night: Calm-colored dark color scheme +" +" Author: rhysd +" License: MIT +" Copyright (c) 2016 rhysd +" +" PLEASE DO NOT MODIFY THIS FILE DIRECTLY! +" Generated by script vim-color-spring-night/gen/{source} + +" Optimization: +" `:set background=dark` has some side effects which takes a time. +" Avoid the side effects when the value is already 'dark'. +if &background !=# 'dark' + set background=dark +endif + +" Optimization: +" `:hi clear` takes a lot of time since it clears all highlights and set default +" highlights. This guard avoids `:hi clear` if spring-night is the first colorscheme. +" applied in vimrc. In almost all cases no additional highlights are set at start +" up since they are set by Vim plugins. +if exists('g:colors_name') + " Remove all existing user-defined highlights and set the defaults. + hi clear +endif + +if exists('g:syntax_on') + syntax reset +endif + +let g:colors_name = 'spring-night' + +let g:spring_night_italic_comments = get(g:, 'spring_night_italic_comments', 0) +let g:spring_night_kill_italic = get(g:, 'spring_night_kill_italic', 0) +let g:spring_night_kill_bold = get(g:, 'spring_night_kill_bold', 0) +let g:spring_night_highlight_terminal = get(g:, 'spring_night_highlight_terminal', 1) +let g:spring_night_cterm_italic = get(g:, 'spring_night_cterm_italic', 0) + +let s:gui_running = has('gui_running') +let s:true_colors = has('termguicolors') && &termguicolors +let s:undercurl_attr = s:gui_running ? 'gui=undercurl cterm=undercurl' : 'gui=underline cterm=underline' +let s:italic_attr = g:spring_night_kill_italic ? '' : g:spring_night_cterm_italic ? 'gui=italic cterm=italic' : 'gui=italic' +let s:bold_attr = g:spring_night_kill_bold ? '' : 'gui=bold cterm=bold' + +if exists('g:spring_night_high_contrast') + if type(g:spring_night_high_contrast) != type(0) + echoerr 'g:spring_night_high_contrast was changed to number value. Please read README.md of vim-color-spring-night repository and set proper value' + let g:spring_night_high_contrast = !s:gui_running && s:true_colors + endif +else + let g:spring_night_high_contrast = !s:gui_running && s:true_colors +endif + +"#, + source = file!(), + ) + } + + fn write_contrast_color_variables(&self, w: &mut impl Write) -> Result<()> { + for (name, color) in { + let mut v = self.palette.iter().collect::>(); + v.sort_by_key(|(&k, _)| k); // Sort by color name to avoid random order + v + } { + if let ColorCode::Contrast(high, low) = color.gui { + writeln!( + w, + "let s:{name}_gui = g:spring_night_high_contrast ? '{high}' : '{low}'", + )?; + } + if let ColorCode::Contrast(high, low) = color.cterm { + writeln!( + w, + "let s:{name}_cterm = g:spring_night_high_contrast ? {high} : {low}", + )?; + } + } + writeln!(w) + } + + fn write_hi_command(&self, w: &mut impl Write, cmd: &HiCommand, indents: u8) -> Result<()> { + fn arg(name: &str, item: &str, color: &ColorCode) -> String { + match color { + ColorCode::Normal(c) => format!("{item}={c}"), + ColorCode::Contrast(..) if item.starts_with("gui") => { + format!("'{item}='.s:{name}_gui") + } + ColorCode::Contrast(..) => format!("'{item}='.s:{name}_cterm"), + } + } + + let mut args = vec![format!("{} term=NONE", cmd.name)]; + + for (color_name, gui, cterm) in + [(&cmd.fg, "guifg", "ctermfg"), (&cmd.bg, "guibg", "ctermbg")] + { + if let Some(name) = color_name { + if name != &"NONE" { + let color = &self.palette[name]; + args.push(arg(name, gui, &color.gui)); + args.push(arg(name, cterm, &color.cterm)); + } else { + args.push(arg(name, gui, &NONE_COLOR)); + args.push(arg(name, cterm, &NONE_COLOR)); + } + } + } + + if let Some(name) = cmd.sp { + // Note: ctermsp does not exist + let color = &self.palette[name].gui; // Currently guisp must not be NONE + args.push(arg(name, "guisp", color)); + } + + let attr_item = match cmd.attr { + HiAttr::Nothing => "", + HiAttr::Bold => "s:bold_attr", + HiAttr::Italic => "s:italic_attr", + HiAttr::Underline => "gui=underline cterm=underline", + HiAttr::Reverse => "gui=reverse cterm=reverse", + HiAttr::None => "gui=NONE cterm=NONE", + HiAttr::CommentItalic => "g:spring_night_italic_comments ? s:italic_attr : ''", + HiAttr::Undercurl => "s:undercurl_attr", + }; + if !attr_item.is_empty() { + args.push(attr_item.into()); + } + + let is_execute = args.iter().any(|a| a.contains("s:") || a.contains("g:")); + if is_execute { + write!(w, "{}exe 'hi'", indent(indents))?; + } else { + write!(w, "{}hi", indent(indents))?; + } + + for arg in &args { + if is_execute && !arg.contains("s:") && !arg.contains("g:") { + write!(w, " '{}'", arg)?; + } else { + write!(w, " {}", arg)?; + } + } + + writeln!(w) + } + + fn write_highlights(&self, w: &mut impl Write) -> Result<()> { + for hl in self.highlights { + match hl { + Highlight::Fixed(hl) => self.write_hi_command(w, hl, 0)?, + Highlight::Dynamic { gui, term } => { + writeln!(w, "if s:gui_running")?; + self.write_hi_command(w, gui, 1)?; + writeln!(w, "else")?; + self.write_hi_command(w, term, 1)?; + writeln!(w, "endif")?; + } + } + } + writeln!(w) + } + + fn write_nvim_term_colors(&self, w: &mut impl Write, indents: u8) -> Result<()> { + writeln!(w, "{}if s:gui_running || s:true_colors", indent(indents))?; + for (index, name) in self.term_colors.iter().enumerate() { + writeln!( + w, + "{indent}let g:terminal_color_{index} = '{color}'", + indent = indent(indents + 1), + color = self.palette[name].gui.normal(), + )?; + } + writeln!(w, "{}else", indent(indents))?; + for (index, name) in self.term_colors.iter().enumerate() { + writeln!( + w, + "{indent}let g:terminal_color_{index} = {color}", + indent = indent(indents + 1), + color = self.palette[name].cterm.normal(), + )?; + } + writeln!(w, "{}endif", indent(indents))?; + writeln!( + w, + "{indent}let g:terminal_color_background = g:terminal_color_0", + indent = indent(indents), + )?; + writeln!( + w, + "{indent}let g:terminal_color_foreground = g:terminal_color_7", + indent = indent(indents), + ) + } + + fn write_vim_term_colors(&self, w: &mut impl Write, indents: u8) -> Result<()> { + write!(w, "{}let g:terminal_ansi_colors = [", indent(indents))?; + for (index, name) in self.term_colors.iter().enumerate() { + if index > 0 { + write!(w, ",")?; + } + write!(w, "'{}'", self.palette[name].gui.normal())?; + } + writeln!(w, "]") + } + + fn write_term_colors(&self, w: &mut impl Write) -> Result<()> { + writeln!(w, "if g:spring_night_highlight_terminal")?; + writeln!(w, "{}if has('nvim')", indent(1))?; + self.write_nvim_term_colors(w, 2)?; + writeln!( + w, + "{indent}elseif (s:gui_running || s:true_colors) && exists('*term_setansicolors')", + indent = indent(1), + )?; + self.write_vim_term_colors(w, 2)?; + writeln!(w, "{}endif", indent(1))?; + writeln!(w, "endif") + } + + pub fn write_to(&mut self, w: &mut impl Write) -> Result<()> { + self.write_header(w)?; + self.write_contrast_color_variables(w)?; + self.write_highlights(w)?; + self.write_term_colors(w) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::palette::Color; + use std::collections::{HashMap, HashSet}; + use std::str; + + #[test] + fn test_write_header() { + let palette = Palette::from(HashMap::new()); + let w = Colorscheme::new(&palette); + let mut out = vec![]; + w.write_header(&mut out).unwrap(); + let rendered = str::from_utf8(&out).unwrap(); + assert!(rendered.starts_with(r#"" spring-night: Calm-colored dark color scheme"#)); + assert!(rendered.contains("let g:colors_name = 'spring-night'")); + } + + #[test] + fn test_write_contrast_color_variables() { + let palette = Palette::from(HashMap::new()); + let w = Colorscheme::new(&palette); + let mut out = vec![]; + w.write_contrast_color_variables(&mut out).unwrap(); + assert_eq!(str::from_utf8(&out).unwrap(), "\n"); + + let mut m = HashMap::new(); + m.insert( + "hi", + Color { + gui: ColorCode::Normal("#123456"), + cterm: ColorCode::Contrast(12, 34), + }, + ); + m.insert( + "hello", + Color { + gui: ColorCode::Contrast("#123456", "#7890ab"), + cterm: ColorCode::Contrast(123, 234), + }, + ); + m.insert( + "hey", + Color { + gui: ColorCode::Normal("#123456"), + cterm: ColorCode::Normal(123), + }, + ); + m.insert( + "goodbye", + Color { + gui: ColorCode::Contrast("#000000", "#ffffff"), + cterm: ColorCode::Normal(123), + }, + ); + let palette = Palette::from(m); + let w = Colorscheme::new(&palette); + let mut out = vec![]; + w.write_contrast_color_variables(&mut out).unwrap(); + for (actual, expected) in [ + "let s:goodbye_gui = g:spring_night_high_contrast ? '#000000' : '#ffffff'", + "let s:hello_gui = g:spring_night_high_contrast ? '#123456' : '#7890ab'", + "let s:hello_cterm = g:spring_night_high_contrast ? 123 : 234", + "let s:hi_cterm = g:spring_night_high_contrast ? 12 : 34", + "", + ] + .iter() + .zip(str::from_utf8(&out).unwrap().lines()) + { + assert_eq!(*actual, expected); + } + } + + #[test] + fn test_write_highlight() { + #[rustfmt::skip] + let testcases = vec![ + ((None, None, None, HiAttr::Nothing), 0, "hi HL term=NONE"), + ((Some("n"), None, None, HiAttr::Nothing), 0, "hi HL term=NONE guifg=#123456 ctermfg=123"), + ((None, Some("n"), None, HiAttr::Nothing), 0, "hi HL term=NONE guibg=#123456 ctermbg=123"), + ((Some("n"), Some("n"), None, HiAttr::Nothing), 0, "hi HL term=NONE guifg=#123456 ctermfg=123 guibg=#123456 ctermbg=123"), + ((None, None, None, HiAttr::Bold), 0, "exe 'hi' 'HL term=NONE' s:bold_attr"), + ((None, None, None, HiAttr::Italic), 0, "exe 'hi' 'HL term=NONE' s:italic_attr"), + ((None, None, None, HiAttr::Underline), 0, "hi HL term=NONE gui=underline cterm=underline"), + ((None, None, None, HiAttr::CommentItalic), 0, "exe 'hi' 'HL term=NONE' g:spring_night_italic_comments ? s:italic_attr : ''"), + ((None, None, None, HiAttr::Undercurl), 0, "exe 'hi' 'HL term=NONE' s:undercurl_attr"), + ((Some("c"), None, None, HiAttr::Nothing), 0, "exe 'hi' 'HL term=NONE' 'guifg='.s:c_gui 'ctermfg='.s:c_cterm"), + ((None, Some("c"), None, HiAttr::Nothing), 0, "exe 'hi' 'HL term=NONE' 'guibg='.s:c_gui 'ctermbg='.s:c_cterm"), + ((Some("c"), Some("c"), None, HiAttr::Underline), 0, "exe 'hi' 'HL term=NONE' 'guifg='.s:c_gui 'ctermfg='.s:c_cterm 'guibg='.s:c_gui 'ctermbg='.s:c_cterm 'gui=underline cterm=underline'"), + ((None, None, Some("n"), HiAttr::Nothing), 0, "hi HL term=NONE guisp=#123456"), + ((None, None, Some("n"), HiAttr::Undercurl), 0, "exe 'hi' 'HL term=NONE' 'guisp=#123456' s:undercurl_attr"), + ((None, None, None, HiAttr::Nothing), 1, " hi HL term=NONE"), + ((None, None, None, HiAttr::Undercurl), 1, " exe 'hi' 'HL term=NONE' s:undercurl_attr"), + ]; + + for ((fg, bg, sp, attr), indent, expected) in testcases { + let cmd = HiCommand { + name: "HL", + fg, + bg, + sp, + attr, + }; + let mut m = HashMap::new(); + m.insert( + "n", + Color { + gui: ColorCode::Normal("#123456"), + cterm: ColorCode::Normal(123), + }, + ); + m.insert( + "c", + Color { + gui: ColorCode::Contrast("#123456", "#7890ab"), + cterm: ColorCode::Contrast(123, 234), + }, + ); + let palette = Palette::from(m); + let w = Colorscheme::new(&palette); + let mut out = vec![]; + w.write_hi_command(&mut out, &cmd, indent).unwrap(); + assert_eq!(str::from_utf8(&out).unwrap(), format!("{}\n", expected)); + } + + // Edge case + let palette = Palette::from(HashMap::new()); + let mut w = Colorscheme::new(&palette); + w.highlights = &[]; + let mut out = vec![]; + w.write_highlights(&mut out).unwrap(); + assert_eq!(str::from_utf8(&out).unwrap(), "\n"); + } + + #[test] + fn test_write_highlights() { + const fn cmd() -> HiCommand { + HiCommand { + name: "HL", + fg: None, + bg: None, + sp: None, + attr: HiAttr::Nothing, + } + } + + let palette = Palette::from(HashMap::new()); + let mut w = Colorscheme::new(&palette); + let fixed = &[Highlight::Fixed(cmd())]; + w.highlights = fixed; + let mut out = vec![]; + w.write_highlights(&mut out).unwrap(); + assert_eq!(str::from_utf8(&out).unwrap(), "hi HL term=NONE\n\n"); + + let dynamic = &[Highlight::Dynamic { + gui: cmd(), + term: cmd(), + }]; + let palette = Palette::from(HashMap::new()); + let mut w = Colorscheme::new(&palette); + w.highlights = dynamic; + let mut out = vec![]; + w.write_highlights(&mut out).unwrap(); + assert_eq!( + str::from_utf8(&out).unwrap().lines().collect::>(), + vec![ + "if s:gui_running", + " hi HL term=NONE", + "else", + " hi HL term=NONE", + "endif", + "", + ], + ); + } + + #[test] + fn test_write_term_colors() { + let mut m = HashMap::new(); + m.insert( + "normal", + Color { + gui: ColorCode::Normal("#123456"), + cterm: ColorCode::Normal(123), + }, + ); + m.insert( + "contrast", + Color { + gui: ColorCode::Contrast("#000000", "#ffffff"), + cterm: ColorCode::Contrast(1, 2), + }, + ); + let palette = Palette::from(m); + let mut w = Colorscheme::new(&palette); + w.term_colors = [ + "normal", "contrast", "normal", "contrast", "normal", "contrast", "normal", "contrast", + "normal", "contrast", "normal", "contrast", "normal", "contrast", "normal", "contrast", + ]; + let mut out = vec![]; + w.write_term_colors(&mut out).unwrap(); + let rendered = str::from_utf8(&out).unwrap(); + assert!(rendered.contains("let g:terminal_color_0 = '#123456'")); + assert!(rendered.contains("let g:terminal_color_1 = '#000000'")); + assert!(rendered.contains("let g:terminal_color_0 = 123")); + assert!(rendered.contains("let g:terminal_color_1 = 1")); + assert!(rendered.contains("let g:terminal_ansi_colors = ['#123456','#000000','#123456','#000000','#123456','#000000','#123456','#000000','#123456','#000000','#123456','#000000','#123456','#000000','#123456','#000000']")); + } + + #[test] + fn test_hilight_uniqueness() { + let palette = Palette::default(); + let w = Colorscheme::new(&palette); + let mut seen = HashSet::new(); + for hl in w.highlights { + let name = match hl { + Highlight::Fixed(h) => h.name, + Highlight::Dynamic { gui, term } => { + assert_eq!(gui.name, term.name); + gui.name + } + }; + assert!(seen.insert(name), "Duplicate highlight '{}'", name); + } + } + + #[test] + fn test_term_colors() { + let palette = Palette::default(); + let w = Colorscheme::new(&palette); + for tc in &w.term_colors { + assert!( + palette.contains_key(tc), + "Terminal color '{tc}' is not present in color names", + ); + } + } +} diff --git a/gen/src/main.rs b/gen/src/main.rs index bda3dea..f7a9d99 100644 --- a/gen/src/main.rs +++ b/gen/src/main.rs @@ -1,1048 +1,19 @@ -#[cfg(test)] -mod test; +mod airline; +mod alacritty; +mod colorscheme; +mod palette; + +use airline::AirlineTheme; +use alacritty::AlacrittyTheme; +use colorscheme::Colorscheme; +use palette::Palette; use anyhow::{Context, Result}; -use std::collections::HashMap; use std::env; -use std::fmt::{self, Display}; use std::fs::File; use std::io::{self, BufWriter, Write}; -use std::ops::Deref; use std::path::PathBuf; -#[derive(Debug, PartialEq)] -enum ColorCode { - Normal(T), - Contrast(T, T), -} - -impl ColorCode { - fn normal(&self) -> &T { - match self { - ColorCode::Normal(c) => c, - ColorCode::Contrast(h, _) => h, - } - } -} - -const NONE_COLOR: ColorCode<&'static str> = ColorCode::Normal("NONE"); - -#[derive(Debug, PartialEq)] -struct Color { - gui: ColorCode<&'static str>, - cterm: ColorCode, -} - -type ColorName = Option<&'static str>; - -#[derive(Debug, PartialEq)] -enum HiAttr { - Nothing, - Bold, - Italic, - Underline, - Reverse, - None, - CommentItalic, - Undercurl, -} - -#[derive(Debug)] -struct HiCommand { - name: &'static str, - fg: ColorName, - bg: ColorName, - sp: ColorName, - attr: HiAttr, -} - -#[derive(Debug)] -enum Highlight { - Fixed(HiCommand), - Dynamic { gui: HiCommand, term: HiCommand }, // Use different highlights for GUI and CUI -} - -#[derive(Debug)] -struct Palette(HashMap<&'static str, Color>); - -impl Deref for Palette { - type Target = HashMap<&'static str, Color>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Default for Palette { - #[rustfmt::skip] - fn default() -> Self { - use ColorCode::{Normal, Contrast}; - - let mut table = HashMap::new(); - let mut color = |name, gui, cterm| { - assert_eq!(table.insert(name, Color { gui, cterm }), None); - }; - - color("bg", Contrast("#132132", "#334152"), Normal(233)); - color("bgweaker", Contrast("#213243", "#3a4b5c"), Normal(235)); - color("bgemphasis", Normal("#3a4b5c"), Normal(235)); - color("bglight", Normal("#435060"), Normal(236)); - color("bgstrong", Normal("#536273"), Normal(238)); - color("light", Normal("#646f7c"), Normal(60)); - color("fg", Normal("#fffeeb"), Contrast(231, 230)); - color("hiddenfg", Normal("#607080"), Normal(60)); - color("weakfg", Normal("#8d9eb2"), Normal(103)); - color("weakerfg", Normal("#788898"), Normal(102)); - color("black", Normal("#111e25"), Normal(233)); - color("gray", Normal("#545f6e"), Normal(59)); - color("white", Normal("#ffffff"), Normal(231)); - color("nasu", Normal("#605779"), Normal(61)); - color("fuchsia", Normal("#b9a5cf"), Normal(183)); - color("purple", Normal("#e7d5ff"), Normal(189)); - color("yaezakura", Normal("#70495d"), Normal(95)); - color("sakura", Normal("#a9667a"), Normal(132)); - color("kakezakura", Normal("#e996aa"), Normal(175)); - color("palepink", Normal("#e7c6b7"), Normal(181)); - color("mikan", Normal("#fb8965"), Normal(209)); - color("orange", Normal("#f0aa8a"), Normal(216)); - color("darkgreen", Normal("#5f8770"), Normal(65)); - color("green", Normal("#a9dd9d"), Normal(150)); - color("lime", Normal("#c9fd88"), Normal(149)); - color("blue", Normal("#7098e6"), Normal(69)); - color("paleblue", Normal("#98b8e6"), Normal(111)); - color("cloudy", Normal("#90aecb"), Normal(75)); - color("skyblue", Normal("#a8d2eb"), Normal(153)); - color("sunny", Normal("#b8e2fb"), Normal(195)); - color("yellow", Normal("#f0eaaa"), Normal(229)); - color("gold", Normal("#fedf81"), Normal(222)); - color("dullgold", Normal("#b6955b"), Normal(221)); - color("darkgold", Contrast("#484000", "#685800"), Normal(58)); - color("mildred", Normal("#ab6560"), Normal(167)); - color("red", Normal("#fd8489"), Normal(210)); - color("crimson", Normal("#ff6a6f"), Normal(203)); - color("darkblue", Normal("#00091e"), Normal(235)); - color("whitepink", Normal("#ebeadb"), Normal(224)); - color("whitegreen", Normal("#eaf0aa"), Normal(194)); - color("whiteblue", Normal("#d8e2f0"), Normal(195)); - color("whitered", Normal("#ffbfaf"), Normal(217)); - color("inu", Normal("#ddbc96"), Normal(180)); - - Self(table) - } -} - -fn indent(level: u8) -> &'static str { - &" "[..level as usize * 4] -} - -#[derive(Debug)] -struct Colorscheme<'a> { - palette: &'a Palette, - highlights: &'a [Highlight], - term_colors: [&'static str; 16], -} - -impl<'a> Colorscheme<'a> { - fn new(palette: &'a Palette) -> Self { - macro_rules! color { - (-) => { - None // '-' means don't care - }; - ($name:ident) => { - Some(stringify!($name)) - }; - } - - macro_rules! hi { - ($name:ident, $fg:tt, $bg:tt , $sp:tt, $attr:ident) => { - HiCommand { - name: stringify!($name), - fg: color!($fg), - bg: color!($bg), - sp: color!($sp), - attr: HiAttr::$attr, - } - }; - } - - use Highlight::{Dynamic, Fixed}; - - #[rustfmt::skip] - let highlights = &[ - // NAME FG BG SP ATTRIBUTES - //--------------------------------------------------------------------------------- - // Normal colors - Fixed(hi!(Boolean, red, -, -, Nothing)), - Fixed(hi!(Character, green, -, -, Nothing)), - Fixed(hi!(ColorColumn, -, bgstrong, -, Nothing)), - Fixed(hi!(Comment, weakfg, -, -, CommentItalic)), - Fixed(hi!(Conceal, mikan, bg, -, Nothing)), - Fixed(hi!(Conditional, skyblue, -, -, Nothing)), - Fixed(hi!(Constant, red, -, -, Nothing)), - Fixed(hi!(Cursor, bg, fg, -, Nothing)), - Fixed(hi!(lCursor, bg, fg, -, Nothing)), - Fixed(hi!(CursorColumn, -, bgemphasis, -, Nothing)), - Fixed(hi!(CursorLine, -, bgemphasis, -, None)), - Fixed(hi!(CursorLineNr, purple, bgstrong, -, Nothing)), - Fixed(hi!(Define, orange, -, -, Nothing)), - Fixed(hi!(Directory, green, -, -, Nothing)), - Fixed(hi!(EndOfBuffer, bgstrong, -, -, Nothing)), - Fixed(hi!(Error, red, bgemphasis, -, Bold)), - Fixed(hi!(ErrorMsg, red, bg, -, Bold)), - Fixed(hi!(Float, red, -, -, Nothing)), - Fixed(hi!(NormalFloat, fg, bgweaker, -, Nothing)), - Fixed(hi!(FloatBorder, weakfg, bgweaker, -, Nothing)), - Fixed(hi!(FoldColumn, purple, bgemphasis, -, Nothing)), - Fixed(hi!(Folded, purple, light, -, Nothing)), - Fixed(hi!(Function, orange, -, -, Nothing)), - Fixed(hi!(Identifier, gold, -, -, Italic)), - Fixed(hi!(IncSearch, NONE, sakura, -, Underline)), - Fixed(hi!(Keyword, yellow, -, -, Bold)), - Fixed(hi!(Label, skyblue, -, -, Nothing)), - Fixed(hi!(LineNr, weakerfg, bgemphasis, -, Nothing)), - Fixed(hi!(MatchParen, bg, gold, -, Bold)), - Fixed(hi!(ModeMsg, gold, -, -, Nothing)), - Fixed(hi!(MoreMsg, green, -, -, Nothing)), - Fixed(hi!(NonText, light, -, -, Nothing)), - Fixed(hi!(Normal, fg, bg, -, Nothing)), - Fixed(hi!(Number, red, -, -, Nothing)), - Fixed(hi!(Operater, orange, -, -, Nothing)), - Fixed(hi!(Pmenu, purple, bgemphasis, -, Nothing)), - Fixed(hi!(PmenuSbar, gold, bgstrong, -, Nothing)), - Fixed(hi!(PmenuSel, gold, bgstrong, -, Nothing)), - Fixed(hi!(PmenuThumb, gold, weakfg, -, Nothing)), - Fixed(hi!(PreProc, orange, -, -, Nothing)), - Fixed(hi!(Question, skyblue, -, -, Nothing)), - Fixed(hi!(Search, NONE, nasu, -, Underline)), - Fixed(hi!(SignColumn, fg, bgemphasis, -, Nothing)), - Fixed(hi!(Special, yellow, -, -, Bold)), - Fixed(hi!(SpecialKey, hiddenfg, -, -, Nothing)), - Fixed(hi!(SpecialComment, palepink, -, -, Nothing)), - Dynamic { - gui: hi!(SpellBad, red, -, red, Undercurl), - term: hi!(SpellBad, red, NONE, red, Undercurl), - }, - Dynamic { - gui: hi!(SpellCap, purple, -, purple, Undercurl), - term: hi!(SpellCap, purple, NONE, purple, Undercurl), - }, - Dynamic { - gui: hi!(SpellLocal, red, -, red, Undercurl), - term: hi!(SpellLocal, red, NONE, red, Undercurl), - }, - Dynamic { - gui: hi!(SpellRare, yellow, -, yellow, Undercurl), - term: hi!(SpellRare, yellow, NONE, yellow, Undercurl), - }, - Fixed(hi!(Statement, skyblue, -, -, Nothing)), - Fixed(hi!(StatusLine, fg, bgstrong, -, Bold)), - Fixed(hi!(StatusLineNC, weakfg, bgemphasis, -, None)), - Fixed(hi!(StatusLineTerm, fg, bgstrong, -, Bold)), - Fixed(hi!(StatusLineTermNC, weakfg, bgemphasis, -, None)), - Fixed(hi!(StorageClass, gold, -, -, Italic)), - Fixed(hi!(String, green, -, -, Nothing)), - Fixed(hi!(TabLine, weakfg, bgstrong, -, Nothing)), - Fixed(hi!(TabLineFill, bgemphasis, -, -, Nothing)), - Fixed(hi!(TabLineSel, gold, bg, -, Bold)), - Fixed(hi!(Tag, orange, -, -, Nothing)), - Fixed(hi!(Title, gold, -, -, Bold)), - Fixed(hi!(Todo, bg, red, -, Bold)), - Fixed(hi!(ToolbarButton, gold, bg, -, Bold)), - Fixed(hi!(ToolbarLine, weakfg, bgstrong, -, Nothing)), - Fixed(hi!(Type, gold, -, -, Nothing)), - Fixed(hi!(Underlined, skyblue, -, -, Underline)), - Fixed(hi!(VertSplit, bgemphasis, bg, -, Nothing)), - Fixed(hi!(Visual, -, yaezakura, -, Nothing)), - Fixed(hi!(WarningMsg, mikan, bgemphasis, -, Nothing)), - Fixed(hi!(WildMenu, bg, gold, -, Nothing)), - // - // File type specific - // - // Markdown is highlighted with H TML highlights in gVim but link text doesn't - // have a color. So define it her e. - Fixed(hi!(cmakeArguments, yellow, -, -, Nothing)), - Fixed(hi!(cmakeOperators, red, -, -, Nothing)), - Fixed(hi!(cStorageClass, yellow, -, -, Nothing)), - Fixed(hi!(cTypedef, yellow, -, -, Nothing)), - Fixed(hi!(DiffAdd, -, darkgreen, -, Bold)), - Fixed(hi!(DiffChange, -, darkgold, -, Bold)), - Fixed(hi!(DiffDelete, fg, mildred, -, Bold)), - Fixed(hi!(DiffText, -, bg, -, Nothing)), - Fixed(hi!(diffAdded, green, -, -, Nothing)), - Fixed(hi!(diffFile, yellow, -, -, Nothing)), - Fixed(hi!(diffIndexLine, gold, -, -, Nothing)), - Fixed(hi!(diffNewFile, yellow, -, -, Nothing)), - Fixed(hi!(diffRemoved, red, -, -, Nothing)), - Fixed(hi!(gitCommitOverflow, -, mildred, -, Nothing)), - Fixed(hi!(gitCommitSummary, yellow, -, -, Nothing)), - Fixed(hi!(gitCommitSelectedFile, skyblue, -, -, Nothing)), - Fixed(hi!(gitconfigSection, skyblue, -, -, Bold)), - Fixed(hi!(goBuiltins, red, -, -, Nothing)), - Fixed(hi!(helpExample, skyblue, -, -, Nothing)), - Fixed(hi!(helpCommand, purple, -, -, Nothing)), - Fixed(hi!(htmlBold, -, bgemphasis, -, Nothing)), - Fixed(hi!(htmlLinkText, skyblue, -, -, Nothing)), - Fixed(hi!(htmlTagName, orange, -, -, Nothing)), - Fixed(hi!(javaScriptBraces, fg, -, -, Nothing)), - Fixed(hi!(makeCommands, yellow, -, -, Nothing)), - Fixed(hi!(markdownCode, yellow, -, -, Nothing)), - Fixed(hi!(markdownUrl, weakfg, -, -, Nothing)), - Fixed(hi!(ocamlConstructor, gold, -, -, Nothing)), - Fixed(hi!(ocamlKeyChar, skyblue, -, -, Nothing)), - Fixed(hi!(ocamlKeyword, gold , -, -, Nothing)), - Fixed(hi!(ocamlFunDef, skyblue, -, -, Nothing)), - Fixed(hi!(plantumlColonLine, skyblue, -, -, Nothing)), - Fixed(hi!(pythonBuiltin, red, -, -, Nothing)), - Fixed(hi!(qfFileName, gold, -, -, Nothing)), - Fixed(hi!(qfLineNr, skyblue, -, -, Nothing)), - Fixed(hi!(rstEmphasis, -, bgemphasis, -, Italic)), - Fixed(hi!(rstStrongEmphasis, -, bgstrong, -, Bold)), - Fixed(hi!(rubyFunction, yellow, -, -, Nothing)), - Fixed(hi!(rubyIdentifier, yellow, -, -, Nothing)), - Fixed(hi!(rustEnumVariant, gold, -, -, Nothing)), - Fixed(hi!(rustFuncCall, fg, -, -, Nothing)), - Fixed(hi!(rustCommentLineDoc, palepink, -, -, Nothing)), - Fixed(hi!(scalaInstanceDeclaration, gold, -, -, Nothing)), - Fixed(hi!(tomlTable, skyblue, -, -, Nothing)), - Fixed(hi!(tomlTableArray, skyblue, -, -, Nothing)), - Fixed(hi!(tomlKey, gold, -, -, Nothing)), - Fixed(hi!(tmuxCommands, skyblue, -, -, Nothing)), - Fixed(hi!(tmuxFlags, gold, -, -, Nothing)), - Fixed(hi!(tmuxFormatString, yellow, -, -, Nothing)), - Fixed(hi!(typescriptBraces, fg, -, -, Nothing)), - Fixed(hi!(typescriptAsyncFuncKeyword, skyblue, -, -, Nothing)), - Fixed(hi!(typescriptKeywordOp, yellow, -, -, Bold)), - Fixed(hi!(vimfilerColumn__SizeLine, weakfg, -, -, Nothing)), - Fixed(hi!(vimfilerClosedFile, green, -, -, Nothing)), - Fixed(hi!(vimCommand, skyblue, -, -, Nothing)), - Fixed(hi!(watListDelimiter, fg, -, -, Nothing)), - Fixed(hi!(watInstGeneral, yellow, -, -, Nothing)), - Fixed(hi!(watInstGetSet, yellow, -, -, Nothing)), - Fixed(hi!(watInstWithType, yellow, -, -, Nothing)), - Fixed(hi!(watUnnamedVar, purple, -, -, Nothing)), - Fixed(hi!(zshDelimiter, skyblue, -, -, Nothing)), - Fixed(hi!(zshPrecommand, red, -, -, Nothing)), - Fixed(hi!(debugPC, bg, skyblue, -, Nothing)), - Fixed(hi!(debugBreakPoint, bg, gold, -, Nothing)), - Fixed(hi!(zigMultilineStringDelimiter, yellow, -, -, Nothing)), - // - // Plugin specific - // - // Some plugins introduce its own highlight definitions. Adjust them for - // working fine with this colorscheme. - Fixed(hi!(ALEWarningSign, orange, bgemphasis, -, Bold)), - Fixed(hi!(ALEErrorSign, bgemphasis, mildred, -, Bold)), - Fixed(hi!(ALEInfoSign, -, light, -, Nothing)), - Fixed(hi!(ALEError, -, mildred, -, Nothing)), - Fixed(hi!(ALEWarning, -, darkgold, -, Nothing)), - Fixed(hi!(Flake8_Error, red, bgemphasis, -, Nothing)), - Fixed(hi!(Flake8_Warning, yellow, bgemphasis, -, Nothing)), - Fixed(hi!(Flake8_PyFlake, skyblue, bgemphasis, -, Nothing)), - Fixed(hi!(Flake8_Complexity, skyblue, bgemphasis, -, Nothing)), - Fixed(hi!(Flake8_Naming, skyblue, bgemphasis, -, Nothing)), - Fixed(hi!(SignifySignAdd, green, bgemphasis, -, Nothing)), - Fixed(hi!(SignifySignChange, yellow, bgemphasis, -, Nothing)), - Fixed(hi!(SignifySignChangeDelete, gold, bgemphasis, -, Nothing)), - Fixed(hi!(SignifySignDelete, red, bgemphasis, -, Nothing)), - Fixed(hi!(CleverFChar, bg, red, -, Nothing)), - Fixed(hi!(CleverFDirect, bg, red, -, Nothing)), - Fixed(hi!(DirvishArg, yellow, -, -, Bold)), - Fixed(hi!(EasyMotionTarget, red, -, -, Bold)), - Fixed(hi!(EasyMotionShade, weakfg, bg, -, Nothing)), - Fixed(hi!(GitGutterAdd, green, bgemphasis, -, Nothing)), - Fixed(hi!(GitGutterChange, yellow, bgemphasis, -, Nothing)), - Fixed(hi!(GitGutterChangeDelete, gold, bgemphasis, -, Nothing)), - Fixed(hi!(GitGutterDelete, red, bgemphasis, -, Nothing)), - Fixed(hi!(HighlightedyankRegion, -, bgemphasis, -, Nothing)), - Dynamic { - gui: hi!(EasyMotionIncCursor, bg, fg, -, Nothing), - term: hi!(EasyMotionIncCursor, -, -, -, Reverse), - }, - Fixed(hi!(plugDeleted, weakfg, -, -, Nothing)), - Fixed(hi!(ConflictMarker, -, mildred, -, Nothing)), - Fixed(hi!(IndentGuidesOdd, -, bgweaker, -, Nothing)), - Fixed(hi!(IndentGuidesEven, -, bgemphasis, -, Nothing)), - ]; - - let term_colors = [ - "bg", // 0: black - "crimson", // 1: red - "green", // 2: green - "gold", // 3: yellow - "blue", // 4: blue - "purple", // 5: magenta - "skyblue", // 6: cyan - "fg", // 7: white - "weakerfg", // 8: bright black (gray) - "red", // 9: bright red - "lime", // 10: bright green - "yellow", // 11: bright yellow - "paleblue", // 12: bright blue - "purple", // 13: bright magenta - "sunny", // 14: bright cyan - "white", // 15: bright white - ]; - - Self { - palette, - highlights, - term_colors, - } - } - - fn write_header(&self, w: &mut impl Write) -> io::Result<()> { - write!( - w, - r#"" spring-night: Calm-colored dark color scheme -" -" Author: rhysd -" License: MIT -" Copyright (c) 2016 rhysd -" -" PLEASE DO NOT MODIFY THIS FILE DIRECTLY! -" Generated by script vim-color-spring-night/gen/{source} - -" Optimization: -" `:set background=dark` has some side effects which takes a time. -" Avoid the side effects when the value is already 'dark'. -if &background !=# 'dark' - set background=dark -endif - -" Optimization: -" `:hi clear` takes a lot of time since it clears all highlights and set default -" highlights. This guard avoids `:hi clear` if spring-night is the first colorscheme. -" applied in vimrc. In almost all cases no additional highlights are set at start -" up since they are set by Vim plugins. -if exists('g:colors_name') - " Remove all existing user-defined highlights and set the defaults. - hi clear -endif - -if exists('g:syntax_on') - syntax reset -endif - -let g:colors_name = 'spring-night' - -let g:spring_night_italic_comments = get(g:, 'spring_night_italic_comments', 0) -let g:spring_night_kill_italic = get(g:, 'spring_night_kill_italic', 0) -let g:spring_night_kill_bold = get(g:, 'spring_night_kill_bold', 0) -let g:spring_night_highlight_terminal = get(g:, 'spring_night_highlight_terminal', 1) -let g:spring_night_cterm_italic = get(g:, 'spring_night_cterm_italic', 0) - -let s:gui_running = has('gui_running') -let s:true_colors = has('termguicolors') && &termguicolors -let s:undercurl_attr = s:gui_running ? 'gui=undercurl cterm=undercurl' : 'gui=underline cterm=underline' -let s:italic_attr = g:spring_night_kill_italic ? '' : g:spring_night_cterm_italic ? 'gui=italic cterm=italic' : 'gui=italic' -let s:bold_attr = g:spring_night_kill_bold ? '' : 'gui=bold cterm=bold' - -if exists('g:spring_night_high_contrast') - if type(g:spring_night_high_contrast) != type(0) - echoerr 'g:spring_night_high_contrast was changed to number value. Please read README.md of vim-color-spring-night repository and set proper value' - let g:spring_night_high_contrast = !s:gui_running && s:true_colors - endif -else - let g:spring_night_high_contrast = !s:gui_running && s:true_colors -endif - -"#, - source = file!(), - ) - } - - fn write_contrast_color_variables(&self, w: &mut impl Write) -> io::Result<()> { - for (name, color) in { - let mut v = self.palette.iter().collect::>(); - v.sort_by_key(|(&k, _)| k); // Sort by color name to avoid random order - v - } { - if let ColorCode::Contrast(high, low) = color.gui { - writeln!( - w, - "let s:{name}_gui = g:spring_night_high_contrast ? '{high}' : '{low}'", - )?; - } - if let ColorCode::Contrast(high, low) = color.cterm { - writeln!( - w, - "let s:{name}_cterm = g:spring_night_high_contrast ? {high} : {low}", - )?; - } - } - writeln!(w) - } - - fn write_hi_command(&self, w: &mut impl Write, cmd: &HiCommand, indents: u8) -> io::Result<()> { - fn arg(name: &str, item: &str, color: &ColorCode) -> String { - match color { - ColorCode::Normal(c) => format!("{item}={c}"), - ColorCode::Contrast(..) if item.starts_with("gui") => { - format!("'{item}='.s:{name}_gui") - } - ColorCode::Contrast(..) => format!("'{item}='.s:{name}_cterm"), - } - } - - let mut args = vec![format!("{} term=NONE", cmd.name)]; - - for (color_name, gui, cterm) in - [(&cmd.fg, "guifg", "ctermfg"), (&cmd.bg, "guibg", "ctermbg")] - { - if let Some(name) = color_name { - if name != &"NONE" { - let color = &self.palette[name]; - args.push(arg(name, gui, &color.gui)); - args.push(arg(name, cterm, &color.cterm)); - } else { - args.push(arg(name, gui, &NONE_COLOR)); - args.push(arg(name, cterm, &NONE_COLOR)); - } - } - } - - if let Some(name) = cmd.sp { - // Note: ctermsp does not exist - let color = &self.palette[name].gui; // Currently guisp must not be NONE - args.push(arg(name, "guisp", color)); - } - - let attr_item = match cmd.attr { - HiAttr::Nothing => "", - HiAttr::Bold => "s:bold_attr", - HiAttr::Italic => "s:italic_attr", - HiAttr::Underline => "gui=underline cterm=underline", - HiAttr::Reverse => "gui=reverse cterm=reverse", - HiAttr::None => "gui=NONE cterm=NONE", - HiAttr::CommentItalic => "g:spring_night_italic_comments ? s:italic_attr : ''", - HiAttr::Undercurl => "s:undercurl_attr", - }; - if !attr_item.is_empty() { - args.push(attr_item.into()); - } - - let is_execute = args.iter().any(|a| a.contains("s:") || a.contains("g:")); - if is_execute { - write!(w, "{}exe 'hi'", indent(indents))?; - } else { - write!(w, "{}hi", indent(indents))?; - } - - for arg in &args { - if is_execute && !arg.contains("s:") && !arg.contains("g:") { - write!(w, " '{}'", arg)?; - } else { - write!(w, " {}", arg)?; - } - } - - writeln!(w) - } - - fn write_highlights(&self, w: &mut impl Write) -> io::Result<()> { - for hl in self.highlights { - match hl { - Highlight::Fixed(hl) => self.write_hi_command(w, hl, 0)?, - Highlight::Dynamic { gui, term } => { - writeln!(w, "if s:gui_running")?; - self.write_hi_command(w, gui, 1)?; - writeln!(w, "else")?; - self.write_hi_command(w, term, 1)?; - writeln!(w, "endif")?; - } - } - } - writeln!(w) - } - - fn write_nvim_term_colors(&self, w: &mut impl Write, indents: u8) -> io::Result<()> { - writeln!(w, "{}if s:gui_running || s:true_colors", indent(indents))?; - for (index, name) in self.term_colors.iter().enumerate() { - writeln!( - w, - "{indent}let g:terminal_color_{index} = '{color}'", - indent = indent(indents + 1), - color = self.palette[name].gui.normal(), - )?; - } - writeln!(w, "{}else", indent(indents))?; - for (index, name) in self.term_colors.iter().enumerate() { - writeln!( - w, - "{indent}let g:terminal_color_{index} = {color}", - indent = indent(indents + 1), - color = self.palette[name].cterm.normal(), - )?; - } - writeln!(w, "{}endif", indent(indents))?; - writeln!( - w, - "{indent}let g:terminal_color_background = g:terminal_color_0", - indent = indent(indents), - )?; - writeln!( - w, - "{indent}let g:terminal_color_foreground = g:terminal_color_7", - indent = indent(indents), - ) - } - - fn write_vim_term_colors(&self, w: &mut impl Write, indents: u8) -> io::Result<()> { - write!(w, "{}let g:terminal_ansi_colors = [", indent(indents))?; - for (index, name) in self.term_colors.iter().enumerate() { - if index > 0 { - write!(w, ",")?; - } - write!(w, "'{}'", self.palette[name].gui.normal())?; - } - writeln!(w, "]") - } - - fn write_term_colors(&self, w: &mut impl Write) -> io::Result<()> { - writeln!(w, "if g:spring_night_highlight_terminal")?; - writeln!(w, "{}if has('nvim')", indent(1))?; - self.write_nvim_term_colors(w, 2)?; - writeln!( - w, - "{indent}elseif (s:gui_running || s:true_colors) && exists('*term_setansicolors')", - indent = indent(1), - )?; - self.write_vim_term_colors(w, 2)?; - writeln!(w, "{}endif", indent(1))?; - writeln!(w, "endif") - } - - fn write_to(&mut self, w: &mut impl Write) -> io::Result<()> { - self.write_header(w)?; - self.write_contrast_color_variables(w)?; - self.write_highlights(w)?; - self.write_term_colors(w) - } -} - -#[derive(Debug, PartialEq, Default)] -struct AirlineModeColors<'a> { - label: (&'a str, &'a str), - info: (&'a str, &'a str), - main: (&'a str, &'a str), - modified: Option<&'a str>, - modified_main: Option<&'a str>, -} - -#[derive(Debug)] -struct AirlineTheme<'a> { - palette: &'a Palette, - modes: HashMap<&'a str, AirlineModeColors<'a>>, - paste: &'a str, - info_mod: &'a str, - error: (&'a str, &'a str), - warning: (&'a str, &'a str), -} - -impl<'a> AirlineTheme<'a> { - fn new(palette: &'a Palette) -> Self { - // Note: Pairs of strings are color names of (fg, bg) - Self { - palette, - modes: { - let mut m = HashMap::new(); - - macro_rules! theme_mode_colors { - ($name:ident { $($n:ident: $e:expr,)+ }) => { - assert_eq!(m.insert(stringify!($name), AirlineModeColors { $($n: $e,)+ }), None) - }; - } - - theme_mode_colors!(normal { - label: ("bg", "gold"), - info: ("gold", "hiddenfg"), - main: ("yellow", "bglight"), - modified: Some("green"), - modified_main: Some("whitegreen"), - }); - - theme_mode_colors!(insert { - label: ("bg", "skyblue"), - info: ("skyblue", "hiddenfg"), - main: ("whiteblue", "bglight"), - modified: None, - modified_main: None, - }); - - theme_mode_colors!(visual { - label: ("bg", "kakezakura"), - info: ("kakezakura", "hiddenfg"), - main: ("whitepink", "bglight"), - modified: Some("sakura"), - modified_main: None, - }); - - theme_mode_colors!(replace { - label: ("bg", "red"), - info: ("red", "hiddenfg"), - main: ("whitered", "bglight"), - modified: Some("crimson"), - modified_main: None, - }); - - theme_mode_colors!(inactive { - label: ("weakfg", "bglight"), - info: ("weakfg", "bglight"), - main: ("weakfg", "bglight"), - modified: None, - modified_main: None, - }); - - m - }, - paste: "mikan", - info_mod: "hiddenfg", - error: ("bg", "red"), - warning: ("bg", "mikan"), - } - } - - fn write_header(&self, w: &mut impl Write) -> io::Result<()> { - let red = &self.palette["red"]; - // Header - write!( - w, - r#"" vim-airline theme for spring-night colorscheme -" -" Author: rhysd -" License: MIT -" Copyright (c) 2016 rhysd -" -" PLEASE DO NOT MODIFY THIS FILE DIRECTLY! -" Generated by script vim-color-spring-night/gen/{source} - -let g:airline#themes#spring_night#palette = {{}} - -let g:airline#themes#spring_night#palette.accents = {{ -\ 'red': ['{guifg}', '', {ctermfg}, '', ''], -\ }} - -"#, - source = file!(), - guifg = red.gui.normal(), - ctermfg = red.cterm.normal(), - ) - } - - fn write_section_color( - &self, - w: &mut impl Write, - name: &str, - (fg, bg): (&'a str, &'a str), - ) -> io::Result<()> { - let fg = &self.palette[fg]; - let bg = &self.palette[bg]; - writeln!( - w, - "\\ 'airline_{name}': ['{gui_fg}', '{gui_bg}', {cterm_fg}, {cterm_bg}, ''],", - gui_fg = fg.gui.normal(), - gui_bg = bg.gui.normal(), - cterm_fg = fg.cterm.normal(), - cterm_bg = bg.cterm.normal(), - ) - } - - fn write_error_warning(&self, w: &mut impl Write) -> io::Result<()> { - self.write_section_color(w, "error", self.error)?; - self.write_section_color(w, "warning", self.warning) - } - - fn write_mode_colors(&self, w: &mut impl Write, name: &str) -> io::Result<()> { - let mode = &self.modes[name]; - - writeln!(w, "let g:airline#themes#spring_night#palette.{name} = {{")?; - self.write_section_color(w, "a", mode.label)?; - self.write_section_color(w, "b", mode.info)?; - self.write_section_color(w, "c", mode.main)?; - self.write_section_color(w, "x", mode.main)?; - self.write_section_color(w, "y", mode.info)?; - self.write_section_color(w, "z", mode.label)?; - self.write_error_warning(w)?; - writeln!(w, "\\ }}")?; - - if let Some(modified) = mode.modified { - let main_fg = mode.modified_main.unwrap_or(modified); - writeln!( - w, - "let g:airline#themes#spring_night#palette.{name}_modified = {{", - )?; - self.write_section_color(w, "a", (mode.label.0, modified))?; - self.write_section_color(w, "b", (modified, self.info_mod))?; - self.write_section_color(w, "c", (main_fg, mode.main.1))?; - self.write_error_warning(w)?; - writeln!(w, "\\ }}")?; - } - - writeln!(w) - } - - fn write_to(&self, w: &mut impl Write) -> io::Result<()> { - self.write_header(w)?; - - for mode in &["normal", "insert", "visual", "replace", "inactive"] { - self.write_mode_colors(w, mode)?; - } - - let normal_map = &self.modes["normal"]; - let insert_map = &self.modes["insert"]; - - // Insert Paste - writeln!( - w, - "let g:airline#themes#spring_night#palette.insert_paste = {{", - )?; - self.write_section_color(w, "a", (insert_map.label.0, self.paste))?; - self.write_section_color(w, "b", (self.paste, self.info_mod))?; - self.write_section_color(w, "c", (self.paste, normal_map.main.1))?; - self.write_error_warning(w)?; - writeln!(w, "\\ }}\n")?; - - // Inactive Modified is a special case - writeln!( - w, - "let g:airline#themes#spring_night#palette.inactive_modified = {{", - )?; - let modified = &self.palette[normal_map.modified.unwrap()]; - let guifg = modified.gui.normal(); - let ctermfg = modified.cterm.normal(); - writeln!(w, "\\ 'airline_c': ['{guifg}', '', {ctermfg}, '', ''],")?; - self.write_error_warning(w)?; - writeln!(w, "\\ }}") - } -} - -#[derive(Clone, Copy, Debug)] -enum AlacrittyBrightness { - Dim, - Normal, - Bright, -} - -impl fmt::Display for AlacrittyBrightness { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Dim => write!(f, "dim"), - Self::Normal => write!(f, "normal"), - Self::Bright => write!(f, "bright"), - } - } -} - -#[derive(Debug)] -struct AlacrittyAnsiColors<'a> { - brightness: AlacrittyBrightness, - black: &'a str, - red: &'a str, - green: &'a str, - yellow: &'a str, - blue: &'a str, - magenta: &'a str, - cyan: &'a str, - white: &'a str, -} - -#[derive(Debug)] -struct AlacrittyForegroundColors<'a> { - dim: &'a str, - normal: &'a str, - bright: &'a str, - hint_head: &'a str, - hint_tail: &'a str, -} - -#[derive(Debug)] -struct AlacrittyBackgroundColors<'a> { - normal: &'a str, - search: &'a str, - search_focus: &'a str, - footer_bar: &'a str, - line_indicator: &'a str, - hint_head: &'a str, - hint_tail: &'a str, -} - -#[derive(Debug)] -struct AlacrittyTheme<'a> { - palette: &'a Palette, - fg: AlacrittyForegroundColors<'a>, - bg: AlacrittyBackgroundColors<'a>, - dim: AlacrittyAnsiColors<'a>, - normal: AlacrittyAnsiColors<'a>, - bright: AlacrittyAnsiColors<'a>, -} - -impl<'a> AlacrittyTheme<'a> { - fn new(palette: &'a Palette) -> Self { - Self { - palette, - fg: AlacrittyForegroundColors { - dim: "yellow", - normal: "fg", - bright: "fg", - hint_head: "bg", - hint_tail: "bg", - }, - bg: AlacrittyBackgroundColors { - normal: "bg", - search: "sakura", - search_focus: "kakezakura", - footer_bar: "bgstrong", - line_indicator: "yaezakura", - hint_head: "mikan", - hint_tail: "orange", - }, - dim: AlacrittyAnsiColors { - brightness: AlacrittyBrightness::Dim, - black: "black", - red: "mildred", - green: "darkgreen", - yellow: "dullgold", - blue: "blue", - magenta: "fuchsia", - cyan: "cloudy", - white: "gray", - }, - normal: AlacrittyAnsiColors { - brightness: AlacrittyBrightness::Normal, - black: "black", - red: "crimson", - green: "green", - yellow: "gold", - blue: "blue", - magenta: "purple", - cyan: "skyblue", - white: "white", - }, - bright: AlacrittyAnsiColors { - brightness: AlacrittyBrightness::Bright, - black: "gray", - red: "red", - green: "lime", - yellow: "yellow", - blue: "paleblue", - magenta: "purple", - cyan: "sunny", - white: "white", - }, - } - } - - fn color(&self, name: &str) -> &'_ str { - self.palette[name].gui.normal() - } - - fn write_header_comment(&self, w: &mut impl Write) -> io::Result<()> { - writeln!( - w, - r#"# Alacritty theme for spring-night colorscheme -# -# Author: rhysd -# License: MIT -# Copyright (c) 2016 rhysd -# -# PLEASE DO NOT MODIFY THIS FILE DIRECTLY! -# Generated by script vim-color-spring-night/gen/{file}"#, - file = file!(), - ) - } - - #[rustfmt::skip] - fn write_primary_section(&self, w: &mut impl Write) -> io::Result<()> { - writeln!(w)?; - writeln!(w, "[colors.primary]")?; - writeln!(w, "background = \"{}\"", self.color(self.bg.normal))?; - writeln!(w, "foreground = \"{}\"", self.color(self.fg.normal))?; - writeln!(w, "dim_foreground = \"{}\"", self.color(self.fg.dim))?; - writeln!(w, "bright_foreground = \"{}\"", self.color(self.fg.bright)) - } - - #[rustfmt::skip] - fn write_ansi_colors_section(&self, w: &mut impl Write, colors: &AlacrittyAnsiColors<'a>) -> io::Result<()> { - writeln!(w)?; - writeln!(w, "[colors.{}]", colors.brightness)?; - writeln!(w, "black = \"{}\"", self.color(colors.black))?; - writeln!(w, "red = \"{}\"", self.color(colors.red))?; - writeln!(w, "green = \"{}\"", self.color(colors.green))?; - writeln!(w, "yellow = \"{}\"", self.color(colors.yellow))?; - writeln!(w, "blue = \"{}\"", self.color(colors.blue))?; - writeln!(w, "magenta = \"{}\"", self.color(colors.magenta))?; - writeln!(w, "cyan = \"{}\"", self.color(colors.cyan))?; - writeln!(w, "white = \"{}\"", self.color(colors.white)) - } - - fn write_search_section(&self, w: &mut impl Write) -> io::Result<()> { - writeln!(w)?; - writeln!(w, "[colors.search]")?; - writeln!( - w, - r#"matches = {{ foreground = "{fg}", background = "{bg}" }}"#, - fg = self.color(self.fg.normal), - bg = self.color(self.bg.search), - )?; - writeln!( - w, - r#"focused_match = {{ foreground = "{fg}", background = "{bg}" }}"#, - fg = self.color(self.fg.bright), - bg = self.color(self.bg.search_focus), - ) - } - - fn write_single_color_section( - &self, - w: &mut impl Write, - name: &str, - fg: &str, - bg: &str, - ) -> io::Result<()> { - writeln!(w)?; - writeln!(w, "[colors.{name}]")?; - writeln!(w, "foreground = \"{}\"", &self.color(fg))?; - writeln!(w, "background = \"{}\"", &self.color(bg)) - } - - fn write_footer_bar_section(&self, w: &mut impl Write) -> io::Result<()> { - self.write_single_color_section(w, "footer_bar", self.fg.normal, self.bg.footer_bar) - } - - fn write_line_indicator_section(&self, w: &mut impl Write) -> io::Result<()> { - self.write_single_color_section(w, "line_indicator", self.fg.normal, self.bg.line_indicator) - } - - fn write_hints_section(&self, w: &mut impl Write) -> io::Result<()> { - writeln!(w)?; - writeln!(w, "[colors.hints]")?; - writeln!( - w, - r#"start = {{ foreground = "{fg}", background = "{bg}" }}"#, - fg = self.color(self.fg.hint_head), - bg = self.color(self.bg.hint_head), - )?; - writeln!( - w, - r#"end = {{ foreground = "{fg}", background = "{bg}" }}"#, - fg = self.color(self.fg.hint_tail), - bg = self.color(self.bg.hint_tail), - ) - } - - fn write_to(&self, w: &mut impl Write) -> io::Result<()> { - self.write_header_comment(w)?; - self.write_primary_section(w)?; - for colors in [&self.dim, &self.normal, &self.bright] { - self.write_ansi_colors_section(w, colors)?; - } - self.write_search_section(w)?; - self.write_footer_bar_section(w)?; - self.write_line_indicator_section(w)?; - self.write_hints_section(w) - } -} - fn write_to_files(dir: &str) -> Result<()> { let palette = Palette::default(); diff --git a/gen/src/palette.rs b/gen/src/palette.rs new file mode 100644 index 0000000..a48586b --- /dev/null +++ b/gen/src/palette.rs @@ -0,0 +1,130 @@ +use std::collections::HashMap; +use std::fmt::Display; +use std::ops::Deref; + +#[derive(Debug, PartialEq)] +pub struct Color { + pub gui: ColorCode<&'static str>, + pub cterm: ColorCode, +} + +#[derive(Debug, PartialEq)] +pub enum ColorCode { + Normal(T), + Contrast(T, T), +} + +impl ColorCode { + pub fn normal(&self) -> &T { + match self { + ColorCode::Normal(c) => c, + ColorCode::Contrast(h, _) => h, + } + } +} + +type Colors = HashMap<&'static str, Color>; + +#[derive(Debug)] +pub struct Palette(Colors); + +impl Deref for Palette { + type Target = Colors; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for Palette { + fn from(m: Colors) -> Self { + Self(m) + } +} + +impl Default for Palette { + #[rustfmt::skip] + fn default() -> Self { + use ColorCode::{Normal, Contrast}; + + let mut table = HashMap::new(); + let mut color = |name, gui, cterm| { + assert_eq!(table.insert(name, Color { gui, cterm }), None); + }; + + color("bg", Contrast("#132132", "#334152"), Normal(233)); + color("bgweaker", Contrast("#213243", "#3a4b5c"), Normal(235)); + color("bgemphasis", Normal("#3a4b5c"), Normal(235)); + color("bglight", Normal("#435060"), Normal(236)); + color("bgstrong", Normal("#536273"), Normal(238)); + color("light", Normal("#646f7c"), Normal(60)); + color("fg", Normal("#fffeeb"), Contrast(231, 230)); + color("hiddenfg", Normal("#607080"), Normal(60)); + color("weakfg", Normal("#8d9eb2"), Normal(103)); + color("weakerfg", Normal("#788898"), Normal(102)); + color("black", Normal("#111e25"), Normal(233)); + color("gray", Normal("#545f6e"), Normal(59)); + color("white", Normal("#ffffff"), Normal(231)); + color("nasu", Normal("#605779"), Normal(61)); + color("fuchsia", Normal("#b9a5cf"), Normal(183)); + color("purple", Normal("#e7d5ff"), Normal(189)); + color("yaezakura", Normal("#70495d"), Normal(95)); + color("sakura", Normal("#a9667a"), Normal(132)); + color("kakezakura", Normal("#e996aa"), Normal(175)); + color("palepink", Normal("#e7c6b7"), Normal(181)); + color("mikan", Normal("#fb8965"), Normal(209)); + color("orange", Normal("#f0aa8a"), Normal(216)); + color("darkgreen", Normal("#5f8770"), Normal(65)); + color("green", Normal("#a9dd9d"), Normal(150)); + color("lime", Normal("#c9fd88"), Normal(149)); + color("blue", Normal("#7098e6"), Normal(69)); + color("paleblue", Normal("#98b8e6"), Normal(111)); + color("cloudy", Normal("#90aecb"), Normal(75)); + color("skyblue", Normal("#a8d2eb"), Normal(153)); + color("sunny", Normal("#b8e2fb"), Normal(195)); + color("yellow", Normal("#f0eaaa"), Normal(229)); + color("gold", Normal("#fedf81"), Normal(222)); + color("dullgold", Normal("#b6955b"), Normal(221)); + color("darkgold", Contrast("#484000", "#685800"), Normal(58)); + color("mildred", Normal("#ab6560"), Normal(167)); + color("red", Normal("#fd8489"), Normal(210)); + color("crimson", Normal("#ff6a6f"), Normal(203)); + color("darkblue", Normal("#00091e"), Normal(235)); + color("whitepink", Normal("#ebeadb"), Normal(224)); + color("whitegreen", Normal("#eaf0aa"), Normal(194)); + color("whiteblue", Normal("#d8e2f0"), Normal(195)); + color("whitered", Normal("#ffbfaf"), Normal(217)); + color("inu", Normal("#ddbc96"), Normal(180)); + + Self(table) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use regex::Regex; + + #[test] + fn test_color_code() { + assert_eq!(*ColorCode::Normal(10).normal(), 10); + assert_eq!(*ColorCode::Contrast(10, 20).normal(), 10); + } + + #[test] + fn test_hex_color_format() { + let palette = Palette::default(); + let re = Regex::new(r"^#[[:xdigit:]]{6}$").unwrap(); + for (name, c) in palette.iter() { + match c.gui { + ColorCode::Normal(c) => { + assert!(re.is_match(c), "'{c}' is invalid color code at '{name}'"); + } + ColorCode::Contrast(c1, c2) => { + assert!(re.is_match(c1), "'{c1}' is invalid color code at '{name}'"); + assert!(re.is_match(c2), "'{c2}' is invalid color code at '{name}'"); + } + } + } + } +} diff --git a/gen/src/test.rs b/gen/src/test.rs deleted file mode 100644 index 6727f79..0000000 --- a/gen/src/test.rs +++ /dev/null @@ -1,430 +0,0 @@ -use super::*; -use regex::Regex; -use std::collections::{HashMap, HashSet}; -use std::str; -use toml_edit::{DocumentMut, Item as TomlItem, Value as TomlValue}; - -#[test] -fn test_color_code() { - assert_eq!(*ColorCode::Normal(10).normal(), 10); - assert_eq!(*ColorCode::Contrast(10, 20).normal(), 10); -} - -#[test] -fn test_write_header() { - let palette = Palette(HashMap::new()); - let w = Colorscheme::new(&palette); - let mut out = vec![]; - w.write_header(&mut out).unwrap(); - let rendered = str::from_utf8(&out).unwrap(); - assert!(rendered.starts_with(r#"" spring-night: Calm-colored dark color scheme"#)); - assert!(rendered.contains("let g:colors_name = 'spring-night'")); -} - -#[test] -fn test_write_contrast_color_variables() { - let palette = Palette(HashMap::new()); - let w = Colorscheme::new(&palette); - let mut out = vec![]; - w.write_contrast_color_variables(&mut out).unwrap(); - assert_eq!(str::from_utf8(&out).unwrap(), "\n"); - - let mut m = HashMap::new(); - m.insert( - "hi", - Color { - gui: ColorCode::Normal("#123456"), - cterm: ColorCode::Contrast(12, 34), - }, - ); - m.insert( - "hello", - Color { - gui: ColorCode::Contrast("#123456", "#7890ab"), - cterm: ColorCode::Contrast(123, 234), - }, - ); - m.insert( - "hey", - Color { - gui: ColorCode::Normal("#123456"), - cterm: ColorCode::Normal(123), - }, - ); - m.insert( - "goodbye", - Color { - gui: ColorCode::Contrast("#000000", "#ffffff"), - cterm: ColorCode::Normal(123), - }, - ); - let palette = Palette(m); - let w = Colorscheme::new(&palette); - let mut out = vec![]; - w.write_contrast_color_variables(&mut out).unwrap(); - for (actual, expected) in [ - "let s:goodbye_gui = g:spring_night_high_contrast ? '#000000' : '#ffffff'", - "let s:hello_gui = g:spring_night_high_contrast ? '#123456' : '#7890ab'", - "let s:hello_cterm = g:spring_night_high_contrast ? 123 : 234", - "let s:hi_cterm = g:spring_night_high_contrast ? 12 : 34", - "", - ] - .iter() - .zip(str::from_utf8(&out).unwrap().lines()) - { - assert_eq!(*actual, expected); - } -} - -#[test] -fn test_write_highlight() { - #[rustfmt::skip] - let testcases = vec![ - ((None, None, None, HiAttr::Nothing), 0, "hi HL term=NONE"), - ((Some("n"), None, None, HiAttr::Nothing), 0, "hi HL term=NONE guifg=#123456 ctermfg=123"), - ((None, Some("n"), None, HiAttr::Nothing), 0, "hi HL term=NONE guibg=#123456 ctermbg=123"), - ((Some("n"), Some("n"), None, HiAttr::Nothing), 0, "hi HL term=NONE guifg=#123456 ctermfg=123 guibg=#123456 ctermbg=123"), - ((None, None, None, HiAttr::Bold), 0, "exe 'hi' 'HL term=NONE' s:bold_attr"), - ((None, None, None, HiAttr::Italic), 0, "exe 'hi' 'HL term=NONE' s:italic_attr"), - ((None, None, None, HiAttr::Underline), 0, "hi HL term=NONE gui=underline cterm=underline"), - ((None, None, None, HiAttr::CommentItalic), 0, "exe 'hi' 'HL term=NONE' g:spring_night_italic_comments ? s:italic_attr : ''"), - ((None, None, None, HiAttr::Undercurl), 0, "exe 'hi' 'HL term=NONE' s:undercurl_attr"), - ((Some("c"), None, None, HiAttr::Nothing), 0, "exe 'hi' 'HL term=NONE' 'guifg='.s:c_gui 'ctermfg='.s:c_cterm"), - ((None, Some("c"), None, HiAttr::Nothing), 0, "exe 'hi' 'HL term=NONE' 'guibg='.s:c_gui 'ctermbg='.s:c_cterm"), - ((Some("c"), Some("c"), None, HiAttr::Underline), 0, "exe 'hi' 'HL term=NONE' 'guifg='.s:c_gui 'ctermfg='.s:c_cterm 'guibg='.s:c_gui 'ctermbg='.s:c_cterm 'gui=underline cterm=underline'"), - ((None, None, Some("n"), HiAttr::Nothing), 0, "hi HL term=NONE guisp=#123456"), - ((None, None, Some("n"), HiAttr::Undercurl), 0, "exe 'hi' 'HL term=NONE' 'guisp=#123456' s:undercurl_attr"), - ((None, None, None, HiAttr::Nothing), 1, " hi HL term=NONE"), - ((None, None, None, HiAttr::Undercurl), 1, " exe 'hi' 'HL term=NONE' s:undercurl_attr"), - ]; - - for ((fg, bg, sp, attr), indent, expected) in testcases { - let cmd = HiCommand { - name: "HL", - fg, - bg, - sp, - attr, - }; - let mut m = HashMap::new(); - m.insert( - "n", - Color { - gui: ColorCode::Normal("#123456"), - cterm: ColorCode::Normal(123), - }, - ); - m.insert( - "c", - Color { - gui: ColorCode::Contrast("#123456", "#7890ab"), - cterm: ColorCode::Contrast(123, 234), - }, - ); - let palette = Palette(m); - let w = Colorscheme::new(&palette); - let mut out = vec![]; - w.write_hi_command(&mut out, &cmd, indent).unwrap(); - assert_eq!(str::from_utf8(&out).unwrap(), format!("{}\n", expected)); - } - - // Edge case - let palette = Palette(HashMap::new()); - let mut w = Colorscheme::new(&palette); - w.highlights = &[]; - let mut out = vec![]; - w.write_highlights(&mut out).unwrap(); - assert_eq!(str::from_utf8(&out).unwrap(), "\n"); -} - -#[test] -fn test_write_highlights() { - const fn cmd() -> HiCommand { - HiCommand { - name: "HL", - fg: None, - bg: None, - sp: None, - attr: HiAttr::Nothing, - } - } - - let palette = Palette(HashMap::new()); - let mut w = Colorscheme::new(&palette); - let fixed = &[Highlight::Fixed(cmd())]; - w.highlights = fixed; - let mut out = vec![]; - w.write_highlights(&mut out).unwrap(); - assert_eq!(str::from_utf8(&out).unwrap(), "hi HL term=NONE\n\n"); - - let dynamic = &[Highlight::Dynamic { - gui: cmd(), - term: cmd(), - }]; - let palette = Palette(HashMap::new()); - let mut w = Colorscheme::new(&palette); - w.highlights = dynamic; - let mut out = vec![]; - w.write_highlights(&mut out).unwrap(); - assert_eq!( - str::from_utf8(&out).unwrap().lines().collect::>(), - vec![ - "if s:gui_running", - " hi HL term=NONE", - "else", - " hi HL term=NONE", - "endif", - "", - ], - ); -} - -#[test] -fn test_write_term_colors() { - let mut m = HashMap::new(); - m.insert( - "normal", - Color { - gui: ColorCode::Normal("#123456"), - cterm: ColorCode::Normal(123), - }, - ); - m.insert( - "contrast", - Color { - gui: ColorCode::Contrast("#000000", "#ffffff"), - cterm: ColorCode::Contrast(1, 2), - }, - ); - let palette = Palette(m); - let mut w = Colorscheme::new(&palette); - w.term_colors = [ - "normal", "contrast", "normal", "contrast", "normal", "contrast", "normal", "contrast", - "normal", "contrast", "normal", "contrast", "normal", "contrast", "normal", "contrast", - ]; - let mut out = vec![]; - w.write_term_colors(&mut out).unwrap(); - let rendered = str::from_utf8(&out).unwrap(); - assert!(rendered.contains("let g:terminal_color_0 = '#123456'")); - assert!(rendered.contains("let g:terminal_color_1 = '#000000'")); - assert!(rendered.contains("let g:terminal_color_0 = 123")); - assert!(rendered.contains("let g:terminal_color_1 = 1")); - assert!(rendered.contains("let g:terminal_ansi_colors = ['#123456','#000000','#123456','#000000','#123456','#000000','#123456','#000000','#123456','#000000','#123456','#000000','#123456','#000000','#123456','#000000']")); -} - -#[test] -fn test_colorscheme_writer() { - let palette = Palette::default(); - let w = Colorscheme::new(&palette); - - // Check duplicate highlights - let mut unique_check = HashSet::new(); - for hl in w.highlights { - let name = match hl { - Highlight::Fixed(h) => h.name, - Highlight::Dynamic { gui, term } => { - assert_eq!(gui.name, term.name); - gui.name - } - }; - assert!(unique_check.insert(name), "Duplicate highlight '{}'", name); - } - - // Check terminal colors are correct - for tc in &w.term_colors { - assert!( - w.palette.contains_key(tc), - "Terminal color '{tc}' is not present in color names", - ); - } - - // Check color code is correct - let re = Regex::new(r"^#[[:xdigit:]]{6}$").unwrap(); - for (name, c) in w.palette.iter() { - match c.gui { - ColorCode::Normal(c) => { - assert!(re.is_match(c), "'{c}' is invalid color code at '{name}'"); - } - ColorCode::Contrast(c1, c2) => { - assert!(re.is_match(c1), "'{c1}' is invalid color code at '{name}'"); - assert!(re.is_match(c2), "'{c2}' is invalid color code at '{name}'"); - } - } - } -} - -#[test] -fn test_write_airline_theme() { - let mut m = HashMap::new(); - m.insert( - "color1", - Color { - gui: ColorCode::Normal("#123456"), - cterm: ColorCode::Normal(123), - }, - ); - m.insert( - "color2", - Color { - gui: ColorCode::Contrast("#000000", "#ffffff"), - cterm: ColorCode::Contrast(1, 2), - }, - ); - // Userd for accents - m.insert( - "red", - Color { - gui: ColorCode::Normal("#ff0000"), - cterm: ColorCode::Normal(123), - }, - ); - - let palette = Palette(m); - let w = AirlineTheme { - palette: &palette, - modes: { - let mut m = HashMap::new(); - m.insert( - "normal", - AirlineModeColors { - label: ("color1", "color2"), - info: ("color2", "color1"), - main: ("color2", "color2"), - modified: Some("color1"), - modified_main: Some("color1"), - }, - ); - m.insert( - "insert", - AirlineModeColors { - label: ("color2", "color1"), - info: ("color1", "color2"), - main: ("color1", "color1"), - modified: Some("color1"), - modified_main: None, - }, - ); - m.insert( - "visual", - AirlineModeColors { - label: ("color1", "color1"), - info: ("color2", "color2"), - main: ("color1", "color1"), - modified: None, - modified_main: None, - }, - ); - m.insert( - "replace", - AirlineModeColors { - label: ("color1", "color1"), - info: ("color2", "color2"), - main: ("color1", "color1"), - modified: Some("color1"), - modified_main: None, - }, - ); - m.insert( - "inactive", - AirlineModeColors { - label: ("color1", "color1"), - info: ("color2", "color2"), - main: ("color1", "color1"), - modified: Some("color2"), - modified_main: Some("color2"), - }, - ); - m - }, - paste: "color1", - info_mod: "color2", - error: ("color1", "color2"), - warning: ("color2", "color1"), - }; - - let mut out = vec![]; - w.write_to(&mut out).unwrap(); - let rendered = str::from_utf8(&out).unwrap(); - - let re_var = Regex::new(r"^let g:airline#themes#spring_night#palette\.(\w+) =").unwrap(); - let re_palette = - Regex::new(r"^\\\s+'(red|airline_(a|b|c|x|y|z|error|warning))': \[('(#[[:xdigit:]]{6})?',\s*){2}((\d{1,3}|''),\s*){2}''\]").unwrap(); - for line in rendered.lines() { - if line.starts_with("let g:") { - match re_var.captures(line) { - Some(found) => { - let mode = &found[1]; - assert!( - w.modes.keys().any(|m| *m == mode - || format!("{}_modified", m) == mode - || format!("{}_paste", m) == mode - || "accents" == mode), - "Unknown mode: {}", - mode - ); - } - None => assert!( - line == "let g:airline#themes#spring_night#palette = {}", - "Invalid variable definition: {}", - line - ), - } - } else if line.starts_with("\\ ") { - assert!(re_palette.is_match(line), "Invalid color palette: {}", line); - } - } -} - -#[test] -fn test_default_alacritty_theme() { - let p = Palette::default(); - let w = AlacrittyTheme::new(&p); - let mut out = vec![]; - w.write_to(&mut out).unwrap(); - let src = str::from_utf8(&out).unwrap(); - let doc: DocumentMut = src.parse().expect(src); - - let hex_color = Regex::new(r"^#[[:xdigit:]]{6}$").unwrap(); - let assert_color = move |path: &str| { - let mut item = doc.as_item(); - for idx in path.split('.') { - item = item.get(idx).expect(path); - } - let TomlItem::Value(v) = item else { - panic!("{path} is not a primitive: {item:?}"); - }; - let TomlValue::String(s) = v else { - panic!("{path} is not a string: {v:?}"); - }; - let s = s.value().as_str(); - assert!(hex_color.is_match(s), "{path} is not a hex color: {s:?}"); - }; - - for path in [ - "foreground", - "background", - "dim_foreground", - "bright_foreground", - ] { - assert_color(&format!("colors.primary.{path}")); - } - - for mode in ["dim", "normal", "bright"] { - for color in [ - "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", - ] { - assert_color(&format!("colors.{mode}.{color}")); - } - } - - for section in [ - "search.matches", - "search.focused_match", - "footer_bar", - "line_indicator", - ] { - for color in ["foreground", "background"] { - assert_color(&format!("colors.{section}.{color}")); - } - } -}