aboutsummaryrefslogtreecommitdiff
path: root/src/config
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-04-22 17:01:25 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-04-22 17:01:25 +0300
commit4b482f37a1e233b9aca6bf99b12e8b4da3e5c05e (patch)
tree69d1a56ad6335547eec9727691310cf96ebe9623 /src/config
parent5bd939fbfc92a8073d0eac11dfb02a31ea638b15 (diff)
feat: add terminal-delivered client notifications
Diffstat (limited to 'src/config')
-rw-r--r--src/config/io.rs57
-rw-r--r--src/config/model.rs75
2 files changed, 112 insertions, 20 deletions
diff --git a/src/config/io.rs b/src/config/io.rs
index 3bc18c4b..1a3c1920 100644
--- a/src/config/io.rs
+++ b/src/config/io.rs
@@ -70,19 +70,6 @@ pub(super) fn resolve_config_relative_path(path: &Path) -> PathBuf {
.join(path)
}
-pub fn save_onboarding_choices(sound_enabled: bool, toast_enabled: bool) -> std::io::Result<()> {
- let path = config_path();
- if let Some(parent) = path.parent() {
- std::fs::create_dir_all(parent)?;
- }
-
- let content = std::fs::read_to_string(&path).unwrap_or_default();
- let content = upsert_top_level_bool(&content, "onboarding", false);
- let content = upsert_section_bool(&content, "ui.sound", "enabled", sound_enabled);
- let content = upsert_section_bool(&content, "ui.toast", "enabled", toast_enabled);
- std::fs::write(path, content)
-}
-
pub fn config_path() -> PathBuf {
if let Ok(path) = std::env::var(CONFIG_PATH_ENV_VAR) {
return PathBuf::from(path);
@@ -109,7 +96,7 @@ pub fn load_live_keybinds() -> Result<LiveKeybindConfig, Vec<String>> {
config.live_keybinds()
}
-pub(super) fn upsert_top_level_bool(content: &str, key: &str, value: bool) -> 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();
let mut in_section = false;
@@ -145,6 +132,38 @@ pub fn upsert_section_bool(content: &str, section: &str, key: &str, value: bool)
upsert_section_raw(content, section, key, &value.to_string())
}
+pub fn remove_section_key(content: &str, section: &str, key: &str) -> String {
+ let header = format!("[{section}]");
+ let lines: Vec<&str> = content.lines().collect();
+ let mut result = Vec::new();
+ let mut i = 0;
+ let mut in_section = false;
+
+ while i < lines.len() {
+ let line = lines[i];
+ let trimmed = line.trim();
+
+ if trimmed.starts_with('[') && trimmed.ends_with(']') {
+ in_section = trimmed == header;
+ result.push(line.to_string());
+ i += 1;
+ continue;
+ }
+
+ if in_section
+ && (trimmed.starts_with(&format!("{key} ")) || trimmed.starts_with(&format!("{key}=")))
+ {
+ i += 1;
+ continue;
+ }
+
+ result.push(line.to_string());
+ i += 1;
+ }
+
+ result.join("\n") + "\n"
+}
+
fn upsert_section_raw(content: &str, section: &str, key: &str, value: &str) -> String {
let header = format!("[{section}]");
let assignment = format!("{key} = {value}");
@@ -223,4 +242,14 @@ mod tests {
assert!(updated.contains("[ui.toast]"));
assert!(updated.contains("enabled = true"));
}
+
+ #[test]
+ fn remove_section_key_removes_matching_key_from_section() {
+ let content =
+ "[ui.toast]\nenabled = true\ndelivery = \"herdr\"\n[ui.sound]\nenabled = true\n";
+ let updated = remove_section_key(content, "ui.toast", "enabled");
+ assert!(!updated.contains("[ui.toast]\nenabled = true"));
+ assert!(updated.contains("delivery = \"herdr\""));
+ assert!(updated.contains("[ui.sound]\nenabled = true"));
+ }
}
diff --git a/src/config/model.rs b/src/config/model.rs
index 70eede5a..e2c39f25 100644
--- a/src/config/model.rs
+++ b/src/config/model.rs
@@ -1,11 +1,19 @@
-use serde::Deserialize;
+use serde::{Deserialize, Deserializer, Serialize};
use super::{CommandKeybindConfig, SoundConfig, ThemeConfig, DEFAULT_SCROLLBACK_LIMIT_BYTES};
-#[derive(Debug, Clone, Deserialize)]
-#[serde(default)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)]
+#[serde(rename_all = "lowercase")]
+pub enum ToastDelivery {
+ #[default]
+ Off,
+ Herdr,
+ Terminal,
+}
+
+#[derive(Debug, Clone)]
pub struct ToastConfig {
- pub enabled: bool,
+ pub delivery: ToastDelivery,
}
#[derive(Debug, Default, Deserialize)]
@@ -144,7 +152,31 @@ impl Default for UiConfig {
impl Default for ToastConfig {
fn default() -> Self {
- Self { enabled: false }
+ Self {
+ delivery: ToastDelivery::Off,
+ }
+ }
+}
+
+impl<'de> Deserialize<'de> for ToastConfig {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ #[derive(Deserialize, Default)]
+ #[serde(default)]
+ struct RawToastConfig {
+ delivery: Option<ToastDelivery>,
+ enabled: Option<bool>,
+ }
+
+ let raw = RawToastConfig::deserialize(deserializer)?;
+ let delivery = raw.delivery.unwrap_or_else(|| match raw.enabled {
+ Some(true) => ToastDelivery::Herdr,
+ Some(false) => ToastDelivery::Off,
+ None => ToastDelivery::Off,
+ });
+ Ok(Self { delivery })
}
}
@@ -165,10 +197,41 @@ mod tests {
fn toast_config_parses() {
let toml = r#"
[ui.toast]
+delivery = "terminal"
+"#;
+ let config: Config = toml::from_str(toml).unwrap();
+ assert_eq!(config.ui.toast.delivery, ToastDelivery::Terminal);
+ }
+
+ #[test]
+ fn toast_config_legacy_enabled_true_maps_to_herdr() {
+ let toml = r#"
+[ui.toast]
+enabled = true
+"#;
+ let config: Config = toml::from_str(toml).unwrap();
+ assert_eq!(config.ui.toast.delivery, ToastDelivery::Herdr);
+ }
+
+ #[test]
+ fn toast_config_legacy_enabled_false_maps_to_off() {
+ let toml = r#"
+[ui.toast]
+enabled = false
+"#;
+ let config: Config = toml::from_str(toml).unwrap();
+ assert_eq!(config.ui.toast.delivery, ToastDelivery::Off);
+ }
+
+ #[test]
+ fn toast_config_delivery_wins_over_legacy_enabled() {
+ let toml = r#"
+[ui.toast]
enabled = true
+delivery = "terminal"
"#;
let config: Config = toml::from_str(toml).unwrap();
- assert!(config.ui.toast.enabled);
+ assert_eq!(config.ui.toast.delivery, ToastDelivery::Terminal);
}
#[test]