aboutsummaryrefslogtreecommitdiff
path: root/src/config
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-04-26 04:01:01 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-04-26 17:06:00 +0300
commite9547a8e0de21d0ce81867b06511b68def16aff2 (patch)
tree8a025301146434b79ff295ff0d0afc833c682502 /src/config
parent3aa7903d28f94e67c14dd5cad48bbe942fbef2e9 (diff)
feat: refine live config lifecycle
Diffstat (limited to 'src/config')
-rw-r--r--src/config/io.rs93
-rw-r--r--src/config/model.rs1
-rw-r--r--src/config/sound.rs4
3 files changed, 94 insertions, 4 deletions
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<LoadedConfig, Vec<String>> {
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::<Config>(&content)
+ load_live_config_from_str(&content)
+}
+
+fn load_live_config_from_str(content: &str) -> Result<LoadedConfig, Vec<String>> {
+ let value = content
+ .parse::<toml::Value>()
.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::<Option<bool>>() {
+ 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<T>(
+ table: &toml::map::Map<String, toml::Value>,
+ section: &'static str,
+ label: &str,
+ diagnostics: &mut Vec<String>,
+ invalid_sections: &mut Vec<String>,
+ apply: impl FnOnce(T),
+) where
+ T: serde::de::DeserializeOwned,
+{
+ let Some(value) = table.get(section) else {
+ return;
+ };
+
+ match value.clone().try_into::<T>() {
+ 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<String> = 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<String>,
+ pub invalid_sections: Vec<String>,
}
#[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,