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
This commit is contained in:
parent
eacac9fe67
commit
09481aac19
9 changed files with 150 additions and 10 deletions
|
|
@ -265,8 +265,8 @@ pub(crate) fn start_client(opts: CliArgs) {
|
||||||
options,
|
options,
|
||||||
})) = opts.command.clone()
|
})) = opts.command.clone()
|
||||||
{
|
{
|
||||||
let config_options = match options {
|
let config_options = match options.as_deref() {
|
||||||
Some(SessionCommand::Options(o)) => config_options.merge_from_cli(o.into()),
|
Some(SessionCommand::Options(o)) => config_options.merge_from_cli(o.to_owned().into()),
|
||||||
None => config_options,
|
None => config_options,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ pub enum Sessions {
|
||||||
|
|
||||||
/// Change the behaviour of zellij
|
/// Change the behaviour of zellij
|
||||||
#[clap(subcommand, name = "options")]
|
#[clap(subcommand, name = "options")]
|
||||||
options: Option<SessionCommand>,
|
options: Option<Box<SessionCommand>>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Kill the specific session
|
/// Kill the specific session
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use std::convert::{TryFrom, TryInto};
|
||||||
use super::keybinds::{Keybinds, KeybindsFromYaml};
|
use super::keybinds::{Keybinds, KeybindsFromYaml};
|
||||||
use super::options::Options;
|
use super::options::Options;
|
||||||
use super::plugins::{PluginsConfig, PluginsConfigError, PluginsConfigFromYaml};
|
use super::plugins::{PluginsConfig, PluginsConfigError, PluginsConfigFromYaml};
|
||||||
use super::theme::{ThemesFromYaml, UiConfigFromYaml};
|
use super::theme::{ThemesFromYamlIntermediate, UiConfigFromYaml};
|
||||||
use crate::cli::{CliArgs, Command};
|
use crate::cli::{CliArgs, Command};
|
||||||
use crate::envs::EnvironmentVariablesFromYaml;
|
use crate::envs::EnvironmentVariablesFromYaml;
|
||||||
use crate::setup;
|
use crate::setup;
|
||||||
|
|
@ -26,7 +26,7 @@ pub struct ConfigFromYaml {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub options: Option<Options>,
|
pub options: Option<Options>,
|
||||||
pub keybinds: Option<KeybindsFromYaml>,
|
pub keybinds: Option<KeybindsFromYaml>,
|
||||||
pub themes: Option<ThemesFromYaml>,
|
pub themes: Option<ThemesFromYamlIntermediate>,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub env: Option<EnvironmentVariablesFromYaml>,
|
pub env: Option<EnvironmentVariablesFromYaml>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
@ -39,7 +39,7 @@ pub struct ConfigFromYaml {
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub keybinds: Keybinds,
|
pub keybinds: Keybinds,
|
||||||
pub options: Options,
|
pub options: Options,
|
||||||
pub themes: Option<ThemesFromYaml>,
|
pub themes: Option<ThemesFromYamlIntermediate>,
|
||||||
pub plugins: PluginsConfig,
|
pub plugins: PluginsConfig,
|
||||||
pub ui: Option<UiConfigFromYaml>,
|
pub ui: Option<UiConfigFromYaml>,
|
||||||
pub env: EnvironmentVariablesFromYaml,
|
pub env: EnvironmentVariablesFromYaml,
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,10 @@ pub struct Options {
|
||||||
/// subdirectory of config dir
|
/// subdirectory of config dir
|
||||||
#[clap(long, value_parser)]
|
#[clap(long, value_parser)]
|
||||||
pub layout_dir: Option<PathBuf>,
|
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)]
|
#[clap(long, value_parser)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
/// Set the handling of mouse events (true or false)
|
/// 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_shell = other.default_shell.or_else(|| self.default_shell.clone());
|
||||||
let default_layout = other.default_layout.or_else(|| self.default_layout.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 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 theme = other.theme.or_else(|| self.theme.clone());
|
||||||
let on_force_close = other.on_force_close.or(self.on_force_close);
|
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);
|
let scroll_buffer_size = other.scroll_buffer_size.or(self.scroll_buffer_size);
|
||||||
|
|
@ -156,6 +161,7 @@ impl Options {
|
||||||
default_shell,
|
default_shell,
|
||||||
default_layout,
|
default_layout,
|
||||||
layout_dir,
|
layout_dir,
|
||||||
|
theme_dir,
|
||||||
mouse_mode,
|
mouse_mode,
|
||||||
pane_frames,
|
pane_frames,
|
||||||
mirror_session,
|
mirror_session,
|
||||||
|
|
@ -192,6 +198,7 @@ impl Options {
|
||||||
let default_shell = other.default_shell.or_else(|| self.default_shell.clone());
|
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 default_layout = other.default_layout.or_else(|| self.default_layout.clone());
|
||||||
let layout_dir = other.layout_dir.or_else(|| self.layout_dir.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 theme = other.theme.or_else(|| self.theme.clone());
|
||||||
let on_force_close = other.on_force_close.or(self.on_force_close);
|
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);
|
let scroll_buffer_size = other.scroll_buffer_size.or(self.scroll_buffer_size);
|
||||||
|
|
@ -209,6 +216,7 @@ impl Options {
|
||||||
default_shell,
|
default_shell,
|
||||||
default_layout,
|
default_layout,
|
||||||
layout_dir,
|
layout_dir,
|
||||||
|
theme_dir,
|
||||||
mouse_mode,
|
mouse_mode,
|
||||||
pane_frames,
|
pane_frames,
|
||||||
mirror_session,
|
mirror_session,
|
||||||
|
|
@ -262,6 +270,7 @@ impl From<CliOptions> for Options {
|
||||||
default_shell: opts.default_shell,
|
default_shell: opts.default_shell,
|
||||||
default_layout: opts.default_layout,
|
default_layout: opts.default_layout,
|
||||||
layout_dir: opts.layout_dir,
|
layout_dir: opts.layout_dir,
|
||||||
|
theme_dir: opts.theme_dir,
|
||||||
mouse_mode: opts.mouse_mode,
|
mouse_mode: opts.mouse_mode,
|
||||||
pane_frames: opts.pane_frames,
|
pane_frames: opts.pane_frames,
|
||||||
mirror_session: opts.mirror_session,
|
mirror_session: opts.mirror_session,
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,26 @@ use serde::{
|
||||||
de::{Error, Visitor},
|
de::{Error, Visitor},
|
||||||
Deserialize, Deserializer, Serialize, Serializer,
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::path::Path;
|
||||||
use std::{collections::HashMap, fmt};
|
use std::{collections::HashMap, fmt};
|
||||||
|
|
||||||
use super::options::Options;
|
use super::{config::ConfigError, options::Options};
|
||||||
use crate::data::{Palette, PaletteColor};
|
use crate::data::{Palette, PaletteColor};
|
||||||
use crate::shared::detect_theme_hue;
|
use crate::shared::detect_theme_hue;
|
||||||
|
|
||||||
/// Intermediate deserialization of themes
|
/// Intermediate deserialization of themes
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
#[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)]
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct UiConfigFromYaml {
|
pub struct UiConfigFromYaml {
|
||||||
|
|
@ -23,7 +34,12 @@ pub struct FrameConfigFromYaml {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
struct Theme {
|
struct ThemeFromYaml {
|
||||||
|
palette: PaletteFromYaml,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct Theme {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
palette: PaletteFromYaml,
|
palette: PaletteFromYaml,
|
||||||
}
|
}
|
||||||
|
|
@ -125,6 +141,30 @@ impl Default for PaletteColorFromYaml {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThemesFromYaml {
|
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> {
|
pub fn theme_config(self, opts: &Options) -> Option<Palette> {
|
||||||
let mut from_yaml = self;
|
let mut from_yaml = self;
|
||||||
match &opts.theme {
|
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;
|
||||||
|
|
|
||||||
16
zellij-utils/src/input/unit/fixtures/themes/dracula.yaml
Normal file
16
zellij-utils/src/input/unit/fixtures/themes/dracula.yaml
Normal file
|
|
@ -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
zellij-utils/src/input/unit/fixtures/themes/nord.yaml
Normal file
16
zellij-utils/src/input/unit/fixtures/themes/nord.yaml
Normal file
|
|
@ -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
zellij-utils/src/input/unit/theme_test.rs
Normal file
22
zellij-utils/src/input/unit/theme_test.rs
Normal file
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ use crate::{
|
||||||
config::{Config, ConfigError},
|
config::{Config, ConfigError},
|
||||||
layout::{LayoutFromYaml, LayoutFromYamlIntermediate},
|
layout::{LayoutFromYaml, LayoutFromYamlIntermediate},
|
||||||
options::Options,
|
options::Options,
|
||||||
|
theme::ThemesFromYaml,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use clap::{Args, IntoApp};
|
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"))
|
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<()> {
|
pub fn dump_asset(asset: &[u8]) -> std::io::Result<()> {
|
||||||
std::io::stdout().write_all(asset)?;
|
std::io::stdout().write_all(asset)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -213,7 +218,7 @@ impl Setup {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let config = if !clean {
|
let mut config = if !clean {
|
||||||
match Config::try_from(opts) {
|
match Config::try_from(opts) {
|
||||||
Ok(config) => config,
|
Ok(config) => config,
|
||||||
Err(e) => {
|
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 {
|
if let Some(Command::Setup(ref setup)) = &opts.command {
|
||||||
setup
|
setup
|
||||||
.from_cli_with_options(opts, &config_options)
|
.from_cli_with_options(opts, &config_options)
|
||||||
|
|
@ -333,6 +356,10 @@ impl Setup {
|
||||||
.layout_dir
|
.layout_dir
|
||||||
.clone()
|
.clone()
|
||||||
.or_else(|| get_layout_dir(config_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 system_data_dir = PathBuf::from(SYSTEM_DEFAULT_DATA_DIR_PREFIX).join("share/zellij");
|
||||||
let config_file = opts
|
let config_file = opts
|
||||||
.config
|
.config
|
||||||
|
|
@ -386,6 +413,11 @@ impl Setup {
|
||||||
} else {
|
} else {
|
||||||
message.push_str("[LAYOUT DIR]: Not Found\n");
|
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, "[SYSTEM DATA DIR]: {:?}", system_data_dir).unwrap();
|
||||||
|
|
||||||
writeln!(&mut message, "[ARROW SEPARATOR]: {}", ARROW_SEPARATOR).unwrap();
|
writeln!(&mut message, "[ARROW SEPARATOR]: {}", ARROW_SEPARATOR).unwrap();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue