summaryrefslogtreecommitdiffstats
path: root/zellij-utils/src/input
diff options
context:
space:
mode:
authora-kenji <aks.kenji@protonmail.com>2021-11-14 22:58:20 +0100
committerGitHub <noreply@github.com>2021-11-14 22:58:20 +0100
commit347e02ea35793ba7e898ca0693f569a21525d459 (patch)
treebeab3ed6f4102b96cffa8c7c7cfbaaa33d794943 /zellij-utils/src/input
parent96315ed332438374743d73d3e3b38c3cdbdc3331 (diff)
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 ```
Diffstat (limited to 'zellij-utils/src/input')
-rw-r--r--zellij-utils/src/input/config.rs16
-rw-r--r--zellij-utils/src/input/keybinds.rs12
-rw-r--r--zellij-utils/src/input/layout.rs173
-rw-r--r--zellij-utils/src/input/plugins.rs8
-rw-r--r--zellij-utils/src/input/theme.rs8
5 files changed, 208 insertions, 9 deletions
diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs
index 752e63e35..4c3765a27 100644
--- a/zellij-utils/src/input/config.rs
+++ b/zellij-utils/src/input/config.rs
@@ -5,7 +5,7 @@ use std::io::{self, Read};
use std::path::{Path, PathBuf};
use thiserror::Error;
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
use super::keybinds::{Keybinds, KeybindsFromYaml};
@@ -20,7 +20,7 @@ const DEFAULT_CONFIG_FILE_NAME: &str = "config.yaml";
type ConfigResult = Result<Config, ConfigError>;
/// Intermediate deserialization config struct
-#[derive(Clone, Debug, Deserialize)]
+#[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq)]
pub struct ConfigFromYaml {
#[serde(flatten)]
pub options: Option<Options>,
@@ -151,6 +151,18 @@ impl Config {
let cfg = String::from_utf8(setup::DEFAULT_CONFIG.to_vec())?;
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 {
diff --git a/zellij-utils/src/input/keybinds.rs b/zellij-utils/src/input/keybinds.rs
index d3d0c7051..e3f88b1a0 100644
--- a/zellij-utils/src/input/keybinds.rs
+++ b/zellij-utils/src/input/keybinds.rs
@@ -16,7 +16,7 @@ pub struct ModeKeybinds(HashMap<Key, Vec<Action>>);
/// Intermediate struct used for deserialisation
/// Used in the config file.
-#[derive(Clone, Debug, PartialEq, Deserialize)]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct KeybindsFromYaml {
#[serde(flatten)]
keybinds: HashMap<InputMode, Vec<KeyActionUnbind>>,
@@ -25,7 +25,7 @@ pub struct KeybindsFromYaml {
}
/// Intermediate enum used for deserialisation
-#[derive(Clone, Debug, PartialEq, Deserialize)]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(untagged)]
enum KeyActionUnbind {
KeyAction(KeyActionFromYaml),
@@ -40,21 +40,21 @@ struct KeyActionUnbindFromYaml {
}
/// Intermediate struct used for deserialisation
-#[derive(Clone, Debug, PartialEq, Deserialize)]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct KeyActionFromYaml {
action: Vec<Action>,
key: Vec<Key>,
}
/// Intermediate struct used for deserialisation
-#[derive(Clone, Debug, PartialEq, Deserialize)]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
struct UnbindFromYaml {
unbind: Unbind,
}
/// 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.
-#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(untagged)]
enum Unbind {
// This is the correct order, don't rearrange!
@@ -168,7 +168,7 @@ impl Keybinds {
/// Merges two Keybinds structs into one Keybinds struct
/// `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();
for mode in InputMode::iter() {
diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs
index df976b8e1..e06f20018 100644
--- a/zellij-utils/src/input/layout.rs
+++ b/zellij-utils/src/input/layout.rs
@@ -18,7 +18,10 @@ use crate::{
};
use crate::{serde, serde_yaml};
-use super::plugins::{PluginTag, PluginsConfigError};
+use super::{
+ config::ConfigFromYaml,
+ plugins::{PluginTag, PluginsConfigError},
+};
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
use std::vec::Vec;
@@ -138,6 +141,26 @@ pub struct Layout {
}
// 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
// a yaml configuration file
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(crate = "self::serde")]
@@ -153,6 +176,124 @@ pub struct LayoutFromYaml {
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>;
impl LayoutFromYaml {
@@ -211,6 +352,7 @@ impl LayoutFromYaml {
))
})
}
+
// Currently still needed but on nightly
// this is already possible:
// 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 {
type Error = ConfigError;
diff --git a/zellij-utils/src/input/plugins.rs b/zellij-utils/src/input/plugins.rs
index e97a8ad03..6be975e4d 100644
--- a/zellij-utils/src/input/plugins.rs
+++ b/zellij-utils/src/input/plugins.rs
@@ -62,6 +62,14 @@ impl PluginsConfig {
pub fn iter(&self) -> impl Iterator<Item = &PluginConfig> {
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 {
diff --git a/zellij-utils/src/input/theme.rs b/zellij-utils/src/input/theme.rs
index 35bd3d87b..da71b6334 100644
--- a/zellij-utils/src/input/theme.rs
+++ b/zellij-utils/src/input/theme.rs
@@ -63,6 +63,14 @@ impl ThemesFromYaml {
.get_theme(theme)
.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 {