aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authora-kenji <aks.kenji@protonmail.com>2021-03-05 22:40:42 +0100
committera-kenji <aks.kenji@protonmail.com>2021-03-09 19:16:15 +0100
commitc97c553870380956904ab7d2daa36e5ce0aef010 (patch)
treef84ac83057df1a5da72af1733ffc048555ad8b40 /src
parentpoking (diff)
downloadsl0ppij-c97c553870380956904ab7d2daa36e5ce0aef010.tar.gz
sl0ppij-c97c553870380956904ab7d2daa36e5ce0aef010.tar.xz
sl0ppij-c97c553870380956904ab7d2daa36e5ce0aef010.zip
feature(input): add config file
* use a simple platform dependent config location `ProjectDir` * deserialise from yaml iterate more on config format, maybe be more verbose? * make keybinds a tuple struct same size as newtype, but makes impls easier to add * merge optionally multiple keys for one keybinding easier configuration
Diffstat (limited to 'src')
-rw-r--r--src/common/config.rs116
-rw-r--r--src/common/input/actions.rs6
-rw-r--r--src/common/input/handler.rs16
-rw-r--r--src/common/input/keybinds.rs414
-rw-r--r--src/common/input/macros.rs12
-rw-r--r--src/common/input/mod.rs3
-rw-r--r--src/common/input/ut/config_test.rs51
-rw-r--r--src/common/input/ut/keybinds_test.rs0
-rw-r--r--src/common/mod.rs8
-rw-r--r--src/tests/fixtures/config/functioning_simple_config.yaml0
-rw-r--r--src/tests/fixtures/config/multiple_keys_for_one_action.yaml8
11 files changed, 403 insertions, 231 deletions
diff --git a/src/common/config.rs b/src/common/config.rs
index 705c1592..bf8a9a9f 100644
--- a/src/common/config.rs
+++ b/src/common/config.rs
@@ -1,104 +1,101 @@
//! Deserializes configuration options.
use std;
-//use std::collections::HashMap;
use std::error;
use std::fmt::{self, Display};
use std::fs::File;
use std::io::{self, Read};
use std::path::PathBuf;
-use super::input::{keybinds, macros};
+use super::input::keybinds::{Keybinds, MultipleKeybinds};
+use crate::utils::logging::*;
use directories_next::ProjectDirs;
use serde::Deserialize;
-/// Intermediate struct
-//pub struct KeybingsFromYaml {
-
-//}
-
-/// Intermediate struct
+/// Intermediate deserialisation config struct
#[derive(Debug, Deserialize)]
pub struct ConfigFromYaml {
- keybinds: Option<keybinds::Keybinds>,
- macros: Option<Vec<macros::Macro>>,
+ pub keybinds: Option<MultipleKeybinds>,
}
-///// Deserialized config state
-#[derive(Debug, Clone, Default, Deserialize)]
+/// Main configuration.
+#[derive(Debug, Clone, PartialEq)]
pub struct Config {
- pub keybinds: keybinds::Keybinds,
+ pub keybinds: Keybinds,
}
#[derive(Debug)]
pub enum ConfigError {
- // from the serde documentation
- // https://serde.rs/error-handling.html
- // One or more variants that can be created by data structures through the
- // `ser::Error` and `de::Error` traits. For example the Serialize impl for
- // Mutex<T> might return an error because the mutex is poisoned, or the
- // Deserialize impl for a struct may return an error because a required
- // field is missing.
- //Message(String),
- // serde_yaml error
+ // Deserialisation error
Serde(serde_yaml::Error),
- //Eof,
- // io::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 {
- /// Deserializes from given path
- pub fn new(path: &PathBuf) -> Result<Config, ConfigError> {
- let config: Config;
- let config_deserialized: ConfigFromYaml;
- let mut config_string = String::new();
+ /// Uses defaults, but lets config override them.
+ pub fn from_yaml(yaml_config: &str) -> Result<Config, ConfigError> {
+ 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 })
+ }
- // TODO fix this unwrap
+ /// Deserializes from given path.
+ /// The allow is here, because rust assumes there is no
+ /// error handling when logging the error to the log file.
+ #[allow(unused_must_use)]
+ pub fn new(path: &PathBuf) -> Result<Config, ConfigError> {
match File::open(path) {
Ok(mut file) => {
- file.read_to_string(&mut config_string)?;
- config_deserialized = serde_yaml::from_str(&config_string)?;
- config = Config {
- keybinds: config_deserialized
- .keybinds
- .unwrap_or_else(|| keybinds::get_default_keybinds().unwrap()),
- }
+ 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) => {
// TODO logging, if a file is not found
// at an expected position - should not
- // panic @a-kenji
- eprintln!("{}", e);
- config = Config::default();
+ // panic, but log to file @a-kenji
+ debug_log_to_file(format!(
+ "{}\nUsing the default configuration!",
+ ConfigError::IoPath(e, path.to_path_buf())
+ ));
+ Ok(Config::default())
}
}
- Ok(config)
}
+ /// Deserializes the config from an optional path, or a platform specific path,
+ /// merges the default configuration - options take precedence.
pub fn from_option_or_default(option: Option<PathBuf>) -> Result<Config, ConfigError> {
- let config;
if let Some(config_path) = option {
- config = Config::new(&config_path)?;
+ Ok(Config::new(&config_path)?)
} else {
- let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap();
- //let config_path = PathBuf::from(project_dirs.config_dir().as_os_str());
- let config_path = project_dirs.config_dir().to_owned().into();
- config = Config::new(&config_path)?;
+ let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap();
+ let mut config_path: PathBuf = project_dirs.config_dir().to_owned().into();
+ config_path.push("config.yaml");
+ Ok(Config::new(&config_path)?)
}
- return Ok(config);
}
}
impl Display for ConfigError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
match self {
- //ConfigError::Message(msg) => formatter.write_str(msg),
- //ConfigError::Eof => formatter.write_str("unexpected end of input"),
- //
- ConfigError::Io(ref err) => write!(formatter, "Io error: {}", err),
- ConfigError::Serde(ref err) => write!(formatter, "Serde error: {}", err),
- /* and so forth */
+ 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),
}
}
}
@@ -106,12 +103,8 @@ impl Display for ConfigError {
impl std::error::Error for ConfigError {
fn cause(&self) -> Option<&dyn error::Error> {
match *self {
- // N.B. Both of these implicitly cast `err` from their concrete
- // types (either `&io::Error` or `&num::ParseIntError`)
- // to a trait object `&Error`. This works because both error types
- // implement `Error`.
ConfigError::Io(ref err) => Some(err),
- //ConfigError::Message(ref err) => Some(err),
+ ConfigError::IoPath(ref err, _) => Some(err),
ConfigError::Serde(ref err) => Some(err),
}
}
@@ -128,3 +121,8 @@ impl From<serde_yaml::Error> for ConfigError {
ConfigError::Serde(err)
}
}
+
+// The unit test location.
+#[cfg(test)]
+#[path = "./input/ut/config_test.rs"]
+mod config_test;
diff --git a/src/common/input/actions.rs b/src/common/input/actions.rs
index d03ca859..6d5c5892 100644
--- a/src/common/input/actions.rs
+++ b/src/common/input/actions.rs
@@ -2,10 +2,10 @@
use super::handler;
//use super::macros;
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
/// The four directions (left, right, up, down).
-#[derive(Clone, Debug, Deserialize)]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum Direction {
Left,
Right,
@@ -14,7 +14,7 @@ pub enum Direction {
}
/// Actions that can be bound to keys.
-#[derive(Clone, Debug, Deserialize)]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum Action {
/// Quit Zellij.
Quit,
diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs
index 9d8b2cf2..9b08491b 100644
--- a/src/common/input/handler.rs
+++ b/src/common/input/handler.rs
@@ -1,7 +1,7 @@
//! Main input logic.
use super::actions::Action;
-use super::keybinds::get_default_keybinds;
+use super::keybinds::Keybinds;
use crate::common::config::Config;
use crate::common::{update_state, AppInstruction, AppState, SenderWithContext, OPENCALLS};
use crate::errors::ContextType;
@@ -15,8 +15,6 @@ use serde::{Deserialize, Serialize};
use strum_macros::EnumIter;
use termion::input::TermReadEventsAndRaw;
-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 {
@@ -62,9 +60,8 @@ 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() {
- let mut merged_keybinds = keybinds;
- merged_keybinds.extend(self.config.keybinds.clone().into_iter());
+ if let Ok(_keybinds) = Keybinds::get_default_keybinds() {
+ let keybinds = self.config.keybinds.clone();
'input_loop: loop {
//@@@ I think this should actually just iterate over stdin directly
let stdin_buffer = self.os_input.read_from_stdin();
@@ -84,11 +81,8 @@ impl InputHandler {
let mut should_break = false;
// Hacked on way to have a means of testing Macros, needs to
// get properly integrated
- for action in key_to_actions(
- &key,
- raw_bytes,
- &self.mode,
- &merged_keybinds,
+ for action in Keybinds::key_to_actions(
+ &key, raw_bytes, &self.mode, &keybinds,
) {
should_break |= self.dispatch_action(action);
}
diff --git a/src/common/input/keybinds.rs b/src/common/input/keybinds.rs
index 8799649d..8b089ecf 100644
--- a/src/common/input/keybinds.rs
+++ b/src/common/input/keybinds.rs
@@ -1,183 +1,311 @@
//! Mapping of inputs to sequences of actions.
+use std::collections::HashMap;
use super::actions::{Action, Direction};
use super::handler::InputMode;
-use std::collections::HashMap;
-
+use serde::Deserialize;
use strum::IntoEnumIterator;
use termion::event::Key;
-pub type Keybinds = HashMap<InputMode, ModeKeybinds>;
-type ModeKeybinds = HashMap<Key, Vec<Action>>;
+#[derive(Clone, Debug, PartialEq, Deserialize)]
+pub struct Keybinds(HashMap<InputMode, ModeKeybinds>);
+#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
+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 for configuration.
+#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
+pub struct MultipleKeybinds(HashMap<InputMode, Vec<ModeKeybind>>);
+/// Intermediate struct for configuration.
+#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
+struct MultipleModeKeybinds(HashMap<Vec<Key>, Vec<Action>>);
- for mode in InputMode::iter() {
- defaults.insert(mode, get_defaults_for_mode(&mode)?);
- }
+/// Intermediate enum for configuration.
+#[derive(Debug, Clone, PartialEq, Deserialize)]
+#[serde(untagged)]
+enum ModeKeybind {
+ ModeKeybinds(ModeKeybinds),
+ MultipleModeKeybinds(MultipleModeKeybinds),
+}
- Ok(defaults)
+impl Default for Keybinds {
+ fn default() -> Keybinds {
+ Keybinds::get_default_keybinds().expect("Something is wrong with the default Keybinds")
+ }
}
-/// Returns the default keybinds for a given [`InputMode`].
-fn get_defaults_for_mode(mode: &InputMode) -> Result<ModeKeybinds, String> {
- let mut defaults = ModeKeybinds::new();
+impl Keybinds {
+ pub fn new() -> Keybinds {
+ Keybinds::from(HashMap::<InputMode, ModeKeybinds>::new())
+ }
- match *mode {
- InputMode::Normal => {
- defaults.insert(
- Key::Ctrl('g'),
- vec![Action::SwitchToMode(InputMode::Command)],
- );
- }
- InputMode::Command => {
- defaults.insert(
- Key::Char('r'),
- vec![Action::SwitchToMode(InputMode::Resize)],
- );
- defaults.insert(Key::Char('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
- defaults.insert(Key::Char('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
- defaults.insert(
- Key::Char('s'),
- vec![Action::SwitchToMode(InputMode::Scroll)],
- );
- defaults.insert(
- Key::Ctrl('g'),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
- defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]);
- defaults.insert(Key::Char('q'), vec![Action::Quit]);
+ pub fn get_default_keybinds() -> Result<Keybinds, String> {
+ let mut defaults = Keybinds::new();
+
+ for mode in InputMode::iter() {
+ defaults
+ .0
+ .insert(mode, Keybinds::get_defaults_for_mode(&mode)?);
}
- InputMode::Resize => {
- 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)]);
+ Ok(defaults)
+ }
+
+ pub fn get_default_keybinds_with_config(keybinds: Option<MultipleKeybinds>) -> Keybinds {
+ let default_keybinds = Keybinds::default();
+ if let Some(keybinds) = keybinds {
+ default_keybinds.merge_keybinds(Keybinds::from(keybinds))
+ } else {
+ default_keybinds
+ }
+ }
- defaults.insert(Key::Ctrl('b'), vec![Action::Resize(Direction::Left)]);
- defaults.insert(Key::Ctrl('n'), vec![Action::Resize(Direction::Down)]);
- defaults.insert(Key::Ctrl('p'), vec![Action::Resize(Direction::Up)]);
- defaults.insert(Key::Ctrl('f'), 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::default();
- defaults.insert(Key::Char('q'), vec![Action::Quit]);
- defaults.insert(
- Key::Ctrl('g'),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
- defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Command)]);
+ for mode in InputMode::iter() {
+ let mut mode_keybinds: ModeKeybinds = if let Some(keybind) = self.0.get(&mode) {
+ keybind.clone()
+ } else {
+ ModeKeybinds::default()
+ };
+ if let Some(keybind) = other.0.get(&mode) {
+ mode_keybinds.0.extend(keybind.0.clone());
+ }
+ keybinds.0.insert(mode, mode_keybinds);
}
- InputMode::Pane => {
- 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)]);
+ keybinds
+ }
+
+ /// Returns the default keybinds for a given [`InputMode`].
+ fn get_defaults_for_mode(mode: &InputMode) -> Result<ModeKeybinds, String> {
+ 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::Command)],
+ );
+ }
+ InputMode::Command => {
+ defaults.insert(
+ Key::Char('r'),
+ vec![Action::SwitchToMode(InputMode::Resize)],
+ );
+ defaults.insert(Key::Char('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
+ defaults.insert(Key::Char('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
+ defaults.insert(
+ Key::Char('s'),
+ vec![Action::SwitchToMode(InputMode::Scroll)],
+ );
+ defaults.insert(
+ Key::Ctrl('g'),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
+ defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]);
+ defaults.insert(Key::Char('q'), vec![Action::Quit]);
+ }
+ InputMode::Resize => {
+ 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::Ctrl('b'), vec![Action::MoveFocus(Direction::Left)]);
- defaults.insert(Key::Ctrl('n'), vec![Action::MoveFocus(Direction::Down)]);
- defaults.insert(Key::Ctrl('p'), vec![Action::MoveFocus(Direction::Up)]);
- defaults.insert(Key::Ctrl('f'), vec![Action::MoveFocus(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)]);
- 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::Ctrl('b'), vec![Action::Resize(Direction::Left)]);
+ defaults.insert(Key::Ctrl('n'), vec![Action::Resize(Direction::Down)]);
+ defaults.insert(Key::Ctrl('p'), vec![Action::Resize(Direction::Up)]);
+ defaults.insert(Key::Ctrl('f'), vec![Action::Resize(Direction::Right)]);
- defaults.insert(Key::Char('f'), vec![Action::ToggleFocusFullscreen]);
+ defaults.insert(Key::Char('q'), vec![Action::Quit]);
+ defaults.insert(
+ Key::Ctrl('g'),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
+ defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Command)]);
+ }
+ InputMode::Pane => {
+ 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::Char('q'), vec![Action::Quit]);
- defaults.insert(
- Key::Ctrl('g'),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
- defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Command)]);
- }
- InputMode::Tab => {
- 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::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::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::Ctrl('b'), vec![Action::MoveFocus(Direction::Left)]);
+ defaults.insert(Key::Ctrl('n'), vec![Action::MoveFocus(Direction::Down)]);
+ defaults.insert(Key::Ctrl('p'), vec![Action::MoveFocus(Direction::Up)]);
+ defaults.insert(Key::Ctrl('f'), vec![Action::MoveFocus(Direction::Right)]);
- defaults.insert(Key::Ctrl('b'), vec![Action::GoToPreviousTab]);
- defaults.insert(Key::Ctrl('n'), vec![Action::GoToNextTab]);
- defaults.insert(Key::Ctrl('p'), vec![Action::GoToPreviousTab]);
- defaults.insert(Key::Ctrl('f'), vec![Action::GoToNextTab]);
+ 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('n'), vec![Action::NewTab]);
- defaults.insert(Key::Char('x'), vec![Action::CloseTab]);
+ defaults.insert(Key::Char('f'), vec![Action::ToggleFocusFullscreen]);
- 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('q'), vec![Action::Quit]);
+ defaults.insert(
+ Key::Ctrl('g'),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
+ defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Command)]);
}
- defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Command)]);
- }
- InputMode::Scroll => {
- defaults.insert(Key::Char('j'), vec![Action::ScrollDown]);
- defaults.insert(Key::Char('k'), vec![Action::ScrollUp]);
+ InputMode::Tab => {
+ 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::Ctrl('b'), vec![Action::GoToPreviousTab]);
+ defaults.insert(Key::Ctrl('n'), vec![Action::GoToNextTab]);
+ defaults.insert(Key::Ctrl('p'), vec![Action::GoToPreviousTab]);
+ defaults.insert(Key::Ctrl('f'), vec![Action::GoToNextTab]);
- defaults.insert(Key::Down, vec![Action::ScrollDown]);
- defaults.insert(Key::Up, vec![Action::ScrollUp]);
+ defaults.insert(Key::Char('n'), vec![Action::NewTab]);
+ defaults.insert(Key::Char('x'), vec![Action::CloseTab]);
- defaults.insert(Key::Ctrl('n'), vec![Action::ScrollDown]);
- defaults.insert(Key::Ctrl('p'), vec![Action::ScrollUp]);
+ 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::Esc, vec![Action::SwitchToMode(InputMode::Command)]);
+ }
+ InputMode::Scroll => {
+ 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::Ctrl('n'), vec![Action::ScrollDown]);
+ defaults.insert(Key::Ctrl('p'), vec![Action::ScrollUp]);
+
+ defaults.insert(Key::Char('q'), vec![Action::Quit]);
+ defaults.insert(
+ Key::Ctrl('g'),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
+ defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Command)]);
+ }
+ }
- defaults.insert(Key::Char('q'), vec![Action::Quit]);
- defaults.insert(
- Key::Ctrl('g'),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
- defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Command)]);
+ Ok(ModeKeybinds::from(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 => mode_keybind_or_action(Action::Write(input)),
+ _ => mode_keybind_or_action(Action::NoOp),
}
}
+}
+
+impl ModeKeybinds {
+ pub fn new() -> ModeKeybinds {
+ ModeKeybinds::from(HashMap::<Key, Vec<Action>>::new())
+ }
- Ok(defaults)
+ /// Merges `self` with `other`, if keys are the same, `other` overwrites.
+ pub fn merge(self, other: ModeKeybinds) -> ModeKeybinds {
+ let mut merged = self;
+ merged.0.extend(other.0.clone());
+ 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| {
+/// Converts from the `MultipleKeybinds` struct to a `Keybinds` struct.
+/// TODO @a-kenji Error on conflicting keybinds?
+/// Atm the Lower Keybind will take precedence and will overwrite the
+/// one further up.
+impl From<MultipleKeybinds> for Keybinds {
+ fn from(multiple: MultipleKeybinds) -> Keybinds {
+ let mut keybinds = Keybinds::new();
+
+ for mode in InputMode::iter() {
+ let mut mode_keybinds = ModeKeybinds::new();
+ for mode_keybind in multiple.0.get(&mode).iter() {
+ for keybind in mode_keybind.iter() {
+ match keybind {
+ ModeKeybind::ModeKeybinds(k) => {
+ mode_keybinds = mode_keybinds.clone().merge(k.clone());
+ }
+ ModeKeybind::MultipleModeKeybinds(k) => {
+ mode_keybinds =
+ mode_keybinds.clone().merge(ModeKeybinds::from(k.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 => mode_keybind_or_action(Action::Write(input)),
- _ => mode_keybind_or_action(Action::NoOp),
}
}
+
+impl From<HashMap<InputMode, ModeKeybinds>> for Keybinds {
+ fn from(map: HashMap<InputMode, ModeKeybinds>) -> Keybinds {
+ Keybinds(map)
+ }
+}
+impl From<HashMap<Key, Vec<Action>>> for ModeKeybinds {
+ fn from(map: HashMap<Key, Vec<Action>>) -> ModeKeybinds {
+ ModeKeybinds(map)
+ }
+}
+
+impl From<MultipleModeKeybinds> for ModeKeybinds {
+ fn from(multiple: MultipleModeKeybinds) -> ModeKeybinds {
+ let single: HashMap<Key, Vec<Action>> = multiple
+ .0
+ .into_iter()
+ .flat_map(|(k, v)| (k.into_iter().map(move |k| (k, v.clone()))))
+ .collect();
+ ModeKeybinds::from(single)
+ }
+}
+
+// The unit test location.
+#[cfg(test)]
+#[path = "./ut/keybinds_test.rs"]
+mod keybinds_test;
diff --git a/src/common/input/macros.rs b/src/common/input/macros.rs
deleted file mode 100644
index 4b7c6df1..00000000
--- a/src/common/input/macros.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-//! Use a list of commands and execute them in a
-//! defined predictable order.
-
-use super::actions::Action;
-
-use serde::Deserialize;
-
-#[derive(Debug, Clone, Deserialize)]
-pub struct Macro {
- name: Option<String>,
- sequence: Vec<Action>,
-}
diff --git a/src/common/input/mod.rs b/src/common/input/mod.rs
index 438df224..a7dca7ed 100644
--- a/src/common/input/mod.rs
+++ b/src/common/input/mod.rs
@@ -1,6 +1,5 @@
-//! The way terminal iput is handled.
+//! The way terminal input is handled.
pub mod actions;
pub mod handler;
pub mod keybinds;
-pub mod macros;
diff --git a/src/common/input/ut/config_test.rs b/src/common/input/ut/config_test.rs
new file mode 100644
index 00000000..001aab15
--- /dev/null
+++ b/src/common/input/ut/config_test.rs
@@ -0,0 +1,51 @@
+//! For Configuration Options
+
+use super::super::config::*;
+use crate::common::input::keybinds::*;
+use crate::common::input::actions::*;
+use std::path::PathBuf;
+
+use termion::event::Key;
+
+
+#[test]
+fn no_config_file_equals_default_config() {
+ let no_file = PathBuf::from(r"../fixtures/config/config.yamlll");
+ let config = Config::from_option_or_default(Some(no_file)).unwrap();
+ let default = Config::default();
+ assert_eq!(config, default);
+}
+
+#[test]
+fn no_config_option_file_equals_default_config() {
+ let config = Config::from_option_or_default(None).unwrap();
+ let default = Config::default();
+ assert_eq!(config, default);
+}
+
+#[test]
+fn multiple_keys_mapped_to_one_action() {
+ let options = r"
+ ---
+keybindings:
+ Normal:
+ - ? - F: 6
+ - F: 7
+ - F: 8
+ : - {GoToTab: 5}
+ ";
+
+ let config_options = Config::from_yaml(&options).unwrap();
+
+ assert_eq!(config_options, config_options)
+}
+
+//#[test]
+//fn merge_keybinds_merges(){
+ //let mut self_keybinds = Keybinds::new();
+ //let mut self_mode_keybinds = ModeKeybinds::new();
+ //self_mode_keybinds.0.insert(Key::F(1), vec![Action::GoToTab(5)]);
+ //let mut other_keybinds = Keybinds::new();
+ //let mut self_mode_keybinds = ModeKeybinds::new();
+ //let mut expected = Keybinds::new();
+//}
diff --git a/src/common/input/ut/keybinds_test.rs b/src/common/input/ut/keybinds_test.rs
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/common/input/ut/keybinds_test.rs
diff --git a/src/common/mod.rs b/src/common/mod.rs
index c8b2ec98..fdd66649 100644
--- a/src/common/mod.rs
+++ b/src/common/mod.rs
@@ -29,6 +29,7 @@ use wasmer_wasi::{Pipe, WasiState};
use crate::cli::CliArgs;
use crate::common::config::Config;
use crate::layout::Layout;
+use crate::utils::logging::*;
use command_is_executing::CommandIsExecuting;
use errors::{AppContext, ContextType, ErrorContext, PluginContext, PtyContext, ScreenContext};
use input::handler::input_loop;
@@ -168,8 +169,13 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
let mut app_state = AppState::default();
+ #[allow(unused_must_use)]
let config = Config::from_option_or_default(opts.config)
- .map_err(|e| eprintln!{"Config Error: {}", e})
+ .map_err(|e| {
+ debug_log_to_file(format!("There was an error in the config file:\n{}", e));
+ eprintln!("There was an error in the config file:\n{}", e);
+ std::process::exit(1);
+ })
.unwrap();
let command_is_executing = CommandIsExecuting::new();
diff --git a/src/tests/fixtures/config/functioning_simple_config.yaml b/src/tests/fixtures/config/functioning_simple_config.yaml
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/tests/fixtures/config/functioning_simple_config.yaml
diff --git a/src/tests/fixtures/config/multiple_keys_for_one_action.yaml b/src/tests/fixtures/config/multiple_keys_for_one_action.yaml
new file mode 100644
index 00000000..7da62541
--- /dev/null
+++ b/src/tests/fixtures/config/multiple_keys_for_one_action.yaml
@@ -0,0 +1,8 @@
+---
+keybindings:
+ Normal:
+ - ? - F: 6
+ - F: 7
+ - F: 8
+ : - {GoToTab: 5}
+