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}