diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 3b2d56d12..3fc91efc8 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -216,14 +216,7 @@ impl FromStr for AutoPairConfig { // only do bool parsing for runtime setting fn from_str(s: &str) -> Result { let enable: bool = s.parse()?; - - let enable = if enable { - AutoPairConfig::Enable(true) - } else { - AutoPairConfig::Enable(false) - }; - - Ok(enable) + Ok(AutoPairConfig::Enable(enable)) } } diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 7866ff9d3..0801b236e 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -872,34 +872,32 @@ pub(super) fn goto_line_number( Ok(()) } +/// Change config at runtime. Access nested values by dot syntax, for +/// example to disable smart case search, use `:set search.smart-case false`. fn setting( cx: &mut compositor::Context, args: &[Cow], _event: PromptEvent, ) -> anyhow::Result<()> { - let runtime_config = &mut cx.editor.config; - if args.len() != 2 { anyhow::bail!("Bad arguments. Usage: `:set key field`"); } - let (key, arg) = (&args[0].to_lowercase(), &args[1]); - match key.as_ref() { - "scrolloff" => runtime_config.scrolloff = arg.parse()?, - "scroll-lines" => runtime_config.scroll_lines = arg.parse()?, - "mouse" => runtime_config.mouse = arg.parse()?, - "line-number" => runtime_config.line_number = arg.parse()?, - "middle-click_paste" => runtime_config.middle_click_paste = arg.parse()?, - "auto-pairs" => runtime_config.auto_pairs = arg.parse()?, - "auto-completion" => runtime_config.auto_completion = arg.parse()?, - "completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?, - "auto-info" => runtime_config.auto_info = arg.parse()?, - "true-color" => runtime_config.true_color = arg.parse()?, - "search.smart-case" => runtime_config.search.smart_case = arg.parse()?, - "search.wrap-around" => runtime_config.search.wrap_around = arg.parse()?, - _ => anyhow::bail!("Unknown key `{}`.", args[0]), - } + let key_error = || anyhow::anyhow!("Unknown key `{key}`"); + let field_error = |_| anyhow::anyhow!("Could not parse field `{arg}`"); + + let mut config = serde_json::to_value(&cx.editor.config).unwrap(); + let pointer = format!("/{}", key.replace('.', "/")); + let value = config.pointer_mut(&pointer).ok_or_else(key_error)?; + + *value = if value.is_string() { + // JSON strings require quotes, so we can't .parse() directly + serde_json::Value::String(arg.to_string()) + } else { + arg.parse().map_err(field_error)? + }; + cx.editor.config = serde_json::from_value(config).map_err(field_error)?; Ok(()) } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 0eb613087..adf0cdf33 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -38,7 +38,7 @@ use helix_core::{ use helix_core::{Position, Selection}; use helix_dap as dap; -use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize}; +use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result where @@ -48,6 +48,18 @@ where Ok(Duration::from_millis(millis)) } +fn serialize_duration_millis(duration: &Duration, serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_u64( + duration + .as_millis() + .try_into() + .map_err(|_| serde::ser::Error::custom("duration value overflowed u64"))?, + ) +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct FilePickerConfig { @@ -109,8 +121,12 @@ pub struct Config { pub auto_pairs: AutoPairConfig, /// Automatic auto-completion, automatically pop up without user trigger. Defaults to true. pub auto_completion: bool, - /// Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. Defaults to 400ms. - #[serde(skip_serializing, deserialize_with = "deserialize_duration_millis")] + /// Time in milliseconds since last keypress before idle timers trigger. + /// Used for autocompletion, set to 0 for instant. Defaults to 400ms. + #[serde( + serialize_with = "serialize_duration_millis", + deserialize_with = "deserialize_duration_millis" + )] pub idle_timeout: Duration, pub completion_trigger_len: u8, /// Whether to display infoboxes. Defaults to true.