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,
|
||||
})) = 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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
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},
|
||||
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();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue