1use anyhow::{Result, anyhow, bail};
2use hashbrown::HashMap;
3use serde::{Deserialize, Serialize};
4
5use crate::accessibility::reduce_motion_preference;
6use crate::status_line::StatusLineConfig;
7use crate::terminal_title::TerminalTitleConfig;
8use vtcode_commons::ui_protocol::AppIconPreference;
9use vtcode_commons::ui_protocol::BusyEnterBehavior;
10use vtcode_commons::ui_protocol::DiffPreviewMode;
11
12mod modes;
13
14pub use modes::{
15 LayoutModeOverride, NotificationBackend, NotificationDeliveryMode, ReasoningDisplayMode, ToolDisplayMode,
16 ToolOutputMode, UiDisplayMode,
17};
18
19#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
21#[derive(Debug, Clone, Deserialize, Serialize)]
22pub struct UiNotificationsConfig {
23 #[serde(default = "default_notifications_enabled")]
25 pub enabled: bool,
26
27 #[serde(default)]
29 pub delivery_mode: NotificationDeliveryMode,
30
31 #[serde(default)]
33 pub backend: NotificationBackend,
34
35 #[serde(default = "default_notifications_suppress_when_focused")]
37 pub suppress_when_focused: bool,
38
39 #[serde(default)]
42 pub command_failure: Option<bool>,
43
44 #[serde(default = "default_notifications_tool_failure")]
46 pub tool_failure: bool,
47
48 #[serde(default = "default_notifications_error")]
50 pub error: bool,
51
52 #[serde(default = "default_notifications_completion")]
55 pub completion: bool,
56
57 #[serde(default)]
60 pub completion_success: Option<bool>,
61
62 #[serde(default)]
65 pub completion_failure: Option<bool>,
66
67 #[serde(default = "default_notifications_hitl")]
69 pub hitl: bool,
70
71 #[serde(default)]
74 pub policy_approval: Option<bool>,
75
76 #[serde(default)]
79 pub request: Option<bool>,
80
81 #[serde(default = "default_notifications_tool_success")]
83 pub tool_success: bool,
84
85 #[serde(default = "default_notifications_repeat_window_seconds")]
87 pub repeat_window_seconds: u64,
88
89 #[serde(default = "default_notifications_max_identical_in_window")]
91 pub max_identical_in_window: u32,
92}
93
94impl Default for UiNotificationsConfig {
95 fn default() -> Self {
96 Self {
97 enabled: default_notifications_enabled(),
98 delivery_mode: NotificationDeliveryMode::default(),
99 backend: NotificationBackend::default(),
100 suppress_when_focused: default_notifications_suppress_when_focused(),
101 command_failure: Some(default_notifications_command_failure()),
102 tool_failure: default_notifications_tool_failure(),
103 error: default_notifications_error(),
104 completion: default_notifications_completion(),
105 completion_success: Some(default_notifications_completion_success()),
106 completion_failure: Some(default_notifications_completion_failure()),
107 hitl: default_notifications_hitl(),
108 policy_approval: Some(default_notifications_policy_approval()),
109 request: Some(default_notifications_request()),
110 tool_success: default_notifications_tool_success(),
111 repeat_window_seconds: default_notifications_repeat_window_seconds(),
112 max_identical_in_window: default_notifications_max_identical_in_window(),
113 }
114 }
115}
116
117#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
118#[derive(Debug, Clone, Deserialize, Serialize)]
119pub struct UiFullscreenConfig {
120 #[serde(default = "default_fullscreen_mouse_capture")]
123 pub mouse_capture: bool,
124
125 #[serde(default = "default_fullscreen_copy_on_select")]
131 pub copy_on_select: bool,
132
133 #[serde(default = "default_fullscreen_scroll_speed")]
137 pub scroll_speed: u8,
138}
139
140impl Default for UiFullscreenConfig {
141 fn default() -> Self {
142 Self {
143 mouse_capture: default_fullscreen_mouse_capture(),
144 copy_on_select: default_fullscreen_copy_on_select(),
145 scroll_speed: default_fullscreen_scroll_speed(),
146 }
147 }
148}
149
150#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
154#[derive(Debug, Clone, Deserialize, Serialize)]
155pub struct UiTranscriptReviewConfig {
156 #[serde(default = "default_transcript_review_control")]
158 pub show_hints: bool,
159
160 #[serde(default = "default_transcript_review_control")]
162 pub show_shortcut_guide: bool,
163
164 #[serde(default = "default_transcript_review_control")]
166 pub show_close_button: bool,
167}
168
169impl Default for UiTranscriptReviewConfig {
170 fn default() -> Self {
171 Self {
172 show_hints: default_transcript_review_control(),
173 show_shortcut_guide: default_transcript_review_control(),
174 show_close_button: default_transcript_review_control(),
175 }
176 }
177}
178
179#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
180#[derive(Debug, Clone, Deserialize, Serialize)]
181pub struct UiConfig {
182 #[serde(default = "default_tool_output_mode")]
184 pub tool_output_mode: ToolOutputMode,
185
186 #[serde(default = "default_tool_display_mode")]
189 pub tool_display_mode: ToolDisplayMode,
190
191 #[serde(default = "default_verb_callouts")]
195 pub verb_callouts: bool,
196
197 #[serde(default, alias = "key_bindings")]
200 #[cfg_attr(
201 feature = "schema",
202 schemars(with = "std::collections::BTreeMap<String, Vec<String>>")
203 )]
204 pub keybindings: HashMap<String, Vec<String>>,
205
206 #[serde(default = "default_tool_output_max_lines")]
208 pub tool_output_max_lines: usize,
209
210 #[serde(default = "default_tool_output_spool_bytes")]
212 pub tool_output_spool_bytes: usize,
213
214 #[serde(default)]
216 pub tool_output_spool_dir: Option<String>,
217
218 #[serde(default = "default_allow_tool_ansi")]
220 pub allow_tool_ansi: bool,
221
222 #[serde(default = "default_inline_viewport_rows")]
224 pub inline_viewport_rows: u16,
225
226 #[serde(default = "default_reasoning_display_mode")]
228 pub reasoning_display_mode: ReasoningDisplayMode,
229
230 #[serde(default = "default_reasoning_visible_default")]
232 pub reasoning_visible_default: bool,
233
234 #[serde(default = "default_thinking_display")]
236 pub thinking_display: vtcode_commons::ui_protocol::ThinkingBlockState,
237
238 #[serde(default = "default_communication_style")]
240 pub communication_style: vtcode_commons::ui_protocol::CommunicationStyle,
241
242 #[serde(default = "default_vim_mode")]
244 pub vim_mode: bool,
245
246 #[serde(default = "default_busy_enter_behavior")]
251 pub busy_enter_behavior: BusyEnterBehavior,
252
253 #[serde(default)]
255 pub status_line: StatusLineConfig,
256
257 #[serde(default)]
259 pub terminal_title: TerminalTitleConfig,
260
261 #[serde(default)]
263 pub keyboard_protocol: KeyboardProtocolConfig,
264
265 #[serde(default)]
267 pub layout_mode: LayoutModeOverride,
268
269 #[serde(default)]
271 pub display_mode: UiDisplayMode,
272
273 #[serde(default = "default_show_sidebar")]
275 pub show_sidebar: bool,
276
277 #[serde(default = "default_dim_completed_todos")]
279 pub dim_completed_todos: bool,
280
281 #[serde(default = "default_show_task_panel")]
285 pub show_task_panel: bool,
286
287 #[serde(default = "default_message_block_spacing")]
289 pub message_block_spacing: bool,
290
291 #[serde(default = "default_show_turn_timer")]
293 pub show_turn_timer: bool,
294
295 #[serde(default = "default_show_diagnostics_in_transcript")]
299 pub show_diagnostics_in_transcript: bool,
300
301 #[serde(default = "default_show_message_timestamps")]
303 pub show_message_timestamps: bool,
304
305 #[serde(default = "default_minimum_contrast")]
313 pub minimum_contrast: f32,
314
315 #[serde(default = "default_bold_is_bright")]
319 pub bold_is_bright: bool,
320
321 #[serde(default = "default_safe_colors_only")]
327 pub safe_colors_only: bool,
328
329 #[serde(default = "default_color_scheme_mode")]
334 pub color_scheme_mode: ColorSchemeMode,
335
336 #[serde(default = "default_app_icon")]
343 pub app_icon: AppIconPreference,
344
345 #[serde(default)]
347 pub program_status: UiProgramStatusConfig,
348
349 #[serde(default)]
351 pub notifications: UiNotificationsConfig,
352
353 #[serde(default)]
355 pub fullscreen: UiFullscreenConfig,
356
357 #[serde(default = "default_screen_reader_mode")]
362 pub screen_reader_mode: bool,
363
364 #[serde(default = "default_reduce_motion_mode")]
368 pub reduce_motion_mode: bool,
369
370 #[serde(default = "default_reduce_motion_keep_progress_animation")]
373 pub reduce_motion_keep_progress_animation: bool,
374
375 #[serde(default = "default_hide_header")]
377 pub hide_header: bool,
378
379 #[serde(default = "default_diff_preview_mode")]
382 pub diff_preview_mode: DiffPreviewMode,
383
384 #[serde(default)]
386 pub transcript_review: UiTranscriptReviewConfig,
387}
388
389#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
391#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
392#[serde(rename_all = "snake_case")]
393pub enum ColorSchemeMode {
394 #[default]
396 Auto,
397 Light,
399 Dark,
401 #[serde(other)]
403 Unknown,
404}
405
406fn default_minimum_contrast() -> f32 {
407 crate::constants::ui::THEME_MIN_CONTRAST_RATIO
408}
409
410fn default_bold_is_bright() -> bool {
411 false
412}
413
414fn default_safe_colors_only() -> bool {
415 false
416}
417
418fn default_color_scheme_mode() -> ColorSchemeMode {
419 ColorSchemeMode::Auto
420}
421
422fn default_app_icon() -> AppIconPreference {
423 AppIconPreference::Dynamic
424}
425
426fn default_show_sidebar() -> bool {
427 true
428}
429
430fn default_dim_completed_todos() -> bool {
431 true
432}
433
434fn default_message_block_spacing() -> bool {
435 true
436}
437
438fn default_program_status_enabled() -> bool {
439 true
440}
441
442fn default_show_turn_timer() -> bool {
443 false
444}
445
446fn default_show_diagnostics_in_transcript() -> bool {
447 false
448}
449
450fn default_show_message_timestamps() -> bool {
451 true
452}
453
454fn default_vim_mode() -> bool {
455 false
456}
457
458fn default_busy_enter_behavior() -> BusyEnterBehavior {
459 BusyEnterBehavior::Queue
460}
461
462fn default_notifications_enabled() -> bool {
463 true
464}
465
466fn default_notifications_suppress_when_focused() -> bool {
467 true
468}
469
470fn default_notifications_command_failure() -> bool {
471 false
472}
473
474fn default_notifications_tool_failure() -> bool {
475 false
476}
477
478fn default_notifications_error() -> bool {
479 true
480}
481
482fn default_notifications_completion() -> bool {
483 true
484}
485
486fn default_notifications_completion_success() -> bool {
487 false
488}
489
490fn default_notifications_completion_failure() -> bool {
491 true
492}
493
494fn default_notifications_hitl() -> bool {
495 true
496}
497
498fn default_notifications_policy_approval() -> bool {
499 true
500}
501
502fn default_notifications_request() -> bool {
503 false
504}
505
506fn default_notifications_tool_success() -> bool {
507 false
508}
509
510fn default_notifications_repeat_window_seconds() -> u64 {
511 30
512}
513
514fn default_notifications_max_identical_in_window() -> u32 {
515 1
516}
517
518fn env_bool_var(name: &str) -> Option<bool> {
519 crate::env_helpers::read_env_var(name).and_then(|v| vtcode_commons::utils::parse_bool_env_value(&v))
520}
521
522fn env_u8_var(name: &str) -> Option<u8> {
523 crate::env_helpers::read_env_var(name)
524 .and_then(|value| value.trim().parse::<u8>().ok())
525 .map(clamp_fullscreen_scroll_speed)
526}
527
528fn clamp_fullscreen_scroll_speed(value: u8) -> u8 {
529 value.clamp(1, 20)
530}
531
532fn default_fullscreen_mouse_capture() -> bool {
533 env_bool_var("VTCODE_FULLSCREEN_MOUSE_CAPTURE").unwrap_or(true)
534}
535
536fn default_fullscreen_copy_on_select() -> bool {
537 env_bool_var("VTCODE_FULLSCREEN_COPY_ON_SELECT").unwrap_or(true)
538}
539
540fn default_fullscreen_scroll_speed() -> u8 {
541 env_u8_var("VTCODE_FULLSCREEN_SCROLL_SPEED").unwrap_or(3)
542}
543
544fn default_screen_reader_mode() -> bool {
545 env_bool_var("VTCODE_SCREEN_READER").unwrap_or(false)
546}
547
548fn default_reduce_motion_mode() -> bool {
549 env_bool_var("VTCODE_REDUCE_MOTION")
550 .or_else(reduce_motion_preference)
551 .unwrap_or(false)
552}
553
554fn default_reduce_motion_keep_progress_animation() -> bool {
555 false
556}
557
558fn default_hide_header() -> bool {
559 true
560}
561
562fn default_diff_preview_mode() -> DiffPreviewMode {
563 DiffPreviewMode::Inline
564}
565
566fn default_transcript_review_control() -> bool {
567 true
568}
569
570fn default_show_task_panel() -> bool {
571 false
572}
573
574fn default_ask_questions_enabled() -> bool {
575 true
576}
577
578impl Default for UiConfig {
579 fn default() -> Self {
580 Self {
581 tool_output_mode: default_tool_output_mode(),
582 tool_display_mode: default_tool_display_mode(),
583 verb_callouts: default_verb_callouts(),
584 keybindings: HashMap::new(),
585 tool_output_max_lines: default_tool_output_max_lines(),
586 tool_output_spool_bytes: default_tool_output_spool_bytes(),
587 tool_output_spool_dir: None,
588 allow_tool_ansi: default_allow_tool_ansi(),
589 inline_viewport_rows: default_inline_viewport_rows(),
590 reasoning_display_mode: default_reasoning_display_mode(),
591 reasoning_visible_default: default_reasoning_visible_default(),
592 thinking_display: default_thinking_display(),
593 communication_style: default_communication_style(),
594 vim_mode: default_vim_mode(),
595 busy_enter_behavior: default_busy_enter_behavior(),
596 status_line: StatusLineConfig::default(),
597 terminal_title: TerminalTitleConfig::default(),
598 keyboard_protocol: KeyboardProtocolConfig::default(),
599 layout_mode: LayoutModeOverride::default(),
600 display_mode: UiDisplayMode::default(),
601 show_sidebar: default_show_sidebar(),
602 dim_completed_todos: default_dim_completed_todos(),
603 show_task_panel: default_show_task_panel(),
604 message_block_spacing: default_message_block_spacing(),
605 show_turn_timer: default_show_turn_timer(),
606 show_diagnostics_in_transcript: default_show_diagnostics_in_transcript(),
607 show_message_timestamps: default_show_message_timestamps(),
608 minimum_contrast: default_minimum_contrast(),
610 bold_is_bright: default_bold_is_bright(),
611 safe_colors_only: default_safe_colors_only(),
612 color_scheme_mode: default_color_scheme_mode(),
613 app_icon: default_app_icon(),
614 program_status: UiProgramStatusConfig::default(),
615 notifications: UiNotificationsConfig::default(),
616 fullscreen: UiFullscreenConfig::default(),
617 screen_reader_mode: default_screen_reader_mode(),
618 reduce_motion_mode: default_reduce_motion_mode(),
619 reduce_motion_keep_progress_animation: default_reduce_motion_keep_progress_animation(),
620 hide_header: default_hide_header(),
621 diff_preview_mode: default_diff_preview_mode(),
622 transcript_review: UiTranscriptReviewConfig::default(),
623 }
624 }
625}
626
627#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
629#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
630#[serde(default)]
631pub struct UiProgramStatusConfig {
632 #[serde(default = "default_program_status_enabled")]
634 pub enabled: bool,
635}
636
637impl Default for UiProgramStatusConfig {
638 fn default() -> Self {
639 Self { enabled: default_program_status_enabled() }
640 }
641}
642
643#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
645#[derive(Debug, Clone, Deserialize, Serialize, Default)]
646pub struct ChatConfig {
647 #[serde(default, rename = "askQuestions", alias = "ask_questions")]
649 pub ask_questions: AskQuestionsConfig,
650}
651
652#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
654#[derive(Debug, Clone, Deserialize, Serialize)]
655pub struct AskQuestionsConfig {
656 #[serde(default = "default_ask_questions_enabled")]
658 pub enabled: bool,
659}
660
661impl Default for AskQuestionsConfig {
662 fn default() -> Self {
663 Self { enabled: default_ask_questions_enabled() }
664 }
665}
666
667#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
669#[derive(Debug, Clone, Deserialize, Serialize)]
670pub struct PtyConfig {
671 #[serde(default = "default_pty_enabled")]
673 pub enabled: bool,
674
675 #[serde(default = "default_pty_rows")]
677 pub default_rows: u16,
678
679 #[serde(default = "default_pty_cols")]
681 pub default_cols: u16,
682
683 #[serde(default = "default_max_pty_sessions")]
685 pub max_sessions: usize,
686
687 #[serde(default = "default_pty_timeout")]
693 pub command_timeout_seconds: u64,
694
695 #[serde(default = "default_stdout_tail_lines")]
697 pub stdout_tail_lines: usize,
698
699 #[serde(default = "default_scrollback_lines")]
701 pub scrollback_lines: usize,
702
703 #[serde(default = "default_max_scrollback_bytes")]
705 pub max_scrollback_bytes: usize,
706
707 #[serde(default = "default_large_output_threshold_kb")]
709 pub large_output_threshold_kb: usize,
710
711 #[serde(default)]
713 pub preferred_shell: Option<String>,
714
715 #[serde(default = "default_shell_zsh_fork")]
717 pub shell_zsh_fork: bool,
718
719 #[serde(default)]
721 pub zsh_path: Option<String>,
722}
723
724impl Default for PtyConfig {
725 fn default() -> Self {
726 Self {
727 enabled: default_pty_enabled(),
728 default_rows: default_pty_rows(),
729 default_cols: default_pty_cols(),
730 max_sessions: default_max_pty_sessions(),
731 command_timeout_seconds: default_pty_timeout(),
732 stdout_tail_lines: default_stdout_tail_lines(),
733 scrollback_lines: default_scrollback_lines(),
734 max_scrollback_bytes: default_max_scrollback_bytes(),
735 large_output_threshold_kb: default_large_output_threshold_kb(),
736 preferred_shell: None,
737 shell_zsh_fork: default_shell_zsh_fork(),
738 zsh_path: None,
739 }
740 }
741}
742
743impl PtyConfig {
744 pub fn validate(&self) -> Result<()> {
745 self.zsh_fork_shell_path()?;
746 Ok(())
747 }
748
749 pub fn zsh_fork_shell_path(&self) -> Result<Option<&str>> {
750 if !self.shell_zsh_fork {
751 return Ok(None);
752 }
753
754 let zsh_path = self
755 .zsh_path
756 .as_deref()
757 .map(str::trim)
758 .filter(|path| !path.is_empty())
759 .ok_or_else(|| {
760 anyhow!(
761 "pty.shell_zsh_fork is enabled, but pty.zsh_path is not configured. \
762 Set pty.zsh_path to an absolute path to patched zsh."
763 )
764 })?;
765
766 #[cfg(not(unix))]
767 {
768 let _ = zsh_path;
769 bail!("pty.shell_zsh_fork is only supported on Unix platforms");
770 }
771
772 #[cfg(unix)]
773 {
774 let path = std::path::Path::new(zsh_path);
775 if !path.is_absolute() {
776 bail!("pty.zsh_path '{zsh_path}' must be an absolute path when pty.shell_zsh_fork is enabled");
777 }
778 if !path.exists() {
779 bail!("pty.zsh_path '{zsh_path}' does not exist (required when pty.shell_zsh_fork is enabled)");
780 }
781 if !path.is_file() {
782 bail!("pty.zsh_path '{zsh_path}' is not a file (required when pty.shell_zsh_fork is enabled)");
783 }
784
785 Ok(Some(zsh_path))
786 }
787 }
788}
789
790fn default_pty_enabled() -> bool {
791 true
792}
793
794fn default_pty_rows() -> u16 {
795 24
796}
797
798fn default_pty_cols() -> u16 {
799 80
800}
801
802fn default_max_pty_sessions() -> usize {
803 10
804}
805
806fn default_pty_timeout() -> u64 {
807 300
808}
809
810fn default_shell_zsh_fork() -> bool {
811 false
812}
813
814fn default_stdout_tail_lines() -> usize {
815 crate::constants::defaults::DEFAULT_PTY_STDOUT_TAIL_LINES
816}
817
818fn default_scrollback_lines() -> usize {
819 crate::constants::defaults::DEFAULT_PTY_SCROLLBACK_LINES
820}
821
822fn default_max_scrollback_bytes() -> usize {
823 25_000_000 }
827
828fn default_large_output_threshold_kb() -> usize {
829 5_000 }
831
832fn default_tool_output_mode() -> ToolOutputMode {
833 ToolOutputMode::Compact
834}
835
836fn default_tool_display_mode() -> ToolDisplayMode {
837 ToolDisplayMode::Compact
838}
839
840fn default_verb_callouts() -> bool {
841 true
842}
843
844fn default_tool_output_max_lines() -> usize {
845 30
846}
847
848fn default_tool_output_spool_bytes() -> usize {
849 80_000
850}
851
852fn default_allow_tool_ansi() -> bool {
853 false
854}
855
856fn default_inline_viewport_rows() -> u16 {
857 crate::constants::ui::DEFAULT_INLINE_VIEWPORT_ROWS
858}
859
860fn default_reasoning_display_mode() -> ReasoningDisplayMode {
861 ReasoningDisplayMode::Toggle
862}
863
864fn default_reasoning_visible_default() -> bool {
865 crate::constants::ui::DEFAULT_REASONING_VISIBLE
866}
867
868fn default_thinking_display() -> vtcode_commons::ui_protocol::ThinkingBlockState {
869 vtcode_commons::ui_protocol::ThinkingBlockState::Collapsed
870}
871
872fn default_communication_style() -> vtcode_commons::ui_protocol::CommunicationStyle {
873 vtcode_commons::ui_protocol::CommunicationStyle::Balanced
874}
875
876#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
880#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
881#[serde(rename_all = "snake_case")]
882pub enum KeyboardProtocolMode {
883 #[default]
885 Default,
886 Full,
888 Minimal,
890 Custom,
892}
893
894impl KeyboardProtocolMode {
895 pub fn as_str(&self) -> &'static str {
897 match self {
898 Self::Default => "default",
899 Self::Full => "full",
900 Self::Minimal => "minimal",
901 Self::Custom => "custom",
902 }
903 }
904}
905
906#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
907#[derive(Debug, Clone, Deserialize, Serialize)]
908pub struct KeyboardProtocolConfig {
909 #[serde(default = "default_keyboard_protocol_enabled")]
911 pub enabled: bool,
912
913 #[serde(default = "default_keyboard_protocol_mode")]
915 pub mode: KeyboardProtocolMode,
916
917 #[serde(default = "default_disambiguate_escape_codes")]
919 pub disambiguate_escape_codes: bool,
920
921 #[serde(default = "default_report_event_types")]
923 pub report_event_types: bool,
924
925 #[serde(default = "default_report_alternate_keys")]
927 pub report_alternate_keys: bool,
928
929 #[serde(default = "default_report_all_keys")]
931 pub report_all_keys: bool,
932}
933
934impl Default for KeyboardProtocolConfig {
935 fn default() -> Self {
936 Self {
937 enabled: default_keyboard_protocol_enabled(),
938 mode: default_keyboard_protocol_mode(),
939 disambiguate_escape_codes: default_disambiguate_escape_codes(),
940 report_event_types: default_report_event_types(),
941 report_alternate_keys: default_report_alternate_keys(),
942 report_all_keys: default_report_all_keys(),
943 }
944 }
945}
946
947impl KeyboardProtocolConfig {
948 pub fn validate(&self) -> Result<()> {
949 let _ = self.mode;
951 Ok(())
952 }
953}
954
955fn default_keyboard_protocol_enabled() -> bool {
956 std::env::var("VTCODE_KEYBOARD_PROTOCOL_ENABLED")
957 .ok()
958 .and_then(|v| v.parse().ok())
959 .unwrap_or(true)
960}
961
962fn default_keyboard_protocol_mode() -> KeyboardProtocolMode {
963 std::env::var("VTCODE_KEYBOARD_PROTOCOL_MODE")
964 .ok()
965 .and_then(|v| match v.to_ascii_lowercase().as_str() {
966 "default" => Some(KeyboardProtocolMode::Default),
967 "full" => Some(KeyboardProtocolMode::Full),
968 "minimal" => Some(KeyboardProtocolMode::Minimal),
969 "custom" => Some(KeyboardProtocolMode::Custom),
970 _ => None,
971 })
972 .unwrap_or_default()
973}
974
975fn default_disambiguate_escape_codes() -> bool {
976 true
977}
978
979fn default_report_event_types() -> bool {
980 true
981}
982
983fn default_report_alternate_keys() -> bool {
984 true
985}
986
987fn default_report_all_keys() -> bool {
988 false
989}
990#[cfg(test)]
991mod tests;