summaryrefslogtreecommitdiffstats
path: root/zellij-utils
diff options
context:
space:
mode:
authorJae-Heon Ji <32578710+jaeheonji@users.noreply.github.com>2022-07-24 21:30:58 +0900
committerGitHub <noreply@github.com>2022-07-24 21:30:58 +0900
commit09481aac1986b7486e92d9dcd2baeb96a5d71e6e (patch)
treec64e376d003e9fb01d5f5567854e4bb5d816243d /zellij-utils
parenteacac9fe67d6cebb27b7f5bcff4f35ca399b13ce (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.rs2
-rw-r--r--zellij-utils/src/input/config.rs6
-rw-r--r--zellij-utils/src/input/options.rs9
-rw-r--r--zellij-utils/src/input/theme.rs51
-rw-r--r--zellij-utils/src/input/unit/fixtures/themes/dracula.yaml16
-rw-r--r--zellij-utils/src/input/unit/fixtures/themes/nord.yaml16
-rw-r--r--zellij-utils/src/input/unit/theme_test.rs22
-rw-r--r--zellij-utils/src/setup.rs34
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();