diff options
Diffstat (limited to 'helix-term')
-rw-r--r-- | helix-term/src/commands.rs | 6 | ||||
-rw-r--r-- | helix-term/src/commands/typed.rs | 26 | ||||
-rw-r--r-- | helix-term/src/config.rs | 136 | ||||
-rw-r--r-- | helix-term/src/keymap.rs | 79 | ||||
-rw-r--r-- | helix-term/src/main.rs | 27 | ||||
-rw-r--r-- | helix-term/tests/test/helpers.rs | 12 |
6 files changed, 193 insertions, 93 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 2f41a2dc..e4d0d753 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -12,7 +12,7 @@ pub use typed::*; use helix_core::{ char_idx_at_visual_offset, comment, doc_formatter::TextFormat, - encoding, find_first_non_whitespace_char, find_root, graphemes, + encoding, find_first_non_whitespace_char, find_workspace, graphemes, history::UndoKind, increment, indent, indent::IndentStyle, @@ -2419,9 +2419,7 @@ fn append_mode(cx: &mut Context) { } fn file_picker(cx: &mut Context) { - // We don't specify language markers, root will be the root of the current - // git repo or the current dir if we're not in a repo - let root = find_root(None, &[]); + let root = find_workspace(); let picker = ui::file_picker(root, &cx.editor.config()); cx.push_layer(Box::new(overlayed(picker))); } diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 2c72686d..ca55151a 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1371,13 +1371,16 @@ fn lsp_restart( return Ok(()); } + let editor_config = cx.editor.config.load(); let (_view, doc) = current!(cx.editor); let config = doc .language_config() .context("LSP not defined for the current document")?; let scope = config.scope.clone(); - cx.editor.language_servers.restart(config, doc.path())?; + cx.editor + .language_servers + .restart(config, doc.path(), &editor_config.workspace_lsp_roots)?; // This collect is needed because refresh_language_server would need to re-borrow editor. let document_ids_to_refresh: Vec<DocumentId> = cx @@ -1970,6 +1973,20 @@ fn open_config( Ok(()) } +fn open_workspace_config( + cx: &mut compositor::Context, + _args: &[Cow<str>], + event: PromptEvent, +) -> anyhow::Result<()> { + if event != PromptEvent::Validate { + return Ok(()); + } + + cx.editor + .open(&helix_loader::workspace_config_file(), Action::Replace)?; + Ok(()) +} + fn open_log( cx: &mut compositor::Context, _args: &[Cow<str>], @@ -2647,6 +2664,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ signature: CommandSignature::none(), }, TypableCommand { + name: "config-open-workspace", + aliases: &[], + doc: "Open the workspace config.toml file.", + fun: open_workspace_config, + signature: CommandSignature::none(), + }, + TypableCommand { name: "log-open", aliases: &[], doc: "Open the helix log file.", diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 4407a882..9776ef7a 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -1,27 +1,34 @@ -use crate::keymap::{default::default, merge_keys, Keymap}; +use crate::keymap; +use crate::keymap::{merge_keys, Keymap}; +use helix_loader::merge_toml_values; use helix_view::document::Mode; 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, Deserialize)] -#[serde(deny_unknown_fields)] +#[derive(Debug, Clone, PartialEq)] pub struct Config { pub theme: Option<String>, - #[serde(default = "default")] pub keys: HashMap<Mode, Keymap>, - #[serde(default)] pub editor: helix_view::editor::Config, } +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigRaw { + pub theme: Option<String>, + pub keys: Option<HashMap<Mode, Keymap>>, + pub editor: Option<toml::Value>, +} + impl Default for Config { fn default() -> Config { Config { theme: None, - keys: default(), + keys: keymap::default(), editor: helix_view::editor::Config::default(), } } @@ -33,6 +40,12 @@ pub enum ConfigLoadError { 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 { @@ -43,17 +56,72 @@ impl Display for ConfigLoadError { } impl Config { - 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( + 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_default() -> Result<Config, ConfigLoadError> { - Config::load(helix_loader::config_file()) + 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) } } @@ -61,6 +129,12 @@ 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; @@ -77,18 +151,24 @@ mod tests { A-F12 = "move_next_word_end" "#; + let mut keys = keymap::default(); + merge_keys( + &mut 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, + })), + }, + ); + assert_eq!( - toml::from_str::<Config>(sample_keymaps).unwrap(), + Config::load_test(sample_keymaps), Config { - 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, - })), - }, + keys, ..Default::default() } ); @@ -97,11 +177,11 @@ mod tests { #[test] fn keys_resolve_to_correct_defaults() { // From serde default - let default_keys = toml::from_str::<Config>("").unwrap().keys; - assert_eq!(default_keys, default()); + let default_keys = Config::load_test("").keys; + assert_eq!(default_keys, keymap::default()); // From the Default trait let default_keys = Config::default().keys; - assert_eq!(default_keys, default()); + assert_eq!(default_keys, keymap::default()); } } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index e94a5f66..3033c6a4 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -2,7 +2,6 @@ pub mod default; pub mod macros; pub use crate::commands::MappableCommand; -use crate::config::Config; use arc_swap::{ access::{DynAccess, DynGuard}, ArcSwap, @@ -16,7 +15,7 @@ use std::{ sync::Arc, }; -use default::default; +pub use default::default; use macros::key; #[derive(Debug, Clone)] @@ -417,12 +416,10 @@ impl Default for Keymaps { } /// 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 { +pub fn merge_keys(dst: &mut HashMap<Mode, Keymap>, mut delta: HashMap<Mode, Keymap>) { + for (mode, keys) in dst { keys.merge(delta.remove(mode).unwrap_or_default()) } - config } #[cfg(test)] @@ -449,26 +446,24 @@ mod tests { #[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 keymap = 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, + }, + }) + ) }; - let mut merged_config = merge_keys(config.clone()); - assert_ne!(config, merged_config); + let mut merged_keyamp = default(); + merge_keys(&mut merged_keyamp, keymap.clone()); + assert_ne!(keymap, merged_keyamp); - let mut keymap = Keymaps::new(Box::new(Constant(merged_config.keys.clone()))); + let mut keymap = Keymaps::new(Box::new(Constant(merged_keyamp.clone()))); assert_eq!( keymap.get(Mode::Normal, key!('i')), KeymapResult::Matched(MappableCommand::normal_mode), @@ -486,7 +481,7 @@ mod tests { "Leaf should replace node" ); - let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap(); + let keymap = merged_keyamp.get_mut(&Mode::Normal).unwrap(); // Assumes that `g` is a node in default keymap assert_eq!( keymap.root().search(&[key!('g'), key!('$')]).unwrap(), @@ -506,30 +501,28 @@ mod tests { "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); + assert!(merged_keyamp.get(&Mode::Normal).unwrap().len() > 1); + assert!(merged_keyamp.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, - }, + let keymap = 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(); + let mut merged_keyamp = default(); + merge_keys(&mut merged_keyamp, keymap.clone()); + assert_ne!(keymap, merged_keyamp); + let keymap = merged_keyamp.get_mut(&Mode::Normal).unwrap(); // Make sure mapping works assert_eq!( keymap diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index aac5c537..e0c3f6e7 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -3,7 +3,7 @@ use crossterm::event::EventStream; use helix_loader::VERSION_AND_GIT_HASH; use helix_term::application::Application; use helix_term::args::Args; -use helix_term::config::Config; +use helix_term::config::{Config, ConfigLoadError}; use std::path::PathBuf; fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> { @@ -126,18 +126,19 @@ FLAGS: helix_loader::initialize_config_file(args.config_file.clone()); - let config = match std::fs::read_to_string(helix_loader::config_file()) { - Ok(config) => toml::from_str(&config) - .map(helix_term::keymap::merge_keys) - .unwrap_or_else(|err| { - eprintln!("Bad config: {}", err); - eprintln!("Press <ENTER> to continue with default config"); - use std::io::Read; - let _ = std::io::stdin().read(&mut []); - Config::default() - }), - Err(err) if err.kind() == std::io::ErrorKind::NotFound => Config::default(), - Err(err) => return Err(Error::new(err)), + let config = match Config::load_default() { + Ok(config) => config, + Err(ConfigLoadError::Error(err)) if err.kind() == std::io::ErrorKind::NotFound => { + Config::default() + } + Err(ConfigLoadError::Error(err)) => return Err(Error::new(err)), + Err(ConfigLoadError::BadConfig(err)) => { + eprintln!("Bad config: {}", err); + eprintln!("Press <ENTER> to continue with default config"); + use std::io::Read; + let _ = std::io::stdin().read(&mut []); + Config::default() + } }; let syn_loader_conf = helix_core::config::user_syntax_loader().unwrap_or_else(|err| { diff --git a/helix-term/tests/test/helpers.rs b/helix-term/tests/test/helpers.rs index ccd07bfa..30fe7d0e 100644 --- a/helix-term/tests/test/helpers.rs +++ b/helix-term/tests/test/helpers.rs @@ -1,6 +1,7 @@ use std::{ fs::File, io::{Read, Write}, + mem::replace, path::PathBuf, time::Duration, }; @@ -222,10 +223,11 @@ pub fn temp_file_with_contents<S: AsRef<str>>( /// Generates a config with defaults more suitable for integration tests pub fn test_config() -> Config { - merge_keys(Config { + Config { editor: test_editor_config(), + keys: helix_term::keymap::default(), ..Default::default() - }) + } } pub fn test_editor_config() -> helix_view::editor::Config { @@ -300,8 +302,10 @@ impl AppBuilder { // Remove this attribute once `with_config` is used in a test: #[allow(dead_code)] - pub fn with_config(mut self, config: Config) -> Self { - self.config = helix_term::keymap::merge_keys(config); + pub fn with_config(mut self, mut config: Config) -> Self { + let keys = replace(&mut config.keys, helix_term::keymap::default()); + merge_keys(&mut config.keys, keys); + self.config = config; self } |