diff --git a/README.md b/README.md index f9bc9fe047..203de8ccf0 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,44 @@ Zellij was initially called "Mosaic". The status bar on the bottom should guide you through the possible keyboard shortcuts in the app. +# Configuration +It is possible to configure keyboard shortcuts and their actions in a yaml file. +An example file can be found under `example/config.yaml`. + +Zellij will look for a file `/zellij/config.yaml` in the default configuration location of your os. + +To pass a config file directly to zellij run it either with: +`cargo run -- config [FILE]` or `zellij config [FILE]`. + +The structure is as follows: +``` +keybinds: + normal: + - action: [] + key: [] +``` +`normal` is one of the `modes` zellij can be in. +It is possible to bind a sequence of actions to numerous keys at the same time. +Here a reference to the [Key](https://docs.rs/termion/1.5.6/termion/event/enum.Key.html) format that is used. + +For example: +``` +keybinds: + normal: + - action: [ NewTab, GoToTab: 1,] + key: [ Char: 'c',] +``` +Will create a new tab and then switch to tab number 1 on pressing the +`c` key. +Whereas: +``` +keybinds: + normal: + - action: [ NewTab,] + key: [ Char: 'c', Char: 'd',] +``` +Will create a new tab on pressing either the `c` or the `d` key. + # What is the current status of the project? Zellij is in the last stages of being VT compatible. As much as modern terminals are. diff --git a/assets/completions/_zellij b/assets/completions/_zellij index 64bd538cd9..fc1cd00f2c 100644 --- a/assets/completions/_zellij +++ b/assets/completions/_zellij @@ -30,16 +30,93 @@ _zellij() { '--help[Prints help information]' \ '-V[Prints version information]' \ '--version[Prints version information]' \ +":: :_zellij_commands" \ +"*::: :->zellij" \ && ret=0 - + case $state in + (zellij) + words=($line[1] "${words[@]}") + (( CURRENT += 1 )) + curcontext="${curcontext%:*:*}:zellij-command-$line[1]:" + case $line[1] in + (c) +_arguments "${_arguments_options[@]}" \ +'--clean[Disables loading of configuration file at default location]' \ +'-h[Prints help information]' \ +'--help[Prints help information]' \ +'-V[Prints version information]' \ +'--version[Prints version information]' \ +'::path:_files' \ +&& ret=0 +;; +(c) +_arguments "${_arguments_options[@]}" \ +'--clean[Disables loading of configuration file at default location]' \ +'-h[Prints help information]' \ +'--help[Prints help information]' \ +'-V[Prints version information]' \ +'--version[Prints version information]' \ +'::path:_files' \ +&& ret=0 +;; +(config) +_arguments "${_arguments_options[@]}" \ +'--clean[Disables loading of configuration file at default location]' \ +'-h[Prints help information]' \ +'--help[Prints help information]' \ +'-V[Prints version information]' \ +'--version[Prints version information]' \ +'::path:_files' \ +&& ret=0 +;; +(help) +_arguments "${_arguments_options[@]}" \ +'-h[Prints help information]' \ +'--help[Prints help information]' \ +'-V[Prints version information]' \ +'--version[Prints version information]' \ +&& ret=0 +;; + esac + ;; +esac } (( $+functions[_zellij_commands] )) || _zellij_commands() { local commands; commands=( - + "config:Path to the configuration yaml file" \ +"help:Prints this message or the help of the given subcommand(s)" \ ) _describe -t commands 'zellij commands' commands "$@" } +(( $+functions[_c_commands] )) || +_c_commands() { + local commands; commands=( + + ) + _describe -t commands 'c commands' commands "$@" +} +(( $+functions[_zellij__c_commands] )) || +_zellij__c_commands() { + local commands; commands=( + + ) + _describe -t commands 'zellij c commands' commands "$@" +} +(( $+functions[_zellij__config_commands] )) || +_zellij__config_commands() { + local commands; commands=( + + ) + _describe -t commands 'zellij config commands' commands "$@" +} +(( $+functions[_zellij__help_commands] )) || +_zellij__help_commands() { + local commands; commands=( + + ) + _describe -t commands 'zellij help commands' commands "$@" +} _zellij "$@" \ No newline at end of file diff --git a/assets/completions/zellij.bash b/assets/completions/zellij.bash index a921e7c990..10c18d5324 100644 --- a/assets/completions/zellij.bash +++ b/assets/completions/zellij.bash @@ -13,6 +13,15 @@ _zellij() { cmd="zellij" ;; + c) + cmd+="__c" + ;; + config) + cmd+="__config" + ;; + help) + cmd+="__help" + ;; *) ;; esac @@ -20,7 +29,7 @@ _zellij() { case "${cmd}" in zellij) - opts=" -m -d -h -V -s -o -l --move-focus --debug --help --version --split --open-file --max-panes --layout " + opts=" -m -d -h -V -s -o -l --move-focus --debug --help --version --split --open-file --max-panes --layout config help c c" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -63,6 +72,51 @@ _zellij() { return 0 ;; + zellij__c) + opts=" -h -V --clean --help --version " + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + zellij__config) + opts=" -h -V --clean --help --version " + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + zellij__help) + opts=" -h -V --help --version " + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; esac } diff --git a/assets/completions/zellij.fish b/assets/completions/zellij.fish index e902823fcd..36bab1924a 100644 --- a/assets/completions/zellij.fish +++ b/assets/completions/zellij.fish @@ -6,3 +6,10 @@ complete -c zellij -n "__fish_use_subcommand" -s m -l move-focus -d 'Send "move complete -c zellij -n "__fish_use_subcommand" -s d -l debug complete -c zellij -n "__fish_use_subcommand" -s h -l help -d 'Prints help information' complete -c zellij -n "__fish_use_subcommand" -s V -l version -d 'Prints version information' +complete -c zellij -n "__fish_use_subcommand" -f -a "config" -d 'Path to the configuration yaml file' +complete -c zellij -n "__fish_use_subcommand" -f -a "help" -d 'Prints this message or the help of the given subcommand(s)' +complete -c zellij -n "__fish_seen_subcommand_from config" -l clean -d 'Disables loading of configuration file at default location' +complete -c zellij -n "__fish_seen_subcommand_from config" -s h -l help -d 'Prints help information' +complete -c zellij -n "__fish_seen_subcommand_from config" -s V -l version -d 'Prints version information' +complete -c zellij -n "__fish_seen_subcommand_from help" -s h -l help -d 'Prints help information' +complete -c zellij -n "__fish_seen_subcommand_from help" -s V -l version -d 'Prints version information' diff --git a/example/config.yaml b/example/config.yaml new file mode 100644 index 0000000000..3bb4fbfada --- /dev/null +++ b/example/config.yaml @@ -0,0 +1,26 @@ +--- +keybinds: + normal: + - action: [GoToTab: 1,] + key: [F: 1,] + - action: [GoToTab: 2,] + key: [F: 2,] + - action: [GoToTab: 3,] + key: [F: 3,] + - action: [GoToTab: 4,] + key: [F: 4,] + - action: [NewTab,] + key: [F: 5,] + - action: [MoveFocus: Left,] + key: [ Alt: h,] + - action: [MoveFocus: Right,] + key: [ Alt: l,] + - action: [MoveFocus: Down,] + key: [ Alt: j,] + - action: [MoveFocus: Up,] + key: [ Alt: k,] + pane: + - action: [ NewPane:, SwitchToMode: Normal,] + key: [Char: 'n',] + - action: [ NewPane: , ] + key: [Char: 'N',] diff --git a/src/cli.rs b/src/cli.rs index 6e384e16b1..b1fb8f3895 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use structopt::StructOpt; -#[derive(StructOpt, Debug, Default)] +#[derive(StructOpt, Default, Debug)] #[structopt(name = "zellij")] pub struct CliArgs { /// Send "split (direction h == horizontal / v == vertical)" to active zellij session @@ -24,6 +24,21 @@ pub struct CliArgs { #[structopt(short, long)] pub layout: Option, + #[structopt(subcommand)] + pub config: Option, + #[structopt(short, long)] pub debug: bool, } + +#[derive(Debug, StructOpt)] +pub enum ConfigCli { + /// Path to the configuration yaml file + #[structopt(alias = "c")] + Config { + path: Option, + #[structopt(long)] + /// Disables loading of configuration file at default location + clean: bool, + }, +} diff --git a/src/common/input/actions.rs b/src/common/input/actions.rs index f422686454..ac923f7ddb 100644 --- a/src/common/input/actions.rs +++ b/src/common/input/actions.rs @@ -1,9 +1,10 @@ //! 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)] +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum Direction { Left, Right, @@ -12,7 +13,7 @@ pub enum Direction { } /// Actions that can be bound to keys. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum Action { /// Quit Zellij. Quit, diff --git a/src/common/input/config.rs b/src/common/input/config.rs new file mode 100644 index 0000000000..4bb9e10f3e --- /dev/null +++ b/src/common/input/config.rs @@ -0,0 +1,158 @@ +//! 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 crate::cli::ConfigCli; + +use directories_next::ProjectDirs; +use serde::Deserialize; + +type ConfigResult = Result; + +/// Intermediate deserialisation config struct +#[derive(Debug, Deserialize)] +pub struct ConfigFromYaml { + pub keybinds: Option, +} + +/// Main configuration. +#[derive(Debug, Clone, PartialEq)] +pub struct Config { + pub keybinds: Keybinds, +} + +#[derive(Debug)] +pub enum ConfigError { + // Deserialisation error + Serde(serde_yaml::Error), + // Io error + Io(io::Error), + // Io error with path context + IoPath(io::Error, PathBuf), +} + +impl Default for Config { + fn default() -> Self { + let keybinds = Keybinds::default(); + Config { keybinds } + } +} + +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); + Ok(Config { keybinds }) + } + + /// Deserializes from given path. + #[allow(unused_must_use)] + 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())), + } + } + + /// Deserializes the config from a default platform specific path, + /// merges the default configuration - options take precedence. + fn from_default_path() -> ConfigResult { + let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap(); + let mut config_path: PathBuf = project_dirs.config_dir().to_owned(); + config_path.push("config.yaml"); + + match Config::new(&config_path) { + Ok(config) => Ok(config), + Err(ConfigError::IoPath(_, _)) => Ok(Config::default()), + Err(e) => Err(e), + } + } + + /// Entry point of the configuration + #[cfg(not(test))] + pub fn from_cli_config(cli_config: Option) -> ConfigResult { + match cli_config { + Some(ConfigCli::Config { clean, .. }) if clean => Ok(Config::default()), + Some(ConfigCli::Config { path, .. }) if path.is_some() => { + Ok(Config::new(&path.unwrap())?) + } + Some(_) | None => Ok(Config::from_default_path()?), + } + } + + //#[allow(unused_must_use)] + /// In order not to mess up tests from changing configurations + #[cfg(test)] + pub fn from_cli_config(_: Option) -> ConfigResult { + Ok(Config::default()) + } +} + +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, "Deserialisation error: {}", 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), + } + } +} + +impl From for ConfigError { + fn from(err: io::Error) -> ConfigError { + ConfigError::Io(err) + } +} + +impl From for ConfigError { + fn from(err: serde_yaml::Error) -> ConfigError { + ConfigError::Serde(err) + } +} + +// The unit test location. +#[cfg(test)] +mod config_test { + use super::*; + + #[test] + fn clean_option_equals_default_config() { + let no_file = PathBuf::from(r"../fixtures/config/config.yamlll"); + let cli_config = ConfigCli::Config { + path: Some(no_file), + clean: true, + }; + let config = Config::from_cli_config(Some(cli_config)).unwrap(); + let default = Config::default(); + assert_eq!(config, default); + } + + #[test] + fn no_config_option_file_equals_default_config() { + let config = Config::from_cli_config(None).unwrap(); + let default = Config::default(); + assert_eq!(config, default); + } +} diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs index b75d267e89..2020fc209d 100644 --- a/src/common/input/handler.rs +++ b/src/common/input/handler.rs @@ -1,7 +1,8 @@ //! Main input logic. use super::actions::Action; -use super::keybinds::get_default_keybinds; +use super::keybinds::Keybinds; +use crate::common::input::config::Config; use crate::common::{AppInstruction, SenderWithContext, OPENCALLS}; use crate::errors::ContextType; use crate::os_input_output::OsApi; @@ -13,14 +14,13 @@ use crate::CommandIsExecuting; use termion::input::{TermRead, TermReadEventsAndRaw}; use zellij_tile::data::{Event, InputMode, Key, ModeInfo}; -use super::keybinds::key_to_actions; - /// Handles the dispatching of [`Action`]s according to the current /// [`InputMode`], and keep tracks of the current [`InputMode`]. struct InputHandler { /// The current input mode mode: InputMode, os_input: Box, + config: Config, command_is_executing: CommandIsExecuting, send_screen_instructions: SenderWithContext, send_pty_instructions: SenderWithContext, @@ -33,6 +33,7 @@ impl InputHandler { fn new( os_input: Box, command_is_executing: CommandIsExecuting, + config: Config, send_screen_instructions: SenderWithContext, send_pty_instructions: SenderWithContext, send_plugin_instructions: SenderWithContext, @@ -41,6 +42,7 @@ impl InputHandler { InputHandler { mode: InputMode::Normal, os_input, + config, command_is_executing, send_screen_instructions, send_pty_instructions, @@ -57,41 +59,37 @@ impl InputHandler { self.send_pty_instructions.update(err_ctx); self.send_app_instructions.update(err_ctx); self.send_screen_instructions.update(err_ctx); - if let Ok(keybinds) = get_default_keybinds() { - 'input_loop: loop { - //@@@ I think this should actually just iterate over stdin directly - let stdin_buffer = self.os_input.read_from_stdin(); - for key_result in stdin_buffer.events_and_raw() { - match key_result { - Ok((event, raw_bytes)) => match event { - termion::event::Event::Key(key) => { - let key = cast_termion_key(key); - // FIXME this explicit break is needed because the current test - // framework relies on it to not create dead threads that loop - // and eat up CPUs. Do not remove until the test framework has - // been revised. Sorry about this (@categorille) - let mut should_break = false; - for action in key_to_actions(&key, raw_bytes, &self.mode, &keybinds) - { - should_break |= self.dispatch_action(action); - } - if should_break { - break 'input_loop; - } + let keybinds = self.config.keybinds.clone(); + 'input_loop: loop { + //@@@ I think this should actually just iterate over stdin directly + let stdin_buffer = self.os_input.read_from_stdin(); + for key_result in stdin_buffer.events_and_raw() { + match key_result { + Ok((event, raw_bytes)) => match event { + termion::event::Event::Key(key) => { + let key = cast_termion_key(key); + // FIXME this explicit break is needed because the current test + // framework relies on it to not create dead threads that loop + // and eat up CPUs. Do not remove until the test framework has + // been revised. Sorry about this (@categorille) + let mut should_break = false; + for action in + Keybinds::key_to_actions(&key, raw_bytes, &self.mode, &keybinds) + { + should_break |= self.dispatch_action(action); } - termion::event::Event::Mouse(_) - | termion::event::Event::Unsupported(_) => { - // Mouse and unsupported events aren't implemented yet, - // use a NoOp untill then. + if should_break { + break 'input_loop; } - }, - Err(err) => panic!("Encountered read error: {:?}", err), - } + } + termion::event::Event::Mouse(_) | termion::event::Event::Unsupported(_) => { + // Mouse and unsupported events aren't implemented yet, + // use a NoOp untill then. + } + }, + Err(err) => panic!("Encountered read error: {:?}", err), } } - } else { - //@@@ Error handling? - self.exit(); } } @@ -291,6 +289,7 @@ pub fn get_mode_info(mode: InputMode) -> ModeInfo { /// its [`InputHandler::handle_input()`] loop. pub fn input_loop( os_input: Box, + config: Config, command_is_executing: CommandIsExecuting, send_screen_instructions: SenderWithContext, send_pty_instructions: SenderWithContext, @@ -300,6 +299,7 @@ pub fn input_loop( let _handler = InputHandler::new( os_input, command_is_executing, + config, send_screen_instructions, send_pty_instructions, send_plugin_instructions, diff --git a/src/common/input/keybinds.rs b/src/common/input/keybinds.rs index 7b1e8779ab..6a14a18232 100644 --- a/src/common/input/keybinds.rs +++ b/src/common/input/keybinds.rs @@ -1,268 +1,368 @@ //! Mapping of inputs to sequences of actions. +use std::collections::HashMap; use super::actions::{Action, Direction}; -use std::collections::HashMap; - +use serde::Deserialize; use strum::IntoEnumIterator; use zellij_tile::data::*; -type Keybinds = HashMap; -type ModeKeybinds = HashMap>; +#[derive(Clone, Debug, PartialEq)] +pub struct Keybinds(HashMap); +#[derive(Clone, Debug, Default, PartialEq)] +pub struct ModeKeybinds(HashMap>); -/// Populates the default hashmap of keybinds. -/// @@@khs26 What about an input config file? -pub fn get_default_keybinds() -> Result { - let mut defaults = Keybinds::new(); +/// Intermediate struct used for deserialisation +#[derive(Clone, Debug, PartialEq, Deserialize)] +pub struct KeybindsFromYaml(HashMap>); - for mode in InputMode::iter() { - defaults.insert(mode, get_defaults_for_mode(&mode)); - } - - Ok(defaults) +/// Intermediate struct used for deserialisation +#[derive(Clone, Debug, PartialEq, Deserialize)] +pub struct KeyActionFromYaml { + action: Vec, + key: Vec, } -/// Returns the default keybinds for a givent [`InputMode`]. -fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds { - let mut defaults = ModeKeybinds::new(); - - match *mode { - InputMode::Normal => { - defaults.insert( - Key::Ctrl('g'), - vec![Action::SwitchToMode(InputMode::Locked)], - ); - defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]); - defaults.insert( - Key::Ctrl('r'), - vec![Action::SwitchToMode(InputMode::Resize)], - ); - defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]); - defaults.insert( - Key::Ctrl('s'), - vec![Action::SwitchToMode(InputMode::Scroll)], - ); - defaults.insert(Key::Ctrl('q'), vec![Action::Quit]); - } - InputMode::Locked => { - defaults.insert( - Key::Ctrl('g'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - } - InputMode::Resize => { - defaults.insert( - Key::Ctrl('g'), - vec![Action::SwitchToMode(InputMode::Locked)], - ); - defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]); - defaults.insert( - Key::Ctrl('r'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]); - defaults.insert( - Key::Ctrl('s'), - vec![Action::SwitchToMode(InputMode::Scroll)], - ); - defaults.insert(Key::Ctrl('q'), vec![Action::Quit]); - defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]); - defaults.insert( - Key::Char('\n'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - defaults.insert( - Key::Char(' '), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - - defaults.insert(Key::Char('h'), vec![Action::Resize(Direction::Left)]); - defaults.insert(Key::Char('j'), vec![Action::Resize(Direction::Down)]); - defaults.insert(Key::Char('k'), vec![Action::Resize(Direction::Up)]); - defaults.insert(Key::Char('l'), vec![Action::Resize(Direction::Right)]); - - defaults.insert(Key::Left, vec![Action::Resize(Direction::Left)]); - defaults.insert(Key::Down, vec![Action::Resize(Direction::Down)]); - defaults.insert(Key::Up, vec![Action::Resize(Direction::Up)]); - defaults.insert(Key::Right, vec![Action::Resize(Direction::Right)]); +impl Default for Keybinds { + fn default() -> Keybinds { + let mut defaults = Keybinds::new(); + + for mode in InputMode::iter() { + defaults + .0 + .insert(mode, Keybinds::get_defaults_for_mode(&mode)); } - InputMode::Pane => { - defaults.insert( - Key::Ctrl('g'), - vec![Action::SwitchToMode(InputMode::Locked)], - ); - defaults.insert( - Key::Ctrl('p'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - defaults.insert( - Key::Ctrl('r'), - vec![Action::SwitchToMode(InputMode::Resize)], - ); - defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]); - defaults.insert( - Key::Ctrl('s'), - vec![Action::SwitchToMode(InputMode::Scroll)], - ); - defaults.insert(Key::Ctrl('q'), vec![Action::Quit]); - defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]); - defaults.insert( - Key::Char('\n'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - defaults.insert( - Key::Char(' '), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - - defaults.insert(Key::Char('h'), vec![Action::MoveFocus(Direction::Left)]); - defaults.insert(Key::Char('j'), vec![Action::MoveFocus(Direction::Down)]); - defaults.insert(Key::Char('k'), vec![Action::MoveFocus(Direction::Up)]); - defaults.insert(Key::Char('l'), vec![Action::MoveFocus(Direction::Right)]); - - defaults.insert(Key::Left, vec![Action::MoveFocus(Direction::Left)]); - defaults.insert(Key::Down, vec![Action::MoveFocus(Direction::Down)]); - defaults.insert(Key::Up, vec![Action::MoveFocus(Direction::Up)]); - defaults.insert(Key::Right, vec![Action::MoveFocus(Direction::Right)]); - - defaults.insert(Key::Char('p'), vec![Action::SwitchFocus(Direction::Right)]); - defaults.insert(Key::Char('n'), vec![Action::NewPane(None)]); - defaults.insert(Key::Char('d'), vec![Action::NewPane(Some(Direction::Down))]); - defaults.insert( - Key::Char('r'), - vec![Action::NewPane(Some(Direction::Right))], - ); - defaults.insert(Key::Char('x'), vec![Action::CloseFocus]); - defaults.insert(Key::Char('f'), vec![Action::ToggleFocusFullscreen]); + defaults + } +} + +impl Keybinds { + pub fn new() -> Keybinds { + Keybinds(HashMap::::new()) + } + + pub fn get_default_keybinds_with_config(keybinds: Option) -> Keybinds { + let default_keybinds = Keybinds::default(); + if let Some(keybinds) = keybinds { + default_keybinds.merge_keybinds(Keybinds::from(keybinds)) + } else { + default_keybinds } - InputMode::Tab => { - defaults.insert( - Key::Ctrl('g'), - vec![Action::SwitchToMode(InputMode::Locked)], - ); - defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]); - defaults.insert( - Key::Ctrl('r'), - vec![Action::SwitchToMode(InputMode::Resize)], - ); - defaults.insert( - Key::Ctrl('t'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - defaults.insert( - Key::Ctrl('s'), - vec![Action::SwitchToMode(InputMode::Scroll)], - ); - defaults.insert(Key::Ctrl('q'), vec![Action::Quit]); - defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]); - defaults.insert( - Key::Char('\n'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - defaults.insert( - Key::Char(' '), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - - defaults.insert(Key::Char('h'), vec![Action::GoToPreviousTab]); - defaults.insert(Key::Char('j'), vec![Action::GoToNextTab]); - defaults.insert(Key::Char('k'), vec![Action::GoToPreviousTab]); - defaults.insert(Key::Char('l'), vec![Action::GoToNextTab]); - - defaults.insert(Key::Left, vec![Action::GoToPreviousTab]); - defaults.insert(Key::Down, vec![Action::GoToNextTab]); - defaults.insert(Key::Up, vec![Action::GoToPreviousTab]); - defaults.insert(Key::Right, vec![Action::GoToNextTab]); - - defaults.insert(Key::Char('n'), vec![Action::NewTab]); - defaults.insert(Key::Char('x'), vec![Action::CloseTab]); - - defaults.insert( - Key::Char('r'), - vec![ - Action::SwitchToMode(InputMode::RenameTab), - Action::TabNameInput(vec![0]), - ], - ); - defaults.insert(Key::Char('q'), vec![Action::Quit]); - defaults.insert( - Key::Ctrl('g'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - for i in '1'..='9' { - defaults.insert(Key::Char(i), vec![Action::GoToTab(i.to_digit(10).unwrap())]); + } + + /// 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); } } - InputMode::Scroll => { - defaults.insert( - Key::Ctrl('g'), - vec![Action::SwitchToMode(InputMode::Locked)], - ); - defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]); - defaults.insert( - Key::Ctrl('r'), - vec![Action::SwitchToMode(InputMode::Resize)], - ); - defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]); - defaults.insert( - Key::Ctrl('s'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - defaults.insert(Key::Ctrl('q'), vec![Action::Quit]); - defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]); - defaults.insert( - Key::Char('\n'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - defaults.insert( - Key::Char(' '), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - - defaults.insert(Key::Char('j'), vec![Action::ScrollDown]); - defaults.insert(Key::Char('k'), vec![Action::ScrollUp]); - - defaults.insert(Key::Down, vec![Action::ScrollDown]); - defaults.insert(Key::Up, vec![Action::ScrollUp]); + keybinds + } + + /// Returns the default keybinds for a given [`InputMode`]. + fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds { + let mut defaults = HashMap::new(); + + match *mode { + InputMode::Normal => { + defaults.insert( + Key::Ctrl('g'), + vec![Action::SwitchToMode(InputMode::Locked)], + ); + defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]); + defaults.insert( + Key::Ctrl('r'), + vec![Action::SwitchToMode(InputMode::Resize)], + ); + defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]); + defaults.insert( + Key::Ctrl('s'), + vec![Action::SwitchToMode(InputMode::Scroll)], + ); + defaults.insert(Key::Ctrl('q'), vec![Action::Quit]); + } + InputMode::Locked => { + defaults.insert( + Key::Ctrl('g'), + vec![Action::SwitchToMode(InputMode::Normal)], + ); + } + InputMode::Resize => { + defaults.insert( + Key::Ctrl('g'), + vec![Action::SwitchToMode(InputMode::Locked)], + ); + defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]); + defaults.insert( + Key::Ctrl('r'), + vec![Action::SwitchToMode(InputMode::Normal)], + ); + defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]); + defaults.insert( + Key::Ctrl('s'), + vec![Action::SwitchToMode(InputMode::Scroll)], + ); + defaults.insert(Key::Ctrl('q'), vec![Action::Quit]); + defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]); + defaults.insert( + Key::Char('\n'), + vec![Action::SwitchToMode(InputMode::Normal)], + ); + defaults.insert( + Key::Char(' '), + vec![Action::SwitchToMode(InputMode::Normal)], + ); + + defaults.insert(Key::Char('h'), vec![Action::Resize(Direction::Left)]); + defaults.insert(Key::Char('j'), vec![Action::Resize(Direction::Down)]); + defaults.insert(Key::Char('k'), vec![Action::Resize(Direction::Up)]); + defaults.insert(Key::Char('l'), vec![Action::Resize(Direction::Right)]); + + defaults.insert(Key::Left, vec![Action::Resize(Direction::Left)]); + defaults.insert(Key::Down, vec![Action::Resize(Direction::Down)]); + defaults.insert(Key::Up, vec![Action::Resize(Direction::Up)]); + defaults.insert(Key::Right, vec![Action::Resize(Direction::Right)]); + } + InputMode::Pane => { + defaults.insert( + Key::Ctrl('g'), + vec![Action::SwitchToMode(InputMode::Locked)], + ); + defaults.insert( + Key::Ctrl('p'), + vec![Action::SwitchToMode(InputMode::Normal)], + ); + defaults.insert( + Key::Ctrl('r'), + vec![Action::SwitchToMode(InputMode::Resize)], + ); + defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]); + defaults.insert( + Key::Ctrl('s'), + vec![Action::SwitchToMode(InputMode::Scroll)], + ); + defaults.insert(Key::Ctrl('q'), vec![Action::Quit]); + defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]); + defaults.insert( + Key::Char('\n'), + vec![Action::SwitchToMode(InputMode::Normal)], + ); + defaults.insert( + Key::Char(' '), + vec![Action::SwitchToMode(InputMode::Normal)], + ); + + defaults.insert(Key::Char('h'), vec![Action::MoveFocus(Direction::Left)]); + defaults.insert(Key::Char('j'), vec![Action::MoveFocus(Direction::Down)]); + defaults.insert(Key::Char('k'), vec![Action::MoveFocus(Direction::Up)]); + defaults.insert(Key::Char('l'), vec![Action::MoveFocus(Direction::Right)]); + + defaults.insert(Key::Left, vec![Action::MoveFocus(Direction::Left)]); + defaults.insert(Key::Down, vec![Action::MoveFocus(Direction::Down)]); + defaults.insert(Key::Up, vec![Action::MoveFocus(Direction::Up)]); + defaults.insert(Key::Right, vec![Action::MoveFocus(Direction::Right)]); + + defaults.insert(Key::Char('p'), vec![Action::SwitchFocus(Direction::Right)]); + defaults.insert(Key::Char('n'), vec![Action::NewPane(None)]); + defaults.insert(Key::Char('d'), vec![Action::NewPane(Some(Direction::Down))]); + defaults.insert( + Key::Char('r'), + vec![Action::NewPane(Some(Direction::Right))], + ); + defaults.insert(Key::Char('x'), vec![Action::CloseFocus]); + defaults.insert(Key::Char('f'), vec![Action::ToggleFocusFullscreen]); + } + InputMode::Tab => { + defaults.insert( + Key::Ctrl('g'), + vec![Action::SwitchToMode(InputMode::Locked)], + ); + defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]); + defaults.insert( + Key::Ctrl('r'), + vec![Action::SwitchToMode(InputMode::Resize)], + ); + defaults.insert( + Key::Ctrl('t'), + vec![Action::SwitchToMode(InputMode::Normal)], + ); + defaults.insert( + Key::Ctrl('s'), + vec![Action::SwitchToMode(InputMode::Scroll)], + ); + defaults.insert(Key::Ctrl('q'), vec![Action::Quit]); + defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]); + defaults.insert( + Key::Char('\n'), + vec![Action::SwitchToMode(InputMode::Normal)], + ); + defaults.insert( + Key::Char(' '), + vec![Action::SwitchToMode(InputMode::Normal)], + ); + + defaults.insert(Key::Char('h'), vec![Action::GoToPreviousTab]); + defaults.insert(Key::Char('j'), vec![Action::GoToNextTab]); + defaults.insert(Key::Char('k'), vec![Action::GoToPreviousTab]); + defaults.insert(Key::Char('l'), vec![Action::GoToNextTab]); + + defaults.insert(Key::Left, vec![Action::GoToPreviousTab]); + defaults.insert(Key::Down, vec![Action::GoToNextTab]); + defaults.insert(Key::Up, vec![Action::GoToPreviousTab]); + defaults.insert(Key::Right, vec![Action::GoToNextTab]); + + defaults.insert(Key::Char('n'), vec![Action::NewTab]); + defaults.insert(Key::Char('x'), vec![Action::CloseTab]); + + defaults.insert( + Key::Char('r'), + vec![ + Action::SwitchToMode(InputMode::RenameTab), + Action::TabNameInput(vec![0]), + ], + ); + defaults.insert(Key::Char('q'), vec![Action::Quit]); + defaults.insert( + Key::Ctrl('g'), + vec![Action::SwitchToMode(InputMode::Normal)], + ); + for i in '1'..='9' { + defaults.insert(Key::Char(i), vec![Action::GoToTab(i.to_digit(10).unwrap())]); + } + } + InputMode::Scroll => { + defaults.insert( + Key::Ctrl('g'), + vec![Action::SwitchToMode(InputMode::Locked)], + ); + defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]); + defaults.insert( + Key::Ctrl('r'), + vec![Action::SwitchToMode(InputMode::Resize)], + ); + defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]); + defaults.insert( + Key::Ctrl('s'), + vec![Action::SwitchToMode(InputMode::Normal)], + ); + defaults.insert(Key::Ctrl('q'), vec![Action::Quit]); + defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]); + defaults.insert( + Key::Char('\n'), + vec![Action::SwitchToMode(InputMode::Normal)], + ); + defaults.insert( + Key::Char(' '), + vec![Action::SwitchToMode(InputMode::Normal)], + ); + + defaults.insert(Key::Char('j'), vec![Action::ScrollDown]); + defaults.insert(Key::Char('k'), vec![Action::ScrollUp]); + + defaults.insert(Key::Down, vec![Action::ScrollDown]); + defaults.insert(Key::Up, vec![Action::ScrollUp]); + } + InputMode::RenameTab => { + defaults.insert(Key::Char('\n'), vec![Action::SwitchToMode(InputMode::Tab)]); + defaults.insert( + Key::Ctrl('g'), + vec![Action::SwitchToMode(InputMode::Normal)], + ); + defaults.insert( + Key::Esc, + vec![ + Action::TabNameInput(vec![0x1b]), + Action::SwitchToMode(InputMode::Tab), + ], + ); + } } - InputMode::RenameTab => { - defaults.insert(Key::Char('\n'), vec![Action::SwitchToMode(InputMode::Tab)]); - defaults.insert( - Key::Ctrl('g'), - vec![Action::SwitchToMode(InputMode::Normal)], - ); - defaults.insert( - Key::Esc, - vec![ - Action::TabNameInput(vec![0x1b]), - Action::SwitchToMode(InputMode::Tab), - ], - ); + ModeKeybinds(defaults) + } + + /// 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, + mode: &InputMode, + keybinds: &Keybinds, + ) -> Vec { + 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::>::new()) + } - defaults + /// 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 + } } -/// 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, - mode: &InputMode, - keybinds: &Keybinds, -) -> Vec { - let mode_keybind_or_action = |action: Action| { +impl From 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.0.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 - .get(mode) - .unwrap_or_else(|| unreachable!("Unrecognized mode: {:?}", mode)) - .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), } } + +/// For each `Key` assigned to `Action`s, +/// map the `Action`s to the key +impl From for ModeKeybinds { + fn from(key_action: KeyActionFromYaml) -> ModeKeybinds { + let keys = key_action.key; + let actions = key_action.action; + + ModeKeybinds( + keys.into_iter() + .map(|k| (k, actions.clone())) + .collect::>>(), + ) + } +} + +// The unit test location. +#[cfg(test)] +#[path = "./unit/keybinds_test.rs"] +mod keybinds_test; diff --git a/src/common/input/mod.rs b/src/common/input/mod.rs index 20c30daec1..6589cac2c4 100644 --- a/src/common/input/mod.rs +++ b/src/common/input/mod.rs @@ -1,5 +1,6 @@ -//! The way terminal iput is handled. +//! The way terminal input is handled. pub mod actions; +pub mod config; pub mod handler; pub mod keybinds; diff --git a/src/common/input/unit/keybinds_test.rs b/src/common/input/unit/keybinds_test.rs new file mode 100644 index 0000000000..6411d01747 --- /dev/null +++ b/src/common/input/unit/keybinds_test.rs @@ -0,0 +1,135 @@ +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 from_keybinds_from_yaml_to_keybinds(){ +//let mut keybinds_from_yaml = KeybindsFromYaml(HashMap>); +//let actions = vec![Action::NoOp, Action::GoToTab(1), ]; +//let keyaction = KeyActionFromYaml { +//action : actions.clone(), +//key : vec![ Key::F(1), Key::Backspace , Key::Char('t'), ], +//}; +//} diff --git a/src/common/mod.rs b/src/common/mod.rs index 87d8cd9419..e9b74d58a5 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -22,6 +22,7 @@ use std::{ }; use crate::cli::CliArgs; +use crate::common::input::config::Config; use crate::layout::Layout; use crate::panes::PaneId; use command_is_executing::CommandIsExecuting; @@ -125,6 +126,13 @@ pub fn start(mut os_input: Box, opts: CliArgs) { .write(take_snapshot.as_bytes()) .unwrap(); + let config = Config::from_cli_config(opts.config) + .map_err(|e| { + eprintln!("There was an error in the config file:\n{}", e); + std::process::exit(1); + }) + .unwrap(); + let command_is_executing = CommandIsExecuting::new(); let full_screen_ws = os_input.get_terminal_size_using_fd(0); @@ -590,9 +598,11 @@ pub fn start(mut os_input: Box, opts: CliArgs) { let send_pty_instructions = send_pty_instructions.clone(); let send_plugin_instructions = send_plugin_instructions.clone(); let os_input = os_input.clone(); + let config = config; move || { input_loop( os_input, + config, command_is_executing, send_screen_instructions, send_pty_instructions, diff --git a/zellij-tile/src/data.rs b/zellij-tile/src/data.rs index 9576285a88..85d12245bc 100644 --- a/zellij-tile/src/data.rs +++ b/zellij-tile/src/data.rs @@ -37,18 +37,25 @@ pub enum Event { pub enum InputMode { /// In `Normal` mode, input is always written to the terminal, except for the shortcuts leading /// to other modes + #[serde(alias = "normal")] Normal, /// In `Locked` mode, input is always written to the terminal and all shortcuts are disabled /// except the one leading back to normal mode + #[serde(alias = "locked")] Locked, /// `Resize` mode allows resizing the different existing panes. + #[serde(alias = "resize")] Resize, /// `Pane` mode allows creating and closing panes, as well as moving between them. + #[serde(alias = "pane")] Pane, /// `Tab` mode allows creating and closing tabs, as well as moving between them. + #[serde(alias = "tab")] Tab, /// `Scroll` mode allows scrolling up and down within a pane. + #[serde(alias = "scroll")] Scroll, + #[serde(alias = "renametab")] RenameTab, }