feature(layout): add layout config (#866)
feature(layout): add layout config (#866) * It is now possible to configure zellij through a layout: The config file and the layout file will be merged, on conflicting options the order is as follows: 1. config options `zellij options` 2. layout 3. config Example: ``` --- template: direction: Horizontal parts: - direction: Vertical body: true - direction: Vertical borderless: true split_size: Fixed: 1 run: plugin: location: "zellij:tab-bar" default_shell: fish ```
This commit is contained in:
parent
96315ed332
commit
347e02ea35
6 changed files with 336 additions and 15 deletions
|
|
@ -5,7 +5,7 @@ use std::io::{self, Read};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
use super::keybinds::{Keybinds, KeybindsFromYaml};
|
use super::keybinds::{Keybinds, KeybindsFromYaml};
|
||||||
|
|
@ -20,7 +20,7 @@ const DEFAULT_CONFIG_FILE_NAME: &str = "config.yaml";
|
||||||
type ConfigResult = Result<Config, ConfigError>;
|
type ConfigResult = Result<Config, ConfigError>;
|
||||||
|
|
||||||
/// Intermediate deserialization config struct
|
/// Intermediate deserialization config struct
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
pub struct ConfigFromYaml {
|
pub struct ConfigFromYaml {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub options: Option<Options>,
|
pub options: Option<Options>,
|
||||||
|
|
@ -151,6 +151,18 @@ impl Config {
|
||||||
let cfg = String::from_utf8(setup::DEFAULT_CONFIG.to_vec())?;
|
let cfg = String::from_utf8(setup::DEFAULT_CONFIG.to_vec())?;
|
||||||
Self::from_yaml(cfg.as_str())
|
Self::from_yaml(cfg.as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Merges two Config structs into one Config struct
|
||||||
|
/// `other` overrides `self`.
|
||||||
|
pub fn merge(&self, other: Self) -> Self {
|
||||||
|
//let themes = if let Some()
|
||||||
|
Self {
|
||||||
|
keybinds: self.keybinds.merge_keybinds(other.keybinds),
|
||||||
|
options: self.options.merge(other.options),
|
||||||
|
themes: None,
|
||||||
|
plugins: self.plugins.merge(other.plugins),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<ConfigFromYaml> for Config {
|
impl TryFrom<ConfigFromYaml> for Config {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ pub struct ModeKeybinds(HashMap<Key, Vec<Action>>);
|
||||||
|
|
||||||
/// Intermediate struct used for deserialisation
|
/// Intermediate struct used for deserialisation
|
||||||
/// Used in the config file.
|
/// Used in the config file.
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct KeybindsFromYaml {
|
pub struct KeybindsFromYaml {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
keybinds: HashMap<InputMode, Vec<KeyActionUnbind>>,
|
keybinds: HashMap<InputMode, Vec<KeyActionUnbind>>,
|
||||||
|
|
@ -25,7 +25,7 @@ pub struct KeybindsFromYaml {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Intermediate enum used for deserialisation
|
/// Intermediate enum used for deserialisation
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
enum KeyActionUnbind {
|
enum KeyActionUnbind {
|
||||||
KeyAction(KeyActionFromYaml),
|
KeyAction(KeyActionFromYaml),
|
||||||
|
|
@ -40,21 +40,21 @@ struct KeyActionUnbindFromYaml {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Intermediate struct used for deserialisation
|
/// Intermediate struct used for deserialisation
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct KeyActionFromYaml {
|
pub struct KeyActionFromYaml {
|
||||||
action: Vec<Action>,
|
action: Vec<Action>,
|
||||||
key: Vec<Key>,
|
key: Vec<Key>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Intermediate struct used for deserialisation
|
/// Intermediate struct used for deserialisation
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
struct UnbindFromYaml {
|
struct UnbindFromYaml {
|
||||||
unbind: Unbind,
|
unbind: Unbind,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List of keys, for which to disable their respective default actions
|
/// List of keys, for which to disable their respective default actions
|
||||||
/// `All` is a catch all, and will disable the default actions for all keys.
|
/// `All` is a catch all, and will disable the default actions for all keys.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
enum Unbind {
|
enum Unbind {
|
||||||
// This is the correct order, don't rearrange!
|
// This is the correct order, don't rearrange!
|
||||||
|
|
@ -168,7 +168,7 @@ impl Keybinds {
|
||||||
|
|
||||||
/// Merges two Keybinds structs into one Keybinds struct
|
/// Merges two Keybinds structs into one Keybinds struct
|
||||||
/// `other` overrides the ModeKeybinds of `self`.
|
/// `other` overrides the ModeKeybinds of `self`.
|
||||||
fn merge_keybinds(&self, other: Keybinds) -> Keybinds {
|
pub fn merge_keybinds(&self, other: Keybinds) -> Keybinds {
|
||||||
let mut keybinds = Keybinds::new();
|
let mut keybinds = Keybinds::new();
|
||||||
|
|
||||||
for mode in InputMode::iter() {
|
for mode in InputMode::iter() {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,10 @@ use crate::{
|
||||||
};
|
};
|
||||||
use crate::{serde, serde_yaml};
|
use crate::{serde, serde_yaml};
|
||||||
|
|
||||||
use super::plugins::{PluginTag, PluginsConfigError};
|
use super::{
|
||||||
|
config::ConfigFromYaml,
|
||||||
|
plugins::{PluginTag, PluginsConfigError},
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
@ -137,6 +140,26 @@ pub struct Layout {
|
||||||
pub borderless: bool,
|
pub borderless: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The struct that is used to deserialize the layout from
|
||||||
|
// a yaml configuration file, is needed because of:
|
||||||
|
// https://github.com/bincode-org/bincode/issues/245
|
||||||
|
// flattened fields don't retain size information.
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||||
|
#[serde(crate = "self::serde")]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct LayoutFromYamlIntermediate {
|
||||||
|
#[serde(default)]
|
||||||
|
pub template: LayoutTemplate,
|
||||||
|
#[serde(default)]
|
||||||
|
pub borderless: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub tabs: Vec<TabLayout>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub session: SessionFromYaml,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub config: Option<ConfigFromYaml>,
|
||||||
|
}
|
||||||
|
|
||||||
// The struct that is used to deserialize the layout from
|
// The struct that is used to deserialize the layout from
|
||||||
// a yaml configuration file
|
// a yaml configuration file
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||||
|
|
@ -153,6 +176,124 @@ pub struct LayoutFromYaml {
|
||||||
pub tabs: Vec<TabLayout>,
|
pub tabs: Vec<TabLayout>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LayoutFromYamlIntermediateResult = Result<LayoutFromYamlIntermediate, ConfigError>;
|
||||||
|
|
||||||
|
impl LayoutFromYamlIntermediate {
|
||||||
|
pub fn from_path(layout_path: &Path) -> LayoutFromYamlIntermediateResult {
|
||||||
|
let mut layout_file = File::open(&layout_path)
|
||||||
|
.or_else(|_| File::open(&layout_path.with_extension("yaml")))
|
||||||
|
.map_err(|e| ConfigError::IoPath(e, layout_path.into()))?;
|
||||||
|
|
||||||
|
let mut layout = String::new();
|
||||||
|
layout_file.read_to_string(&mut layout)?;
|
||||||
|
let layout: Option<LayoutFromYamlIntermediate> = match serde_yaml::from_str(&layout) {
|
||||||
|
Err(e) => {
|
||||||
|
// needs direct check, as `[ErrorImpl]` is private
|
||||||
|
// https://github.com/dtolnay/serde-yaml/issues/121
|
||||||
|
if layout.is_empty() {
|
||||||
|
return Ok(LayoutFromYamlIntermediate::default());
|
||||||
|
}
|
||||||
|
return Err(ConfigError::Serde(e));
|
||||||
|
}
|
||||||
|
Ok(config) => config,
|
||||||
|
};
|
||||||
|
|
||||||
|
match layout {
|
||||||
|
Some(layout) => {
|
||||||
|
for tab in layout.tabs.clone() {
|
||||||
|
tab.check()?;
|
||||||
|
}
|
||||||
|
Ok(layout)
|
||||||
|
}
|
||||||
|
None => Ok(LayoutFromYamlIntermediate::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_yaml(yaml: &str) -> LayoutFromYamlIntermediateResult {
|
||||||
|
let layout: LayoutFromYamlIntermediate = match serde_yaml::from_str(yaml) {
|
||||||
|
Err(e) => {
|
||||||
|
// needs direct check, as `[ErrorImpl]` is private
|
||||||
|
// https://github.com/dtolnay/serde-yaml/issues/121
|
||||||
|
if yaml.is_empty() {
|
||||||
|
return Ok(LayoutFromYamlIntermediate::default());
|
||||||
|
}
|
||||||
|
return Err(ConfigError::Serde(e));
|
||||||
|
}
|
||||||
|
Ok(config) => config,
|
||||||
|
};
|
||||||
|
Ok(layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_layout_and_config(&self) -> (LayoutFromYaml, Option<ConfigFromYaml>) {
|
||||||
|
let config = self.config.clone();
|
||||||
|
let layout = self.clone().into();
|
||||||
|
(layout, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_path_or_default(
|
||||||
|
layout: Option<&PathBuf>,
|
||||||
|
layout_path: Option<&PathBuf>,
|
||||||
|
layout_dir: Option<PathBuf>,
|
||||||
|
) -> Option<LayoutFromYamlIntermediateResult> {
|
||||||
|
layout
|
||||||
|
.map(|p| LayoutFromYamlIntermediate::from_dir(p, layout_dir.as_ref()))
|
||||||
|
.or_else(|| layout_path.map(|p| LayoutFromYamlIntermediate::from_path(p)))
|
||||||
|
.or_else(|| {
|
||||||
|
Some(LayoutFromYamlIntermediate::from_dir(
|
||||||
|
&std::path::PathBuf::from("default"),
|
||||||
|
layout_dir.as_ref(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// It wants to use Path here, but that doesn't compile.
|
||||||
|
#[allow(clippy::ptr_arg)]
|
||||||
|
pub fn from_dir(
|
||||||
|
layout: &PathBuf,
|
||||||
|
layout_dir: Option<&PathBuf>,
|
||||||
|
) -> LayoutFromYamlIntermediateResult {
|
||||||
|
match layout_dir {
|
||||||
|
Some(dir) => Self::from_path(&dir.join(layout))
|
||||||
|
.or_else(|_| LayoutFromYamlIntermediate::from_default_assets(layout.as_path())),
|
||||||
|
None => LayoutFromYamlIntermediate::from_default_assets(layout.as_path()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Currently still needed but on nightly
|
||||||
|
// this is already possible:
|
||||||
|
// HashMap<&'static str, Vec<u8>>
|
||||||
|
pub fn from_default_assets(path: &Path) -> LayoutFromYamlIntermediateResult {
|
||||||
|
match path.to_str() {
|
||||||
|
Some("default") => Self::default_from_assets(),
|
||||||
|
Some("strider") => Self::strider_from_assets(),
|
||||||
|
Some("disable-status-bar") => Self::disable_status_from_assets(),
|
||||||
|
None | Some(_) => Err(ConfigError::IoPath(
|
||||||
|
std::io::Error::new(std::io::ErrorKind::Other, "The layout was not found"),
|
||||||
|
path.into(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Deserialize the assets from bytes &[u8],
|
||||||
|
// once serde-yaml supports zero-copy
|
||||||
|
pub fn default_from_assets() -> LayoutFromYamlIntermediateResult {
|
||||||
|
let layout: LayoutFromYamlIntermediate =
|
||||||
|
serde_yaml::from_str(String::from_utf8(setup::DEFAULT_LAYOUT.to_vec())?.as_str())?;
|
||||||
|
Ok(layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn strider_from_assets() -> LayoutFromYamlIntermediateResult {
|
||||||
|
let layout: LayoutFromYamlIntermediate =
|
||||||
|
serde_yaml::from_str(String::from_utf8(setup::STRIDER_LAYOUT.to_vec())?.as_str())?;
|
||||||
|
Ok(layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable_status_from_assets() -> LayoutFromYamlIntermediateResult {
|
||||||
|
let layout: LayoutFromYamlIntermediate =
|
||||||
|
serde_yaml::from_str(String::from_utf8(setup::NO_STATUS_LAYOUT.to_vec())?.as_str())?;
|
||||||
|
Ok(layout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type LayoutFromYamlResult = Result<LayoutFromYaml, ConfigError>;
|
type LayoutFromYamlResult = Result<LayoutFromYaml, ConfigError>;
|
||||||
|
|
||||||
impl LayoutFromYaml {
|
impl LayoutFromYaml {
|
||||||
|
|
@ -211,6 +352,7 @@ impl LayoutFromYaml {
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Currently still needed but on nightly
|
// Currently still needed but on nightly
|
||||||
// this is already possible:
|
// this is already possible:
|
||||||
// HashMap<&'static str, Vec<u8>>
|
// HashMap<&'static str, Vec<u8>>
|
||||||
|
|
@ -525,6 +667,35 @@ impl TryFrom<RunFromYaml> for Run {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<LayoutFromYamlIntermediate> for LayoutFromYaml {
|
||||||
|
fn from(layout_from_yaml_intermediate: LayoutFromYamlIntermediate) -> Self {
|
||||||
|
Self {
|
||||||
|
template: layout_from_yaml_intermediate.template,
|
||||||
|
borderless: layout_from_yaml_intermediate.borderless,
|
||||||
|
tabs: layout_from_yaml_intermediate.tabs,
|
||||||
|
session: layout_from_yaml_intermediate.session,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LayoutFromYaml> for LayoutFromYamlIntermediate {
|
||||||
|
fn from(layout_from_yaml: LayoutFromYaml) -> Self {
|
||||||
|
Self {
|
||||||
|
template: layout_from_yaml.template,
|
||||||
|
borderless: layout_from_yaml.borderless,
|
||||||
|
tabs: layout_from_yaml.tabs,
|
||||||
|
config: None,
|
||||||
|
session: layout_from_yaml.session,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LayoutFromYamlIntermediate {
|
||||||
|
fn default() -> Self {
|
||||||
|
LayoutFromYaml::default().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<TabLayout> for Layout {
|
impl TryFrom<TabLayout> for Layout {
|
||||||
type Error = ConfigError;
|
type Error = ConfigError;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,14 @@ impl PluginsConfig {
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &PluginConfig> {
|
pub fn iter(&self) -> impl Iterator<Item = &PluginConfig> {
|
||||||
self.0.values()
|
self.0.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Merges two PluginConfig structs into one PluginConfig struct
|
||||||
|
/// `other` overrides the PluginConfig of `self`.
|
||||||
|
pub fn merge(&self, other: Self) -> Self {
|
||||||
|
let mut plugin_config = self.0.clone();
|
||||||
|
plugin_config.extend(other.0);
|
||||||
|
Self(plugin_config)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PluginsConfig {
|
impl Default for PluginsConfig {
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,14 @@ impl ThemesFromYaml {
|
||||||
.get_theme(theme)
|
.get_theme(theme)
|
||||||
.map(|t| Palette::from(t.palette))
|
.map(|t| Palette::from(t.palette))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Merges two Theme structs into one Theme struct
|
||||||
|
/// `other` overrides the Theme of `self`.
|
||||||
|
pub fn merge(&self, other: Self) -> Self {
|
||||||
|
let mut theme = self.0.clone();
|
||||||
|
theme.extend(other.0);
|
||||||
|
Self(theme)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PaletteFromYaml> for Palette {
|
impl From<PaletteFromYaml> for Palette {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
input::{
|
input::{
|
||||||
config::{Config, ConfigError},
|
config::{Config, ConfigError},
|
||||||
layout::LayoutFromYaml,
|
layout::{LayoutFromYaml, LayoutFromYamlIntermediate},
|
||||||
options::Options,
|
options::Options,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -147,10 +147,12 @@ pub struct Setup {
|
||||||
impl Setup {
|
impl Setup {
|
||||||
/// Entrypoint from main
|
/// Entrypoint from main
|
||||||
/// Merges options from the config file and the command line options
|
/// Merges options from the config file and the command line options
|
||||||
/// into `[Options]`, the command line options superceding the config
|
/// into `[Options]`, the command line options superceeding the layout
|
||||||
/// file options:
|
/// file options, superceeding the config file options:
|
||||||
/// 1. command line options (`zellij options`)
|
/// 1. command line options (`zellij options`)
|
||||||
/// 2. config options (`config.yaml`)
|
/// 2. layout options
|
||||||
|
/// (`layout.yaml` / `zellij --layout` / `zellij --layout-path`)
|
||||||
|
/// 3. config options (`config.yaml`)
|
||||||
pub fn from_options(
|
pub fn from_options(
|
||||||
opts: &CliArgs,
|
opts: &CliArgs,
|
||||||
) -> Result<(Config, Option<LayoutFromYaml>, Options), ConfigError> {
|
) -> Result<(Config, Option<LayoutFromYaml>, Options), ConfigError> {
|
||||||
|
|
@ -187,7 +189,7 @@ impl Setup {
|
||||||
.layout_dir
|
.layout_dir
|
||||||
.clone()
|
.clone()
|
||||||
.or_else(|| get_layout_dir(opts.config_dir.clone().or_else(find_default_config_dir)));
|
.or_else(|| get_layout_dir(opts.config_dir.clone().or_else(find_default_config_dir)));
|
||||||
let layout_result = LayoutFromYaml::from_path_or_default(
|
let layout_result = LayoutFromYamlIntermediate::from_path_or_default(
|
||||||
opts.layout.as_ref(),
|
opts.layout.as_ref(),
|
||||||
opts.layout_path.as_ref(),
|
opts.layout_path.as_ref(),
|
||||||
layout_dir,
|
layout_dir,
|
||||||
|
|
@ -212,7 +214,7 @@ impl Setup {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((config, layout, config_options))
|
Setup::merge_config_with_layout(config, layout, config_options)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// General setup helpers
|
/// General setup helpers
|
||||||
|
|
@ -252,6 +254,30 @@ impl Setup {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn merge_config_with_layout(
|
||||||
|
config: Config,
|
||||||
|
layout: Option<LayoutFromYamlIntermediate>,
|
||||||
|
config_options: Options,
|
||||||
|
) -> Result<(Config, Option<LayoutFromYaml>, Options), ConfigError> {
|
||||||
|
let (layout, layout_config) = match layout.map(|l| l.to_layout_and_config()) {
|
||||||
|
None => (None, None),
|
||||||
|
Some((layout, layout_config)) => (Some(layout), layout_config),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (config, config_options) = if let Some(layout_config) = layout_config {
|
||||||
|
let config_options = if let Some(options) = layout_config.options.clone() {
|
||||||
|
config_options.merge(options)
|
||||||
|
} else {
|
||||||
|
config_options
|
||||||
|
};
|
||||||
|
let config = config.merge(layout_config.try_into()?);
|
||||||
|
(config, config_options)
|
||||||
|
} else {
|
||||||
|
(config, config_options)
|
||||||
|
};
|
||||||
|
Ok((config, layout, config_options))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn check_defaults_config(opts: &CliArgs, config_options: &Options) -> std::io::Result<()> {
|
pub fn check_defaults_config(opts: &CliArgs, config_options: &Options) -> std::io::Result<()> {
|
||||||
let data_dir = opts.data_dir.clone().unwrap_or_else(get_default_data_dir);
|
let data_dir = opts.data_dir.clone().unwrap_or_else(get_default_data_dir);
|
||||||
let config_dir = opts.config_dir.clone().or_else(find_default_config_dir);
|
let config_dir = opts.config_dir.clone().or_else(find_default_config_dir);
|
||||||
|
|
@ -360,3 +386,99 @@ impl Setup {
|
||||||
CliArgs::clap().gen_completions_to("zellij", shell, &mut out);
|
CliArgs::clap().gen_completions_to("zellij", shell, &mut out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod setup_test {
|
||||||
|
use super::Setup;
|
||||||
|
use crate::input::{
|
||||||
|
config::{Config, ConfigError},
|
||||||
|
keybinds::Keybinds,
|
||||||
|
layout::{LayoutFromYaml, LayoutFromYamlIntermediate},
|
||||||
|
options::Options,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn deserialise_config_and_layout(
|
||||||
|
config: &str,
|
||||||
|
layout: &str,
|
||||||
|
) -> Result<(Config, LayoutFromYamlIntermediate), ConfigError> {
|
||||||
|
let config = Config::from_yaml(&config)?;
|
||||||
|
let layout = LayoutFromYamlIntermediate::from_yaml(&layout)?;
|
||||||
|
Ok((config, layout))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_config_empty_layout() {
|
||||||
|
let goal = Config::default();
|
||||||
|
let config = r"";
|
||||||
|
let layout = r"";
|
||||||
|
let config_layout_result = deserialise_config_and_layout(config, layout);
|
||||||
|
let (config, layout) = config_layout_result.unwrap();
|
||||||
|
let config_options = Options::default();
|
||||||
|
let (config, _layout, _config_options) =
|
||||||
|
Setup::merge_config_with_layout(config, Some(layout), config_options).unwrap();
|
||||||
|
assert_eq!(config, goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_empty_layout() {
|
||||||
|
let mut goal = Config::default();
|
||||||
|
goal.options.default_shell = Some(std::path::PathBuf::from("fish"));
|
||||||
|
let config = r"---
|
||||||
|
default_shell: fish";
|
||||||
|
let layout = r"";
|
||||||
|
let config_layout_result = deserialise_config_and_layout(config, layout);
|
||||||
|
let (config, layout) = config_layout_result.unwrap();
|
||||||
|
let config_options = Options::default();
|
||||||
|
let (config, _layout, _config_options) =
|
||||||
|
Setup::merge_config_with_layout(config, Some(layout), config_options).unwrap();
|
||||||
|
assert_eq!(config, goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn layout_overwrites_config() {
|
||||||
|
let mut goal = Config::default();
|
||||||
|
goal.options.default_shell = Some(std::path::PathBuf::from("bash"));
|
||||||
|
let config = r"---
|
||||||
|
default_shell: fish";
|
||||||
|
let layout = r"---
|
||||||
|
default_shell: bash";
|
||||||
|
let config_layout_result = deserialise_config_and_layout(config, layout);
|
||||||
|
let (config, layout) = config_layout_result.unwrap();
|
||||||
|
let config_options = Options::default();
|
||||||
|
let (config, _layout, _config_options) =
|
||||||
|
Setup::merge_config_with_layout(config, Some(layout), config_options).unwrap();
|
||||||
|
assert_eq!(config, goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_config_nonempty_layout() {
|
||||||
|
let mut goal = Config::default();
|
||||||
|
goal.options.default_shell = Some(std::path::PathBuf::from("bash"));
|
||||||
|
let config = r"";
|
||||||
|
let layout = r"---
|
||||||
|
default_shell: bash";
|
||||||
|
let config_layout_result = deserialise_config_and_layout(config, layout);
|
||||||
|
let (config, layout) = config_layout_result.unwrap();
|
||||||
|
let config_options = Options::default();
|
||||||
|
let (config, _layout, _config_options) =
|
||||||
|
Setup::merge_config_with_layout(config, Some(layout), config_options).unwrap();
|
||||||
|
assert_eq!(config, goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nonempty_config_nonempty_layout() {
|
||||||
|
let mut goal = Config::default();
|
||||||
|
goal.options.default_shell = Some(std::path::PathBuf::from("bash"));
|
||||||
|
goal.options.default_mode = Some(zellij_tile::prelude::InputMode::Locked);
|
||||||
|
let config = r"---
|
||||||
|
default_mode: locked";
|
||||||
|
let layout = r"---
|
||||||
|
default_shell: bash";
|
||||||
|
let config_layout_result = deserialise_config_and_layout(config, layout);
|
||||||
|
let (config, layout) = config_layout_result.unwrap();
|
||||||
|
let config_options = Options::default();
|
||||||
|
let (config, _layout, _config_options) =
|
||||||
|
Setup::merge_config_with_layout(config, Some(layout), config_options).unwrap();
|
||||||
|
assert_eq!(config, goal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue