vtcode_config/
terminal_title.rs1use serde::{Deserialize, Serialize};
2
3pub const DEFAULT_TERMINAL_TITLE_ITEMS: &[&str] = &["spinner", "project"];
4
5#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
6#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq)]
7pub struct TerminalTitleConfig {
8 #[serde(default)]
12 pub items: Option<Vec<String>>,
13
14 #[serde(default = "default_global_indicator")]
17 pub global_indicator: bool,
18}
19
20fn default_global_indicator() -> bool {
21 true
22}
23
24pub fn effective_title_items(items: Option<&[String]>, global_indicator: bool) -> Vec<String> {
27 let resolved = items
28 .map(<[String]>::to_vec)
29 .unwrap_or_else(|| DEFAULT_TERMINAL_TITLE_ITEMS.iter().map(|item| (*item).to_string()).collect());
30 if global_indicator {
31 resolved
32 } else {
33 resolved.into_iter().filter(|item| item != "spinner").collect()
34 }
35}
36
37impl TerminalTitleConfig {
38 #[must_use]
39 fn effective_items(&self) -> Vec<String> {
40 self.items
41 .clone()
42 .unwrap_or_else(|| DEFAULT_TERMINAL_TITLE_ITEMS.iter().map(|item| (*item).to_string()).collect())
43 }
44
45 #[must_use]
46 fn manages_title(&self) -> bool {
47 self.items.as_ref().is_none_or(|items| !items.is_empty())
48 }
49}
50
51#[cfg(test)]
52mod tests {
53 use super::{DEFAULT_TERMINAL_TITLE_ITEMS, TerminalTitleConfig, effective_title_items};
54
55 #[test]
56 fn shipped_default_items_lead_with_the_spinner() {
57 assert_eq!(DEFAULT_TERMINAL_TITLE_ITEMS, ["spinner", "project"]);
60 }
61
62 #[test]
63 fn serde_defaults_when_terminal_title_table_is_missing() {
64 let config: TerminalTitleConfig = toml::from_str("").expect("config should parse");
65
66 assert_eq!(config.items, None);
67 }
68
69 #[test]
70 fn serde_preserves_explicit_empty_items() {
71 let config: TerminalTitleConfig = toml::from_str("items = []").expect("config should parse");
72
73 assert_eq!(config.items, Some(Vec::new()));
74 }
75
76 #[test]
77 fn serde_preserves_valid_item_list() {
78 let config: TerminalTitleConfig =
79 toml::from_str("items = [\"spinner\", \"project\", \"model\"]").expect("config should parse");
80
81 assert_eq!(config.items, Some(vec!["spinner".to_string(), "project".to_string(), "model".to_string()]));
82 }
83
84 #[test]
85 fn disabled_global_indicator_drops_only_the_spinner() {
86 assert_eq!(
87 effective_title_items(None, true),
88 DEFAULT_TERMINAL_TITLE_ITEMS
89 .iter()
90 .map(|item| (*item).to_string())
91 .collect::<Vec<_>>()
92 );
93 assert_eq!(effective_title_items(None, false), vec!["project".to_string()]);
95 let configured = Some(vec!["status".to_string(), "spinner".to_string(), "thread".to_string()]);
96 assert_eq!(
97 effective_title_items(configured.as_deref(), false),
98 vec!["status".to_string(), "thread".to_string()]
99 );
100 let config: TerminalTitleConfig = toml::from_str("").expect("config should parse");
102 assert!(config.global_indicator);
103 }
104
105 #[test]
106 fn missing_items_uses_default_terminal_title_items() {
107 let config = TerminalTitleConfig::default();
108
109 assert_eq!(
110 config.effective_items(),
111 DEFAULT_TERMINAL_TITLE_ITEMS
112 .iter()
113 .map(|item| (*item).to_string())
114 .collect::<Vec<_>>()
115 );
116 assert!(config.manages_title());
117 }
118
119 #[test]
120 fn explicit_empty_items_disables_terminal_title_management() {
121 let config = TerminalTitleConfig { items: Some(Vec::new()), global_indicator: true };
122
123 assert!(config.effective_items().is_empty());
124 assert!(!config.manages_title());
125 }
126
127 #[test]
128 fn explicit_items_are_preserved() {
129 let config = TerminalTitleConfig {
130 items: Some(vec!["project".to_string(), "model".to_string()]),
131 global_indicator: true,
132 };
133
134 assert_eq!(config.effective_items(), vec!["project".to_string(), "model".to_string()]);
135 assert!(config.manages_title());
136 }
137}