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:
|
keybinds:
|
||||||
Normal:
|
Normal:
|
||||||
Backspace: [NewPane:, NewPane:,]
|
- {F: 1}: [GoToTab: 1,]
|
||||||
{F: 1}: [NewPane:,]
|
- {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.
|
//! Deserializes configuration options.
|
||||||
use std;
|
use std;
|
||||||
//use std::collections::HashMap;
|
|
||||||
use std::error;
|
use std::error;
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::input::{keybinds, macros};
|
use super::input::keybinds::{Keybinds, MultipleKeybinds};
|
||||||
|
use crate::utils::logging::*;
|
||||||
|
|
||||||
use directories_next::ProjectDirs;
|
use directories_next::ProjectDirs;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
/// Intermediate struct
|
/// Intermediate deserialisation config struct
|
||||||
//pub struct KeybingsFromYaml {
|
|
||||||
|
|
||||||
//}
|
|
||||||
|
|
||||||
/// Intermediate struct
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct ConfigFromYaml {
|
pub struct ConfigFromYaml {
|
||||||
keybinds: Option<keybinds::Keybinds>,
|
pub keybinds: Option<MultipleKeybinds>,
|
||||||
macros: Option<Vec<macros::Macro>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///// Deserialized config state
|
/// Main configuration.
|
||||||
#[derive(Debug, Clone, Default, Deserialize)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub keybinds: keybinds::Keybinds,
|
pub keybinds: Keybinds,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ConfigError {
|
pub enum ConfigError {
|
||||||
// from the serde documentation
|
// Deserialisation error
|
||||||
// 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
|
|
||||||
Serde(serde_yaml::Error),
|
Serde(serde_yaml::Error),
|
||||||
//Eof,
|
// Io error
|
||||||
// io::Error
|
|
||||||
Io(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 {
|
impl Config {
|
||||||
/// Deserializes from given path
|
/// Uses defaults, but lets config override them.
|
||||||
pub fn new(path: &PathBuf) -> Result<Config, ConfigError> {
|
pub fn from_yaml(yaml_config: &str) -> Result<Config, ConfigError> {
|
||||||
let config: Config;
|
let config_from_yaml: ConfigFromYaml = serde_yaml::from_str(&yaml_config)?;
|
||||||
let config_deserialized: ConfigFromYaml;
|
let keybinds = Keybinds::get_default_keybinds_with_config(config_from_yaml.keybinds);
|
||||||
let mut config_string = String::new();
|
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) {
|
match File::open(path) {
|
||||||
Ok(mut file) => {
|
Ok(mut file) => {
|
||||||
file.read_to_string(&mut config_string)?;
|
let mut yaml_config = String::new();
|
||||||
config_deserialized = serde_yaml::from_str(&config_string)?;
|
file.read_to_string(&mut yaml_config)
|
||||||
config = Config {
|
.map_err(|e| ConfigError::IoPath(e, path.to_path_buf()))?;
|
||||||
keybinds: config_deserialized
|
Ok(Config::from_yaml(&yaml_config)?)
|
||||||
.keybinds
|
|
||||||
.unwrap_or_else(|| keybinds::get_default_keybinds().unwrap()),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// TODO logging, if a file is not found
|
// TODO logging, if a file is not found
|
||||||
// at an expected position - should not
|
// at an expected position - should not
|
||||||
// panic @a-kenji
|
// panic, but log to file @a-kenji
|
||||||
eprintln!("{}", e);
|
debug_log_to_file(format!(
|
||||||
config = Config::default();
|
"{}\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> {
|
pub fn from_option_or_default(option: Option<PathBuf>) -> Result<Config, ConfigError> {
|
||||||
let config;
|
|
||||||
if let Some(config_path) = option {
|
if let Some(config_path) = option {
|
||||||
config = Config::new(&config_path)?;
|
Ok(Config::new(&config_path)?)
|
||||||
} else {
|
} else {
|
||||||
let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap();
|
let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap();
|
||||||
//let config_path = PathBuf::from(project_dirs.config_dir().as_os_str());
|
let mut config_path: PathBuf = project_dirs.config_dir().to_owned().into();
|
||||||
let config_path = project_dirs.config_dir().to_owned().into();
|
config_path.push("config.yaml");
|
||||||
config = Config::new(&config_path)?;
|
Ok(Config::new(&config_path)?)
|
||||||
}
|
}
|
||||||
return Ok(config);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ConfigError {
|
impl Display for ConfigError {
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
//ConfigError::Message(msg) => formatter.write_str(msg),
|
ConfigError::Io(ref err) => write!(formatter, "IoError: {}", err),
|
||||||
//ConfigError::Eof => formatter.write_str("unexpected end of input"),
|
ConfigError::IoPath(ref err, ref path) => {
|
||||||
//
|
write!(formatter, "IoError: {}, File: {}", err, path.display(),)
|
||||||
ConfigError::Io(ref err) => write!(formatter, "Io error: {}", err),
|
}
|
||||||
ConfigError::Serde(ref err) => write!(formatter, "Serde error: {}", err),
|
ConfigError::Serde(ref err) => write!(formatter, "Deserialisation error: {}", err),
|
||||||
/* and so forth */
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -106,12 +103,8 @@ impl Display for ConfigError {
|
||||||
impl std::error::Error for ConfigError {
|
impl std::error::Error for ConfigError {
|
||||||
fn cause(&self) -> Option<&dyn error::Error> {
|
fn cause(&self) -> Option<&dyn error::Error> {
|
||||||
match *self {
|
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::Io(ref err) => Some(err),
|
||||||
//ConfigError::Message(ref err) => Some(err),
|
ConfigError::IoPath(ref err, _) => Some(err),
|
||||||
ConfigError::Serde(ref err) => Some(err),
|
ConfigError::Serde(ref err) => Some(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -128,3 +121,8 @@ impl From<serde_yaml::Error> for ConfigError {
|
||||||
ConfigError::Serde(err)
|
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::handler;
|
||||||
//use super::macros;
|
//use super::macros;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// The four directions (left, right, up, down).
|
/// The four directions (left, right, up, down).
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
|
|
@ -14,7 +14,7 @@ pub enum Direction {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Actions that can be bound to keys.
|
/// Actions that can be bound to keys.
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
/// Quit Zellij.
|
/// Quit Zellij.
|
||||||
Quit,
|
Quit,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//! Main input logic.
|
//! Main input logic.
|
||||||
|
|
||||||
use super::actions::Action;
|
use super::actions::Action;
|
||||||
use super::keybinds::get_default_keybinds;
|
use super::keybinds::Keybinds;
|
||||||
use crate::common::config::Config;
|
use crate::common::config::Config;
|
||||||
use crate::common::{update_state, AppInstruction, AppState, SenderWithContext, OPENCALLS};
|
use crate::common::{update_state, AppInstruction, AppState, SenderWithContext, OPENCALLS};
|
||||||
use crate::errors::ContextType;
|
use crate::errors::ContextType;
|
||||||
|
|
@ -15,8 +15,6 @@ use serde::{Deserialize, Serialize};
|
||||||
use strum_macros::EnumIter;
|
use strum_macros::EnumIter;
|
||||||
use termion::input::TermReadEventsAndRaw;
|
use termion::input::TermReadEventsAndRaw;
|
||||||
|
|
||||||
use super::keybinds::key_to_actions;
|
|
||||||
|
|
||||||
/// Handles the dispatching of [`Action`]s according to the current
|
/// Handles the dispatching of [`Action`]s according to the current
|
||||||
/// [`InputMode`], and keep tracks of the current [`InputMode`].
|
/// [`InputMode`], and keep tracks of the current [`InputMode`].
|
||||||
struct InputHandler {
|
struct InputHandler {
|
||||||
|
|
@ -62,9 +60,8 @@ impl InputHandler {
|
||||||
self.send_pty_instructions.update(err_ctx);
|
self.send_pty_instructions.update(err_ctx);
|
||||||
self.send_app_instructions.update(err_ctx);
|
self.send_app_instructions.update(err_ctx);
|
||||||
self.send_screen_instructions.update(err_ctx);
|
self.send_screen_instructions.update(err_ctx);
|
||||||
if let Ok(keybinds) = get_default_keybinds() {
|
if let Ok(_keybinds) = Keybinds::get_default_keybinds() {
|
||||||
let mut merged_keybinds = keybinds;
|
let keybinds = self.config.keybinds.clone();
|
||||||
merged_keybinds.extend(self.config.keybinds.clone().into_iter());
|
|
||||||
'input_loop: loop {
|
'input_loop: loop {
|
||||||
//@@@ I think this should actually just iterate over stdin directly
|
//@@@ I think this should actually just iterate over stdin directly
|
||||||
let stdin_buffer = self.os_input.read_from_stdin();
|
let stdin_buffer = self.os_input.read_from_stdin();
|
||||||
|
|
@ -84,11 +81,8 @@ impl InputHandler {
|
||||||
let mut should_break = false;
|
let mut should_break = false;
|
||||||
// Hacked on way to have a means of testing Macros, needs to
|
// Hacked on way to have a means of testing Macros, needs to
|
||||||
// get properly integrated
|
// get properly integrated
|
||||||
for action in key_to_actions(
|
for action in Keybinds::key_to_actions(
|
||||||
&key,
|
&key, raw_bytes, &self.mode, &keybinds,
|
||||||
raw_bytes,
|
|
||||||
&self.mode,
|
|
||||||
&merged_keybinds,
|
|
||||||
) {
|
) {
|
||||||
should_break |= self.dispatch_action(action);
|
should_break |= self.dispatch_action(action);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,87 @@
|
||||||
//! Mapping of inputs to sequences of actions.
|
//! Mapping of inputs to sequences of actions.
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use super::actions::{Action, Direction};
|
use super::actions::{Action, Direction};
|
||||||
use super::handler::InputMode;
|
use super::handler::InputMode;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use termion::event::Key;
|
use termion::event::Key;
|
||||||
|
|
||||||
pub type Keybinds = HashMap<InputMode, ModeKeybinds>;
|
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
||||||
type ModeKeybinds = HashMap<Key, Vec<Action>>;
|
pub struct Keybinds(HashMap<InputMode, ModeKeybinds>);
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
|
||||||
|
pub struct ModeKeybinds(HashMap<Key, Vec<Action>>);
|
||||||
|
|
||||||
|
/// 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>>);
|
||||||
|
|
||||||
|
/// Intermediate enum for configuration.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum ModeKeybind {
|
||||||
|
ModeKeybinds(ModeKeybinds),
|
||||||
|
MultipleModeKeybinds(MultipleModeKeybinds),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Keybinds {
|
||||||
|
fn default() -> Keybinds {
|
||||||
|
Keybinds::get_default_keybinds().expect("Something is wrong with the default Keybinds")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Keybinds {
|
||||||
|
pub fn new() -> Keybinds {
|
||||||
|
Keybinds::from(HashMap::<InputMode, ModeKeybinds>::new())
|
||||||
|
}
|
||||||
|
|
||||||
/// Populates the default hashmap of keybinds.
|
|
||||||
/// @@@khs26 What about an input config file?
|
|
||||||
pub fn get_default_keybinds() -> Result<Keybinds, String> {
|
pub fn get_default_keybinds() -> Result<Keybinds, String> {
|
||||||
let mut defaults = Keybinds::new();
|
let mut defaults = Keybinds::new();
|
||||||
|
|
||||||
for mode in InputMode::iter() {
|
for mode in InputMode::iter() {
|
||||||
defaults.insert(mode, get_defaults_for_mode(&mode)?);
|
defaults
|
||||||
|
.0
|
||||||
|
.insert(mode, Keybinds::get_defaults_for_mode(&mode)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(defaults)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
keybinds
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the default keybinds for a given [`InputMode`].
|
/// Returns the default keybinds for a given [`InputMode`].
|
||||||
fn get_defaults_for_mode(mode: &InputMode) -> Result<ModeKeybinds, String> {
|
fn get_defaults_for_mode(mode: &InputMode) -> Result<ModeKeybinds, String> {
|
||||||
let mut defaults = ModeKeybinds::new();
|
let mut defaults = HashMap::new();
|
||||||
|
|
||||||
match *mode {
|
match *mode {
|
||||||
InputMode::Normal => {
|
InputMode::Normal => {
|
||||||
|
|
@ -157,7 +213,7 @@ fn get_defaults_for_mode(mode: &InputMode) -> Result<ModeKeybinds, String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(defaults)
|
Ok(ModeKeybinds::from(defaults))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a [`Key`] terminal event to a sequence of [`Action`]s according to the current
|
/// Converts a [`Key`] terminal event to a sequence of [`Action`]s according to the current
|
||||||
|
|
@ -170,8 +226,10 @@ pub fn key_to_actions(
|
||||||
) -> Vec<Action> {
|
) -> Vec<Action> {
|
||||||
let mode_keybind_or_action = |action: Action| {
|
let mode_keybind_or_action = |action: Action| {
|
||||||
keybinds
|
keybinds
|
||||||
|
.0
|
||||||
.get(mode)
|
.get(mode)
|
||||||
.unwrap_or_else(|| unreachable!("Unrecognized mode: {:?}", mode))
|
.unwrap_or_else(|| unreachable!("Unrecognized mode: {:?}", mode))
|
||||||
|
.0
|
||||||
.get(key)
|
.get(key)
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_else(|| vec![action])
|
.unwrap_or_else(|| vec![action])
|
||||||
|
|
@ -181,3 +239,73 @@ pub fn key_to_actions(
|
||||||
_ => mode_keybind_or_action(Action::NoOp),
|
_ => 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 actions;
|
||||||
pub mod handler;
|
pub mod handler;
|
||||||
pub mod keybinds;
|
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::cli::CliArgs;
|
||||||
use crate::common::config::Config;
|
use crate::common::config::Config;
|
||||||
use crate::layout::Layout;
|
use crate::layout::Layout;
|
||||||
|
use crate::utils::logging::*;
|
||||||
use command_is_executing::CommandIsExecuting;
|
use command_is_executing::CommandIsExecuting;
|
||||||
use errors::{AppContext, ContextType, ErrorContext, PluginContext, PtyContext, ScreenContext};
|
use errors::{AppContext, ContextType, ErrorContext, PluginContext, PtyContext, ScreenContext};
|
||||||
use input::handler::input_loop;
|
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();
|
let mut app_state = AppState::default();
|
||||||
|
|
||||||
|
#[allow(unused_must_use)]
|
||||||
let config = Config::from_option_or_default(opts.config)
|
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();
|
.unwrap();
|
||||||
|
|
||||||
let command_is_executing = CommandIsExecuting::new();
|
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