Skip to main content

par_term_config/
types.rs

1//! Configuration types and enums.
2
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6// ============================================================================
7// Keybinding Types
8// ============================================================================
9
10/// Keyboard modifier for keybindings.
11///
12/// This enum is exported for potential future use (e.g., custom keybinding UI).
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15#[allow(dead_code)]
16pub enum KeyModifier {
17    /// Control key
18    Ctrl,
19    /// Alt/Option key
20    Alt,
21    /// Shift key
22    Shift,
23    /// Cmd on macOS, Ctrl on other platforms (cross-platform convenience)
24    CmdOrCtrl,
25    /// Always the Cmd/Super/Windows key
26    Super,
27}
28
29/// A keybinding configuration entry
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub struct KeyBinding {
32    /// Key combination string, e.g., "CmdOrCtrl+Shift+B"
33    pub key: String,
34    /// Action name, e.g., "toggle_background_shader"
35    pub action: String,
36}
37
38/// VSync mode (presentation mode)
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)]
40#[serde(rename_all = "lowercase")]
41pub enum VsyncMode {
42    /// No VSync - render as fast as possible (lowest latency, highest GPU usage)
43    Immediate,
44    /// Mailbox VSync - cap at monitor refresh rate with triple buffering (balanced)
45    Mailbox,
46    /// FIFO VSync - strict vsync with double buffering (lowest GPU usage, most compatible)
47    #[default]
48    Fifo,
49}
50
51impl VsyncMode {
52    /// Convert to wgpu::PresentMode
53    #[cfg(feature = "wgpu-types")]
54    pub fn to_present_mode(self) -> wgpu::PresentMode {
55        match self {
56            VsyncMode::Immediate => wgpu::PresentMode::Immediate,
57            VsyncMode::Mailbox => wgpu::PresentMode::Mailbox,
58            VsyncMode::Fifo => wgpu::PresentMode::Fifo,
59        }
60    }
61}
62
63/// GPU power preference for adapter selection
64///
65/// Controls which GPU adapter is preferred when multiple GPUs are available
66/// (e.g., integrated Intel GPU vs discrete NVIDIA/AMD GPU).
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)]
68#[serde(rename_all = "snake_case")]
69pub enum PowerPreference {
70    /// No preference - let the system decide (default)
71    #[default]
72    None,
73    /// Prefer integrated GPU (Intel/AMD iGPU) - saves battery
74    LowPower,
75    /// Prefer discrete GPU (NVIDIA/AMD) - maximum performance
76    HighPerformance,
77}
78
79impl PowerPreference {
80    /// Convert to wgpu::PowerPreference
81    #[cfg(feature = "wgpu-types")]
82    pub fn to_wgpu(self) -> wgpu::PowerPreference {
83        match self {
84            PowerPreference::None => wgpu::PowerPreference::None,
85            PowerPreference::LowPower => wgpu::PowerPreference::LowPower,
86            PowerPreference::HighPerformance => wgpu::PowerPreference::HighPerformance,
87        }
88    }
89
90    /// Display name for UI
91    pub fn display_name(&self) -> &'static str {
92        match self {
93            PowerPreference::None => "None (System Default)",
94            PowerPreference::LowPower => "Low Power (Integrated GPU)",
95            PowerPreference::HighPerformance => "High Performance (Discrete GPU)",
96        }
97    }
98
99    /// All available power preferences for UI iteration
100    pub fn all() -> &'static [PowerPreference] {
101        &[
102            PowerPreference::None,
103            PowerPreference::LowPower,
104            PowerPreference::HighPerformance,
105        ]
106    }
107}
108
109/// Cursor style
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
111#[serde(rename_all = "lowercase")]
112pub enum CursorStyle {
113    /// Block cursor (fills entire cell)
114    #[default]
115    Block,
116    /// Beam cursor (vertical line at cell start)
117    Beam,
118    /// Underline cursor (horizontal line at cell bottom)
119    Underline,
120}
121
122/// Unfocused cursor style - how the cursor appears when window loses focus
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
124#[serde(rename_all = "lowercase")]
125pub enum UnfocusedCursorStyle {
126    /// Show outline-only (hollow) block cursor when unfocused
127    #[default]
128    Hollow,
129    /// Keep same cursor style when unfocused
130    Same,
131    /// Hide cursor completely when unfocused
132    Hidden,
133}
134
135/// Image scaling quality for inline graphics (Sixel, iTerm2, Kitty)
136///
137/// Controls the GPU texture sampling filter used when scaling inline images.
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
139#[serde(rename_all = "lowercase")]
140pub enum ImageScalingMode {
141    /// Nearest-neighbor filtering - sharp pixels, good for pixel art
142    Nearest,
143    /// Bilinear filtering - smooth scaling (default)
144    #[default]
145    Linear,
146}
147
148impl ImageScalingMode {
149    /// Display name for UI
150    pub fn display_name(&self) -> &'static str {
151        match self {
152            ImageScalingMode::Nearest => "Nearest (Sharp)",
153            ImageScalingMode::Linear => "Linear (Smooth)",
154        }
155    }
156
157    /// All available modes for UI iteration
158    pub fn all() -> &'static [ImageScalingMode] {
159        &[ImageScalingMode::Nearest, ImageScalingMode::Linear]
160    }
161
162    /// Convert to wgpu FilterMode
163    #[cfg(feature = "wgpu-types")]
164    pub fn to_filter_mode(self) -> wgpu::FilterMode {
165        match self {
166            ImageScalingMode::Nearest => wgpu::FilterMode::Nearest,
167            ImageScalingMode::Linear => wgpu::FilterMode::Linear,
168        }
169    }
170}
171
172/// Background image display mode
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
174#[serde(rename_all = "lowercase")]
175pub enum BackgroundImageMode {
176    /// Scale to fit window while maintaining aspect ratio (may have letterboxing)
177    Fit,
178    /// Scale to fill window while maintaining aspect ratio (may crop edges)
179    Fill,
180    /// Stretch to fill window exactly (ignores aspect ratio)
181    #[default]
182    Stretch,
183    /// Repeat image in a tiled pattern at original size
184    Tile,
185    /// Center image at original size (no scaling)
186    Center,
187}
188
189/// Default save location for downloaded files
190#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
191#[serde(rename_all = "snake_case")]
192pub enum DownloadSaveLocation {
193    /// Save to ~/Downloads (default)
194    #[default]
195    Downloads,
196    /// Remember and re-use the last directory the user saved to
197    LastUsed,
198    /// Use the shell's current working directory
199    Cwd,
200    /// Use a custom directory path
201    Custom(String),
202}
203
204impl DownloadSaveLocation {
205    /// Get all non-Custom variants for settings UI dropdown
206    pub fn variants() -> &'static [DownloadSaveLocation] {
207        &[
208            DownloadSaveLocation::Downloads,
209            DownloadSaveLocation::LastUsed,
210            DownloadSaveLocation::Cwd,
211        ]
212    }
213
214    /// Display name for settings UI
215    pub fn display_name(&self) -> &str {
216        match self {
217            DownloadSaveLocation::Downloads => "Downloads folder",
218            DownloadSaveLocation::LastUsed => "Last used directory",
219            DownloadSaveLocation::Cwd => "Current working directory",
220            DownloadSaveLocation::Custom(_) => "Custom directory",
221        }
222    }
223}
224
225/// Background source selection
226#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
227#[serde(rename_all = "lowercase")]
228pub enum BackgroundMode {
229    /// Use theme's default background color
230    #[default]
231    Default,
232    /// Use a custom solid color
233    Color,
234    /// Use a background image
235    Image,
236}
237
238/// Per-pane background image configuration (for config persistence)
239#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
240pub struct PaneBackgroundConfig {
241    /// Pane index (0-based)
242    pub index: usize,
243    /// Image path
244    pub image: String,
245    /// Display mode
246    #[serde(default)]
247    pub mode: BackgroundImageMode,
248    /// Opacity
249    #[serde(default = "crate::defaults::background_image_opacity")]
250    pub opacity: f32,
251    /// Darken amount (0.0 = no darkening, 1.0 = fully black)
252    #[serde(default = "crate::defaults::pane_background_darken")]
253    pub darken: f32,
254}
255
256/// Tab visual style preset
257///
258/// Controls the cosmetic appearance of tabs (colors, sizes, spacing).
259/// Each preset applies a set of color/size/spacing adjustments to the tab bar.
260#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
261#[serde(rename_all = "snake_case")]
262pub enum TabStyle {
263    /// Default dark theme styling
264    #[default]
265    Dark,
266    /// Light theme tab styling
267    Light,
268    /// Smaller tabs, more visible terminal content
269    Compact,
270    /// Clean, minimal tab appearance
271    Minimal,
272    /// Enhanced contrast for accessibility
273    HighContrast,
274    /// Automatically switch between light/dark styles based on system theme
275    Automatic,
276}
277
278impl TabStyle {
279    /// Display name for UI
280    pub fn display_name(&self) -> &'static str {
281        match self {
282            TabStyle::Dark => "Dark",
283            TabStyle::Light => "Light",
284            TabStyle::Compact => "Compact",
285            TabStyle::Minimal => "Minimal",
286            TabStyle::HighContrast => "High Contrast",
287            TabStyle::Automatic => "Automatic",
288        }
289    }
290
291    /// All available styles for UI iteration
292    pub fn all() -> &'static [TabStyle] {
293        &[
294            TabStyle::Dark,
295            TabStyle::Light,
296            TabStyle::Compact,
297            TabStyle::Minimal,
298            TabStyle::HighContrast,
299            TabStyle::Automatic,
300        ]
301    }
302
303    /// All concrete styles (excludes Automatic) — for sub-style dropdowns
304    pub fn all_concrete() -> &'static [TabStyle] {
305        &[
306            TabStyle::Dark,
307            TabStyle::Light,
308            TabStyle::Compact,
309            TabStyle::Minimal,
310            TabStyle::HighContrast,
311        ]
312    }
313}
314
315/// Tab bar position
316#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
317#[serde(rename_all = "snake_case")]
318pub enum TabBarPosition {
319    /// Tab bar at the top of the window (default)
320    #[default]
321    Top,
322    /// Tab bar at the bottom of the window
323    Bottom,
324    /// Tab bar on the left side of the window (vertical layout)
325    Left,
326}
327
328impl TabBarPosition {
329    /// Display name for UI
330    pub fn display_name(&self) -> &'static str {
331        match self {
332            TabBarPosition::Top => "Top",
333            TabBarPosition::Bottom => "Bottom",
334            TabBarPosition::Left => "Left",
335        }
336    }
337
338    /// All available positions for UI iteration
339    pub fn all() -> &'static [TabBarPosition] {
340        &[
341            TabBarPosition::Top,
342            TabBarPosition::Bottom,
343            TabBarPosition::Left,
344        ]
345    }
346
347    /// Returns true if the tab bar is horizontal (top or bottom)
348    pub fn is_horizontal(&self) -> bool {
349        matches!(self, TabBarPosition::Top | TabBarPosition::Bottom)
350    }
351}
352
353/// Tab bar visibility mode
354#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
355#[serde(rename_all = "snake_case")]
356pub enum TabBarMode {
357    /// Always show tab bar (default)
358    #[default]
359    Always,
360    /// Show tab bar only when there are multiple tabs
361    WhenMultiple,
362    /// Never show tab bar
363    Never,
364}
365
366/// Controls how tab titles are automatically updated
367#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)]
368#[serde(rename_all = "snake_case")]
369pub enum TabTitleMode {
370    /// OSC title first, then CWD from shell integration, then keep default
371    #[default]
372    Auto,
373    /// Only update from explicit OSC escape sequences; never auto-set from CWD
374    OscOnly,
375}
376
377/// Status bar position
378#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)]
379#[serde(rename_all = "lowercase")]
380pub enum StatusBarPosition {
381    /// Status bar at the top of the window
382    Top,
383    /// Status bar at the bottom of the window (default)
384    #[default]
385    Bottom,
386}
387
388/// Window type for different display modes
389#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
390#[serde(rename_all = "snake_case")]
391pub enum WindowType {
392    /// Normal window (default)
393    #[default]
394    Normal,
395    /// Start in fullscreen mode
396    Fullscreen,
397    /// Edge-anchored window (for dropdown/Quake-style terminals)
398    /// Note: Edge-anchored windows require additional platform-specific support
399    EdgeTop,
400    /// Edge-anchored to bottom of screen
401    EdgeBottom,
402    /// Edge-anchored to left of screen
403    EdgeLeft,
404    /// Edge-anchored to right of screen
405    EdgeRight,
406}
407
408impl WindowType {
409    /// Display name for UI
410    pub fn display_name(&self) -> &'static str {
411        match self {
412            WindowType::Normal => "Normal",
413            WindowType::Fullscreen => "Fullscreen",
414            WindowType::EdgeTop => "Edge (Top)",
415            WindowType::EdgeBottom => "Edge (Bottom)",
416            WindowType::EdgeLeft => "Edge (Left)",
417            WindowType::EdgeRight => "Edge (Right)",
418        }
419    }
420
421    /// All available window types for UI iteration
422    pub fn all() -> &'static [WindowType] {
423        &[
424            WindowType::Normal,
425            WindowType::Fullscreen,
426            WindowType::EdgeTop,
427            WindowType::EdgeBottom,
428            WindowType::EdgeLeft,
429            WindowType::EdgeRight,
430        ]
431    }
432
433    /// Returns true if this is an edge-anchored window type
434    pub fn is_edge(&self) -> bool {
435        matches!(
436            self,
437            WindowType::EdgeTop
438                | WindowType::EdgeBottom
439                | WindowType::EdgeLeft
440                | WindowType::EdgeRight
441        )
442    }
443}
444
445/// Quote style for dropped file paths
446///
447/// Controls how filenames containing special characters are quoted when dropped into the terminal.
448#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
449#[serde(rename_all = "snake_case")]
450pub enum DroppedFileQuoteStyle {
451    /// Single quotes - safest for most shells (handles $, !, spaces, etc.)
452    /// Example: '/path/to/file with spaces.txt'
453    #[default]
454    SingleQuotes,
455    /// Double quotes - allows variable expansion
456    /// Example: "/path/to/file with spaces.txt"
457    DoubleQuotes,
458    /// Backslash escaping - escape individual special characters
459    /// Example: /path/to/file\ with\ spaces.txt
460    Backslash,
461    /// No quoting - insert path as-is (not recommended for paths with special chars)
462    None,
463}
464
465impl DroppedFileQuoteStyle {
466    /// Display name for UI
467    pub fn display_name(&self) -> &'static str {
468        match self {
469            DroppedFileQuoteStyle::SingleQuotes => "Single quotes ('...')",
470            DroppedFileQuoteStyle::DoubleQuotes => "Double quotes (\"...\")",
471            DroppedFileQuoteStyle::Backslash => "Backslash escaping (\\)",
472            DroppedFileQuoteStyle::None => "None (raw path)",
473        }
474    }
475
476    /// All available quote styles for UI iteration
477    pub fn all() -> &'static [DroppedFileQuoteStyle] {
478        &[
479            DroppedFileQuoteStyle::SingleQuotes,
480            DroppedFileQuoteStyle::DoubleQuotes,
481            DroppedFileQuoteStyle::Backslash,
482            DroppedFileQuoteStyle::None,
483        ]
484    }
485}
486
487/// Option/Alt key behavior mode
488///
489/// Controls what happens when Option (macOS) or Alt (Linux/Windows) key is pressed
490/// with a character key. This is essential for emacs and vim users who rely on
491/// Meta key combinations (M-x, M-f, M-b, etc.).
492#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
493#[serde(rename_all = "lowercase")]
494pub enum OptionKeyMode {
495    /// Normal - sends special characters (default macOS behavior)
496    /// Option+f → ƒ (special character)
497    Normal,
498    /// Meta - sets the high bit (8th bit) on the character
499    /// Option+f → 0xE6 (f with high bit set)
500    Meta,
501    /// Esc - sends Escape prefix before the character (most compatible)
502    /// Option+f → ESC f (escape then f)
503    /// This is the most compatible mode for terminal applications like emacs and vim
504    #[default]
505    Esc,
506}
507
508// ============================================================================
509// Modifier Remapping Types
510// ============================================================================
511
512/// Target modifier for remapping.
513///
514/// Allows remapping one modifier key to behave as another.
515/// For example, remap Caps Lock to Ctrl, or swap Ctrl and Super.
516#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
517#[serde(rename_all = "lowercase")]
518pub enum ModifierTarget {
519    /// No remapping - use the key's normal function
520    #[default]
521    None,
522    /// Remap to Control key
523    Ctrl,
524    /// Remap to Alt/Option key
525    Alt,
526    /// Remap to Shift key
527    Shift,
528    /// Remap to Super/Cmd/Windows key
529    Super,
530}
531
532impl ModifierTarget {
533    /// Display name for UI
534    pub fn display_name(&self) -> &'static str {
535        match self {
536            ModifierTarget::None => "None (disabled)",
537            ModifierTarget::Ctrl => "Ctrl",
538            ModifierTarget::Alt => "Alt/Option",
539            ModifierTarget::Shift => "Shift",
540            ModifierTarget::Super => "Super/Cmd",
541        }
542    }
543
544    /// All available targets for UI iteration
545    pub fn all() -> &'static [ModifierTarget] {
546        &[
547            ModifierTarget::None,
548            ModifierTarget::Ctrl,
549            ModifierTarget::Alt,
550            ModifierTarget::Shift,
551            ModifierTarget::Super,
552        ]
553    }
554}
555
556/// Modifier remapping configuration.
557///
558/// Allows users to remap modifier keys to different functions.
559/// This is useful for:
560/// - Swapping Ctrl and Caps Lock
561/// - Using Ctrl as Cmd on macOS
562/// - Customizing modifier layout for ergonomic keyboards
563#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
564pub struct ModifierRemapping {
565    /// What the left Ctrl key should act as
566    #[serde(default)]
567    pub left_ctrl: ModifierTarget,
568    /// What the right Ctrl key should act as
569    #[serde(default)]
570    pub right_ctrl: ModifierTarget,
571    /// What the left Alt key should act as
572    #[serde(default)]
573    pub left_alt: ModifierTarget,
574    /// What the right Alt key should act as
575    #[serde(default)]
576    pub right_alt: ModifierTarget,
577    /// What the left Super/Cmd key should act as
578    #[serde(default)]
579    pub left_super: ModifierTarget,
580    /// What the right Super/Cmd key should act as
581    #[serde(default)]
582    pub right_super: ModifierTarget,
583}
584
585/// Font mapping for a specific Unicode range
586#[derive(Debug, Clone, Serialize, Deserialize)]
587pub struct FontRange {
588    /// Start of Unicode range (inclusive), e.g., 0x4E00 for CJK
589    pub start: u32,
590    /// End of Unicode range (inclusive), e.g., 0x9FFF for CJK
591    pub end: u32,
592    /// Font family name to use for this range
593    pub font_family: String,
594}
595
596/// Thin strokes / font smoothing mode
597///
598/// Controls font stroke weight adjustment for improved rendering,
599/// particularly on high-DPI/Retina displays. Inspired by iTerm2's thin strokes feature.
600#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
601#[serde(rename_all = "snake_case")]
602pub enum ThinStrokesMode {
603    /// Never apply thin strokes
604    Never,
605    /// Apply thin strokes only on Retina/HiDPI displays (default)
606    #[default]
607    RetinaOnly,
608    /// Apply thin strokes only on dark backgrounds
609    DarkBackgroundsOnly,
610    /// Apply thin strokes only on Retina displays with dark backgrounds
611    RetinaDarkBackgroundsOnly,
612    /// Always apply thin strokes
613    Always,
614}
615
616/// Shader install prompt mode
617///
618/// Controls whether the user is prompted to install shaders when the shaders
619/// folder is missing or empty on startup.
620#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
621#[serde(rename_all = "snake_case")]
622pub enum ShaderInstallPrompt {
623    /// Ask the user if they want to install shaders (default)
624    #[default]
625    Ask,
626    /// Never ask - user declined installation
627    Never,
628    /// Shaders have been installed
629    Installed,
630}
631
632/// State of an integration's install prompt
633#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
634#[serde(rename_all = "snake_case")]
635pub enum InstallPromptState {
636    /// Prompt user when appropriate (default)
637    #[default]
638    Ask,
639    /// User said "never ask again"
640    Never,
641    /// Currently installed
642    Installed,
643}
644
645impl InstallPromptState {
646    /// Display name for UI
647    pub fn display_name(&self) -> &'static str {
648        match self {
649            Self::Ask => "Ask",
650            Self::Never => "Never",
651            Self::Installed => "Installed",
652        }
653    }
654}
655
656/// Tracks installed and prompted versions for integrations
657#[derive(Debug, Clone, Default, Serialize, Deserialize)]
658pub struct IntegrationVersions {
659    /// Version when shaders were installed
660    pub shaders_installed_version: Option<String>,
661    /// Version when user was last prompted about shaders
662    pub shaders_prompted_version: Option<String>,
663    /// Version when shell integration was installed
664    pub shell_integration_installed_version: Option<String>,
665    /// Version when user was last prompted about shell integration
666    pub shell_integration_prompted_version: Option<String>,
667}
668
669/// Startup directory mode
670///
671/// Controls where the terminal starts its working directory.
672#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
673#[serde(rename_all = "snake_case")]
674pub enum StartupDirectoryMode {
675    /// Start in the user's home directory (default)
676    #[default]
677    Home,
678    /// Remember and restore the last working directory from the previous session
679    Previous,
680    /// Start in a user-specified custom path
681    Custom,
682}
683
684impl StartupDirectoryMode {
685    /// Display name for UI
686    pub fn display_name(&self) -> &'static str {
687        match self {
688            Self::Home => "Home Directory",
689            Self::Previous => "Previous Session",
690            Self::Custom => "Custom Directory",
691        }
692    }
693
694    /// All available modes for UI iteration
695    pub fn all() -> &'static [StartupDirectoryMode] {
696        &[
697            StartupDirectoryMode::Home,
698            StartupDirectoryMode::Previous,
699            StartupDirectoryMode::Custom,
700        ]
701    }
702}
703
704/// Action to take when the shell process exits
705///
706/// Controls what happens when a shell session terminates.
707#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
708#[serde(rename_all = "snake_case")]
709pub enum ShellExitAction {
710    /// Close the tab/pane when shell exits (default)
711    #[default]
712    Close,
713    /// Keep the pane open showing the terminated shell
714    Keep,
715    /// Immediately restart the shell
716    RestartImmediately,
717    /// Show a prompt message and wait for Enter before restarting
718    RestartWithPrompt,
719    /// Restart the shell after a 1 second delay
720    RestartAfterDelay,
721}
722
723impl ShellExitAction {
724    /// Display name for UI
725    pub fn display_name(&self) -> &'static str {
726        match self {
727            Self::Close => "Close tab/pane",
728            Self::Keep => "Keep open",
729            Self::RestartImmediately => "Restart immediately",
730            Self::RestartWithPrompt => "Restart with prompt",
731            Self::RestartAfterDelay => "Restart after 1s delay",
732        }
733    }
734
735    /// All available actions for UI iteration
736    pub fn all() -> &'static [ShellExitAction] {
737        &[
738            ShellExitAction::Close,
739            ShellExitAction::Keep,
740            ShellExitAction::RestartImmediately,
741            ShellExitAction::RestartWithPrompt,
742            ShellExitAction::RestartAfterDelay,
743        ]
744    }
745
746    /// Returns true if this action involves restarting the shell
747    pub fn is_restart(&self) -> bool {
748        matches!(
749            self,
750            Self::RestartImmediately | Self::RestartWithPrompt | Self::RestartAfterDelay
751        )
752    }
753}
754
755/// Detected shell type
756#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
757#[serde(rename_all = "lowercase")]
758pub enum ShellType {
759    Bash,
760    Zsh,
761    Fish,
762    #[default]
763    Unknown,
764}
765
766impl ShellType {
767    /// Classify a shell path string into a `ShellType`.
768    fn from_path(path: &str) -> Self {
769        if path.contains("zsh") {
770            Self::Zsh
771        } else if path.contains("bash") {
772            Self::Bash
773        } else if path.contains("fish") {
774            Self::Fish
775        } else {
776            Self::Unknown
777        }
778    }
779
780    /// Detect shell type using multiple strategies.
781    ///
782    /// 1. `$SHELL` environment variable (works in terminals).
783    /// 2. macOS Directory Services (`dscl`) — works for app-bundle launches.
784    /// 3. `/etc/passwd` entry for the current user (Linux / older macOS).
785    pub fn detect() -> Self {
786        // 1. $SHELL env var
787        if let Ok(shell) = std::env::var("SHELL") {
788            let t = Self::from_path(&shell);
789            if t != Self::Unknown {
790                return t;
791            }
792        }
793
794        // 2. macOS: query Directory Services for the login shell
795        #[cfg(target_os = "macos")]
796        {
797            if let Some(t) = Self::detect_via_dscl() {
798                return t;
799            }
800        }
801
802        // 3. Parse /etc/passwd for the current user's shell
803        #[cfg(unix)]
804        {
805            if let Some(t) = Self::detect_from_passwd() {
806                return t;
807            }
808        }
809
810        Self::Unknown
811    }
812
813    /// macOS: run `dscl . -read /Users/<user> UserShell` to get the login shell.
814    #[cfg(target_os = "macos")]
815    fn detect_via_dscl() -> Option<Self> {
816        let user = std::env::var("USER")
817            .or_else(|_| std::env::var("LOGNAME"))
818            .ok()?;
819        let output = std::process::Command::new("dscl")
820            .args([".", "-read", &format!("/Users/{}", user), "UserShell"])
821            .output()
822            .ok()?;
823        let text = String::from_utf8_lossy(&output.stdout);
824        // Output looks like: "UserShell: /bin/zsh"
825        let shell_path = text.split_whitespace().last()?;
826        let t = Self::from_path(shell_path);
827        if t != Self::Unknown { Some(t) } else { None }
828    }
829
830    /// Unix: parse `/etc/passwd` for the current user's configured shell.
831    #[cfg(unix)]
832    fn detect_from_passwd() -> Option<Self> {
833        let user = std::env::var("USER")
834            .or_else(|_| std::env::var("LOGNAME"))
835            .ok()?;
836        let contents = std::fs::read_to_string("/etc/passwd").ok()?;
837        for line in contents.lines() {
838            let parts: Vec<&str> = line.splitn(7, ':').collect();
839            if parts.len() == 7 && parts[0] == user {
840                let t = Self::from_path(parts[6]);
841                if t != Self::Unknown {
842                    return Some(t);
843                }
844            }
845        }
846        None
847    }
848
849    /// Display name for UI
850    pub fn display_name(&self) -> &'static str {
851        match self {
852            Self::Bash => "Bash",
853            Self::Zsh => "Zsh",
854            Self::Fish => "Fish",
855            Self::Unknown => "Unknown",
856        }
857    }
858
859    /// File extension for integration script
860    pub fn extension(&self) -> &'static str {
861        match self {
862            Self::Bash => "bash",
863            Self::Zsh => "zsh",
864            Self::Fish => "fish",
865            Self::Unknown => "sh",
866        }
867    }
868}
869
870/// Update check frequency
871///
872/// Controls how often par-term checks GitHub for new releases.
873#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
874#[serde(rename_all = "snake_case")]
875pub enum UpdateCheckFrequency {
876    /// Never check for updates
877    Never,
878    /// Check once per hour
879    Hourly,
880    /// Check once per day (default)
881    #[default]
882    Daily,
883    /// Check once per week
884    Weekly,
885    /// Check once per month
886    Monthly,
887}
888
889impl UpdateCheckFrequency {
890    /// Get the duration in seconds for this frequency
891    pub fn as_seconds(&self) -> Option<u64> {
892        match self {
893            UpdateCheckFrequency::Never => None,
894            UpdateCheckFrequency::Hourly => Some(3600),
895            UpdateCheckFrequency::Daily => Some(24 * 60 * 60), // 86400
896            UpdateCheckFrequency::Weekly => Some(7 * 24 * 60 * 60), // 604800
897            UpdateCheckFrequency::Monthly => Some(30 * 24 * 60 * 60), // 2592000
898        }
899    }
900
901    /// Display name for UI
902    pub fn display_name(&self) -> &'static str {
903        match self {
904            UpdateCheckFrequency::Never => "Never",
905            UpdateCheckFrequency::Hourly => "Hourly",
906            UpdateCheckFrequency::Daily => "Daily",
907            UpdateCheckFrequency::Weekly => "Weekly",
908            UpdateCheckFrequency::Monthly => "Monthly",
909        }
910    }
911}
912
913// ============================================================================
914// Session Logging Types
915// ============================================================================
916
917/// Log format for session logging
918///
919/// Controls the format used when automatically logging terminal sessions.
920#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
921#[serde(rename_all = "lowercase")]
922pub enum SessionLogFormat {
923    /// Plain text - strips escape sequences, captures only printable output
924    Plain,
925    /// HTML - preserves colors and styling as HTML
926    Html,
927    /// Asciicast v2 - asciinema-compatible format for replay/sharing
928    #[default]
929    Asciicast,
930}
931
932impl SessionLogFormat {
933    /// Display name for UI
934    pub fn display_name(&self) -> &'static str {
935        match self {
936            SessionLogFormat::Plain => "Plain Text",
937            SessionLogFormat::Html => "HTML",
938            SessionLogFormat::Asciicast => "Asciicast (asciinema)",
939        }
940    }
941
942    /// All available formats for UI iteration
943    pub fn all() -> &'static [SessionLogFormat] {
944        &[
945            SessionLogFormat::Plain,
946            SessionLogFormat::Html,
947            SessionLogFormat::Asciicast,
948        ]
949    }
950
951    /// File extension for this format
952    pub fn extension(&self) -> &'static str {
953        match self {
954            SessionLogFormat::Plain => "txt",
955            SessionLogFormat::Html => "html",
956            SessionLogFormat::Asciicast => "cast",
957        }
958    }
959}
960
961/// Log level for debug logging to file.
962///
963/// Controls the verbosity of log output written to the debug log file.
964/// Environment variables `RUST_LOG` and `--log-level` CLI flag take precedence.
965#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
966#[serde(rename_all = "lowercase")]
967pub enum LogLevel {
968    /// No logging (log file not created)
969    #[default]
970    Off,
971    /// Errors only
972    Error,
973    /// Warnings and errors
974    Warn,
975    /// Informational messages
976    Info,
977    /// Debug messages
978    Debug,
979    /// Most verbose
980    Trace,
981}
982
983impl LogLevel {
984    /// Display name for UI
985    pub fn display_name(&self) -> &'static str {
986        match self {
987            LogLevel::Off => "Off",
988            LogLevel::Error => "Error",
989            LogLevel::Warn => "Warn",
990            LogLevel::Info => "Info",
991            LogLevel::Debug => "Debug",
992            LogLevel::Trace => "Trace",
993        }
994    }
995
996    /// All available levels for UI iteration
997    pub fn all() -> &'static [LogLevel] {
998        &[
999            LogLevel::Off,
1000            LogLevel::Error,
1001            LogLevel::Warn,
1002            LogLevel::Info,
1003            LogLevel::Debug,
1004            LogLevel::Trace,
1005        ]
1006    }
1007
1008    /// Convert to `log::LevelFilter`
1009    pub fn to_level_filter(self) -> log::LevelFilter {
1010        match self {
1011            LogLevel::Off => log::LevelFilter::Off,
1012            LogLevel::Error => log::LevelFilter::Error,
1013            LogLevel::Warn => log::LevelFilter::Warn,
1014            LogLevel::Info => log::LevelFilter::Info,
1015            LogLevel::Debug => log::LevelFilter::Debug,
1016            LogLevel::Trace => log::LevelFilter::Trace,
1017        }
1018    }
1019}
1020
1021/// Editor selection mode for semantic history
1022///
1023/// Controls how the editor is selected when opening files via semantic history.
1024#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1025#[serde(rename_all = "snake_case")]
1026pub enum SemanticHistoryEditorMode {
1027    /// Use custom editor command from `semantic_history_editor` setting
1028    Custom,
1029    /// Use $EDITOR or $VISUAL environment variable
1030    #[default]
1031    EnvironmentVariable,
1032    /// Use system default application for each file type
1033    SystemDefault,
1034}
1035
1036impl SemanticHistoryEditorMode {
1037    /// Display name for UI
1038    pub fn display_name(&self) -> &'static str {
1039        match self {
1040            SemanticHistoryEditorMode::Custom => "Custom Editor",
1041            SemanticHistoryEditorMode::EnvironmentVariable => "Environment Variable ($EDITOR)",
1042            SemanticHistoryEditorMode::SystemDefault => "System Default",
1043        }
1044    }
1045
1046    /// All available modes for UI iteration
1047    pub fn all() -> &'static [SemanticHistoryEditorMode] {
1048        &[
1049            SemanticHistoryEditorMode::Custom,
1050            SemanticHistoryEditorMode::EnvironmentVariable,
1051            SemanticHistoryEditorMode::SystemDefault,
1052        ]
1053    }
1054}
1055
1056/// Style for link highlight underlines.
1057#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1058#[serde(rename_all = "snake_case")]
1059pub enum LinkUnderlineStyle {
1060    /// Solid continuous underline
1061    Solid,
1062    /// Dotted/stipple underline (alternating pixels)
1063    #[default]
1064    Stipple,
1065}
1066
1067impl LinkUnderlineStyle {
1068    /// Display name for UI
1069    pub fn display_name(&self) -> &'static str {
1070        match self {
1071            LinkUnderlineStyle::Solid => "Solid",
1072            LinkUnderlineStyle::Stipple => "Stipple",
1073        }
1074    }
1075
1076    /// All available styles for UI iteration
1077    pub fn all() -> &'static [LinkUnderlineStyle] {
1078        &[LinkUnderlineStyle::Solid, LinkUnderlineStyle::Stipple]
1079    }
1080}
1081
1082// ============================================================================
1083// Per-Shader Configuration Types
1084// ============================================================================
1085
1086/// Metadata embedded in shader files via YAML block comments.
1087///
1088/// Parsed from `/*! par-term shader metadata ... */` blocks at the top of shader files.
1089#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1090pub struct ShaderMetadata {
1091    /// Human-readable name for the shader (e.g., "CRT Effect")
1092    pub name: Option<String>,
1093    /// Author of the shader
1094    pub author: Option<String>,
1095    /// Description of what the shader does
1096    pub description: Option<String>,
1097    /// Version string (e.g., "1.0.0")
1098    pub version: Option<String>,
1099    /// Default configuration values for this shader
1100    #[serde(default)]
1101    pub defaults: ShaderConfig,
1102}
1103
1104/// Per-shader configuration settings.
1105///
1106/// Used both for embedded defaults in shader files and for user overrides in config.yaml.
1107/// All fields are optional to allow partial overrides.
1108#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1109pub struct ShaderConfig {
1110    /// Animation speed multiplier (1.0 = normal speed)
1111    pub animation_speed: Option<f32>,
1112    /// Brightness multiplier (0.05-1.0)
1113    pub brightness: Option<f32>,
1114    /// Text opacity when using this shader (0.0-1.0)
1115    pub text_opacity: Option<f32>,
1116    /// When true, shader receives full terminal content for manipulation
1117    pub full_content: Option<bool>,
1118    /// Path to texture for iChannel0
1119    pub channel0: Option<String>,
1120    /// Path to texture for iChannel1
1121    pub channel1: Option<String>,
1122    /// Path to texture for iChannel2
1123    pub channel2: Option<String>,
1124    /// Path to texture for iChannel3
1125    pub channel3: Option<String>,
1126    /// Path prefix for cubemap faces
1127    pub cubemap: Option<String>,
1128    /// Whether cubemap sampling is enabled
1129    pub cubemap_enabled: Option<bool>,
1130    /// Use the app's background image as iChannel0 instead of a separate texture
1131    pub use_background_as_channel0: Option<bool>,
1132}
1133
1134/// Cursor shader specific configuration.
1135///
1136/// Extends base ShaderConfig with cursor-specific settings.
1137#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1138pub struct CursorShaderConfig {
1139    /// Base shader configuration
1140    #[serde(flatten)]
1141    pub base: ShaderConfig,
1142    /// Hide the default cursor when this shader is enabled
1143    pub hides_cursor: Option<bool>,
1144    /// Disable cursor shader while in alt screen (vim, less, htop)
1145    pub disable_in_alt_screen: Option<bool>,
1146    /// Cursor glow radius in pixels
1147    pub glow_radius: Option<f32>,
1148    /// Cursor glow intensity (0.0-1.0)
1149    pub glow_intensity: Option<f32>,
1150    /// Duration of cursor trail effect in seconds
1151    pub trail_duration: Option<f32>,
1152    /// Cursor color for shader effects [R, G, B] (0-255)
1153    pub cursor_color: Option<[u8; 3]>,
1154}
1155
1156/// Metadata embedded in cursor shader files via YAML block comments.
1157///
1158/// Parsed from `/*! par-term shader metadata ... */` blocks at the top of cursor shader files.
1159/// Similar to `ShaderMetadata` but with cursor-specific defaults.
1160#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1161pub struct CursorShaderMetadata {
1162    /// Human-readable name for the shader (e.g., "Cursor Glow Effect")
1163    pub name: Option<String>,
1164    /// Author of the shader
1165    pub author: Option<String>,
1166    /// Description of what the shader does
1167    pub description: Option<String>,
1168    /// Version string (e.g., "1.0.0")
1169    pub version: Option<String>,
1170    /// Default configuration values for this cursor shader
1171    #[serde(default)]
1172    pub defaults: CursorShaderConfig,
1173}
1174
1175/// Fully resolved shader configuration with all values filled in.
1176///
1177/// Created by merging user overrides, shader metadata defaults, and global defaults.
1178#[derive(Debug, Clone)]
1179#[allow(dead_code)]
1180pub struct ResolvedShaderConfig {
1181    /// Animation speed multiplier
1182    pub animation_speed: f32,
1183    /// Brightness multiplier
1184    pub brightness: f32,
1185    /// Text opacity
1186    pub text_opacity: f32,
1187    /// Full content mode enabled
1188    pub full_content: bool,
1189    /// Resolved path to iChannel0 texture
1190    pub channel0: Option<PathBuf>,
1191    /// Resolved path to iChannel1 texture
1192    pub channel1: Option<PathBuf>,
1193    /// Resolved path to iChannel2 texture
1194    pub channel2: Option<PathBuf>,
1195    /// Resolved path to iChannel3 texture
1196    pub channel3: Option<PathBuf>,
1197    /// Resolved cubemap path prefix
1198    pub cubemap: Option<PathBuf>,
1199    /// Cubemap sampling enabled
1200    pub cubemap_enabled: bool,
1201    /// Use the app's background image as iChannel0
1202    pub use_background_as_channel0: bool,
1203}
1204
1205impl Default for ResolvedShaderConfig {
1206    fn default() -> Self {
1207        Self {
1208            animation_speed: 1.0,
1209            brightness: 1.0,
1210            text_opacity: 1.0,
1211            full_content: false,
1212            channel0: None,
1213            channel1: None,
1214            channel2: None,
1215            channel3: None,
1216            cubemap: None,
1217            cubemap_enabled: true,
1218            use_background_as_channel0: false,
1219        }
1220    }
1221}
1222
1223/// Fully resolved cursor shader configuration with all values filled in.
1224#[derive(Debug, Clone)]
1225#[allow(dead_code)]
1226pub struct ResolvedCursorShaderConfig {
1227    /// Base resolved shader config
1228    pub base: ResolvedShaderConfig,
1229    /// Hide the default cursor when this shader is enabled
1230    pub hides_cursor: bool,
1231    /// Disable cursor shader while in alt screen (vim, less, htop)
1232    pub disable_in_alt_screen: bool,
1233    /// Cursor glow radius in pixels
1234    pub glow_radius: f32,
1235    /// Cursor glow intensity (0.0-1.0)
1236    pub glow_intensity: f32,
1237    /// Duration of cursor trail effect in seconds
1238    pub trail_duration: f32,
1239    /// Cursor color for shader effects [R, G, B] (0-255)
1240    pub cursor_color: [u8; 3],
1241}
1242
1243impl Default for ResolvedCursorShaderConfig {
1244    fn default() -> Self {
1245        Self {
1246            base: ResolvedShaderConfig::default(),
1247            hides_cursor: false,
1248            disable_in_alt_screen: true,
1249            glow_radius: 80.0,
1250            glow_intensity: 0.3,
1251            trail_duration: 0.5,
1252            cursor_color: [255, 255, 255],
1253        }
1254    }
1255}
1256
1257// ============================================================================
1258// Progress Bar Types
1259// ============================================================================
1260
1261/// Progress bar visual style
1262#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1263#[serde(rename_all = "lowercase")]
1264pub enum ProgressBarStyle {
1265    /// Thin bar line (default)
1266    #[default]
1267    Bar,
1268    /// Bar with percentage text
1269    BarWithText,
1270}
1271
1272impl ProgressBarStyle {
1273    /// Display name for UI
1274    pub fn display_name(&self) -> &'static str {
1275        match self {
1276            ProgressBarStyle::Bar => "Bar",
1277            ProgressBarStyle::BarWithText => "Bar with Text",
1278        }
1279    }
1280
1281    /// All available styles for UI iteration
1282    pub fn all() -> &'static [ProgressBarStyle] {
1283        &[ProgressBarStyle::Bar, ProgressBarStyle::BarWithText]
1284    }
1285}
1286
1287/// Progress bar position on screen
1288#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1289#[serde(rename_all = "lowercase")]
1290pub enum ProgressBarPosition {
1291    /// Top of the terminal window (default)
1292    #[default]
1293    Top,
1294    /// Bottom of the terminal window
1295    Bottom,
1296}
1297
1298impl ProgressBarPosition {
1299    /// Display name for UI
1300    pub fn display_name(&self) -> &'static str {
1301        match self {
1302            ProgressBarPosition::Bottom => "Bottom",
1303            ProgressBarPosition::Top => "Top",
1304        }
1305    }
1306
1307    /// All available positions for UI iteration
1308    pub fn all() -> &'static [ProgressBarPosition] {
1309        &[ProgressBarPosition::Top, ProgressBarPosition::Bottom]
1310    }
1311}
1312
1313// ============================================================================
1314// Smart Selection Types
1315// ============================================================================
1316
1317/// Precision level for smart selection rules.
1318///
1319/// Higher precision rules are checked first and match more specific patterns.
1320/// Based on iTerm2's smart selection precision levels.
1321#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1322#[serde(rename_all = "snake_case")]
1323pub enum SmartSelectionPrecision {
1324    /// Very low precision (0.00001) - matches almost anything
1325    VeryLow,
1326    /// Low precision (0.001) - broad patterns
1327    Low,
1328    /// Normal precision (1.0) - standard patterns
1329    #[default]
1330    Normal,
1331    /// High precision (1000.0) - specific patterns
1332    High,
1333    /// Very high precision (1000000.0) - most specific patterns (checked first)
1334    VeryHigh,
1335}
1336
1337impl SmartSelectionPrecision {
1338    /// Get the numeric precision value for sorting
1339    pub fn value(&self) -> f64 {
1340        match self {
1341            SmartSelectionPrecision::VeryLow => 0.00001,
1342            SmartSelectionPrecision::Low => 0.001,
1343            SmartSelectionPrecision::Normal => 1.0,
1344            SmartSelectionPrecision::High => 1000.0,
1345            SmartSelectionPrecision::VeryHigh => 1_000_000.0,
1346        }
1347    }
1348
1349    /// Display name for UI
1350    pub fn display_name(&self) -> &'static str {
1351        match self {
1352            SmartSelectionPrecision::VeryLow => "Very Low",
1353            SmartSelectionPrecision::Low => "Low",
1354            SmartSelectionPrecision::Normal => "Normal",
1355            SmartSelectionPrecision::High => "High",
1356            SmartSelectionPrecision::VeryHigh => "Very High",
1357        }
1358    }
1359}
1360
1361/// Position of pane title bars
1362#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1363#[serde(rename_all = "snake_case")]
1364pub enum PaneTitlePosition {
1365    /// Title bar at the top of the pane (default)
1366    #[default]
1367    Top,
1368    /// Title bar at the bottom of the pane
1369    Bottom,
1370}
1371
1372impl PaneTitlePosition {
1373    /// All available positions for UI dropdowns
1374    pub const ALL: &'static [PaneTitlePosition] =
1375        &[PaneTitlePosition::Top, PaneTitlePosition::Bottom];
1376
1377    /// Display name for UI
1378    pub fn display_name(&self) -> &'static str {
1379        match self {
1380            PaneTitlePosition::Top => "Top",
1381            PaneTitlePosition::Bottom => "Bottom",
1382        }
1383    }
1384}
1385
1386/// Style of dividers between panes
1387#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1388#[serde(rename_all = "snake_case")]
1389pub enum DividerStyle {
1390    /// Solid line (default)
1391    #[default]
1392    Solid,
1393    /// Double line effect (two thin lines with gap)
1394    Double,
1395    /// Dashed line effect
1396    Dashed,
1397    /// Shadow effect (gradient fade)
1398    Shadow,
1399}
1400
1401impl DividerStyle {
1402    /// All available styles for UI dropdowns
1403    pub const ALL: &'static [DividerStyle] = &[
1404        DividerStyle::Solid,
1405        DividerStyle::Double,
1406        DividerStyle::Dashed,
1407        DividerStyle::Shadow,
1408    ];
1409
1410    /// Display name for UI
1411    pub fn display_name(&self) -> &'static str {
1412        match self {
1413            DividerStyle::Solid => "Solid",
1414            DividerStyle::Double => "Double",
1415            DividerStyle::Dashed => "Dashed",
1416            DividerStyle::Shadow => "Shadow",
1417        }
1418    }
1419}
1420
1421/// Terminal events that can trigger alert sounds
1422#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1423#[serde(rename_all = "snake_case")]
1424pub enum AlertEvent {
1425    /// Bell character received (BEL / 0x07)
1426    Bell,
1427    /// Command completed (requires shell integration)
1428    CommandComplete,
1429    /// A new tab was created
1430    NewTab,
1431    /// A tab was closed
1432    TabClose,
1433}
1434
1435impl AlertEvent {
1436    /// Display name for UI
1437    pub fn display_name(&self) -> &'static str {
1438        match self {
1439            AlertEvent::Bell => "Bell",
1440            AlertEvent::CommandComplete => "Command Complete",
1441            AlertEvent::NewTab => "New Tab",
1442            AlertEvent::TabClose => "Tab Close",
1443        }
1444    }
1445
1446    /// All available events for UI iteration
1447    pub fn all() -> &'static [AlertEvent] {
1448        &[
1449            AlertEvent::Bell,
1450            AlertEvent::CommandComplete,
1451            AlertEvent::NewTab,
1452            AlertEvent::TabClose,
1453        ]
1454    }
1455}
1456
1457/// Configuration for an alert sound tied to a specific event
1458#[derive(Debug, Clone, Serialize, Deserialize)]
1459pub struct AlertSoundConfig {
1460    /// Whether this alert sound is enabled
1461    #[serde(default = "crate::defaults::bool_true")]
1462    pub enabled: bool,
1463    /// Volume 0-100 (0 effectively disables)
1464    #[serde(default = "crate::defaults::bell_sound")]
1465    pub volume: u8,
1466    /// Optional path to a custom sound file (WAV/OGG/FLAC).
1467    /// If None, uses built-in tone with the configured frequency.
1468    #[serde(default)]
1469    pub sound_file: Option<String>,
1470    /// Frequency in Hz for the built-in tone (used when sound_file is None)
1471    #[serde(default = "default_alert_frequency")]
1472    pub frequency: f32,
1473    /// Duration of the built-in tone in milliseconds
1474    #[serde(default = "default_alert_duration_ms")]
1475    pub duration_ms: u64,
1476}
1477
1478fn default_alert_frequency() -> f32 {
1479    800.0
1480}
1481
1482fn default_alert_duration_ms() -> u64 {
1483    100
1484}
1485
1486impl Default for AlertSoundConfig {
1487    fn default() -> Self {
1488        Self {
1489            enabled: true,
1490            volume: 50,
1491            sound_file: None,
1492            frequency: 800.0,
1493            duration_ms: 100,
1494        }
1495    }
1496}
1497
1498/// A smart selection rule for pattern-based text selection.
1499///
1500/// When double-clicking, rules are evaluated by precision (highest first).
1501/// If a pattern matches at the cursor position, that text is selected.
1502#[derive(Debug, Clone, Serialize, Deserialize)]
1503pub struct SmartSelectionRule {
1504    /// Human-readable name for this rule (e.g., "HTTP URL", "Email address")
1505    pub name: String,
1506    /// Regular expression pattern to match
1507    pub regex: String,
1508    /// Precision level - higher precision rules are checked first
1509    #[serde(default)]
1510    pub precision: SmartSelectionPrecision,
1511    /// Whether this rule is enabled
1512    #[serde(default = "default_enabled")]
1513    pub enabled: bool,
1514}
1515
1516fn default_enabled() -> bool {
1517    true
1518}
1519
1520impl SmartSelectionRule {
1521    /// Create a new smart selection rule
1522    pub fn new(
1523        name: impl Into<String>,
1524        regex: impl Into<String>,
1525        precision: SmartSelectionPrecision,
1526    ) -> Self {
1527        Self {
1528            name: name.into(),
1529            regex: regex.into(),
1530            precision,
1531            enabled: true,
1532        }
1533    }
1534}
1535
1536/// Get the default smart selection rules (based on iTerm2's defaults)
1537pub fn default_smart_selection_rules() -> Vec<SmartSelectionRule> {
1538    vec![
1539        // Very High precision - most specific, checked first
1540        SmartSelectionRule::new(
1541            "HTTP URL",
1542            r"https?://[^\s<>\[\]{}|\\^`\x00-\x1f]+",
1543            SmartSelectionPrecision::VeryHigh,
1544        ),
1545        SmartSelectionRule::new(
1546            "SSH URL",
1547            r"\bssh://([a-zA-Z0-9_]+@)?([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+(/[^\s]*)?",
1548            SmartSelectionPrecision::VeryHigh,
1549        ),
1550        SmartSelectionRule::new(
1551            "Git URL",
1552            r"\bgit://([a-zA-Z0-9_]+@)?([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+(/[^\s]*)?",
1553            SmartSelectionPrecision::VeryHigh,
1554        ),
1555        SmartSelectionRule::new(
1556            "File URL",
1557            r"file://[^\s]+",
1558            SmartSelectionPrecision::VeryHigh,
1559        ),
1560        // High precision
1561        SmartSelectionRule::new(
1562            "Email address",
1563            r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b",
1564            SmartSelectionPrecision::High,
1565        ),
1566        SmartSelectionRule::new(
1567            "IPv4 address",
1568            r"\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b",
1569            SmartSelectionPrecision::High,
1570        ),
1571        // Normal precision
1572        SmartSelectionRule::new(
1573            "File path",
1574            r"~?/?(?:[a-zA-Z0-9._-]+/)+[a-zA-Z0-9._-]+/?",
1575            SmartSelectionPrecision::Normal,
1576        ),
1577        SmartSelectionRule::new(
1578            "Java/Python import",
1579            // Require at least 2 dots to avoid matching simple filenames like "file.txt"
1580            r"(?:[a-zA-Z_][a-zA-Z0-9_]*\.){2,}[a-zA-Z_][a-zA-Z0-9_]*",
1581            SmartSelectionPrecision::Normal,
1582        ),
1583        SmartSelectionRule::new(
1584            "C++ namespace",
1585            r"(?:[a-zA-Z_][a-zA-Z0-9_]*::)+[a-zA-Z_][a-zA-Z0-9_]*",
1586            SmartSelectionPrecision::Normal,
1587        ),
1588        SmartSelectionRule::new(
1589            "Quoted string",
1590            r#""(?:[^"\\]|\\.)*""#,
1591            SmartSelectionPrecision::Normal,
1592        ),
1593        SmartSelectionRule::new(
1594            "UUID",
1595            r"\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b",
1596            SmartSelectionPrecision::Normal,
1597        ),
1598        // Note: No "whitespace-bounded" catch-all pattern here - that would defeat
1599        // the purpose of configurable word_characters. If no smart pattern matches,
1600        // selection falls back to word boundary detection using word_characters.
1601    ]
1602}
1603
1604// ============================================================================
1605// Rendering Types
1606// ============================================================================
1607
1608/// Per-pane background image configuration (runtime state)
1609#[derive(Debug, Clone, Default)]
1610pub struct PaneBackground {
1611    /// Path to the background image (None = use global background)
1612    pub image_path: Option<String>,
1613    /// Display mode (fit/fill/stretch/tile/center)
1614    pub mode: BackgroundImageMode,
1615    /// Opacity (0.0-1.0)
1616    pub opacity: f32,
1617    /// Darken amount (0.0 = no darkening, 1.0 = fully black)
1618    pub darken: f32,
1619}
1620
1621impl PaneBackground {
1622    /// Create a new PaneBackground with default settings
1623    pub fn new() -> Self {
1624        Self {
1625            image_path: None,
1626            mode: BackgroundImageMode::default(),
1627            opacity: 1.0,
1628            darken: 0.0,
1629        }
1630    }
1631
1632    /// Returns true if this pane has a custom background image set
1633    pub fn has_image(&self) -> bool {
1634        self.image_path.is_some()
1635    }
1636}
1637
1638/// A divider rectangle between panes
1639#[derive(Debug, Clone, Copy)]
1640pub struct DividerRect {
1641    /// X position in pixels
1642    pub x: f32,
1643    /// Y position in pixels
1644    pub y: f32,
1645    /// Width in pixels
1646    pub width: f32,
1647    /// Height in pixels
1648    pub height: f32,
1649    /// Whether this is a horizontal divider (vertical line)
1650    pub is_horizontal: bool,
1651}
1652
1653impl DividerRect {
1654    /// Create a new divider rect
1655    pub fn new(x: f32, y: f32, width: f32, height: f32, is_horizontal: bool) -> Self {
1656        Self {
1657            x,
1658            y,
1659            width,
1660            height,
1661            is_horizontal,
1662        }
1663    }
1664
1665    /// Check if a point is inside the divider (with optional padding for easier grabbing)
1666    pub fn contains(&self, px: f32, py: f32, padding: f32) -> bool {
1667        px >= self.x - padding
1668            && px < self.x + self.width + padding
1669            && py >= self.y - padding
1670            && py < self.y + self.height + padding
1671    }
1672}
1673
1674/// Visible command separator mark: (row, col_offset, optional_color)
1675pub type SeparatorMark = (usize, Option<i32>, Option<(u8, u8, u8)>);
1676
1677// ============================================================================
1678// Shared ID Types
1679// ============================================================================
1680
1681/// Unique identifier for a pane
1682pub type PaneId = u64;
1683
1684/// Unique identifier for a tab
1685pub type TabId = u64;