1use serde::{Deserialize, Serialize};
5use std::default::Default;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct SheetsConfig {
10 pub theme: ThemeConfig,
12
13 pub display: DisplayConfig,
15
16 pub behavior: BehaviorConfig,
18
19 pub columns: ColumnConfig,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ThemeConfig {
26 pub background: String,
28
29 pub text: String,
31
32 pub header_background: String,
34
35 pub header_text: String,
37
38 pub selected_background: String,
40
41 pub selected_text: String,
43
44 pub column_header_background: String,
46
47 pub column_header_text: String,
49
50 pub accent_colors: AccentColors,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct AccentColors {
57 pub number: String,
59
60 pub string: String,
62
63 pub boolean: String,
65
66 pub date: String,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct DisplayConfig {
73 pub preview_rows: usize,
75
76 pub show_column_numbers: bool,
78
79 pub show_row_numbers: bool,
81
82 pub truncate_long_values: bool,
84
85 pub max_cell_length: usize,
87
88 pub show_data_types: bool,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct BehaviorConfig {
95 pub auto_refresh: bool,
97
98 pub auto_refresh_interval: u64,
100
101 pub scroll_speed: ScrollSpeed,
103
104 pub page_size: usize,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct ScrollSpeed {
111 pub normal: f32,
113
114 pub fast: f32,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct ColumnConfig {
121 pub auto_width: bool,
123
124 pub fixed_widths: Vec<usize>,
126
127 pub min_column_width: usize,
129
130 pub max_column_width: usize,
132
133 pub width_mode: ColumnWidthMode,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139pub enum ColumnWidthMode {
140 Auto,
142 Fixed,
144 Mixed,
146}
147
148impl Default for SheetsConfig {
150 fn default() -> Self {
151 Self {
152 theme: ThemeConfig::default(),
153 display: DisplayConfig::default(),
154 behavior: BehaviorConfig::default(),
155 columns: ColumnConfig::default(),
156 }
157 }
158}
159
160impl Default for ThemeConfig {
162 fn default() -> Self {
163 Self {
164 background: "#000000".to_string(),
165 text: "#FFFFFF".to_string(),
166 header_background: "#0055AA".to_string(),
167 header_text: "#FFFFFF".to_string(),
168 selected_background: "#00AAFF".to_string(),
169 selected_text: "#000000".to_string(),
170 column_header_background: "#004488".to_string(),
171 column_header_text: "#00FFFF".to_string(),
172 accent_colors: AccentColors::default(),
173 }
174 }
175}
176
177impl Default for AccentColors {
179 fn default() -> Self {
180 Self {
181 number: "#00FF00".to_string(),
182 string: "#FFFF00".to_string(),
183 boolean: "#FF00FF".to_string(),
184 date: "#FF8800".to_string(),
185 }
186 }
187}
188
189impl Default for DisplayConfig {
191 fn default() -> Self {
192 Self {
193 preview_rows: 20,
194 show_column_numbers: true,
195 show_row_numbers: false,
196 truncate_long_values: true,
197 max_cell_length: 50,
198 show_data_types: false,
199 }
200 }
201}
202
203impl Default for BehaviorConfig {
205 fn default() -> Self {
206 Self {
207 auto_refresh: false,
208 auto_refresh_interval: 5,
209 scroll_speed: ScrollSpeed::default(),
210 page_size: 10,
211 }
212 }
213}
214
215impl Default for ScrollSpeed {
217 fn default() -> Self {
218 Self {
219 normal: 1.0,
220 fast: 3.0,
221 }
222 }
223}
224
225impl Default for ColumnConfig {
227 fn default() -> Self {
228 Self {
229 auto_width: true,
230 fixed_widths: Vec::new(),
231 min_column_width: 8,
232 max_column_width: 40,
233 width_mode: ColumnWidthMode::Auto,
234 }
235 }
236}
237
238pub fn load_config(path: &str) -> Result<SheetsConfig, ConfigError> {
240 let content = std::fs::read_to_string(path)?;
241 let config: SheetsConfig = toml::from_str(&content)?;
242 Ok(config)
243}
244
245pub fn save_config(config: &SheetsConfig, path: &str) -> Result<(), ConfigError> {
247 let content = toml::to_string_pretty(config)?;
248 std::fs::write(path, content)?;
249 Ok(())
250}
251
252#[derive(Debug, thiserror::Error)]
254pub enum ConfigError {
255 #[error("IO error: {0}")]
256 IoError(#[from] std::io::Error),
257
258 #[error("TOML parsing error: {0}")]
259 TomlError(#[from] toml::de::Error),
260
261 #[error("TOML serialization error: {0}")]
262 TomlSerializeError(#[from] toml::ser::Error),
263
264 #[error("Invalid configuration: {0}")]
265 InvalidConfig(String),
266}
267
268pub fn default_config_path() -> Option<String> {
270 let home = std::env::var("HOME").ok()?;
271 let config_dir = format!("{}/.config/zellij-sheets", home);
272 std::fs::create_dir_all(&config_dir).ok()?;
273 Some(format!("{}/config.toml", config_dir))
274}
275
276pub fn validate_config(config: &SheetsConfig) -> Result<(), ConfigError> {
278 validate_color(&config.theme.background)?;
280 validate_color(&config.theme.text)?;
281 validate_color(&config.theme.header_background)?;
282 validate_color(&config.theme.header_text)?;
283 validate_color(&config.theme.selected_background)?;
284 validate_color(&config.theme.selected_text)?;
285 validate_color(&config.theme.column_header_background)?;
286 validate_color(&config.theme.column_header_text)?;
287
288 validate_color(&config.theme.accent_colors.number)?;
290 validate_color(&config.theme.accent_colors.string)?;
291 validate_color(&config.theme.accent_colors.boolean)?;
292 validate_color(&config.theme.accent_colors.date)?;
293
294 if config.display.preview_rows == 0 {
296 return Err(ConfigError::InvalidConfig(
297 "preview_rows must be greater than 0".to_string(),
298 ));
299 }
300
301 if config.display.max_cell_length == 0 {
302 return Err(ConfigError::InvalidConfig(
303 "max_cell_length must be greater than 0".to_string(),
304 ));
305 }
306
307 if config.behavior.auto_refresh_interval == 0 {
309 return Err(ConfigError::InvalidConfig(
310 "auto_refresh_interval must be greater than 0".to_string(),
311 ));
312 }
313
314 if config.behavior.page_size == 0 {
315 return Err(ConfigError::InvalidConfig(
316 "page_size must be greater than 0".to_string(),
317 ));
318 }
319
320 if config.columns.min_column_width == 0 {
322 return Err(ConfigError::InvalidConfig(
323 "min_column_width must be greater than 0".to_string(),
324 ));
325 }
326
327 if config.columns.max_column_width == 0 {
328 return Err(ConfigError::InvalidConfig(
329 "max_column_width must be greater than 0".to_string(),
330 ));
331 }
332
333 if config.columns.min_column_width > config.columns.max_column_width {
334 return Err(ConfigError::InvalidConfig(
335 "min_column_width must not exceed max_column_width".to_string(),
336 ));
337 }
338
339 for &width in &config.columns.fixed_widths {
341 if width == 0 {
342 return Err(ConfigError::InvalidConfig(
343 "fixed_widths must be greater than 0".to_string(),
344 ));
345 }
346 }
347
348 Ok(())
349}
350
351fn validate_color(color: &str) -> Result<(), ConfigError> {
353 if color.starts_with('#') {
354 let hex = color.trim_start_matches('#');
356 if hex.len() != 6 && hex.len() != 3 {
357 return Err(ConfigError::InvalidConfig(format!(
358 "Invalid hex color format: {}",
359 color
360 )));
361 }
362 }
363 Ok(())
365}