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