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}
252
253/// Tab visual style preset
254///
255/// Controls the cosmetic appearance of tabs (colors, sizes, spacing).
256/// Each preset applies a set of color/size/spacing adjustments to the tab bar.
257#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
258#[serde(rename_all = "snake_case")]
259pub enum TabStyle {
260    /// Default dark theme styling
261    #[default]
262    Dark,
263    /// Light theme tab styling
264    Light,
265    /// Smaller tabs, more visible terminal content
266    Compact,
267    /// Clean, minimal tab appearance
268    Minimal,
269    /// Enhanced contrast for accessibility
270    HighContrast,
271    /// Automatically switch between light/dark styles based on system theme
272    Automatic,
273}
274
275impl TabStyle {
276    /// Display name for UI
277    pub fn display_name(&self) -> &'static str {
278        match self {
279            TabStyle::Dark => "Dark",
280            TabStyle::Light => "Light",
281            TabStyle::Compact => "Compact",
282            TabStyle::Minimal => "Minimal",
283            TabStyle::HighContrast => "High Contrast",
284            TabStyle::Automatic => "Automatic",
285        }
286    }
287
288    /// All available styles for UI iteration
289    pub fn all() -> &'static [TabStyle] {
290        &[
291            TabStyle::Dark,
292            TabStyle::Light,
293            TabStyle::Compact,
294            TabStyle::Minimal,
295            TabStyle::HighContrast,
296            TabStyle::Automatic,
297        ]
298    }
299
300    /// All concrete styles (excludes Automatic) — for sub-style dropdowns
301    pub fn all_concrete() -> &'static [TabStyle] {
302        &[
303            TabStyle::Dark,
304            TabStyle::Light,
305            TabStyle::Compact,
306            TabStyle::Minimal,
307            TabStyle::HighContrast,
308        ]
309    }
310}
311
312/// Tab bar position
313#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
314#[serde(rename_all = "snake_case")]
315pub enum TabBarPosition {
316    /// Tab bar at the top of the window (default)
317    #[default]
318    Top,
319    /// Tab bar at the bottom of the window
320    Bottom,
321    /// Tab bar on the left side of the window (vertical layout)
322    Left,
323}
324
325impl TabBarPosition {
326    /// Display name for UI
327    pub fn display_name(&self) -> &'static str {
328        match self {
329            TabBarPosition::Top => "Top",
330            TabBarPosition::Bottom => "Bottom",
331            TabBarPosition::Left => "Left",
332        }
333    }
334
335    /// All available positions for UI iteration
336    pub fn all() -> &'static [TabBarPosition] {
337        &[
338            TabBarPosition::Top,
339            TabBarPosition::Bottom,
340            TabBarPosition::Left,
341        ]
342    }
343
344    /// Returns true if the tab bar is horizontal (top or bottom)
345    pub fn is_horizontal(&self) -> bool {
346        matches!(self, TabBarPosition::Top | TabBarPosition::Bottom)
347    }
348}
349
350/// Tab bar visibility mode
351#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
352#[serde(rename_all = "snake_case")]
353pub enum TabBarMode {
354    /// Always show tab bar
355    Always,
356    /// Show tab bar only when there are multiple tabs (default)
357    #[default]
358    WhenMultiple,
359    /// Never show tab bar
360    Never,
361}
362
363/// Status bar position
364#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)]
365#[serde(rename_all = "lowercase")]
366pub enum StatusBarPosition {
367    /// Status bar at the top of the window
368    Top,
369    /// Status bar at the bottom of the window (default)
370    #[default]
371    Bottom,
372}
373
374/// Window type for different display modes
375#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
376#[serde(rename_all = "snake_case")]
377pub enum WindowType {
378    /// Normal window (default)
379    #[default]
380    Normal,
381    /// Start in fullscreen mode
382    Fullscreen,
383    /// Edge-anchored window (for dropdown/Quake-style terminals)
384    /// Note: Edge-anchored windows require additional platform-specific support
385    EdgeTop,
386    /// Edge-anchored to bottom of screen
387    EdgeBottom,
388    /// Edge-anchored to left of screen
389    EdgeLeft,
390    /// Edge-anchored to right of screen
391    EdgeRight,
392}
393
394impl WindowType {
395    /// Display name for UI
396    pub fn display_name(&self) -> &'static str {
397        match self {
398            WindowType::Normal => "Normal",
399            WindowType::Fullscreen => "Fullscreen",
400            WindowType::EdgeTop => "Edge (Top)",
401            WindowType::EdgeBottom => "Edge (Bottom)",
402            WindowType::EdgeLeft => "Edge (Left)",
403            WindowType::EdgeRight => "Edge (Right)",
404        }
405    }
406
407    /// All available window types for UI iteration
408    pub fn all() -> &'static [WindowType] {
409        &[
410            WindowType::Normal,
411            WindowType::Fullscreen,
412            WindowType::EdgeTop,
413            WindowType::EdgeBottom,
414            WindowType::EdgeLeft,
415            WindowType::EdgeRight,
416        ]
417    }
418
419    /// Returns true if this is an edge-anchored window type
420    pub fn is_edge(&self) -> bool {
421        matches!(
422            self,
423            WindowType::EdgeTop
424                | WindowType::EdgeBottom
425                | WindowType::EdgeLeft
426                | WindowType::EdgeRight
427        )
428    }
429}
430
431/// Quote style for dropped file paths
432///
433/// Controls how filenames containing special characters are quoted when dropped into the terminal.
434#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
435#[serde(rename_all = "snake_case")]
436pub enum DroppedFileQuoteStyle {
437    /// Single quotes - safest for most shells (handles $, !, spaces, etc.)
438    /// Example: '/path/to/file with spaces.txt'
439    #[default]
440    SingleQuotes,
441    /// Double quotes - allows variable expansion
442    /// Example: "/path/to/file with spaces.txt"
443    DoubleQuotes,
444    /// Backslash escaping - escape individual special characters
445    /// Example: /path/to/file\ with\ spaces.txt
446    Backslash,
447    /// No quoting - insert path as-is (not recommended for paths with special chars)
448    None,
449}
450
451impl DroppedFileQuoteStyle {
452    /// Display name for UI
453    pub fn display_name(&self) -> &'static str {
454        match self {
455            DroppedFileQuoteStyle::SingleQuotes => "Single quotes ('...')",
456            DroppedFileQuoteStyle::DoubleQuotes => "Double quotes (\"...\")",
457            DroppedFileQuoteStyle::Backslash => "Backslash escaping (\\)",
458            DroppedFileQuoteStyle::None => "None (raw path)",
459        }
460    }
461
462    /// All available quote styles for UI iteration
463    pub fn all() -> &'static [DroppedFileQuoteStyle] {
464        &[
465            DroppedFileQuoteStyle::SingleQuotes,
466            DroppedFileQuoteStyle::DoubleQuotes,
467            DroppedFileQuoteStyle::Backslash,
468            DroppedFileQuoteStyle::None,
469        ]
470    }
471}
472
473/// Option/Alt key behavior mode
474///
475/// Controls what happens when Option (macOS) or Alt (Linux/Windows) key is pressed
476/// with a character key. This is essential for emacs and vim users who rely on
477/// Meta key combinations (M-x, M-f, M-b, etc.).
478#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
479#[serde(rename_all = "lowercase")]
480pub enum OptionKeyMode {
481    /// Normal - sends special characters (default macOS behavior)
482    /// Option+f → ƒ (special character)
483    Normal,
484    /// Meta - sets the high bit (8th bit) on the character
485    /// Option+f → 0xE6 (f with high bit set)
486    Meta,
487    /// Esc - sends Escape prefix before the character (most compatible)
488    /// Option+f → ESC f (escape then f)
489    /// This is the most compatible mode for terminal applications like emacs and vim
490    #[default]
491    Esc,
492}
493
494// ============================================================================
495// Modifier Remapping Types
496// ============================================================================
497
498/// Target modifier for remapping.
499///
500/// Allows remapping one modifier key to behave as another.
501/// For example, remap Caps Lock to Ctrl, or swap Ctrl and Super.
502#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
503#[serde(rename_all = "lowercase")]
504pub enum ModifierTarget {
505    /// No remapping - use the key's normal function
506    #[default]
507    None,
508    /// Remap to Control key
509    Ctrl,
510    /// Remap to Alt/Option key
511    Alt,
512    /// Remap to Shift key
513    Shift,
514    /// Remap to Super/Cmd/Windows key
515    Super,
516}
517
518impl ModifierTarget {
519    /// Display name for UI
520    pub fn display_name(&self) -> &'static str {
521        match self {
522            ModifierTarget::None => "None (disabled)",
523            ModifierTarget::Ctrl => "Ctrl",
524            ModifierTarget::Alt => "Alt/Option",
525            ModifierTarget::Shift => "Shift",
526            ModifierTarget::Super => "Super/Cmd",
527        }
528    }
529
530    /// All available targets for UI iteration
531    pub fn all() -> &'static [ModifierTarget] {
532        &[
533            ModifierTarget::None,
534            ModifierTarget::Ctrl,
535            ModifierTarget::Alt,
536            ModifierTarget::Shift,
537            ModifierTarget::Super,
538        ]
539    }
540}
541
542/// Modifier remapping configuration.
543///
544/// Allows users to remap modifier keys to different functions.
545/// This is useful for:
546/// - Swapping Ctrl and Caps Lock
547/// - Using Ctrl as Cmd on macOS
548/// - Customizing modifier layout for ergonomic keyboards
549#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
550pub struct ModifierRemapping {
551    /// What the left Ctrl key should act as
552    #[serde(default)]
553    pub left_ctrl: ModifierTarget,
554    /// What the right Ctrl key should act as
555    #[serde(default)]
556    pub right_ctrl: ModifierTarget,
557    /// What the left Alt key should act as
558    #[serde(default)]
559    pub left_alt: ModifierTarget,
560    /// What the right Alt key should act as
561    #[serde(default)]
562    pub right_alt: ModifierTarget,
563    /// What the left Super/Cmd key should act as
564    #[serde(default)]
565    pub left_super: ModifierTarget,
566    /// What the right Super/Cmd key should act as
567    #[serde(default)]
568    pub right_super: ModifierTarget,
569}
570
571/// Font mapping for a specific Unicode range
572#[derive(Debug, Clone, Serialize, Deserialize)]
573pub struct FontRange {
574    /// Start of Unicode range (inclusive), e.g., 0x4E00 for CJK
575    pub start: u32,
576    /// End of Unicode range (inclusive), e.g., 0x9FFF for CJK
577    pub end: u32,
578    /// Font family name to use for this range
579    pub font_family: String,
580}
581
582/// Thin strokes / font smoothing mode
583///
584/// Controls font stroke weight adjustment for improved rendering,
585/// particularly on high-DPI/Retina displays. Inspired by iTerm2's thin strokes feature.
586#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
587#[serde(rename_all = "snake_case")]
588pub enum ThinStrokesMode {
589    /// Never apply thin strokes
590    Never,
591    /// Apply thin strokes only on Retina/HiDPI displays (default)
592    #[default]
593    RetinaOnly,
594    /// Apply thin strokes only on dark backgrounds
595    DarkBackgroundsOnly,
596    /// Apply thin strokes only on Retina displays with dark backgrounds
597    RetinaDarkBackgroundsOnly,
598    /// Always apply thin strokes
599    Always,
600}
601
602/// Shader install prompt mode
603///
604/// Controls whether the user is prompted to install shaders when the shaders
605/// folder is missing or empty on startup.
606#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
607#[serde(rename_all = "snake_case")]
608pub enum ShaderInstallPrompt {
609    /// Ask the user if they want to install shaders (default)
610    #[default]
611    Ask,
612    /// Never ask - user declined installation
613    Never,
614    /// Shaders have been installed
615    Installed,
616}
617
618/// State of an integration's install prompt
619#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
620#[serde(rename_all = "snake_case")]
621pub enum InstallPromptState {
622    /// Prompt user when appropriate (default)
623    #[default]
624    Ask,
625    /// User said "never ask again"
626    Never,
627    /// Currently installed
628    Installed,
629}
630
631impl InstallPromptState {
632    /// Display name for UI
633    pub fn display_name(&self) -> &'static str {
634        match self {
635            Self::Ask => "Ask",
636            Self::Never => "Never",
637            Self::Installed => "Installed",
638        }
639    }
640}
641
642/// Tracks installed and prompted versions for integrations
643#[derive(Debug, Clone, Default, Serialize, Deserialize)]
644pub struct IntegrationVersions {
645    /// Version when shaders were installed
646    pub shaders_installed_version: Option<String>,
647    /// Version when user was last prompted about shaders
648    pub shaders_prompted_version: Option<String>,
649    /// Version when shell integration was installed
650    pub shell_integration_installed_version: Option<String>,
651    /// Version when user was last prompted about shell integration
652    pub shell_integration_prompted_version: Option<String>,
653}
654
655/// Startup directory mode
656///
657/// Controls where the terminal starts its working directory.
658#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
659#[serde(rename_all = "snake_case")]
660pub enum StartupDirectoryMode {
661    /// Start in the user's home directory (default)
662    #[default]
663    Home,
664    /// Remember and restore the last working directory from the previous session
665    Previous,
666    /// Start in a user-specified custom path
667    Custom,
668}
669
670impl StartupDirectoryMode {
671    /// Display name for UI
672    pub fn display_name(&self) -> &'static str {
673        match self {
674            Self::Home => "Home Directory",
675            Self::Previous => "Previous Session",
676            Self::Custom => "Custom Directory",
677        }
678    }
679
680    /// All available modes for UI iteration
681    pub fn all() -> &'static [StartupDirectoryMode] {
682        &[
683            StartupDirectoryMode::Home,
684            StartupDirectoryMode::Previous,
685            StartupDirectoryMode::Custom,
686        ]
687    }
688}
689
690/// Action to take when the shell process exits
691///
692/// Controls what happens when a shell session terminates.
693#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
694#[serde(rename_all = "snake_case")]
695pub enum ShellExitAction {
696    /// Close the tab/pane when shell exits (default)
697    #[default]
698    Close,
699    /// Keep the pane open showing the terminated shell
700    Keep,
701    /// Immediately restart the shell
702    RestartImmediately,
703    /// Show a prompt message and wait for Enter before restarting
704    RestartWithPrompt,
705    /// Restart the shell after a 1 second delay
706    RestartAfterDelay,
707}
708
709impl ShellExitAction {
710    /// Display name for UI
711    pub fn display_name(&self) -> &'static str {
712        match self {
713            Self::Close => "Close tab/pane",
714            Self::Keep => "Keep open",
715            Self::RestartImmediately => "Restart immediately",
716            Self::RestartWithPrompt => "Restart with prompt",
717            Self::RestartAfterDelay => "Restart after 1s delay",
718        }
719    }
720
721    /// All available actions for UI iteration
722    pub fn all() -> &'static [ShellExitAction] {
723        &[
724            ShellExitAction::Close,
725            ShellExitAction::Keep,
726            ShellExitAction::RestartImmediately,
727            ShellExitAction::RestartWithPrompt,
728            ShellExitAction::RestartAfterDelay,
729        ]
730    }
731
732    /// Returns true if this action involves restarting the shell
733    pub fn is_restart(&self) -> bool {
734        matches!(
735            self,
736            Self::RestartImmediately | Self::RestartWithPrompt | Self::RestartAfterDelay
737        )
738    }
739}
740
741/// Detected shell type
742#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
743#[serde(rename_all = "lowercase")]
744pub enum ShellType {
745    Bash,
746    Zsh,
747    Fish,
748    #[default]
749    Unknown,
750}
751
752impl ShellType {
753    /// Detect shell from $SHELL environment variable
754    pub fn detect() -> Self {
755        if let Ok(shell) = std::env::var("SHELL") {
756            if shell.contains("zsh") {
757                Self::Zsh
758            } else if shell.contains("bash") {
759                Self::Bash
760            } else if shell.contains("fish") {
761                Self::Fish
762            } else {
763                Self::Unknown
764            }
765        } else {
766            Self::Unknown
767        }
768    }
769
770    /// Display name for UI
771    pub fn display_name(&self) -> &'static str {
772        match self {
773            Self::Bash => "Bash",
774            Self::Zsh => "Zsh",
775            Self::Fish => "Fish",
776            Self::Unknown => "Unknown",
777        }
778    }
779
780    /// File extension for integration script
781    pub fn extension(&self) -> &'static str {
782        match self {
783            Self::Bash => "bash",
784            Self::Zsh => "zsh",
785            Self::Fish => "fish",
786            Self::Unknown => "sh",
787        }
788    }
789}
790
791/// Update check frequency
792///
793/// Controls how often par-term checks GitHub for new releases.
794#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
795#[serde(rename_all = "snake_case")]
796pub enum UpdateCheckFrequency {
797    /// Never check for updates
798    Never,
799    /// Check once per day (default)
800    #[default]
801    Daily,
802    /// Check once per week
803    Weekly,
804    /// Check once per month
805    Monthly,
806}
807
808impl UpdateCheckFrequency {
809    /// Get the duration in seconds for this frequency
810    pub fn as_seconds(&self) -> Option<u64> {
811        match self {
812            UpdateCheckFrequency::Never => None,
813            UpdateCheckFrequency::Daily => Some(24 * 60 * 60), // 86400
814            UpdateCheckFrequency::Weekly => Some(7 * 24 * 60 * 60), // 604800
815            UpdateCheckFrequency::Monthly => Some(30 * 24 * 60 * 60), // 2592000
816        }
817    }
818
819    /// Display name for UI
820    pub fn display_name(&self) -> &'static str {
821        match self {
822            UpdateCheckFrequency::Never => "Never",
823            UpdateCheckFrequency::Daily => "Daily",
824            UpdateCheckFrequency::Weekly => "Weekly",
825            UpdateCheckFrequency::Monthly => "Monthly",
826        }
827    }
828}
829
830// ============================================================================
831// Session Logging Types
832// ============================================================================
833
834/// Log format for session logging
835///
836/// Controls the format used when automatically logging terminal sessions.
837#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
838#[serde(rename_all = "lowercase")]
839pub enum SessionLogFormat {
840    /// Plain text - strips escape sequences, captures only printable output
841    Plain,
842    /// HTML - preserves colors and styling as HTML
843    Html,
844    /// Asciicast v2 - asciinema-compatible format for replay/sharing
845    #[default]
846    Asciicast,
847}
848
849impl SessionLogFormat {
850    /// Display name for UI
851    pub fn display_name(&self) -> &'static str {
852        match self {
853            SessionLogFormat::Plain => "Plain Text",
854            SessionLogFormat::Html => "HTML",
855            SessionLogFormat::Asciicast => "Asciicast (asciinema)",
856        }
857    }
858
859    /// All available formats for UI iteration
860    pub fn all() -> &'static [SessionLogFormat] {
861        &[
862            SessionLogFormat::Plain,
863            SessionLogFormat::Html,
864            SessionLogFormat::Asciicast,
865        ]
866    }
867
868    /// File extension for this format
869    pub fn extension(&self) -> &'static str {
870        match self {
871            SessionLogFormat::Plain => "txt",
872            SessionLogFormat::Html => "html",
873            SessionLogFormat::Asciicast => "cast",
874        }
875    }
876}
877
878/// Log level for debug logging to file.
879///
880/// Controls the verbosity of log output written to the debug log file.
881/// Environment variables `RUST_LOG` and `--log-level` CLI flag take precedence.
882#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
883#[serde(rename_all = "lowercase")]
884pub enum LogLevel {
885    /// No logging (log file not created)
886    #[default]
887    Off,
888    /// Errors only
889    Error,
890    /// Warnings and errors
891    Warn,
892    /// Informational messages
893    Info,
894    /// Debug messages
895    Debug,
896    /// Most verbose
897    Trace,
898}
899
900impl LogLevel {
901    /// Display name for UI
902    pub fn display_name(&self) -> &'static str {
903        match self {
904            LogLevel::Off => "Off",
905            LogLevel::Error => "Error",
906            LogLevel::Warn => "Warn",
907            LogLevel::Info => "Info",
908            LogLevel::Debug => "Debug",
909            LogLevel::Trace => "Trace",
910        }
911    }
912
913    /// All available levels for UI iteration
914    pub fn all() -> &'static [LogLevel] {
915        &[
916            LogLevel::Off,
917            LogLevel::Error,
918            LogLevel::Warn,
919            LogLevel::Info,
920            LogLevel::Debug,
921            LogLevel::Trace,
922        ]
923    }
924
925    /// Convert to `log::LevelFilter`
926    pub fn to_level_filter(self) -> log::LevelFilter {
927        match self {
928            LogLevel::Off => log::LevelFilter::Off,
929            LogLevel::Error => log::LevelFilter::Error,
930            LogLevel::Warn => log::LevelFilter::Warn,
931            LogLevel::Info => log::LevelFilter::Info,
932            LogLevel::Debug => log::LevelFilter::Debug,
933            LogLevel::Trace => log::LevelFilter::Trace,
934        }
935    }
936}
937
938/// Editor selection mode for semantic history
939///
940/// Controls how the editor is selected when opening files via semantic history.
941#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
942#[serde(rename_all = "snake_case")]
943pub enum SemanticHistoryEditorMode {
944    /// Use custom editor command from `semantic_history_editor` setting
945    Custom,
946    /// Use $EDITOR or $VISUAL environment variable
947    #[default]
948    EnvironmentVariable,
949    /// Use system default application for each file type
950    SystemDefault,
951}
952
953impl SemanticHistoryEditorMode {
954    /// Display name for UI
955    pub fn display_name(&self) -> &'static str {
956        match self {
957            SemanticHistoryEditorMode::Custom => "Custom Editor",
958            SemanticHistoryEditorMode::EnvironmentVariable => "Environment Variable ($EDITOR)",
959            SemanticHistoryEditorMode::SystemDefault => "System Default",
960        }
961    }
962
963    /// All available modes for UI iteration
964    pub fn all() -> &'static [SemanticHistoryEditorMode] {
965        &[
966            SemanticHistoryEditorMode::Custom,
967            SemanticHistoryEditorMode::EnvironmentVariable,
968            SemanticHistoryEditorMode::SystemDefault,
969        ]
970    }
971}
972
973// ============================================================================
974// Per-Shader Configuration Types
975// ============================================================================
976
977/// Metadata embedded in shader files via YAML block comments.
978///
979/// Parsed from `/*! par-term shader metadata ... */` blocks at the top of shader files.
980#[derive(Debug, Clone, Default, Serialize, Deserialize)]
981pub struct ShaderMetadata {
982    /// Human-readable name for the shader (e.g., "CRT Effect")
983    pub name: Option<String>,
984    /// Author of the shader
985    pub author: Option<String>,
986    /// Description of what the shader does
987    pub description: Option<String>,
988    /// Version string (e.g., "1.0.0")
989    pub version: Option<String>,
990    /// Default configuration values for this shader
991    #[serde(default)]
992    pub defaults: ShaderConfig,
993}
994
995/// Per-shader configuration settings.
996///
997/// Used both for embedded defaults in shader files and for user overrides in config.yaml.
998/// All fields are optional to allow partial overrides.
999#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1000pub struct ShaderConfig {
1001    /// Animation speed multiplier (1.0 = normal speed)
1002    pub animation_speed: Option<f32>,
1003    /// Brightness multiplier (0.05-1.0)
1004    pub brightness: Option<f32>,
1005    /// Text opacity when using this shader (0.0-1.0)
1006    pub text_opacity: Option<f32>,
1007    /// When true, shader receives full terminal content for manipulation
1008    pub full_content: Option<bool>,
1009    /// Path to texture for iChannel0
1010    pub channel0: Option<String>,
1011    /// Path to texture for iChannel1
1012    pub channel1: Option<String>,
1013    /// Path to texture for iChannel2
1014    pub channel2: Option<String>,
1015    /// Path to texture for iChannel3
1016    pub channel3: Option<String>,
1017    /// Path prefix for cubemap faces
1018    pub cubemap: Option<String>,
1019    /// Whether cubemap sampling is enabled
1020    pub cubemap_enabled: Option<bool>,
1021    /// Use the app's background image as iChannel0 instead of a separate texture
1022    pub use_background_as_channel0: Option<bool>,
1023}
1024
1025/// Cursor shader specific configuration.
1026///
1027/// Extends base ShaderConfig with cursor-specific settings.
1028#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1029pub struct CursorShaderConfig {
1030    /// Base shader configuration
1031    #[serde(flatten)]
1032    pub base: ShaderConfig,
1033    /// Hide the default cursor when this shader is enabled
1034    pub hides_cursor: Option<bool>,
1035    /// Disable cursor shader while in alt screen (vim, less, htop)
1036    pub disable_in_alt_screen: Option<bool>,
1037    /// Cursor glow radius in pixels
1038    pub glow_radius: Option<f32>,
1039    /// Cursor glow intensity (0.0-1.0)
1040    pub glow_intensity: Option<f32>,
1041    /// Duration of cursor trail effect in seconds
1042    pub trail_duration: Option<f32>,
1043    /// Cursor color for shader effects [R, G, B] (0-255)
1044    pub cursor_color: Option<[u8; 3]>,
1045}
1046
1047/// Metadata embedded in cursor shader files via YAML block comments.
1048///
1049/// Parsed from `/*! par-term shader metadata ... */` blocks at the top of cursor shader files.
1050/// Similar to `ShaderMetadata` but with cursor-specific defaults.
1051#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1052pub struct CursorShaderMetadata {
1053    /// Human-readable name for the shader (e.g., "Cursor Glow Effect")
1054    pub name: Option<String>,
1055    /// Author of the shader
1056    pub author: Option<String>,
1057    /// Description of what the shader does
1058    pub description: Option<String>,
1059    /// Version string (e.g., "1.0.0")
1060    pub version: Option<String>,
1061    /// Default configuration values for this cursor shader
1062    #[serde(default)]
1063    pub defaults: CursorShaderConfig,
1064}
1065
1066/// Fully resolved shader configuration with all values filled in.
1067///
1068/// Created by merging user overrides, shader metadata defaults, and global defaults.
1069#[derive(Debug, Clone)]
1070#[allow(dead_code)]
1071pub struct ResolvedShaderConfig {
1072    /// Animation speed multiplier
1073    pub animation_speed: f32,
1074    /// Brightness multiplier
1075    pub brightness: f32,
1076    /// Text opacity
1077    pub text_opacity: f32,
1078    /// Full content mode enabled
1079    pub full_content: bool,
1080    /// Resolved path to iChannel0 texture
1081    pub channel0: Option<PathBuf>,
1082    /// Resolved path to iChannel1 texture
1083    pub channel1: Option<PathBuf>,
1084    /// Resolved path to iChannel2 texture
1085    pub channel2: Option<PathBuf>,
1086    /// Resolved path to iChannel3 texture
1087    pub channel3: Option<PathBuf>,
1088    /// Resolved cubemap path prefix
1089    pub cubemap: Option<PathBuf>,
1090    /// Cubemap sampling enabled
1091    pub cubemap_enabled: bool,
1092    /// Use the app's background image as iChannel0
1093    pub use_background_as_channel0: bool,
1094}
1095
1096impl Default for ResolvedShaderConfig {
1097    fn default() -> Self {
1098        Self {
1099            animation_speed: 1.0,
1100            brightness: 1.0,
1101            text_opacity: 1.0,
1102            full_content: false,
1103            channel0: None,
1104            channel1: None,
1105            channel2: None,
1106            channel3: None,
1107            cubemap: None,
1108            cubemap_enabled: true,
1109            use_background_as_channel0: false,
1110        }
1111    }
1112}
1113
1114/// Fully resolved cursor shader configuration with all values filled in.
1115#[derive(Debug, Clone)]
1116#[allow(dead_code)]
1117pub struct ResolvedCursorShaderConfig {
1118    /// Base resolved shader config
1119    pub base: ResolvedShaderConfig,
1120    /// Hide the default cursor when this shader is enabled
1121    pub hides_cursor: bool,
1122    /// Disable cursor shader while in alt screen (vim, less, htop)
1123    pub disable_in_alt_screen: bool,
1124    /// Cursor glow radius in pixels
1125    pub glow_radius: f32,
1126    /// Cursor glow intensity (0.0-1.0)
1127    pub glow_intensity: f32,
1128    /// Duration of cursor trail effect in seconds
1129    pub trail_duration: f32,
1130    /// Cursor color for shader effects [R, G, B] (0-255)
1131    pub cursor_color: [u8; 3],
1132}
1133
1134impl Default for ResolvedCursorShaderConfig {
1135    fn default() -> Self {
1136        Self {
1137            base: ResolvedShaderConfig::default(),
1138            hides_cursor: false,
1139            disable_in_alt_screen: true,
1140            glow_radius: 80.0,
1141            glow_intensity: 0.3,
1142            trail_duration: 0.5,
1143            cursor_color: [255, 255, 255],
1144        }
1145    }
1146}
1147
1148// ============================================================================
1149// Progress Bar Types
1150// ============================================================================
1151
1152/// Progress bar visual style
1153#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1154#[serde(rename_all = "lowercase")]
1155pub enum ProgressBarStyle {
1156    /// Thin bar line (default)
1157    #[default]
1158    Bar,
1159    /// Bar with percentage text
1160    BarWithText,
1161}
1162
1163impl ProgressBarStyle {
1164    /// Display name for UI
1165    pub fn display_name(&self) -> &'static str {
1166        match self {
1167            ProgressBarStyle::Bar => "Bar",
1168            ProgressBarStyle::BarWithText => "Bar with Text",
1169        }
1170    }
1171
1172    /// All available styles for UI iteration
1173    pub fn all() -> &'static [ProgressBarStyle] {
1174        &[ProgressBarStyle::Bar, ProgressBarStyle::BarWithText]
1175    }
1176}
1177
1178/// Progress bar position on screen
1179#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1180#[serde(rename_all = "lowercase")]
1181pub enum ProgressBarPosition {
1182    /// Top of the terminal window (default)
1183    #[default]
1184    Top,
1185    /// Bottom of the terminal window
1186    Bottom,
1187}
1188
1189impl ProgressBarPosition {
1190    /// Display name for UI
1191    pub fn display_name(&self) -> &'static str {
1192        match self {
1193            ProgressBarPosition::Bottom => "Bottom",
1194            ProgressBarPosition::Top => "Top",
1195        }
1196    }
1197
1198    /// All available positions for UI iteration
1199    pub fn all() -> &'static [ProgressBarPosition] {
1200        &[ProgressBarPosition::Top, ProgressBarPosition::Bottom]
1201    }
1202}
1203
1204// ============================================================================
1205// Smart Selection Types
1206// ============================================================================
1207
1208/// Precision level for smart selection rules.
1209///
1210/// Higher precision rules are checked first and match more specific patterns.
1211/// Based on iTerm2's smart selection precision levels.
1212#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1213#[serde(rename_all = "snake_case")]
1214pub enum SmartSelectionPrecision {
1215    /// Very low precision (0.00001) - matches almost anything
1216    VeryLow,
1217    /// Low precision (0.001) - broad patterns
1218    Low,
1219    /// Normal precision (1.0) - standard patterns
1220    #[default]
1221    Normal,
1222    /// High precision (1000.0) - specific patterns
1223    High,
1224    /// Very high precision (1000000.0) - most specific patterns (checked first)
1225    VeryHigh,
1226}
1227
1228impl SmartSelectionPrecision {
1229    /// Get the numeric precision value for sorting
1230    pub fn value(&self) -> f64 {
1231        match self {
1232            SmartSelectionPrecision::VeryLow => 0.00001,
1233            SmartSelectionPrecision::Low => 0.001,
1234            SmartSelectionPrecision::Normal => 1.0,
1235            SmartSelectionPrecision::High => 1000.0,
1236            SmartSelectionPrecision::VeryHigh => 1_000_000.0,
1237        }
1238    }
1239
1240    /// Display name for UI
1241    pub fn display_name(&self) -> &'static str {
1242        match self {
1243            SmartSelectionPrecision::VeryLow => "Very Low",
1244            SmartSelectionPrecision::Low => "Low",
1245            SmartSelectionPrecision::Normal => "Normal",
1246            SmartSelectionPrecision::High => "High",
1247            SmartSelectionPrecision::VeryHigh => "Very High",
1248        }
1249    }
1250}
1251
1252/// Position of pane title bars
1253#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1254#[serde(rename_all = "snake_case")]
1255pub enum PaneTitlePosition {
1256    /// Title bar at the top of the pane (default)
1257    #[default]
1258    Top,
1259    /// Title bar at the bottom of the pane
1260    Bottom,
1261}
1262
1263impl PaneTitlePosition {
1264    /// All available positions for UI dropdowns
1265    pub const ALL: &'static [PaneTitlePosition] =
1266        &[PaneTitlePosition::Top, PaneTitlePosition::Bottom];
1267
1268    /// Display name for UI
1269    pub fn display_name(&self) -> &'static str {
1270        match self {
1271            PaneTitlePosition::Top => "Top",
1272            PaneTitlePosition::Bottom => "Bottom",
1273        }
1274    }
1275}
1276
1277/// Style of dividers between panes
1278#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
1279#[serde(rename_all = "snake_case")]
1280pub enum DividerStyle {
1281    /// Solid line (default)
1282    #[default]
1283    Solid,
1284    /// Double line effect (two thin lines with gap)
1285    Double,
1286    /// Dashed line effect
1287    Dashed,
1288    /// Shadow effect (gradient fade)
1289    Shadow,
1290}
1291
1292impl DividerStyle {
1293    /// All available styles for UI dropdowns
1294    pub const ALL: &'static [DividerStyle] = &[
1295        DividerStyle::Solid,
1296        DividerStyle::Double,
1297        DividerStyle::Dashed,
1298        DividerStyle::Shadow,
1299    ];
1300
1301    /// Display name for UI
1302    pub fn display_name(&self) -> &'static str {
1303        match self {
1304            DividerStyle::Solid => "Solid",
1305            DividerStyle::Double => "Double",
1306            DividerStyle::Dashed => "Dashed",
1307            DividerStyle::Shadow => "Shadow",
1308        }
1309    }
1310}
1311
1312/// Terminal events that can trigger alert sounds
1313#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1314#[serde(rename_all = "snake_case")]
1315pub enum AlertEvent {
1316    /// Bell character received (BEL / 0x07)
1317    Bell,
1318    /// Command completed (requires shell integration)
1319    CommandComplete,
1320    /// A new tab was created
1321    NewTab,
1322    /// A tab was closed
1323    TabClose,
1324}
1325
1326impl AlertEvent {
1327    /// Display name for UI
1328    pub fn display_name(&self) -> &'static str {
1329        match self {
1330            AlertEvent::Bell => "Bell",
1331            AlertEvent::CommandComplete => "Command Complete",
1332            AlertEvent::NewTab => "New Tab",
1333            AlertEvent::TabClose => "Tab Close",
1334        }
1335    }
1336
1337    /// All available events for UI iteration
1338    pub fn all() -> &'static [AlertEvent] {
1339        &[
1340            AlertEvent::Bell,
1341            AlertEvent::CommandComplete,
1342            AlertEvent::NewTab,
1343            AlertEvent::TabClose,
1344        ]
1345    }
1346}
1347
1348/// Configuration for an alert sound tied to a specific event
1349#[derive(Debug, Clone, Serialize, Deserialize)]
1350pub struct AlertSoundConfig {
1351    /// Whether this alert sound is enabled
1352    #[serde(default = "crate::defaults::bool_true")]
1353    pub enabled: bool,
1354    /// Volume 0-100 (0 effectively disables)
1355    #[serde(default = "crate::defaults::bell_sound")]
1356    pub volume: u8,
1357    /// Optional path to a custom sound file (WAV/OGG/FLAC).
1358    /// If None, uses built-in tone with the configured frequency.
1359    #[serde(default)]
1360    pub sound_file: Option<String>,
1361    /// Frequency in Hz for the built-in tone (used when sound_file is None)
1362    #[serde(default = "default_alert_frequency")]
1363    pub frequency: f32,
1364    /// Duration of the built-in tone in milliseconds
1365    #[serde(default = "default_alert_duration_ms")]
1366    pub duration_ms: u64,
1367}
1368
1369fn default_alert_frequency() -> f32 {
1370    800.0
1371}
1372
1373fn default_alert_duration_ms() -> u64 {
1374    100
1375}
1376
1377impl Default for AlertSoundConfig {
1378    fn default() -> Self {
1379        Self {
1380            enabled: true,
1381            volume: 50,
1382            sound_file: None,
1383            frequency: 800.0,
1384            duration_ms: 100,
1385        }
1386    }
1387}
1388
1389/// A smart selection rule for pattern-based text selection.
1390///
1391/// When double-clicking, rules are evaluated by precision (highest first).
1392/// If a pattern matches at the cursor position, that text is selected.
1393#[derive(Debug, Clone, Serialize, Deserialize)]
1394pub struct SmartSelectionRule {
1395    /// Human-readable name for this rule (e.g., "HTTP URL", "Email address")
1396    pub name: String,
1397    /// Regular expression pattern to match
1398    pub regex: String,
1399    /// Precision level - higher precision rules are checked first
1400    #[serde(default)]
1401    pub precision: SmartSelectionPrecision,
1402    /// Whether this rule is enabled
1403    #[serde(default = "default_enabled")]
1404    pub enabled: bool,
1405}
1406
1407fn default_enabled() -> bool {
1408    true
1409}
1410
1411impl SmartSelectionRule {
1412    /// Create a new smart selection rule
1413    pub fn new(
1414        name: impl Into<String>,
1415        regex: impl Into<String>,
1416        precision: SmartSelectionPrecision,
1417    ) -> Self {
1418        Self {
1419            name: name.into(),
1420            regex: regex.into(),
1421            precision,
1422            enabled: true,
1423        }
1424    }
1425}
1426
1427/// Get the default smart selection rules (based on iTerm2's defaults)
1428pub fn default_smart_selection_rules() -> Vec<SmartSelectionRule> {
1429    vec![
1430        // Very High precision - most specific, checked first
1431        SmartSelectionRule::new(
1432            "HTTP URL",
1433            r"https?://[^\s<>\[\]{}|\\^`\x00-\x1f]+",
1434            SmartSelectionPrecision::VeryHigh,
1435        ),
1436        SmartSelectionRule::new(
1437            "SSH URL",
1438            r"\bssh://([a-zA-Z0-9_]+@)?([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+(/[^\s]*)?",
1439            SmartSelectionPrecision::VeryHigh,
1440        ),
1441        SmartSelectionRule::new(
1442            "Git URL",
1443            r"\bgit://([a-zA-Z0-9_]+@)?([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+(/[^\s]*)?",
1444            SmartSelectionPrecision::VeryHigh,
1445        ),
1446        SmartSelectionRule::new(
1447            "File URL",
1448            r"file://[^\s]+",
1449            SmartSelectionPrecision::VeryHigh,
1450        ),
1451        // High precision
1452        SmartSelectionRule::new(
1453            "Email address",
1454            r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b",
1455            SmartSelectionPrecision::High,
1456        ),
1457        SmartSelectionRule::new(
1458            "IPv4 address",
1459            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",
1460            SmartSelectionPrecision::High,
1461        ),
1462        // Normal precision
1463        SmartSelectionRule::new(
1464            "File path",
1465            r"~?/?(?:[a-zA-Z0-9._-]+/)+[a-zA-Z0-9._-]+/?",
1466            SmartSelectionPrecision::Normal,
1467        ),
1468        SmartSelectionRule::new(
1469            "Java/Python import",
1470            // Require at least 2 dots to avoid matching simple filenames like "file.txt"
1471            r"(?:[a-zA-Z_][a-zA-Z0-9_]*\.){2,}[a-zA-Z_][a-zA-Z0-9_]*",
1472            SmartSelectionPrecision::Normal,
1473        ),
1474        SmartSelectionRule::new(
1475            "C++ namespace",
1476            r"(?:[a-zA-Z_][a-zA-Z0-9_]*::)+[a-zA-Z_][a-zA-Z0-9_]*",
1477            SmartSelectionPrecision::Normal,
1478        ),
1479        SmartSelectionRule::new(
1480            "Quoted string",
1481            r#""(?:[^"\\]|\\.)*""#,
1482            SmartSelectionPrecision::Normal,
1483        ),
1484        SmartSelectionRule::new(
1485            "UUID",
1486            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",
1487            SmartSelectionPrecision::Normal,
1488        ),
1489        // Note: No "whitespace-bounded" catch-all pattern here - that would defeat
1490        // the purpose of configurable word_characters. If no smart pattern matches,
1491        // selection falls back to word boundary detection using word_characters.
1492    ]
1493}
1494
1495// ============================================================================
1496// Rendering Types
1497// ============================================================================
1498
1499/// Per-pane background image configuration (runtime state)
1500#[derive(Debug, Clone, Default)]
1501pub struct PaneBackground {
1502    /// Path to the background image (None = use global background)
1503    pub image_path: Option<String>,
1504    /// Display mode (fit/fill/stretch/tile/center)
1505    pub mode: BackgroundImageMode,
1506    /// Opacity (0.0-1.0)
1507    pub opacity: f32,
1508}
1509
1510impl PaneBackground {
1511    /// Create a new PaneBackground with default settings
1512    pub fn new() -> Self {
1513        Self {
1514            image_path: None,
1515            mode: BackgroundImageMode::default(),
1516            opacity: 1.0,
1517        }
1518    }
1519
1520    /// Returns true if this pane has a custom background image set
1521    pub fn has_image(&self) -> bool {
1522        self.image_path.is_some()
1523    }
1524}
1525
1526/// A divider rectangle between panes
1527#[derive(Debug, Clone, Copy)]
1528pub struct DividerRect {
1529    /// X position in pixels
1530    pub x: f32,
1531    /// Y position in pixels
1532    pub y: f32,
1533    /// Width in pixels
1534    pub width: f32,
1535    /// Height in pixels
1536    pub height: f32,
1537    /// Whether this is a horizontal divider (vertical line)
1538    pub is_horizontal: bool,
1539}
1540
1541impl DividerRect {
1542    /// Create a new divider rect
1543    pub fn new(x: f32, y: f32, width: f32, height: f32, is_horizontal: bool) -> Self {
1544        Self {
1545            x,
1546            y,
1547            width,
1548            height,
1549            is_horizontal,
1550        }
1551    }
1552
1553    /// Check if a point is inside the divider (with optional padding for easier grabbing)
1554    pub fn contains(&self, px: f32, py: f32, padding: f32) -> bool {
1555        px >= self.x - padding
1556            && px < self.x + self.width + padding
1557            && py >= self.y - padding
1558            && py < self.y + self.height + padding
1559    }
1560}
1561
1562/// Visible command separator mark: (row, col_offset, optional_color)
1563pub type SeparatorMark = (usize, Option<i32>, Option<(u8, u8, u8)>);