Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/config.rs')
| -rw-r--r-- | helix-term/src/config.rs | 254 |
1 files changed, 141 insertions, 113 deletions
diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index dd051984..27dcb971 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,51 +1,47 @@ -use crate::keymap; -use crate::keymap::{merge_keys, KeyTrie}; -use helix_loader::merge_toml_values; -use helix_view::{document::Mode, theme}; +use helix_view::document::Mode; +use helix_view::keymap::{default::default, Keymap}; use serde::Deserialize; use std::collections::HashMap; use std::fmt::Display; -use std::fs; use std::io::Error as IOError; +use std::path::PathBuf; use toml::de::Error as TomlError; -#[derive(Debug, Clone, PartialEq)] -pub struct Config { - pub theme: Option<theme::Config>, - pub keys: HashMap<Mode, KeyTrie>, - pub editor: helix_view::editor::Config, -} - #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(deny_unknown_fields)] -pub struct ConfigRaw { - pub theme: Option<theme::Config>, - pub keys: Option<HashMap<Mode, KeyTrie>>, - pub editor: Option<toml::Value>, +pub struct Config { + pub theme: Option<String>, + #[serde(default = "default")] + pub keys: HashMap<Mode, Keymap>, + #[serde(default)] + pub editor: helix_view::editor::Config, } impl Default for Config { fn default() -> Config { Config { theme: None, - keys: keymap::default(), + keys: default(), editor: helix_view::editor::Config::default(), } } } +/// Merge default config keys with user overwritten keys for custom user config. +pub fn merge_keys(mut config: Config) -> Config { + let mut delta = std::mem::replace(&mut config.keys, default()); + for (mode, keys) in &mut config.keys { + keys.merge(delta.remove(mode).unwrap_or_default()) + } + config +} + #[derive(Debug)] pub enum ConfigLoadError { BadConfig(TomlError), Error(IOError), } -impl Default for ConfigLoadError { - fn default() -> Self { - ConfigLoadError::Error(IOError::new(std::io::ErrorKind::NotFound, "place holder")) - } -} - impl Display for ConfigLoadError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -56,73 +52,17 @@ impl Display for ConfigLoadError { } impl Config { - pub fn load( - global: Result<String, ConfigLoadError>, - local: Result<String, ConfigLoadError>, - ) -> Result<Config, ConfigLoadError> { - let global_config: Result<ConfigRaw, ConfigLoadError> = - global.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig)); - let local_config: Result<ConfigRaw, ConfigLoadError> = - local.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig)); - let res = match (global_config, local_config) { - (Ok(global), Ok(local)) => { - let mut keys = keymap::default(); - if let Some(global_keys) = global.keys { - merge_keys(&mut keys, global_keys) - } - if let Some(local_keys) = local.keys { - merge_keys(&mut keys, local_keys) - } - - let editor = match (global.editor, local.editor) { - (None, None) => helix_view::editor::Config::default(), - (None, Some(val)) | (Some(val), None) => { - val.try_into().map_err(ConfigLoadError::BadConfig)? - } - (Some(global), Some(local)) => merge_toml_values(global, local, 3) - .try_into() - .map_err(ConfigLoadError::BadConfig)?, - }; - - Config { - theme: local.theme.or(global.theme), - keys, - editor, - } - } - // if any configs are invalid return that first - (_, Err(ConfigLoadError::BadConfig(err))) - | (Err(ConfigLoadError::BadConfig(err)), _) => { - return Err(ConfigLoadError::BadConfig(err)) - } - (Ok(config), Err(_)) | (Err(_), Ok(config)) => { - let mut keys = keymap::default(); - if let Some(keymap) = config.keys { - merge_keys(&mut keys, keymap); - } - Config { - theme: config.theme, - keys, - editor: config.editor.map_or_else( - || Ok(helix_view::editor::Config::default()), - |val| val.try_into().map_err(ConfigLoadError::BadConfig), - )?, - } - } - - // these are just two io errors return the one for the global config - (Err(err), Err(_)) => return Err(err), - }; - - Ok(res) + pub fn load(config_path: PathBuf) -> Result<Config, ConfigLoadError> { + match std::fs::read_to_string(config_path) { + Ok(config) => toml::from_str(&config) + .map(merge_keys) + .map_err(ConfigLoadError::BadConfig), + Err(err) => Err(ConfigLoadError::Error(err)), + } } pub fn load_default() -> Result<Config, ConfigLoadError> { - let global_config = - fs::read_to_string(helix_loader::config_file()).map_err(ConfigLoadError::Error); - let local_config = fs::read_to_string(helix_loader::workspace_config_file()) - .map_err(ConfigLoadError::Error); - Config::load(global_config, local_config) + Config::load(helix_loader::config_file()) } } @@ -130,17 +70,11 @@ impl Config { mod tests { use super::*; - impl Config { - fn load_test(config: &str) -> Config { - Config::load(Ok(config.to_owned()), Err(ConfigLoadError::default())).unwrap() - } - } - #[test] fn parsing_keymaps_config_file() { - use crate::keymap; use helix_core::hashmap; use helix_view::document::Mode; + use helix_view::keymap::{self, Keymap}; let sample_keymaps = r#" [keys.insert] @@ -151,24 +85,18 @@ mod tests { A-F12 = "move_next_word_end" "#; - let mut keys = keymap::default(); - merge_keys( - &mut keys, - hashmap! { - Mode::Insert => keymap!({ "Insert mode" - "y" => move_line_down, - "S-C-a" => delete_selection, - }), - Mode::Normal => keymap!({ "Normal mode" - "A-F12" => move_next_word_end, - }), - }, - ); - assert_eq!( - Config::load_test(sample_keymaps), + toml::from_str::<Config>(sample_keymaps).unwrap(), Config { - keys, + keys: hashmap! { + Mode::Insert => Keymap::new(keymap!({ "Insert mode" + "y" => move_line_down, + "S-C-a" => delete_selection, + })), + Mode::Normal => Keymap::new(keymap!({ "Normal mode" + "A-F12" => move_next_word_end, + })), + }, ..Default::default() } ); @@ -177,11 +105,111 @@ mod tests { #[test] fn keys_resolve_to_correct_defaults() { // From serde default - let default_keys = Config::load_test("").keys; - assert_eq!(default_keys, keymap::default()); + let default_keys = toml::from_str::<Config>("").unwrap().keys; + assert_eq!(default_keys, default()); // From the Default trait let default_keys = Config::default().keys; - assert_eq!(default_keys, keymap::default()); + assert_eq!(default_keys, default()); + } + + use arc_swap::access::Constant; + use helix_core::hashmap; + + #[test] + fn merge_partial_keys() { + let config = Config { + keys: hashmap! { + Mode::Normal => Keymap::new( + keymap!({ "Normal mode" + "i" => normal_mode, + "无" => insert_mode, + "z" => jump_backward, + "g" => { "Merge into goto mode" + "$" => goto_line_end, + "g" => delete_char_forward, + }, + }) + ) + }, + ..Default::default() + }; + let mut merged_config = merge_keys(config.clone()); + assert_ne!(config, merged_config); + + let mut keymap = Keymaps::new(Box::new(Constant(merged_config.keys.clone()))); + assert_eq!( + keymap.get(Mode::Normal, key!('i')), + KeymapResult::Matched(MappableCommand::normal_mode), + "Leaf should replace leaf" + ); + assert_eq!( + keymap.get(Mode::Normal, key!('无')), + KeymapResult::Matched(MappableCommand::insert_mode), + "New leaf should be present in merged keymap" + ); + // Assumes that z is a node in the default keymap + assert_eq!( + keymap.get(Mode::Normal, key!('z')), + KeymapResult::Matched(MappableCommand::jump_backward), + "Leaf should replace node" + ); + + let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap(); + // Assumes that `g` is a node in default keymap + assert_eq!( + keymap.root().search(&[key!('g'), key!('$')]).unwrap(), + &KeyTrie::Leaf(MappableCommand::goto_line_end), + "Leaf should be present in merged subnode" + ); + // Assumes that `gg` is in default keymap + assert_eq!( + keymap.root().search(&[key!('g'), key!('g')]).unwrap(), + &KeyTrie::Leaf(MappableCommand::delete_char_forward), + "Leaf should replace old leaf in merged subnode" + ); + // Assumes that `ge` is in default keymap + assert_eq!( + keymap.root().search(&[key!('g'), key!('e')]).unwrap(), + &KeyTrie::Leaf(MappableCommand::goto_last_line), + "Old leaves in subnode should be present in merged node" + ); + + assert!(merged_config.keys.get(&Mode::Normal).unwrap().len() > 1); + assert!(merged_config.keys.get(&Mode::Insert).unwrap().len() > 0); + } + + #[test] + fn order_should_be_set() { + let config = Config { + keys: hashmap! { + Mode::Normal => Keymap::new( + keymap!({ "Normal mode" + "space" => { "" + "s" => { "" + "v" => vsplit, + "c" => hsplit, + }, + }, + }) + ) + }, + ..Default::default() + }; + let mut merged_config = merge_keys(config.clone()); + assert_ne!(config, merged_config); + let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap(); + // Make sure mapping works + assert_eq!( + keymap + .root() + .search(&[key!(' '), key!('s'), key!('v')]) + .unwrap(), + &KeyTrie::Leaf(MappableCommand::vsplit), + "Leaf should be present in merged subnode" + ); + // Make sure an order was set during merge + let node = keymap.root().search(&[helix_view::key!(' ')]).unwrap(); + assert!(!node.node().unwrap().order().is_empty()) } } |