pacsea/ui/helpers/
query.rs

1//! Query resolution and preview utilities.
2//!
3//! This module provides functions for resolving query strings to packages and triggering
4//! asynchronous preview fetches.
5
6use super::filter::filtered_recent_indices;
7use crate::state::AppState;
8
9/// What: Resolve a free-form query string to a best-effort matching package.
10///
11/// Inputs:
12/// - `q`: Query string to resolve
13///
14/// Output:
15/// - `Some(PackageItem)` per the priority rules below; `None` if nothing usable is found.
16///
17/// Details (selection priority):
18///   1) Exact-name match from the official index;
19///   2) Exact-name match from AUR;
20///   3) First official result;
21///   4) Otherwise, first AUR result.
22///
23/// Performs network I/O for AUR; tolerates errors.
24pub async fn fetch_first_match_for_query(q: String) -> Option<crate::state::PackageItem> {
25    // Prefer exact match from official index, then from AUR, else first official, then first AUR
26    // Use normal substring search for this helper (not fuzzy)
27    let official_results = crate::index::search_official(&q, false);
28    let official: Vec<crate::state::PackageItem> =
29        official_results.into_iter().map(|(item, _)| item).collect();
30    if let Some(off) = official
31        .iter()
32        .find(|it| it.name.eq_ignore_ascii_case(&q))
33        .cloned()
34    {
35        return Some(off);
36    }
37    let (aur, _errors) = crate::sources::fetch_all_with_errors(q.clone()).await;
38    if let Some(a) = aur
39        .iter()
40        .find(|it| it.name.eq_ignore_ascii_case(&q))
41        .cloned()
42    {
43        return Some(a);
44    }
45    if let Some(off) = official.first().cloned() {
46        return Some(off);
47    }
48    aur.into_iter().next()
49}
50
51/// What: Trigger an asynchronous preview fetch for the selected Recent query when applicable.
52///
53/// Inputs:
54/// - `app`: Application state (focus, selection, recent list)
55/// - `preview_tx`: Channel to send the preview `PackageItem`
56///
57/// Output:
58/// - Spawns a task to resolve and send a preview item; no return payload; exits early when inapplicable.
59///
60/// Details:
61/// - Requires: focus on Recent, a valid selection within the filtered view, and a query string present.
62/// - Resolves via [`fetch_first_match_for_query`] and sends over `preview_tx`; ignores send errors.
63pub fn trigger_recent_preview(
64    app: &AppState,
65    preview_tx: &tokio::sync::mpsc::UnboundedSender<crate::state::PackageItem>,
66) {
67    if !matches!(app.focus, crate::state::Focus::Recent)
68        || matches!(app.app_mode, crate::state::types::AppMode::News)
69    {
70        return;
71    }
72    let Some(idx) = app.history_state.selected() else {
73        return;
74    };
75    let inds = filtered_recent_indices(app);
76    if idx >= inds.len() {
77        return;
78    }
79    let Some(q) = app.recent_value_at(inds[idx]) else {
80        return;
81    };
82    let tx = preview_tx.clone();
83    tokio::spawn(async move {
84        if let Some(item) = fetch_first_match_for_query(q).await {
85            let _ = tx.send(item);
86        }
87    });
88}