Merge pull request #224 from a-kenji/config-file

Add: Config file
This commit is contained in:
a-kenji 2021-04-05 12:01:23 +02:00 committed by GitHub
commit 381b63d357
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 917 additions and 288 deletions

View file

@ -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. 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? # What is the current status of the project?
Zellij is in the last stages of being VT compatible. As much as modern terminals are. Zellij is in the last stages of being VT compatible. As much as modern terminals are.

View file

@ -30,16 +30,93 @@ _zellij() {
'--help[Prints help information]' \ '--help[Prints help information]' \
'-V[Prints version information]' \ '-V[Prints version information]' \
'--version[Prints version information]' \ '--version[Prints version information]' \
":: :_zellij_commands" \
"*::: :->zellij" \
&& ret=0 && 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] )) || (( $+functions[_zellij_commands] )) ||
_zellij_commands() { _zellij_commands() {
local commands; 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 "$@" _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 "$@" _zellij "$@"

View file

@ -13,6 +13,15 @@ _zellij() {
cmd="zellij" cmd="zellij"
;; ;;
c)
cmd+="__c"
;;
config)
cmd+="__config"
;;
help)
cmd+="__help"
;;
*) *)
;; ;;
esac esac
@ -20,7 +29,7 @@ _zellij() {
case "${cmd}" in case "${cmd}" in
zellij) 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 if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0 return 0
@ -63,6 +72,51 @@ _zellij() {
return 0 return 0
;; ;;
zellij__c)
opts=" -h -V --clean --help --version <path> "
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 <path> "
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 esac
} }

View file

@ -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 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 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" -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'

26
example/config.yaml Normal file
View file

@ -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',]

View file

@ -1,7 +1,7 @@
use std::path::PathBuf; use std::path::PathBuf;
use structopt::StructOpt; use structopt::StructOpt;
#[derive(StructOpt, Debug, Default)] #[derive(StructOpt, Default, Debug)]
#[structopt(name = "zellij")] #[structopt(name = "zellij")]
pub struct CliArgs { pub struct CliArgs {
/// Send "split (direction h == horizontal / v == vertical)" to active zellij session /// Send "split (direction h == horizontal / v == vertical)" to active zellij session
@ -24,6 +24,21 @@ pub struct CliArgs {
#[structopt(short, long)] #[structopt(short, long)]
pub layout: Option<PathBuf>, pub layout: Option<PathBuf>,
#[structopt(subcommand)]
pub config: Option<ConfigCli>,
#[structopt(short, long)] #[structopt(short, long)]
pub debug: bool, pub debug: bool,
} }
#[derive(Debug, StructOpt)]
pub enum ConfigCli {
/// Path to the configuration yaml file
#[structopt(alias = "c")]
Config {
path: Option<PathBuf>,
#[structopt(long)]
/// Disables loading of configuration file at default location
clean: bool,
},
}

View file

@ -1,9 +1,10 @@
//! Definition of the actions that can be bound to keys. //! Definition of the actions that can be bound to keys.
use serde::{Deserialize, Serialize};
use zellij_tile::data::InputMode; use zellij_tile::data::InputMode;
/// The four directions (left, right, up, down). /// The four directions (left, right, up, down).
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum Direction { pub enum Direction {
Left, Left,
Right, Right,
@ -12,7 +13,7 @@ pub enum Direction {
} }
/// Actions that can be bound to keys. /// Actions that can be bound to keys.
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum Action { pub enum Action {
/// Quit Zellij. /// Quit Zellij.
Quit, Quit,

158
src/common/input/config.rs Normal file
View file

@ -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<Config, ConfigError>;
/// Intermediate deserialisation config struct
#[derive(Debug, Deserialize)]
pub struct ConfigFromYaml {
pub keybinds: Option<KeybindsFromYaml>,
}
/// 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<ConfigCli>) -> 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<ConfigCli>) -> 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<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)
}
}
// 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);
}
}

View file

