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")]
61 pub tool_output_mode: ToolOutputMode,
62
63 #[serde(default = "default_tool_output_max_lines")]
65 pub tool_output_max_lines: usize,
66
67 #[serde(default = "default_tool_output_spool_bytes")]
69 pub tool_output_spool_bytes: usize,
70
71 #[serde(default)]
73 pub tool_output_spool_dir: Option<String>,
74
75 #[serde(default = "default_allow_tool_ansi")]
77 pub allow_tool_ansi: bool,
78
79 #[serde(default = "default_inline_viewport_rows")]
81 pub inline_viewport_rows: u16,
82
83 #[serde(default = "default_reasoning_display_mode")]
85 pub reasoning_display_mode: ReasoningDisplayMode,
86
87 #[serde(default = "default_reasoning_visible_default")]
89 pub reasoning_visible_default: bool,
90
91 #[serde(default)]
93 pub status_line: StatusLineConfig,
94
95 #[serde(default)]
97 pub keyboard_protocol: KeyboardProtocolConfig,
98
99 #[serde(default)]
101 pub layout_mode: LayoutModeOverride,
102
103 #[serde(default)]
105 pub display_mode: UiDisplayMode,
106
107 #[serde(default = "default_show_sidebar")]
109 pub show_sidebar: bool,
110
111 #[serde(default = "default_dim_completed_todos")]
113 pub dim_completed_todos: bool,
114
115 #[serde(default = "default_message_block_spacing")]
117 pub message_block_spacing: bool,
118
119 #[serde(default = "default_minimum_contrast")]
128 pub minimum_contrast: f64,
129
130 #[serde(default = "default_bold_is_bright")]
134 pub bold_is_bright: bool,
135
136 #[serde(default = "default_safe_colors_only")]
142 pub safe_colors_only: bool,
143
144 #[serde(default = "default_color_scheme_mode")]
149 pub color_scheme_mode: ColorSchemeMode,
150}
151
152#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
154#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
155#[serde(rename_all = "snake_case")]
156pub enum ColorSchemeMode {
157 #[default]
159 Auto,
160 Light,
162 Dark,
164}
165
166fn default_minimum_contrast() -> f64 {
167 crate::constants::ui::THEME_MIN_CONTRAST_RATIO
168}
169
170fn default_bold_is_bright() -> bool {
171 false
172}
173
174fn default_safe_colors_only() -> bool {
175 false
176}
177
178fn default_color_scheme_mode() -> ColorSchemeMode {
179 ColorSchemeMode::Auto
180}
181
182fn default_show_sidebar() -> bool {
183 true
184}
185
186fn default_dim_completed_todos() -> bool {
187 true
188}
189
190fn default_message_block_spacing() -> bool {
191 true
192}
193
194fn default_ask_questions_enabled() -> bool {
195 true
196}
197
198impl Default for UiConfig {
199 fn default() -> Self {
200 Self {
201 tool_output_mode: default_tool_output_mode(),
202 tool_output_max_lines: default_tool_output_max_lines(),
203 tool_output_spool_bytes: default_tool_output_spool_bytes(),
204 tool_output_spool_dir: None,
205 allow_tool_ansi: default_allow_tool_ansi(),
206 inline_viewport_rows: default_inline_viewport_rows(),
207 reasoning_display_mode: default_reasoning_display_mode(),
208 reasoning_visible_default: default_reasoning_visible_default(),
209 status_line: StatusLineConfig::default(),
210 keyboard_protocol: KeyboardProtocolConfig::default(),
211 layout_mode: LayoutModeOverride::default(),
212 display_mode: UiDisplayMode::default(),
213 show_sidebar: default_show_sidebar(),
214 dim_completed_todos: default_dim_completed_todos(),
215 message_block_spacing: default_message_block_spacing(),
216 minimum_contrast: default_minimum_contrast(),
218 bold_is_bright: default_bold_is_bright(),
219 safe_colors_only: default_safe_colors_only(),
220 color_scheme_mode: default_color_scheme_mode(),
221 }
222 }
223}
224
225#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
227#[derive(Debug, Clone, Deserialize, Serialize, Default)]
228pub struct ChatConfig {
229 #[serde(default, rename = "askQuestions", alias = "ask_questions")]
231 pub ask_questions: AskQuestionsConfig,
232}
233
234#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
236#[derive(Debug, Clone, Deserialize, Serialize)]
237pub struct AskQuestionsConfig {
238 #[serde(default = "default_ask_questions_enabled")]
240 pub enabled: bool,
241}
242
243impl Default for AskQuestionsConfig {
244 fn default() -> Self {
245 Self {
246 enabled: default_ask_questions_enabled(),
247 }
248 }
249}
250
251#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
253#[derive(Debug, Clone, Deserialize, Serialize)]
254pub struct PtyConfig {
255 #[serde(default = "default_pty_enabled")]
257 pub enabled: bool,
258
259 #[serde(default = "default_pty_rows")]
261 pub default_rows: u16,
262
263 #[serde(default = "default_pty_cols")]
265 pub default_cols: u16,
266
267 #[serde(default = "default_max_pty_sessions")]
269 pub max_sessions: usize,
270
271 #[serde(default = "default_pty_timeout")]
273 pub command_timeout_seconds: u64,
274
275 #[serde(default = "default_stdout_tail_lines")]
277 pub stdout_tail_lines: usize,
278
279 #[serde(default = "default_scrollback_lines")]
281 pub scrollback_lines: usize,
282
283 #[serde(default = "default_max_scrollback_bytes")]
285 pub max_scrollback_bytes: usize,
286
287 #[serde(default = "default_large_output_threshold_kb")]
289 pub large_output_threshold_kb: usize,
290
291 #[serde(default)]
293 pub preferred_shell: Option<String>,
294}
295
296impl Default for PtyConfig {
297 fn default() -> Self {
298 Self {
299 enabled: default_pty_enabled(),
300 default_rows: default_pty_rows(),
301 default_cols: default_pty_cols(),
302 max_sessions: default_max_pty_sessions(),
303 command_timeout_seconds: default_pty_timeout(),
304 stdout_tail_lines: default_stdout_tail_lines(),
305 scrollback_lines: default_scrollback_lines(),
306 max_scrollback_bytes: default_max_scrollback_bytes(),
307 large_output_threshold_kb: default_large_output_threshold_kb(),
308 preferred_shell: None,
309 }
310 }
311}
312
313fn default_pty_enabled() -> bool {
314 true
315}
316
317fn default_pty_rows() -> u16 {
318 24
319}
320
321fn default_pty_cols() -> u16 {
322 80
323}
324
325fn default_max_pty_sessions() -> usize {
326 10
327}
328
329fn default_pty_timeout() -> u64 {
330 300
331}
332
333fn default_stdout_tail_lines() -> usize {
334 crate::constants::defaults::DEFAULT_PTY_STDOUT_TAIL_LINES
335}
336
337fn default_scrollback_lines() -> usize {
338 crate::constants::defaults::DEFAULT_PTY_SCROLLBACK_LINES
339}
340
341fn default_max_scrollback_bytes() -> usize {
342 25_000_000 }
346
347fn default_large_output_threshold_kb() -> usize {
348 5_000 }
350
351fn default_tool_output_mode() -> ToolOutputMode {
352 ToolOutputMode::Compact
353}
354
355fn default_tool_output_max_lines() -> usize {
356 600
357}
358
359fn default_tool_output_spool_bytes() -> usize {
360 200_000
361}
362
363fn default_allow_tool_ansi() -> bool {
364 false
365}
366
367fn default_inline_viewport_rows() -> u16 {
368 crate::constants::ui::DEFAULT_INLINE_VIEWPORT_ROWS
369}
370
371fn default_reasoning_display_mode() -> ReasoningDisplayMode {
372 ReasoningDisplayMode::Toggle
373}
374
375fn default_reasoning_visible_default() -> bool {
376 crate::constants::ui::DEFAULT_REASONING_VISIBLE
377}
378
379#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
382#[derive(Debug, Clone, Deserialize, Serialize)]
383pub struct KeyboardProtocolConfig {
384 #[serde(default = "default_keyboard_protocol_enabled")]
386 pub enabled: bool,
387
388 #[serde(default = "default_keyboard_protocol_mode")]
390 pub mode: String,
391
392 #[serde(default = "default_disambiguate_escape_codes")]
394 pub disambiguate_escape_codes: bool,
395
396 #[serde(default = "default_report_event_types")]
398 pub report_event_types: bool,
399
400 #[serde(default = "default_report_alternate_keys")]
402 pub report_alternate_keys: bool,
403
404 #[serde(default = "default_report_all_keys")]
406 pub report_all_keys: bool,
407}
408
409impl Default for KeyboardProtocolConfig {
410 fn default() -> Self {
411 Self {
412 enabled: default_keyboard_protocol_enabled(),
413 mode: default_keyboard_protocol_mode(),
414 disambiguate_escape_codes: default_disambiguate_escape_codes(),
415 report_event_types: default_report_event_types(),
416 report_alternate_keys: default_report_alternate_keys(),
417 report_all_keys: default_report_all_keys(),
418 }
419 }
420}
421
422impl KeyboardProtocolConfig {
423 pub fn validate(&self) -> anyhow::Result<()> {
424 match self.mode.as_str() {
425 "default" | "full" | "minimal" | "custom" => Ok(()),
426 _ => anyhow::bail!(
427 "Invalid keyboard protocol mode '{}'. Must be: default, full, minimal, or custom",
428 self.mode
429 ),
430 }
431 }
432}
433
434fn default_keyboard_protocol_enabled() -> bool {
435 std::env::var("VTCODE_KEYBOARD_PROTOCOL_ENABLED")
436 .ok()
437 .and_then(|v| v.parse().ok())
438 .unwrap_or(true)
439}
440
441fn default_keyboard_protocol_mode() -> String {
442 std::env::var("VTCODE_KEYBOARD_PROTOCOL_MODE").unwrap_or_else(|_| "default".to_string())
443}
444
445fn default_disambiguate_escape_codes() -> bool {
446 true
447}
448
449fn default_report_event_types() -> bool {
450 true
451}
452
453fn default_report_alternate_keys() -> bool {
454 true
455}
456
457fn default_report_all_keys() -> bool {
458 false
459}