Skip to main content

vtcode_config/
status_line.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
4#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
5#[cfg_attr(feature = "schema", schemars(rename_all = "lowercase"))]
6#[serde(rename_all = "lowercase")]
7#[derive(Default)]
8pub enum StatusLineMode {
9    /// Automatic status line: displays git branch, model summary, current time, and context indicators (default)
10    #[default]
11    Auto,
12    /// Command mode: executes custom shell command to render status line
13    Command,
14    /// Hidden mode: disables the status line entirely
15    Hidden,
16    /// Catch-all for unknown modes added by future versions.
17    #[serde(other)]
18    Unknown,
19}
20
21impl std::str::FromStr for StatusLineMode {
22    type Err = String;
23
24    fn from_str(s: &str) -> Result<Self, Self::Err> {
25        match s.trim().to_lowercase().as_str() {
26            "auto" => Ok(StatusLineMode::Auto),
27            "command" => Ok(StatusLineMode::Command),
28            "hidden" => Ok(StatusLineMode::Hidden),
29            _ => Ok(StatusLineMode::Unknown),
30        }
31    }
32}
33
34#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
35#[derive(Debug, Clone, Deserialize, Serialize)]
36pub struct StatusLineConfig {
37    #[serde(default = "default_status_line_mode")]
38    pub mode: StatusLineMode,
39    #[serde(default)]
40    pub command: Option<String>,
41    #[serde(default = "default_status_line_refresh_interval_ms")]
42    pub refresh_interval_ms: u64,
43    #[serde(default = "default_status_line_command_timeout_ms")]
44    pub command_timeout_ms: u64,
45    /// Show the current system time (`HH:MM:SS`) on the status line (default: true).
46    #[serde(default = "default_status_line_show_clock")]
47    pub show_clock: bool,
48}
49
50impl StatusLineConfig {
51    /// Get the effective status line mode, using defaults for unset values.
52    ///
53    /// Following Codex PR #12015's pattern for default configuration fallback,
54    /// this method ensures that when a status line config is not explicitly set,
55    /// sensible defaults are applied (automatic status line by default).
56    pub fn effective_mode(&self) -> StatusLineMode {
57        self.mode.clone()
58    }
59}
60
61impl Default for StatusLineConfig {
62    fn default() -> Self {
63        Self {
64            mode: default_status_line_mode(),
65            command: None,
66            refresh_interval_ms: default_status_line_refresh_interval_ms(),
67            command_timeout_ms: default_status_line_command_timeout_ms(),
68            show_clock: default_status_line_show_clock(),
69        }
70    }
71}
72
73/// Returns the default status line clock visibility when not explicitly configured.
74const DEFAULT_STATUS_LINE_SHOW_CLOCK: bool = true;
75
76fn default_status_line_show_clock() -> bool {
77    DEFAULT_STATUS_LINE_SHOW_CLOCK
78}
79
80/// Returns the default status line mode when not explicitly configured.
81///
82/// This is the fallback when `tui.status_line` config is unset (pattern from Codex PR #12015).
83const DEFAULT_STATUS_LINE_MODE: StatusLineMode = StatusLineMode::Auto;
84
85fn default_status_line_mode() -> StatusLineMode {
86    DEFAULT_STATUS_LINE_MODE
87}
88
89fn default_status_line_refresh_interval_ms() -> u64 {
90    crate::constants::ui::STATUS_LINE_REFRESH_INTERVAL_MS
91}
92
93fn default_status_line_command_timeout_ms() -> u64 {
94    crate::constants::ui::STATUS_LINE_COMMAND_TIMEOUT_MS
95}
96
97#[cfg(test)]
98mod tests {
99    use super::{StatusLineConfig, StatusLineMode};
100
101    #[derive(Debug, serde::Deserialize)]
102    struct Wrapper {
103        mode: StatusLineMode,
104    }
105
106    #[test]
107    fn deserializes_lowercase_status_line_mode() {
108        for (input, expected) in [
109            ("\"auto\"", StatusLineMode::Auto),
110            ("\"command\"", StatusLineMode::Command),
111            ("\"hidden\"", StatusLineMode::Hidden),
112        ] {
113            let parsed: Wrapper =
114                toml::from_str(&format!("mode = {input}")).expect("lowercase status line mode must parse");
115            assert_eq!(parsed.mode, expected, "input {input}");
116        }
117    }
118
119    #[test]
120    fn serializes_lowercase_status_line_mode() {
121        for (mode, expected) in [
122            (StatusLineMode::Auto, "\"auto\""),
123            (StatusLineMode::Command, "\"command\""),
124            (StatusLineMode::Hidden, "\"hidden\""),
125        ] {
126            let serialized = serde_json::to_string(&mode).expect("serialize");
127            assert_eq!(serialized, expected);
128        }
129    }
130
131    #[test]
132    fn show_clock_defaults_to_true() {
133        let config = StatusLineConfig::default();
134        assert!(config.show_clock);
135
136        let parsed: StatusLineConfig = toml::from_str("").expect("empty config must parse with defaults");
137        assert!(parsed.show_clock);
138    }
139
140    #[test]
141    fn show_clock_deserializes_when_explicit() {
142        let parsed: StatusLineConfig = toml::from_str("show_clock = false").expect("show_clock = false must parse");
143        assert!(!parsed.show_clock);
144
145        let parsed: StatusLineConfig = toml::from_str("show_clock = true").expect("show_clock = true must parse");
146        assert!(parsed.show_clock);
147    }
148}