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
99#[derive(Debug, Clone, Default, Serialize, Deserialize)]
101pub struct TuiColumnProfile {
102 #[serde(default)]
104 pub visibility: Vec<bool>,
105
106 #[serde(default)]
108 pub order: Vec<String>,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
113pub struct TuiSortSpec {
114 pub field: String,
116
117 pub order: String,
119}
120
121impl TuiConfig {
122 pub fn is_empty(&self) -> bool {
124 self.version.is_none()
125 && self.column_visibility.is_empty()
126 && self.column_order.is_empty()
127 && self.refresh_interval_ms.is_none()
128 && self.transcript_poll_interval_ms.is_none()
129 && self.theme.is_none()
130 && self.show_borders.is_none()
131 && self.profiles.is_empty()
132 && self.group_panel_enabled.is_none()
133 && self.animation_enabled.is_none()
134 && self.animation_duration_ms.is_none()
135 && self.sort_spec.is_none()
136 && self.auto_enable_frameworks.is_none()
137 && self.terminal_spawn_method.is_none()
138 && self.shorten_model_names.is_none()
139 && self.check_for_updates.is_none()
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_tui_config_default_is_empty() {
149 let config = TuiConfig::default();
150 assert!(config.is_empty());
151 }
152
153 #[test]
154 fn test_tui_config_serde_roundtrip() {
155 let config = TuiConfig {
156 version: Some(CURRENT_TUI_CONFIG_VERSION),
157 column_visibility: vec![true, false, true],
158 column_order: vec!["pid".to_string(), "status".to_string(), "age".to_string()],
159 refresh_interval_ms: Some(1000),
160 transcript_poll_interval_ms: Some(5000),
161 theme: Some("dracula".to_string()),
162 show_borders: Some(true),
163 profiles: HashMap::new(),
164 group_panel_enabled: Some(true),
165 animation_enabled: Some(true),
166 animation_duration_ms: Some(220),
167 sort_spec: Some(vec![TuiSortSpec {
168 field: "cpu".to_string(),
169 order: "descending".to_string(),
170 }]),
171 auto_enable_frameworks: Some(false),
172 terminal_spawn_method: Some("new_mux_pane".to_string()),
173 shorten_model_names: Some(true),
174 check_for_updates: Some(true),
175 };
176
177 let toml_str = toml::to_string_pretty(&config).unwrap();
178 let parsed: TuiConfig = toml::from_str(&toml_str).unwrap();
179
180 assert_eq!(parsed.version, config.version);
181 assert_eq!(parsed.column_visibility, config.column_visibility);
182 assert_eq!(parsed.column_order, config.column_order);
183 assert_eq!(parsed.theme, config.theme);
184 assert_eq!(parsed.refresh_interval_ms, config.refresh_interval_ms);
185 }
186
187 #[test]
188 fn test_tui_config_empty_fields_not_serialized() {
189 let config = TuiConfig {
190 theme: Some("default".to_string()),
191 ..Default::default()
192 };
193
194 let toml_str = toml::to_string_pretty(&config).unwrap();
195 assert!(!toml_str.contains("column_visibility"));
197 assert!(!toml_str.contains("profiles"));
198 assert!(toml_str.contains("theme"));
200 }
201}