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    /// Bell indicator with count
49    BellIndicator,
50    /// Currently running command name
51    CurrentCommand,
52    /// Update available notification
53    UpdateAvailable,
54    /// Custom widget (user-defined via format string)
55    Custom(String),
56}
57
58impl WidgetId {
59    /// Human-readable label for UI display.
60    pub fn label(&self) -> &str {
61        match self {
62            WidgetId::Clock => "Clock",
63            WidgetId::UsernameHostname => "User@Host",
64            WidgetId::CurrentDirectory => "Directory",
65            WidgetId::GitBranch => "Git Branch",
66            WidgetId::CpuUsage => "CPU Usage",
67            WidgetId::MemoryUsage => "Memory Usage",
68            WidgetId::NetworkStatus => "Network Status",
69            WidgetId::BellIndicator => "Bell Indicator",
70            WidgetId::CurrentCommand => "Current Command",
71            WidgetId::UpdateAvailable => "Update Available",
72            WidgetId::Custom(name) => name.as_str(),
73        }
74    }
75
76    /// Icon/prefix character for the widget.
77    pub fn icon(&self) -> &str {
78        match self {
79            WidgetId::Clock => "\u{1f551}",            // clock emoji
80            WidgetId::UsernameHostname => "\u{1f464}", // bust in silhouette
81            WidgetId::CurrentDirectory => "\u{1f4c2}", // open file folder
82            WidgetId::GitBranch => "\u{1f500}",        // twisted rightwards arrows (branch)
83            WidgetId::CpuUsage => "\u{1f4bb}",         // laptop
84            WidgetId::MemoryUsage => "\u{1f4be}",      // floppy disk
85            WidgetId::NetworkStatus => "\u{1f310}",    // globe with meridians
86            WidgetId::BellIndicator => "\u{1f514}",    // bell
87            WidgetId::CurrentCommand => "\u{25b6}",    // play button
88            WidgetId::UpdateAvailable => "\u{2b06}",   // upwards arrow
89            WidgetId::Custom(_) => "\u{2699}",         // gear
90        }
91    }
92
93    /// Whether this widget requires the system monitor to be running.
94    pub fn needs_system_monitor(&self) -> bool {
95        matches!(
96            self,
97            WidgetId::CpuUsage | WidgetId::MemoryUsage | WidgetId::NetworkStatus
98        )
99    }
100
101    /// Stable string key used for YAML serialization. Built-in widgets use their
102    /// snake_case name; custom widgets are prefixed with `custom:`.
103    fn as_key(&self) -> String {
104        match self {
105            WidgetId::Clock => "clock".to_string(),
106            WidgetId::UsernameHostname => "username_hostname".to_string(),
107            WidgetId::CurrentDirectory => "current_directory".to_string(),
108            WidgetId::GitBranch => "git_branch".to_string(),
109            WidgetId::CpuUsage => "cpu_usage".to_string(),
110            WidgetId::MemoryUsage => "memory_usage".to_string(),
111            WidgetId::NetworkStatus => "network_status".to_string(),
112            WidgetId::BellIndicator => "bell_indicator".to_string(),
113            WidgetId::CurrentCommand => "current_command".to_string(),
114            WidgetId::UpdateAvailable => "update_available".to_string(),
115            WidgetId::Custom(name) => format!("custom:{name}"),
116        }
117    }
118
119    /// Parse a serialization key back into a [`WidgetId`]. Returns `None` for
120    /// unrecognized built-in names.
121    fn from_key(key: &str) -> Option<WidgetId> {
122        if let Some(name) = key.strip_prefix("custom:") {
123            return Some(WidgetId::Custom(name.to_string()));
124        }
125        Some(match key {
126            "clock" => WidgetId::Clock,
127            "username_hostname" => WidgetId::UsernameHostname,
128            "current_directory" => WidgetId::CurrentDirectory,
129            "git_branch" => WidgetId::GitBranch,
130            "cpu_usage" => WidgetId::CpuUsage,
131            "memory_usage" => WidgetId::MemoryUsage,
132            "network_status" => WidgetId::NetworkStatus,
133            "bell_indicator" => WidgetId::BellIndicator,
134            "current_command" => WidgetId::CurrentCommand,
135            "update_available" => WidgetId::UpdateAvailable,
136            _ => return None,
137        })
138    }
139}
140
141impl Serialize for WidgetId {
142    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
143    where
144        S: Serializer,
145    {
146        serializer.serialize_str(&self.as_key())
147    }
148}
149
150impl<'de> Deserialize<'de> for WidgetId {
151    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
152    where
153        D: Deserializer<'de>,
154    {
155        let key = String::deserialize(deserializer)?;
156        WidgetId::from_key(&key)
157            .ok_or_else(|| de::Error::custom(format!("unknown status bar widget id: `{key}`")))
158    }
159}
160
161/// Configuration for a single status bar widget.
162#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
163pub struct StatusBarWidgetConfig {
164    /// Which widget to display
165    pub id: WidgetId,
166    /// Whether this widget is enabled
167    #[serde(default = "default_true")]
168    pub enabled: bool,
169    /// Section placement (left, center, right)
170    #[serde(default)]
171    pub section: StatusBarSection,
172    /// Sort order within the section (lower values first)
173    #[serde(default)]
174    pub order: i32,
175    /// Optional format override string with `\(variable)` interpolation
176    #[serde(default, skip_serializing_if = "Option::is_none")]
177    pub format: Option<String>,
178}
179
180fn default_true() -> bool {
181    true
182}
183
184/// Default widget configuration set.
185///
186/// Returns a sensible starting set of widgets covering common use-cases.
187/// System monitor widgets (CPU, memory, network) are disabled by default
188/// to avoid unnecessary resource usage.
189pub fn default_widgets() -> Vec<StatusBarWidgetConfig> {
190    vec![
191        StatusBarWidgetConfig {
192            id: WidgetId::UsernameHostname,
193            enabled: true,
194            section: StatusBarSection::Left,
195            order: 0,
196            format: None,
197        },
198        StatusBarWidgetConfig {
199            id: WidgetId::CurrentDirectory,
200            enabled: true,
201            section: StatusBarSection::Left,
202            order: 1,
203            format: None,
204        },
205        StatusBarWidgetConfig {
206            id: WidgetId::GitBranch,
207            enabled: true,
208            section: StatusBarSection::Left,
209            order: 2,
210            format: None,
211        },
212        StatusBarWidgetConfig {
213            id: WidgetId::CurrentCommand,
214            enabled: true,
215            section: StatusBarSection::Center,
216            order: 0,
217            format: None,
218        },
219        StatusBarWidgetConfig {
220            id: WidgetId::CpuUsage,
221            enabled: false,
222            section: StatusBarSection::Right,
223            order: 0,
224            format: None,
225        },
226        StatusBarWidgetConfig {
227            id: WidgetId::MemoryUsage,
228            enabled: false,
229            section: StatusBarSection::Right,
230            order: 1,
231            format: None,
232        },
233        StatusBarWidgetConfig {
234            id: WidgetId::NetworkStatus,
235            enabled: false,
236            section: StatusBarSection::Right,
237            order: 2,
238            format: None,
239        },
240        StatusBarWidgetConfig {
241            id: WidgetId::BellIndicator,
242            enabled: true,
243            section: StatusBarSection::Right,
244            order: 3,
245            format: None,
246        },
247        StatusBarWidgetConfig {
248            id: WidgetId::Clock,
249            enabled: true,
250            section: StatusBarSection::Right,
251            order: 4,
252            format: None,
253        },
254        StatusBarWidgetConfig {
255            id: WidgetId::UpdateAvailable,
256            enabled: true,
257            section: StatusBarSection::Right,
258            order: 5,
259            format: None,
260        },
261    ]
262}