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