aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-utils/src/input
diff options
context:
space:
mode:
authorKunal Mohan <kunalmohan99@gmail.com>2021-05-16 16:42:50 +0530
committerKunal Mohan <kunalmohan99@gmail.com>2021-05-16 21:41:56 +0530
commit2038947a14d32d89da2bdbcaa1381c3015ac9c7a (patch)
tree5a332470e32b380816098594a89d3c3db1f6eb53 /zellij-utils/src/input
parenthandle error on termios initialization (#511) (diff)
downloadsl0ppij-2038947a14d32d89da2bdbcaa1381c3015ac9c7a.tar.gz
sl0ppij-2038947a14d32d89da2bdbcaa1381c3015ac9c7a.tar.xz
sl0ppij-2038947a14d32d89da2bdbcaa1381c3015ac9c7a.zip
Big refactor: separate crates for client, server and utilities
Diffstat (limited to 'zellij-utils/src/input')
-rw-r--r--zellij-utils/src/input/actions.rs64
-rw-r--r--zellij-utils/src/input/config.rs218
-rw-r--r--zellij-utils/src/input/keybinds.rs301
-rw-r--r--zellij-utils/src/input/mod.rs87
-rw-r--r--zellij-utils/src/input/options.rs45
-rw-r--r--zellij-utils/src/input/unit/keybinds_test.rs801
6 files changed, 1516 insertions, 0 deletions
diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs
new file mode 100644
index 00000000..b806aa3f
--- /dev/null
+++ b/zellij-utils/src/input/actions.rs
@@ -0,0 +1,64 @@
+//! Definition of the actions that can be bound to keys.
+
+use serde::{Deserialize, Serialize};
+use zellij_tile::data::InputMode;
+
+/// The four directions (left, right, up, down).
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
+pub enum Direction {
+ Left,
+ Right,
+ Up,
+ Down,
+}
+
+/// Actions that can be bound to keys.
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
+pub enum Action {
+ /// Quit Zellij.
+ Quit,
+ /// Write to the terminal.
+ Write(Vec<u8>),
+ /// Switch to the specified input mode.
+ SwitchToMode(InputMode),
+ /// Resize focus pane in specified direction.
+ Resize(Direction),
+ /// Switch focus to next pane in specified direction.
+ FocusNextPane,
+ FocusPreviousPane,
+ /// Move the focus pane in specified direction.
+ SwitchFocus,
+ MoveFocus(Direction),
+ /// Tries to move the focus pane in specified direction.
+ /// If there is no pane in the direction, move to previous/next Tab.
+ MoveFocusOrTab(Direction),
+ /// Scroll up in focus pane.
+ ScrollUp,
+ /// Scroll down in focus pane.
+ ScrollDown,
+ /// Scroll up one page in focus pane.
+ PageScrollUp,
+ /// Scroll down one page in focus pane.
+ PageScrollDown,
+ /// Toggle between fullscreen focus pane and normal layout.
+ ToggleFocusFullscreen,
+ /// Toggle between sending text commands to all panes on the current tab and normal mode.
+ ToggleActiveSyncTab,
+ /// Open a new pane in the specified direction (relative to focus).
+ /// If no direction is specified, will try to use the biggest available space.
+ NewPane(Option<Direction>),
+ /// Close the focus pane.
+ CloseFocus,
+ /// Create a new tab.
+ NewTab,
+ /// Do nothing.
+ NoOp,
+ /// Go to the next tab.
+ GoToNextTab,
+ /// Go to the previous tab.
+ GoToPreviousTab,
+ /// Close the current tab.
+ CloseTab,
+ GoToTab(u32),
+ TabNameInput(Vec<u8>),
+}
diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs
new file mode 100644
index 00000000..a0341689
--- /dev/null
+++ b/zellij-utils/src/input/config.rs
@@ -0,0 +1,218 @@
+//! Deserializes configuration options.
+use std::error;
+use std::fmt::{self, Display};
+use std::fs::File;
+use std::io::{self, Read};
+use std::path::{Path, PathBuf};
+
+use super::keybinds::{Keybinds, KeybindsFromYaml};
+use super::options::Options;
+use crate::cli::{CliArgs, ConfigCli};
+use crate::setup;
+
+use serde::{Deserialize, Serialize};
+use std::convert::TryFrom;
+
+const DEFAULT_CONFIG_FILE_NAME: &str = "config.yaml";
+
+type ConfigResult = Result<Config, ConfigError>;
+
+/// Intermediate deserialization config struct
+#[derive(Debug, Deserialize)]
+pub struct ConfigFromYaml {
+ #[serde(flatten)]
+ pub options: Option<Options>,
+ pub keybinds: Option<KeybindsFromYaml>,
+}
+
+/// Main configuration.
+#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
+pub struct Config {
+ pub keybinds: Keybinds,
+ pub options: Options,
+}
+
+#[derive(Debug)]
+pub enum ConfigError {
+ // Deserialization error
+ Serde(serde_yaml::Error),
+ // Io error
+ Io(io::Error),
+ // Io error with path context
+ IoPath(io::Error, PathBuf),
+ // Internal Deserialization Error
+ FromUtf8(std::string::FromUtf8Error),
+}
+
+impl Default for Config {
+ fn default() -> Self {
+ let keybinds = Keybinds::default();
+ let options = Options::default();
+ Config { keybinds, options }
+ }
+}
+
+impl TryFrom<&CliArgs> for Config {
+ type Error = ConfigError;
+
+ fn try_from(opts: &CliArgs) -> ConfigResult {
+ if let Some(ref path) = opts.config {
+ return Config::new(&path);
+ }
+
+ if let Some(ConfigCli::Setup(setup)) = opts.option.clone() {
+ if setup.clean {
+ return Config::from_default_assets();
+ }
+ }
+
+ let config_dir = opts
+ .config_dir
+ .clone()
+ .or_else(setup::find_default_config_dir);
+
+ if let Some(ref config) = config_dir {
+ let path = config.join(DEFAULT_CONFIG_FILE_NAME);
+ if path.exists() {
+ Config::new(&path)
+ } else {
+ Config::from_default_assets()
+ }
+ } else {
+ Config::from_default_assets()
+ }
+ }
+}
+
+impl Config {
+ /// Uses defaults, but lets config override them.
+ pub fn from_yaml(yaml_config: &str) -> ConfigResult {
+ let config_from_yaml: ConfigFromYaml = serde_yaml::from_str(&yaml_config)?;
+ let keybinds = Keybinds::get_default_keybinds_with_config(config_from_yaml.keybinds);
+ let options = Options::from_yaml(config_from_yaml.options);
+ Ok(Config { keybinds, options })
+ }
+
+ /// Deserializes from given path.
+ pub fn new(path: &Path) -> ConfigResult {
+ match File::open(path) {
+ Ok(mut file) => {
+ let mut yaml_config = String::new();
+ file.read_to_string(&mut yaml_config)
+ .map_err(|e| ConfigError::IoPath(e, path.to_path_buf()))?;
+ Ok(Config::from_yaml(&yaml_config)?)
+ }
+ Err(e) => Err(ConfigError::IoPath(e, path.into())),
+ }
+ }
+
+ /// Gets default configuration from assets
+ // TODO Deserialize the Configuration from bytes &[u8],
+ // once serde-yaml supports zero-copy
+ pub fn from_default_assets() -> ConfigResult {
+ Self::from_yaml(String::from_utf8(setup::DEFAULT_CONFIG.to_vec())?.as_str())
+ }
+}
+
+impl Display for ConfigError {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ ConfigError::Io(ref err) => write!(formatter, "IoError: {}", err),
+ ConfigError::IoPath(ref err, ref path) => {
+ write!(formatter, "IoError: {}, File: {}", err, path.display(),)
+ }
+ ConfigError::Serde(ref err) => write!(formatter, "Deserialization error: {}", err),
+ ConfigError::FromUtf8(ref err) => write!(formatter, "FromUtf8Error: {}", err),
+ }
+ }
+}
+
+impl std::error::Error for ConfigError {
+ fn cause(&self) -> Option<&dyn error::Error> {
+ match *self {
+ ConfigError::Io(ref err) => Some(err),
+ ConfigError::IoPath(ref err, _) => Some(err),
+ ConfigError::Serde(ref err) => Some(err),
+ ConfigError::FromUtf8(ref err) => Some(err),
+ }
+ }
+}
+
+impl From<io::Error> for ConfigError {
+ fn from(err: io::Error) -> ConfigError {
+ ConfigError::Io(err)
+ }
+}
+
+impl From<serde_yaml::Error> for ConfigError {
+ fn from(err: serde_yaml::Error) -> ConfigError {
+ ConfigError::Serde(err)
+ }
+}
+
+impl From<std::string::FromUtf8Error> for ConfigError {
+ fn from(err: std::string::FromUtf8Error) -> ConfigError {
+ ConfigError::FromUtf8(err)
+ }
+}
+
+// The unit test location.
+#[cfg(test)]
+mod config_test {
+ use std::io::Write;
+
+ use tempfile::tempdir;
+
+ use super::*;
+
+ #[test]
+ fn try_from_cli_args_with_config() {
+ let arbitrary_config = PathBuf::from("nonexistent.yaml");
+ let mut opts = CliArgs::default();
+ opts.config = Some(arbitrary_config);
+ println!("OPTS= {:?}", opts);
+ let result = Config::try_from(&opts);
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn try_from_cli_args_with_option_clean() {
+ use crate::setup::Setup;
+ let mut opts = CliArgs::default();
+ opts.option = Some(ConfigCli::Setup(Setup {
+ clean: true,
+ ..Setup::default()
+ }));
+ let result = Config::try_from(&opts);
+ assert!(result.is_ok());
+ }
+
+ #[test]
+ fn try_from_cli_args_with_config_dir() {
+ let mut opts = CliArgs::default();
+ let tmp = tempdir().unwrap();
+ File::create(tmp.path().join(DEFAULT_CONFIG_FILE_NAME))
+ .unwrap()
+ .write_all(b"keybinds: invalid\n")
+ .unwrap();
+ opts.config_dir = Some(tmp.path().to_path_buf());
+ let result = Config::try_from(&opts);
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn try_from_cli_args_with_config_dir_without_config() {
+ let mut opts = CliArgs::default();
+ let tmp = tempdir().unwrap();
+ opts.config_dir = Some(tmp.path().to_path_buf());
+ let result = Config::try_from(&opts);
+ assert_eq!(result.unwrap(), Config::default());
+ }
+
+ #[test]
+ fn try_from_cli_args_default() {
+ let opts = CliArgs::default();
+ let result = Config::try_from(&opts);
+ assert_eq!(result.unwrap(), Config::default());
+ }
+}
diff --git a/zellij-utils/src/input/keybinds.rs b/zellij-utils/src/input/keybinds.rs
new file mode 100644
index 00000000..8bef47fd
--- /dev/null
+++ b/zellij-utils/src/input/keybinds.rs
@@ -0,0 +1,301 @@
+//! Mapping of inputs to sequences of actions.
+use std::collections::HashMap;
+
+use super::actions::Action;
+use super::config;
+
+use serde::{Deserialize, Serialize};
+use strum::IntoEnumIterator;
+use zellij_tile::data::*;
+
+/// Used in the config struct
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
+pub struct Keybinds(HashMap<InputMode, ModeKeybinds>);
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
+pub struct ModeKeybinds(HashMap<Key, Vec<Action>>);
+
+/// Intermediate struct used for deserialisation
+/// Used in the config file.
+#[derive(Clone, Debug, PartialEq, Deserialize)]
+pub struct KeybindsFromYaml {
+ #[serde(flatten)]
+ keybinds: HashMap<InputMode, Vec<KeyActionUnbind>>,
+ #[serde(default)]
+ unbind: Unbind,
+}
+
+/// Intermediate enum used for deserialisation
+#[derive(Clone, Debug, PartialEq, Deserialize)]
+#[serde(untagged)]
+enum KeyActionUnbind {
+ KeyAction(KeyActionFromYaml),
+ Unbind(UnbindFromYaml),
+}
+
+/// Intermediate struct used for deserialisation
+#[derive(Clone, Debug, PartialEq, Deserialize)]
+struct KeyActionUnbindFromYaml {
+ keybinds: Vec<KeyActionFromYaml>,
+ unbind: Unbind,
+}
+
+/// Intermediate struct used for deserialisation
+#[derive(Clone, Debug, PartialEq, Deserialize)]
+pub struct KeyActionFromYaml {
+ action: Vec<Action>,
+ key: Vec<Key>,
+}
+
+/// Intermediate struct used for deserialisation
+#[derive(Clone, Debug, PartialEq, Deserialize)]
+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)]
+#[serde(untagged)]
+enum Unbind {
+ // This is the correct order, don't rearrange!
+ // Suspected Bug in the untagged macro.
+ // 1. Keys
+ Keys(Vec<Key>),
+ // 2. All
+ All(bool),
+}
+
+impl Default for Keybinds {
+ // Use once per codepath
+ // TODO investigate why
+ fn default() -> Keybinds {
+ Self::from_default_assets()
+ }
+}
+
+impl Keybinds {
+ pub fn new() -> Keybinds {
+ Keybinds(HashMap::<InputMode, ModeKeybinds>::new())
+ }
+
+ fn from_default_assets() -> Keybinds {
+ config::Config::from_default_assets()
+ .expect("Keybinds from default assets Error!")
+ .keybinds
+ }
+
+ /// Entrypoint from the config module
+ pub fn get_default_keybinds_with_config(from_yaml: Option<KeybindsFromYaml>) -> Keybinds {
+ let default_keybinds = match from_yaml.clone() {
+ Some(keybinds) => match keybinds.unbind {
+ Unbind::All(true) => Keybinds::new(),
+ Unbind::All(false) | Unbind::Keys(_) => Keybinds::unbind(keybinds),
+ },
+ None => Keybinds::default(),
+ };
+
+ if let Some(keybinds) = from_yaml {
+ default_keybinds.merge_keybinds(Keybinds::from(keybinds))
+ } else {
+ default_keybinds
+ }
+ }
+
+ /// Unbinds the default keybindings in relation to their mode
+ fn unbind(from_yaml: KeybindsFromYaml) -> Keybinds {
+ let mut keybind_config = Self::new();
+ let mut unbind_config: HashMap<InputMode, Unbind> = HashMap::new();
+ let keybinds_from_yaml = from_yaml.keybinds;
+
+ for mode in InputMode::iter() {
+ if let Some(keybinds) = keybinds_from_yaml.get(&mode) {
+ for keybind in keybinds.iter() {
+ match keybind {
+ KeyActionUnbind::Unbind(unbind) => {
+ unbind_config.insert(mode, unbind.unbind.clone());
+ }
+ KeyActionUnbind::KeyAction(key_action_from_yaml) => {
+ keybind_config
+ .0
+ .insert(mode, ModeKeybinds::from(key_action_from_yaml.clone()));
+ }
+ }
+ }
+ }
+ }
+
+ let mut default = Self::default().unbind_mode(unbind_config);
+
+ // Toplevel Unbinds
+ if let Unbind::Keys(_) = from_yaml.unbind {
+ let mut unbind_config: HashMap<InputMode, Unbind> = HashMap::new();
+ for mode in InputMode::iter() {
+ unbind_config.insert(mode, from_yaml.unbind.clone());
+ }
+ default = default.unbind_mode(unbind_config);
+ };
+
+ default.merge_keybinds(keybind_config)
+ }
+
+ /// Unbind [`Key`] bindings respective to their mode
+ fn unbind_mode(&self, unbind: HashMap<InputMode, Unbind>) -> Keybinds {
+ let mut keybinds = Keybinds::new();
+
+ for mode in InputMode::iter() {
+ if let Some(unbind) = unbind.get(&mode) {
+ match unbind {
+ Unbind::All(true) => {}
+ Unbind::Keys(keys) => {
+ if let Some(defaults) = self.0.get(&mode) {
+ keybinds
+ .0
+ .insert(mode, defaults.clone().unbind_keys(keys.to_vec()));
+ }
+ }
+ Unbind::All(false) => {
+ if let Some(defaults) = self.0.get(&mode) {
+ keybinds.0.insert(mode, defaults.clone());
+ }
+ }
+ }
+ } else if let Some(defaults) = self.0.get(&mode) {
+ keybinds.0.insert(mode, defaults.clone());
+ }
+ }
+ keybinds
+ }
+
+ /// Merges two Keybinds structs into one Keybinds struct
+ /// `other` overrides the ModeKeybinds of `self`.
+ fn merge_keybinds(&self, other: Keybinds) -> Keybinds {
+ let mut keybinds = Keybinds::new();
+
+ for mode in InputMode::iter() {
+ let mut mode_keybinds = ModeKeybinds::new();
+ if let Some(keybind) = self.0.get(&mode) {
+ mode_keybinds.0.extend(keybind.0.clone());
+ };
+ if let Some(keybind) = other.0.get(&mode) {
+ mode_keybinds.0.extend(keybind.0.clone());
+ }
+ if !mode_keybinds.0.is_empty() {
+ keybinds.0.insert(mode, mode_keybinds);
+ }
+ }
+ keybinds
+ }
+
+ /// Converts a [`Key`] terminal event to a sequence of [`Action`]s according to the current
+ /// [`InputMode`] and [`Keybinds`].
+ pub fn key_to_actions(
+ key: &Key,
+ input: Vec<u8>,
+ mode: &InputMode,
+ keybinds: &Keybinds,
+ ) -> Vec<Action> {
+ let mode_keybind_or_action = |action: Action| {
+ keybinds
+ .0
+ .get(mode)
+ .unwrap_or_else(|| unreachable!("Unrecognized mode: {:?}", mode))
+ .0
+ .get(key)
+ .cloned()
+ .unwrap_or_else(|| vec![action])
+ };
+ match *mode {
+ InputMode::Normal | InputMode::Locked => mode_keybind_or_action(Action::Write(input)),
+ InputMode::RenameTab => mode_keybind_or_action(Action::TabNameInput(input)),
+ _ => mode_keybind_or_action(Action::NoOp),
+ }
+ }
+}
+
+impl ModeKeybinds {
+ fn new() -> ModeKeybinds {
+ ModeKeybinds(HashMap::<Key, Vec<Action>>::new())
+ }
+
+ /// Merges `self` with `other`, if keys are the same, `other` overwrites.
+ fn merge(self, other: ModeKeybinds) -> ModeKeybinds {
+ let mut merged = self;
+ merged.0.extend(other.0);
+ merged
+ }
+
+ /// Remove [`Key`]'s from [`ModeKeybinds`]
+ fn unbind_keys(self, unbind: Vec<Key>) -> Self {
+ let mut keymap = self;
+ for key in unbind {
+ keymap.0.remove(&key);
+ }
+ keymap
+ }
+}
+
+impl From<KeybindsFromYaml> for Keybinds {
+ fn from(keybinds_from_yaml: KeybindsFromYaml) -> Keybinds {
+ let mut keybinds = Keybinds::new();
+
+ for mode in InputMode::iter() {
+ let mut mode_keybinds = ModeKeybinds::new();
+ for key_action in keybinds_from_yaml.keybinds.get(&mode).iter() {
+ for keybind in key_action.iter() {
+ mode_keybinds = mode_keybinds.merge(ModeKeybinds::from(keybind.clone()));
+ }
+ }
+ keybinds.0.insert(mode, mode_keybinds);
+ }
+ keybinds
+ }
+}
+
+/// For each [`Key`] assigned to [`Action`]s,
+/// map the [`Action`]s to the [`Key`]
+impl From<KeyActionFromYaml> for ModeKeybinds {
+ fn from(key_action: KeyActionFromYaml) -> ModeKeybinds {
+ let actions = key_action.action;
+
+ ModeKeybinds(
+ key_action
+ .key
+ .into_iter()
+ .map(|k| (k, actions.clone()))
+ .collect::<HashMap<Key, Vec<Action>>>(),
+ )
+ }
+}
+
+impl From<KeyActionUnbind> for ModeKeybinds {
+ fn from(key_action_unbind: KeyActionUnbind) -> ModeKeybinds {
+ match key_action_unbind {
+ KeyActionUnbind::KeyAction(key_action) => ModeKeybinds::from(key_action),
+ KeyActionUnbind::Unbind(_) => ModeKeybinds::new(),
+ }
+ }
+}
+
+impl From<Vec<KeyActionFromYaml>> for ModeKeybinds {
+ fn from(key_action_from_yaml: Vec<KeyActionFromYaml>) -> ModeKeybinds {
+ let mut mode_keybinds = ModeKeybinds::new();
+
+ for keybind in key_action_from_yaml {
+ for key in keybind.key {
+ mode_keybinds.0.insert(key, keybind.action.clone());
+ }
+ }
+ mode_keybinds
+ }
+}
+
+impl Default for Unbind {
+ fn default() -> Unbind {
+ Unbind::All(false)
+ }
+}
+
+// The unit test location.
+#[cfg(test)]
+#[path = "./unit/keybinds_test.rs"]
+mod keybinds_test;
diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs
new file mode 100644
index 00000000..2747205b
--- /dev/null
+++ b/zellij-utils/src/input/mod.rs
@@ -0,0 +1,87 @@
+//! The way terminal input is handled.
+
+pub mod actions;
+pub mod config;
+pub mod keybinds;
+pub mod options;
+
+use termion::input::TermRead;
+use zellij_tile::data::{InputMode, Key, ModeInfo, Palette, PluginCapabilities};
+
+/// Creates a [`Help`] struct indicating the current [`InputMode`] and its keybinds
+/// (as pairs of [`String`]s).
+// TODO this should probably be automatically generated in some way
+pub fn get_mode_info(
+ mode: InputMode,
+ palette: Palette,
+ capabilities: PluginCapabilities,
+) -> ModeInfo {
+ let mut keybinds: Vec<(String, String)> = vec![];
+ match mode {
+ InputMode::Normal | InputMode::Locked => {}
+ InputMode::Resize => {
+ keybinds.push(("←↓↑→".to_string(), "Resize".to_string()));
+ }
+ InputMode::Pane => {
+ keybinds.push(("←↓↑→".to_string(), "Move focus".to_string()));
+ keybinds.push(("p".to_string(), "Next".to_string()));
+ keybinds.push(("n".to_string(), "New".to_string()));
+ keybinds.push(("d".to_string(), "Down split".to_string()));
+ keybinds.push(("r".to_string(), "Right split".to_string()));
+ keybinds.push(("x".to_string(), "Close".to_string()));
+ keybinds.push(("f".to_string(), "Fullscreen".to_string()));
+ }
+ InputMode::Tab => {
+ keybinds.push(("←↓↑→".to_string(), "Move focus".to_string()));
+ keybinds.push(("n".to_string(), "New".to_string()));
+ keybinds.push(("x".to_string(), "Close".to_string()));
+ keybinds.push(("r".to_string(), "Rename".to_string()));
+ keybinds.push(("s".to_string(), "Sync".to_string()));
+ }
+ InputMode::Scroll => {
+ keybinds.push(("↓↑".to_string(), "Scroll".to_string()));
+ keybinds.push(("PgUp/PgDn".to_string(), "Scroll Page".to_string()));
+ }
+ InputMode::RenameTab => {
+ keybinds.push(("Enter".to_string(), "when done".to_string()));
+ }
+ }
+ ModeInfo {
+ mode,
+ keybinds,
+ palette,
+ capabilities,
+ }
+}
+
+pub fn parse_keys(input_bytes: &[u8]) -> Vec<Key> {
+ input_bytes.keys().flatten().map(cast_termion_key).collect()
+}
+
+// FIXME: This is an absolutely cursed function that should be destroyed as soon
+// as an alternative that doesn't touch zellij-tile can be developed...
+pub fn cast_termion_key(event: termion::event::Key) -> Key {
+ match event {
+ termion::event::Key::Backspace => Key::Backspace,
+ termion::event::Key::Left => Key::Left,
+ termion::event::Key::Right => Key::Right,
+ termion::event::Key::Up => Key::Up,
+ termion::event::Key::Down => Key::Down,
+ termion::event::Key::Home => Key::Home,
+ termion::event::Key::End => Key::End,
+ termion::event::Key::PageUp => Key::PageUp,
+ termion::event::Key::PageDown => Key::PageDown,
+ termion::event::Key::BackTab => Key::BackTab,
+ termion::event::Key::Delete => Key::Delete,
+ termion::event::Key::Insert => Key::Insert,
+ termion::event::Key::F(n) => Key::F(n),
+ termion::event::Key::Char(c) => Key::Char(c),
+ termion::event::Key::Alt(c) => Key::Alt(c),
+ termion::event::Key::Ctrl(c) => Key::Ctrl(c),
+ termion::event::Key::Null => Key::Null,
+ termion::event::Key::Esc => Key::Esc,
+ _ => {
+ unimplemented!("Encountered an unknown key!")
+ }
+ }
+}
diff --git a/zellij-utils/src/input/options.rs b/zellij-utils/src/input/options.rs
new file mode 100644
index 00000000..33625b65
--- /dev/null
+++ b/zellij-utils/src/input/options.rs
@@ -0,0 +1,45 @@
+//! Handles cli and configuration options
+use crate::cli::ConfigCli;
+use serde::{Deserialize, Serialize};
+use structopt::StructOpt;
+
+#[derive(Clone, Default, Debug, PartialEq, Deserialize, Serialize, StructOpt)]
+/// Options that can be set either through the config file,
+/// or cli flags
+pub struct Options {
+ /// Allow plugins to use a more simplified layout
+ /// that is compatible with more fonts
+ #[structopt(long)]
+ pub simplified_ui: bool,
+}
+
+impl Options {
+ pub fn from_yaml(from_yaml: Option<Options>) -> Options {
+ if let Some(opts) = from_yaml {
+ opts
+ } else {
+ Options::default()
+ }
+ }
+
+ /// Merges two [`Options`] structs, a `Some` in `other`
+ /// will supercede a `Some` in `self`
+ // TODO: Maybe a good candidate for a macro?
+ pub fn merge(&self, other: Options) -> Options {
+ let simplified_ui = if other.simplified_ui {
+ true
+ } else {
+ self.simplified_ui
+ };
+
+ Options { simplified_ui }
+ }
+
+ pub fn from_cli(&self, other: Option<ConfigCli>) -> Options {
+ if let Some(ConfigCli::Options(options)) = other {
+ Options::merge(&self, options)
+ } else {
+ self.to_owned()
+ }
+ }
+}
diff --git a/zellij-utils/src/input/unit/keybinds_test.rs b/zellij-utils/src/input/unit/keybinds_test.rs
new file mode 100644
index 00000000..4767fc15
--- /dev/null
+++ b/zellij-utils/src/input/unit/keybinds_test.rs
@@ -0,0 +1,801 @@
+use super::super::actions::*;
+use super::super::keybinds::*;
+use zellij_tile::data::Key;
+
+#[test]
+fn merge_keybinds_merges_different_keys() {
+ let mut mode_keybinds_self = ModeKeybinds::new();
+ mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]);
+ let mut mode_keybinds_other = ModeKeybinds::new();
+ mode_keybinds_other
+ .0
+ .insert(Key::Backspace, vec![Action::NoOp]);
+
+ let mut mode_keybinds_expected = ModeKeybinds::new();
+ mode_keybinds_expected
+ .0
+ .insert(Key::F(1), vec![Action::NoOp]);
+ mode_keybinds_expected
+ .0
+ .insert(Key::Backspace, vec![Action::NoOp]);
+
+ let mode_keybinds_merged = mode_keybinds_self.merge(mode_keybinds_other);
+
+ assert_eq!(mode_keybinds_expected, mode_keybinds_merged);
+}
+
+#[test]
+fn merge_mode_keybinds_overwrites_same_keys() {
+ let mut mode_keybinds_self = ModeKeybinds::new();
+ mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]);
+ let mut mode_keybinds_other = ModeKeybinds::new();
+ mode_keybinds_other
+ .0
+ .insert(Key::F(1), vec![Action::GoToTab(1)]);
+
+ let mut mode_keybinds_expected = ModeKeybinds::new();
+ mode_keybinds_expected
+ .0
+ .insert(Key::F(1), vec![Action::GoToTab(1)]);
+
+ let mode_keybinds_merged = mode_keybinds_self.merge(mode_keybinds_other);
+
+ assert_eq!(mode_keybinds_expected, mode_keybinds_merged);
+}
+
+#[test]
+fn merge_keybinds_merges() {
+ let mut mode_keybinds_self = ModeKeybinds::new();
+ mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]);
+ let mut mode_keybinds_other = ModeKeybinds::new();
+ mode_keybinds_other
+ .0
+ .insert(Key::Backspace, vec![Action::NoOp]);
+ let mut keybinds_self = Keybinds::new();
+ keybinds_self
+ .0
+ .insert(InputMode::Normal, mode_keybinds_self.clone());
+ let mut keybinds_other = Keybinds::new();
+ keybinds_other
+ .0
+ .insert(InputMode::Resize, mode_keybinds_other.clone());
+ let mut keybinds_expected = Keybinds::new();
+ keybinds_expected
+ .0
+ .insert(InputMode::Normal, mode_keybinds_self);
+ keybinds_expected
+ .0
+ .insert(InputMode::Resize, mode_keybinds_other);
+
+ assert_eq!(
+ keybinds_expected,
+ keybinds_self.merge_keybinds(keybinds_other)
+ )
+}
+
+#[test]
+fn merge_keybinds_overwrites_same_keys() {
+ let mut mode_keybinds_self = ModeKeybinds::new();
+ mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]);
+ mode_keybinds_self.0.insert(Key::F(2), vec![Action::NoOp]);
+ mode_keybinds_self.0.insert(Key::F(3), vec![Action::NoOp]);
+ let mut mode_keybinds_other = ModeKeybinds::new();
+ mode_keybinds_other
+ .0
+ .insert(Key::F(1), vec![Action::GoToTab(1)]);
+ mode_keybinds_other
+ .0
+ .insert(Key::F(2), vec![Action::GoToTab(2)]);
+ mode_keybinds_other
+ .0
+ .insert(Key::F(3), vec![Action::GoToTab(3)]);
+ let mut keybinds_self = Keybinds::new();
+ keybinds_self
+ .0
+ .insert(InputMode::Normal, mode_keybinds_self.clone());
+ let mut keybinds_other = Keybinds::new();
+ keybinds_other
+ .0
+ .insert(InputMode::Normal, mode_keybinds_other.clone());
+ let mut keybinds_expected = Keybinds::new();
+ keybinds_expected
+ .0
+ .insert(InputMode::Normal, mode_keybinds_other);
+
+ assert_eq!(
+ keybinds_expected,
+ keybinds_self.merge_keybinds(keybinds_other)
+ )
+}
+
+#[test]
+fn from_keyaction_from_yaml_to_mode_keybindings() {
+ let actions = vec![Action::NoOp, Action::GoToTab(1)];
+ let keyaction = KeyActionFromYaml {
+ action: actions.clone(),
+ key: vec![Key::F(1), Key::Backspace, Key::Char('t')],
+ };
+
+ let mut expected = ModeKeybinds::new();
+ expected.0.insert(Key::F(1), actions.clone());
+ expected.0.insert(Key::Backspace, actions.clone());
+ expected.0.insert(Key::Char('t'), actions);
+
+ assert_eq!(expected, ModeKeybinds::from(keyaction));
+}
+
+#[test]
+fn toplevel_unbind_unbinds_all() {
+ let from_yaml = KeybindsFromYaml {
+ unbind: Unbind::All(true),
+ keybinds: HashMap::new(),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+
+ assert_eq!(keybinds_from_yaml, Keybinds::new());
+}
+
+#[test]
+fn no_unbind_unbinds_none() {
+ let from_yaml = KeybindsFromYaml {
+ unbind: Unbind::All(false),
+ keybinds: HashMap::new(),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+
+ assert_eq!(keybinds_from_yaml, Keybinds::default());
+}
+
+#[test]
+fn last_keybind_is_taken() {
+ let actions_1 = vec![Action::NoOp, Action::NewTab];
+ let keyaction_1 = KeyActionFromYaml {
+ action: actions_1.clone(),
+ key: vec![Key::F(1), Key::Backspace, Key::Char('t')],
+ };
+ let actions_2 = vec![Action::GoToTab(1)];
+ let keyaction_2 = KeyActionFromYaml {
+ action: actions_2.clone(),
+ key: vec![Key::F(1), Key::Backspace, Key::Char('t')],
+ };
+
+ let mut expected = ModeKeybinds::new();
+ expected.0.insert(Key::F(1), actions_2.clone());
+ expected.0.insert(Key::Backspace, actions_2.clone());
+ expected.0.insert(Key::Char('t'), actions_2);
+
+ assert_eq!(expected, ModeKeybinds::from(vec![keyaction_1, keyaction_2]));
+}
+
+#[test]
+fn last_keybind_overwrites() {
+ let actions_1 = vec![Action::NoOp, Action::NewTab];
+ let keyaction_1 = KeyActionFromYaml {
+ action: actions_1.clone(),
+ key: vec![Key::F(1), Key::Backspace, Key::Char('t')],
+ };
+ let actions_2 = vec![Action::GoToTab(1)];
+ let keyaction_2 = KeyActionFromYaml {
+ action: actions_2.clone(),
+ key: vec![Key::F(1), Key::Char('t')],
+ };
+
+ let mut expected = ModeKeybinds::new();
+ expected.0.insert(Key::F(1), actions_2.clone());
+ expected.0.insert(Key::Backspace, actions_1.clone());
+ expected.0.insert(Key::Char('t'), actions_2);
+
+ assert_eq!(expected, ModeKeybinds::from(vec![keyaction_1, keyaction_2]));
+}
+
+#[test]
+fn unbind_single_mode() {
+ let unbind = Unbind::All(true);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbinds = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds);
+
+ let keybinds_from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(false),
+ };
+
+ let keybinds = Keybinds::unbind(keybinds_from_yaml);
+ let result = keybinds.0.get(&InputMode::Normal);
+ assert!(result.is_none());
+}
+
+#[test]
+fn unbind_multiple_modes() {
+ let unbind = Unbind::All(true);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbinds = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds.clone());
+ keys.insert(InputMode::Pane, key_action_unbinds);
+
+ let keybinds_from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(false),
+ };
+
+ let keybinds = Keybinds::unbind(keybinds_from_yaml);
+ let normal = keybinds.0.get(&InputMode::Normal);
+ let pane = keybinds.0.get(&InputMode::Pane);
+ assert!(normal.is_none());
+ assert!(pane.is_none());
+}
+
+#[test]
+fn unbind_single_keybind_single_mode() {
+ let unbind = Unbind::Keys(vec![Key::Alt('n')]);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbinds = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds);
+
+ let keybinds_from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(false),
+ };
+
+ let keybinds = Keybinds::unbind(keybinds_from_yaml);
+ let mode_keybinds = keybinds.0.get(&InputMode::Normal);
+ let result = mode_keybinds
+ .expect("Mode shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ assert!(result.is_none());
+}
+
+#[test]
+fn unbind_single_keybind_multiple_modes() {
+ let unbind_n = Unbind::Keys(vec![Key::Alt('n')]);
+ let unbind_h = Unbind::Keys(vec![Key::Alt('h')]);
+ let unbind_from_yaml_n = UnbindFromYaml { unbind: unbind_n };
+ let unbind_from_yaml_h = UnbindFromYaml { unbind: unbind_h };
+ let key_action_unbinds_n = vec![KeyActionUnbind::Unbind(unbind_from_yaml_n)];
+ let key_action_unbinds_h = vec![KeyActionUnbind::Unbind(unbind_from_yaml_h)];
+
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds_n);
+ keys.insert(InputMode::Pane, key_action_unbinds_h);
+
+ let keybinds_from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(false),
+ };
+
+ let keybinds = Keybinds::unbind(keybinds_from_yaml);
+ let normal = keybinds.0.get(&InputMode::Normal);
+ let pane = keybinds.0.get(&InputMode::Pane);
+ let result_normal = normal
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_pane = pane.expect("Mode shouldn't be empty").0.get(&Key::Alt('h'));
+ assert!(result_normal.is_none());
+ assert!(result_pane.is_none());
+}
+
+#[test]
+fn unbind_multiple_keybinds_single_mode() {
+ let unbind = Unbind::Keys(vec![Key::Alt('n'), Key::Ctrl('p')]);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbinds = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds);
+
+ let keybinds_from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(false),
+ };
+
+ let keybinds = Keybinds::unbind(keybinds_from_yaml);
+ let mode_keybinds = keybinds.0.get(&InputMode::Normal);
+ let result_n = mode_keybinds
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_p = mode_keybinds
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Ctrl('p'));
+ assert!(result_n.is_none());
+ assert!(result_p.is_none());
+}
+
+#[test]
+fn unbind_multiple_keybinds_multiple_modes() {
+ let unbind_normal = Unbind::Keys(vec![Key::Alt('n'), Key::Ctrl('p')]);
+ let unbind_resize = Unbind::Keys(vec![Key::Char('h'), Key::Ctrl('r')]);
+ let unbind_from_yaml_normal = UnbindFromYaml {
+ unbind: unbind_normal,
+ };
+ let unbind_from_yaml_resize = UnbindFromYaml {
+ unbind: unbind_resize,
+ };
+ let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml_normal)];
+ let key_action_unbinds_resize = vec![KeyActionUnbind::Unbind(unbind_from_yaml_resize)];
+
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds_normal);
+ keys.insert(InputMode::Resize, key_action_unbinds_resize);
+
+ let keybinds_from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(false),
+ };
+
+ let keybinds = Keybinds::unbind(keybinds_from_yaml);
+ let mode_keybinds_normal = keybinds.0.get(&InputMode::Normal);
+ let mode_keybinds_resize = keybinds.0.get(&InputMode::Resize);
+ let result_normal_1 = mode_keybinds_normal
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_normal_2 = mode_keybinds_normal
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Ctrl('p'));
+ let result_resize_1 = mode_keybinds_resize
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Char('h'));
+ let result_resize_2 = mode_keybinds_resize
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Ctrl('r'));
+ assert!(result_normal_1.is_none());
+ assert!(result_resize_1.is_none());
+ assert!(result_normal_2.is_none());
+ assert!(result_resize_2.is_none());
+}
+
+#[test]
+fn unbind_multiple_keybinds_all_modes() {
+ let unbind = Unbind::Keys(vec![Key::Alt('n'), Key::Alt('h')]);
+ let keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ let keybinds_from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind,
+ };
+
+ let keybinds = Keybinds::unbind(keybinds_from_yaml);
+ let mode_keybinds_normal = keybinds.0.get(&InputMode::Normal);
+ let mode_keybinds_resize = keybinds.0.get(&InputMode::Resize);
+ let result_normal_1 = mode_keybinds_normal
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_normal_2 = mode_keybinds_normal
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Ctrl('h'));
+ let result_resize_1 = mode_keybinds_resize
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Char('n'));
+ let result_resize_2 = mode_keybinds_resize
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Ctrl('h'));
+ assert!(result_normal_1.is_none());
+ assert!(result_resize_1.is_none());
+ assert!(result_normal_2.is_none());
+ assert!(result_resize_2.is_none());
+}
+
+#[test]
+fn unbind_all_toplevel_single_key_single_mode() {
+ let unbind = Unbind::Keys(vec![Key::Alt('h')]);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds_normal);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(true),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+ assert_eq!(keybinds_from_yaml, Keybinds::new());
+}
+
+#[test]
+fn unbind_all_toplevel_single_key_multiple_modes() {
+ let unbind_n = Unbind::Keys(vec![Key::Alt('n')]);
+ let unbind_h = Unbind::Keys(vec![Key::Alt('h')]);
+ let unbind_from_yaml_n = UnbindFromYaml { unbind: unbind_n };
+ let unbind_from_yaml_h = UnbindFromYaml { unbind: unbind_h };
+ let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml_n)];
+ let key_action_unbinds_pane = vec![KeyActionUnbind::Unbind(unbind_from_yaml_h)];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds_normal);
+ keys.insert(InputMode::Pane, key_action_unbinds_pane);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(true),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+ assert_eq!(keybinds_from_yaml, Keybinds::new());
+}
+
+#[test]
+fn unbind_all_toplevel_multiple_key_multiple_modes() {
+ let unbind_n = Unbind::Keys(vec![Key::Alt('n'), Key::Ctrl('p')]);
+ let unbind_h = Unbind::Keys(vec![Key::Alt('h'), Key::Ctrl('t')]);
+ let unbind_from_yaml_n = UnbindFromYaml { unbind: unbind_n };
+ let unbind_from_yaml_h = UnbindFromYaml { unbind: unbind_h };
+ let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml_n)];
+ let key_action_unbinds_pane = vec![KeyActionUnbind::Unbind(unbind_from_yaml_h)];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds_normal);
+ keys.insert(InputMode::Pane, key_action_unbinds_pane);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(true),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+ assert_eq!(keybinds_from_yaml, Keybinds::new());
+}
+
+#[test]
+fn unbind_all_toplevel_all_key_multiple_modes() {
+ let unbind = Unbind::All(true);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml.clone())];
+ let key_action_unbinds_pane = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbinds_normal);
+ keys.insert(InputMode::Pane, key_action_unbinds_pane);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(true),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+ assert_eq!(keybinds_from_yaml, Keybinds::new());
+}
+
+#[test]
+fn unbind_single_keybind_all_modes() {
+ let keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::Keys(vec![Key::Alt('n')]),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+
+ let result_normal = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_pane = keybinds_from_yaml
+ .0
+ .get(&InputMode::Pane)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_resize = keybinds_from_yaml
+ .0
+ .get(&InputMode::Resize)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_tab = keybinds_from_yaml
+ .0
+ .get(&InputMode::Tab)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+
+ assert!(result_normal.is_none());
+ assert!(result_pane.is_none());
+ assert!(result_resize.is_none());
+ assert!(result_tab.is_none());
+}
+
+#[test]
+fn unbind_single_toplevel_single_key_single_mode_identical() {
+ let unbind = Unbind::Keys(vec![Key::Alt('n')]);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbind);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::Keys(vec![Key::Alt('n')]),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+
+ let result_normal = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_pane = keybinds_from_yaml
+ .0
+ .get(&InputMode::Pane)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_resize = keybinds_from_yaml
+ .0
+ .get(&InputMode::Resize)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_tab = keybinds_from_yaml
+ .0
+ .get(&InputMode::Tab)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+
+ assert!(result_normal.is_none());
+ assert!(result_pane.is_none());
+ assert!(result_resize.is_none());
+ assert!(result_tab.is_none());
+}
+
+#[test]
+fn unbind_single_toplevel_single_key_single_mode_differing() {
+ let unbind = Unbind::Keys(vec![Key::Alt('l')]);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbind);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::Keys(vec![Key::Alt('n')]),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+
+ let result_normal_n = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_normal_l = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('l'));
+ let result_resize_n = keybinds_from_yaml
+ .0
+ .get(&InputMode::Resize)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_resize_l = keybinds_from_yaml
+ .0
+ .get(&InputMode::Resize)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('l'));
+
+ assert!(result_normal_n.is_none());
+ assert!(result_normal_l.is_none());
+ assert!(result_resize_n.is_none());
+ assert!(result_resize_l.is_some());
+}
+
+#[test]
+fn unbind_single_toplevel_single_key_multiple_modes() {
+ let unbind = Unbind::Keys(vec![Key::Alt('l')]);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbind.clone());
+ keys.insert(InputMode::Pane, key_action_unbind);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::Keys(vec![Key::Alt('n')]),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+
+ let result_normal_n = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_normal_l = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('l'));
+ let result_pane_n = keybinds_from_yaml
+ .0
+ .get(&InputMode::Pane)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_pane_l = keybinds_from_yaml
+ .0
+ .get(&InputMode::Pane)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('l'));
+
+ assert!(result_normal_n.is_none());
+ assert!(result_normal_l.is_none());
+ assert!(result_pane_n.is_none());
+ assert!(result_pane_l.is_none());
+}
+
+#[test]
+fn unbind_single_toplevel_multiple_keys_single_mode() {
+ let unbind = Unbind::Keys(vec![
+ Key::Alt('l'),
+ Key::Alt('h'),
+ Key::Alt('j'),
+ Key::Alt('k'),
+ ]);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbind.clone());
+ keys.insert(InputMode::Pane, key_action_unbind);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::Keys(vec![Key::Alt('n')]),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+
+ let result_normal_n = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_normal_l = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('l'));
+ let result_normal_k = keybinds_from_yaml
+ .0
+ .get(&InputMode::Pane)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('k'));
+ let result_normal_h = keybinds_from_yaml
+ .0
+ .get(&InputMode::Pane)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('h'));
+
+ assert!(result_normal_n.is_none());
+ assert!(result_normal_l.is_none());
+ assert!(result_normal_h.is_none());
+ assert!(result_normal_k.is_none());
+}
+
+#[test]
+fn unbind_single_toplevel_multiple_keys_multiple_modes() {
+ let unbind_normal = Unbind::Keys(vec![Key::Alt('l'), Key::Ctrl('p')]);
+ let unbind_from_yaml_normal = UnbindFromYaml {
+ unbind: unbind_normal,
+ };
+ let key_action_unbind_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml_normal)];
+ let unbind = Unbind::Keys(vec![Key::Alt('l'), Key::Alt('k')]);
+ let unbind_from_yaml = UnbindFromYaml { unbind };
+ let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbind_normal);
+ keys.insert(InputMode::Pane, key_action_unbind);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::Keys(vec![Key::Alt('n')]),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+
+ let result_normal_n = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_normal_p = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Ctrl('p'));
+ let result_normal_l = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('l'));
+ let result_pane_p = keybinds_from_yaml
+ .0
+ .get(&InputMode::Pane)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Ctrl('p'));
+ let result_pane_n = keybinds_from_yaml
+ .0
+ .get(&InputMode::Pane)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('n'));
+ let result_pane_l = keybinds_from_yaml
+ .0
+ .get(&InputMode::Pane)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Alt('l'));
+
+ assert!(result_normal_n.is_none());
+ assert!(result_normal_l.is_none());
+ assert!(result_normal_p.is_none());
+ assert!(result_pane_n.is_none());
+ assert!(result_pane_p.is_some());
+ assert!(result_pane_l.is_none());
+}
+
+#[test]
+fn uppercase_and_lowercase_are_distinct() {
+ let key_action_n = KeyActionFromYaml {
+ key: vec![Key::Char('n')],
+ action: vec![Action::NewTab],
+ };
+ let key_action_large_n = KeyActionFromYaml {
+ key: vec![Key::Char('N')],
+ action: vec![Action::NewPane(None)],
+ };
+
+ let key_action_unbind = vec![
+ KeyActionUnbind::KeyAction(key_action_n),
+ KeyActionUnbind::KeyAction(key_action_large_n),
+ ];
+ let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new();
+ keys.insert(InputMode::Normal, key_action_unbind);
+ let from_yaml = KeybindsFromYaml {
+ keybinds: keys,
+ unbind: Unbind::All(false),
+ };
+
+ let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml));
+ let result_n = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Char('n'));
+ let result_large_n = keybinds_from_yaml
+ .0
+ .get(&InputMode::Normal)
+ .expect("ModeKeybinds shouldn't be empty")
+ .0
+ .get(&Key::Char('N'));
+
+ assert!(result_n.is_some());
+ assert!(result_large_n.is_some());
+}