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.rs254
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())
}
}