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

Add layout config #866

Merged
merged 6 commits into from
Nov 14, 2021
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
16 changes: 14 additions & 2 deletions zellij-utils/src/input/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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>,
Expand Down Expand Up @@ -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 {
Expand Down
12 changes: 6 additions & 6 deletions zellij-utils/src/input/keybinds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>>,
Expand All @@ -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),
Expand All @@ -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!
Expand Down Expand Up @@ -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() {
Expand Down
173 changes: 172 additions & 1 deletion zellij-utils/src/input/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -137,6 +140,26 @@ pub struct Layout {
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
// a yaml configuration file
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
Expand All @@ -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 {
Expand Down Expand Up @@ -211,6 +352,7 @@ impl LayoutFromYaml {
))
})
}

// Currently still needed but on nightly
// this is already possible:
// HashMap<&'static str, Vec<u8>>
Expand Down Expand Up @@ -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;

Expand Down
8 changes: 8 additions & 0 deletions zellij-utils/src/input/plugins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions zellij-utils/src/input/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading