Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support themes directory #1577

Merged
merged 7 commits into from
Jul 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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