make loading of workspace config.toml files optional and disable it by default

pull/9545/head
Dipsy 10 months ago
parent 987bfc3bd4
commit 6d9c654b2e

@ -30,8 +30,9 @@ You can use a custom configuration file by specifying it with the `-c` or
Additionally, you can reload the configuration file by sending the USR1 Additionally, you can reload the configuration file by sending the USR1
signal to the Helix process on Unix operating systems, such as by using the command `pkill -USR1 hx`. signal to the Helix process on Unix operating systems, such as by using the command `pkill -USR1 hx`.
Finally, you can have a `config.toml` local to a project by putting it under a `.helix` directory in your repository. Finally, you can have a `config.toml` local to a project by putting it under a `.helix` directory in your repository and adding `workspace-config = true` to the top of your configuration directory `config.toml`.
Its settings will be merged with the configuration directory `config.toml` and the built-in configuration. Its settings will be merged with the configuration directory `config.toml` and the built-in configuration.
Enabling workspace configs is a potential security risk. A project from an untrusted sources may e.g. contain a `.helix/config.toml` that overrides the `editor.shell` parameter to execute malicious code on your machine.
## Editor ## Editor

@ -11,14 +11,16 @@ use toml::de::Error as TomlError;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Config { pub struct Config {
pub workspace_config: bool,
pub theme: Option<String>, pub theme: Option<String>,
pub keys: HashMap<Mode, KeyTrie>, pub keys: HashMap<Mode, KeyTrie>,
pub editor: helix_view::editor::Config, pub editor: helix_view::editor::Config,
} }
#[derive(Debug, Clone, PartialEq, Deserialize)] #[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(deny_unknown_fields)] #[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct ConfigRaw { pub struct ConfigRaw {
pub workspace_config: Option<bool>,
pub theme: Option<String>, pub theme: Option<String>,
pub keys: Option<HashMap<Mode, KeyTrie>>, pub keys: Option<HashMap<Mode, KeyTrie>>,
pub editor: Option<toml::Value>, pub editor: Option<toml::Value>,
@ -27,6 +29,7 @@ pub struct ConfigRaw {
impl Default for Config { impl Default for Config {
fn default() -> Config { fn default() -> Config {
Config { Config {
workspace_config: false,
theme: None, theme: None,
keys: keymap::default(), keys: keymap::default(),
editor: helix_view::editor::Config::default(), editor: helix_view::editor::Config::default(),
@ -57,72 +60,45 @@ impl Display for ConfigLoadError {
impl Config { impl Config {
pub fn load( pub fn load(
global: Result<String, ConfigLoadError>, mut global: ConfigRaw, mut workspace: Option<ConfigRaw>,
local: Result<String, ConfigLoadError>,
) -> Result<Config, ConfigLoadError> { ) -> Result<Config, ConfigLoadError> {
let global_config: Result<ConfigRaw, ConfigLoadError> = // Create merged keymap
global.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig)); let mut keys = keymap::default();
let local_config: Result<ConfigRaw, ConfigLoadError> = [Some(&mut global), workspace.as_mut()].into_iter()
local.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig)); .flatten().filter_map(|c| c.keys.take())
let res = match (global_config, local_config) { .for_each(|k| merge_keys(&mut keys, k));
(Ok(global), Ok(local)) => {
let mut keys = keymap::default(); // Create config
if let Some(global_keys) = global.keys { let config = Config {
merge_keys(&mut keys, global_keys) workspace_config: global.workspace_config.unwrap_or(false),
} theme: workspace.as_mut().and_then(|c| c.theme.take()).or(global.theme),
if let Some(local_keys) = local.keys { keys,
merge_keys(&mut keys, local_keys) editor: match (global.editor, workspace.and_then(|c| c.editor)) {
} (None, None) => Ok(helix_view::editor::Config::default()),
(None, Some(editor)) | (Some(editor), None) => editor.try_into(),
let editor = match (global.editor, local.editor) { (Some(glob), Some(work)) => merge_toml_values(glob, work, 3).try_into(),
(None, None) => helix_view::editor::Config::default(), }.map_err(ConfigLoadError::BadConfig)?,
(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) Ok(config)
} }
pub fn load_default() -> Result<Config, ConfigLoadError> { pub fn load_default() -> Result<Config, ConfigLoadError> {
let global_config = // Load and parse global config returning all errors
fs::read_to_string(helix_loader::config_file()).map_err(ConfigLoadError::Error); let global: ConfigRaw = fs::read_to_string(helix_loader::config_file())
let local_config = fs::read_to_string(helix_loader::workspace_config_file()) .map_err(ConfigLoadError::Error)
.map_err(ConfigLoadError::Error); .and_then(|c| toml::from_str(&c)
Config::load(global_config, local_config) .map_err(ConfigLoadError::BadConfig))?;
// Load and parse workspace config if enabled ignoring IO errors
let workspace: Option<ConfigRaw> = global.workspace_config.unwrap_or(false)
.then(|| helix_loader::workspace_config_file())
.and_then(|f| fs::read_to_string(f).ok())
.map(|c| toml::from_str(&c).map_err(ConfigLoadError::BadConfig))
.transpose()?;
// Create merged config
Config::load(global, workspace)
} }
} }
@ -131,8 +107,8 @@ mod tests {
use super::*; use super::*;
impl Config { impl Config {
fn load_test(config: &str) -> Config { fn load_test(file: &str) -> Config {
Config::load(Ok(config.to_owned()), Err(ConfigLoadError::default())).unwrap() Config::load(toml::from_str(file).unwrap(), None).unwrap()
} }
} }

Loading…
Cancel
Save