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, dividers)
48    Full,
49    /// Minimal UI - no sidebar, no footer, no dividers
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    /// Show message dividers between conversation turns
93    #[serde(default = "default_show_message_dividers")]
94    pub show_message_dividers: bool,
95
96    /// Dim completed todo items (- [x]) in agent output
97    #[serde(default = "default_dim_completed_todos")]
98    pub dim_completed_todos: bool,
99
100    /// Add spacing between message blocks
101    #[serde(default = "default_message_block_spacing")]
102    pub message_block_spacing: bool,
103}
104
105fn default_show_sidebar() -> bool {
106    true
107}
108
109fn default_show_message_dividers() -> bool {
110    false // Clean UI by default
111}
112
113fn default_dim_completed_todos() -> bool {
114    true
115}
116
117fn default_message_block_spacing() -> bool {
118    true
119}
120
121impl Default for UiConfig {
122    fn default() -> Self {
123        Self {
124            tool_output_mode: default_tool_output_mode(),
125            tool_output_max_lines: default_tool_output_max_lines(),
126            tool_output_spool_bytes: default_tool_output_spool_bytes(),
127            tool_output_spool_dir: None,
128            allow_tool_ansi: default_allow_tool_ansi(),
129            inline_viewport_rows: default_inline_viewport_rows(),
130            reasoning_display_mode: default_reasoning_display_mode(),
131            reasoning_visible_default: default_reasoning_visible_default(),
132            status_line: StatusLineConfig::default(),
133            keyboard_protocol: KeyboardProtocolConfig::default(),
134            layout_mode: LayoutModeOverride::default(),
135            display_mode: UiDisplayMode::default(),
136            show_sidebar: default_show_sidebar(),
137            show_message_dividers: default_show_message_dividers(),
138            dim_completed_todos: default_dim_completed_todos(),
139            message_block_spacing: default_message_block_spacing(),
140        }
141    }
142}
143
144/// PTY configuration
145#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
146#[derive(Debug, Clone, Deserialize, Serialize)]
147pub struct PtyConfig {
148    /// Enable PTY functionality
149    #[serde(default = "default_pty_enabled")]
150    pub enabled: bool,
151
152    /// Default terminal rows
153    #[serde(default = "default_pty_rows")]
154    pub default_rows: u16,
155
156    /// Default terminal columns
157    #[serde(default = "default_pty_cols")]
158    pub default_cols: u16,
159
160    /// Maximum number of concurrent PTY sessions
161    #[serde(default = "default_max_pty_sessions")]
162    pub max_sessions: usize,
163
164    /// Command timeout in seconds
165    #[serde(default = "default_pty_timeout")]
166    pub command_timeout_seconds: u64,
167
168    /// Number of PTY stdout lines to display in chat output
169    #[serde(default = "default_stdout_tail_lines")]
170    pub stdout_tail_lines: usize,
171
172    /// Maximum number of scrollback lines retained per PTY session
173    #[serde(default = "default_scrollback_lines")]
174    pub scrollback_lines: usize,
175
176    /// Maximum bytes of output to retain per PTY session (prevents memory explosion)
177    #[serde(default = "default_max_scrollback_bytes")]
178    pub max_scrollback_bytes: usize,
179
180    /// Threshold (KB) at which to auto-spool large outputs to disk
181    #[serde(default = "default_large_output_threshold_kb")]
182    pub large_output_threshold_kb: usize,
183
184    /// Preferred shell program for PTY sessions (falls back to environment when unset)
185    #[serde(default)]
186    pub preferred_shell: Option<String>,
187}
188
189impl Default for PtyConfig {
190    fn default() -> Self {
191        Self {
192            enabled: default_pty_enabled(),
193            default_rows: default_pty_rows(),
194            default_cols: default_pty_cols(),
195            max_sessions: default_max_pty_sessions(),
196            command_timeout_seconds: default_pty_timeout(),
197            stdout_tail_lines: default_stdout_tail_lines(),
198            scrollback_lines: default_scrollback_lines(),
199            max_scrollback_bytes: default_max_scrollback_bytes(),
200            large_output_threshold_kb: default_large_output_threshold_kb(),
201            preferred_shell: None,
202        }
203    }
204}
205
206fn default_pty_enabled() -> bool {
207    true
208}
209
210fn default_pty_rows() -> u16 {
211    24
212}
213
214fn default_pty_cols() -> u16 {
215    80
216}
217
218fn default_max_pty_sessions() -> usize {
219    10
220}
221
222fn default_pty_timeout() -> u64 {
223    300
224}
225
226fn default_stdout_tail_lines() -> usize {
227    crate::constants::defaults::DEFAULT_PTY_STDOUT_TAIL_LINES
228}
229
230fn default_scrollback_lines() -> usize {
231    crate::constants::defaults::DEFAULT_PTY_SCROLLBACK_LINES
232}
233
234fn default_max_scrollback_bytes() -> usize {
235    // Reduced from 50MB to 25MB for memory-constrained development environments
236    // Can be overridden in vtcode.toml with: pty.max_scrollback_bytes = 52428800
237    25_000_000 // 25MB max to prevent memory explosion
238}
239
240fn default_large_output_threshold_kb() -> usize {
241    5_000 // 5MB threshold for auto-spooling
242}
243
244fn default_tool_output_mode() -> ToolOutputMode {
245    ToolOutputMode::Compact
246}
247
248fn default_tool_output_max_lines() -> usize {
249    600
250}
251
252fn default_tool_output_spool_bytes() -> usize {
253    200_000
254}
255
256fn default_allow_tool_ansi() -> bool {
257    false
258}
259
260fn default_inline_viewport_rows() -> u16 {
261    crate::constants::ui::DEFAULT_INLINE_VIEWPORT_ROWS
262}
263
264fn default_reasoning_display_mode() -> ReasoningDisplayMode {
265    ReasoningDisplayMode::Toggle
266}
267
268fn default_reasoning_visible_default() -> bool {
269    crate::constants::ui::DEFAULT_REASONING_VISIBLE
270}
271
272/// Kitty keyboard protocol configuration
273/// Reference: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
274#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
275#[derive(Debug, Clone, Deserialize, Serialize)]
276pub struct KeyboardProtocolConfig {
277    /// Enable keyboard protocol enhancements (master toggle)
278    #[serde(default = "default_keyboard_protocol_enabled")]
279    pub enabled: bool,
280
281    /// Preset mode: "default", "full", "minimal", "custom"
282    #[serde(default = "default_keyboard_protocol_mode")]
283    pub mode: String,
284
285    /// Individual flag controls (used when mode = "custom")
286    /// Resolve Esc key ambiguity (recommended)
287    #[serde(default = "default_disambiguate_escape_codes")]
288    pub disambiguate_escape_codes: bool,
289
290    /// Report press/release/repeat events
291    #[serde(default = "default_report_event_types")]
292    pub report_event_types: bool,
293
294    /// Report alternate key layouts
295    #[serde(default = "default_report_alternate_keys")]
296    pub report_alternate_keys: bool,
297
298    /// Report modifier-only keys (Shift, Ctrl, Alt alone)
299    #[serde(default = "default_report_all_keys")]
300    pub report_all_keys: bool,
301}
302
303impl Default for KeyboardProtocolConfig {
304    fn default() -> Self {
305        Self {
306            enabled: default_keyboard_protocol_enabled(),
307            mode: default_keyboard_protocol_mode(),
308            disambiguate_escape_codes: default_disambiguate_escape_codes(),
309            report_event_types: default_report_event_types(),
310            report_alternate_keys: default_report_alternate_keys(),
311            report_all_keys: default_report_all_keys(),
312        }
313    }
314}
315
316impl KeyboardProtocolConfig {
317    pub fn validate(&self) -> anyhow::Result<()> {
318        match self.mode.as_str() {
319            "default" | "full" | "minimal" | "custom" => Ok(()),
320            _ => anyhow::bail!(
321                "Invalid keyboard protocol mode '{}'. Must be: default, full, minimal, or custom",
322                self.mode
323            ),
324        }
325    }
326}
327
328fn default_keyboard_protocol_enabled() -> bool {
329    std::env::var("VTCODE_KEYBOARD_PROTOCOL_ENABLED")
330        .ok()
331        .and_then(|v| v.parse().ok())
332        .unwrap_or(true)
333}
334
335fn default_keyboard_protocol_mode() -> String {
336    std::env::var("VTCODE_KEYBOARD_PROTOCOL_MODE").unwrap_or_else(|_| "default".to_string())
337}
338
339fn default_disambiguate_escape_codes() -> bool {
340    true
341}
342
343fn default_report_event_types() -> bool {
344    true
345}
346
347fn default_report_alternate_keys() -> bool {
348    true
349}
350
351fn default_report_all_keys() -> bool {
352    false
353}