1use std::path::PathBuf;
2use std::time::{Duration, SystemTime};
3
4#[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize)]
6pub struct Config {
7 pub theme: Option<String>,
9 pub custom_theme: Option<CustomThemeColors>,
11 #[serde(default)]
13 pub keybindings: Option<KeyBindings>,
14 #[serde(default)]
16 pub plugins: Option<PluginConfig>,
17}
18
19#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
23pub struct CustomThemeColors {
24 pub name: Option<String>,
25 pub accent: Option<String>,
26 pub highlight: Option<String>,
27 pub logo: Option<String>,
28 pub text: Option<String>,
29 pub text_muted: Option<String>,
30 pub background: Option<String>,
31 pub background_panel: Option<String>,
32 pub background_overlay: Option<String>,
33 pub border: Option<String>,
34 pub success: Option<String>,
35 pub error: Option<String>,
36 pub inverted_text: Option<String>,
37}
38
39#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
41pub struct KeyBindings {}
42
43#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
45pub struct PluginConfig {}
46
47impl Config {
48 pub fn load_from(dir: &std::path::Path) -> Self {
51 Self::try_load_from(dir).unwrap_or_else(|_| Config::default())
52 }
53
54 pub fn try_load_from(dir: &std::path::Path) -> Result<Self, String> {
57 let path = dir.join("config.toml");
58 if !path.exists() {
59 return Err("config.toml not found".into());
60 }
61 let content = std::fs::read_to_string(&path)
62 .map_err(|e| format!("Failed to read config.toml: {e}"))?;
63 toml::from_str(&content).map_err(|e| format!("Failed to parse config.toml: {e}"))
64 }
65
66 pub fn save_to(&self, dir: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {
68 let path = dir.join("config.toml");
69 let content = toml::to_string_pretty(self)?;
70 std::fs::write(&path, content)?;
71 Ok(())
72 }
73}
74
75#[derive(Debug)]
81pub struct ConfigManager {
82 dir: PathBuf,
83 config: Config,
84 last_modified: Option<SystemTime>,
85 pub dirty: bool,
87 error: Option<String>,
89 tick_rate: Duration,
91}
92
93impl ConfigManager {
94 pub fn new(dir: PathBuf) -> Self {
96 let last_modified = dir
97 .join("config.toml")
98 .metadata()
99 .ok()
100 .and_then(|m| m.modified().ok());
101 let (config, error) = match Config::try_load_from(&dir) {
102 Ok(cfg) => (cfg, None),
103 Err(e) => (Config::default(), Some(e)),
104 };
105 ConfigManager {
106 dir,
107 config,
108 last_modified,
109 dirty: false,
110 error,
111 tick_rate: Duration::from_millis(100),
112 }
113 }
114
115 pub fn poll(&mut self) {
117 let path = self.dir.join("config.toml");
118 let modified = match path.metadata().ok().and_then(|m| m.modified().ok()) {
119 Some(t) => t,
120 None => return,
121 };
122 let changed = match self.last_modified {
123 Some(last) => modified != last,
124 None => true,
125 };
126 if !changed {
127 return;
128 }
129 self.last_modified = Some(modified);
130 match Config::try_load_from(&self.dir) {
131 Ok(cfg) => {
132 self.config = cfg;
133 self.error = None;
134 }
135 Err(e) => {
136 self.error = Some(e);
137 }
138 }
139 self.dirty = true;
140 }
141
142 pub fn ack(&mut self) {
144 self.dirty = false;
145 }
146
147 pub fn config(&self) -> &Config {
148 &self.config
149 }
150
151 pub fn error(&self) -> Option<&str> {
153 self.error.as_deref()
154 }
155
156 pub fn save_theme(&mut self, theme_name: &str) {
160 self.config.theme = Some(theme_name.to_string());
161 self.config.custom_theme = None;
162 self.persist();
163 }
164
165 pub fn save_custom_theme(&mut self, colors: CustomThemeColors) {
167 self.config.custom_theme = Some(colors);
168 self.persist();
169 }
170
171 pub fn tick_rate(&self) -> Duration {
172 self.tick_rate
173 }
174
175 pub fn set_tick_rate(&mut self, duration: Duration) {
176 self.tick_rate = duration;
177 }
178
179 pub fn clear_custom_theme(&mut self) {
181 if self.config.custom_theme.is_some() {
182 self.config.custom_theme = None;
183 self.persist();
184 }
185 }
186
187 fn persist(&mut self) {
190 if let Err(e) = self.config.save_to(&self.dir) {
191 log::error!("[santui] Failed to save config: {e}");
192 return;
193 }
194 self.last_modified = self
195 .dir
196 .join("config.toml")
197 .metadata()
198 .ok()
199 .and_then(|m| m.modified().ok());
200 }
201}