diff options
| author | Kunal Mohan <kunalmohan99@gmail.com> | 2021-05-16 16:42:50 +0530 |
|---|---|---|
| committer | Kunal Mohan <kunalmohan99@gmail.com> | 2021-05-16 21:41:56 +0530 |
| commit | 2038947a14d32d89da2bdbcaa1381c3015ac9c7a (patch) | |
| tree | 5a332470e32b380816098594a89d3c3db1f6eb53 /zellij-utils/src/input | |
| parent | handle error on termios initialization (#511) (diff) | |
| download | sl0ppij-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.rs | 64 | ||||
| -rw-r--r-- | zellij-utils/src/input/config.rs | 218 | ||||
| -rw-r--r-- | zellij-utils/src/input/keybinds.rs | 301 | ||||
| -rw-r--r-- | zellij-utils/src/input/mod.rs | 87 | ||||
| -rw-r--r-- | zellij-utils/src/input/options.rs | 45 | ||||
| -rw-r--r-- | zellij-utils/src/input/unit/keybinds_test.rs | 801 |
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()); +} |
