Skip to main content

zellij_sheets/
config.rs

1// Zellij Sheets - Configuration Module
2// Handles plugin configuration and settings
3
4use serde::{Deserialize, Serialize};
5use std::default::Default;
6
7/// Main configuration structure for the spreadsheet viewer
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct SheetsConfig {
10    /// Theme configuration
11    pub theme: ThemeConfig,
12
13    /// Display settings
14    pub display: DisplayConfig,
15
16    /// Behavior settings
17    pub behavior: BehaviorConfig,
18
19    /// Column configuration
20    pub columns: ColumnConfig,
21}
22
23/// Theme configuration for the spreadsheet viewer
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ThemeConfig {
26    /// Background color
27    pub background: String,
28
29    /// Text color
30    pub text: String,
31
32    /// Header background color
33    pub header_background: String,
34
35    /// Header text color
36    pub header_text: String,
37
38    /// Selected row background color
39    pub selected_background: String,
40
41    /// Selected row text color
42    pub selected_text: String,
43
44    /// Column header background color
45    pub column_header_background: String,
46
47    /// Column header text color
48    pub column_header_text: String,
49
50    /// Accent colors for different data types
51    pub accent_colors: AccentColors,
52}
53
54/// Accent colors for different data types
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct AccentColors {
57    /// Number color
58    pub number: String,
59
60    /// String color
61    pub string: String,
62
63    /// Boolean color
64    pub boolean: String,
65
66    /// Date color
67    pub date: String,
68}
69
70/// Display configuration for the spreadsheet viewer
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct DisplayConfig {
73    /// Number of preview rows
74    pub preview_rows: usize,
75
76    /// Show column numbers
77    pub show_column_numbers: bool,
78
79    /// Show row numbers
80    pub show_row_numbers: bool,
81
82    /// Truncate long cell values
83    pub truncate_long_values: bool,
84
85    /// Maximum cell value length before truncation
86    pub max_cell_length: usize,
87
88    /// Show data type indicators
89    pub show_data_types: bool,
90}
91
92/// Behavior configuration for the spreadsheet viewer
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct BehaviorConfig {
95    /// Auto-refresh when file changes
96    pub auto_refresh: bool,
97
98    /// Auto-refresh interval in seconds
99    pub auto_refresh_interval: u64,
100
101    /// Default scroll speed
102    pub scroll_speed: ScrollSpeed,
103
104    /// Page size for page navigation
105    pub page_size: usize,
106}
107
108/// Scroll speed configuration
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct ScrollSpeed {
111    /// Normal scroll speed
112    pub normal: f32,
113
114    /// Fast scroll speed
115    pub fast: f32,
116}
117
118/// Column configuration for the spreadsheet viewer
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct ColumnConfig {
121    /// Auto-width columns based on content
122    pub auto_width: bool,
123
124    /// Fixed column widths
125    pub fixed_widths: Vec<usize>,
126
127    /// Minimum column width
128    pub min_column_width: usize,
129
130    /// Maximum column width
131    pub max_column_width: usize,
132
133    /// Column width mode
134    pub width_mode: ColumnWidthMode,
135}
136
137/// Column width mode for the spreadsheet viewer
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub enum ColumnWidthMode {
140    /// Auto-width based on content
141    Auto,
142    /// Fixed width for all columns
143    Fixed,
144    /// Mixed mode with some auto and some fixed
145    Mixed,
146}
147
148/// Default configuration
149impl 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
160/// Default configuration
161impl 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
177/// Default configuration
178impl 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
189/// Default configuration
190impl 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
203/// Default configuration
204impl 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
215/// Default configuration
216impl Default for ScrollSpeed {
217    fn default() -> Self {
218        Self {
219            normal: 1.0,
220            fast: 3.0,
221        }
222    }
223}
224
225/// Default configuration
226impl 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
238/// Load configuration from TOML file
239pub 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
245/// Save configuration to TOML file
246pub 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/// Configuration error types
253#[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
268/// Get default configuration path
269pub 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
276/// Validate configuration
277pub fn validate_config(config: &SheetsConfig) -> Result<(), ConfigError> {
278    // Validate theme colors
279    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 accent colors
289    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    // Validate display settings
295    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    // Validate behavior settings
308    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    // Validate column configuration
321    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    // Validate fixed widths if provided
340    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
351/// Validate color format (hex or named)
352fn validate_color(color: &str) -> Result<(), ConfigError> {
353    if color.starts_with('#') {
354        // Hex color
355        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    // Allow named colors (we'll let the terminal handle them)
364    Ok(())
365}