aboutsummaryrefslogtreecommitdiffstats
path: root/src/common
diff options
context:
space:
mode:
authordenis <denismaximov98@gmail.com>2021-04-09 14:14:50 +0300
committerdenis <denismaximov98@gmail.com>2021-04-09 14:14:50 +0300
commit748a7ffa6ea4cf3b2093adb30e27bea9117d40d6 (patch)
tree2e906db54b01ec1ce47f77ad2406690242d63c45 /src/common
parentwip: need to figure out how to clear lines (diff)
parentfeat(controls): add quick navigation (#260) (diff)
downloadsl0ppij-748a7ffa6ea4cf3b2093adb30e27bea9117d40d6.tar.gz
sl0ppij-748a7ffa6ea4cf3b2093adb30e27bea9117d40d6.tar.xz
sl0ppij-748a7ffa6ea4cf3b2093adb30e27bea9117d40d6.zip
wip: cleaning up a bit, need to map the colors to the right places in the palette
Diffstat (limited to 'src/common')
-rw-r--r--src/common/command_is_executing.rs4
-rw-r--r--src/common/errors.rs8
-rw-r--r--src/common/input/actions.rs9
-rw-r--r--src/common/input/config.rs158
-rw-r--r--src/common/input/handler.rs96
-rw-r--r--src/common/input/keybinds.rs611
-rw-r--r--src/common/input/mod.rs3
-rw-r--r--src/common/input/unit/keybinds_test.rs135
-rw-r--r--src/common/mod.rs124
-rw-r--r--src/common/os_input_output.rs8
-rw-r--r--src/common/screen.rs4
-rw-r--r--src/common/utils/shared.rs73
12 files changed, 864 insertions, 369 deletions
diff --git a/src/common/command_is_executing.rs b/src/common/command_is_executing.rs
index 93c44eb6..775a7bfc 100644
--- a/src/common/command_is_executing.rs
+++ b/src/common/command_is_executing.rs
@@ -23,7 +23,7 @@ impl CommandIsExecuting {
let (lock, cvar) = &*self.closing_pane;
let mut closing_pane = lock.lock().unwrap();
*closing_pane = false;
- cvar.notify_one();
+ cvar.notify_all();
}
pub fn opening_new_pane(&mut self) {
let (lock, _cvar) = &*self.opening_new_pane;
@@ -34,7 +34,7 @@ impl CommandIsExecuting {
let (lock, cvar) = &*self.opening_new_pane;
let mut opening_new_pane = lock.lock().unwrap();
*opening_new_pane = false;
- cvar.notify_one();
+ cvar.notify_all();
}
pub fn wait_until_pane_is_closed(&self) {
let (lock, cvar) = &*self.closing_pane;
diff --git a/src/common/errors.rs b/src/common/errors.rs
index 00284cd4..1548aec0 100644
--- a/src/common/errors.rs
+++ b/src/common/errors.rs
@@ -178,7 +178,9 @@ pub enum ScreenContext {
ResizeRight,
ResizeDown,
ResizeUp,
- MoveFocus,
+ SwitchFocus,
+ FocusNextPane,
+ FocusPreviousPane,
MoveFocusLeft,
MoveFocusDown,
MoveFocusUp,
@@ -218,7 +220,9 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::ResizeRight => ScreenContext::ResizeRight,
ScreenInstruction::ResizeDown => ScreenContext::ResizeDown,
ScreenInstruction::ResizeUp => ScreenContext::ResizeUp,
- ScreenInstruction::MoveFocus => ScreenContext::MoveFocus,
+ ScreenInstruction::SwitchFocus => ScreenContext::SwitchFocus,
+ ScreenInstruction::FocusNextPane => ScreenContext::FocusNextPane,
+ ScreenInstruction::FocusPreviousPane => ScreenContext::FocusPreviousPane,
ScreenInstruction::MoveFocusLeft => ScreenContext::MoveFocusLeft,
ScreenInstruction::MoveFocusDown => ScreenContext::MoveFocusDown,
ScreenInstruction::MoveFocusUp => ScreenContext::MoveFocusUp,
diff --git a/src/common/input/actions.rs b/src/common/input/actions.rs
index f4226864..47a5f763 100644
--- a/src/common/input/actions.rs
+++ b/src/common/input/actions.rs
@@ -1,9 +1,10 @@
//! Definition of the actions that can be bound to keys.
+use serde::{Deserialize, Serialize};
use zellij_tile::data::InputMode;
/// The four directions (left, right, up, down).
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum Direction {
Left,
Right,
@@ -12,7 +13,7 @@ pub enum Direction {
}
/// Actions that can be bound to keys.
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum Action {
/// Quit Zellij.
Quit,
@@ -23,8 +24,10 @@ pub enum Action {
/// Resize focus pane in specified direction.
Resize(Direction),
/// Switch focus to next pane in specified direction.
- SwitchFocus(Direction),
+ FocusNextPane,
+ FocusPreviousPane,
/// Move the focus pane in specified direction.
+ SwitchFocus,
MoveFocus(Direction),
/// Scroll up in focus pane.
ScrollUp,
diff --git a/src/common/input/config.rs b/src/common/input/config.rs
new file mode 100644
index 00000000..4bb9e10f
--- /dev/null
+++ b/src/common/input/config.rs
@@ -0,0 +1,158 @@
+//! Deserializes configuration options.
+use std::error;
+use std::fmt::{self, Display};
+use std::fs::File;
+use std::io::{self, Read};
+use std::path::{Path, PathBuf};
+
+use super::keybinds::{Keybinds, KeybindsFromYaml};
+use crate::cli::ConfigCli;
+
+use directories_next::ProjectDirs;
+use serde::Deserialize;
+
+type ConfigResult = Result<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);
+ }
+}
diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs
index 1ed9169e..2484f08c 100644
--- a/src/common/input/handler.rs
+++ b/src/common/input/handler.rs
@@ -1,8 +1,9 @@
//! Main input logic.
use super::actions::Action;
-use super::keybinds::get_default_keybinds;
-use crate::common::{load_palette, AppInstruction, SenderWithContext, OPENCALLS};
+use super::keybinds::Keybinds;
+use crate::common::input::config::Config;
+use crate::common::{AppInstruction, SenderWithContext, OPENCALLS};
use crate::errors::ContextType;
use crate::os_input_output::OsApi;
use crate::pty_bus::PtyInstruction;
@@ -10,22 +11,23 @@ use crate::screen::ScreenInstruction;
use crate::wasm_vm::PluginInstruction;
use crate::CommandIsExecuting;
+use crate::common::utils::shared::load_palette;
use termion::input::{TermRead, TermReadEventsAndRaw};
use zellij_tile::data::{Event, InputMode, Key, ModeInfo};
-use super::keybinds::key_to_actions;
-
/// Handles the dispatching of [`Action`]s according to the current
/// [`InputMode`], and keep tracks of the current [`InputMode`].
struct InputHandler {
/// The current input mode
mode: InputMode,
os_input: Box<dyn OsApi>,
+ config: Config,
command_is_executing: CommandIsExecuting,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
+ should_exit: bool,
}
impl InputHandler {
@@ -33,6 +35,7 @@ impl InputHandler {
fn new(
os_input: Box<dyn OsApi>,
command_is_executing: CommandIsExecuting,
+ config: Config,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
@@ -41,11 +44,13 @@ impl InputHandler {
InputHandler {
mode: InputMode::Normal,
os_input,
+ config,
command_is_executing,
send_screen_instructions,
send_pty_instructions,
send_plugin_instructions,
send_app_instructions,
+ should_exit: false,
}
}
@@ -57,41 +62,44 @@ impl InputHandler {
self.send_pty_instructions.update(err_ctx);
self.send_app_instructions.update(err_ctx);
self.send_screen_instructions.update(err_ctx);
- if let Ok(keybinds) = get_default_keybinds() {
- 'input_loop: loop {
- //@@@ I think this should actually just iterate over stdin directly
- let stdin_buffer = self.os_input.read_from_stdin();
- for key_result in stdin_buffer.events_and_raw() {
- match key_result {
- Ok((event, raw_bytes)) => match event {
- termion::event::Event::Key(key) => {
- let key = cast_termion_key(key);
- // FIXME this explicit break is needed because the current test
- // framework relies on it to not create dead threads that loop
- // and eat up CPUs. Do not remove until the test framework has
- // been revised. Sorry about this (@categorille)
- let mut should_break = false;
- for action in key_to_actions(&key, raw_bytes, &self.mode, &keybinds)
- {
- should_break |= self.dispatch_action(action);
- }
- if should_break {
- break 'input_loop;
- }
- }
- termion::event::Event::Mouse(_)
- | termion::event::Event::Unsupported(_) => {
- // Mouse and unsupported events aren't implemented yet,
- // use a NoOp untill then.
+ let keybinds = self.config.keybinds.clone();
+ let alt_left_bracket = vec![27, 91];
+ loop {
+ if self.should_exit {
+ break;
+ }
+ let stdin_buffer = self.os_input.read_from_stdin();
+ for key_result in stdin_buffer.events_and_raw() {
+ match key_result {
+ Ok((event, raw_bytes)) => match event {
+ termion::event::Event::Key(key) => {
+ let key = cast_termion_key(key);
+ self.handle_key(&key, raw_bytes, &keybinds);
+ }
+ termion::event::Event::Unsupported(unsupported_key) => {
+ // we have to do this because of a bug in termion
+ // this should be a key event and not an unsupported event
+ if unsupported_key == alt_left_bracket {
+ let key = Key::Alt('[');
+ self.handle_key(&key, raw_bytes, &keybinds);
}
- },
- Err(err) => panic!("Encountered read error: {:?}", err),
- }
+ }
+ termion::event::Event::Mouse(_) => {
+ // Mouse events aren't implemented yet,
+ // use a NoOp untill then.
+ }
+ },
+ Err(err) => panic!("Encountered read error: {:?}", err),
}
}
- } else {
- //@@@ Error handling?
- self.exit();
+ }
+ }
+ fn handle_key(&mut self, key: &Key, raw_bytes: Vec<u8>, keybinds: &Keybinds) {
+ for action in Keybinds::key_to_actions(&key, raw_bytes, &self.mode, &keybinds) {
+ let should_exit = self.dispatch_action(action);
+ if should_exit {
+ self.should_exit = true;
+ }
}
}
@@ -146,9 +154,19 @@ impl InputHandler {
};
self.send_screen_instructions.send(screen_instr).unwrap();
}
- Action::SwitchFocus(_) => {
+ Action::SwitchFocus => {
+ self.send_screen_instructions
+ .send(ScreenInstruction::SwitchFocus)
+ .unwrap();
+ }
+ Action::FocusNextPane => {
+ self.send_screen_instructions
+ .send(ScreenInstruction::FocusNextPane)
+ .unwrap();
+ }
+ Action::FocusPreviousPane => {
self.send_screen_instructions
- .send(ScreenInstruction::MoveFocus)
+ .send(ScreenInstruction::FocusPreviousPane)
.unwrap();
}
Action::MoveFocus(direction) => {
@@ -296,6 +314,7 @@ pub fn get_mode_info(mode: InputMode) -> ModeInfo {
/// its [`InputHandler::handle_input()`] loop.
pub fn input_loop(
os_input: Box<dyn OsApi>,
+ config: Config,
command_is_executing: CommandIsExecuting,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
@@ -305,6 +324,7 @@ pub fn input_loop(
let _handler = InputHandler::new(
os_input,
command_is_executing,
+ config,
send_screen_instructions,
send_pty_instructions,
send_plugin_instructions,
diff --git a/src/common/input/keybinds.rs b/src/common/input/keybinds.rs
index 7b1e8779..8a064970 100644
--- a/src/common/input/keybinds.rs
+++ b/src/common/input/keybinds.rs
@@ -1,268 +1,415 @@
//! Mapping of inputs to sequences of actions.
+use std::collections::HashMap;
use super::actions::{Action, Direction};
-use std::collections::HashMap;
-
+use serde::Deserialize;
use strum::IntoEnumIterator;
use zellij_tile::data::*;
-type Keybinds = HashMap<InputMode, ModeKeybinds>;
-type ModeKeybinds = HashMap<Key, Vec<Action>>;
+#[derive(Clone, Debug, PartialEq)]
+pub struct Keybinds(HashMap<InputMode, ModeKeybinds>);
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct ModeKeybinds(HashMap<Key, Vec<Action>>);
-/// Populates the default hashmap of keybinds.
-/// @@@khs26 What about an input config file?
-pub fn get_default_keybinds() -> Result<Keybinds, String> {
- let mut defaults = Keybinds::new();
+/// Intermediate struct used for deserialisation
+#[derive(Clone, Debug, PartialEq, Deserialize)]
+pub struct KeybindsFromYaml(HashMap<InputMode, Vec<KeyActionFromYaml>>);
- for mode in InputMode::iter() {
- defaults.insert(mode, get_defaults_for_mode(&mode));
- }
-
- Ok(defaults)
+/// Intermediate struct used for deserialisation
+#[derive(Clone, Debug, PartialEq, Deserialize)]
+pub struct KeyActionFromYaml {
+ action: Vec<Action>,
+ key: Vec<Key>,
}
-/// Returns the default keybinds for a givent [`InputMode`].
-fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds {
- let mut defaults = ModeKeybinds::new();
+impl Default for Keybinds {
+ fn default() -> Keybinds {
+ let mut defaults = Keybinds::new();
- match *mode {
- InputMode::Normal => {
- defaults.insert(
- Key::Ctrl('g'),
- vec![Action::SwitchToMode(InputMode::Locked)],
- );
- defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
- defaults.insert(
- Key::Ctrl('r'),
- vec![Action::SwitchToMode(InputMode::Resize)],
- );
- defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
- defaults.insert(
- Key::Ctrl('s'),
- vec![Action::SwitchToMode(InputMode::Scroll)],
- );
- defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
+ for mode in InputMode::iter() {
+ defaults
+ .0
+ .insert(mode, Keybinds::get_defaults_for_mode(&mode));
}
- InputMode::Locked => {
- defaults.insert(
- Key::Ctrl('g'),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
+ defaults
+ }
+}
+
+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)]);
- 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)]);
+ /// Merges two Keybinds structs into one Keybinds struct
+ /// `other` overrides the ModeKeybinds of `self`.
+ fn merge_keybinds(&self, other: Keybinds) -> Keybinds {
+ let mut keybinds = Keybinds::new();
- 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)]);
+ for mode in InputMode::iter() {
+ let mut mode_keybinds = ModeKeybinds::new();
+ if let Some(keybind) = self.0.get(&mode) {
+ mode_keybinds.0.extend(keybind.0.clone());
+ };
+ if let Some(keybind) = other.0.get(&mode) {
+ mode_keybinds.0.extend(keybind.0.clone());
+ }
+ if !mode_keybinds.0.is_empty() {
+ keybinds.0.insert(mode, mode_keybinds);
+ }
}
- InputMode::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)],
- );
+ keybinds
+ }
- 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)]);
+ /// Returns the default keybinds for a given [`InputMode`].
+ fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds {
+ let mut defaults = HashMap::new();
- 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)]);
+ match *mode {
+ InputMode::Normal => {
+ defaults.insert(
+ Key::Ctrl('g'),
+ vec![Action::SwitchToMode(InputMode::Locked)],
+ );
+ defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
+ defaults.insert(
+ Key::Ctrl('r'),
+ vec![Action::SwitchToMode(InputMode::Resize)],
+ );
+ defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
+ defaults.insert(
+ Key::Ctrl('s'),
+ vec![Action::SwitchToMode(InputMode::Scroll)],
+ );
+ defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
- 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::Alt('n'), vec![Action::NewPane(None)]);
+ defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]);
+ defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]);
+ defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]);
+ defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]);
+ defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]);
+ defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]);
+ }
+ 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::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::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::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::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)]);
- defaults.insert(Key::Char('n'), vec![Action::NewTab]);
- defaults.insert(Key::Char('x'), vec![Action::CloseTab]);
+ defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]);
+ defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]);
+ defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]);
+ defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]);
+ defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]);
+ defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]);
+ defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]);
+ }
+ 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('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())]);
+ defaults.insert(Key::Char('p'), vec![Action::SwitchFocus]);
+ defaults.insert(Key::Char('n'), vec![Action::NewPane(None)]);
+ defaults.insert(Key::Char('d'), vec![Action::NewPane(Some(Direction::Down))]);
+ defaults.insert(
+ Key::Char('r'),
+ vec![Action::NewPane(Some(Direction::Right))],
+ );
+ defaults.insert(Key::Char('x'), vec![Action::CloseFocus]);
+ defaults.insert(Key::Char('f'), vec![Action::ToggleFocusFullscreen]);
+
+ defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]);
+ defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]);
+ defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]);
+ defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]);
+ defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]);
+ defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]);
+ defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]);
}
- }
- 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)],
- );
+ 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('j'), vec![Action::ScrollDown]);
- defaults.insert(Key::Char('k'), vec![Action::ScrollUp]);
+ 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::Down, vec![Action::ScrollDown]);
- defaults.insert(Key::Up, vec![Action::ScrollUp]);
+ 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())]);
+ }
+ defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]);
+ defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]);
+ defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]);
+ defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]);
+ defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]);
+ defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]);
+ defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]);
+ }
+ 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]);
+
+ defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]);
+ defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]);
+ defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]);
+ defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]);
+ defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]);
+ defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]);
+ defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]);
+ }
+ 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.insert(Key::Alt('n'), vec![Action::NewPane(None)]);
+ defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]);
+ defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]);
+ defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]);
+ defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]);
+ defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]);
+ defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]);
+ }
}
- 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),
}
}
+}
- defaults
+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
+ }
}
-/// 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| {
+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
- .get(mode)
- .unwrap_or_else(|| unreachable!("Unrecognized mode: {:?}", mode))
- .get(key)
- .cloned()
- .unwrap_or_else(|| vec![action])
- };
- match *mode {
- InputMode::Normal | InputMode::Locked => mode_keybind_or_action(Action::Write(input)),
- InputMode::RenameTab => mode_keybind_or_action(Action::TabNameInput(input)),
- _ => mode_keybind_or_action(Action::NoOp),
}
}
+
+/// For each `Key` assigned to `Action`s,
+/// map the `Action`s to the key
+impl From<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;
diff --git a/src/common/input/mod.rs b/src/common/input/mod.rs
index 20c30dae..6589cac2 100644
--- a/src/common/input/mod.rs
+++ b/src/common/input/mod.rs
@@ -1,5 +1,6 @@
-//! The way terminal iput is handled.
+//! The way terminal input is handled.
pub mod actions;
+pub mod config;
pub mod handler;
pub mod keybinds;
diff --git a/src/common/input/unit/keybinds_test.rs b/src/common/input/unit/keybinds_test.rs
new file mode 100644
index 00000000..6411d017
--- /dev/null
+++ b/src/common/input/unit/keybinds_test.rs
@@ -0,0 +1,135 @@
+use super::super::actions::*;
+use super::super::keybinds::*;
+use zellij_tile::data::Key;
+
+#[test]
+fn merge_keybinds_merges_different_keys() {
+ let mut mode_keybinds_self = ModeKeybinds::new();
+ mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]);
+ let mut mode_keybinds_other = ModeKeybinds::new();
+ mode_keybinds_other
+ .0
+ .insert(Key::Backspace, vec![Action::NoOp]);
+
+ let mut mode_keybinds_expected = ModeKeybinds::new();
+ mode_keybinds_expected
+ .0
+ .insert(Key::F(1), vec![Action::NoOp]);
+ mode_keybinds_expected
+ .0
+ .insert(Key::Backspace, vec![Action::NoOp]);
+
+ let mode_keybinds_merged = mode_keybinds_self.merge(mode_keybinds_other);
+
+ assert_eq!(mode_keybinds_expected, mode_keybinds_merged);
+}
+
+#[test]
+fn merge_mode_keybinds_overwrites_same_keys() {
+ let mut mode_keybinds_self = ModeKeybinds::new();
+ mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]);
+ let mut mode_keybinds_other = ModeKeybinds::new();
+ mode_keybinds_other
+ .0
+ .insert(Key::F(1), vec![Action::GoToTab(1)]);
+
+ let mut mode_keybinds_expected = ModeKeybinds::new();
+ mode_keybinds_expected
+ .0
+ .insert(Key::F(1), vec![Action::GoToTab(1)]);
+
+ let mode_keybinds_merged = mode_keybinds_self.merge(mode_keybinds_other);
+
+ assert_eq!(mode_keybinds_expected, mode_keybinds_merged);
+}
+
+#[test]
+fn merge_keybinds_merges() {
+ let mut mode_keybinds_self = ModeKeybinds::new();
+ mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]);
+ let mut mode_keybinds_other = ModeKeybinds::new();
+ mode_keybinds_other
+ .0
+ .insert(Key::Backspace, vec![Action::NoOp]);
+ let mut keybinds_self = Keybinds::new();
+ keybinds_self
+ .0
+ .insert(InputMode::Normal, mode_keybinds_self.clone());
+ let mut keybinds_other = Keybinds::new();
+ keybinds_other
+ .0
+ .insert(InputMode::Resize, mode_keybinds_other.clone());
+ let mut keybinds_expected = Keybinds::new();
+ keybinds_expected
+ .0
+ .insert(InputMode::Normal, mode_keybinds_self);
+ keybinds_expected
+ .0
+ .insert(InputMode::Resize, mode_keybinds_other);
+
+ assert_eq!(
+ keybinds_expected,
+ keybinds_self.merge_keybinds(keybinds_other)
+ )
+}
+
+#[test]
+fn merge_keybinds_overwrites_same_keys() {
+ let mut mode_keybinds_self = ModeKeybinds::new();
+ mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]);
+ mode_keybinds_self.0.insert(Key::F(2), vec![Action::NoOp]);
+ mode_keybinds_self.0.insert(Key::F(3), vec![Action::NoOp]);
+ let mut mode_keybinds_other = ModeKeybinds::new();
+ mode_keybinds_other
+ .0
+ .insert(Key::F(1), vec![Action::GoToTab(1)]);
+ mode_keybinds_other
+ .0
+ .insert(Key::F(2), vec![Action::GoToTab(2)]);
+ mode_keybinds_other
+ .0
+ .insert(Key::F(3), vec![Action::GoToTab(3)]);
+ let mut keybinds_self = Keybinds::new();
+ keybinds_self
+ .0
+ .insert(InputMode::Normal, mode_keybinds_self.clone());
+ let mut keybinds_other = Keybinds::new();
+ keybinds_other
+ .0
+ .insert(InputMode::Normal, mode_keybinds_other.clone());
+ let mut keybinds_expected = Keybinds::new();
+ keybinds_expected
+ .0
+ .insert(InputMode::Normal, mode_keybinds_other);
+
+ assert_eq!(
+ keybinds_expected,
+ keybinds_self.merge_keybinds(keybinds_other)
+ )
+}
+
+#[test]
+fn from_keyaction_from_yaml_to_mode_keybindings() {
+ let actions = vec![Action::NoOp, Action::GoToTab(1)];
+ let keyaction = KeyActionFromYaml {
+ action: actions.clone(),
+ key: vec![Key::F(1), Key::Backspace, Key::Char('t')],
+ };
+
+ let mut expected = ModeKeybinds::new();
+ expected.0.insert(Key::F(1), actions.clone());
+ expected.0.insert(Key::Backspace, actions.clone());
+ expected.0.insert(Key::Char('t'), actions);
+
+ assert_eq!(expected, ModeKeybinds::from(keyaction));
+}
+
+//#[test]
+//fn from_keybinds_from_yaml_to_keybinds(){
+//let mut keybinds_from_yaml = KeybindsFromYaml(HashMap<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'), ],
+//};
+//}
diff --git a/src/common/mod.rs b/src/common/mod.rs
index afd1f78f..0e3e733b 100644
--- a/src/common/mod.rs
+++ b/src/common/mod.rs
@@ -22,6 +22,7 @@ use std::{
};
use crate::cli::CliArgs;
+use crate::common::input::config::Config;
use crate::layout::Layout;
use crate::panes::PaneId;
use colors_transform::{Color, Rgb};
@@ -33,7 +34,7 @@ use os_input_output::OsApi;
use pty_bus::{PtyBus, PtyInstruction};
use screen::{Screen, ScreenInstruction};
use serde::{Deserialize, Serialize};
-use utils::consts::ZELLIJ_IPC_PIPE;
+use utils::{consts::ZELLIJ_IPC_PIPE, shared::load_palette};
use wasm_vm::PluginEnv;
use wasm_vm::{wasi_stdout, wasi_write_string, zellij_imports, PluginInstruction};
use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
@@ -119,88 +120,6 @@ pub enum AppInstruction {
Error(String),
}
-pub mod colors {
- pub const WHITE: (u8, u8, u8) = (238, 238, 238);
- pub const GREEN: (u8, u8, u8) = (175, 255, 0);
- pub const GRAY: (u8, u8, u8) = (68, 68, 68);
- pub const BRIGHT_GRAY: (u8, u8, u8) = (138, 138, 138);
- pub const RED: (u8, u8, u8) = (135, 0, 0);
- pub const BLACK: (u8, u8, u8) = (0, 0, 0);
-}
-
-pub fn detect_theme(bg: (u8, u8, u8)) -> Theme {
- let (r, g, b) = bg;
- // HSP, P stands for perceived brightness
- let hsp: f64 = (0.299 * (r as f64 * r as f64)
- + 0.587 * (g as f64 * g as f64)
- + 0.114 * (b as f64 * b as f64))
- .sqrt();
- match hsp > 127.5 {
- true => Theme::Light,
- false => Theme::Dark,
- }
-}
-
-pub fn load_palette() -> Palette {
- let palette = match Colors::new("xresources") {
- Some(colors) => {
- let fg = colors.fg.unwrap();
- let fg_imm = &fg;
- let fg_hex: &str = &fg_imm;
- let fg = Rgb::from_hex_str(fg_hex).unwrap().as_tuple();
- let fg = (fg.0 as u8, fg.1 as u8, fg.2 as u8);
- let bg = colors.bg.unwrap();
- let bg_imm = &bg;
- let bg_hex: &str = &bg_imm;
- let bg = Rgb::from_hex_str(bg_hex).unwrap().as_tuple();
- let bg = (bg.0 as u8, bg.1 as u8, bg.2 as u8);
- let colors: Vec<(u8, u8, u8)> = colors
- .colors
- .iter()
- .map(|c| {
- let c = c.clone();
- let imm_str = &c.unwrap();
- let hex_str: &str = &imm_str;
- let rgb = Rgb::from_hex_str(hex_str).unwrap().as_tuple();
- (rgb.0 as u8, rgb.1 as u8, rgb.2 as u8)
- })
- .collect();
- let theme = detect_theme(bg);
- debug_log_to_file(format!(
- "{:?} {:?}, white: {:?}, black: {:?}, fg: {:?}",
- theme, bg, colors[7], colors[0], fg
- ));
- Palette {
- theme,
- fg,
- bg,
- black: colors[0],
- red: colors[1],
- green: colors[2],
- yellow: colors[3],
- blue: colors[4],
- magenta: colors[5],
- cyan: colors[6],
- white: colors[7],
- }
- }
- None => Palette {
- theme: Theme::Dark,
- fg: colors::BRIGHT_GRAY,
- bg: colors::BLACK,
- black: colors::BLACK,
- red: colors::RED,
- green: colors::GREEN,
- yellow: colors::GRAY,
- blue: colors::GRAY,
- magenta: colors::GRAY,
- cyan: colors::GRAY,
- white: colors::WHITE,
- },
- };
- palette
-}
-
/// Start Zellij with the specified [`OsApi`] and command-line arguments.
// FIXME this should definitely be modularized and split into different functions.
pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
@@ -211,6 +130,13 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
.write(take_snapshot.as_bytes())
.unwrap();
+ let config = Config::from_cli_config(opts.config)
+ .map_err(|e| {
+ eprintln!("There was an error in the config file:\n{}", e);
+ std::process::exit(1);
+ })
+ .unwrap();
+
let command_is_executing = CommandIsExecuting::new();
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
@@ -360,10 +286,22 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
screen.send_pty_instructions.update(err_ctx);
match event {
ScreenInstruction::Pty(pid, vte_event) => {
- screen
- .get_active_tab_mut()
- .unwrap()
- .handle_pty_event(pid, vte_event);
+ let active_tab = screen.get_active_tab_mut().unwrap();
+ if active_tab.has_terminal_pid(pid) {
+ // it's most likely that this event is directed at the active tab
+ // look there first
+ active_tab.handle_pty_event(pid, vte_event);
+ } else {
+ // if this event wasn't directed at the active tab, start looking
+ // in other tabs
+ let all_tabs = screen.get_tabs_mut();
+ for tab in all_tabs.values_mut() {
+ if tab.has_terminal_pid(pid) {
+ tab.handle_pty_event(pid, vte_event);
+ break;
+ }
+ }
+ }
}
ScreenInstruction::Render => {
screen.render();
@@ -398,9 +336,15 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
ScreenInstruction::ResizeUp => {
screen.get_active_tab_mut().unwrap().resize_up();
}
- ScreenInstruction::MoveFocus => {
+ ScreenInstruction::SwitchFocus => {
screen.get_active_tab_mut().unwrap().move_focus();
}
+ ScreenInstruction::FocusNextPane => {
+ screen.get_active_tab_mut().unwrap().focus_next_pane();
+ }
+ ScreenInstruction::FocusPreviousPane => {
+ screen.get_active_tab_mut().unwrap().focus_previous_pane();
+ }
ScreenInstruction::MoveFocusLeft => {
screen.get_active_tab_mut().unwrap().move_focus_left();
}
@@ -661,7 +605,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
}
ApiCommand::MoveFocus => {
send_screen_instructions
- .send(ScreenInstruction::MoveFocus)
+ .send(ScreenInstruction::FocusNextPane)
.unwrap();
}
}
@@ -682,9 +626,11 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
let send_pty_instructions = send_pty_instructions.clone();
let send_plugin_instructions = send_plugin_instructions.clone();
let os_input = os_input.clone();
+ let config = config;
move || {
input_loop(
os_input,
+ config,
command_is_executing,
send_screen_instructions,
send_pty_instructions,
diff --git a/src/common/os_input_output.rs b/src/common/os_input_output.rs
index de0c6a3b..013b9f12 100644
--- a/src/common/os_input_output.rs
+++ b/src/common/os_input_output.rs
@@ -242,7 +242,13 @@ impl OsApi for OsInputOutput {
Box::new(stdout)
}
fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error> {
- kill(Pid::from_raw(pid), Some(Signal::SIGINT)).unwrap();
+ // TODO:
+ // Ideally, we should be using SIGINT rather than SIGKILL here, but there are cases in which
+ // the terminal we're trying to kill hangs on SIGINT and so all the app gets stuck
+ // that's why we're sending SIGKILL here
+ // A better solution would be to send SIGINT here and not wait for it, and then have
+ // a background thread do the waitpid stuff and send SIGKILL if the process is stuck
+ kill(Pid::from_raw(pid), Some(Signal::SIGKILL)).unwrap();
waitpid(Pid::from_raw(pid), None).unwrap();
Ok(())
}
diff --git a/src/common/screen.rs b/src/common/screen.rs
index 7546bece..6ef7e9e0 100644
--- a/src/common/screen.rs
+++ b/src/common/screen.rs
@@ -28,7 +28,9 @@ pub enum ScreenInstruction {
ResizeRight,
ResizeDown,
ResizeUp,
- MoveFocus,
+ SwitchFocus,
+ FocusNextPane,
+ FocusPreviousPane,
MoveFocusLeft,
MoveFocusDown,
MoveFocusUp,
diff --git a/src/common/utils/shared.rs b/src/common/utils/shared.rs
index 93892d4f..5d01e78e 100644
--- a/src/common/utils/shared.rs
+++ b/src/common/utils/shared.rs
@@ -4,6 +4,10 @@ use std::{iter, str::from_utf8};
use strip_ansi_escapes::strip;
+use colors_transform::{Color, Rgb};
+use xrdb::Colors;
+use zellij_tile::data::{Palette, Theme};
+
fn ansi_len(s: &str) -> usize {
from_utf8(&strip(s.as_bytes()).unwrap())
.unwrap()
@@ -28,3 +32,72 @@ pub fn adjust_to_size(s: &str, rows: usize, columns: usize) -> String {
.collect::<Vec<_>>()
.join("\n\r")
}
+
+// Colors
+pub mod colors {
+ pub const WHITE: (u8, u8, u8) = (238, 238, 238);
+ pub const GREEN: (u8, u8, u8) = (175, 255, 0);
+ pub const GRAY: (u8, u8, u8) = (68, 68, 68);
+ pub const BRIGHT_GRAY: (u8, u8, u8) = (138, 138, 138);
+ pub const RED: (u8, u8, u8) = (135, 0, 0);
+ pub const BLACK: (u8, u8, u8) = (0, 0, 0);
+}
+
+pub fn hex_to_rgb(hex: &Option<String>) -> (u8, u8, u8) {
+ let c = hex.clone();
+ let imm_str = &c.unwrap();
+ let hex_str: &str = &imm_str;
+ let rgb = Rgb::from_hex_str(hex_str).unwrap().as_tuple();
+ (rgb.0 as u8, rgb.1 as u8, rgb.2 as u8)
+}
+
+pub fn detect_theme(bg: (u8, u8, u8)) -> Theme {
+ let (r, g, b) = bg;
+ // HSP, P stands for perceived brightness
+ let hsp: f64 = (0.299 * (r as f64 * r as f64)
+ + 0.587 * (g as f64 * g as f64)
+ + 0.114 * (b as f64 * b as f64))
+ .sqrt();
+ match hsp > 127.5 {
+ true => Theme::Light,
+ false => Theme::Dark,
+ }
+}
+
+pub fn load_palette() -> Palette {
+ let palette = match Colors::new("xresources") {
+ Some(palette) => {
+ let fg = hex_to_rgb(&palette.fg);
+ let bg = hex_to_rgb(&palette.bg);
+ let colors: Vec<(u8, u8, u8)> = palette.colors.iter().map(|c| hex_to_rgb(c)).collect();
+ let theme = detect_theme(bg);
+ Palette {
+ theme,
+ fg,
+ bg,
+ black: colors[0],
+ red: colors[1],
+ green: colors[2],
+ yellow: colors[3],
+ blue: colors[4],
+ magenta: colors[5],
+ cyan: colors[6],
+ white: colors[7],
+ }
+ }
+ None => Palette {
+ theme: Theme::Dark,
+ fg: colors::BRIGHT_GRAY,
+ bg: colors::BLACK,
+ black: colors::BLACK,
+ red: colors::RED,
+ green: colors::GREEN,
+ yellow: colors::GRAY,
+ blue: colors::GRAY,
+ magenta: colors::GRAY,
+ cyan: colors::GRAY,
+ white: colors::WHITE,
+ },
+ };
+ palette
+}