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