@ -1,7 +1,8 @@
//! Main input logic. //! Main input logic.
use super::actions::Action; 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::common::{AppInstruction, SenderWithContext, OPENCALLS};
use crate::errors::ContextType; use crate::errors::ContextType;
use crate::os_input_output::OsApi; use crate::os_input_output::OsApi;
@ -13,14 +14,13 @@ use crate::CommandIsExecuting;
use termion::input::{TermRead, TermReadEventsAndRaw}; use termion::input::{TermRead, TermReadEventsAndRaw};
use zellij_tile::data::{Event, InputMode, Key, ModeInfo}; use zellij_tile::data::{Event, InputMode, Key, ModeInfo};
use super::keybinds::key_to_actions;
/// Handles the dispatching of [`Action`]s according to the current /// Handles the dispatching of [`Action`]s according to the current
/// [`InputMode`], and keep tracks of the current [`InputMode`]. /// [`InputMode`], and keep tracks of the current [`InputMode`].
struct InputHandler { struct InputHandler {
/// The current input mode /// The current input mode
mode: InputMode, mode: InputMode,
os_input: Box<dyn OsApi>, os_input: Box<dyn OsApi>,
config: Config,
command_is_executing: CommandIsExecuting, command_is_executing: CommandIsExecuting,
send_screen_instructions: SenderWithContext<ScreenInstruction>, send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>, send_pty_instructions: SenderWithContext<PtyInstruction>,
@ -33,6 +33,7 @@ impl InputHandler {
fn new( fn new(
os_input: Box<dyn OsApi>, os_input: Box<dyn OsApi>,
command_is_executing: CommandIsExecuting, command_is_executing: CommandIsExecuting,
config: Config,
send_screen_instructions: SenderWithContext<ScreenInstruction>, send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>, send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>, send_plugin_instructions: SenderWithContext<PluginInstruction>,
@ -41,6 +42,7 @@ impl InputHandler {
InputHandler { InputHandler {
mode: InputMode::Normal, mode: InputMode::Normal,
os_input, os_input,
config,
command_is_executing, command_is_executing,
send_screen_instructions, send_screen_instructions,
send_pty_instructions, send_pty_instructions,
@ -57,41 +59,37 @@ impl InputHandler {
self.send_pty_instructions.update(err_ctx); self.send_pty_instructions.update(err_ctx);
self.send_app_instructions.update(err_ctx); self.send_app_instructions.update(err_ctx);
self.send_screen_instructions.update(err_ctx); self.send_screen_instructions.update(err_ctx);
if let Ok(keybinds) = get_default_keybinds() { let keybinds = self.config.keybinds.clone();
'input_loop: loop { 'input_loop: loop {
//@@@ I think this should actually just iterate over stdin directly //@@@ I think this should actually just iterate over stdin directly
let stdin_buffer = self.os_input.read_from_stdin(); let stdin_buffer = self.os_input.read_from_stdin();
for key_result in stdin_buffer.events_and_raw() { for key_result in stdin_buffer.events_and_raw() {
match key_result { match key_result {
Ok((event, raw_bytes)) => match event { Ok((event, raw_bytes)) => match event {
termion::event::Event::Key(key) => { termion::event::Event::Key(key) => {
let key = cast_termion_key(key); let key = cast_termion_key(key);
// FIXME this explicit break is needed because the current test // FIXME this explicit break is needed because the current test
// framework relies on it to not create dead threads that loop // framework relies on it to not create dead threads that loop
// and eat up CPUs. Do not remove until the test framework has // and eat up CPUs. Do not remove until the test framework has
// been revised. Sorry about this (@categorille) // been revised. Sorry about this (@categorille)
let mut should_break = false; let mut should_break = false;
for action in key_to_actions(&key, raw_bytes, &self.mode, &keybinds) for action in
{ Keybinds::key_to_actions(&key, raw_bytes, &self.mode, &keybinds)
should_break |= self.dispatch_action(action); {
} should_break |= self.dispatch_action(action);
if should_break {
break 'input_loop;
}
} }
termion::event::Event::Mouse(_) if should_break {
| termion::event::Event::Unsupported(_) => { break 'input_loop;
// Mouse and unsupported events aren't implemented yet,
// use a NoOp untill then.
} }
}, }
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. /// its [`InputHandler::handle_input()`] loop.
pub fn input_loop( pub fn input_loop(
os_input: Box<dyn OsApi>, os_input: Box<dyn OsApi>,
config: Config,
command_is_executing: CommandIsExecuting, command_is_executing: CommandIsExecuting,
send_screen_instructions: SenderWithContext<ScreenInstruction>, send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>, send_pty_instructions: SenderWithContext<PtyInstruction>,
@ -300,6 +299,7 @@ pub fn input_loop(
let _handler = InputHandler::new( let _handler = InputHandler::new(
os_input, os_input,
command_is_executing, command_is_executing,
config,
send_screen_instructions, send_screen_instructions,
send_pty_instructions, send_pty_instructions,
send_plugin_instructions, send_plugin_instructions,

View file

@ -1,268 +1,368 @@
//! Mapping of inputs to sequences of actions. //! Mapping of inputs to sequences of actions.
use std::collections::HashMap;
use super::actions::{Action, Direction}; use super::actions::{Action, Direction};
use std::collections::HashMap; use serde::Deserialize;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use zellij_tile::data::*; use zellij_tile::data::*;
type Keybinds = HashMap<InputMode, ModeKeybinds>; #[derive(Clone, Debug, PartialEq)]
type ModeKeybinds = HashMap<Key, Vec<Action>>; pub struct Keybinds(HashMap<InputMode, ModeKeybinds>);
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ModeKeybinds(HashMap<Key, Vec<Action>>);
/// Populates the default hashmap of keybinds. /// Intermediate struct used for deserialisation
/// @@@khs26 What about an input config file? #[derive(Clone, Debug, PartialEq, Deserialize)]
pub fn get_default_keybinds() -> Result<Keybinds, String> { pub struct KeybindsFromYaml(HashMap<InputMode, Vec<KeyActionFromYaml>>);
let mut defaults = Keybinds::new();
for mode in InputMode::iter() { /// Intermediate struct used for deserialisation
defaults.insert(mode, get_defaults_for_mode(&mode)); #[derive(Clone, Debug, PartialEq, Deserialize)]
} pub struct KeyActionFromYaml {
action: Vec<Action>,
Ok(defaults) key: Vec<Key>,
} }
/// Returns the default keybinds for a givent [`InputMode`]. impl Default for Keybinds {
fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds { fn default() -> Keybinds {
let mut defaults = ModeKeybinds::new(); let mut defaults = Keybinds::new();
match *mode { for mode in InputMode::iter() {
InputMode::Normal => { defaults
defaults.insert( .0
Key::Ctrl('g'), .insert(mode, Keybinds::get_defaults_for_mode(&mode));
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
defaults.insert( }
Key::Ctrl('g'), }
vec![Action::SwitchToMode(InputMode::Normal)],
); impl Keybinds {
pub fn new() -> Keybinds {
Keybinds(HashMap::<InputMode, ModeKeybinds>::new())
}
pub fn get_default_keybinds_with_config(keybinds: Option<KeybindsFromYaml>) -> Keybinds {
let default_keybinds = Keybinds::default();
if let Some(keybinds) = keybinds {
default_keybinds.merge_keybinds(Keybinds::from(keybinds))
} else {
default_keybinds
} }
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)]); /// Merges two Keybinds structs into one Keybinds struct
defaults.insert(Key::Char('j'), vec![Action::Resize(Direction::Down)]); /// `other` overrides the ModeKeybinds of `self`.
defaults.insert(Key::Char('k'), vec![Action::Resize(Direction::Up)]); fn merge_keybinds(&self, other: Keybinds) -> Keybinds {
defaults.insert(Key::Char('l'), vec![Action::Resize(Direction::Right)]); let mut keybinds = Keybinds::new();
defaults.insert(Key::Left, vec![Action::Resize(Direction::Left)]); for mode in InputMode::iter() {
defaults.insert(Key::Down, vec![Action::Resize(Direction::Down)]); let mut mode_keybinds = ModeKeybinds::new();
defaults.insert(Key::Up, vec![Action::Resize(Direction::Up)]); if let Some(keybind) = self.0.get(&mode) {
defaults.insert(Key::Right, vec![Action::Resize(Direction::Right)]); mode_keybinds.0.extend(keybind.0.clone());
} };
InputMode::Pane => { if let Some(keybind) = other.0.get(&mode) {
defaults.insert( mode_keybinds.0.extend(keybind.0.clone());
Key::Ctrl('g'), }
vec![Action::SwitchToMode(InputMode::Locked)], if !mode_keybinds.0.is_empty() {
); keybinds.0.insert(mode, mode_keybinds);
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),
],
);
}
}
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<u8>,
mode: &InputMode,
keybinds: &Keybinds,
) -> Vec<Action> {
let mode_keybind_or_action = |action: Action| {
keybinds keybinds
.get(mode) }
.unwrap_or_else(|| unreachable!("Unrecognized mode: {:?}", mode))
.get(key) /// Returns the default keybinds for a given [`InputMode`].
.cloned() fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds {
.unwrap_or_else(|| vec![action]) let mut defaults = HashMap::new();
};
match *mode { match *mode {
InputMode::Normal | InputMode::Locked => mode_keybind_or_action(Action::Write(input)), InputMode::Normal => {
InputMode::RenameTab => mode_keybind_or_action(Action::TabNameInput(input)), defaults.insert(
_ => mode_keybind_or_action(Action::NoOp), 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),
],
);
}
}
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<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
}
}
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.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
}
}
/// 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 keys = key_action.key;
let actions = key_action.action;
ModeKeybinds(
keys.into_iter()
.map(|k| (k, actions.clone()))
.collect::<HashMap<Key, Vec<Action>>>(),
)
}
}
// The unit test location.
#[cfg(test)]
#[path = "./unit/keybinds_test.rs"]
mod keybinds_test;

View file

@ -1,5 +1,6 @@
//! The way terminal iput is handled. //! The way terminal input is handled.
pub mod actions; pub mod actions;
pub mod config;
pub mod handler; pub mod handler;
pub mod keybinds; pub mod keybinds;

View file

@ -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<InputMode, Vec<KeyActionFromYaml>>);
//let actions = vec![Action::NoOp, Action::GoToTab(1), ];
//let keyaction = KeyActionFromYaml {
//action : actions.clone(),
//key : vec![ Key::F(1), Key::Backspace , Key::Char('t'), ],
//};
//}

View file

@ -22,6 +22,7 @@ use std::{
}; };
use crate::cli::CliArgs; use crate::cli::CliArgs;
use crate::common::input::config::Config;
use crate::layout::Layout; use crate::layout::Layout;
use crate::panes::PaneId; use crate::panes::PaneId;
use command_is_executing::CommandIsExecuting; use command_is_executing::CommandIsExecuting;
@ -125,6 +126,13 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
.write(take_snapshot.as_bytes()) .write(take_snapshot.as_bytes())
.unwrap(); .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 command_is_executing = CommandIsExecuting::new();
let full_screen_ws = os_input.get_terminal_size_using_fd(0); let full_screen_ws = os_input.get_terminal_size_using_fd(0);
@ -590,9 +598,11 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
let send_pty_instructions = send_pty_instructions.clone(); let send_pty_instructions = send_pty_instructions.clone();
let send_plugin_instructions = send_plugin_instructions.clone(); let send_plugin_instructions = send_plugin_instructions.clone();
let os_input = os_input.clone(); let os_input = os_input.clone();
let config = config;
move || { move || {
input_loop( input_loop(
os_input, os_input,
config,
command_is_executing, command_is_executing,
send_screen_instructions, send_screen_instructions,
send_pty_instructions, send_pty_instructions,

View file

@ -37,18 +37,25 @@ pub enum Event {
pub enum InputMode { pub enum InputMode {
/// In `Normal` mode, input is always written to the terminal, except for the shortcuts leading /// In `Normal` mode, input is always written to the terminal, except for the shortcuts leading
/// to other modes /// to other modes
#[serde(alias = "normal")]
Normal, Normal,
/// In `Locked` mode, input is always written to the terminal and all shortcuts are disabled /// In `Locked` mode, input is always written to the terminal and all shortcuts are disabled
/// except the one leading back to normal mode /// except the one leading back to normal mode
#[serde(alias = "locked")]
Locked, Locked,
/// `Resize` mode allows resizing the different existing panes. /// `Resize` mode allows resizing the different existing panes.
#[serde(alias = "resize")]
Resize, Resize,
/// `Pane` mode allows creating and closing panes, as well as moving between them. /// `Pane` mode allows creating and closing panes, as well as moving between them.
#[serde(alias = "pane")]
Pane, Pane,
/// `Tab` mode allows creating and closing tabs, as well as moving between them. /// `Tab` mode allows creating and closing tabs, as well as moving between them.
#[serde(alias = "tab")]
Tab, Tab,
/// `Scroll` mode allows scrolling up and down within a pane. /// `Scroll` mode allows scrolling up and down within a pane.
#[serde(alias = "scroll")]
Scroll, Scroll,
#[serde(alias = "renametab")]
RenameTab, RenameTab,
} }