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
This commit is contained in:
parent
c644a1c243
commit
c97c553870
12 changed files with 430 additions and 254 deletions
14
config.yaml
14
config.yaml
|
|
@ -1,8 +1,12 @@
|
|||
---
|
||||
macro:
|
||||
{name:"closePane", sequence: [NewPane: Right,]}
|
||||
|
||||
keybinds:
|
||||
Normal:
|
||||
Backspace: [NewPane:, NewPane:,]
|
||||
{F: 1}: [NewPane:,]
|
||||
- {F: 1}: [GoToTab: 1,]
|
||||
- {F: 2}: [GoToTab: 2,]
|
||||
- {F: 3}: [GoToTab: 3,]
|
||||
- {F: 4}: [GoToTab: 4,]
|
||||
- {F: 5}: [NewTab: ,]
|
||||
- ? - F: 6
|
||||
- F: 7
|
||||
: - {GoToTab: 5}
|
||||
- Backspace: [NewPane: Right, NewPane: Right,]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)?);
|
||||
}
|
||||
|
||||
Ok(defaults)
|
||||
/// Intermediate enum for configuration.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum ModeKeybind {
|
||||
ModeKeybinds(ModeKeybinds),
|
||||
MultipleModeKeybinds(MultipleModeKeybinds),
|
||||
}
|
||||
|
||||
/// Returns the default keybinds for a given [`InputMode`].
|
||||
fn get_defaults_for_mode(mode: &InputMode) -> Result<ModeKeybinds, String> {
|
||||
let mut defaults = ModeKeybinds::new();
|
||||
impl Default for Keybinds {
|
||||
fn default() -> Keybinds {
|
||||
Keybinds::get_default_keybinds().expect("Something is wrong with the default Keybinds")
|
||||
}
|
||||
}
|
||||
|
||||
match *mode {
|
||||
InputMode::Normal => {
|
||||
defaults.insert(
|
||||
Key::Ctrl('g'),
|
||||
vec![Action::SwitchToMode(InputMode::Command)],
|
||||
);
|
||||
impl Keybinds {
|
||||
pub fn new() -> Keybinds {
|
||||
Keybinds::from(HashMap::<InputMode, ModeKeybinds>::new())
|
||||
}
|
||||
|
||||
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::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]);
|
||||
|
||||
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
|
||||
}
|
||||
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)]);
|
||||
/// 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::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('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::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::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::Char('p'), vec![Action::SwitchFocus(Direction::Right)]);
|
||||
defaults.insert(Key::Char('n'), vec![Action::NewPane(None)]);
|
||||
defaults.insert(Key::Char('d'), vec![Action::NewPane(Some(Direction::Down))]);
|
||||
defaults.insert(
|
||||
Key::Char('r'),
|
||||
vec![Action::NewPane(Some(Direction::Right))],
|
||||
);
|
||||
defaults.insert(Key::Char('x'), vec![Action::CloseFocus]);
|
||||
|
||||
defaults.insert(Key::Char('f'), vec![Action::ToggleFocusFullscreen]);
|
||||
|
||||
defaults.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::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::Char('n'), vec![Action::NewTab]);
|
||||
defaults.insert(Key::Char('x'), vec![Action::CloseTab]);
|
||||
|
||||
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())]);
|
||||
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());
|
||||
}
|
||||
defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Command)]);
|
||||
keybinds.0.insert(mode, mode_keybinds);
|
||||
}
|
||||
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)]);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(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
|
||||
.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),
|
||||
}
|
||||
|
||||
/// Returns the default keybinds for a given [`InputMode`].
|
||||
fn get_defaults_for_mode(mode: &InputMode) -> Result<ModeKeybinds, String> {
|
||||
let mut defaults = HashMap::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]);
|
||||
}
|
||||
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)]);
|
||||
|
||||
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('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::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::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::Char('p'), vec![Action::SwitchFocus(Direction::Right)]);
|
||||
defaults.insert(Key::Char('n'), vec![Action::NewPane(None)]);
|
||||
defaults.insert(Key::Char('d'), vec![Action::NewPane(Some(Direction::Down))]);
|
||||
defaults.insert(
|
||||
Key::Char('r'),
|
||||
vec![Action::NewPane(Some(Direction::Right))],
|
||||
);
|
||||
defaults.insert(Key::Char('x'), vec![Action::CloseFocus]);
|
||||
|
||||
defaults.insert(Key::Char('f'), vec![Action::ToggleFocusFullscreen]);
|
||||
|
||||
defaults.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::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::Char('n'), vec![Action::NewTab]);
|
||||
defaults.insert(Key::Char('x'), vec![Action::CloseTab]);
|
||||
|
||||
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)]);
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
/// 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 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
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
51
src/common/input/ut/config_test.rs
Normal file
51
src/common/input/ut/config_test.rs
Normal file
|
|
@ -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();
|
||||
//}
|
||||
0
src/common/input/ut/keybinds_test.rs
Normal file
0
src/common/input/ut/keybinds_test.rs
Normal file
|
|
@ -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();
|
||||
|
|
|
|||
0
src/tests/fixtures/config/functioning_simple_config.yaml
vendored
Normal file
0
src/tests/fixtures/config/functioning_simple_config.yaml
vendored
Normal file
8
src/tests/fixtures/config/multiple_keys_for_one_action.yaml
vendored
Normal file
8
src/tests/fixtures/config/multiple_keys_for_one_action.yaml
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
keybindings:
|
||||
Normal:
|
||||
- ? - F: 6
|
||||
- F: 7
|
||||
- F: 8
|
||||
: - {GoToTab: 5}
|
||||
|
||||
Loading…
Add table
Reference in a new issue