commit
381b63d357
14 changed files with 917 additions and 288 deletions
38
README.md
38
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.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -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 "$@"
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
26
example/config.yaml
Normal 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',]
|
||||||
17
src/cli.rs
17
src/cli.rs
|
|
@ -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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
158
src/common/input/config.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
135
src/common/input/unit/keybinds_test.rs
Normal file
135
src/common/input/unit/keybinds_test.rs
Normal 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'), ],
|
||||||
|
//};
|
||||||
|
//}
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue