vtcode_config/
root.rs

1use serde::{Deserialize, Serialize};
2
3use crate::status_line::StatusLineConfig;
4
5#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
6#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
7#[serde(rename_all = "snake_case")]
8#[derive(Default)]
9pub enum ToolOutputMode {
10    #[default]
11    Compact,
12    Full,
13}
14
15#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
16#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
17#[serde(rename_all = "snake_case")]
18#[derive(Default)]
19pub enum ReasoningDisplayMode {
20    Always,
21    #[default]
22    Toggle,
23    Hidden,
24}
25
26/// Layout mode override for responsive UI
27#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
28#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
29#[serde(rename_all = "snake_case")]
30pub enum LayoutModeOverride {
31    /// Auto-detect based on terminal size
32    #[default]
33    Auto,
34    /// Force compact mode (no borders)
35    Compact,
36    /// Force standard mode (borders, no sidebar/footer)
37    Standard,
38    /// Force wide mode (sidebar + footer)
39    Wide,
40}
41
42/// UI display mode variants for quick presets
43#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
44#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
45#[serde(rename_all = "snake_case")]
46pub enum UiDisplayMode {
47    /// Full UI with all features (sidebar, footer)
48    Full,
49    /// Minimal UI - no sidebar, no footer
50    #[default]
51    Minimal,
52    /// Focused mode - transcript only, maximum content space
53    Focused,
54}
55
56#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
57#[derive(Debug, Clone, Deserialize, Serialize)]
58pub struct UiConfig {
59    #[serde(default = "default_tool_output_mode")]
60    pub tool_output_mode: ToolOutputMode,
61    #[serde(default = "default_tool_output_max_lines")]
62    pub tool_output_max_lines: usize,
63    #[serde(default = "default_tool_output_spool_bytes")]
64    pub tool_output_spool_bytes: usize,
65    #[serde(default)]
66    pub tool_output_spool_dir: Option<String>,
67    #[serde(default = "default_allow_tool_ansi")]
68    pub allow_tool_ansi: bool,
69    #[serde(default = "default_inline_viewport_rows")]
70    pub inline_viewport_rows: u16,
71    #[serde(default = "default_reasoning_display_mode")]
72    pub reasoning_display_mode: ReasoningDisplayMode,
73    #[serde(default = "default_reasoning_visible_default")]
74    pub reasoning_visible_default: bool,
75    #[serde(default)]
76    pub status_line: StatusLineConfig,
77    #[serde(default)]
78    pub keyboard_protocol: KeyboardProtocolConfig,
79
80    /// Override the responsive layout mode
81    #[serde(default)]
82    pub layout_mode: LayoutModeOverride,
83
84    /// UI display mode preset (full, minimal, focused)
85    #[serde(default)]
86    pub display_mode: UiDisplayMode,
87
88    /// Show the right sidebar (queue, context, tools)
89    #[serde(default = "default_show_sidebar")]
90    pub show_sidebar: bool,
91
92    /// Dim completed todo items (- [x]) in agent output
93    #[serde(default = "default_dim_completed_todos")]
94    pub dim_completed_todos: bool,
95
96    /// Add spacing between message blocks
97    #[serde(default = "default_message_block_spacing")]
98    pub message_block_spacing: bool,
99}
100
101fn default_show_sidebar() -> bool {
102    true
103}
104
105fn default_dim_completed_todos() -> bool {
106    true
107}
108
109fn default_message_block_spacing() -> bool {
110    true
111}
112
113impl Default for UiConfig {
114    fn default() -> Self {
115        Self {
116            tool_output_mode: default_tool_output_mode(),
117            tool_output_max_lines: default_tool_output_max_lines(),
118            tool_output_spool_bytes: default_tool_output_spool_bytes(),
119            tool_output_spool_dir: None,
120            allow_tool_ansi: default_allow_tool_ansi(),
121            inline_viewport_rows: default_inline_viewport_rows(),
122            reasoning_display_mode: default_reasoning_display_mode(),
123            reasoning_visible_default: default_reasoning_visible_default(),
124            status_line: StatusLineConfig::default(),
125            keyboard_protocol: KeyboardProtocolConfig::default(),
126            layout_mode: LayoutModeOverride::default(),
127            display_mode: UiDisplayMode::default(),
128            show_sidebar: default_show_sidebar(),
129            dim_completed_todos: default_dim_completed_todos(),
130            message_block_spacing: default_message_block_spacing(),
131        }
132    }
133}
134
135/// PTY configuration
136#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
137#[derive(Debug, Clone, Deserialize, Serialize)]
138pub struct PtyConfig {
139    /// Enable PTY functionality
140    #[serde(default = "default_pty_enabled")]
141    pub enabled: bool,
142
143    /// Default terminal rows
144    #[serde(default = "default_pty_rows")]
145    pub default_rows: u16,
146
147    /// Default terminal columns
148    #[serde(default = "default_pty_cols")]
149    pub default_cols: u16,
150
151    /// Maximum number of concurrent PTY sessions
152    #[serde(default = "default_max_pty_sessions")]
153    pub max_sessions: usize,
154
155    /// Command timeout in seconds
156    #[serde(default = "default_pty_timeout")]
157    pub command_timeout_seconds: u64,
158
159    /// Number of PTY stdout lines to display in chat output
160    #[serde(default = "default_stdout_tail_lines")]
161    pub stdout_tail_lines: usize,
162
163    /// Maximum number of scrollback lines retained per PTY session
164    #[serde(default = "default_scrollback_lines")]
165    pub scrollback_lines: usize,
166
167    /// Maximum bytes of output to retain per PTY session (prevents memory explosion)
168    #[serde(default = "default_max_scrollback_bytes")]
169    pub max_scrollback_bytes: usize,
170
171    /// Threshold (KB) at which to auto-spool large outputs to disk
172    #[serde(default = "default_large_output_threshold_kb")]
173    pub large_output_threshold_kb: usize,
174
175    /// Preferred shell program for PTY sessions (falls back to environment when unset)
176    #[serde(default)]
177    pub preferred_shell: Option<String>,
178}
179
180impl Default for PtyConfig {
181    fn default() -> Self {
182        Self {
183            enabled: default_pty_enabled(),
184            default_rows: default_pty_rows(),
185            default_cols: default_pty_cols(),
186            max_sessions: default_max_pty_sessions(),
187            command_timeout_seconds: default_pty_timeout(),
188            stdout_tail_lines: default_stdout_tail_lines(),
189            scrollback_lines: default_scrollback_lines(),
190            max_scrollback_bytes: default_max_scrollback_bytes(),
191            large_output_threshold_kb: default_large_output_threshold_kb(),
192            preferred_shell: None,
193        }
194    }
195}
196
197fn default_pty_enabled() -> bool {
198    true
199}
200
201fn default_pty_rows() -> u16 {
202    24
203}
204
205fn default_pty_cols() -> u16 {
206    80
207}
208
209fn default_max_pty_sessions() -> usize {
210    10
211}
212
213fn default_pty_timeout() -> u64 {
214    300
215}
216
217fn default_stdout_tail_lines() -> usize {
218    crate::constants::defaults::DEFAULT_PTY_STDOUT_TAIL_LINES
219}
220
221fn default_scrollback_lines() -> usize {
222    crate::constants::defaults::DEFAULT_PTY_SCROLLBACK_LINES
223}
224
225fn default_max_scrollback_bytes() -> usize {
226    // Reduced from 50MB to 25MB for memory-constrained development environments
227    // Can be overridden in vtcode.toml with: pty.max_scrollback_bytes = 52428800
228    25_000_000 // 25MB max to prevent memory explosion
229}
230
231fn default_large_output_threshold_kb() -> usize {
232    5_000 // 5MB threshold for auto-spooling
233}
234
235fn default_tool_output_mode() -> ToolOutputMode {
236    ToolOutputMode::Compact
237}
238
239fn default_tool_output_max_lines() -> usize {
240    600
241}
242
243fn default_tool_output_spool_bytes() -> usize {
244    200_000
245}
246
247fn default_allow_tool_ansi() -> bool {
248    false
249}
250
251fn default_inline_viewport_rows() -> u16 {
252    crate::constants::ui::DEFAULT_INLINE_VIEWPORT_ROWS
253}
254
255fn default_reasoning_display_mode() -> ReasoningDisplayMode {
256    ReasoningDisplayMode::Toggle
257}
258
259fn default_reasoning_visible_default() -> bool {
260    crate::constants::ui::DEFAULT_REASONING_VISIBLE
261}
262
263/// Kitty keyboard protocol configuration
264/// Reference: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
265#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
266#[derive(Debug, Clone, Deserialize, Serialize)]
267pub struct KeyboardProtocolConfig {
268    /// Enable keyboard protocol enhancements (master toggle)
269    #[serde(default = "default_keyboard_protocol_enabled")]
270    pub enabled: bool,
271
272    /// Preset mode: "default", "full", "minimal", "custom"
273    #[serde(default = "default_keyboard_protocol_mode")]
274    pub mode: String,
275
276    /// Individual flag controls (used when mode = "custom")
277    /// Resolve Esc key ambiguity (recommended)
278    #[serde(default = "default_disambiguate_escape_codes")]
279    pub disambiguate_escape_codes: bool,
280
281    /// Report press/release/repeat events
282    #[serde(default = "default_report_event_types")]
283    pub report_event_types: bool,
284
285    /// Report alternate key layouts
286    #[serde(default = "default_report_alternate_keys")]
287    pub report_alternate_keys: bool,
288
289    /// Report modifier-only keys (Shift, Ctrl, Alt alone)
290    #[serde(default = "default_report_all_keys")]
291    pub report_all_keys: bool,
292}
293
294impl Default for KeyboardProtocolConfig {
295    fn default() -> Self {
296        Self {
297            enabled: default_keyboard_protocol_enabled(),
298            mode: default_keyboard_protocol_mode(),
299            disambiguate_escape_codes: default_disambiguate_escape_codes(),
300            report_event_types: default_report_event_types(),
301            report_alternate_keys: default_report_alternate_keys(),
302            report_all_keys: default_report_all_keys(),
303        }
304    }
305}
306
307impl KeyboardProtocolConfig {
308    pub fn validate(&self) -> anyhow::Result<()> {
309        match self.mode.as_str() {
310            "default" | "full" | "minimal" | "custom" => Ok(()),
311            _ => anyhow::bail!(
312                "Invalid keyboard protocol mode '{}'. Must be: default, full, minimal, or custom",
313                self.mode
314            ),
315        }
316    }
317}
318
319fn default_keyboard_protocol_enabled() -> bool {
320    std::env::var("VTCODE_KEYBOARD_PROTOCOL_ENABLED")
321        .ok()
322        .and_then(|v| v.parse().ok())
323        .unwrap_or(true)
324}
325
326fn default_keyboard_protocol_mode() -> String {
327    std::env::var("VTCODE_KEYBOARD_PROTOCOL_MODE").unwrap_or_else(|_| "default".to_string())
328}
329
330fn default_disambiguate_escape_codes() -> bool {
331    true
332}
333
334fn default_report_event_types() -> bool {
335    true
336}
337
338fn default_report_alternate_keys() -> bool {
339    true
340}
341
342fn default_report_all_keys() -> bool {
343    false
344}