par_term/
config.rs

1use anyhow::Result;
2use serde::{Deserialize, Serialize};
3use std::fs;
4use std::path::PathBuf;
5
6/// VSync mode (presentation mode)
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)]
8#[serde(rename_all = "lowercase")]
9pub enum VsyncMode {
10    /// No VSync - render as fast as possible (lowest latency, highest GPU usage)
11    #[default]
12    Immediate,
13    /// Mailbox VSync - cap at monitor refresh rate with triple buffering (balanced)
14    Mailbox,
15    /// FIFO VSync - strict vsync with double buffering (lowest GPU usage, slight input lag)
16    Fifo,
17}
18
19impl VsyncMode {
20    /// Convert to wgpu::PresentMode
21    pub fn to_present_mode(self) -> wgpu::PresentMode {
22        match self {
23            VsyncMode::Immediate => wgpu::PresentMode::Immediate,
24            VsyncMode::Mailbox => wgpu::PresentMode::Mailbox,
25            VsyncMode::Fifo => wgpu::PresentMode::Fifo,
26        }
27    }
28}
29
30use crate::themes::Theme;
31
32/// Cursor style
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
34#[serde(rename_all = "lowercase")]
35pub enum CursorStyle {
36    /// Block cursor (fills entire cell)
37    #[default]
38    Block,
39    /// Beam cursor (vertical line at cell start)
40    Beam,
41    /// Underline cursor (horizontal line at cell bottom)
42    Underline,
43}
44
45/// Background image display mode
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
47#[serde(rename_all = "lowercase")]
48pub enum BackgroundImageMode {
49    /// Scale to fit window while maintaining aspect ratio (may have letterboxing)
50    Fit,
51    /// Scale to fill window while maintaining aspect ratio (may crop edges)
52    Fill,
53    /// Stretch to fill window exactly (ignores aspect ratio)
54    #[default]
55    Stretch,
56    /// Repeat image in a tiled pattern at original size
57    Tile,
58    /// Center image at original size (no scaling)
59    Center,
60}
61
62/// Font mapping for a specific Unicode range
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct FontRange {
65    /// Start of Unicode range (inclusive), e.g., 0x4E00 for CJK
66    pub start: u32,
67    /// End of Unicode range (inclusive), e.g., 0x9FFF for CJK
68    pub end: u32,
69    /// Font family name to use for this range
70    pub font_family: String,
71}
72
73/// Configuration for the terminal emulator
74/// Aligned with par-tui-term naming conventions for consistency
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct Config {
77    // ========================================================================
78    // Window & Display (GUI-specific)
79    // ========================================================================
80    /// Number of columns in the terminal
81    #[serde(default = "default_cols")]
82    pub cols: usize,
83
84    /// Number of rows in the terminal
85    #[serde(default = "default_rows")]
86    pub rows: usize,
87
88    /// Font size in points
89    #[serde(default = "default_font_size")]
90    pub font_size: f32,
91
92    /// Font family name (regular/normal weight)
93    #[serde(default = "default_font_family")]
94    pub font_family: String,
95
96    /// Bold font family name (optional, defaults to font_family)
97    #[serde(default)]
98    pub font_family_bold: Option<String>,
99
100    /// Italic font family name (optional, defaults to font_family)
101    #[serde(default)]
102    pub font_family_italic: Option<String>,
103
104    /// Bold italic font family name (optional, defaults to font_family)
105    #[serde(default)]
106    pub font_family_bold_italic: Option<String>,
107
108    /// Custom font mappings for specific Unicode ranges
109    /// Format: Vec of (start_codepoint, end_codepoint, font_family_name)
110    /// Example: [(0x4E00, 0x9FFF, "Noto Sans CJK SC")] for CJK Unified Ideographs
111    #[serde(default)]
112    pub font_ranges: Vec<FontRange>,
113
114    /// Line height multiplier (1.0 = tight, 1.2 = default, 1.5 = spacious)
115    #[serde(default = "default_line_spacing")]
116    pub line_spacing: f32,
117
118    /// Character width multiplier (0.5 = narrow, 0.6 = default, 0.7 = wide)
119    #[serde(default = "default_char_spacing")]
120    pub char_spacing: f32,
121
122    /// Enable text shaping for ligatures and complex scripts
123    /// When enabled, uses HarfBuzz for proper ligature, emoji, and complex script rendering
124    #[serde(default = "default_text_shaping")]
125    pub enable_text_shaping: bool,
126
127    /// Enable ligatures (requires enable_text_shaping)
128    #[serde(default = "default_true")]
129    pub enable_ligatures: bool,
130
131    /// Enable kerning adjustments (requires enable_text_shaping)
132    #[serde(default = "default_true")]
133    pub enable_kerning: bool,
134
135    /// Window title
136    #[serde(default = "default_window_title")]
137    pub window_title: String,
138
139    /// Allow applications to change the window title via OSC escape sequences
140    /// When false, the window title will always be the configured window_title
141    #[serde(default = "default_true")]
142    pub allow_title_change: bool,
143
144    /// Maximum frames per second (FPS) target
145    /// Controls how frequently the terminal requests screen redraws.
146    /// Note: On macOS, actual FPS may be lower (~22-25) due to system-level
147    /// VSync throttling in wgpu/Metal, regardless of this setting.
148    /// Default: 60
149    #[serde(default = "default_max_fps", alias = "refresh_rate")]
150    pub max_fps: u32,
151
152    /// VSync mode - controls GPU frame synchronization
153    /// - immediate: No VSync, render as fast as possible (lowest latency, highest power)
154    /// - mailbox: Cap at monitor refresh rate with triple buffering (balanced)
155    /// - fifo: Strict VSync with double buffering (lowest power, slight input lag)
156    ///
157    /// Default: immediate (for maximum performance)
158    #[serde(default)]
159    pub vsync_mode: VsyncMode,
160
161    /// Window padding in pixels
162    #[serde(default = "default_window_padding")]
163    pub window_padding: f32,
164
165    /// Window opacity/transparency (0.0 = fully transparent, 1.0 = fully opaque)
166    #[serde(default = "default_window_opacity")]
167    pub window_opacity: f32,
168
169    /// Keep window always on top of other windows
170    #[serde(default = "default_false")]
171    pub window_always_on_top: bool,
172
173    /// Show window decorations (title bar, borders)
174    #[serde(default = "default_true")]
175    pub window_decorations: bool,
176
177    /// Initial window width in pixels
178    #[serde(default = "default_window_width")]
179    pub window_width: u32,
180
181    /// Initial window height in pixels
182    #[serde(default = "default_window_height")]
183    pub window_height: u32,
184
185    /// Background image path (optional, supports ~ for home directory)
186    #[serde(default)]
187    pub background_image: Option<String>,
188
189    /// Enable or disable background image rendering (even if a path is set)
190    #[serde(default = "default_true")]
191    pub background_image_enabled: bool,
192
193    /// Background image display mode
194    /// - fit: Scale to fit window while maintaining aspect ratio (default)
195    /// - fill: Scale to fill window while maintaining aspect ratio (may crop)
196    /// - stretch: Stretch to fill window (ignores aspect ratio)
197    /// - tile: Repeat image in a tiled pattern
198    /// - center: Center image at original size
199    #[serde(default)]
200    pub background_image_mode: BackgroundImageMode,
201
202    /// Background image opacity (0.0 = fully transparent, 1.0 = fully opaque)
203    #[serde(default = "default_background_image_opacity")]
204    pub background_image_opacity: f32,
205
206    /// Custom shader file path (GLSL format, relative to shaders folder or absolute)
207    /// Shaders are loaded from ~/.config/par-term/shaders/ by default
208    /// Supports Ghostty/Shadertoy-style GLSL shaders with iTime, iResolution, iChannel0
209    #[serde(default)]
210    pub custom_shader: Option<String>,
211
212    /// Enable or disable the custom shader (even if a path is set)
213    #[serde(default = "default_true")]
214    pub custom_shader_enabled: bool,
215
216    /// Enable animation in custom shader (updates iTime uniform each frame)
217    /// When disabled, iTime is fixed at 0.0 for static effects
218    #[serde(default = "default_true")]
219    pub custom_shader_animation: bool,
220
221    /// Animation speed multiplier for custom shader (1.0 = normal speed)
222    #[serde(default = "default_custom_shader_speed")]
223    pub custom_shader_animation_speed: f32,
224
225    /// Text opacity when using custom shader (0.0 = transparent, 1.0 = fully opaque)
226    /// This allows text to remain readable while the shader effect shows through the background
227    #[serde(default = "default_text_opacity")]
228    pub custom_shader_text_opacity: f32,
229
230    /// When enabled, the shader receives the full rendered terminal content (text + background)
231    /// and can manipulate/distort it. When disabled (default), the shader only provides
232    /// a background and text is composited on top cleanly.
233    #[serde(default = "default_false")]
234    pub custom_shader_full_content: bool,
235
236    // ========================================================================
237    // Selection & Clipboard
238    // ========================================================================
239    /// Automatically copy selected text to clipboard
240    #[serde(default = "default_true")]
241    pub auto_copy_selection: bool,
242
243    /// Include trailing newline when copying lines
244    /// Note: Inverted logic from old strip_trailing_newline_on_copy
245    #[serde(default = "default_false", alias = "strip_trailing_newline_on_copy")]
246    pub copy_trailing_newline: bool,
247
248    /// Paste on middle mouse button click
249    #[serde(default = "default_true")]
250    pub middle_click_paste: bool,
251
252    // ========================================================================
253    // Mouse Behavior
254    // ========================================================================
255    /// Mouse wheel scroll speed multiplier
256    #[serde(default = "default_scroll_speed")]
257    pub mouse_scroll_speed: f32,
258
259    /// Double-click timing threshold in milliseconds
260    #[serde(default = "default_double_click_threshold")]
261    pub mouse_double_click_threshold: u64,
262
263    /// Triple-click timing threshold in milliseconds (typically same as double-click)
264    #[serde(default = "default_triple_click_threshold")]
265    pub mouse_triple_click_threshold: u64,
266
267    // ========================================================================
268    // Scrollback & Cursor
269    // ========================================================================
270    /// Maximum number of lines to keep in scrollback buffer
271    #[serde(default = "default_scrollback", alias = "scrollback_size")]
272    pub scrollback_lines: usize,
273
274    /// Enable cursor blinking
275    #[serde(default = "default_false")]
276    pub cursor_blink: bool,
277
278    /// Cursor blink interval in milliseconds
279    #[serde(default = "default_cursor_blink_interval")]
280    pub cursor_blink_interval: u64,
281
282    /// Cursor style (block, beam, underline)
283    #[serde(default)]
284    pub cursor_style: CursorStyle,
285
286    // ========================================================================
287    // Scrollbar
288    // ========================================================================
289    /// Auto-hide scrollbar after inactivity (milliseconds, 0 = never hide)
290    #[serde(default = "default_scrollbar_autohide_delay")]
291    pub scrollbar_autohide_delay: u64,
292
293    // ========================================================================
294    // Theme & Colors
295    // ========================================================================
296    /// Color theme name to use for terminal colors
297    #[serde(default = "default_theme")]
298    pub theme: String,
299
300    // ========================================================================
301    // Screenshot
302    // ========================================================================
303    /// File format for screenshots (png, jpeg, svg, html)
304    #[serde(default = "default_screenshot_format")]
305    pub screenshot_format: String,
306
307    // ========================================================================
308    // Shell Behavior
309    // ========================================================================
310    /// Exit when shell exits
311    #[serde(default = "default_true", alias = "close_on_shell_exit")]
312    pub exit_on_shell_exit: bool,
313
314    /// Custom shell command (defaults to system shell if not specified)
315    #[serde(default)]
316    pub custom_shell: Option<String>,
317
318    /// Arguments to pass to the shell
319    #[serde(default)]
320    pub shell_args: Option<Vec<String>>,
321
322    /// Working directory for the shell (defaults to current directory)
323    #[serde(default)]
324    pub working_directory: Option<String>,
325
326    /// Environment variables to set for the shell
327    #[serde(default)]
328    pub shell_env: Option<std::collections::HashMap<String, String>>,
329
330    // ========================================================================
331    // Scrollbar (GUI-specific)
332    // ========================================================================
333    /// Scrollbar position (left or right)
334    #[serde(default = "default_scrollbar_position")]
335    pub scrollbar_position: String,
336
337    /// Scrollbar width in pixels
338    #[serde(default = "default_scrollbar_width")]
339    pub scrollbar_width: f32,
340
341    /// Scrollbar thumb color (RGBA: [r, g, b, a] where each is 0.0-1.0)
342    #[serde(default = "default_scrollbar_thumb_color")]
343    pub scrollbar_thumb_color: [f32; 4],
344
345    /// Scrollbar track color (RGBA: [r, g, b, a] where each is 0.0-1.0)
346    #[serde(default = "default_scrollbar_track_color")]
347    pub scrollbar_track_color: [f32; 4],
348
349    // ========================================================================
350    // Clipboard Sync Limits
351    // ========================================================================
352    /// Maximum clipboard sync events retained for diagnostics
353    #[serde(
354        default = "default_clipboard_max_sync_events",
355        alias = "max_clipboard_sync_events"
356    )]
357    pub clipboard_max_sync_events: usize,
358
359    /// Maximum bytes stored per clipboard sync event
360    #[serde(
361        default = "default_clipboard_max_event_bytes",
362        alias = "max_clipboard_event_bytes"
363    )]
364    pub clipboard_max_event_bytes: usize,
365
366    // ========================================================================
367    // Notifications
368    // ========================================================================
369    /// Forward BEL events to desktop notification centers
370    #[serde(default = "default_false", alias = "bell_desktop")]
371    pub notification_bell_desktop: bool,
372
373    /// Volume (0-100) for backend bell sound alerts (0 disables)
374    #[serde(default = "default_bell_sound", alias = "bell_sound")]
375    pub notification_bell_sound: u8,
376
377    /// Enable backend visual bell overlay
378    #[serde(default = "default_true", alias = "bell_visual")]
379    pub notification_bell_visual: bool,
380
381    /// Enable notifications when activity resumes after inactivity
382    #[serde(default = "default_false", alias = "activity_notifications")]
383    pub notification_activity_enabled: bool,
384
385    /// Seconds of inactivity required before an activity alert fires
386    #[serde(default = "default_activity_threshold", alias = "activity_threshold")]
387    pub notification_activity_threshold: u64,
388
389    /// Enable notifications after prolonged silence
390    #[serde(default = "default_false", alias = "silence_notifications")]
391    pub notification_silence_enabled: bool,
392
393    /// Seconds of silence before a silence alert fires
394    #[serde(default = "default_silence_threshold", alias = "silence_threshold")]
395    pub notification_silence_threshold: u64,
396
397    /// Maximum number of OSC 9/777 notification entries retained by backend
398    #[serde(
399        default = "default_notification_max_buffer",
400        alias = "max_notifications"
401    )]
402    pub notification_max_buffer: usize,
403}
404
405// Default value functions
406fn default_cols() -> usize {
407    80
408}
409
410fn default_rows() -> usize {
411    24
412}
413
414fn default_font_size() -> f32 {
415    13.0
416}
417
418fn default_font_family() -> String {
419    "JetBrains Mono".to_string()
420}
421
422fn default_line_spacing() -> f32 {
423    1.0 // Default line height multiplier
424}
425
426fn default_char_spacing() -> f32 {
427    1.0 // Default character width multiplier
428}
429
430fn default_text_shaping() -> bool {
431    true // Enabled by default - OpenType features now properly configured via Feature::from_str()
432}
433
434fn default_scrollback() -> usize {
435    10000
436}
437
438fn default_window_title() -> String {
439    "par-term".to_string()
440}
441
442fn default_theme() -> String {
443    "dark-background".to_string()
444}
445
446fn default_screenshot_format() -> String {
447    "png".to_string()
448}
449
450fn default_max_fps() -> u32 {
451    60
452}
453
454fn default_window_padding() -> f32 {
455    10.0
456}
457
458fn default_scrollbar_position() -> String {
459    "right".to_string()
460}
461
462fn default_scrollbar_width() -> f32 {
463    15.0
464}
465
466fn default_scrollbar_thumb_color() -> [f32; 4] {
467    [0.4, 0.4, 0.4, 0.95] // Medium gray, nearly opaque
468}
469
470fn default_scrollbar_track_color() -> [f32; 4] {
471    [0.15, 0.15, 0.15, 0.6] // Dark gray, semi-transparent
472}
473
474fn default_clipboard_max_sync_events() -> usize {
475    64 // Aligned with sister project
476}
477
478fn default_clipboard_max_event_bytes() -> usize {
479    2048 // Aligned with sister project
480}
481
482fn default_activity_threshold() -> u64 {
483    10 // Aligned with sister project (10 seconds)
484}
485
486fn default_silence_threshold() -> u64 {
487    300 // 5 minutes
488}
489
490fn default_notification_max_buffer() -> usize {
491    64 // Aligned with sister project
492}
493
494fn default_scroll_speed() -> f32 {
495    3.0 // Lines per scroll tick
496}
497
498fn default_double_click_threshold() -> u64 {
499    500 // 500 milliseconds
500}
501
502fn default_triple_click_threshold() -> u64 {
503    500 // 500 milliseconds (same as double-click)
504}
505
506fn default_cursor_blink_interval() -> u64 {
507    500 // 500 milliseconds (blink twice per second)
508}
509
510fn default_scrollbar_autohide_delay() -> u64 {
511    0 // 0 = never auto-hide (always visible when scrollback exists)
512}
513
514fn default_window_opacity() -> f32 {
515    1.0 // Fully opaque by default
516}
517
518fn default_window_width() -> u32 {
519    1600 // Default initial width
520}
521
522fn default_window_height() -> u32 {
523    600 // Default initial height
524}
525
526fn default_background_image_opacity() -> f32 {
527    1.0 // Fully opaque by default
528}
529
530fn default_false() -> bool {
531    false
532}
533
534fn default_true() -> bool {
535    true
536}
537
538fn default_text_opacity() -> f32 {
539    1.0 // Fully opaque text by default
540}
541
542fn default_custom_shader_speed() -> f32 {
543    1.0 // Normal animation speed
544}
545
546fn default_bell_sound() -> u8 {
547    50 // Default to 50% volume
548}
549
550impl Default for Config {
551    fn default() -> Self {
552        Self {
553            cols: default_cols(),
554            rows: default_rows(),
555            font_size: default_font_size(),
556            font_family: default_font_family(),
557            font_family_bold: None,
558            font_family_italic: None,
559            font_family_bold_italic: None,
560            font_ranges: Vec::new(),
561            line_spacing: default_line_spacing(),
562            char_spacing: default_char_spacing(),
563            enable_text_shaping: default_text_shaping(),
564            enable_ligatures: default_true(),
565            enable_kerning: default_true(),
566            scrollback_lines: default_scrollback(),
567            cursor_blink: default_false(),
568            cursor_blink_interval: default_cursor_blink_interval(),
569            cursor_style: CursorStyle::default(),
570            scrollbar_autohide_delay: default_scrollbar_autohide_delay(),
571            window_title: default_window_title(),
572            allow_title_change: default_true(),
573            theme: default_theme(),
574            auto_copy_selection: default_true(),
575            copy_trailing_newline: default_false(),
576            middle_click_paste: default_true(),
577            mouse_scroll_speed: default_scroll_speed(),
578            mouse_double_click_threshold: default_double_click_threshold(),
579            mouse_triple_click_threshold: default_triple_click_threshold(),
580            screenshot_format: default_screenshot_format(),
581            max_fps: default_max_fps(),
582            vsync_mode: VsyncMode::default(),
583            window_padding: default_window_padding(),
584            window_opacity: default_window_opacity(),
585            window_always_on_top: default_false(),
586            window_decorations: default_true(),
587            window_width: default_window_width(),
588            window_height: default_window_height(),
589            background_image: None,
590            background_image_enabled: default_true(),
591            background_image_mode: BackgroundImageMode::default(),
592            background_image_opacity: default_background_image_opacity(),
593            custom_shader: None,
594            custom_shader_enabled: default_true(),
595            custom_shader_animation: default_true(),
596            custom_shader_animation_speed: default_custom_shader_speed(),
597            custom_shader_text_opacity: default_text_opacity(),
598            custom_shader_full_content: default_false(),
599            exit_on_shell_exit: default_true(),
600            custom_shell: None,
601            shell_args: None,
602            working_directory: None,
603            shell_env: None,
604            scrollbar_position: default_scrollbar_position(),
605            scrollbar_width: default_scrollbar_width(),
606            scrollbar_thumb_color: default_scrollbar_thumb_color(),
607            scrollbar_track_color: default_scrollbar_track_color(),
608            clipboard_max_sync_events: default_clipboard_max_sync_events(),
609            clipboard_max_event_bytes: default_clipboard_max_event_bytes(),
610            notification_bell_desktop: default_false(),
611            notification_bell_sound: default_bell_sound(),
612            notification_bell_visual: default_true(),
613            notification_activity_enabled: default_false(),
614            notification_activity_threshold: default_activity_threshold(),
615            notification_silence_enabled: default_false(),
616            notification_silence_threshold: default_silence_threshold(),
617            notification_max_buffer: default_notification_max_buffer(),
618        }
619    }
620}
621
622impl Config {
623    /// Create a new configuration with default values
624    #[allow(dead_code)]
625    pub fn new() -> Self {
626        Self::default()
627    }
628
629    /// Load configuration from file or create default
630    pub fn load() -> Result<Self> {
631        let config_path = Self::config_path();
632        log::info!("Config path: {:?}", config_path);
633
634        if config_path.exists() {
635            log::info!("Loading existing config from {:?}", config_path);
636            let contents = fs::read_to_string(&config_path)?;
637            let config: Config = serde_yaml::from_str(&contents)?;
638            Ok(config)
639        } else {
640            log::info!(
641                "Config file not found, creating default at {:?}",
642                config_path
643            );
644            // Create default config and save it
645            let config = Self::default();
646            if let Err(e) = config.save() {
647                log::error!("Failed to save default config: {}", e);
648                return Err(e);
649            }
650            log::info!("Default config created successfully");
651            Ok(config)
652        }
653    }
654
655    /// Save configuration to file
656    pub fn save(&self) -> Result<()> {
657        let config_path = Self::config_path();
658
659        // Create parent directory if it doesn't exist
660        if let Some(parent) = config_path.parent() {
661            fs::create_dir_all(parent)?;
662        }
663
664        let yaml = serde_yaml::to_string(self)?;
665        fs::write(&config_path, yaml)?;
666
667        Ok(())
668    }
669
670    /// Get the configuration file path (using XDG convention)
671    pub fn config_path() -> PathBuf {
672        #[cfg(target_os = "windows")]
673        {
674            if let Some(config_dir) = dirs::config_dir() {
675                config_dir.join("par-term").join("config.yaml")
676            } else {
677                PathBuf::from("config.yaml")
678            }
679        }
680        #[cfg(not(target_os = "windows"))]
681        {
682            // Use XDG convention on all platforms: ~/.config/par-term/config.yaml
683            if let Some(home_dir) = dirs::home_dir() {
684                home_dir
685                    .join(".config")
686                    .join("par-term")
687                    .join("config.yaml")
688            } else {
689                // Fallback if home directory cannot be determined
690                PathBuf::from("config.yaml")
691            }
692        }
693    }
694
695    /// Get the shaders directory path (using XDG convention)
696    pub fn shaders_dir() -> PathBuf {
697        #[cfg(target_os = "windows")]
698        {
699            if let Some(config_dir) = dirs::config_dir() {
700                config_dir.join("par-term").join("shaders")
701            } else {
702                PathBuf::from("shaders")
703            }
704        }
705        #[cfg(not(target_os = "windows"))]
706        {
707            if let Some(home_dir) = dirs::home_dir() {
708                home_dir.join(".config").join("par-term").join("shaders")
709            } else {
710                PathBuf::from("shaders")
711            }
712        }
713    }
714
715    /// Get the full path to a shader file
716    /// If the shader path is absolute, returns it as-is
717    /// Otherwise, resolves it relative to the shaders directory
718    pub fn shader_path(shader_name: &str) -> PathBuf {
719        let path = PathBuf::from(shader_name);
720        if path.is_absolute() {
721            path
722        } else {
723            Self::shaders_dir().join(shader_name)
724        }
725    }
726
727    /// Set window dimensions
728    #[allow(dead_code)]
729    pub fn with_dimensions(mut self, cols: usize, rows: usize) -> Self {
730        self.cols = cols;
731        self.rows = rows;
732        self
733    }
734
735    /// Set font size
736    #[allow(dead_code)]
737    pub fn with_font_size(mut self, size: f32) -> Self {
738        self.font_size = size;
739        self
740    }
741
742    /// Set font family
743    #[allow(dead_code)]
744    pub fn with_font_family(mut self, family: impl Into<String>) -> Self {
745        self.font_family = family.into();
746        self
747    }
748
749    /// Set the window title
750    #[allow(dead_code)]
751    pub fn with_title(mut self, title: impl Into<String>) -> Self {
752        self.window_title = title.into();
753        self
754    }
755
756    /// Set the scrollback buffer size
757    #[allow(dead_code)]
758    pub fn with_scrollback(mut self, size: usize) -> Self {
759        self.scrollback_lines = size;
760        self
761    }
762
763    /// Load theme configuration
764    pub fn load_theme(&self) -> Theme {
765        Theme::by_name(&self.theme).unwrap_or_default()
766    }
767}