1use serde::de::{self, Deserializer};
7use serde::ser::Serializer;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)]
12#[serde(rename_all = "lowercase")]
13pub enum StatusBarSection {
14 #[default]
16 Left,
17 Center,
19 Right,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
34pub enum WidgetId {
35 Clock,
37 UsernameHostname,
39 CurrentDirectory,
41 GitBranch,
43 CpuUsage,
45 MemoryUsage,
47 NetworkStatus,
49 DiskFree,
51 BellIndicator,
53 CurrentCommand,
55 UpdateAvailable,
57 AgentUsage,
59 AgentRoster,
62 Custom(String),
64 Plugin(String),
66}
67
68impl WidgetId {
69 pub fn label(&self) -> &str {
71 match self {
72 WidgetId::Clock => "Clock",
73 WidgetId::UsernameHostname => "User@Host",
74 WidgetId::CurrentDirectory => "Directory",
75 WidgetId::GitBranch => "Git Branch",
76 WidgetId::CpuUsage => "CPU Usage",
77 WidgetId::MemoryUsage => "Memory Usage",
78 WidgetId::NetworkStatus => "Network Status",
79 WidgetId::DiskFree => "Disk Free",
80 WidgetId::BellIndicator => "Bell Indicator",
81 WidgetId::CurrentCommand => "Current Command",
82 WidgetId::UpdateAvailable => "Update Available",
83 WidgetId::AgentUsage => "Agent Usage",
84 WidgetId::AgentRoster => "Agent Roster",
85 WidgetId::Custom(name) => name.as_str(),
86 WidgetId::Plugin(id) => id.as_str(),
87 }
88 }
89
90 pub fn icon(&self) -> &str {
92 match self {
93 WidgetId::Clock => "\u{1f551}", WidgetId::UsernameHostname => "\u{1f464}", WidgetId::CurrentDirectory => "\u{1f4c2}", WidgetId::GitBranch => "\u{1f500}", WidgetId::CpuUsage => "\u{1f4bb}", WidgetId::MemoryUsage => "\u{1f4be}", WidgetId::NetworkStatus => "\u{1f310}", WidgetId::DiskFree => "\u{1f4bf}", WidgetId::BellIndicator => "\u{1f514}", WidgetId::CurrentCommand => "\u{25b6}", WidgetId::UpdateAvailable => "\u{2b06}", WidgetId::AgentUsage => "\u{25c6}", WidgetId::AgentRoster => "\u{1f465}", WidgetId::Custom(_) => "\u{2699}", WidgetId::Plugin(_) => "\u{1f9e9}", }
109 }
110
111 pub fn needs_system_monitor(&self) -> bool {
113 matches!(
114 self,
115 WidgetId::CpuUsage | WidgetId::MemoryUsage | WidgetId::NetworkStatus
116 )
117 }
118
119 pub fn needs_disk_monitor(&self) -> bool {
121 matches!(self, WidgetId::DiskFree)
122 }
123
124 fn as_key(&self) -> String {
127 match self {
128 WidgetId::Clock => "clock".to_string(),
129 WidgetId::UsernameHostname => "username_hostname".to_string(),
130 WidgetId::CurrentDirectory => "current_directory".to_string(),
131 WidgetId::GitBranch => "git_branch".to_string(),
132 WidgetId::CpuUsage => "cpu_usage".to_string(),
133 WidgetId::MemoryUsage => "memory_usage".to_string(),
134 WidgetId::NetworkStatus => "network_status".to_string(),
135 WidgetId::DiskFree => "disk_free".to_string(),
136 WidgetId::BellIndicator => "bell_indicator".to_string(),
137 WidgetId::CurrentCommand => "current_command".to_string(),
138 WidgetId::UpdateAvailable => "update_available".to_string(),
139 WidgetId::AgentUsage => "agent_usage".to_string(),
140 WidgetId::AgentRoster => "agent_roster".to_string(),
141 WidgetId::Custom(name) => format!("custom:{name}"),
142 WidgetId::Plugin(id) => format!("plugin:{id}"),
143 }
144 }
145
146 fn from_key(key: &str) -> Option<WidgetId> {
149 if let Some(name) = key.strip_prefix("custom:") {
150 return Some(WidgetId::Custom(name.to_string()));
151 }
152 if let Some(id) = key.strip_prefix("plugin:") {
153 return Some(WidgetId::Plugin(id.to_string()));
154 }
155 Some(match key {
156 "clock" => WidgetId::Clock,
157 "username_hostname" => WidgetId::UsernameHostname,
158 "current_directory" => WidgetId::CurrentDirectory,
159 "git_branch" => WidgetId::GitBranch,
160 "cpu_usage" => WidgetId::CpuUsage,
161 "memory_usage" => WidgetId::MemoryUsage,
162 "network_status" => WidgetId::NetworkStatus,
163 "disk_free" => WidgetId::DiskFree,
164 "bell_indicator" => WidgetId::BellIndicator,
165 "current_command" => WidgetId::CurrentCommand,
166 "update_available" => WidgetId::UpdateAvailable,
167 "agent_usage" => WidgetId::AgentUsage,
168 "agent_roster" => WidgetId::AgentRoster,
169 _ => return None,
170 })
171 }
172}
173
174impl Serialize for WidgetId {
175 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
176 where
177 S: Serializer,
178 {
179 serializer.serialize_str(&self.as_key())
180 }
181}
182
183impl<'de> Deserialize<'de> for WidgetId {
184 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
185 where
186 D: Deserializer<'de>,
187 {
188 let key = String::deserialize(deserializer)?;
189 WidgetId::from_key(&key)
190 .ok_or_else(|| de::Error::custom(format!("unknown status bar widget id: `{key}`")))
191 }
192}
193
194#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
196pub struct StatusBarWidgetConfig {
197 pub id: WidgetId,
199 #[serde(default = "default_true")]
201 pub enabled: bool,
202 #[serde(default)]
204 pub section: StatusBarSection,
205 #[serde(default)]
207 pub order: i32,
208 #[serde(default, skip_serializing_if = "Option::is_none")]
210 pub format: Option<String>,
211}
212
213fn default_true() -> bool {
214 true
215}
216
217pub fn default_widgets() -> Vec<StatusBarWidgetConfig> {
223 vec![
224 StatusBarWidgetConfig {
225 id: WidgetId::UsernameHostname,
226 enabled: true,
227 section: StatusBarSection::Left,
228 order: 0,
229 format: None,
230 },
231 StatusBarWidgetConfig {
232 id: WidgetId::CurrentDirectory,
233 enabled: true,
234 section: StatusBarSection::Left,
235 order: 1,
236 format: None,
237 },
238 StatusBarWidgetConfig {
239 id: WidgetId::GitBranch,
240 enabled: true,
241 section: StatusBarSection::Left,
242 order: 2,
243 format: None,
244 },
245 StatusBarWidgetConfig {
246 id: WidgetId::CurrentCommand,
247 enabled: true,
248 section: StatusBarSection::Center,
249 order: 0,
250 format: None,
251 },
252 StatusBarWidgetConfig {
253 id: WidgetId::CpuUsage,
254 enabled: false,
255 section: StatusBarSection::Right,
256 order: 0,
257 format: None,
258 },
259 StatusBarWidgetConfig {
260 id: WidgetId::MemoryUsage,
261 enabled: false,
262 section: StatusBarSection::Right,
263 order: 1,
264 format: None,
265 },
266 StatusBarWidgetConfig {
267 id: WidgetId::NetworkStatus,
268 enabled: false,
269 section: StatusBarSection::Right,
270 order: 2,
271 format: None,
272 },
273 StatusBarWidgetConfig {
274 id: WidgetId::DiskFree,
275 enabled: false,
276 section: StatusBarSection::Right,
277 order: 3,
278 format: None,
279 },
280 StatusBarWidgetConfig {
281 id: WidgetId::BellIndicator,
282 enabled: true,
283 section: StatusBarSection::Right,
284 order: 4,
285 format: None,
286 },
287 StatusBarWidgetConfig {
288 id: WidgetId::Clock,
289 enabled: true,
290 section: StatusBarSection::Right,
291 order: 5,
292 format: None,
293 },
294 StatusBarWidgetConfig {
295 id: WidgetId::UpdateAvailable,
296 enabled: true,
297 section: StatusBarSection::Right,
298 order: 6,
299 format: None,
300 },
301 StatusBarWidgetConfig {
302 id: WidgetId::AgentUsage,
303 enabled: false,
304 section: StatusBarSection::Right,
305 order: 7,
306 format: None,
307 },
308 StatusBarWidgetConfig {
309 id: WidgetId::AgentRoster,
310 enabled: false,
311 section: StatusBarSection::Right,
312 order: 8,
313 format: None,
314 },
315 ]
316}