1use anyhow::{Result, anyhow, bail};
2use serde::{Deserialize, Serialize};
3
4use crate::status_line::StatusLineConfig;
5use crate::terminal_title::TerminalTitleConfig;
6
7#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
8#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
9#[serde(rename_all = "snake_case")]
10#[derive(Default)]
11pub enum ToolOutputMode {
12 #[default]
13 Compact,
14 Full,
15}
16
17#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
18#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
19#[serde(rename_all = "snake_case")]
20#[derive(Default)]
21pub enum ReasoningDisplayMode {
22 Always,
23 #[default]
24 Toggle,
25 Hidden,
26}
27
28#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
30#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
31#[serde(rename_all = "snake_case")]
32pub enum LayoutModeOverride {
33 #[default]
35 Auto,
36 Compact,
38 Standard,
40 Wide,
42}
43
44#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
46#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
47#[serde(rename_all = "snake_case")]
48pub enum UiDisplayMode {
49 Full,
51 #[default]
53 Minimal,
54 Focused,
56}
57
58#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
60#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
61#[serde(rename_all = "snake_case")]
62pub enum NotificationDeliveryMode {
63 Terminal,
65 Hybrid,
67 #[default]
69 Desktop,
70}
71
72#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
74#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
75#[serde(rename_all = "snake_case")]
76pub enum NotificationBackend {
77 #[default]
79 Auto,
80 Osascript,
82 NotifyRust,
84 Terminal,
86}
87
88#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
90#[derive(Debug, Clone, Deserialize, Serialize)]
91pub struct UiNotificationsConfig {
92 #[serde(default = "default_notifications_enabled")]
94 pub enabled: bool,
95
96 #[serde(default)]
98 pub delivery_mode: NotificationDeliveryMode,
99
100 #[serde(default)]
102 pub backend: NotificationBackend,
103
104 #[serde(default = "default_notifications_suppress_when_focused")]
106 pub suppress_when_focused: bool,
107
108 #[serde(default)]
111 pub command_failure: Option<bool>,
112
113 #[serde(default = "default_notifications_tool_failure")]
115 pub tool_failure: bool,
116
117 #[serde(default = "default_notifications_error")]
119 pub error: bool,
120
121 #[serde(default = "default_notifications_completion")]
124 pub completion: bool,
125
126 #[serde(default)]
129 pub completion_success: Option<bool>,
130
131 #[serde(default)]
134 pub completion_failure: Option<bool>,
135
136 #[serde(default = "default_notifications_hitl")]
138 pub hitl: bool,
139
140 #[serde(default)]
143 pub policy_approval: Option<bool>,
144
145 #[serde(default)]
148 pub request: Option<bool>,
149
150 #[serde(default = "default_notifications_tool_success")]
152 pub tool_success: bool,
153
154 #[serde(default = "default_notifications_repeat_window_seconds")]
156 pub repeat_window_seconds: u64,
157
158 #[serde(default = "default_notifications_max_identical_in_window")]
160 pub max_identical_in_window: u32,
161}
162
163impl Default for UiNotificationsConfig {
164 fn default() -> Self {
165 Self {
166 enabled: default_notifications_enabled(),
167 delivery_mode: NotificationDeliveryMode::default(),
168 backend: NotificationBackend::default(),
169 suppress_when_focused: default_notifications_suppress_when_focused(),
170 command_failure: Some(default_notifications_command_failure()),
171 tool_failure: default_notifications_tool_failure(),
172 error: default_notifications_error(),
173 completion: default_notifications_completion(),
174 completion_success: Some(default_notifications_completion_success()),
175 completion_failure: Some(default_notifications_completion_failure()),
176 hitl: default_notifications_hitl(),
177 policy_approval: Some(default_notifications_policy_approval()),
178 request: Some(default_notifications_request()),
179 tool_success: default_notifications_tool_success(),
180 repeat_window_seconds: default_notifications_repeat_window_seconds(),
181 max_identical_in_window: default_notifications_max_identical_in_window(),
182 }
183 }
184}
185
186#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
187#[derive(Debug, Clone, Deserialize, Serialize)]
188pub struct UiFullscreenConfig {
189 #[serde(default = "default_fullscreen_mouse_capture")]
192 pub mouse_capture: bool,
193
194 #[serde(default = "default_fullscreen_copy_on_select")]
197 pub copy_on_select: bool,
198
199 #[serde(default = "default_fullscreen_scroll_speed")]
203 pub scroll_speed: u8,
204}
205
206impl Default for UiFullscreenConfig {
207 fn default() -> Self {
208 Self {
209 mouse_capture: default_fullscreen_mouse_capture(),
210 copy_on_select: default_fullscreen_copy_on_select(),
211 scroll_speed: default_fullscreen_scroll_speed(),
212 }
213 }
214}
215
216#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
217#[derive(Debug, Clone, Deserialize, Serialize)]
218pub struct UiConfig {
219 #[serde(default = "default_tool_output_mode")]
221 pub tool_output_mode: ToolOutputMode,
222
223 #[serde(default = "default_tool_output_max_lines")]
225 pub tool_output_max_lines: usize,
226
227 #[serde(default = "default_tool_output_spool_bytes")]
229 pub tool_output_spool_bytes: usize,
230
231 #[serde(default)]
233 pub tool_output_spool_dir: Option<String>,
234
235 #[serde(default = "default_allow_tool_ansi")]
237 pub allow_tool_ansi: bool,
238
239 #[serde(default = "default_inline_viewport_rows")]
241 pub inline_viewport_rows: u16,
242
243 #[serde(default = "default_reasoning_display_mode")]
245 pub reasoning_display_mode: ReasoningDisplayMode,
246
247 #[serde(default = "default_reasoning_visible_default")]
249 pub reasoning_visible_default: bool,
250
251 #[serde(default = "default_vim_mode")]
253 pub vim_mode: bool,
254
255 #[serde(default)]
257 pub status_line: StatusLineConfig,
258
259 #[serde(default)]
261 pub terminal_title: TerminalTitleConfig,
262
263 #[serde(default)]
265 pub keyboard_protocol: KeyboardProtocolConfig,
266
267 #[serde(default)]
269 pub layout_mode: LayoutModeOverride,
270
271 #[serde(default)]
273 pub display_mode: UiDisplayMode,
274
275 #[serde(default = "default_show_sidebar")]
277 pub show_sidebar: bool,
278
279 #[serde(default = "default_dim_completed_todos")]
281 pub dim_completed_todos: bool,
282
283 #[serde(default = "default_message_block_spacing")]
285 pub message_block_spacing: bool,
286
287 #[serde(default = "default_show_turn_timer")]
289 pub show_turn_timer: bool,
290
291 #[serde(default = "default_show_diagnostics_in_transcript")]
295 pub show_diagnostics_in_transcript: bool,
296
297 #[serde(default = "default_minimum_contrast")]
305 pub minimum_contrast: f32,
306
307 #[serde(default = "default_bold_is_bright")]
311 pub bold_is_bright: bool,
312
313 #[serde(default = "default_safe_colors_only")]
319 pub safe_colors_only: bool,
320
321 #[serde(default = "default_color_scheme_mode")]
326 pub color_scheme_mode: ColorSchemeMode,
327
328 #[serde(default)]
330 pub notifications: UiNotificationsConfig,
331
332 #[serde(default)]
334 pub fullscreen: UiFullscreenConfig,
335
336 #[serde(default = "default_screen_reader_mode")]
340 pub screen_reader_mode: bool,
341
342 #[serde(default = "default_reduce_motion_mode")]
345 pub reduce_motion_mode: bool,
346
347 #[serde(default = "default_reduce_motion_keep_progress_animation")]
349 pub reduce_motion_keep_progress_animation: bool,
350
351 #[serde(default = "default_hide_header")]
353 pub hide_header: bool,
354}
355
356#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
358#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
359#[serde(rename_all = "snake_case")]
360pub enum ColorSchemeMode {
361 #[default]
363 Auto,
364 Light,
366 Dark,
368}
369
370fn default_minimum_contrast() -> f32 {
371 crate::constants::ui::THEME_MIN_CONTRAST_RATIO
372}
373
374fn default_bold_is_bright() -> bool {
375 false
376}
377
378fn default_safe_colors_only() -> bool {
379 false
380}
381
382fn default_color_scheme_mode() -> ColorSchemeMode {
383 ColorSchemeMode::Auto
384}
385
386fn default_show_sidebar() -> bool {
387 true
388}
389
390fn default_dim_completed_todos() -> bool {
391 true
392}
393
394fn default_message_block_spacing() -> bool {
395 true
396}
397
398fn default_show_turn_timer() -> bool {
399 false
400}
401
402fn default_show_diagnostics_in_transcript() -> bool {
403 false
404}
405
406fn default_vim_mode() -> bool {
407 false
408}
409
410fn default_notifications_enabled() -> bool {
411 true
412}
413
414fn default_notifications_suppress_when_focused() -> bool {
415 true
416}
417
418fn default_notifications_command_failure() -> bool {
419 false
420}
421
422fn default_notifications_tool_failure() -> bool {
423 false
424}
425
426fn default_notifications_error() -> bool {
427 true
428}
429
430fn default_notifications_completion() -> bool {
431 true
432}
433
434fn default_notifications_completion_success() -> bool {
435 false
436}
437
438fn default_notifications_completion_failure() -> bool {
439 true
440}
441
442fn default_notifications_hitl() -> bool {
443 true
444}
445
446fn default_notifications_policy_approval() -> bool {
447 true
448}
449
450fn default_notifications_request() -> bool {
451 false
452}
453
454fn default_notifications_tool_success() -> bool {
455 false
456}
457
458fn default_notifications_repeat_window_seconds() -> u64 {
459 30
460}
461
462fn default_notifications_max_identical_in_window() -> u32 {
463 1
464}
465
466fn env_bool_var(name: &str) -> Option<bool> {
467 crate::env_helpers::read_env_var(name).and_then(|v| {
468 let normalized = v.trim().to_ascii_lowercase();
469 match normalized.as_str() {
470 "1" | "true" | "yes" | "on" => Some(true),
471 "0" | "false" | "no" | "off" => Some(false),
472 _ => None,
473 }
474 })
475}
476
477fn env_u8_var(name: &str) -> Option<u8> {
478 crate::env_helpers::read_env_var(name)
479 .and_then(|value| value.trim().parse::<u8>().ok())
480 .map(clamp_fullscreen_scroll_speed)
481}
482
483fn clamp_fullscreen_scroll_speed(value: u8) -> u8 {
484 value.clamp(1, 20)
485}
486
487fn default_fullscreen_mouse_capture() -> bool {
488 env_bool_var("VTCODE_FULLSCREEN_MOUSE_CAPTURE").unwrap_or(true)
489}
490
491fn default_fullscreen_copy_on_select() -> bool {
492 env_bool_var("VTCODE_FULLSCREEN_COPY_ON_SELECT").unwrap_or(true)
493}
494
495fn default_fullscreen_scroll_speed() -> u8 {
496 env_u8_var("VTCODE_FULLSCREEN_SCROLL_SPEED").unwrap_or(3)
497}
498
499fn default_screen_reader_mode() -> bool {
500 env_bool_var("VTCODE_SCREEN_READER").unwrap_or(false)
501}
502
503fn default_reduce_motion_mode() -> bool {
504 env_bool_var("VTCODE_REDUCE_MOTION").unwrap_or(false)
505}
506
507fn default_reduce_motion_keep_progress_animation() -> bool {
508 false
509}
510
511fn default_hide_header() -> bool {
512 true
513}
514
515fn default_ask_questions_enabled() -> bool {
516 true
517}
518
519impl Default for UiConfig {
520 fn default() -> Self {
521 Self {
522 tool_output_mode: default_tool_output_mode(),
523 tool_output_max_lines: default_tool_output_max_lines(),
524 tool_output_spool_bytes: default_tool_output_spool_bytes(),
525 tool_output_spool_dir: None,
526 allow_tool_ansi: default_allow_tool_ansi(),
527 inline_viewport_rows: default_inline_viewport_rows(),
528 reasoning_display_mode: default_reasoning_display_mode(),
529 reasoning_visible_default: default_reasoning_visible_default(),
530 vim_mode: default_vim_mode(),
531 status_line: StatusLineConfig::default(),
532 terminal_title: TerminalTitleConfig::default(),
533 keyboard_protocol: KeyboardProtocolConfig::default(),
534 layout_mode: LayoutModeOverride::default(),
535 display_mode: UiDisplayMode::default(),
536 show_sidebar: default_show_sidebar(),
537 dim_completed_todos: default_dim_completed_todos(),
538 message_block_spacing: default_message_block_spacing(),
539 show_turn_timer: default_show_turn_timer(),
540 show_diagnostics_in_transcript: default_show_diagnostics_in_transcript(),
541 minimum_contrast: default_minimum_contrast(),
543 bold_is_bright: default_bold_is_bright(),
544 safe_colors_only: default_safe_colors_only(),
545 color_scheme_mode: default_color_scheme_mode(),
546 notifications: UiNotificationsConfig::default(),
547 fullscreen: UiFullscreenConfig::default(),
548 screen_reader_mode: default_screen_reader_mode(),
549 reduce_motion_mode: default_reduce_motion_mode(),
550 reduce_motion_keep_progress_animation: default_reduce_motion_keep_progress_animation(),
551 hide_header: default_hide_header(),
552 }
553 }
554}
555
556#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
558#[derive(Debug, Clone, Deserialize, Serialize, Default)]
559pub struct ChatConfig {
560 #[serde(default, rename = "askQuestions", alias = "ask_questions")]
562 pub ask_questions: AskQuestionsConfig,
563}
564
565#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
567#[derive(Debug, Clone, Deserialize, Serialize)]
568pub struct AskQuestionsConfig {
569 #[serde(default = "default_ask_questions_enabled")]
571 pub enabled: bool,
572}
573
574impl Default for AskQuestionsConfig {
575 fn default() -> Self {
576 Self {
577 enabled: default_ask_questions_enabled(),
578 }
579 }
580}
581
582#[cfg(test)]
583mod tests {
584 use super::*;
585 use serial_test::serial;
586
587 fn with_env_var<F>(key: &str, value: Option<&str>, f: F)
588 where
589 F: FnOnce(),
590 {
591 let previous = crate::env_helpers::test_env_overrides::get(key);
592 crate::env_helpers::test_env_overrides::set(key, value);
593 f();
594 crate::env_helpers::test_env_overrides::restore(key, previous);
595 }
596
597 #[test]
598 #[serial]
599 fn fullscreen_defaults_match_expected_values() {
600 let fullscreen = UiFullscreenConfig::default();
601
602 assert!(fullscreen.mouse_capture);
603 assert!(fullscreen.copy_on_select);
604 assert_eq!(fullscreen.scroll_speed, 3);
605 }
606
607 #[test]
608 #[serial]
609 fn fullscreen_env_overrides_apply_to_defaults() {
610 with_env_var("VTCODE_FULLSCREEN_MOUSE_CAPTURE", Some("0"), || {
611 with_env_var("VTCODE_FULLSCREEN_COPY_ON_SELECT", Some("false"), || {
612 with_env_var("VTCODE_FULLSCREEN_SCROLL_SPEED", Some("7"), || {
613 let fullscreen = UiFullscreenConfig::default();
614 assert!(!fullscreen.mouse_capture);
615 assert!(!fullscreen.copy_on_select);
616 assert_eq!(fullscreen.scroll_speed, 7);
617 });
618 });
619 });
620 }
621
622 #[test]
623 #[serial]
624 fn fullscreen_scroll_speed_is_clamped() {
625 with_env_var("VTCODE_FULLSCREEN_SCROLL_SPEED", Some("0"), || {
626 assert_eq!(UiFullscreenConfig::default().scroll_speed, 1);
627 });
628
629 with_env_var("VTCODE_FULLSCREEN_SCROLL_SPEED", Some("99"), || {
630 assert_eq!(UiFullscreenConfig::default().scroll_speed, 20);
631 });
632 }
633}
634
635#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
637#[derive(Debug, Clone, Deserialize, Serialize)]
638pub struct PtyConfig {
639 #[serde(default = "default_pty_enabled")]
641 pub enabled: bool,
642
643 #[serde(default = "default_pty_rows")]
645 pub default_rows: u16,
646
647 #[serde(default = "default_pty_cols")]
649 pub default_cols: u16,
650
651 #[serde(default = "default_max_pty_sessions")]
653 pub max_sessions: usize,
654
655 #[serde(default = "default_pty_timeout")]
657 pub command_timeout_seconds: u64,
658
659 #[serde(default = "default_stdout_tail_lines")]
661 pub stdout_tail_lines: usize,
662
663 #[serde(default = "default_scrollback_lines")]
665 pub scrollback_lines: usize,
666
667 #[serde(default = "default_max_scrollback_bytes")]
669 pub max_scrollback_bytes: usize,
670
671 #[serde(default)]
673 pub emulation_backend: PtyEmulationBackend,
674
675 #[serde(default = "default_large_output_threshold_kb")]
677 pub large_output_threshold_kb: usize,
678
679 #[serde(default)]
681 pub preferred_shell: Option<String>,
682
683 #[serde(default = "default_shell_zsh_fork")]
685 pub shell_zsh_fork: bool,
686
687 #[serde(default)]
689 pub zsh_path: Option<String>,
690}
691
692impl Default for PtyConfig {
693 fn default() -> Self {
694 Self {
695 enabled: default_pty_enabled(),
696 default_rows: default_pty_rows(),
697 default_cols: default_pty_cols(),
698 max_sessions: default_max_pty_sessions(),
699 command_timeout_seconds: default_pty_timeout(),
700 stdout_tail_lines: default_stdout_tail_lines(),
701 scrollback_lines: default_scrollback_lines(),
702 max_scrollback_bytes: default_max_scrollback_bytes(),
703 emulation_backend: PtyEmulationBackend::default(),
704 large_output_threshold_kb: default_large_output_threshold_kb(),
705 preferred_shell: None,
706 shell_zsh_fork: default_shell_zsh_fork(),
707 zsh_path: None,
708 }
709 }
710}
711
712#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
713#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
714#[serde(rename_all = "snake_case")]
715pub enum PtyEmulationBackend {
716 #[default]
717 Ghostty,
718 LegacyVt100,
719}
720
721impl PtyEmulationBackend {
722 #[must_use]
723 pub const fn as_str(self) -> &'static str {
724 match self {
725 Self::Ghostty => "ghostty",
726 Self::LegacyVt100 => "legacy_vt100",
727 }
728 }
729}
730
731impl PtyConfig {
732 pub fn validate(&self) -> Result<()> {
733 self.zsh_fork_shell_path()?;
734 Ok(())
735 }
736
737 pub fn zsh_fork_shell_path(&self) -> Result<Option<&str>> {
738 if !self.shell_zsh_fork {
739 return Ok(None);
740 }
741
742 let zsh_path = self
743 .zsh_path
744 .as_deref()
745 .map(str::trim)
746 .filter(|path| !path.is_empty())
747 .ok_or_else(|| {
748 anyhow!(
749 "pty.shell_zsh_fork is enabled, but pty.zsh_path is not configured. \
750 Set pty.zsh_path to an absolute path to patched zsh."
751 )
752 })?;
753
754 #[cfg(not(unix))]
755 {
756 let _ = zsh_path;
757 bail!("pty.shell_zsh_fork is only supported on Unix platforms");
758 }
759
760 #[cfg(unix)]
761 {
762 let path = std::path::Path::new(zsh_path);
763 if !path.is_absolute() {
764 bail!(
765 "pty.zsh_path '{}' must be an absolute path when pty.shell_zsh_fork is enabled",
766 zsh_path
767 );
768 }
769 if !path.exists() {
770 bail!(
771 "pty.zsh_path '{}' does not exist (required when pty.shell_zsh_fork is enabled)",
772 zsh_path
773 );
774 }
775 if !path.is_file() {
776 bail!(
777 "pty.zsh_path '{}' is not a file (required when pty.shell_zsh_fork is enabled)",
778 zsh_path
779 );
780 }
781 }
782
783 Ok(Some(zsh_path))
784 }
785}
786
787fn default_pty_enabled() -> bool {
788 true
789}
790
791fn default_pty_rows() -> u16 {
792 24
793}
794
795fn default_pty_cols() -> u16 {
796 80
797}
798
799fn default_max_pty_sessions() -> usize {
800 10
801}
802
803fn default_pty_timeout() -> u64 {
804 300
805}
806
807fn default_shell_zsh_fork() -> bool {
808 false
809}
810
811fn default_stdout_tail_lines() -> usize {
812 crate::constants::defaults::DEFAULT_PTY_STDOUT_TAIL_LINES
813}
814
815fn default_scrollback_lines() -> usize {
816 crate::constants::defaults::DEFAULT_PTY_SCROLLBACK_LINES
817}
818
819fn default_max_scrollback_bytes() -> usize {
820 25_000_000 }
824
825fn default_large_output_threshold_kb() -> usize {
826 5_000 }
828
829fn default_tool_output_mode() -> ToolOutputMode {
830 ToolOutputMode::Compact
831}
832
833fn default_tool_output_max_lines() -> usize {
834 600
835}
836
837fn default_tool_output_spool_bytes() -> usize {
838 200_000
839}
840
841fn default_allow_tool_ansi() -> bool {
842 false
843}
844
845fn default_inline_viewport_rows() -> u16 {
846 crate::constants::ui::DEFAULT_INLINE_VIEWPORT_ROWS
847}
848
849fn default_reasoning_display_mode() -> ReasoningDisplayMode {
850 ReasoningDisplayMode::Toggle
851}
852
853fn default_reasoning_visible_default() -> bool {
854 crate::constants::ui::DEFAULT_REASONING_VISIBLE
855}
856
857#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
860#[derive(Debug, Clone, Deserialize, Serialize)]
861pub struct KeyboardProtocolConfig {
862 #[serde(default = "default_keyboard_protocol_enabled")]
864 pub enabled: bool,
865
866 #[serde(default = "default_keyboard_protocol_mode")]
868 pub mode: String,
869
870 #[serde(default = "default_disambiguate_escape_codes")]
872 pub disambiguate_escape_codes: bool,
873
874 #[serde(default = "default_report_event_types")]
876 pub report_event_types: bool,
877
878 #[serde(default = "default_report_alternate_keys")]
880 pub report_alternate_keys: bool,
881
882 #[serde(default = "default_report_all_keys")]
884 pub report_all_keys: bool,
885}
886
887impl Default for KeyboardProtocolConfig {
888 fn default() -> Self {
889 Self {
890 enabled: default_keyboard_protocol_enabled(),
891 mode: default_keyboard_protocol_mode(),
892 disambiguate_escape_codes: default_disambiguate_escape_codes(),
893 report_event_types: default_report_event_types(),
894 report_alternate_keys: default_report_alternate_keys(),
895 report_all_keys: default_report_all_keys(),
896 }
897 }
898}
899
900impl KeyboardProtocolConfig {
901 pub fn validate(&self) -> Result<()> {
902 match self.mode.as_str() {
903 "default" | "full" | "minimal" | "custom" => Ok(()),
904 _ => anyhow::bail!(
905 "Invalid keyboard protocol mode '{}'. Must be: default, full, minimal, or custom",
906 self.mode
907 ),
908 }
909 }
910}
911
912fn default_keyboard_protocol_enabled() -> bool {
913 std::env::var("VTCODE_KEYBOARD_PROTOCOL_ENABLED")
914 .ok()
915 .and_then(|v| v.parse().ok())
916 .unwrap_or(true)
917}
918
919fn default_keyboard_protocol_mode() -> String {
920 std::env::var("VTCODE_KEYBOARD_PROTOCOL_MODE").unwrap_or_else(|_| "default".to_string())
921}
922
923fn default_disambiguate_escape_codes() -> bool {
924 true
925}
926
927fn default_report_event_types() -> bool {
928 true
929}
930
931fn default_report_alternate_keys() -> bool {
932 true
933}
934
935fn default_report_all_keys() -> bool {
936 false
937}