diff options
author | Jae-Heon Ji <32578710+jaeheonji@users.noreply.github.com> | 2022-07-24 21:30:58 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-24 21:30:58 +0900 |
commit | 09481aac1986b7486e92d9dcd2baeb96a5d71e6e (patch) | |
tree | c64e376d003e9fb01d5f5567854e4bb5d816243d /zellij-utils | |
parent | eacac9fe67d6cebb27b7f5bcff4f35ca399b13ce (diff) |
feat: support `themes` directory (#1577)
* feat: add serde struct for theme file
* feat: update client to load the theme palette
* feat: merge themes in the setup phase
* chore: delete debug message in test
* feat: add theme_dir options
* fix: boxing large enum variant
Diffstat (limited to 'zellij-utils')
-rw-r--r-- | zellij-utils/src/cli.rs | 2 | ||||
-rw-r--r-- | zellij-utils/src/input/config.rs | 6 | ||||
-rw-r--r-- | zellij-utils/src/input/options.rs | 9 | ||||
-rw-r--r-- | zellij-utils/src/input/theme.rs | 51 | ||||
-rw-r--r-- | zellij-utils/src/input/unit/fixtures/themes/dracula.yaml | 16 | ||||
-rw-r--r-- | zellij-utils/src/input/unit/fixtures/themes/nord.yaml | 16 | ||||
-rw-r--r-- | zellij-utils/src/input/unit/theme_test.rs | 22 | ||||
-rw-r--r-- | zellij-utils/src/setup.rs | 34 |
8 files changed, 148 insertions, 8 deletions
diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs index c8a3ba1d0..a947f9434 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -91,7 +91,7 @@ pub enum Sessions { /// Change the behaviour of zellij #[clap(subcommand, name = "options")] - options: Option<SessionCommand>, + options: Option<Box<SessionCommand>>, }, /// Kill the specific session diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs index 4b9f4100b..6d3946369 100644 --- a/zellij-utils/src/input/config.rs +++ b/zellij-utils/src/input/config.rs @@ -11,7 +11,7 @@ use std::convert::{TryFrom, TryInto}; use super::keybinds::{Keybinds, KeybindsFromYaml}; use super::options::Options; use super::plugins::{PluginsConfig, PluginsConfigError, PluginsConfigFromYaml}; -use super::theme::{ThemesFromYaml, UiConfigFromYaml}; +use super::theme::{ThemesFromYamlIntermediate, UiConfigFromYaml}; use crate::cli::{CliArgs, Command}; use crate::envs::EnvironmentVariablesFromYaml; use crate::setup; @@ -26,7 +26,7 @@ pub struct ConfigFromYaml { #[serde(flatten)] pub options: Option<Options>, pub keybinds: Option<KeybindsFromYaml>, - pub themes: Option<ThemesFromYaml>, + pub themes: Option<ThemesFromYamlIntermediate>, #[serde(flatten)] pub env: Option<EnvironmentVariablesFromYaml>, #[serde(default)] @@ -39,7 +39,7 @@ pub struct ConfigFromYaml { pub struct Config { pub keybinds: Keybinds, pub options: Options, - pub themes: Option<ThemesFromYaml>, + pub themes: Option<ThemesFromYamlIntermediate>, pub plugins: PluginsConfig, pub ui: Option<UiConfigFromYaml>, pub env: EnvironmentVariablesFromYaml, diff --git a/zellij-utils/src/input/options.rs b/zellij-utils/src/input/options.rs index 8af9c8a4d..460712efd 100644 --- a/zellij-utils/src/input/options.rs +++ b/zellij-utils/src/input/options.rs @@ -59,6 +59,10 @@ pub struct Options { /// subdirectory of config dir #[clap(long, value_parser)] pub layout_dir: Option<PathBuf>, + /// Set the theme_dir, defaults to + /// subdirectory of config dir + #[clap(long, value_parser)] + pub theme_dir: Option<PathBuf>, #[clap(long, value_parser)] #[serde(default)] /// Set the handling of mouse events (true or false) @@ -139,6 +143,7 @@ impl Options { let default_shell = other.default_shell.or_else(|| self.default_shell.clone()); let default_layout = other.default_layout.or_else(|| self.default_layout.clone()); let layout_dir = other.layout_dir.or_else(|| self.layout_dir.clone()); + let theme_dir = other.theme_dir.or_else(|| self.theme_dir.clone()); let theme = other.theme.or_else(|| self.theme.clone()); let on_force_close = other.on_force_close.or(self.on_force_close); let scroll_buffer_size = other.scroll_buffer_size.or(self.scroll_buffer_size); @@ -156,6 +161,7 @@ impl Options { default_shell, default_layout, layout_dir, + theme_dir, mouse_mode, pane_frames, mirror_session, @@ -192,6 +198,7 @@ impl Options { let default_shell = other.default_shell.or_else(|| self.default_shell.clone()); let default_layout = other.default_layout.or_else(|| self.default_layout.clone()); let layout_dir = other.layout_dir.or_else(|| self.layout_dir.clone()); + let theme_dir = other.theme_dir.or_else(|| self.theme_dir.clone()); let theme = other.theme.or_else(|| self.theme.clone()); let on_force_close = other.on_force_close.or(self.on_force_close); let scroll_buffer_size = other.scroll_buffer_size.or(self.scroll_buffer_size); @@ -209,6 +216,7 @@ impl Options { default_shell, default_layout, layout_dir, + theme_dir, mouse_mode, pane_frames, mirror_session, @@ -262,6 +270,7 @@ impl From<CliOptions> for Options { default_shell: opts.default_shell, default_layout: opts.default_layout, layout_dir: opts.layout_dir, + theme_dir: opts.theme_dir, mouse_mode: opts.mouse_mode, pane_frames: opts.pane_frames, mirror_session: opts.mirror_session, diff --git a/zellij-utils/src/input/theme.rs b/zellij-utils/src/input/theme.rs index fc8329a7e..b148e0d43 100644 --- a/zellij-utils/src/input/theme.rs +++ b/zellij-utils/src/input/theme.rs @@ -2,15 +2,26 @@ use serde::{ de::{Error, Visitor}, Deserialize, Deserializer, Serialize, Serializer, }; + +use std::fs::File; +use std::io::Read; +use std::path::Path; use std::{collections::HashMap, fmt}; -use super::options::Options; +use super::{config::ConfigError, options::Options}; use crate::data::{Palette, PaletteColor}; use crate::shared::detect_theme_hue; /// Intermediate deserialization of themes #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub struct ThemesFromYaml(HashMap<String, Theme>); +pub struct ThemesFromYamlIntermediate(HashMap<String, Theme>); + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct ThemesFromYaml { + pub themes: ThemesFromYamlIntermediate, +} + +type ThemesFromYamlResult = Result<ThemesFromYaml, ConfigError>; #[derive(Debug, Default, Clone, Copy, PartialEq, Deserialize, Serialize)] pub struct UiConfigFromYaml { @@ -23,7 +34,12 @@ pub struct FrameConfigFromYaml { } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -struct Theme { +struct ThemeFromYaml { + palette: PaletteFromYaml, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct Theme { #[serde(flatten)] palette: PaletteFromYaml, } @@ -125,6 +141,30 @@ impl Default for PaletteColorFromYaml { } impl ThemesFromYaml { + pub fn from_path(theme_path: &Path) -> ThemesFromYamlResult { + let mut theme_file = File::open(&theme_path) + .or_else(|_| File::open(&theme_path.with_extension("yaml"))) + .map_err(|e| ConfigError::IoPath(e, theme_path.into()))?; + + let mut theme = String::new(); + theme_file.read_to_string(&mut theme)?; + + let theme: ThemesFromYaml = match serde_yaml::from_str(&theme) { + Err(e) => return Err(ConfigError::Serde(e)), + Ok(theme) => theme, + }; + + Ok(theme) + } +} + +impl From<ThemesFromYaml> for ThemesFromYamlIntermediate { + fn from(yaml: ThemesFromYaml) -> Self { + yaml.themes + } +} + +impl ThemesFromYamlIntermediate { pub fn theme_config(self, opts: &Options) -> Option<Palette> { let mut from_yaml = self; match &opts.theme { @@ -182,3 +222,8 @@ impl From<PaletteColorFromYaml> for PaletteColor { } } } + +// The unit test location. +#[cfg(test)] +#[path = "./unit/theme_test.rs"] +mod theme_test; diff --git a/zellij-utils/src/input/unit/fixtures/themes/dracula.yaml b/zellij-utils/src/input/unit/fixtures/themes/dracula.yaml new file mode 100644 index 000000000..b9c8a5afb --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/themes/dracula.yaml @@ -0,0 +1,16 @@ +# Dracula Theme + +themes: + dracula: + # From https://github.com/dracula/zellij + bg: [40, 42, 54] + red: [255, 85, 85] + green: [80, 250, 123] + yellow: [241, 250, 140] + blue: [98, 114, 164] + magenta: [255, 121, 198] + orange: [255, 184, 108] + fg: [248, 248, 242] + cyan: [139, 233, 253] + black: [0, 0, 0] + white: [255, 255, 255] diff --git a/zellij-utils/src/input/unit/fixtures/themes/nord.yaml b/zellij-utils/src/input/unit/fixtures/themes/nord.yaml new file mode 100644 index 000000000..61851ad20 --- /dev/null +++ b/zellij-utils/src/input/unit/fixtures/themes/nord.yaml @@ -0,0 +1,16 @@ +# Nord theme + +themes: + nord: + fg: [216, 222, 233] #D8DEE9 + bg: [46, 52, 64] #2E3440 + black: [59, 66, 82] #3B4252 + red: [191, 97, 106] #BF616A + green: [163, 190, 140] #A3BE8C + yellow: [235,203,139] #EBCB8B + blue: [129, 161, 193] #81A1C1 + magenta: [180, 142, 173] #B48EAD + cyan: [136, 192, 208] #88C0D0 + white: [229, 233, 240] #E5E9F0 + orange: [208, 135, 112] #D08770 + diff --git a/zellij-utils/src/input/unit/theme_test.rs b/zellij-utils/src/input/unit/theme_test.rs new file mode 100644 index 000000000..ad5b480af --- /dev/null +++ b/zellij-utils/src/input/unit/theme_test.rs @@ -0,0 +1,22 @@ +use super::super::theme::*; +use std::path::PathBuf; + +fn theme_test_dir(theme: String) -> PathBuf { + let root = Path::new(env!("CARGO_MANIFEST_DIR")); + let theme_dir = root.join("src/input/unit/fixtures/themes"); + theme_dir.join(theme) +} + +#[test] +fn dracula_theme_is_ok() { + let path = theme_test_dir("dracula.yaml".into()); + let theme = ThemesFromYaml::from_path(&path); + assert!(theme.is_ok()); +} + +#[test] +fn no_theme_is_err() { + let path = theme_test_dir("nonexistent.yaml".into()); + let theme = ThemesFromYaml::from_path(&path); + assert!(theme.is_err()); +} diff --git a/zellij-utils/src/setup.rs b/zellij-utils/src/setup.rs index 9b2c36b23..209dc1897 100644 --- a/zellij-utils/src/setup.rs +++ b/zellij-utils/src/setup.rs @@ -8,6 +8,7 @@ use crate::{ config::{Config, ConfigError}, layout::{LayoutFromYaml, LayoutFromYamlIntermediate}, options::Options, + theme::ThemesFromYaml, }, }; use clap::{Args, IntoApp}; @@ -80,6 +81,10 @@ pub fn get_layout_dir(config_dir: Option<PathBuf>) -> Option<PathBuf> { config_dir.map(|dir| dir.join("layouts")) } +pub fn get_theme_dir(config_dir: Option<PathBuf>) -> Option<PathBuf> { + config_dir.map(|dir| dir.join("themes")) +} + pub fn dump_asset(asset: &[u8]) -> std::io::Result<()> { std::io::stdout().write_all(asset)?; Ok(()) @@ -213,7 +218,7 @@ impl Setup { ); }; - let config = if !clean { + let mut config = if !clean { match Config::try_from(opts) { Ok(config) => config, Err(e) => { @@ -244,6 +249,24 @@ impl Setup { }, }; + if let Some(theme_dir) = config_options + .theme_dir + .clone() + .or_else(|| get_theme_dir(opts.config_dir.clone().or_else(find_default_config_dir))) + { + if theme_dir.is_dir() { + for entry in (theme_dir.read_dir()?).flatten() { + if let Some(extension) = entry.path().extension() { + if extension == "yaml" || extension == "yml" { + if let Ok(themes) = ThemesFromYaml::from_path(&entry.path()) { + config.themes = config.themes.map(|t| t.merge(themes.into())); + } + } + } + } + } + } + if let Some(Command::Setup(ref setup)) = &opts.command { setup .from_cli_with_options(opts, &config_options) @@ -333,6 +356,10 @@ impl Setup { .layout_dir .clone() .or_else(|| get_layout_dir(config_dir.clone())); + let theme_dir = config_options + .theme_dir + .clone() + .or_else(|| get_theme_dir(config_dir.clone())); let system_data_dir = PathBuf::from(SYSTEM_DEFAULT_DATA_DIR_PREFIX).join("share/zellij"); let config_file = opts .config @@ -386,6 +413,11 @@ impl Setup { } else { message.push_str("[LAYOUT DIR]: Not Found\n"); } + if let Some(theme_dir) = theme_dir { + writeln!(&mut message, "[THEME DIR]: {:?}", theme_dir).unwrap(); + } else { + message.push_str("[THEME DIR]: Not Found\n"); + } writeln!(&mut message, "[SYSTEM DATA DIR]: {:?}", system_data_dir).unwrap(); writeln!(&mut message, "[ARROW SEPARATOR]: {}", ARROW_SEPARATOR).unwrap(); |