feat(spaceshd): config terminal+appearance sections and save

This commit is contained in:
2026-06-14 09:17:20 +07:00
parent 052f484142
commit 80113da066
+77 -3
View File
@@ -4,14 +4,34 @@
//! partial config never breaks startup.
use std::path::Path;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Deserialize)]
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct TerminalConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub font_family: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub font_size: Option<u16>,
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct AppearanceConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub theme: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub accent: Option<String>,
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct Config {
/// Shell launched for plain (no-command) panels. When unset, the daemon
/// auto-detects the user's login shell.
#[serde(default)]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_shell: Option<String>,
#[serde(default)]
pub terminal: TerminalConfig,
#[serde(default)]
pub appearance: AppearanceConfig,
}
impl Config {
@@ -27,6 +47,23 @@ impl Config {
Err(_) => Self::default(),
}
}
/// Persist to `~/.spacesh/config.toml`.
pub fn save(&self) -> std::io::Result<()> {
let dir = crate::lifecycle::spacesh_dir()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
self.save_to(&dir.join("config.toml"))
}
/// Persist to an arbitrary path. Creates the parent directory if needed.
pub fn save_to(&self, path: &Path) -> std::io::Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let s = toml::to_string_pretty(self)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
std::fs::write(path, s)
}
}
/// Resolve the shell to spawn for a plain panel.
@@ -98,4 +135,41 @@ mod tests {
std::env::remove_var("SPACESH_SHELL");
assert_eq!(s, "/tmp/fake-shell");
}
#[test]
fn parses_terminal_and_appearance() {
let dir = std::env::temp_dir().join(format!("spacesh-cfg-sections-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("config.toml");
std::fs::write(&path,
"default_shell = \"/bin/zsh\"\n[terminal]\nfont_family = \"Menlo\"\nfont_size = 15\n[appearance]\ntheme = \"light\"\naccent = \"teal\"\n").unwrap();
let c = Config::from_path(&path);
assert_eq!(c.terminal.font_family.as_deref(), Some("Menlo"));
assert_eq!(c.terminal.font_size, Some(15));
assert_eq!(c.appearance.theme.as_deref(), Some("light"));
assert_eq!(c.appearance.accent.as_deref(), Some("teal"));
let _ = std::fs::remove_file(&path);
}
#[test]
fn missing_sections_default() {
let c = Config::from_path(Path::new("/no/such/cfg.toml"));
assert!(c.terminal.font_family.is_none());
assert!(c.appearance.theme.is_none());
}
#[test]
fn save_then_reload_round_trips() {
let dir = std::env::temp_dir().join(format!("spacesh-cfg-save-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("config.toml");
let mut c = Config::default();
c.terminal.font_size = Some(14);
c.appearance.accent = Some("purple".into());
c.save_to(&path).unwrap();
let back = Config::from_path(&path);
assert_eq!(back.terminal.font_size, Some(14));
assert_eq!(back.appearance.accent.as_deref(), Some("purple"));
let _ = std::fs::remove_file(&path);
}
}