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: false).
46    ///
47    /// Per-message timestamps (`ui.show_message_timestamps`) replaced the
48    /// ticking clock; this remains as an opt-in for the old behavior.
49    #[serde(default = "default_status_line_show_clock")]
50    pub show_clock: bool,
51}
52
53impl StatusLineConfig {
54    /// Get the effective status line mode, using defaults for unset values.
55    ///
56    /// Following Codex PR #12015's pattern for default configuration fallback,
57    /// this method ensures that when a status line config is not explicitly set,
58    /// sensible defaults are applied (automatic status line by default).
59    pub fn effective_mode(&self) -> StatusLineMode {
60        self.mode.clone()
61    }
62}
63
64impl Default for StatusLineConfig {
65    fn default() -> Self {
66        Self {
67            mode: default_status_line_mode(),
68            command: None,
69            refresh_interval_ms: default_status_line_refresh_interval_ms(),
70            command_timeout_ms: default_status_line_command_timeout_ms(),
71            show_clock: default_status_line_show_clock(),
72        }
73    }
74}
75
76/// Returns the default status line clock visibility when not explicitly configured.
77const DEFAULT_STATUS_LINE_SHOW_CLOCK: bool = false;
78
79fn default_status_line_show_clock() -> bool {
80    DEFAULT_STATUS_LINE_SHOW_CLOCK
81}
82
83/// Returns the default status line mode when not explicitly configured.
84///
85/// This is the fallback when `tui.status_line` config is unset (pattern from Codex PR #12015).
86const DEFAULT_STATUS_LINE_MODE: StatusLineMode = StatusLineMode::Auto;
87
88fn default_status_line_mode() -> StatusLineMode {
89    DEFAULT_STATUS_LINE_MODE
90}
91
92fn default_status_line_refresh_interval_ms() -> u64 {
93    crate::constants::ui::STATUS_LINE_REFRESH_INTERVAL_MS
94}
95
96fn default_status_line_command_timeout_ms() -> u64 {
97    crate::constants::ui::STATUS_LINE_COMMAND_TIMEOUT_MS
98}
99
100#[cfg(test)]
101mod tests {
102    use super::{StatusLineConfig, StatusLineMode};
103
104    #[derive(Debug, serde::Deserialize)]
105    struct Wrapper {
106        mode: StatusLineMode,
107    }
108
109    #[test]
110    fn deserializes_lowercase_status_line_mode() {
111        for (input, expected) in [
112            ("\"auto\"", StatusLineMode::Auto),
113            ("\"command\"", StatusLineMode::Command),
114            ("\"hidden\"", StatusLineMode::Hidden),
115        ] {
116            let parsed: Wrapper =
117                toml::from_str(&format!("mode = {input}")).expect("lowercase status line mode must parse");
118            assert_eq!(parsed.mode, expected, "input {input}");
119        }
120    }
121
122    #[test]
123    fn serializes_lowercase_status_line_mode() {
124        for (mode, expected) in [
125            (StatusLineMode::Auto, "\"auto\""),
126            (StatusLineMode::Command, "\"command\""),
127            (StatusLineMode::Hidden, "\"hidden\""),
128        ] {
129            let serialized = serde_json::to_string(&mode).expect("serialize");
130            assert_eq!(serialized, expected);
131        }
132    }
133
134    #[test]
135    fn show_clock_defaults_to_false() {
136        let config = StatusLineConfig::default();
137        assert!(!config.show_clock);
138
139        let parsed: StatusLineConfig = toml::from_str("").expect("empty config must parse with defaults");
140        assert!(!parsed.show_clock);
141    }
142
143    #[test]
144    fn show_clock_deserializes_when_explicit() {
145        let parsed: StatusLineConfig = toml::from_str("show_clock = false").expect("show_clock = false must parse");
146        assert!(!parsed.show_clock);
147
148        let parsed: StatusLineConfig = toml::from_str("show_clock = true").expect("show_clock = true must parse");
149        assert!(parsed.show_clock);
150    }
151}