Skip to main content

pacsea/theme/
types.rs

1use std::collections::HashMap;
2
3use crossterm::event::{KeyCode, KeyModifiers};
4use ratatui::style::Color;
5
6/// Application theme palette used by rendering code.
7///
8/// All colors are provided as [`ratatui::style::Color`] and are suitable for
9/// direct use with widgets and styles.
10#[derive(Clone, Copy, Debug)]
11pub struct Theme {
12    /// Primary background color for the canvas.
13    pub base: Color,
14    /// Slightly lighter background layer used behind panels.
15    pub mantle: Color,
16    /// Darkest background shade for deep contrast areas.
17    pub crust: Color,
18    /// Subtle surface color for component backgrounds (level 1).
19    pub surface1: Color,
20    /// Subtle surface color for component backgrounds (level 2).
21    pub surface2: Color,
22    /// Muted overlay line/border color (primary).
23    pub overlay1: Color,
24    /// Muted overlay line/border color (secondary).
25    pub overlay2: Color,
26    /// Primary foreground text color.
27    pub text: Color,
28    /// Secondary text for less prominent content.
29    pub subtext0: Color,
30    /// Tertiary text for captions and low-emphasis content.
31    pub subtext1: Color,
32    /// Accent color commonly used for selection and interactive highlights.
33    pub sapphire: Color,
34    /// Accent color for emphasized headings or selections.
35    pub mauve: Color,
36    /// Success/positive state color.
37    pub green: Color,
38    /// Warning/attention state color.
39    pub yellow: Color,
40    /// Error/danger state color.
41    pub red: Color,
42    /// Accent color for subtle emphasis and borders.
43    pub lavender: Color,
44}
45
46/// User-configurable application settings parsed from `pacsea.conf`.
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub enum PackageMarker {
49    /// Color the entire line for the marked package.
50    FullLine,
51    /// Add a marker at the front of the line.
52    Front,
53    /// Add a marker at the end of the line.
54    End,
55}
56
57/// User-configurable application settings parsed from `pacsea.conf`.
58#[derive(Clone, Debug)]
59#[allow(clippy::struct_excessive_bools)]
60pub struct Settings {
61    /// Percentage width allocated to the Recent pane (left column).
62    pub layout_left_pct: u16,
63    /// Percentage width allocated to the Search pane (center column).
64    pub layout_center_pct: u16,
65    /// Percentage width allocated to the Install pane (right column).
66    pub layout_right_pct: u16,
67    /// Vertical order of the main stack: results list, middle search row, package info (each once).
68    pub main_pane_order: [crate::state::MainVerticalPane; 3],
69    /// Minimum terminal rows for the results list band.
70    pub vertical_min_results: u16,
71    /// Maximum terminal rows for the results list band.
72    pub vertical_max_results: u16,
73    /// Minimum terminal rows for the middle (search) row.
74    pub vertical_min_middle: u16,
75    /// Maximum terminal rows for the middle row.
76    pub vertical_max_middle: u16,
77    /// Minimum terminal rows for package info when that band is shown.
78    pub vertical_min_package_info: u16,
79    /// Default value for the application's dry-run mode on startup.
80    /// This can be toggled via the `--dry-run` CLI flag.
81    pub app_dry_run_default: bool,
82    /// Configurable key bindings parsed from `pacsea.conf`
83    pub keymap: KeyMap,
84    /// Initial sort mode for results list.
85    pub sort_mode: crate::state::SortMode,
86    /// Text appended when copying PKGBUILD to clipboard.
87    pub clipboard_suffix: String,
88    /// Whether the Search history pane should be shown on startup.
89    pub show_recent_pane: bool,
90    /// Whether the Install/Remove pane should be shown on startup.
91    pub show_install_pane: bool,
92    /// Whether the keybinds footer should be shown on startup.
93    pub show_keybinds_footer: bool,
94    /// Selected countries used when updating mirrors (comma-separated or multiple).
95    pub selected_countries: String,
96    /// Number of mirrors to fetch/rank when updating.
97    pub mirror_count: u16,
98    /// `VirusTotal` API key for security scanning.
99    pub virustotal_api_key: String,
100    /// Whether to run `ClamAV` scan on AUR packages.
101    pub scan_do_clamav: bool,
102    /// Whether to run `Trivy` scan on AUR packages.
103    pub scan_do_trivy: bool,
104    /// Whether to run `Semgrep` scan on AUR packages.
105    pub scan_do_semgrep: bool,
106    /// Whether to run `ShellCheck` scan on AUR packages.
107    pub scan_do_shellcheck: bool,
108    /// Whether to run `VirusTotal` scan on AUR packages.
109    pub scan_do_virustotal: bool,
110    /// Whether to run custom scan on AUR packages.
111    pub scan_do_custom: bool,
112    /// Whether to run Sleuth scan on AUR packages.
113    pub scan_do_sleuth: bool,
114    /// Comma-separated `ShellCheck` rule IDs to pass as `--exclude` when running PKGBUILD static checks.
115    /// Empty means `ShellCheck` runs without `--exclude`.
116    pub pkgbuild_shellcheck_exclude: String,
117    /// When true, the PKGBUILD details pane may show the expandable `Raw output:` block from static checks.
118    /// When false, findings and status still show; raw command output is hidden. Defaults to false.
119    pub pkgbuild_checks_show_raw_output: bool,
120    /// Whether to start the app in News mode (true) or Package mode (false).
121    pub start_in_news: bool,
122    /// Whether to show Arch news items in the News view.
123    pub news_filter_show_arch_news: bool,
124    /// Whether to show security advisories in the News view.
125    pub news_filter_show_advisories: bool,
126    /// Whether to show installed package update items in the News view.
127    pub news_filter_show_pkg_updates: bool,
128    /// Whether to show AUR package update items in the News view.
129    pub news_filter_show_aur_updates: bool,
130    /// Whether to show installed AUR comment items in the News view.
131    pub news_filter_show_aur_comments: bool,
132    /// Whether to restrict advisories to installed packages in the News view.
133    pub news_filter_installed_only: bool,
134    /// Maximum age of news items in days (None = unlimited).
135    pub news_max_age_days: Option<u32>,
136    /// Whether startup news popup setup has been completed.
137    pub startup_news_configured: bool,
138    /// Whether to show Arch news in startup news popup.
139    pub startup_news_show_arch_news: bool,
140    /// Whether to show security advisories in startup news popup.
141    pub startup_news_show_advisories: bool,
142    /// Whether to show AUR updates in startup news popup.
143    pub startup_news_show_aur_updates: bool,
144    /// Whether to show AUR comments in startup news popup.
145    pub startup_news_show_aur_comments: bool,
146    /// Whether to show official package updates in startup news popup.
147    pub startup_news_show_pkg_updates: bool,
148    /// Maximum age of news items in days for startup news popup (None = unlimited).
149    pub startup_news_max_age_days: Option<u32>,
150    /// How many days to keep Arch news and advisories cached on disk.
151    /// Default is 7 days. Helps reduce network requests on startup.
152    pub news_cache_ttl_days: u32,
153    /// Visual marker style for packages added to Install/Remove/Downgrade lists.
154    pub package_marker: PackageMarker,
155    /// Symbol used to mark a news item as read in the News modal.
156    pub news_read_symbol: String,
157    /// Symbol used to mark a news item as unread in the News modal.
158    pub news_unread_symbol: String,
159    /// Preferred terminal binary name to spawn for shell commands (e.g., "alacritty", "kitty", "gnome-terminal").
160    /// When empty, Pacsea auto-detects from available terminals.
161    pub preferred_terminal: String,
162    /// When true, skip the Preflight modal and execute actions directly (install/remove/downgrade).
163    /// Defaults to false to preserve the safer, review-first workflow.
164    pub skip_preflight: bool,
165    /// Locale code for translations (e.g., "de-DE", "en-US").
166    /// Empty string means auto-detect from system locale.
167    pub locale: String,
168    /// Search input mode on startup.
169    /// When false, starts in insert mode (default).
170    /// When true, starts in normal mode.
171    pub search_startup_mode: bool,
172    /// Whether fuzzy search is enabled by default on startup.
173    /// When false, uses normal substring search (default).
174    /// When true, uses fuzzy matching (fzf-style).
175    pub fuzzy_search: bool,
176    /// Refresh interval in seconds for pacman -Qu and AUR helper checks.
177    /// Default is 30 seconds. Set to a higher value to reduce resource usage on slow systems.
178    pub updates_refresh_interval: u64,
179    /// Filter mode for installed packages display.
180    /// `LeafOnly` shows explicitly installed packages with no dependents.
181    /// `AllExplicit` shows all explicitly installed packages.
182    pub installed_packages_mode: crate::state::InstalledPackagesMode,
183    /// Whether to fetch remote announcements from GitHub Gist.
184    /// If `true`, fetches announcements from the configured Gist URL.
185    /// If `false`, remote announcements are disabled (version announcements still show).
186    pub get_announcement: bool,
187    /// Whether to use passwordless sudo for install operations when available.
188    /// If `false` (default), password prompt is always shown even if passwordless sudo is configured.
189    /// If `true`, passwordless sudo is used when available, skipping the password prompt.
190    /// This acts as an additional safety barrier requiring explicit opt-in.
191    pub use_passwordless_sudo: bool,
192    /// Privilege escalation tool selection mode.
193    /// Controls which tool (sudo/doas) is used for privileged operations.
194    /// `Auto` (default): prefer doas if available, fall back to sudo.
195    /// `Sudo`: always use sudo. `Doas`: always use doas.
196    pub privilege_mode: crate::logic::privilege::PrivilegeMode,
197    /// Authentication mode for privilege escalation.
198    /// Controls how Pacsea handles password/authentication before privileged operations.
199    /// `Prompt` (default): Pacsea captures password only for stdin-capable tools (sudo).
200    /// For doas, prompt mode is coerced to `Interactive` because doas cannot read stdin passwords.
201    /// `PasswordlessOnly`: Skip prompt only when `{tool} -n true` succeeds.
202    /// `Interactive`: Let the privilege tool handle auth directly (fingerprint via PAM, etc.).
203    pub auth_mode: crate::logic::privilege::AuthMode,
204    /// Whether to use the terminal's theme colors instead of theme.conf.
205    /// If `true`, Pacsea queries the terminal for foreground/background colors via OSC 10/11.
206    /// If `false` (default), uses theme.conf colors.
207    /// Note: Terminal theme is also used automatically when theme.conf is missing/invalid
208    /// and the terminal is on the supported list (alacritty, kitty, konsole, ghostty, xterm,
209    /// gnome-terminal, xfce4-terminal, tilix, mate-terminal, wezterm-gui, `WezTerm`).
210    pub use_terminal_theme: bool,
211    /// Whether AUR voting via SSH is enabled.
212    /// Requires an SSH key uploaded to the user's AUR account.
213    pub aur_vote_enabled: bool,
214    /// SSH connect timeout in seconds for AUR vote commands.
215    pub aur_vote_ssh_timeout_seconds: u32,
216    /// SSH binary path or name for AUR vote commands.
217    /// Defaults to `"ssh"`. Override for non-standard SSH setups.
218    pub aur_vote_ssh_command: String,
219    /// Dynamic results-list toggles from `repos.conf` filter ids (canonical keys, see `repos` module).
220    ///
221    /// Keys match canonical `results_filter` tokens from repos.conf (e.g. `vendor_pkgs` for `results_filter_show_vendor_pkgs`).
222    pub results_filter_toggles: HashMap<String, bool>,
223}
224
225impl Default for Settings {
226    /// What: Provide the built-in baseline configuration for Pacsea settings.
227    ///
228    /// Inputs:
229    /// - None.
230    ///
231    /// Output:
232    /// - Returns a `Settings` instance populated with Pacsea's preferred defaults.
233    ///
234    /// Details:
235    /// - Sets balanced pane layout percentages and enables all panes by default.
236    /// - Enables all scan types and uses Catppuccin-inspired news glyphs.
237    fn default() -> Self {
238        Self {
239            layout_left_pct: 20,
240            layout_center_pct: 60,
241            layout_right_pct: 20,
242            main_pane_order: crate::state::DEFAULT_MAIN_PANE_ORDER,
243            vertical_min_results: 3,
244            vertical_max_results: 17,
245            vertical_min_middle: 3,
246            vertical_max_middle: 5,
247            vertical_min_package_info: 3,
248            app_dry_run_default: false,
249            keymap: KeyMap::default(),
250            sort_mode: crate::state::SortMode::RepoThenName,
251            clipboard_suffix: "Check PKGBUILD and source for suspicious and malicious activities"
252                .to_string(),
253            show_recent_pane: true,
254            show_install_pane: true,
255            show_keybinds_footer: true,
256            selected_countries: "Worldwide".to_string(),
257            mirror_count: 20,
258            virustotal_api_key: String::new(),
259            scan_do_clamav: true,
260            scan_do_trivy: true,
261            scan_do_semgrep: true,
262            scan_do_shellcheck: true,
263            scan_do_virustotal: true,
264            scan_do_custom: true,
265            scan_do_sleuth: true,
266            pkgbuild_shellcheck_exclude: "SC2034, SC2164, SC2148, SC2154".to_string(),
267            pkgbuild_checks_show_raw_output: false,
268            start_in_news: false,
269            news_filter_show_arch_news: true,
270            news_filter_show_advisories: true,
271            news_filter_show_pkg_updates: true,
272            news_filter_show_aur_updates: true,
273            news_filter_show_aur_comments: true,
274            news_filter_installed_only: false,
275            news_max_age_days: Some(30),
276            startup_news_configured: false,
277            startup_news_show_arch_news: true,
278            startup_news_show_advisories: true,
279            startup_news_show_aur_updates: true,
280            startup_news_show_aur_comments: true,
281            startup_news_show_pkg_updates: true,
282            startup_news_max_age_days: Some(7),
283            news_cache_ttl_days: 7,
284            package_marker: PackageMarker::Front,
285            news_read_symbol: "✓".to_string(),
286            news_unread_symbol: "∘".to_string(),
287            preferred_terminal: String::new(),
288            skip_preflight: false,
289            locale: String::new(),        // Empty means auto-detect from system
290            search_startup_mode: false,   // Default to insert mode
291            fuzzy_search: false,          // Default to normal substring search
292            updates_refresh_interval: 30, // Default to 30 seconds
293            installed_packages_mode: crate::state::InstalledPackagesMode::LeafOnly,
294            get_announcement: true, // Default to fetching remote announcements
295            use_passwordless_sudo: false, // Default to always showing password prompt (safety barrier)
296            privilege_mode: crate::logic::privilege::PrivilegeMode::Auto, // Default to auto-detect (prefer doas, fallback sudo)
297            auth_mode: crate::logic::privilege::AuthMode::Prompt, // Default to Pacsea password modal
298            use_terminal_theme: false, // Default to using theme.conf colors
299            aur_vote_enabled: true,    // Enabled by default; requires SSH key configured on AUR
300            aur_vote_ssh_timeout_seconds: 10,
301            aur_vote_ssh_command: "ssh".to_string(),
302            results_filter_toggles: HashMap::new(),
303        }
304    }
305}
306
307/// A single keyboard chord (modifiers + key).
308#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
309pub struct KeyChord {
310    /// The key code (e.g., Char('a'), Enter, Esc).
311    pub code: KeyCode,
312    /// The modifier keys (e.g., Ctrl, Shift, Alt).
313    pub mods: KeyModifiers,
314}
315
316impl KeyChord {
317    /// Return a short display label such as "Ctrl+R", "F1", "Shift+Del", "+/ ?".
318    #[must_use]
319    pub fn label(&self) -> String {
320        let mut parts: Vec<&'static str> = Vec::new();
321        if self.mods.contains(KeyModifiers::CONTROL) {
322            parts.push("Ctrl");
323        }
324        if self.mods.contains(KeyModifiers::ALT) {
325            parts.push("Alt");
326        }
327        if self.mods.contains(KeyModifiers::SHIFT) {
328            parts.push("Shift");
329        }
330        if self.mods.contains(KeyModifiers::SUPER) {
331            parts.push("Super");
332        }
333        let key = match self.code {
334            KeyCode::Char(ch) => {
335                // Show uppercase character for display
336                let up = ch.to_ascii_uppercase();
337                if up == ' ' {
338                    "Space".to_string()
339                } else {
340                    up.to_string()
341                }
342            }
343            KeyCode::Enter => "Enter".to_string(),
344            KeyCode::Esc => "Esc".to_string(),
345            KeyCode::Backspace => "Backspace".to_string(),
346            KeyCode::Tab => "Tab".to_string(),
347            KeyCode::BackTab => "Shift+Tab".to_string(),
348            KeyCode::Delete => "Del".to_string(),
349            KeyCode::Insert => "Ins".to_string(),
350            KeyCode::Home => "Home".to_string(),
351            KeyCode::End => "End".to_string(),
352            KeyCode::PageUp => "PgUp".to_string(),
353            KeyCode::PageDown => "PgDn".to_string(),
354            KeyCode::Up => "↑".to_string(),
355            KeyCode::Down => "↓".to_string(),
356            KeyCode::Left => "←".to_string(),
357            KeyCode::Right => "→".to_string(),
358            KeyCode::F(n) => format!("F{n}"),
359            _ => "?".to_string(),
360        };
361        if parts.is_empty() || matches!(self.code, KeyCode::BackTab) {
362            key
363        } else {
364            format!("{}+{key}", parts.join("+"))
365        }
366    }
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372
373    #[test]
374    /// What: Ensure `KeyChord::label` renders user-facing text for modifier and key combinations.
375    ///
376    /// Inputs:
377    /// - Sample chords covering control characters, space, function keys, Shift+BackTab, arrow keys, and multi-modifier chords.
378    ///
379    /// Output:
380    /// - Labels such as `"Ctrl+R"`, `"Space"`, `"F5"`, `"Shift+Tab"`, arrow glyphs, and combined modifier strings.
381    ///
382    /// Details:
383    /// - Protects the formatting logic that appears in keybinding help overlays.
384    fn theme_keychord_label_variants() {
385        let kc = KeyChord {
386            code: KeyCode::Char('r'),
387            mods: KeyModifiers::CONTROL,
388        };
389        assert_eq!(kc.label(), "Ctrl+R");
390
391        let kc2 = KeyChord {
392            code: KeyCode::Char(' '),
393            mods: KeyModifiers::empty(),
394        };
395        assert_eq!(kc2.label(), "Space");
396
397        let kc3 = KeyChord {
398            code: KeyCode::F(5),
399            mods: KeyModifiers::empty(),
400        };
401        assert_eq!(kc3.label(), "F5");
402
403        let kc4 = KeyChord {
404            code: KeyCode::BackTab,
405            mods: KeyModifiers::SHIFT,
406        };
407        assert_eq!(kc4.label(), "Shift+Tab");
408
409        let kc5 = KeyChord {
410            code: KeyCode::Left,
411            mods: KeyModifiers::empty(),
412        };
413        assert_eq!(kc5.label(), "←");
414
415        let kc6 = KeyChord {
416            code: KeyCode::Char('x'),
417            mods: KeyModifiers::ALT | KeyModifiers::SHIFT,
418        };
419        assert_eq!(kc6.label(), "Alt+Shift+X");
420    }
421}
422
423/// Application key bindings.
424/// Each action can have multiple chords.
425#[derive(Clone, Debug)]
426pub struct KeyMap {
427    // Global
428    /// Key chords to show help overlay.
429    pub help_overlay: Vec<KeyChord>,
430    /// Key chords to reload configuration.
431    pub reload_config: Vec<KeyChord>,
432    /// Key chords to exit the application.
433    pub exit: Vec<KeyChord>,
434    /// Global: Show/Hide PKGBUILD viewer
435    pub show_pkgbuild: Vec<KeyChord>,
436    /// Global: Show/Hide AUR comments viewer
437    pub comments_toggle: Vec<KeyChord>,
438    /// Global: Run PKGBUILD static checks in preview panel.
439    pub run_pkgbuild_checks: Vec<KeyChord>,
440    /// Global: Cycle PKGBUILD pane between body, `ShellCheck`, and `Namcap` sections.
441    pub cycle_pkgbuild_sections: Vec<KeyChord>,
442    /// Global: Change results sorting mode
443    pub change_sort: Vec<KeyChord>,
444    /// Key chords to move to next pane.
445    pub pane_next: Vec<KeyChord>,
446    /// Key chords to move focus left.
447    pub pane_left: Vec<KeyChord>,
448    /// Key chords to move focus right.
449    pub pane_right: Vec<KeyChord>,
450    /// Global: Toggle Config/Lists dropdown
451    pub config_menu_toggle: Vec<KeyChord>,
452    /// Global: Toggle Options dropdown
453    pub options_menu_toggle: Vec<KeyChord>,
454    /// Global: Toggle Panels dropdown
455    pub panels_menu_toggle: Vec<KeyChord>,
456
457    // Search
458    /// Key chords to move selection up in search results.
459    pub search_move_up: Vec<KeyChord>,
460    /// Key chords to move selection down in search results.
461    pub search_move_down: Vec<KeyChord>,
462    /// Key chords to page up in search results.
463    pub search_page_up: Vec<KeyChord>,
464    /// Key chords to page down in search results.
465    pub search_page_down: Vec<KeyChord>,
466    /// Key chords to add package to install list.
467    pub search_add: Vec<KeyChord>,
468    /// Key chords to install selected package.
469    pub search_install: Vec<KeyChord>,
470    /// Key chords to move focus left from search pane.
471    pub search_focus_left: Vec<KeyChord>,
472    /// Key chords to move focus right from search pane.
473    pub search_focus_right: Vec<KeyChord>,
474    /// Key chords for backspace in search input.
475    pub search_backspace: Vec<KeyChord>,
476    /// Insert mode: clear entire search input (default: Shift+Del)
477    pub search_insert_clear: Vec<KeyChord>,
478
479    // Search normal mode
480    /// Toggle Search normal mode on/off (works from both insert/normal)
481    pub search_normal_toggle: Vec<KeyChord>,
482    /// Enter insert mode while in Search normal mode
483    pub search_normal_insert: Vec<KeyChord>,
484    /// Normal mode: extend selection to the left (default: h)
485    pub search_normal_select_left: Vec<KeyChord>,
486    /// Normal mode: extend selection to the right (default: l)
487    pub search_normal_select_right: Vec<KeyChord>,
488    /// Normal mode: delete selected text (default: d)
489    pub search_normal_delete: Vec<KeyChord>,
490    /// Normal mode: clear entire search input (default: Shift+Del)
491    pub search_normal_clear: Vec<KeyChord>,
492    /// Normal mode: open Arch status page in browser (default: Shift+S)
493    pub search_normal_open_status: Vec<KeyChord>,
494    /// Normal mode: trigger Import packages dialog
495    pub search_normal_import: Vec<KeyChord>,
496    /// Normal mode: trigger Export Install list
497    pub search_normal_export: Vec<KeyChord>,
498    /// Normal mode: open Available Updates window
499    pub search_normal_updates: Vec<KeyChord>,
500    /// Toggle fuzzy search mode on/off
501    pub toggle_fuzzy: Vec<KeyChord>,
502
503    // Recent
504    /// Key chords to move selection up in recent queries.
505    pub recent_move_up: Vec<KeyChord>,
506    /// Key chords to move selection down in recent queries.
507    pub recent_move_down: Vec<KeyChord>,
508    /// Key chords to find/search in recent queries.
509    pub recent_find: Vec<KeyChord>,
510    /// Key chords to use selected recent query.
511    pub recent_use: Vec<KeyChord>,
512    /// Key chords to add package from recent to install list.
513    pub recent_add: Vec<KeyChord>,
514    /// Key chords to move focus from recent to search pane.
515    pub recent_to_search: Vec<KeyChord>,
516    /// Key chords to move focus right from recent pane.
517    pub recent_focus_right: Vec<KeyChord>,
518    /// Remove one entry from Recent
519    pub recent_remove: Vec<KeyChord>,
520    /// Clear all entries in Recent
521    pub recent_clear: Vec<KeyChord>,
522
523    // Install
524    /// Key chords to move selection up in install list.
525    pub install_move_up: Vec<KeyChord>,
526    /// Key chords to move selection down in install list.
527    pub install_move_down: Vec<KeyChord>,
528    /// Key chords to confirm and execute install/remove operation.
529    pub install_confirm: Vec<KeyChord>,
530    /// Key chords to remove item from install list.
531    pub install_remove: Vec<KeyChord>,
532    /// Key chords to clear install list.
533    pub install_clear: Vec<KeyChord>,
534    /// Key chords to find/search in install list.
535    pub install_find: Vec<KeyChord>,
536    /// Key chords to move focus from install to search pane.
537    pub install_to_search: Vec<KeyChord>,
538    /// Key chords to move focus left from install pane.
539    pub install_focus_left: Vec<KeyChord>,
540
541    // News modal
542    /// Mark currently listed News items as read (without opening URL)
543    pub news_mark_read: Vec<KeyChord>,
544    /// Mark all listed News items as read
545    pub news_mark_all_read: Vec<KeyChord>,
546    /// Mark selected News Feed item as read.
547    pub news_mark_read_feed: Vec<KeyChord>,
548    /// Mark selected News Feed item as unread.
549    pub news_mark_unread_feed: Vec<KeyChord>,
550    /// Toggle read/unread for selected News Feed item.
551    pub news_toggle_read_feed: Vec<KeyChord>,
552}
553
554/// Type alias for global key bindings tuple.
555///
556/// Contains 10 `Vec<KeyChord>` for `help_overlay`, `reload_config`, `exit`, `show_pkgbuild`, `comments_toggle`, `run_pkgbuild_checks`, `change_sort`, and pane navigation keys.
557type GlobalKeys = (
558    Vec<KeyChord>,
559    Vec<KeyChord>,
560    Vec<KeyChord>,
561    Vec<KeyChord>,
562    Vec<KeyChord>,
563    Vec<KeyChord>,
564    Vec<KeyChord>,
565    Vec<KeyChord>,
566    Vec<KeyChord>,
567    Vec<KeyChord>,
568);
569
570/// Type alias for search key bindings tuple.
571///
572/// Contains 10 `Vec<KeyChord>` for search navigation, actions, and focus keys.
573type SearchKeys = (
574    Vec<KeyChord>,
575    Vec<KeyChord>,
576    Vec<KeyChord>,
577    Vec<KeyChord>,
578    Vec<KeyChord>,
579    Vec<KeyChord>,
580    Vec<KeyChord>,
581    Vec<KeyChord>,
582    Vec<KeyChord>,
583    Vec<KeyChord>,
584);
585
586/// Type alias for search normal mode key bindings tuple.
587///
588/// Contains 10 `Vec<KeyChord>` for Vim-like normal mode search keys.
589type SearchNormalKeys = (
590    Vec<KeyChord>,
591    Vec<KeyChord>,
592    Vec<KeyChord>,
593    Vec<KeyChord>,
594    Vec<KeyChord>,
595    Vec<KeyChord>,
596    Vec<KeyChord>,
597    Vec<KeyChord>,
598    Vec<KeyChord>,
599    Vec<KeyChord>,
600);
601
602/// Type alias for recent pane key bindings tuple.
603///
604/// Contains 9 `Vec<KeyChord>` for recent pane navigation and action keys.
605type RecentKeys = (
606    Vec<KeyChord>,
607    Vec<KeyChord>,
608    Vec<KeyChord>,
609    Vec<KeyChord>,
610    Vec<KeyChord>,
611    Vec<KeyChord>,
612    Vec<KeyChord>,
613    Vec<KeyChord>,
614    Vec<KeyChord>,
615);
616
617/// Type alias for install list key bindings tuple.
618///
619/// Contains 8 `Vec<KeyChord>` for install list navigation and action keys.
620type InstallKeys = (
621    Vec<KeyChord>,
622    Vec<KeyChord>,
623    Vec<KeyChord>,
624    Vec<KeyChord>,
625    Vec<KeyChord>,
626    Vec<KeyChord>,
627    Vec<KeyChord>,
628    Vec<KeyChord>,
629);
630
631/// What: Create default global key bindings.
632///
633/// Inputs:
634/// - `none`: Empty key modifiers
635/// - `ctrl`: Control modifier
636///
637/// Output:
638/// - Tuple of global key binding vectors
639///
640/// Details:
641/// - Returns `help_overlay`, `reload_config`, `exit`, `show_pkgbuild`, `change_sort`, and pane navigation keys.
642fn default_global_keys(none: KeyModifiers, ctrl: KeyModifiers) -> GlobalKeys {
643    use KeyCode::{BackTab, Char, Left, Right, Tab};
644    (
645        vec![
646            KeyChord {
647                code: KeyCode::F(1),
648                mods: none,
649            },
650            KeyChord {
651                code: Char('?'),
652                mods: none,
653            },
654        ],
655        vec![KeyChord {
656            code: Char('r'),
657            mods: ctrl,
658        }],
659        vec![KeyChord {
660            code: Char('c'),
661            mods: ctrl,
662        }],
663        vec![KeyChord {
664            code: Char('x'),
665            mods: ctrl,
666        }],
667        vec![KeyChord {
668            code: Char('t'),
669            mods: ctrl,
670        }],
671        vec![KeyChord {
672            code: Char('k'),
673            mods: ctrl,
674        }],
675        vec![KeyChord {
676            code: BackTab,
677            mods: none,
678        }],
679        vec![KeyChord {
680            code: Tab,
681            mods: none,
682        }],
683        vec![KeyChord {
684            code: Left,
685            mods: none,
686        }],
687        vec![KeyChord {
688            code: Right,
689            mods: none,
690        }],
691    )
692}
693
694/// What: Create default dropdown toggle key bindings.
695///
696/// Inputs:
697/// - `shift`: Shift modifier
698///
699/// Output:
700/// - Tuple of dropdown toggle key binding vectors
701///
702/// Details:
703/// - Returns `config_menu_toggle`, `options_menu_toggle`, and `panels_menu_toggle` keys.
704fn default_dropdown_keys(shift: KeyModifiers) -> (Vec<KeyChord>, Vec<KeyChord>, Vec<KeyChord>) {
705    use KeyCode::Char;
706    (
707        vec![KeyChord {
708            code: Char('c'),
709            mods: shift,
710        }],
711        vec![KeyChord {
712            code: Char('o'),
713            mods: shift,
714        }],
715        vec![KeyChord {
716            code: Char('p'),
717            mods: shift,
718        }],
719    )
720}
721
722/// What: Create default search key bindings.
723///
724/// Inputs:
725/// - `none`: Empty key modifiers
726///
727/// Output:
728/// - Tuple of search key binding vectors
729///
730/// Details:
731/// - Returns all search-related key bindings for navigation, actions, and focus.
732fn default_search_keys(none: KeyModifiers) -> SearchKeys {
733    use KeyCode::{Backspace, Char, Down, Enter, Left, PageDown, PageUp, Right, Up};
734    (
735        vec![KeyChord {
736            code: Up,
737            mods: none,
738        }],
739        vec![KeyChord {
740            code: Down,
741            mods: none,
742        }],
743        vec![KeyChord {
744            code: PageUp,
745            mods: none,
746        }],
747        vec![KeyChord {
748            code: PageDown,
749            mods: none,
750        }],
751        vec![KeyChord {
752            code: Char(' '),
753            mods: none,
754        }],
755        vec![KeyChord {
756            code: Enter,
757            mods: none,
758        }],
759        vec![KeyChord {
760            code: Left,
761            mods: none,
762        }],
763        vec![KeyChord {
764            code: Right,
765            mods: none,
766        }],
767        vec![KeyChord {
768            code: Backspace,
769            mods: none,
770        }],
771        vec![KeyChord {
772            code: KeyCode::Delete,
773            mods: KeyModifiers::SHIFT,
774        }],
775    )
776}
777
778/// What: Create default search normal mode key bindings.
779///
780/// Inputs:
781/// - `none`: Empty key modifiers
782/// - `shift`: Shift modifier
783///
784/// Output:
785/// - Tuple of search normal mode key binding vectors
786///
787/// Details:
788/// - Returns all Vim-like normal mode key bindings for search.
789fn default_search_normal_keys(none: KeyModifiers, shift: KeyModifiers) -> SearchNormalKeys {
790    use KeyCode::{Char, Delete, Esc};
791    (
792        vec![KeyChord {
793            code: Esc,
794            mods: none,
795        }],
796        vec![KeyChord {
797            code: Char('i'),
798            mods: none,
799        }],
800        vec![KeyChord {
801            code: Char('h'),
802            mods: none,
803        }],
804        vec![KeyChord {
805            code: Char('l'),
806            mods: none,
807        }],
808        vec![KeyChord {
809            code: Char('d'),
810            mods: none,
811        }],
812        vec![KeyChord {
813            code: Delete,
814            mods: shift,
815        }],
816        vec![KeyChord {
817            code: Char('s'),
818            mods: shift,
819        }],
820        vec![KeyChord {
821            code: Char('i'),
822            mods: shift,
823        }],
824        vec![KeyChord {
825            code: Char('e'),
826            mods: shift,
827        }],
828        vec![KeyChord {
829            code: Char('u'),
830            mods: shift,
831        }],
832    )
833}
834
835/// What: Create default recent pane key bindings.
836///
837/// Inputs:
838/// - `none`: Empty key modifiers
839/// - `shift`: Shift modifier
840///
841/// Output:
842/// - Tuple of recent pane key binding vectors
843///
844/// Details:
845/// - Returns all recent pane navigation and action key bindings.
846fn default_recent_keys(none: KeyModifiers, shift: KeyModifiers) -> RecentKeys {
847    use KeyCode::{Char, Delete, Down, Enter, Esc, Right, Up};
848    (
849        vec![
850            KeyChord {
851                code: Char('k'),
852                mods: none,
853            },
854            KeyChord {
855                code: Up,
856                mods: none,
857            },
858        ],
859        vec![
860            KeyChord {
861                code: Char('j'),
862                mods: none,
863            },
864            KeyChord {
865                code: Down,
866                mods: none,
867            },
868        ],
869        vec![KeyChord {
870            code: Char('/'),
871            mods: none,
872        }],
873        vec![KeyChord {
874            code: Enter,
875            mods: none,
876        }],
877        vec![KeyChord {
878            code: Char(' '),
879            mods: none,
880        }],
881        vec![KeyChord {
882            code: Esc,
883            mods: none,
884        }],
885        vec![KeyChord {
886            code: Right,
887            mods: none,
888        }],
889        vec![
890            KeyChord {
891                code: Char('d'),
892                mods: none,
893            },
894            KeyChord {
895                code: Delete,
896                mods: none,
897            },
898        ],
899        vec![KeyChord {
900            code: Delete,
901            mods: shift,
902        }],
903    )
904}
905
906/// What: Create default install list key bindings.
907///
908/// Inputs:
909/// - `none`: Empty key modifiers
910/// - `shift`: Shift modifier
911///
912/// Output:
913/// - Tuple of install list key binding vectors
914///
915/// Details:
916/// - Returns all install list navigation and action key bindings.
917fn default_install_keys(none: KeyModifiers, shift: KeyModifiers) -> InstallKeys {
918    use KeyCode::{Char, Delete, Down, Enter, Esc, Left, Up};
919    (
920        vec![
921            KeyChord {
922                code: Char('k'),
923                mods: none,
924            },
925            KeyChord {
926                code: Up,
927                mods: none,
928            },
929        ],
930        vec![
931            KeyChord {
932                code: Char('j'),
933                mods: none,
934            },
935            KeyChord {
936                code: Down,
937                mods: none,
938            },
939        ],
940        vec![KeyChord {
941            code: Enter,
942            mods: none,
943        }],
944        vec![
945            KeyChord {
946                code: Delete,
947                mods: none,
948            },
949            KeyChord {
950                code: Char('d'),
951                mods: none,
952            },
953        ],
954        vec![KeyChord {
955            code: Delete,
956            mods: shift,
957        }],
958        vec![KeyChord {
959            code: Char('/'),
960            mods: none,
961        }],
962        vec![KeyChord {
963            code: Esc,
964            mods: none,
965        }],
966        vec![KeyChord {
967            code: Left,
968            mods: none,
969        }],
970    )
971}
972
973/// What: Create default news modal key bindings.
974///
975/// Inputs:
976/// - `none`: Empty key modifiers
977/// - `ctrl`: Control modifier
978///
979/// Output:
980/// - Tuple of news modal key binding vectors
981///
982/// Details:
983/// - Returns `news_mark_read` and `news_mark_all_read` key bindings.
984fn default_news_keys(none: KeyModifiers, ctrl: KeyModifiers) -> (Vec<KeyChord>, Vec<KeyChord>) {
985    use KeyCode::Char;
986    (
987        vec![KeyChord {
988            code: Char('r'),
989            mods: none,
990        }],
991        vec![KeyChord {
992            code: Char('r'),
993            mods: ctrl,
994        }],
995    )
996}
997
998/// What: Create default News Feed key bindings.
999///
1000/// Inputs:
1001/// - `none`: Empty key modifiers
1002///
1003/// Output:
1004/// - Tuple of news feed key binding vectors
1005///
1006/// Details:
1007/// - Returns `news_mark_read_feed`, `news_mark_unread_feed`, and `news_toggle_read_feed`.
1008fn default_news_feed_keys(none: KeyModifiers) -> (Vec<KeyChord>, Vec<KeyChord>, Vec<KeyChord>) {
1009    use KeyCode::Char;
1010    (
1011        vec![KeyChord {
1012            code: Char('r'),
1013            mods: none,
1014        }],
1015        vec![KeyChord {
1016            code: Char('u'),
1017            mods: none,
1018        }],
1019        vec![KeyChord {
1020            code: Char('t'),
1021            mods: none,
1022        }],
1023    )
1024}
1025
1026/// What: Build the default `KeyMap` by constructing it from helper functions.
1027///
1028/// Inputs:
1029/// - None (uses internal modifier constants).
1030///
1031/// Output:
1032/// - Returns a fully constructed `KeyMap` with all default key bindings.
1033///
1034/// Details:
1035/// - Consolidates all key binding construction to reduce data flow complexity.
1036/// - All key bindings are constructed inline within the struct initialization.
1037fn build_default_keymap() -> KeyMap {
1038    let none = KeyModifiers::empty();
1039    let ctrl = KeyModifiers::CONTROL;
1040    let shift = KeyModifiers::SHIFT;
1041
1042    let global = default_global_keys(none, ctrl);
1043    let dropdown = default_dropdown_keys(shift);
1044    let search = default_search_keys(none);
1045    let search_normal = default_search_normal_keys(none, shift);
1046    let recent = default_recent_keys(none, shift);
1047    let install = default_install_keys(none, shift);
1048    let news = default_news_keys(none, ctrl);
1049    let news_feed = default_news_feed_keys(none);
1050
1051    KeyMap {
1052        help_overlay: global.0,
1053        reload_config: global.1,
1054        exit: global.2,
1055        show_pkgbuild: global.3,
1056        comments_toggle: global.4,
1057        run_pkgbuild_checks: global.5,
1058        cycle_pkgbuild_sections: vec![KeyChord {
1059            code: KeyCode::Char('d'),
1060            mods: ctrl,
1061        }],
1062        change_sort: global.6,
1063        pane_next: global.7,
1064        pane_left: global.8,
1065        pane_right: global.9,
1066        config_menu_toggle: dropdown.0,
1067        options_menu_toggle: dropdown.1,
1068        panels_menu_toggle: dropdown.2,
1069        search_move_up: search.0,
1070        search_move_down: search.1,
1071        search_page_up: search.2,
1072        search_page_down: search.3,
1073        search_add: search.4,
1074        search_install: search.5,
1075        search_focus_left: search.6,
1076        search_focus_right: search.7,
1077        search_backspace: search.8,
1078        search_insert_clear: search.9,
1079        search_normal_toggle: search_normal.0,
1080        search_normal_insert: search_normal.1,
1081        search_normal_select_left: search_normal.2,
1082        search_normal_select_right: search_normal.3,
1083        search_normal_delete: search_normal.4,
1084        search_normal_clear: search_normal.5,
1085        search_normal_open_status: search_normal.6,
1086        search_normal_import: search_normal.7,
1087        search_normal_export: search_normal.8,
1088        search_normal_updates: search_normal.9,
1089        toggle_fuzzy: vec![KeyChord {
1090            code: KeyCode::Char('f'),
1091            mods: ctrl,
1092        }],
1093        recent_move_up: recent.0,
1094        recent_move_down: recent.1,
1095        recent_find: recent.2,
1096        recent_use: recent.3,
1097        recent_add: recent.4,
1098        recent_to_search: recent.5,
1099        recent_focus_right: recent.6,
1100        recent_remove: recent.7,
1101        recent_clear: recent.8,
1102        install_move_up: install.0,
1103        install_move_down: install.1,
1104        install_confirm: install.2,
1105        install_remove: install.3,
1106        install_clear: install.4,
1107        install_find: install.5,
1108        install_to_search: install.6,
1109        install_focus_left: install.7,
1110        news_mark_read: news.0,
1111        news_mark_all_read: news.1,
1112        news_mark_read_feed: news_feed.0,
1113        news_mark_unread_feed: news_feed.1,
1114        news_toggle_read_feed: news_feed.2,
1115    }
1116}
1117
1118impl Default for KeyMap {
1119    /// What: Supply the default key bindings for Pacsea interactions.
1120    ///
1121    /// Inputs:
1122    /// - None.
1123    ///
1124    /// Output:
1125    /// - Returns a `KeyMap` prefilling chord vectors for global, search, recent, install, and news actions.
1126    ///
1127    /// Details:
1128    /// - Encodes human-friendly defaults such as `F1` for help and `Ctrl+R` to reload the configuration.
1129    /// - Provides multiple bindings for certain actions (e.g., `F1` and `?` for help).
1130    /// - Delegates to `build_default_keymap()` to reduce data flow complexity.
1131    fn default() -> Self {
1132        build_default_keymap()
1133    }
1134}