1use std::collections::HashMap;
8
9use serde::{Deserialize, Serialize};
10
11pub const CURRENT_TUI_CONFIG_VERSION: u32 = 3;
22
23#[derive(Debug, Clone, Default, Serialize, Deserialize)]
29pub struct TuiConfig {
30 #[serde(default)]
32 pub version: Option<u32>,
33
34 #[serde(default, skip_serializing_if = "Vec::is_empty")]
36 pub column_visibility: Vec<bool>,
37
38 #[serde(default, skip_serializing_if = "Vec::is_empty")]
40 pub column_order: Vec<String>,
41
42 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub refresh_interval_ms: Option<u64>,
45
46 #[serde(default, skip_serializing_if = "Option::is_none")]
48 pub transcript_poll_interval_ms: Option<u64>,
49
50 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub theme: Option<String>,
53
54 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub show_borders: Option<bool>,
57
58 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
61 pub profiles: HashMap<String, TuiColumnProfile>,
62
63 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub group_panel_enabled: Option<bool>,
66
67 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub animation_enabled: Option<bool>,
70
71 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub animation_duration_ms: Option<u64>,
74
75 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub sort_spec: Option<Vec<TuiSortSpec>>,
78
79 #[serde(default, skip_serializing_if = "Option::is_none")]
81 pub auto_enable_frameworks: Option<bool>,
82
83 #[serde(default, skip_serializing_if = "Option::is_none")]
85 pub terminal_spawn_method: Option<String>,
86
87 #[serde(default, skip_serializing_if = "Option::is_none")]
90 pub shorten_model_names: Option<bool>,
91
92 #[serde(default, skip_serializing_if = "Option::is_none")]
96 pub check_for_updates: Option<bool>,
97
98 #[serde(default, skip_serializing_if = "Option::is_none")]
102 pub pane_backgrounds: Option<bool>,
103}
104
105#[derive(Debug, Clone, Default, Serialize, Deserialize)]
107pub struct TuiColumnProfile {
108 #[serde(default)]
110 pub visibility: Vec<bool>,
111
112 #[serde(default)]
114 pub order: Vec<String>,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
119pub struct TuiSortSpec {
120 pub field: String,
122
123 pub order: String,
125}
126
127impl TuiConfig {
128 pub fn is_empty(&self) -> bool {
130 self.version.is_none()
131 && self.column_visibility.is_empty()
132 && self.column_order.is_empty()
133 && self.refresh_interval_ms.is_none()
134 && self.transcript_poll_interval_ms.is_none()
135 && self.theme.is_none()
136 && self.show_borders.is_none()
137 && self.profiles.is_empty()
138 && self.group_panel_enabled.is_none()
139 && self.animation_enabled.is_none()
140 && self.animation_duration_ms.is_none()
141 && self.sort_spec.is_none()
142 && self.auto_enable_frameworks.is_none()
143 && self.terminal_spawn_method.is_none()
144 && self.shorten_model_names.is_none()
145 && self.check_for_updates.is_none()
146 && self.pane_backgrounds.is_none()
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_tui_config_default_is_empty() {
156 let config = TuiConfig::default();
157 assert!(config.is_empty());
158 }
159
160 #[test]
161 fn test_tui_config_serde_roundtrip() {
162 let config = TuiConfig {
163 version: Some(CURRENT_TUI_CONFIG_VERSION),
164 column_visibility: vec![true, false, true],
165 column_order: vec!["pid".to_string(), "status".to_string(), "age".to_string()],
166 refresh_interval_ms: Some(1000),
167 transcript_poll_interval_ms: Some(5000),
168 theme: Some("dracula".to_string()),
169 show_borders: Some(true),
170 profiles: HashMap::new(),
171 group_panel_enabled: Some(true),
172 animation_enabled: Some(true),
173 animation_duration_ms: Some(220),
174 sort_spec: Some(vec![TuiSortSpec {
175 field: "cpu".to_string(),
176 order: "descending".to_string(),
177 }]),
178 auto_enable_frameworks: Some(false),
179 terminal_spawn_method: Some("new_mux_pane".to_string()),
180 shorten_model_names: Some(true),
181 check_for_updates: Some(true),
182 pane_backgrounds: Some(false),
183 };
184
185 let toml_str = toml::to_string_pretty(&config).unwrap();
186 let parsed: TuiConfig = toml::from_str(&toml_str).unwrap();
187
188 assert_eq!(parsed.version, config.version);
189 assert_eq!(parsed.column_visibility, config.column_visibility);
190 assert_eq!(parsed.column_order, config.column_order);
191 assert_eq!(parsed.theme, config.theme);
192 assert_eq!(parsed.refresh_interval_ms, config.refresh_interval_ms);
193 }
194
195 #[test]
196 fn test_tui_config_empty_fields_not_serialized() {
197 let config = TuiConfig {
198 theme: Some("default".to_string()),
199 ..Default::default()
200 };
201
202 let toml_str = toml::to_string_pretty(&config).unwrap();
203 assert!(!toml_str.contains("column_visibility"));
205 assert!(!toml_str.contains("profiles"));
206 assert!(toml_str.contains("theme"));
208 }
209}