diff --git a/book/src/configuration.md b/book/src/configuration.md index affd497c7580..eba6f5f1856e 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -26,7 +26,11 @@ hidden = false ``` You may also specify a file to use for configuration with the `-c` or -`--config` CLI argument: `hx -c path/to/custom-config.toml`. +`--config` CLI argument: `hx -c path/to/custom-config.toml`. + +Finally, you can have a `config.toml` local to a project by putting it under a `.helix` directory in your repository. +Its settings will be merged with the configuration directory `config.toml` and the built-in configuration, +if you have enabled the feature under `[editor.security]` in your global configuration. ## Editor @@ -229,3 +233,11 @@ Example: render = true character = "╎" ``` + +### `[editor.security]` Section + +Features that the paranoid among us may choose to disable. + +| Key | Description | Default | +| --- | --- | --- | +| `load-local-config` | Load `config.toml` from `$PWD/.helix` that will merge with your global configuration. | `true` | diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 4407a882f838..437d557696eb 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -55,6 +55,72 @@ impl Config { pub fn load_default() -> Result { Config::load(helix_loader::config_file()) } + + // Load a merged config from configuration and $PWD/.helix/config.toml + pub fn load_merged_config() -> Config { + let root_config: Config = std::fs::read_to_string(helix_loader::config_file()) + .ok() + .and_then(|config| toml::from_str(&config).ok()) + .unwrap_or_else(|| { + eprintln!("Bad config: {:?}", helix_loader::config_file()); + Config::halt_and_confirm("default"); + Config::default() + }); + + // Load each config file + let local_config_values = helix_loader::local_config_dirs() + .into_iter() + .map(|path| path.join("config.toml")) + .chain([helix_loader::config_file()]) + .filter_map(|file| Config::load_config_toml_values(&root_config, file)); + + // Merge configs and return, or alert user of error and load default + match local_config_values.reduce(|a, b| helix_loader::merge_toml_values(b, a, 3)) { + Some(conf) => conf.try_into().unwrap_or_default(), + None => root_config, + } + } + + // Load a specific config file if allowed by config + // Stay with toml::Values as they can be merged + pub fn load_config_toml_values( + root_config: &Config, + config_path: std::path::PathBuf, + ) -> Option { + if !config_path.exists() + || (config_path != helix_loader::config_file() + && !root_config.editor.security.load_local_config) + { + return None; + } + log::debug!("Load config: {:?}", config_path); + let bytes = std::fs::read(&config_path); + let cfg: Option = match bytes { + Ok(bytes) => { + let cfg = toml::from_slice(&bytes); + match cfg { + Ok(cfg) => Some(cfg), + Err(e) => { + eprintln!("Toml parse error for {:?}: {}", &config_path, e); + Config::halt_and_confirm("loaded"); + None + } + } + } + Err(e) => { + eprintln!("Could not read {:?}: {}", &config_path, e); + Config::halt_and_confirm("loaded"); + None + } + }; + cfg + } + + fn halt_and_confirm(config_type: &'static str) { + eprintln!("Press to continue with {} config", config_type); + let mut tmp = String::new(); + let _ = std::io::stdin().read_line(&mut tmp); + } } #[cfg(test)] diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index 7f04f2014b95..b7c80f30d65d 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Error, Result}; +use anyhow::{Context, Result}; use crossterm::event::EventStream; use helix_term::application::Application; use helix_term::args::Args; @@ -122,19 +122,7 @@ 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 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 = Config::load_merged_config(); // TODO: use the thread local executor to spawn the application task separately from the work pool let mut app = Application::new(args, config).context("unable to create new application")?; diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 0bf7ebd02cc9..5aa062bf0a4d 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -156,6 +156,9 @@ pub struct Config { /// Search configuration. #[serde(default)] pub search: SearchConfig, + /// Security settings (i.e. loading TOML files from $PWD/.helix) + #[serde(default)] + pub security: SecurityConfig, pub lsp: LspConfig, pub terminal: Option, /// Column numbers at which to draw the rulers. Default to `[]`, meaning no rulers. @@ -168,6 +171,21 @@ pub struct Config { pub color_modes: bool, } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] +pub struct SecurityConfig { + pub load_local_config: bool, + //pub load_local_languages: bool, //TODO: implement +} + +impl Default for SecurityConfig { + fn default() -> Self { + Self { + load_local_config: true, + } + } +} + #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] #[serde(default, rename_all = "kebab-case", deny_unknown_fields)] pub struct TerminalConfig { @@ -550,6 +568,7 @@ impl Default for Config { cursor_shape: CursorShapeConfig::default(), true_color: false, search: SearchConfig::default(), + security: SecurityConfig::default(), lsp: LspConfig::default(), terminal: get_terminal_provider(), rulers: Vec::new(),