Skip to main content

par_term_config/
status_bar.rs

1//! Status bar widget configuration types.
2//!
3//! Defines the widget identifiers, section layout, and per-widget configuration
4//! used by the status bar system.
5
6use serde::de::{self, Deserializer};
7use serde::ser::Serializer;
8use serde::{Deserialize, Serialize};
9
10/// Section of the status bar where a widget is placed.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)]
12#[serde(rename_all = "lowercase")]
13pub enum StatusBarSection {
14    /// Left-aligned section (default)
15    #[default]
16    Left,
17    /// Center-aligned section
18    Center,
19    /// Right-aligned section
20    Right,
21}
22
23/// Identifier for a built-in or custom status bar widget.
24///
25/// Serialized as a single plain string (`as_key`/`from_key`) so it round-trips
26/// through `config.yaml`, which embeds the status bar via `#[serde(flatten)]`.
27/// Built-in widgets use their snake_case name (e.g. `git_branch`); custom
28/// widgets use `custom:<name>`. serde's flatten path cannot deserialize the
29/// externally-tagged `Custom(String)` map form (`"untagged and internally tagged
30/// enums do not support enum input"`), so a manual scalar representation is
31/// used instead of the derived one.
32#[derive(Debug, Clone, PartialEq, Eq, Hash)]
33pub enum WidgetId {
34    /// Current time (HH:MM:SS)
35    Clock,
36    /// user@hostname
37    UsernameHostname,
38    /// Current working directory
39    CurrentDirectory,
40    /// Git branch name with icon
41    GitBranch,
42    /// CPU usage percentage
43    CpuUsage,
44    /// Memory usage (used / total)
45    MemoryUsage,
46    /// Network throughput (rx/tx rates)
47    NetworkStatus,
48    /// Free disk space (percent + bytes)
49    DiskFree,
50    /// Bell indicator with count
51    BellIndicator,
52    /// Currently running command name
53    CurrentCommand,
54    /// Update available notification
55    UpdateAvailable,
56    /// Custom widget (user-defined via format string)
57    Custom(String),
58}
59
60impl WidgetId {
61    /// Human-readable label for UI display.
62    pub fn label(&self) -> &str {
63        match self {
64            WidgetId::Clock => "Clock",
65            WidgetId::UsernameHostname => "User@Host",
66            WidgetId::CurrentDirectory => "Directory",
67            WidgetId::GitBranch => "Git Branch",
68            WidgetId::CpuUsage => "CPU Usage",
69            WidgetId::MemoryUsage => "Memory Usage",
70            WidgetId::NetworkStatus => "Network Status",
71            WidgetId::DiskFree => "Disk Free",
72            WidgetId::BellIndicator => "Bell Indicator",
73            WidgetId::CurrentCommand => "Current Command",
74            WidgetId::UpdateAvailable => "Update Available",
75            WidgetId::Custom(name) => name.as_str(),
76        }
77    }
78
79    /// Icon/prefix character for the widget.
80    pub fn icon(&self) -> &str {
81        match self {
82            WidgetId::Clock => "\u{1f551}",            // clock emoji
83            WidgetId::UsernameHostname => "\u{1f464}", // bust in silhouette
84            WidgetId::CurrentDirectory => "\u{1f4c2}", // open file folder
85            WidgetId::GitBranch => "\u{1f500}",        // twisted rightwards arrows (branch)
86            WidgetId::CpuUsage => "\u{1f4bb}",         // laptop
87            WidgetId::MemoryUsage => "\u{1f4be}",      // floppy disk
88            WidgetId::NetworkStatus => "\u{1f310}",    // globe with meridians
89            WidgetId::DiskFree => "\u{1f4bf}",         // optical disk
90            WidgetId::BellIndicator => "\u{1f514}",    // bell
91            WidgetId::CurrentCommand => "\u{25b6}",    // play button
92            WidgetId::UpdateAvailable => "\u{2b06}",   // upwards arrow
93            WidgetId::Custom(_) => "\u{2699}",         // gear
94        }
95    }
96
97    /// Whether this widget requires the system monitor to be running.
98    pub fn needs_system_monitor(&self) -> bool {
99        matches!(
100            self,
101            WidgetId::CpuUsage | WidgetId::MemoryUsage | WidgetId::NetworkStatus
102        )
103    }
104
105    /// Whether this widget requires the disk monitor to be running.
106    pub fn needs_disk_monitor(&self) -> bool {
107        matches!(self, WidgetId::DiskFree)
108    }
109
110    /// Stable string key used for YAML serialization. Built-in widgets use their
111    /// snake_case name; custom widgets are prefixed with `custom:`.
112    fn as_key(&self) -> String {
113        match self {
114            WidgetId::Clock => "clock".to_string(),
115            WidgetId::UsernameHostname => "username_hostname".to_string(),
116            WidgetId::CurrentDirectory => "current_directory".to_string(),
117            WidgetId::GitBranch => "git_branch".to_string(),
118            WidgetId::CpuUsage => "cpu_usage".to_string(),
119            WidgetId::MemoryUsage => "memory_usage".to_string(),
120            WidgetId::NetworkStatus => "network_status".to_string(),
121            WidgetId::DiskFree => "disk_free".to_string(),
122            WidgetId::BellIndicator => "bell_indicator".to_string(),
123            WidgetId::CurrentCommand => "current_command".to_string(),
124            WidgetId::UpdateAvailable => "update_available".to_string(),
125            WidgetId::Custom(name) => format!("custom:{name}"),
126        }
127    }
128
129    /// Parse a serialization key back into a [`WidgetId`]. Returns `None` for
130    /// unrecognized built-in names.
131    fn from_key(key: &str) -> Option<WidgetId> {
132        if let Some(name) = key.strip_prefix("custom:") {
133            return Some(WidgetId::Custom(name.to_string()));
134        }
135        Some(match key {
136            "clock" => WidgetId::Clock,
137            "username_hostname" => WidgetId::UsernameHostname,
138            "current_directory" => WidgetId::CurrentDirectory,
139            "git_branch" => WidgetId::GitBranch,
140            "cpu_usage" => WidgetId::CpuUsage,
141            "memory_usage" => WidgetId::MemoryUsage,
142            "network_status" => WidgetId::NetworkStatus,
143            "disk_free" => WidgetId::DiskFree,
144            "bell_indicator" => WidgetId::BellIndicator,
145            "current_command" => WidgetId::CurrentCommand,
146            "update_available" => WidgetId::UpdateAvailable,
147            _ => return None,
148        })
149    }
150}
151
152impl Serialize for WidgetId {
153    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
154    where
155        S: Serializer,
156    {
157        serializer.serialize_str(&self.as_key())
158    }
159}
160
161impl<'de> Deserialize<'de> for WidgetId {
162    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
163    where
164        D: Deserializer<'de>,
165    {
166        let key = String::deserialize(deserializer)?;
167        WidgetId::from_key(&key)
168            .ok_or_else(|| de::Error::custom(format!("unknown status bar widget id: `{key}`")))
169    }
170}
171
172/// Configuration for a single status bar widget.
173#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
174pub struct StatusBarWidgetConfig {
175    /// Which widget to display
176    pub id: WidgetId,
177    /// Whether this widget is enabled
178    #[serde(default = "default_true")]
179    pub enabled: bool,
180    /// Section placement (left, center, right)
181    #[serde(default)]
182    pub section: StatusBarSection,
183    /// Sort order within the section (lower values first)
184    #[serde(default)]
185    pub order: i32,
186    /// Optional format override string with `\(variable)` interpolation
187    #[serde(default, skip_serializing_if = "Option::is_none")]
188    pub format: Option<String>,
189}
190
191fn default_true() -> bool {
192    true
193}
194
195/// Default widget configuration set.
196///
197/// Returns a sensible starting set of widgets covering common use-cases.
198/// System monitor widgets (CPU, memory, network) are disabled by default
199/// to avoid unnecessary resource usage.
200pub fn default_widgets() -> Vec<StatusBarWidgetConfig> {
201    vec![
202        StatusBarWidgetConfig {
203            id: WidgetId::UsernameHostname,
204            enabled: true,
205            section: StatusBarSection::Left,
206            order: 0,
207            format: None,
208        },
209        StatusBarWidgetConfig {
210            id: WidgetId::CurrentDirectory,
211            enabled: true,
212            section: StatusBarSection::Left,
213            order: 1,
214            format: None,
215        },
216        StatusBarWidgetConfig {
217            id: WidgetId::GitBranch,
218            enabled: true,
219            section: StatusBarSection::Left,
220            order: 2,
221            format: None,
222        },
223        StatusBarWidgetConfig {
224            id: WidgetId::CurrentCommand,
225            enabled: true,
226            section: StatusBarSection::Center,
227            order: 0,
228            format: None,
229        },
230        StatusBarWidgetConfig {
231            id: WidgetId::CpuUsage,
232            enabled: false,
233            section: StatusBarSection::Right,
234            order: 0,
235            format: None,
236        },
237        StatusBarWidgetConfig {
238            id: WidgetId::MemoryUsage,
239            enabled: false,
240            section: StatusBarSection::Right,
241            order: 1,
242            format: None,
243        },
244        StatusBarWidgetConfig {
245            id: WidgetId::NetworkStatus,
246            enabled: false,
247            section: StatusBarSection::Right,
248            order: 2,
249            format: None,
250        },
251        StatusBarWidgetConfig {
252            id: WidgetId::DiskFree,
253            enabled: false,
254            section: StatusBarSection::Right,
255            order: 3,
256            format: None,
257        },
258        StatusBarWidgetConfig {
259            id: WidgetId::BellIndicator,
260            enabled: true,
261            section: StatusBarSection::Right,
262            order: 4,
263            format: None,
264        },
265        StatusBarWidgetConfig {
266            id: WidgetId::Clock,
267            enabled: true,
268            section: StatusBarSection::Right,
269            order: 5,
270            format: None,
271        },
272        StatusBarWidgetConfig {
273            id: WidgetId::UpdateAvailable,
274            enabled: true,
275            section: StatusBarSection::Right,
276            order: 6,
277            format: None,
278        },
279    ]
280}