From e9547a8e0de21d0ce81867b06511b68def16aff2 Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Sun, 26 Apr 2026 04:01:01 +0300 Subject: feat: refine live config lifecycle --- src/config/io.rs | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++-- src/config/model.rs | 1 + src/config/sound.rs | 4 +-- 3 files changed, 94 insertions(+), 4 deletions(-) (limited to 'src/config') diff --git a/src/config/io.rs b/src/config/io.rs index 2774fc93..16602972 100644 --- a/src/config/io.rs +++ b/src/config/io.rs @@ -33,6 +33,7 @@ impl Config { return LoadedConfig { config, diagnostics, + invalid_sections: Vec::new(), }; } Err(err) => { @@ -40,6 +41,7 @@ impl Config { return LoadedConfig { config: Self::default(), diagnostics: vec![format!("config parse error: {err}; using defaults")], + invalid_sections: Vec::new(), }; } }, @@ -48,6 +50,7 @@ impl Config { return LoadedConfig { config: Self::default(), diagnostics: vec![format!("config read error: {err}; using defaults")], + invalid_sections: Vec::new(), }; } } @@ -55,6 +58,7 @@ impl Config { LoadedConfig { config: Self::default(), diagnostics: Vec::new(), + invalid_sections: Vec::new(), } } } @@ -97,19 +101,104 @@ pub fn load_live_config() -> Result> { return Ok(LoadedConfig { config: Config::default(), diagnostics: Vec::new(), + invalid_sections: Vec::new(), }); } let content = std::fs::read_to_string(&path) .map_err(|err| vec![format!("config read error: {err}; keeping current config")])?; - let config = toml::from_str::(&content) + load_live_config_from_str(&content) +} + +fn load_live_config_from_str(content: &str) -> Result> { + let value = content + .parse::() .map_err(|err| vec![format!("config parse error: {err}; keeping current config")])?; + let table = value.as_table().ok_or_else(|| { + vec![ + "config parse error: top-level config must be a table; keeping current config" + .to_string(), + ] + })?; + + let mut config = Config::default(); + let mut diagnostics = Vec::new(); + let mut invalid_sections = Vec::new(); + + if let Some(value) = table.get("onboarding") { + match value.clone().try_into::>() { + Ok(onboarding) => config.onboarding = onboarding, + Err(err) => diagnostics.push(format!( + "invalid onboarding setting: {err}; keeping current onboarding state" + )), + } + } + + load_live_section( + table, + "theme", + "theme config", + &mut diagnostics, + &mut invalid_sections, + |section| config.theme = section, + ); + load_live_section( + table, + "keys", + "keybinding config", + &mut diagnostics, + &mut invalid_sections, + |section| config.keys = section, + ); + load_live_section( + table, + "ui", + "ui config", + &mut diagnostics, + &mut invalid_sections, + |section| config.ui = section, + ); + load_live_section( + table, + "advanced", + "advanced config", + &mut diagnostics, + &mut invalid_sections, + |section| config.advanced = section, + ); + Ok(LoadedConfig { config, - diagnostics: Vec::new(), + diagnostics, + invalid_sections, }) } +fn load_live_section( + table: &toml::map::Map, + section: &'static str, + label: &str, + diagnostics: &mut Vec, + invalid_sections: &mut Vec, + apply: impl FnOnce(T), +) where + T: serde::de::DeserializeOwned, +{ + let Some(value) = table.get(section) else { + return; + }; + + match value.clone().try_into::() { + Ok(section_config) => apply(section_config), + Err(err) => { + diagnostics.push(format!( + "invalid {label}: {err}; keeping current {section} settings" + )); + invalid_sections.push(section.to_string()); + } + } +} + pub(crate) fn upsert_top_level_bool(content: &str, key: &str, value: bool) -> String { let replacement = format!("{key} = {value}"); let mut lines: Vec = content.lines().map(|line| line.to_string()).collect(); diff --git a/src/config/model.rs b/src/config/model.rs index 32d60493..5a17bea7 100644 --- a/src/config/model.rs +++ b/src/config/model.rs @@ -44,6 +44,7 @@ pub struct Config { pub struct LoadedConfig { pub config: Config, pub diagnostics: Vec, + pub invalid_sections: Vec, } #[derive(Debug, Deserialize)] diff --git a/src/config/sound.rs b/src/config/sound.rs index 063a521b..6219d698 100644 --- a/src/config/sound.rs +++ b/src/config/sound.rs @@ -6,7 +6,7 @@ use crate::detect::Agent; use super::io::resolve_config_relative_path; -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(default)] pub struct SoundConfig { pub enabled: bool, @@ -22,7 +22,7 @@ pub struct SoundConfig { pub agents: AgentSoundOverrides, } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(default)] pub struct AgentSoundOverrides { pub pi: AgentSoundSetting, -- cgit v1.2.3