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#[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 #[default]
33 Auto,
34 Compact,
36 Standard,
38 Wide,
40}
41
42#[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,
49 #[default]
51 Minimal,
52 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 #[serde(default)]
82 pub layout_mode: LayoutModeOverride,
83
84 #[serde(default)]
86 pub display_mode: UiDisplayMode,
87
88 #[serde(default = "default_show_sidebar")]
90 pub show_sidebar: bool,
91
92 #[serde(default = "default_dim_completed_todos")]
94 pub dim_completed_todos: bool,
95
96 #[serde(default = "default_message_block_spacing")]
98 pub message_block_spacing: bool,
99
100 #[serde(default = "default_minimum_contrast")]
110 pub minimum_contrast: f64,
111
112 #[serde(default = "default_bold_is_bright")]
116 pub bold_is_bright: bool,
117
118 #[serde(default = "default_safe_colors_only")]
124 pub safe_colors_only: bool,
125
126 #[serde(default = "default_color_scheme_mode")]
131 pub color_scheme_mode: ColorSchemeMode,
132}
133
134#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
136#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
137#[serde(rename_all = "snake_case")]
138pub enum ColorSchemeMode {
139 #[default]
141 Auto,
142 Light,
144 Dark,
146}
147
148fn default_minimum_contrast() -> f64 {
149 crate::constants::ui::THEME_MIN_CONTRAST_RATIO
150}
151
152fn default_bold_is_bright() -> bool {
153 false
154}
155
156fn default_safe_colors_only() -> bool {
157 false
158}
159
160fn default_color_scheme_mode() -> ColorSchemeMode {
161 ColorSchemeMode::Auto
162}
163
164fn default_show_sidebar() -> bool {
165 true
166}
167
168fn default_dim_completed_todos() -> bool {
169 true
170}
171
172fn default_message_block_spacing() -> bool {
173 true
174}
175
176impl Default for UiConfig {
177 fn default() -> Self {
178 Self {
179 tool_output_mode: default_tool_output_mode(),
180 tool_output_max_lines: default_tool_output_max_lines(),
181 tool_output_spool_bytes: default_tool_output_spool_bytes(),
182 tool_output_spool_dir: None,
183 allow_tool_ansi: default_allow_tool_ansi(),
184 inline_viewport_rows: default_inline_viewport_rows(),
185 reasoning_display_mode: default_reasoning_display_mode(),
186 reasoning_visible_default: default_reasoning_visible_default(),
187 status_line: StatusLineConfig::default(),
188 keyboard_protocol: KeyboardProtocolConfig::default(),
189 layout_mode: LayoutModeOverride::default(),
190 display_mode: UiDisplayMode::default(),
191 show_sidebar: default_show_sidebar(),
192 dim_completed_todos: default_dim_completed_todos(),
193 message_block_spacing: default_message_block_spacing(),
194 minimum_contrast: default_minimum_contrast(),
196 bold_is_bright: default_bold_is_bright(),
197 safe_colors_only: default_safe_colors_only(),
198 color_scheme_mode: default_color_scheme_mode(),
199 }
200 }
201}
202
203#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
205#[derive(Debug, Clone, Deserialize, Serialize)]
206pub struct PtyConfig {
207 #[serde(default = "default_pty_enabled")]
209 pub enabled: bool,
210
211 #[serde(default = "default_pty_rows")]
213 pub default_rows: u16,
214
215 #[serde(default = "default_pty_cols")]
217 pub default_cols: u16,
218
219 #[serde(default = "default_max_pty_sessions")]
221 pub max_sessions: usize,
222
223 #[serde(default = "default_pty_timeout")]
225 pub command_timeout_seconds: u64,
226
227 #[serde(default = "default_stdout_tail_lines")]
229 pub stdout_tail_lines: usize,
230
231 #[serde(default = "default_scrollback_lines")]
233 pub scrollback_lines: usize,
234
235 #[serde(default = "default_max_scrollback_bytes")]
237 pub max_scrollback_bytes: usize,
238
239 #[serde(default = "default_large_output_threshold_kb")]
241 pub large_output_threshold_kb: usize,
242
243 #[serde(default)]
245 pub preferred_shell: Option<String>,
246}
247
248impl Default for PtyConfig {
249 fn default() -> Self {
250 Self {
251 enabled: default_pty_enabled(),
252 default_rows: default_pty_rows(),
253 default_cols: default_pty_cols(),
254 max_sessions: default_max_pty_sessions(),
255 command_timeout_seconds: default_pty_timeout(),
256 stdout_tail_lines: default_stdout_tail_lines(),
257 scrollback_lines: default_scrollback_lines(),
258 max_scrollback_bytes: default_max_scrollback_bytes(),
259 large_output_threshold_kb: default_large_output_threshold_kb(),
260 preferred_shell: None,
261 }
262 }
263}
264
265fn default_pty_enabled() -> bool {
266 true
267}
268
269fn default_pty_rows() -> u16 {
270 24
271}
272
273fn default_pty_cols() -> u16 {
274 80
275}
276
277fn default_max_pty_sessions() -> usize {
278 10
279}
280
281fn default_pty_timeout() -> u64 {
282 300
283}
284
285fn default_stdout_tail_lines() -> usize {
286 crate::constants::defaults::DEFAULT_PTY_STDOUT_TAIL_LINES
287}
288
289fn default_scrollback_lines() -> usize {
290 crate::constants::defaults::DEFAULT_PTY_SCROLLBACK_LINES
291}
292
293fn default_max_scrollback_bytes() -> usize {
294 25_000_000 }
298
299fn default_large_output_threshold_kb() -> usize {
300 5_000 }
302
303fn default_tool_output_mode() -> ToolOutputMode {
304 ToolOutputMode::Compact
305}
306
307fn default_tool_output_max_lines() -> usize {
308 600
309}
310
311fn default_tool_output_spool_bytes() -> usize {
312 200_000
313}
314
315fn default_allow_tool_ansi() -> bool {
316 false
317}
318
319fn default_inline_viewport_rows() -> u16 {
320 crate::constants::ui::DEFAULT_INLINE_VIEWPORT_ROWS
321}
322
323fn default_reasoning_display_mode() -> ReasoningDisplayMode {
324 ReasoningDisplayMode::Toggle
325}
326
327fn default_reasoning_visible_default() -> bool {
328 crate::constants::ui::DEFAULT_REASONING_VISIBLE
329}
330
331#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
334#[derive(Debug, Clone, Deserialize, Serialize)]
335pub struct KeyboardProtocolConfig {
336 #[serde(default = "default_keyboard_protocol_enabled")]
338 pub enabled: bool,
339
340 #[serde(default = "default_keyboard_protocol_mode")]
342 pub mode: String,
343
344 #[serde(default = "default_disambiguate_escape_codes")]
347 pub disambiguate_escape_codes: bool,
348
349 #[serde(default = "default_report_event_types")]
351 pub report_event_types: bool,
352
353 #[serde(default = "default_report_alternate_keys")]
355 pub report_alternate_keys: bool,
356
357 #[serde(default = "default_report_all_keys")]
359 pub report_all_keys: bool,
360}
361
362impl Default for KeyboardProtocolConfig {
363 fn default() -> Self {
364 Self {
365 enabled: default_keyboard_protocol_enabled(),
366 mode: default_keyboard_protocol_mode(),
367 disambiguate_escape_codes: default_disambiguate_escape_codes(),
368 report_event_types: default_report_event_types(),
369 report_alternate_keys: default_report_alternate_keys(),
370 report_all_keys: default_report_all_keys(),
371 }
372 }
373}
374
375impl KeyboardProtocolConfig {
376 pub fn validate(&self) -> anyhow::Result<()> {
377 match self.mode.as_str() {
378 "default" | "full" | "minimal" | "custom" => Ok(()),
379 _ => anyhow::bail!(
380 "Invalid keyboard protocol mode '{}'. Must be: default, full, minimal, or custom",
381 self.mode
382 ),
383 }
384 }
385}
386
387fn default_keyboard_protocol_enabled() -> bool {
388 std::env::var("VTCODE_KEYBOARD_PROTOCOL_ENABLED")
389 .ok()
390 .and_then(|v| v.parse().ok())
391 .unwrap_or(true)
392}
393
394fn default_keyboard_protocol_mode() -> String {
395 std::env::var("VTCODE_KEYBOARD_PROTOCOL_MODE").unwrap_or_else(|_| "default".to_string())
396}
397
398fn default_disambiguate_escape_codes() -> bool {
399 true
400}
401
402fn default_report_event_types() -> bool {
403 true
404}
405
406fn default_report_alternate_keys() -> bool {
407 true
408}
409
410fn default_report_all_keys() -> bool {
411 false
412}