1use crate::snippets::{CustomActionConfig, SnippetConfig};
7use crate::themes::Theme;
8use crate::types::{
9 AlertEvent, AlertSoundConfig, BackgroundImageMode, BackgroundMode, CursorShaderConfig,
10 CursorStyle, DividerStyle, DownloadSaveLocation, DroppedFileQuoteStyle, FontRange,
11 ImageScalingMode, InstallPromptState, IntegrationVersions, KeyBinding, LogLevel,
12 ModifierRemapping, OptionKeyMode, PaneBackgroundConfig, PaneTitlePosition, PowerPreference,
13 ProgressBarPosition, ProgressBarStyle, SemanticHistoryEditorMode, SessionLogFormat,
14 ShaderConfig, ShaderInstallPrompt, ShellExitAction, SmartSelectionRule, StartupDirectoryMode,
15 StatusBarPosition, TabBarMode, TabBarPosition, TabStyle, ThinStrokesMode, UnfocusedCursorStyle,
16 UpdateCheckFrequency, VsyncMode, WindowType, default_smart_selection_rules,
17};
18
19use anyhow::Result;
20use regex::Regex;
21use serde::{Deserialize, Serialize};
22use std::collections::HashMap;
23use std::fs;
24use std::path::PathBuf;
25
26pub fn substitute_variables(input: &str) -> String {
36 let escaped_placeholder = "\x00ESC_DOLLAR\x00";
38 let working = input.replace("$${", escaped_placeholder);
39
40 let re = Regex::new(r"\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-((?:[^}\\]|\\.)*))?}")
42 .expect("invalid regex");
43
44 let result = re.replace_all(&working, |caps: ®ex::Captures| {
45 let var_name = &caps[1];
46 match std::env::var(var_name) {
47 Ok(val) => val,
48 Err(_) => {
49 caps.get(2)
51 .map(|m| m.as_str().replace("\\}", "}"))
52 .unwrap_or_else(|| caps[0].to_string())
53 }
54 }
55 });
56
57 result.replace(escaped_placeholder, "${")
59}
60
61fn deserialize_shell_exit_action<'de, D>(deserializer: D) -> Result<ShellExitAction, D::Error>
67where
68 D: serde::Deserializer<'de>,
69{
70 #[derive(Deserialize)]
71 #[serde(untagged)]
72 enum BoolOrAction {
73 Bool(bool),
74 Action(ShellExitAction),
75 }
76
77 match BoolOrAction::deserialize(deserializer)? {
78 BoolOrAction::Bool(true) => Ok(ShellExitAction::Close),
79 BoolOrAction::Bool(false) => Ok(ShellExitAction::Keep),
80 BoolOrAction::Action(action) => Ok(action),
81 }
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct Config {
88 #[serde(default = "crate::defaults::cols")]
93 pub cols: usize,
94
95 #[serde(default = "crate::defaults::rows")]
97 pub rows: usize,
98
99 #[serde(default = "crate::defaults::font_size")]
101 pub font_size: f32,
102
103 #[serde(default = "crate::defaults::font_family")]
105 pub font_family: String,
106
107 #[serde(default)]
109 pub font_family_bold: Option<String>,
110
111 #[serde(default)]
113 pub font_family_italic: Option<String>,
114
115 #[serde(default)]
117 pub font_family_bold_italic: Option<String>,
118
119 #[serde(default)]
123 pub font_ranges: Vec<FontRange>,
124
125 #[serde(default = "crate::defaults::line_spacing")]
127 pub line_spacing: f32,
128
129 #[serde(default = "crate::defaults::char_spacing")]
131 pub char_spacing: f32,
132
133 #[serde(default = "crate::defaults::text_shaping")]
136 pub enable_text_shaping: bool,
137
138 #[serde(default = "crate::defaults::bool_true")]
140 pub enable_ligatures: bool,
141
142 #[serde(default = "crate::defaults::bool_true")]
144 pub enable_kerning: bool,
145
146 #[serde(default = "crate::defaults::bool_true")]
149 pub font_antialias: bool,
150
151 #[serde(default)]
155 pub font_hinting: bool,
156
157 #[serde(default)]
165 pub font_thin_strokes: ThinStrokesMode,
166
167 #[serde(default = "crate::defaults::minimum_contrast")]
175 pub minimum_contrast: f32,
176
177 #[serde(default = "crate::defaults::window_title")]
179 pub window_title: String,
180
181 #[serde(default = "crate::defaults::bool_true")]
184 pub allow_title_change: bool,
185
186 #[serde(default = "crate::defaults::max_fps", alias = "refresh_rate")]
192 pub max_fps: u32,
193
194 #[serde(default)]
201 pub vsync_mode: VsyncMode,
202
203 #[serde(default)]
210 pub power_preference: PowerPreference,
211
212 #[serde(default = "crate::defaults::reduce_flicker")]
215 pub reduce_flicker: bool,
216
217 #[serde(default = "crate::defaults::reduce_flicker_delay_ms")]
221 pub reduce_flicker_delay_ms: u32,
222
223 #[serde(default = "crate::defaults::maximize_throughput")]
227 pub maximize_throughput: bool,
228
229 #[serde(default = "crate::defaults::throughput_render_interval_ms")]
232 pub throughput_render_interval_ms: u32,
233
234 #[serde(default = "crate::defaults::window_padding")]
236 pub window_padding: f32,
237
238 #[serde(default = "crate::defaults::bool_true")]
241 pub hide_window_padding_on_split: bool,
242
243 #[serde(default = "crate::defaults::window_opacity")]
245 pub window_opacity: f32,
246
247 #[serde(default = "crate::defaults::bool_false")]
249 pub window_always_on_top: bool,
250
251 #[serde(default = "crate::defaults::bool_true")]
253 pub window_decorations: bool,
254
255 #[serde(default)]
260 pub window_type: WindowType,
261
262 #[serde(default)]
265 pub target_monitor: Option<usize>,
266
267 #[serde(default)]
271 pub target_space: Option<u32>,
272
273 #[serde(default = "crate::defaults::bool_false")]
276 pub lock_window_size: bool,
277
278 #[serde(default = "crate::defaults::bool_false")]
281 pub show_window_number: bool,
282
283 #[serde(default = "crate::defaults::bool_true")]
287 pub transparency_affects_only_default_background: bool,
288
289 #[serde(default = "crate::defaults::bool_true")]
292 pub keep_text_opaque: bool,
293
294 #[serde(default = "crate::defaults::bool_false")]
297 pub blur_enabled: bool,
298
299 #[serde(default = "crate::defaults::blur_radius")]
302 pub blur_radius: u32,
303
304 #[serde(default)]
306 pub background_image: Option<String>,
307
308 #[serde(default = "crate::defaults::bool_true")]
310 pub background_image_enabled: bool,
311
312 #[serde(default)]
319 pub background_image_mode: BackgroundImageMode,
320
321 #[serde(default = "crate::defaults::background_image_opacity")]
323 pub background_image_opacity: f32,
324
325 #[serde(default)]
330 pub image_scaling_mode: ImageScalingMode,
331
332 #[serde(default = "crate::defaults::bool_true")]
334 pub image_preserve_aspect_ratio: bool,
335
336 #[serde(default)]
338 pub background_mode: BackgroundMode,
339
340 #[serde(default)]
342 pub pane_backgrounds: Vec<crate::config::PaneBackgroundConfig>,
343
344 #[serde(default = "crate::defaults::background_color")]
348 pub background_color: [u8; 3],
349
350 #[serde(default)]
355 pub download_save_location: DownloadSaveLocation,
356
357 #[serde(default, skip_serializing_if = "Option::is_none")]
359 pub last_download_directory: Option<String>,
360
361 #[serde(default)]
365 pub custom_shader: Option<String>,
366
367 #[serde(default = "crate::defaults::bool_true")]
369 pub custom_shader_enabled: bool,
370
371 #[serde(default = "crate::defaults::bool_true")]
374 pub custom_shader_animation: bool,
375
376 #[serde(default = "crate::defaults::custom_shader_speed")]
378 pub custom_shader_animation_speed: f32,
379
380 #[serde(default = "crate::defaults::text_opacity")]
383 pub custom_shader_text_opacity: f32,
384
385 #[serde(default = "crate::defaults::bool_false")]
389 pub custom_shader_full_content: bool,
390
391 #[serde(default = "crate::defaults::custom_shader_brightness")]
394 pub custom_shader_brightness: f32,
395
396 #[serde(default)]
399 pub custom_shader_channel0: Option<String>,
400
401 #[serde(default)]
403 pub custom_shader_channel1: Option<String>,
404
405 #[serde(default)]
407 pub custom_shader_channel2: Option<String>,
408
409 #[serde(default)]
411 pub custom_shader_channel3: Option<String>,
412
413 #[serde(default)]
418 pub custom_shader_cubemap: Option<String>,
419
420 #[serde(default = "crate::defaults::cubemap_enabled")]
423 pub custom_shader_cubemap_enabled: bool,
424
425 #[serde(default = "crate::defaults::use_background_as_channel0")]
430 pub custom_shader_use_background_as_channel0: bool,
431
432 #[serde(default)]
438 pub cursor_shader: Option<String>,
439
440 #[serde(default = "crate::defaults::bool_false")]
442 pub cursor_shader_enabled: bool,
443
444 #[serde(default = "crate::defaults::bool_true")]
446 pub cursor_shader_animation: bool,
447
448 #[serde(default = "crate::defaults::custom_shader_speed")]
450 pub cursor_shader_animation_speed: f32,
451
452 #[serde(default = "crate::defaults::cursor_shader_color")]
455 pub cursor_shader_color: [u8; 3],
456
457 #[serde(default = "crate::defaults::cursor_trail_duration")]
460 pub cursor_shader_trail_duration: f32,
461
462 #[serde(default = "crate::defaults::cursor_glow_radius")]
465 pub cursor_shader_glow_radius: f32,
466
467 #[serde(default = "crate::defaults::cursor_glow_intensity")]
470 pub cursor_shader_glow_intensity: f32,
471
472 #[serde(default = "crate::defaults::bool_false")]
476 pub cursor_shader_hides_cursor: bool,
477
478 #[serde(default = "crate::defaults::cursor_shader_disable_in_alt_screen")]
481 pub cursor_shader_disable_in_alt_screen: bool,
482
483 #[serde(default)]
491 pub left_option_key_mode: OptionKeyMode,
492
493 #[serde(default)]
499 pub right_option_key_mode: OptionKeyMode,
500
501 #[serde(default)]
504 pub modifier_remapping: ModifierRemapping,
505
506 #[serde(default = "crate::defaults::bool_false")]
511 pub use_physical_keys: bool,
512
513 #[serde(default = "crate::defaults::bool_true")]
518 pub auto_copy_selection: bool,
519
520 #[serde(
523 default = "crate::defaults::bool_false",
524 alias = "strip_trailing_newline_on_copy"
525 )]
526 pub copy_trailing_newline: bool,
527
528 #[serde(default = "crate::defaults::bool_true")]
530 pub middle_click_paste: bool,
531
532 #[serde(default = "crate::defaults::paste_delay_ms")]
535 pub paste_delay_ms: u64,
536
537 #[serde(default)]
543 pub dropped_file_quote_style: DroppedFileQuoteStyle,
544
545 #[serde(default = "crate::defaults::scroll_speed")]
550 pub mouse_scroll_speed: f32,
551
552 #[serde(default = "crate::defaults::double_click_threshold")]
554 pub mouse_double_click_threshold: u64,
555
556 #[serde(default = "crate::defaults::triple_click_threshold")]
558 pub mouse_triple_click_threshold: u64,
559
560 #[serde(default = "crate::defaults::bool_true")]
564 pub option_click_moves_cursor: bool,
565
566 #[serde(default = "crate::defaults::bool_false")]
569 pub focus_follows_mouse: bool,
570
571 #[serde(default = "crate::defaults::bool_true")]
574 pub report_horizontal_scroll: bool,
575
576 #[serde(default = "crate::defaults::word_characters")]
583 pub word_characters: String,
584
585 #[serde(default = "crate::defaults::smart_selection_enabled")]
589 pub smart_selection_enabled: bool,
590
591 #[serde(default = "crate::types::default_smart_selection_rules")]
595 pub smart_selection_rules: Vec<SmartSelectionRule>,
596
597 #[serde(default = "crate::defaults::bool_true")]
604 pub copy_mode_enabled: bool,
605
606 #[serde(default = "crate::defaults::bool_true")]
610 pub copy_mode_auto_exit_on_yank: bool,
611
612 #[serde(default = "crate::defaults::bool_true")]
616 pub copy_mode_show_status: bool,
617
618 #[serde(default = "crate::defaults::scrollback", alias = "scrollback_size")]
623 pub scrollback_lines: usize,
624
625 #[serde(default = "crate::defaults::unicode_version")]
632 pub unicode_version: par_term_emu_core_rust::UnicodeVersion,
633
634 #[serde(default = "crate::defaults::ambiguous_width")]
638 pub ambiguous_width: par_term_emu_core_rust::AmbiguousWidth,
639
640 #[serde(default = "crate::defaults::normalization_form")]
648 pub normalization_form: par_term_emu_core_rust::NormalizationForm,
649
650 #[serde(default = "crate::defaults::bool_false")]
652 pub cursor_blink: bool,
653
654 #[serde(default = "crate::defaults::cursor_blink_interval")]
656 pub cursor_blink_interval: u64,
657
658 #[serde(default)]
660 pub cursor_style: CursorStyle,
661
662 #[serde(default = "crate::defaults::cursor_color")]
664 pub cursor_color: [u8; 3],
665
666 #[serde(default)]
670 pub cursor_text_color: Option<[u8; 3]>,
671
672 #[serde(default = "crate::defaults::bool_false")]
675 pub lock_cursor_visibility: bool,
676
677 #[serde(default = "crate::defaults::bool_false")]
680 pub lock_cursor_style: bool,
681
682 #[serde(default = "crate::defaults::bool_false")]
685 pub lock_cursor_blink: bool,
686
687 #[serde(default = "crate::defaults::bool_false")]
692 pub cursor_guide_enabled: bool,
693
694 #[serde(default = "crate::defaults::cursor_guide_color")]
696 pub cursor_guide_color: [u8; 4],
697
698 #[serde(default = "crate::defaults::bool_false")]
700 pub cursor_shadow_enabled: bool,
701
702 #[serde(default = "crate::defaults::cursor_shadow_color")]
704 pub cursor_shadow_color: [u8; 4],
705
706 #[serde(default = "crate::defaults::cursor_shadow_offset")]
708 pub cursor_shadow_offset: [f32; 2],
709
710 #[serde(default = "crate::defaults::cursor_shadow_blur")]
712 pub cursor_shadow_blur: f32,
713
714 #[serde(default = "crate::defaults::cursor_boost")]
717 pub cursor_boost: f32,
718
719 #[serde(default = "crate::defaults::cursor_boost_color")]
721 pub cursor_boost_color: [u8; 3],
722
723 #[serde(default)]
728 pub unfocused_cursor_style: UnfocusedCursorStyle,
729
730 #[serde(default = "crate::defaults::scrollbar_autohide_delay")]
735 pub scrollbar_autohide_delay: u64,
736
737 #[serde(default = "crate::defaults::theme")]
742 pub theme: String,
743
744 #[serde(default)]
746 pub auto_dark_mode: bool,
747
748 #[serde(default = "crate::defaults::light_theme")]
750 pub light_theme: String,
751
752 #[serde(default = "crate::defaults::dark_theme")]
754 pub dark_theme: String,
755
756 #[serde(default = "crate::defaults::screenshot_format")]
761 pub screenshot_format: String,
762
763 #[serde(
770 default,
771 deserialize_with = "deserialize_shell_exit_action",
772 alias = "exit_on_shell_exit",
773 alias = "close_on_shell_exit"
774 )]
775 pub shell_exit_action: ShellExitAction,
776
777 #[serde(default)]
779 pub custom_shell: Option<String>,
780
781 #[serde(default)]
783 pub shell_args: Option<Vec<String>>,
784
785 #[serde(default)]
788 pub working_directory: Option<String>,
789
790 #[serde(default)]
795 pub startup_directory_mode: StartupDirectoryMode,
796
797 #[serde(default)]
800 pub startup_directory: Option<String>,
801
802 #[serde(default)]
805 pub last_working_directory: Option<String>,
806
807 #[serde(default)]
809 pub shell_env: Option<std::collections::HashMap<String, String>>,
810
811 #[serde(default = "crate::defaults::login_shell")]
815 pub login_shell: bool,
816
817 #[serde(default = "crate::defaults::initial_text")]
820 pub initial_text: String,
821
822 #[serde(default = "crate::defaults::initial_text_delay_ms")]
824 pub initial_text_delay_ms: u64,
825
826 #[serde(default = "crate::defaults::initial_text_send_newline")]
828 pub initial_text_send_newline: bool,
829
830 #[serde(default = "crate::defaults::answerback_string")]
836 pub answerback_string: String,
837
838 #[serde(default = "crate::defaults::bool_false")]
843 pub prompt_on_quit: bool,
844
845 #[serde(default = "crate::defaults::bool_false")]
849 pub confirm_close_running_jobs: bool,
850
851 #[serde(default = "crate::defaults::jobs_to_ignore")]
856 pub jobs_to_ignore: Vec<String>,
857
858 #[serde(default = "crate::defaults::bool_true")]
864 pub semantic_history_enabled: bool,
865
866 #[serde(default)]
872 pub semantic_history_editor_mode: SemanticHistoryEditorMode,
873
874 #[serde(default = "crate::defaults::semantic_history_editor")]
884 pub semantic_history_editor: String,
885
886 #[serde(default = "crate::defaults::link_highlight_color")]
888 pub link_highlight_color: [u8; 3],
889
890 #[serde(default = "crate::defaults::bool_true")]
892 pub link_highlight_underline: bool,
893
894 #[serde(default)]
896 pub link_underline_style: crate::types::LinkUnderlineStyle,
897
898 #[serde(default)]
909 pub link_handler_command: String,
910
911 #[serde(default = "crate::defaults::scrollbar_position")]
916 pub scrollbar_position: String,
917
918 #[serde(default = "crate::defaults::scrollbar_width")]
920 pub scrollbar_width: f32,
921
922 #[serde(default = "crate::defaults::scrollbar_thumb_color")]
924 pub scrollbar_thumb_color: [f32; 4],
925
926 #[serde(default = "crate::defaults::scrollbar_track_color")]
928 pub scrollbar_track_color: [f32; 4],
929
930 #[serde(default = "crate::defaults::bool_true")]
932 pub scrollbar_command_marks: bool,
933
934 #[serde(default = "crate::defaults::bool_false")]
936 pub scrollbar_mark_tooltips: bool,
937
938 #[serde(default = "crate::defaults::bool_false")]
943 pub command_separator_enabled: bool,
944
945 #[serde(default = "crate::defaults::command_separator_thickness")]
947 pub command_separator_thickness: f32,
948
949 #[serde(default = "crate::defaults::command_separator_opacity")]
951 pub command_separator_opacity: f32,
952
953 #[serde(default = "crate::defaults::bool_true")]
955 pub command_separator_exit_color: bool,
956
957 #[serde(default = "crate::defaults::command_separator_color")]
959 pub command_separator_color: [u8; 3],
960
961 #[serde(
966 default = "crate::defaults::clipboard_max_sync_events",
967 alias = "max_clipboard_sync_events"
968 )]
969 pub clipboard_max_sync_events: usize,
970
971 #[serde(
973 default = "crate::defaults::clipboard_max_event_bytes",
974 alias = "max_clipboard_event_bytes"
975 )]
976 pub clipboard_max_event_bytes: usize,
977
978 #[serde(default = "crate::defaults::command_history_max_entries")]
983 pub command_history_max_entries: usize,
984
985 #[serde(default = "crate::defaults::bool_false", alias = "bell_desktop")]
990 pub notification_bell_desktop: bool,
991
992 #[serde(default = "crate::defaults::bell_sound", alias = "bell_sound")]
994 pub notification_bell_sound: u8,
995
996 #[serde(default = "crate::defaults::bool_true", alias = "bell_visual")]
998 pub notification_bell_visual: bool,
999
1000 #[serde(
1002 default = "crate::defaults::bool_false",
1003 alias = "activity_notifications"
1004 )]
1005 pub notification_activity_enabled: bool,
1006
1007 #[serde(
1009 default = "crate::defaults::activity_threshold",
1010 alias = "activity_threshold"
1011 )]
1012 pub notification_activity_threshold: u64,
1013
1014 #[serde(default = "crate::defaults::bool_false")]
1016 pub anti_idle_enabled: bool,
1017
1018 #[serde(default = "crate::defaults::anti_idle_seconds")]
1020 pub anti_idle_seconds: u64,
1021
1022 #[serde(default = "crate::defaults::anti_idle_code")]
1024 pub anti_idle_code: u8,
1025
1026 #[serde(
1028 default = "crate::defaults::bool_false",
1029 alias = "silence_notifications"
1030 )]
1031 pub notification_silence_enabled: bool,
1032
1033 #[serde(
1035 default = "crate::defaults::silence_threshold",
1036 alias = "silence_threshold"
1037 )]
1038 pub notification_silence_threshold: u64,
1039
1040 #[serde(default = "crate::defaults::bool_false", alias = "session_ended")]
1042 pub notification_session_ended: bool,
1043
1044 #[serde(default = "crate::defaults::bool_true")]
1046 pub suppress_notifications_when_focused: bool,
1047
1048 #[serde(
1050 default = "crate::defaults::notification_max_buffer",
1051 alias = "max_notifications"
1052 )]
1053 pub notification_max_buffer: usize,
1054
1055 #[serde(default)]
1058 pub alert_sounds: HashMap<AlertEvent, AlertSoundConfig>,
1059
1060 #[serde(default = "crate::defaults::bool_false")]
1065 pub enable_mdns_discovery: bool,
1066
1067 #[serde(default = "crate::defaults::mdns_timeout")]
1069 pub mdns_scan_timeout_secs: u32,
1070
1071 #[serde(default = "crate::defaults::bool_true")]
1073 pub ssh_auto_profile_switch: bool,
1074
1075 #[serde(default = "crate::defaults::bool_true")]
1077 pub ssh_revert_profile_on_disconnect: bool,
1078
1079 #[serde(default)]
1085 pub tab_style: TabStyle,
1086
1087 #[serde(default = "crate::defaults::light_tab_style")]
1089 pub light_tab_style: TabStyle,
1090
1091 #[serde(default = "crate::defaults::dark_tab_style")]
1093 pub dark_tab_style: TabStyle,
1094
1095 #[serde(default)]
1097 pub tab_bar_mode: TabBarMode,
1098
1099 #[serde(default = "crate::defaults::tab_bar_height")]
1101 pub tab_bar_height: f32,
1102
1103 #[serde(default)]
1105 pub tab_bar_position: TabBarPosition,
1106
1107 #[serde(default = "crate::defaults::tab_bar_width")]
1109 pub tab_bar_width: f32,
1110
1111 #[serde(default = "crate::defaults::bool_true")]
1113 pub tab_show_close_button: bool,
1114
1115 #[serde(default = "crate::defaults::bool_false")]
1117 pub tab_show_index: bool,
1118
1119 #[serde(default = "crate::defaults::bool_true")]
1121 pub tab_inherit_cwd: bool,
1122
1123 #[serde(default = "crate::defaults::zero")]
1125 pub max_tabs: usize,
1126
1127 #[serde(default = "crate::defaults::bool_false")]
1130 pub show_profile_drawer_button: bool,
1131
1132 #[serde(default = "crate::defaults::bool_false")]
1135 pub new_tab_shortcut_shows_profiles: bool,
1136
1137 #[serde(default = "crate::defaults::tab_bar_background")]
1142 pub tab_bar_background: [u8; 3],
1143
1144 #[serde(default = "crate::defaults::tab_active_background")]
1146 pub tab_active_background: [u8; 3],
1147
1148 #[serde(default = "crate::defaults::tab_inactive_background")]
1150 pub tab_inactive_background: [u8; 3],
1151
1152 #[serde(default = "crate::defaults::tab_hover_background")]
1154 pub tab_hover_background: [u8; 3],
1155
1156 #[serde(default = "crate::defaults::tab_active_text")]
1158 pub tab_active_text: [u8; 3],
1159
1160 #[serde(default = "crate::defaults::tab_inactive_text")]
1162 pub tab_inactive_text: [u8; 3],
1163
1164 #[serde(default = "crate::defaults::tab_active_indicator")]
1166 pub tab_active_indicator: [u8; 3],
1167
1168 #[serde(default = "crate::defaults::tab_activity_indicator")]
1170 pub tab_activity_indicator: [u8; 3],
1171
1172 #[serde(default = "crate::defaults::tab_bell_indicator")]
1174 pub tab_bell_indicator: [u8; 3],
1175
1176 #[serde(default = "crate::defaults::tab_close_button")]
1178 pub tab_close_button: [u8; 3],
1179
1180 #[serde(default = "crate::defaults::tab_close_button_hover")]
1182 pub tab_close_button_hover: [u8; 3],
1183
1184 #[serde(default = "crate::defaults::bool_true")]
1187 pub dim_inactive_tabs: bool,
1188
1189 #[serde(default = "crate::defaults::inactive_tab_opacity")]
1193 pub inactive_tab_opacity: f32,
1194
1195 #[serde(default = "crate::defaults::tab_min_width")]
1198 pub tab_min_width: f32,
1199
1200 #[serde(default = "crate::defaults::tab_stretch_to_fill")]
1203 pub tab_stretch_to_fill: bool,
1204
1205 #[serde(default = "crate::defaults::tab_html_titles")]
1208 pub tab_html_titles: bool,
1209
1210 #[serde(default = "crate::defaults::tab_border_color")]
1213 pub tab_border_color: [u8; 3],
1214
1215 #[serde(default = "crate::defaults::tab_border_width")]
1217 pub tab_border_width: f32,
1218
1219 #[serde(default = "crate::defaults::bool_false")]
1223 pub tab_inactive_outline_only: bool,
1224
1225 #[serde(default = "crate::defaults::pane_divider_width")]
1230 pub pane_divider_width: Option<f32>,
1231
1232 #[serde(default = "crate::defaults::pane_divider_hit_width")]
1235 pub pane_divider_hit_width: f32,
1236
1237 #[serde(default = "crate::defaults::pane_padding")]
1239 pub pane_padding: f32,
1240
1241 #[serde(default = "crate::defaults::pane_min_size")]
1244 pub pane_min_size: usize,
1245
1246 #[serde(default = "crate::defaults::pane_background_opacity")]
1249 pub pane_background_opacity: f32,
1250
1251 #[serde(default = "crate::defaults::pane_divider_color")]
1253 pub pane_divider_color: [u8; 3],
1254
1255 #[serde(default = "crate::defaults::pane_divider_hover_color")]
1257 pub pane_divider_hover_color: [u8; 3],
1258
1259 #[serde(default = "crate::defaults::bool_false")]
1261 pub dim_inactive_panes: bool,
1262
1263 #[serde(default = "crate::defaults::inactive_pane_opacity")]
1265 pub inactive_pane_opacity: f32,
1266
1267 #[serde(default = "crate::defaults::bool_false")]
1269 pub show_pane_titles: bool,
1270
1271 #[serde(default = "crate::defaults::pane_title_height")]
1273 pub pane_title_height: f32,
1274
1275 #[serde(default)]
1277 pub pane_title_position: PaneTitlePosition,
1278
1279 #[serde(default = "crate::defaults::pane_title_color")]
1281 pub pane_title_color: [u8; 3],
1282
1283 #[serde(default = "crate::defaults::pane_title_bg_color")]
1285 pub pane_title_bg_color: [u8; 3],
1286
1287 #[serde(default)]
1289 pub pane_title_font: String,
1290
1291 #[serde(default)]
1293 pub pane_divider_style: DividerStyle,
1294
1295 #[serde(default = "crate::defaults::max_panes")]
1297 pub max_panes: usize,
1298
1299 #[serde(default = "crate::defaults::bool_true")]
1301 pub pane_focus_indicator: bool,
1302
1303 #[serde(default = "crate::defaults::pane_focus_color")]
1305 pub pane_focus_color: [u8; 3],
1306
1307 #[serde(default = "crate::defaults::pane_focus_width")]
1309 pub pane_focus_width: f32,
1310
1311 #[serde(default = "crate::defaults::bool_false")]
1316 pub tmux_enabled: bool,
1317
1318 #[serde(default = "crate::defaults::tmux_path")]
1320 pub tmux_path: String,
1321
1322 #[serde(default = "crate::defaults::tmux_default_session")]
1324 pub tmux_default_session: Option<String>,
1325
1326 #[serde(default = "crate::defaults::bool_false")]
1328 pub tmux_auto_attach: bool,
1329
1330 #[serde(default = "crate::defaults::tmux_auto_attach_session")]
1332 pub tmux_auto_attach_session: Option<String>,
1333
1334 #[serde(default = "crate::defaults::bool_true")]
1337 pub tmux_clipboard_sync: bool,
1338
1339 #[serde(default)]
1343 pub tmux_profile: Option<String>,
1344
1345 #[serde(default = "crate::defaults::bool_false")]
1348 pub tmux_show_status_bar: bool,
1349
1350 #[serde(default = "crate::defaults::tmux_status_bar_refresh_ms")]
1355 pub tmux_status_bar_refresh_ms: u64,
1356
1357 #[serde(default = "crate::defaults::tmux_prefix_key")]
1362 pub tmux_prefix_key: String,
1363
1364 #[serde(default = "crate::defaults::bool_false")]
1369 pub tmux_status_bar_use_native_format: bool,
1370
1371 #[serde(default = "crate::defaults::tmux_status_bar_left")]
1383 pub tmux_status_bar_left: String,
1384
1385 #[serde(default = "crate::defaults::tmux_status_bar_right")]
1391 pub tmux_status_bar_right: String,
1392
1393 #[serde(default = "crate::defaults::bool_true")]
1399 pub pause_shaders_on_blur: bool,
1400
1401 #[serde(default = "crate::defaults::bool_false")]
1404 pub pause_refresh_on_blur: bool,
1405
1406 #[serde(default = "crate::defaults::unfocused_fps")]
1409 pub unfocused_fps: u32,
1410
1411 #[serde(default = "crate::defaults::bool_false")]
1417 pub shader_hot_reload: bool,
1418
1419 #[serde(default = "crate::defaults::shader_hot_reload_delay")]
1422 pub shader_hot_reload_delay: u64,
1423
1424 #[serde(default)]
1430 pub shader_configs: HashMap<String, ShaderConfig>,
1431
1432 #[serde(default)]
1434 pub cursor_shader_configs: HashMap<String, CursorShaderConfig>,
1435
1436 #[serde(default = "crate::defaults::keybindings")]
1442 pub keybindings: Vec<KeyBinding>,
1443
1444 #[serde(default)]
1452 pub shader_install_prompt: ShaderInstallPrompt,
1453
1454 #[serde(default)]
1456 pub shell_integration_state: InstallPromptState,
1457
1458 #[serde(default)]
1460 pub integration_versions: IntegrationVersions,
1461
1462 #[serde(default = "crate::defaults::update_check_frequency")]
1471 pub update_check_frequency: UpdateCheckFrequency,
1472
1473 #[serde(default)]
1475 pub last_update_check: Option<String>,
1476
1477 #[serde(default)]
1479 pub skipped_version: Option<String>,
1480
1481 #[serde(default)]
1483 pub last_notified_version: Option<String>,
1484
1485 #[serde(default, skip_serializing_if = "Option::is_none")]
1490 pub auto_restore_arrangement: Option<String>,
1491
1492 #[serde(default = "crate::defaults::bool_false")]
1494 pub restore_session: bool,
1495
1496 #[serde(default = "crate::defaults::session_undo_timeout_secs")]
1498 pub session_undo_timeout_secs: u32,
1499
1500 #[serde(default = "crate::defaults::session_undo_max_entries")]
1502 pub session_undo_max_entries: usize,
1503
1504 #[serde(default = "crate::defaults::session_undo_preserve_shell")]
1507 pub session_undo_preserve_shell: bool,
1508
1509 #[serde(default = "crate::defaults::search_highlight_color")]
1514 pub search_highlight_color: [u8; 4],
1515
1516 #[serde(default = "crate::defaults::search_current_highlight_color")]
1518 pub search_current_highlight_color: [u8; 4],
1519
1520 #[serde(default = "crate::defaults::bool_false")]
1522 pub search_case_sensitive: bool,
1523
1524 #[serde(default = "crate::defaults::bool_false")]
1526 pub search_regex: bool,
1527
1528 #[serde(default = "crate::defaults::bool_true")]
1530 pub search_wrap_around: bool,
1531
1532 #[serde(default = "crate::defaults::bool_false")]
1538 pub auto_log_sessions: bool,
1539
1540 #[serde(default)]
1545 pub session_log_format: SessionLogFormat,
1546
1547 #[serde(default = "crate::defaults::session_log_directory")]
1550 pub session_log_directory: String,
1551
1552 #[serde(default = "crate::defaults::bool_true")]
1555 pub archive_on_close: bool,
1556
1557 #[serde(default)]
1564 pub log_level: LogLevel,
1565
1566 #[serde(default = "crate::defaults::bool_false")]
1571 pub badge_enabled: bool,
1572
1573 #[serde(default = "crate::defaults::badge_format")]
1576 pub badge_format: String,
1577
1578 #[serde(default = "crate::defaults::badge_color")]
1580 pub badge_color: [u8; 3],
1581
1582 #[serde(default = "crate::defaults::badge_color_alpha")]
1584 pub badge_color_alpha: f32,
1585
1586 #[serde(default = "crate::defaults::badge_font")]
1588 pub badge_font: String,
1589
1590 #[serde(default = "crate::defaults::bool_true")]
1592 pub badge_font_bold: bool,
1593
1594 #[serde(default = "crate::defaults::badge_top_margin")]
1596 pub badge_top_margin: f32,
1597
1598 #[serde(default = "crate::defaults::badge_right_margin")]
1600 pub badge_right_margin: f32,
1601
1602 #[serde(default = "crate::defaults::badge_max_width")]
1604 pub badge_max_width: f32,
1605
1606 #[serde(default = "crate::defaults::badge_max_height")]
1608 pub badge_max_height: f32,
1609
1610 #[serde(default = "crate::defaults::bool_false")]
1615 pub status_bar_enabled: bool,
1616
1617 #[serde(default)]
1619 pub status_bar_position: StatusBarPosition,
1620
1621 #[serde(default = "crate::defaults::status_bar_height")]
1623 pub status_bar_height: f32,
1624
1625 #[serde(default = "crate::defaults::status_bar_bg_color")]
1627 pub status_bar_bg_color: [u8; 3],
1628
1629 #[serde(default = "crate::defaults::status_bar_bg_alpha")]
1631 pub status_bar_bg_alpha: f32,
1632
1633 #[serde(default = "crate::defaults::status_bar_fg_color")]
1635 pub status_bar_fg_color: [u8; 3],
1636
1637 #[serde(default)]
1639 pub status_bar_font: String,
1640
1641 #[serde(default = "crate::defaults::status_bar_font_size")]
1643 pub status_bar_font_size: f32,
1644
1645 #[serde(default = "crate::defaults::status_bar_separator")]
1647 pub status_bar_separator: String,
1648
1649 #[serde(default = "crate::defaults::bool_true")]
1651 pub status_bar_auto_hide_fullscreen: bool,
1652
1653 #[serde(default = "crate::defaults::bool_false")]
1655 pub status_bar_auto_hide_mouse_inactive: bool,
1656
1657 #[serde(default = "crate::defaults::status_bar_mouse_inactive_timeout")]
1659 pub status_bar_mouse_inactive_timeout: f32,
1660
1661 #[serde(default = "crate::defaults::status_bar_system_poll_interval")]
1663 pub status_bar_system_poll_interval: f32,
1664
1665 #[serde(default = "crate::defaults::status_bar_git_poll_interval")]
1667 pub status_bar_git_poll_interval: f32,
1668
1669 #[serde(default = "crate::defaults::status_bar_time_format")]
1671 pub status_bar_time_format: String,
1672
1673 #[serde(default = "crate::defaults::bool_true")]
1675 pub status_bar_git_show_status: bool,
1676
1677 #[serde(default = "crate::status_bar::default_widgets")]
1679 pub status_bar_widgets: Vec<crate::status_bar::StatusBarWidgetConfig>,
1680
1681 #[serde(default = "crate::defaults::bool_true")]
1687 pub progress_bar_enabled: bool,
1688
1689 #[serde(default)]
1693 pub progress_bar_style: ProgressBarStyle,
1694
1695 #[serde(default)]
1699 pub progress_bar_position: ProgressBarPosition,
1700
1701 #[serde(default = "crate::defaults::progress_bar_height")]
1703 pub progress_bar_height: f32,
1704
1705 #[serde(default = "crate::defaults::progress_bar_opacity")]
1707 pub progress_bar_opacity: f32,
1708
1709 #[serde(default = "crate::defaults::progress_bar_normal_color")]
1711 pub progress_bar_normal_color: [u8; 3],
1712
1713 #[serde(default = "crate::defaults::progress_bar_warning_color")]
1715 pub progress_bar_warning_color: [u8; 3],
1716
1717 #[serde(default = "crate::defaults::progress_bar_error_color")]
1719 pub progress_bar_error_color: [u8; 3],
1720
1721 #[serde(default = "crate::defaults::progress_bar_indeterminate_color")]
1723 pub progress_bar_indeterminate_color: [u8; 3],
1724
1725 #[serde(default)]
1730 pub triggers: Vec<crate::automation::TriggerConfig>,
1731
1732 #[serde(default)]
1734 pub coprocesses: Vec<crate::automation::CoprocessDefConfig>,
1735
1736 #[serde(default)]
1738 pub scripts: Vec<crate::scripting::ScriptConfig>,
1739
1740 #[serde(default)]
1745 pub snippets: Vec<SnippetConfig>,
1746
1747 #[serde(default)]
1749 pub actions: Vec<CustomActionConfig>,
1750
1751 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1757 pub collapsed_settings_sections: Vec<String>,
1758
1759 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1764 pub dynamic_profile_sources: Vec<crate::profile::DynamicProfileSource>,
1765
1766 #[serde(default = "crate::defaults::ai_inspector_enabled")]
1771 pub ai_inspector_enabled: bool,
1772
1773 #[serde(default = "crate::defaults::ai_inspector_open_on_startup")]
1775 pub ai_inspector_open_on_startup: bool,
1776
1777 #[serde(default = "crate::defaults::ai_inspector_width")]
1779 pub ai_inspector_width: f32,
1780
1781 #[serde(default = "crate::defaults::ai_inspector_default_scope")]
1783 pub ai_inspector_default_scope: String,
1784
1785 #[serde(default = "crate::defaults::ai_inspector_view_mode")]
1787 pub ai_inspector_view_mode: String,
1788
1789 #[serde(default = "crate::defaults::ai_inspector_live_update")]
1791 pub ai_inspector_live_update: bool,
1792
1793 #[serde(default = "crate::defaults::ai_inspector_show_zones")]
1795 pub ai_inspector_show_zones: bool,
1796
1797 #[serde(default = "crate::defaults::ai_inspector_agent")]
1799 pub ai_inspector_agent: String,
1800
1801 #[serde(default = "crate::defaults::ai_inspector_auto_launch")]
1803 pub ai_inspector_auto_launch: bool,
1804
1805 #[serde(default = "crate::defaults::ai_inspector_auto_context")]
1807 pub ai_inspector_auto_context: bool,
1808
1809 #[serde(default = "crate::defaults::ai_inspector_context_max_lines")]
1811 pub ai_inspector_context_max_lines: usize,
1812
1813 #[serde(default = "crate::defaults::ai_inspector_auto_approve")]
1815 pub ai_inspector_auto_approve: bool,
1816
1817 #[serde(default = "crate::defaults::ai_inspector_agent_terminal_access")]
1819 pub ai_inspector_agent_terminal_access: bool,
1820}
1821
1822impl Default for Config {
1823 fn default() -> Self {
1824 Self {
1825 cols: crate::defaults::cols(),
1826 rows: crate::defaults::rows(),
1827 font_size: crate::defaults::font_size(),
1828 font_family: crate::defaults::font_family(),
1829 font_family_bold: None,
1830 font_family_italic: None,
1831 font_family_bold_italic: None,
1832 font_ranges: Vec::new(),
1833 line_spacing: crate::defaults::line_spacing(),
1834 char_spacing: crate::defaults::char_spacing(),
1835 enable_text_shaping: crate::defaults::text_shaping(),
1836 enable_ligatures: crate::defaults::bool_true(),
1837 enable_kerning: crate::defaults::bool_true(),
1838 font_antialias: crate::defaults::bool_true(),
1839 font_hinting: false,
1840 font_thin_strokes: ThinStrokesMode::default(),
1841 minimum_contrast: crate::defaults::minimum_contrast(),
1842 copy_mode_enabled: crate::defaults::bool_true(),
1843 copy_mode_auto_exit_on_yank: crate::defaults::bool_true(),
1844 copy_mode_show_status: crate::defaults::bool_true(),
1845 scrollback_lines: crate::defaults::scrollback(),
1846 unicode_version: crate::defaults::unicode_version(),
1847 ambiguous_width: crate::defaults::ambiguous_width(),
1848 normalization_form: crate::defaults::normalization_form(),
1849 cursor_blink: crate::defaults::bool_false(),
1850 cursor_blink_interval: crate::defaults::cursor_blink_interval(),
1851 cursor_style: CursorStyle::default(),
1852 cursor_color: crate::defaults::cursor_color(),
1853 cursor_text_color: None,
1854 lock_cursor_visibility: crate::defaults::bool_false(),
1855 lock_cursor_style: crate::defaults::bool_false(),
1856 lock_cursor_blink: crate::defaults::bool_false(),
1857 cursor_guide_enabled: crate::defaults::bool_false(),
1858 cursor_guide_color: crate::defaults::cursor_guide_color(),
1859 cursor_shadow_enabled: crate::defaults::bool_false(),
1860 cursor_shadow_color: crate::defaults::cursor_shadow_color(),
1861 cursor_shadow_offset: crate::defaults::cursor_shadow_offset(),
1862 cursor_shadow_blur: crate::defaults::cursor_shadow_blur(),
1863 cursor_boost: crate::defaults::cursor_boost(),
1864 cursor_boost_color: crate::defaults::cursor_boost_color(),
1865 unfocused_cursor_style: UnfocusedCursorStyle::default(),
1866 scrollbar_autohide_delay: crate::defaults::scrollbar_autohide_delay(),
1867 window_title: crate::defaults::window_title(),
1868 allow_title_change: crate::defaults::bool_true(),
1869 theme: crate::defaults::theme(),
1870 auto_dark_mode: false,
1871 light_theme: crate::defaults::light_theme(),
1872 dark_theme: crate::defaults::dark_theme(),
1873 left_option_key_mode: OptionKeyMode::default(),
1874 right_option_key_mode: OptionKeyMode::default(),
1875 modifier_remapping: ModifierRemapping::default(),
1876 use_physical_keys: crate::defaults::bool_false(),
1877 auto_copy_selection: crate::defaults::bool_true(),
1878 copy_trailing_newline: crate::defaults::bool_false(),
1879 middle_click_paste: crate::defaults::bool_true(),
1880 paste_delay_ms: crate::defaults::paste_delay_ms(),
1881 dropped_file_quote_style: DroppedFileQuoteStyle::default(),
1882 mouse_scroll_speed: crate::defaults::scroll_speed(),
1883 mouse_double_click_threshold: crate::defaults::double_click_threshold(),
1884 mouse_triple_click_threshold: crate::defaults::triple_click_threshold(),
1885 option_click_moves_cursor: crate::defaults::bool_true(),
1886 focus_follows_mouse: crate::defaults::bool_false(),
1887 report_horizontal_scroll: crate::defaults::bool_true(),
1888 word_characters: crate::defaults::word_characters(),
1889 smart_selection_enabled: crate::defaults::smart_selection_enabled(),
1890 smart_selection_rules: default_smart_selection_rules(),
1891 screenshot_format: crate::defaults::screenshot_format(),
1892 max_fps: crate::defaults::max_fps(),
1893 vsync_mode: VsyncMode::default(),
1894 power_preference: PowerPreference::default(),
1895 reduce_flicker: crate::defaults::reduce_flicker(),
1896 reduce_flicker_delay_ms: crate::defaults::reduce_flicker_delay_ms(),
1897 maximize_throughput: crate::defaults::maximize_throughput(),
1898 throughput_render_interval_ms: crate::defaults::throughput_render_interval_ms(),
1899 window_padding: crate::defaults::window_padding(),
1900 hide_window_padding_on_split: crate::defaults::bool_true(),
1901 window_opacity: crate::defaults::window_opacity(),
1902 window_always_on_top: crate::defaults::bool_false(),
1903 window_decorations: crate::defaults::bool_true(),
1904 window_type: WindowType::default(),
1905 target_monitor: None,
1906 target_space: None,
1907 lock_window_size: crate::defaults::bool_false(),
1908 show_window_number: crate::defaults::bool_false(),
1909 transparency_affects_only_default_background: crate::defaults::bool_true(),
1910 keep_text_opaque: crate::defaults::bool_true(),
1911 blur_enabled: crate::defaults::bool_false(),
1912 blur_radius: crate::defaults::blur_radius(),
1913 background_image: None,
1914 background_image_enabled: crate::defaults::bool_true(),
1915 background_image_mode: BackgroundImageMode::default(),
1916 background_image_opacity: crate::defaults::background_image_opacity(),
1917 image_scaling_mode: ImageScalingMode::default(),
1918 image_preserve_aspect_ratio: crate::defaults::bool_true(),
1919 background_mode: BackgroundMode::default(),
1920 pane_backgrounds: Vec::new(),
1921 background_color: crate::defaults::background_color(),
1922 download_save_location: DownloadSaveLocation::default(),
1923 last_download_directory: None,
1924 custom_shader: None,
1925 custom_shader_enabled: crate::defaults::bool_true(),
1926 custom_shader_animation: crate::defaults::bool_true(),
1927 custom_shader_animation_speed: crate::defaults::custom_shader_speed(),
1928 custom_shader_text_opacity: crate::defaults::text_opacity(),
1929 custom_shader_full_content: crate::defaults::bool_false(),
1930 custom_shader_brightness: crate::defaults::custom_shader_brightness(),
1931 custom_shader_channel0: None,
1932 custom_shader_channel1: None,
1933 custom_shader_channel2: None,
1934 custom_shader_channel3: None,
1935 custom_shader_cubemap: None,
1936 custom_shader_cubemap_enabled: crate::defaults::cubemap_enabled(),
1937 custom_shader_use_background_as_channel0: crate::defaults::use_background_as_channel0(),
1938 cursor_shader: None,
1939 cursor_shader_enabled: crate::defaults::bool_false(),
1940 cursor_shader_animation: crate::defaults::bool_true(),
1941 cursor_shader_animation_speed: crate::defaults::custom_shader_speed(),
1942 cursor_shader_color: crate::defaults::cursor_shader_color(),
1943 cursor_shader_trail_duration: crate::defaults::cursor_trail_duration(),
1944 cursor_shader_glow_radius: crate::defaults::cursor_glow_radius(),
1945 cursor_shader_glow_intensity: crate::defaults::cursor_glow_intensity(),
1946 cursor_shader_hides_cursor: crate::defaults::bool_false(),
1947 cursor_shader_disable_in_alt_screen:
1948 crate::defaults::cursor_shader_disable_in_alt_screen(),
1949 shell_exit_action: ShellExitAction::default(),
1950 custom_shell: None,
1951 shell_args: None,
1952 working_directory: None,
1953 startup_directory_mode: StartupDirectoryMode::default(),
1954 startup_directory: None,
1955 last_working_directory: None,
1956 shell_env: None,
1957 login_shell: crate::defaults::login_shell(),
1958 initial_text: crate::defaults::initial_text(),
1959 initial_text_delay_ms: crate::defaults::initial_text_delay_ms(),
1960 initial_text_send_newline: crate::defaults::initial_text_send_newline(),
1961 answerback_string: crate::defaults::answerback_string(),
1962 prompt_on_quit: crate::defaults::bool_false(),
1963 confirm_close_running_jobs: crate::defaults::bool_false(),
1964 jobs_to_ignore: crate::defaults::jobs_to_ignore(),
1965 semantic_history_enabled: crate::defaults::bool_true(),
1966 semantic_history_editor_mode: SemanticHistoryEditorMode::default(),
1967 semantic_history_editor: crate::defaults::semantic_history_editor(),
1968 link_highlight_color: crate::defaults::link_highlight_color(),
1969 link_highlight_underline: crate::defaults::bool_true(),
1970 link_underline_style: crate::types::LinkUnderlineStyle::default(),
1971 link_handler_command: String::new(),
1972 scrollbar_position: crate::defaults::scrollbar_position(),
1973 scrollbar_width: crate::defaults::scrollbar_width(),
1974 scrollbar_thumb_color: crate::defaults::scrollbar_thumb_color(),
1975 scrollbar_track_color: crate::defaults::scrollbar_track_color(),
1976 scrollbar_command_marks: crate::defaults::bool_true(),
1977 scrollbar_mark_tooltips: crate::defaults::bool_false(),
1978 command_separator_enabled: crate::defaults::bool_false(),
1979 command_separator_thickness: crate::defaults::command_separator_thickness(),
1980 command_separator_opacity: crate::defaults::command_separator_opacity(),
1981 command_separator_exit_color: crate::defaults::bool_true(),
1982 command_separator_color: crate::defaults::command_separator_color(),
1983 clipboard_max_sync_events: crate::defaults::clipboard_max_sync_events(),
1984 clipboard_max_event_bytes: crate::defaults::clipboard_max_event_bytes(),
1985 command_history_max_entries: crate::defaults::command_history_max_entries(),
1986 notification_bell_desktop: crate::defaults::bool_false(),
1987 notification_bell_sound: crate::defaults::bell_sound(),
1988 notification_bell_visual: crate::defaults::bool_true(),
1989 notification_activity_enabled: crate::defaults::bool_false(),
1990 notification_activity_threshold: crate::defaults::activity_threshold(),
1991 anti_idle_enabled: crate::defaults::bool_false(),
1992 anti_idle_seconds: crate::defaults::anti_idle_seconds(),
1993 anti_idle_code: crate::defaults::anti_idle_code(),
1994 notification_silence_enabled: crate::defaults::bool_false(),
1995 notification_silence_threshold: crate::defaults::silence_threshold(),
1996 notification_session_ended: crate::defaults::bool_false(),
1997 suppress_notifications_when_focused: crate::defaults::bool_true(),
1998 notification_max_buffer: crate::defaults::notification_max_buffer(),
1999 alert_sounds: HashMap::new(),
2000 enable_mdns_discovery: crate::defaults::bool_false(),
2001 mdns_scan_timeout_secs: crate::defaults::mdns_timeout(),
2002 ssh_auto_profile_switch: crate::defaults::bool_true(),
2003 ssh_revert_profile_on_disconnect: crate::defaults::bool_true(),
2004 tab_style: TabStyle::default(),
2005 light_tab_style: crate::defaults::light_tab_style(),
2006 dark_tab_style: crate::defaults::dark_tab_style(),
2007 tab_bar_mode: TabBarMode::default(),
2008 tab_bar_height: crate::defaults::tab_bar_height(),
2009 tab_bar_position: TabBarPosition::default(),
2010 tab_bar_width: crate::defaults::tab_bar_width(),
2011 tab_show_close_button: crate::defaults::bool_true(),
2012 tab_show_index: crate::defaults::bool_false(),
2013 tab_inherit_cwd: crate::defaults::bool_true(),
2014 max_tabs: crate::defaults::zero(),
2015 show_profile_drawer_button: crate::defaults::bool_false(),
2016 new_tab_shortcut_shows_profiles: crate::defaults::bool_false(),
2017 tab_bar_background: crate::defaults::tab_bar_background(),
2018 tab_active_background: crate::defaults::tab_active_background(),
2019 tab_inactive_background: crate::defaults::tab_inactive_background(),
2020 tab_hover_background: crate::defaults::tab_hover_background(),
2021 tab_active_text: crate::defaults::tab_active_text(),
2022 tab_inactive_text: crate::defaults::tab_inactive_text(),
2023 tab_active_indicator: crate::defaults::tab_active_indicator(),
2024 tab_activity_indicator: crate::defaults::tab_activity_indicator(),
2025 tab_bell_indicator: crate::defaults::tab_bell_indicator(),
2026 tab_close_button: crate::defaults::tab_close_button(),
2027 tab_close_button_hover: crate::defaults::tab_close_button_hover(),
2028 dim_inactive_tabs: crate::defaults::bool_true(),
2029 inactive_tab_opacity: crate::defaults::inactive_tab_opacity(),
2030 tab_min_width: crate::defaults::tab_min_width(),
2031 tab_stretch_to_fill: crate::defaults::tab_stretch_to_fill(),
2032 tab_html_titles: crate::defaults::tab_html_titles(),
2033 tab_border_color: crate::defaults::tab_border_color(),
2034 tab_border_width: crate::defaults::tab_border_width(),
2035 tab_inactive_outline_only: crate::defaults::bool_false(),
2036 pane_divider_width: crate::defaults::pane_divider_width(),
2038 pane_divider_hit_width: crate::defaults::pane_divider_hit_width(),
2039 pane_padding: crate::defaults::pane_padding(),
2040 pane_min_size: crate::defaults::pane_min_size(),
2041 pane_background_opacity: crate::defaults::pane_background_opacity(),
2042 pane_divider_color: crate::defaults::pane_divider_color(),
2043 pane_divider_hover_color: crate::defaults::pane_divider_hover_color(),
2044 dim_inactive_panes: crate::defaults::bool_false(),
2045 inactive_pane_opacity: crate::defaults::inactive_pane_opacity(),
2046 show_pane_titles: crate::defaults::bool_false(),
2047 pane_title_height: crate::defaults::pane_title_height(),
2048 pane_title_position: PaneTitlePosition::default(),
2049 pane_title_color: crate::defaults::pane_title_color(),
2050 pane_title_bg_color: crate::defaults::pane_title_bg_color(),
2051 pane_title_font: String::new(),
2052 pane_divider_style: DividerStyle::default(),
2053 max_panes: crate::defaults::max_panes(),
2054 pane_focus_indicator: crate::defaults::bool_true(),
2055 pane_focus_color: crate::defaults::pane_focus_color(),
2056 pane_focus_width: crate::defaults::pane_focus_width(),
2057 tmux_enabled: crate::defaults::bool_false(),
2058 tmux_path: crate::defaults::tmux_path(),
2059 tmux_default_session: crate::defaults::tmux_default_session(),
2060 tmux_auto_attach: crate::defaults::bool_false(),
2061 tmux_auto_attach_session: crate::defaults::tmux_auto_attach_session(),
2062 tmux_clipboard_sync: crate::defaults::bool_true(),
2063 tmux_profile: None,
2064 tmux_show_status_bar: crate::defaults::bool_false(),
2065 tmux_status_bar_refresh_ms: crate::defaults::tmux_status_bar_refresh_ms(),
2066 tmux_prefix_key: crate::defaults::tmux_prefix_key(),
2067 tmux_status_bar_use_native_format: crate::defaults::bool_false(),
2068 tmux_status_bar_left: crate::defaults::tmux_status_bar_left(),
2069 tmux_status_bar_right: crate::defaults::tmux_status_bar_right(),
2070 pause_shaders_on_blur: crate::defaults::bool_true(),
2071 pause_refresh_on_blur: crate::defaults::bool_false(),
2072 unfocused_fps: crate::defaults::unfocused_fps(),
2073 shader_hot_reload: crate::defaults::bool_false(),
2074 shader_hot_reload_delay: crate::defaults::shader_hot_reload_delay(),
2075 shader_configs: HashMap::new(),
2076 cursor_shader_configs: HashMap::new(),
2077 keybindings: crate::defaults::keybindings(),
2078 shader_install_prompt: ShaderInstallPrompt::default(),
2079 shell_integration_state: InstallPromptState::default(),
2080 integration_versions: IntegrationVersions::default(),
2081 update_check_frequency: crate::defaults::update_check_frequency(),
2082 last_update_check: None,
2083 skipped_version: None,
2084 last_notified_version: None,
2085 auto_restore_arrangement: None,
2086 restore_session: crate::defaults::bool_false(),
2087 session_undo_timeout_secs: crate::defaults::session_undo_timeout_secs(),
2088 session_undo_max_entries: crate::defaults::session_undo_max_entries(),
2089 session_undo_preserve_shell: crate::defaults::session_undo_preserve_shell(),
2090 search_highlight_color: crate::defaults::search_highlight_color(),
2091 search_current_highlight_color: crate::defaults::search_current_highlight_color(),
2092 search_case_sensitive: crate::defaults::bool_false(),
2093 search_regex: crate::defaults::bool_false(),
2094 search_wrap_around: crate::defaults::bool_true(),
2095 auto_log_sessions: crate::defaults::bool_false(),
2097 session_log_format: SessionLogFormat::default(),
2098 session_log_directory: crate::defaults::session_log_directory(),
2099 archive_on_close: crate::defaults::bool_true(),
2100 log_level: LogLevel::default(),
2102 badge_enabled: crate::defaults::bool_false(),
2104 badge_format: crate::defaults::badge_format(),
2105 badge_color: crate::defaults::badge_color(),
2106 badge_color_alpha: crate::defaults::badge_color_alpha(),
2107 badge_font: crate::defaults::badge_font(),
2108 badge_font_bold: crate::defaults::bool_true(),
2109 badge_top_margin: crate::defaults::badge_top_margin(),
2110 badge_right_margin: crate::defaults::badge_right_margin(),
2111 badge_max_width: crate::defaults::badge_max_width(),
2112 badge_max_height: crate::defaults::badge_max_height(),
2113 status_bar_enabled: crate::defaults::bool_false(),
2115 status_bar_position: StatusBarPosition::default(),
2116 status_bar_height: crate::defaults::status_bar_height(),
2117 status_bar_bg_color: crate::defaults::status_bar_bg_color(),
2118 status_bar_bg_alpha: crate::defaults::status_bar_bg_alpha(),
2119 status_bar_fg_color: crate::defaults::status_bar_fg_color(),
2120 status_bar_font: String::new(),
2121 status_bar_font_size: crate::defaults::status_bar_font_size(),
2122 status_bar_separator: crate::defaults::status_bar_separator(),
2123 status_bar_auto_hide_fullscreen: crate::defaults::bool_true(),
2124 status_bar_auto_hide_mouse_inactive: crate::defaults::bool_false(),
2125 status_bar_mouse_inactive_timeout: crate::defaults::status_bar_mouse_inactive_timeout(),
2126 status_bar_system_poll_interval: crate::defaults::status_bar_system_poll_interval(),
2127 status_bar_git_poll_interval: crate::defaults::status_bar_git_poll_interval(),
2128 status_bar_time_format: crate::defaults::status_bar_time_format(),
2129 status_bar_git_show_status: crate::defaults::bool_true(),
2130 status_bar_widgets: crate::status_bar::default_widgets(),
2131 progress_bar_enabled: crate::defaults::bool_true(),
2133 progress_bar_style: ProgressBarStyle::default(),
2134 progress_bar_position: ProgressBarPosition::default(),
2135 progress_bar_height: crate::defaults::progress_bar_height(),
2136 progress_bar_opacity: crate::defaults::progress_bar_opacity(),
2137 progress_bar_normal_color: crate::defaults::progress_bar_normal_color(),
2138 progress_bar_warning_color: crate::defaults::progress_bar_warning_color(),
2139 progress_bar_error_color: crate::defaults::progress_bar_error_color(),
2140 progress_bar_indeterminate_color: crate::defaults::progress_bar_indeterminate_color(),
2141 triggers: Vec::new(),
2142 coprocesses: Vec::new(),
2143 scripts: Vec::new(),
2144 snippets: Vec::new(),
2145 actions: Vec::new(),
2146 collapsed_settings_sections: Vec::new(),
2147 dynamic_profile_sources: Vec::new(),
2148 ai_inspector_enabled: crate::defaults::ai_inspector_enabled(),
2150 ai_inspector_open_on_startup: crate::defaults::ai_inspector_open_on_startup(),
2151 ai_inspector_width: crate::defaults::ai_inspector_width(),
2152 ai_inspector_default_scope: crate::defaults::ai_inspector_default_scope(),
2153 ai_inspector_view_mode: crate::defaults::ai_inspector_view_mode(),
2154 ai_inspector_live_update: crate::defaults::ai_inspector_live_update(),
2155 ai_inspector_show_zones: crate::defaults::ai_inspector_show_zones(),
2156 ai_inspector_agent: crate::defaults::ai_inspector_agent(),
2157 ai_inspector_auto_launch: crate::defaults::ai_inspector_auto_launch(),
2158 ai_inspector_auto_context: crate::defaults::ai_inspector_auto_context(),
2159 ai_inspector_context_max_lines: crate::defaults::ai_inspector_context_max_lines(),
2160 ai_inspector_auto_approve: crate::defaults::ai_inspector_auto_approve(),
2161 ai_inspector_agent_terminal_access: crate::defaults::ai_inspector_agent_terminal_access(
2162 ),
2163 }
2164 }
2165}
2166
2167impl Config {
2168 pub fn apply_tab_style(&mut self) {
2173 match self.tab_style {
2174 TabStyle::Dark => {
2175 self.tab_bar_background = crate::defaults::tab_bar_background();
2177 self.tab_active_background = crate::defaults::tab_active_background();
2178 self.tab_inactive_background = crate::defaults::tab_inactive_background();
2179 self.tab_hover_background = crate::defaults::tab_hover_background();
2180 self.tab_active_text = crate::defaults::tab_active_text();
2181 self.tab_inactive_text = crate::defaults::tab_inactive_text();
2182 self.tab_active_indicator = crate::defaults::tab_active_indicator();
2183 self.tab_border_color = crate::defaults::tab_border_color();
2184 self.tab_border_width = crate::defaults::tab_border_width();
2185 self.tab_inactive_outline_only = false;
2186 self.tab_bar_height = crate::defaults::tab_bar_height();
2187 }
2188 TabStyle::Light => {
2189 self.tab_bar_background = [235, 235, 235];
2190 self.tab_active_background = [255, 255, 255];
2191 self.tab_inactive_background = [225, 225, 225];
2192 self.tab_hover_background = [240, 240, 240];
2193 self.tab_active_text = [30, 30, 30];
2194 self.tab_inactive_text = [100, 100, 100];
2195 self.tab_active_indicator = [50, 120, 220];
2196 self.tab_border_color = [200, 200, 200];
2197 self.tab_border_width = 1.0;
2198 self.tab_inactive_outline_only = false;
2199 self.tab_bar_height = crate::defaults::tab_bar_height();
2200 }
2201 TabStyle::Compact => {
2202 self.tab_bar_background = [35, 35, 35];
2204 self.tab_active_background = [55, 55, 55];
2205 self.tab_inactive_background = [35, 35, 35];
2206 self.tab_hover_background = [45, 45, 45];
2207 self.tab_active_text = [240, 240, 240];
2208 self.tab_inactive_text = [160, 160, 160];
2209 self.tab_active_indicator = [80, 140, 240];
2210 self.tab_border_color = [60, 60, 60];
2211 self.tab_border_width = 0.5;
2212 self.tab_inactive_outline_only = false;
2213 self.tab_bar_height = 22.0;
2214 }
2215 TabStyle::Minimal => {
2216 self.tab_bar_background = [30, 30, 30];
2218 self.tab_active_background = [30, 30, 30];
2219 self.tab_inactive_background = [30, 30, 30];
2220 self.tab_hover_background = [40, 40, 40];
2221 self.tab_active_text = [255, 255, 255];
2222 self.tab_inactive_text = [120, 120, 120];
2223 self.tab_active_indicator = [100, 150, 255];
2224 self.tab_border_color = [30, 30, 30]; self.tab_border_width = 0.0;
2226 self.tab_inactive_outline_only = false;
2227 self.tab_bar_height = 26.0;
2228 }
2229 TabStyle::HighContrast => {
2230 self.tab_bar_background = [0, 0, 0];
2232 self.tab_active_background = [255, 255, 255];
2233 self.tab_inactive_background = [30, 30, 30];
2234 self.tab_hover_background = [60, 60, 60];
2235 self.tab_active_text = [0, 0, 0];
2236 self.tab_inactive_text = [255, 255, 255];
2237 self.tab_active_indicator = [255, 255, 0];
2238 self.tab_border_color = [255, 255, 255];
2239 self.tab_border_width = 2.0;
2240 self.tab_inactive_outline_only = false;
2241 self.tab_bar_height = 30.0;
2242 }
2243 TabStyle::Automatic => {
2244 }
2246 }
2247 }
2248
2249 pub fn load() -> Result<Self> {
2251 let config_path = Self::config_path();
2252 log::info!("Config path: {:?}", config_path);
2253
2254 if config_path.exists() {
2255 log::info!("Loading existing config from {:?}", config_path);
2256 let contents = fs::read_to_string(&config_path)?;
2257 let contents = substitute_variables(&contents);
2258 let mut config: Config = serde_yaml::from_str(&contents)?;
2259
2260 config.merge_default_keybindings();
2262
2263 config.merge_default_widgets();
2265
2266 config.generate_snippet_action_keybindings();
2268
2269 config.load_last_working_directory();
2271
2272 Ok(config)
2273 } else {
2274 log::info!(
2275 "Config file not found, creating default at {:?}",
2276 config_path
2277 );
2278 let mut config = Self::default();
2280 config.generate_snippet_action_keybindings();
2282 if let Err(e) = config.save() {
2283 log::error!("Failed to save default config: {}", e);
2284 return Err(e);
2285 }
2286
2287 config.load_last_working_directory();
2289
2290 log::info!("Default config created successfully");
2291 Ok(config)
2292 }
2293 }
2294
2295 fn merge_default_keybindings(&mut self) {
2299 let default_keybindings = crate::defaults::keybindings();
2300
2301 let existing_actions: std::collections::HashSet<String> = self
2303 .keybindings
2304 .iter()
2305 .map(|kb| kb.action.clone())
2306 .collect();
2307
2308 let mut added_count = 0;
2310 for default_kb in default_keybindings {
2311 if !existing_actions.contains(&default_kb.action) {
2312 log::info!(
2313 "Adding new default keybinding: {} -> {}",
2314 default_kb.key,
2315 default_kb.action
2316 );
2317 self.keybindings.push(default_kb);
2318 added_count += 1;
2319 }
2320 }
2321
2322 if added_count > 0 {
2323 log::info!(
2324 "Merged {} new default keybinding(s) into user config",
2325 added_count
2326 );
2327 }
2328 }
2329
2330 fn merge_default_widgets(&mut self) {
2334 let default_widgets = crate::status_bar::default_widgets();
2335
2336 let existing_ids: std::collections::HashSet<crate::status_bar::WidgetId> = self
2337 .status_bar_widgets
2338 .iter()
2339 .map(|w| w.id.clone())
2340 .collect();
2341
2342 let mut added_count = 0;
2343 for default_widget in default_widgets {
2344 if !existing_ids.contains(&default_widget.id) {
2345 log::info!(
2346 "Adding new default status bar widget: {:?}",
2347 default_widget.id
2348 );
2349 self.status_bar_widgets.push(default_widget);
2350 added_count += 1;
2351 }
2352 }
2353
2354 if added_count > 0 {
2355 log::info!(
2356 "Merged {} new default status bar widget(s) into user config",
2357 added_count
2358 );
2359 }
2360 }
2361
2362 pub fn generate_snippet_action_keybindings(&mut self) {
2368 use crate::config::KeyBinding;
2369
2370 let mut seen_actions = std::collections::HashSet::new();
2372 let mut added_count = 0;
2373 let mut updated_count = 0;
2374
2375 for snippet in &self.snippets {
2377 if let Some(key) = &snippet.keybinding {
2378 let action = format!("snippet:{}", snippet.id);
2379 seen_actions.insert(action.clone());
2380
2381 if !key.is_empty() && snippet.enabled && snippet.keybinding_enabled {
2382 if let Some(existing) =
2384 self.keybindings.iter_mut().find(|kb| kb.action == action)
2385 {
2386 if existing.key != *key {
2388 log::info!(
2389 "Updating keybinding for snippet '{}': {} -> {} (was: {})",
2390 snippet.title,
2391 key,
2392 action,
2393 existing.key
2394 );
2395 existing.key = key.clone();
2396 updated_count += 1;
2397 }
2398 } else {
2399 log::info!(
2401 "Adding keybinding for snippet '{}': {} -> {} (enabled={}, keybinding_enabled={})",
2402 snippet.title,
2403 key,
2404 action,
2405 snippet.enabled,
2406 snippet.keybinding_enabled
2407 );
2408 self.keybindings.push(KeyBinding {
2409 key: key.clone(),
2410 action,
2411 });
2412 added_count += 1;
2413 }
2414 } else if !key.is_empty() {
2415 log::info!(
2416 "Skipping keybinding for snippet '{}': {} (enabled={}, keybinding_enabled={})",
2417 snippet.title,
2418 key,
2419 snippet.enabled,
2420 snippet.keybinding_enabled
2421 );
2422 }
2423 }
2424 }
2425
2426 for action_config in &self.actions {
2428 if let Some(key) = action_config.keybinding() {
2429 let action = format!("action:{}", action_config.id());
2430 seen_actions.insert(action.clone());
2431
2432 if !key.is_empty() && action_config.keybinding_enabled() {
2433 if let Some(existing) =
2435 self.keybindings.iter_mut().find(|kb| kb.action == action)
2436 {
2437 if existing.key != key {
2439 log::info!(
2440 "Updating keybinding for action '{}': {} -> {} (was: {})",
2441 action_config.title(),
2442 key,
2443 action,
2444 existing.key
2445 );
2446 existing.key = key.to_string();
2447 updated_count += 1;
2448 }
2449 } else {
2450 log::info!(
2452 "Adding keybinding for action '{}': {} -> {} (keybinding_enabled={})",
2453 action_config.title(),
2454 key,
2455 action,
2456 action_config.keybinding_enabled()
2457 );
2458 self.keybindings.push(KeyBinding {
2459 key: key.to_string(),
2460 action,
2461 });
2462 added_count += 1;
2463 }
2464 } else if !key.is_empty() {
2465 log::info!(
2466 "Skipping keybinding for action '{}': {} (keybinding_enabled={})",
2467 action_config.title(),
2468 key,
2469 action_config.keybinding_enabled()
2470 );
2471 }
2472 }
2473 }
2474
2475 let original_len = self.keybindings.len();
2477 self.keybindings.retain(|kb| {
2478 if !kb.action.starts_with("snippet:") && !kb.action.starts_with("action:") {
2480 return true;
2481 }
2482 seen_actions.contains(&kb.action)
2484 });
2485 let removed_count = original_len - self.keybindings.len();
2486
2487 if added_count > 0 || updated_count > 0 || removed_count > 0 {
2488 log::info!(
2489 "Snippet/Action keybindings: {} added, {} updated, {} removed",
2490 added_count,
2491 updated_count,
2492 removed_count
2493 );
2494 }
2495 }
2496
2497 pub fn save(&self) -> Result<()> {
2499 let config_path = Self::config_path();
2500
2501 if let Some(parent) = config_path.parent() {
2503 fs::create_dir_all(parent)?;
2504 }
2505
2506 let yaml = serde_yaml::to_string(self)?;
2507 fs::write(&config_path, yaml)?;
2508
2509 Ok(())
2510 }
2511
2512 pub fn config_path() -> PathBuf {
2514 #[cfg(target_os = "windows")]
2515 {
2516 if let Some(config_dir) = dirs::config_dir() {
2517 config_dir.join("par-term").join("config.yaml")
2518 } else {
2519 PathBuf::from("config.yaml")
2520 }
2521 }
2522 #[cfg(not(target_os = "windows"))]
2523 {
2524 if let Some(home_dir) = dirs::home_dir() {
2526 home_dir
2527 .join(".config")
2528 .join("par-term")
2529 .join("config.yaml")
2530 } else {
2531 PathBuf::from("config.yaml")
2533 }
2534 }
2535 }
2536
2537 pub fn resolve_tmux_path(&self) -> String {
2542 let configured = &self.tmux_path;
2543
2544 if configured.starts_with('/') && std::path::Path::new(configured).exists() {
2546 return configured.clone();
2547 }
2548
2549 if configured != "tmux" {
2551 return configured.clone();
2552 }
2553
2554 if let Ok(path_env) = std::env::var("PATH") {
2556 let separator = if cfg!(windows) { ';' } else { ':' };
2557 let executable = if cfg!(windows) { "tmux.exe" } else { "tmux" };
2558
2559 for dir in path_env.split(separator) {
2560 let candidate = std::path::Path::new(dir).join(executable);
2561 if candidate.exists() {
2562 return candidate.to_string_lossy().to_string();
2563 }
2564 }
2565 }
2566
2567 #[cfg(target_os = "macos")]
2569 {
2570 let macos_paths = [
2571 "/opt/homebrew/bin/tmux", "/usr/local/bin/tmux", ];
2574 for path in macos_paths {
2575 if std::path::Path::new(path).exists() {
2576 return path.to_string();
2577 }
2578 }
2579 }
2580
2581 #[cfg(target_os = "linux")]
2582 {
2583 let linux_paths = [
2584 "/usr/bin/tmux", "/usr/local/bin/tmux", "/snap/bin/tmux", ];
2588 for path in linux_paths {
2589 if std::path::Path::new(path).exists() {
2590 return path.to_string();
2591 }
2592 }
2593 }
2594
2595 configured.clone()
2597 }
2598
2599 pub fn logs_dir(&self) -> PathBuf {
2602 let path = if self.session_log_directory.starts_with("~/") {
2603 if let Some(home) = dirs::home_dir() {
2604 home.join(&self.session_log_directory[2..])
2605 } else {
2606 PathBuf::from(&self.session_log_directory)
2607 }
2608 } else {
2609 PathBuf::from(&self.session_log_directory)
2610 };
2611
2612 if !path.exists()
2614 && let Err(e) = std::fs::create_dir_all(&path)
2615 {
2616 log::warn!("Failed to create logs directory {:?}: {}", path, e);
2617 }
2618
2619 path
2620 }
2621
2622 pub fn shaders_dir() -> PathBuf {
2624 #[cfg(target_os = "windows")]
2625 {
2626 if let Some(config_dir) = dirs::config_dir() {
2627 config_dir.join("par-term").join("shaders")
2628 } else {
2629 PathBuf::from("shaders")
2630 }
2631 }
2632 #[cfg(not(target_os = "windows"))]
2633 {
2634 if let Some(home_dir) = dirs::home_dir() {
2635 home_dir.join(".config").join("par-term").join("shaders")
2636 } else {
2637 PathBuf::from("shaders")
2638 }
2639 }
2640 }
2641
2642 pub fn shader_path(shader_name: &str) -> PathBuf {
2646 let path = PathBuf::from(shader_name);
2647 if path.is_absolute() {
2648 path
2649 } else {
2650 Self::shaders_dir().join(shader_name)
2651 }
2652 }
2653
2654 pub fn resolve_texture_path(path: &str) -> PathBuf {
2658 if path.starts_with("~/")
2659 && let Some(home) = dirs::home_dir()
2660 {
2661 return home.join(&path[2..]);
2662 }
2663 let path_buf = PathBuf::from(path);
2664 if path_buf.is_absolute() {
2665 path_buf
2666 } else {
2667 Self::shaders_dir().join(path)
2668 }
2669 }
2670
2671 #[allow(dead_code)]
2674 pub fn shader_channel_paths(&self) -> [Option<PathBuf>; 4] {
2675 [
2676 self.custom_shader_channel0
2677 .as_ref()
2678 .map(|p| Self::resolve_texture_path(p)),
2679 self.custom_shader_channel1
2680 .as_ref()
2681 .map(|p| Self::resolve_texture_path(p)),
2682 self.custom_shader_channel2
2683 .as_ref()
2684 .map(|p| Self::resolve_texture_path(p)),
2685 self.custom_shader_channel3
2686 .as_ref()
2687 .map(|p| Self::resolve_texture_path(p)),
2688 ]
2689 }
2690
2691 #[allow(dead_code)]
2694 pub fn shader_cubemap_path(&self) -> Option<PathBuf> {
2695 self.custom_shader_cubemap
2696 .as_ref()
2697 .map(|p| Self::resolve_texture_path(p))
2698 }
2699
2700 #[allow(dead_code)]
2702 pub fn with_title(mut self, title: impl Into<String>) -> Self {
2703 self.window_title = title.into();
2704 self
2705 }
2706
2707 pub fn load_theme(&self) -> Theme {
2709 Theme::by_name(&self.theme).unwrap_or_default()
2710 }
2711
2712 pub fn apply_system_theme(&mut self, is_dark: bool) -> bool {
2715 if !self.auto_dark_mode {
2716 return false;
2717 }
2718 let new_theme = if is_dark {
2719 &self.dark_theme
2720 } else {
2721 &self.light_theme
2722 };
2723 if self.theme != *new_theme {
2724 self.theme = new_theme.clone();
2725 true
2726 } else {
2727 false
2728 }
2729 }
2730
2731 pub fn apply_system_tab_style(&mut self, is_dark: bool) -> bool {
2734 if self.tab_style != TabStyle::Automatic {
2735 return false;
2736 }
2737 let target = if is_dark {
2738 self.dark_tab_style
2739 } else {
2740 self.light_tab_style
2741 };
2742 self.tab_style = target;
2744 self.apply_tab_style();
2745 self.tab_style = TabStyle::Automatic;
2746 true
2747 }
2748
2749 pub fn get_shader_override(&self, shader_name: &str) -> Option<&ShaderConfig> {
2751 self.shader_configs.get(shader_name)
2752 }
2753
2754 pub fn get_cursor_shader_override(&self, shader_name: &str) -> Option<&CursorShaderConfig> {
2756 self.cursor_shader_configs.get(shader_name)
2757 }
2758
2759 pub fn get_or_create_shader_override(&mut self, shader_name: &str) -> &mut ShaderConfig {
2761 self.shader_configs
2762 .entry(shader_name.to_string())
2763 .or_default()
2764 }
2765
2766 pub fn get_or_create_cursor_shader_override(
2768 &mut self,
2769 shader_name: &str,
2770 ) -> &mut CursorShaderConfig {
2771 self.cursor_shader_configs
2772 .entry(shader_name.to_string())
2773 .or_default()
2774 }
2775
2776 pub fn remove_shader_override(&mut self, shader_name: &str) {
2778 self.shader_configs.remove(shader_name);
2779 }
2780
2781 pub fn remove_cursor_shader_override(&mut self, shader_name: &str) {
2783 self.cursor_shader_configs.remove(shader_name);
2784 }
2785
2786 pub fn should_prompt_shader_install(&self) -> bool {
2789 if self.shader_install_prompt != ShaderInstallPrompt::Ask {
2791 return false;
2792 }
2793
2794 let shaders_dir = Self::shaders_dir();
2795
2796 if !shaders_dir.exists() {
2798 return true;
2799 }
2800
2801 if let Ok(entries) = std::fs::read_dir(&shaders_dir) {
2803 for entry in entries.flatten() {
2804 if let Some(ext) = entry.path().extension()
2805 && ext == "glsl"
2806 {
2807 return false; }
2809 }
2810 }
2811
2812 true }
2814
2815 pub fn config_dir() -> PathBuf {
2817 #[cfg(target_os = "windows")]
2818 {
2819 if let Some(config_dir) = dirs::config_dir() {
2820 config_dir.join("par-term")
2821 } else {
2822 PathBuf::from(".")
2823 }
2824 }
2825 #[cfg(not(target_os = "windows"))]
2826 {
2827 if let Some(home_dir) = dirs::home_dir() {
2828 home_dir.join(".config").join("par-term")
2829 } else {
2830 PathBuf::from(".")
2831 }
2832 }
2833 }
2834
2835 pub fn shell_integration_dir() -> PathBuf {
2837 Self::config_dir()
2838 }
2839
2840 pub fn should_prompt_shell_integration(&self) -> bool {
2842 if self.shell_integration_state != InstallPromptState::Ask {
2843 return false;
2844 }
2845
2846 let current_version = env!("CARGO_PKG_VERSION");
2847
2848 if let Some(ref prompted) = self.integration_versions.shell_integration_prompted_version
2850 && prompted == current_version
2851 {
2852 return false;
2853 }
2854
2855 if let Some(ref installed) = self
2857 .integration_versions
2858 .shell_integration_installed_version
2859 && installed == current_version
2860 {
2861 return false;
2862 }
2863
2864 true
2865 }
2866
2867 pub fn should_prompt_shader_install_versioned(&self) -> bool {
2869 if self.shader_install_prompt != ShaderInstallPrompt::Ask {
2870 return false;
2871 }
2872
2873 let current_version = env!("CARGO_PKG_VERSION");
2874
2875 if let Some(ref prompted) = self.integration_versions.shaders_prompted_version
2877 && prompted == current_version
2878 {
2879 return false;
2880 }
2881
2882 if let Some(ref installed) = self.integration_versions.shaders_installed_version
2884 && installed == current_version
2885 {
2886 return false;
2887 }
2888
2889 let shaders_dir = Self::shaders_dir();
2891 !shaders_dir.exists() || !Self::has_shader_files(&shaders_dir)
2892 }
2893
2894 fn has_shader_files(dir: &PathBuf) -> bool {
2896 if let Ok(entries) = std::fs::read_dir(dir) {
2897 for entry in entries.flatten() {
2898 if let Some(ext) = entry.path().extension()
2899 && ext == "glsl"
2900 {
2901 return true;
2902 }
2903 }
2904 }
2905 false
2906 }
2907
2908 pub fn should_prompt_integrations(&self) -> bool {
2910 self.should_prompt_shader_install_versioned() || self.should_prompt_shell_integration()
2911 }
2912
2913 pub fn get_effective_startup_directory(&self) -> Option<String> {
2924 if let Some(ref wd) = self.working_directory {
2926 let expanded = Self::expand_home_dir(wd);
2927 if std::path::Path::new(&expanded).exists() {
2928 return Some(expanded);
2929 }
2930 log::warn!(
2931 "Configured working_directory '{}' does not exist, using default",
2932 wd
2933 );
2934 }
2935
2936 match self.startup_directory_mode {
2937 StartupDirectoryMode::Home => {
2938 dirs::home_dir().map(|p| p.to_string_lossy().to_string())
2940 }
2941 StartupDirectoryMode::Previous => {
2942 if let Some(ref last_dir) = self.last_working_directory {
2944 let expanded = Self::expand_home_dir(last_dir);
2945 if std::path::Path::new(&expanded).exists() {
2946 return Some(expanded);
2947 }
2948 log::warn!(
2949 "Previous session directory '{}' no longer exists, using home",
2950 last_dir
2951 );
2952 }
2953 dirs::home_dir().map(|p| p.to_string_lossy().to_string())
2955 }
2956 StartupDirectoryMode::Custom => {
2957 if let Some(ref custom_dir) = self.startup_directory {
2959 let expanded = Self::expand_home_dir(custom_dir);
2960 if std::path::Path::new(&expanded).exists() {
2961 return Some(expanded);
2962 }
2963 log::warn!(
2964 "Custom startup directory '{}' does not exist, using home",
2965 custom_dir
2966 );
2967 }
2968 dirs::home_dir().map(|p| p.to_string_lossy().to_string())
2970 }
2971 }
2972 }
2973
2974 fn expand_home_dir(path: &str) -> String {
2976 if let Some(suffix) = path.strip_prefix("~/")
2977 && let Some(home) = dirs::home_dir()
2978 {
2979 return home.join(suffix).to_string_lossy().to_string();
2980 }
2981 path.to_string()
2982 }
2983
2984 pub fn state_file_path() -> PathBuf {
2986 #[cfg(target_os = "windows")]
2987 {
2988 if let Some(data_dir) = dirs::data_local_dir() {
2989 data_dir.join("par-term").join("state.yaml")
2990 } else {
2991 PathBuf::from("state.yaml")
2992 }
2993 }
2994 #[cfg(not(target_os = "windows"))]
2995 {
2996 if let Some(home_dir) = dirs::home_dir() {
2997 home_dir
2998 .join(".local")
2999 .join("share")
3000 .join("par-term")
3001 .join("state.yaml")
3002 } else {
3003 PathBuf::from("state.yaml")
3004 }
3005 }
3006 }
3007
3008 pub fn save_last_working_directory(&mut self, directory: &str) -> Result<()> {
3010 self.last_working_directory = Some(directory.to_string());
3011
3012 let state_path = Self::state_file_path();
3014 if let Some(parent) = state_path.parent() {
3015 fs::create_dir_all(parent)?;
3016 }
3017
3018 #[derive(Serialize)]
3020 struct SessionState {
3021 last_working_directory: Option<String>,
3022 }
3023
3024 let state = SessionState {
3025 last_working_directory: Some(directory.to_string()),
3026 };
3027
3028 let yaml = serde_yaml::to_string(&state)?;
3029 fs::write(&state_path, yaml)?;
3030
3031 log::debug!(
3032 "Saved last working directory to {:?}: {}",
3033 state_path,
3034 directory
3035 );
3036 Ok(())
3037 }
3038
3039 pub fn get_pane_background(
3042 &self,
3043 index: usize,
3044 ) -> Option<(String, BackgroundImageMode, f32, f32)> {
3045 self.pane_backgrounds
3046 .iter()
3047 .find(|pb| pb.index == index)
3048 .map(|pb| (pb.image.clone(), pb.mode, pb.opacity, pb.darken))
3049 }
3050
3051 pub fn load_last_working_directory(&mut self) {
3053 let state_path = Self::state_file_path();
3054 if !state_path.exists() {
3055 return;
3056 }
3057
3058 #[derive(Deserialize)]
3059 struct SessionState {
3060 last_working_directory: Option<String>,
3061 }
3062
3063 match fs::read_to_string(&state_path) {
3064 Ok(contents) => {
3065 if let Ok(state) = serde_yaml::from_str::<SessionState>(&contents)
3066 && let Some(dir) = state.last_working_directory
3067 {
3068 log::debug!("Loaded last working directory from state file: {}", dir);
3069 self.last_working_directory = Some(dir);
3070 }
3071 }
3072 Err(e) => {
3073 log::warn!("Failed to read state file {:?}: {}", state_path, e);
3074 }
3075 }
3076 }
3077}