Skip to content

Commit

Permalink
feat: support themes directory (#1577)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jaeheonji committed Jul 24, 2022
1 parent eacac9f commit 09481aa
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 10 deletions.
4 changes: 2 additions & 2 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,8 @@ pub(crate) fn start_client(opts: CliArgs) {
options,
})) = opts.command.clone()
{
let config_options = match options {
Some(SessionCommand::Options(o)) => config_options.merge_from_cli(o.into()),
let config_options = match options.as_deref() {
Some(SessionCommand::Options(o)) => config_options.merge_from_cli(o.to_owned().into()),
None => config_options,
};

Expand Down
2 changes: 1 addition & 1 deletion zellij-utils/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions zellij-utils/src/input/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)]
Expand All @@ -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,
Expand Down
9 changes: 9 additions & 0 deletions zellij-utils/src/input/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand All @@ -156,6 +161,7 @@ impl Options {
default_shell,
default_layout,
layout_dir,
theme_dir,
mouse_mode,
pane_frames,
mirror_session,
Expand Down Expand Up @@ -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);
Expand All @@ -209,6 +216,7 @@ impl Options {
default_shell,
default_layout,
layout_dir,
theme_dir,
mouse_mode,
pane_frames,
mirror_session,
Expand Down Expand Up @@ -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,
Expand Down
51 changes: 48 additions & 3 deletions zellij-utils/src/input/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -182,3 +222,8 @@ impl From<PaletteColorFromYaml> for PaletteColor {
}
}
}

// The unit test location.
#[cfg(test)]
#[path = "./unit/theme_test.rs"]
mod theme_test;
16 changes: 16 additions & 0 deletions zellij-utils/src/input/unit/fixtures/themes/dracula.yaml
Original file line number Diff line number Diff line change
@@ -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]
16 changes: 16 additions & 0 deletions zellij-utils/src/input/unit/fixtures/themes/nord.yaml
Original file line number Diff line number Diff line change
@@ -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

22 changes: 22 additions & 0 deletions zellij-utils/src/input/unit/theme_test.rs
Original file line number Diff line number Diff line change
@@ -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());
}
34 changes: 33 additions & 1 deletion zellij-utils/src/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::{
config::{Config, ConfigError},
layout::{LayoutFromYaml, LayoutFromYamlIntermediate},
options::Options,
theme::ThemesFromYaml,
},
};
use clap::{Args, IntoApp};
Expand Down Expand Up @@ -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(())
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand Down

0 comments on commit 09481aa

Please sign in to comment.