pacsea/app/runtime/
mod.rs

1use ratatui::{Terminal, backend::CrosstermBackend};
2
3use crate::logic::send_query;
4use crate::state::AppState;
5
6use super::terminal::{restore_terminal, setup_terminal};
7
8/// Background worker management and spawning.
9mod background;
10/// Channel definitions for runtime communication.
11mod channels;
12/// Cleanup operations on application exit.
13mod cleanup;
14/// Main event loop implementation.
15mod event_loop;
16/// Event handlers for different event types.
17mod handlers;
18/// Application state initialization module.
19pub mod init;
20/// Tick handler for periodic UI updates.
21mod tick_handler;
22/// Background worker implementations.
23mod workers;
24
25use background::{Channels, spawn_auxiliary_workers, spawn_event_thread};
26use cleanup::cleanup_on_exit;
27use event_loop::run_event_loop;
28use init::{initialize_app_state, run_startup_config_preflight, trigger_initial_resolutions};
29
30/// Result type alias for runtime operations.
31type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
32
33/// What: Run the Pacsea TUI application end-to-end.
34///
35/// This function initializes terminal and state, spawns background workers
36/// (index, search, details, status/news), drives the event loop, persists
37/// caches, and restores the terminal on exit.
38///
39/// Inputs:
40/// - `dry_run_flag`: When `true`, install/remove/downgrade actions are displayed but not executed
41///   (overrides the config default for the session).
42///
43/// Output:
44/// - `Ok(())` when the UI exits cleanly; `Err` on unrecoverable terminal or runtime errors.
45///
46/// # Errors
47/// - Returns `Err` when terminal setup fails (e.g., unable to initialize terminal backend)
48/// - Returns `Err` when terminal restoration fails on exit
49/// - Returns `Err` when critical runtime errors occur during initialization or event loop execution
50///
51/// Details:
52/// - Config/state: Migrates legacy configs, loads settings (layout, keymap, sort), and reads
53///   persisted files (details cache, recent queries, install list, on-disk official index).
54/// - Background tasks: Spawns channels and tasks for batched details fetch, AUR/official search,
55///   PKGBUILD retrieval, official index refresh/enrichment, Arch status text, and Arch news.
56/// - Event loop: Renders UI frames and handles keyboard, mouse, tick, and channel messages to
57///   update results, details, ring-prefetch, PKGBUILD viewer, installed-only mode, and modals.
58/// - Persistence: Debounces and periodically writes recent, details cache, and install list.
59/// - Cleanup: Flushes pending writes and restores terminal modes before returning.
60pub async fn run(dry_run_flag: bool) -> Result<()> {
61    let headless = std::env::var("PACSEA_TEST_HEADLESS").ok().as_deref() == Some("1");
62
63    // Run startup config preflight once before first theme resolution.
64    let prefs = run_startup_config_preflight();
65
66    // Force theme resolution BEFORE terminal setup.
67    // This is important because theme resolution may query terminal colors via OSC 10/11,
68    // which must happen before mouse capture is enabled to avoid input conflicts.
69    let _ = crate::theme::theme();
70
71    if !headless {
72        setup_terminal()?;
73    }
74    let mut terminal = if headless {
75        None
76    } else {
77        Some(Terminal::new(CrosstermBackend::new(std::io::stdout()))?)
78    };
79
80    let mut app = AppState::default();
81
82    // Initialize application state (loads settings, caches, etc.)
83    let init_flags = initialize_app_state(&mut app, dry_run_flag, headless, &prefs);
84
85    // Create channels and spawn background workers
86    let mut channels = Channels::new(app.official_index_path.clone());
87
88    // Get updates refresh interval from settings (minimum 60s per requirement)
89    let updates_refresh_interval = crate::theme::settings().updates_refresh_interval.max(60);
90
91    // Spawn auxiliary workers (status, news, tick, index updates)
92    spawn_auxiliary_workers(
93        headless,
94        &channels.status_tx,
95        &channels.news_tx,
96        &channels.news_feed_tx,
97        &channels.news_incremental_tx,
98        &channels.announcement_tx,
99        &channels.tick_tx,
100        &app.news_read_ids,
101        &app.news_read_urls,
102        &app.news_seen_pkg_versions,
103        &app.news_seen_aur_comments,
104        &app.official_index_path,
105        &channels.net_err_tx,
106        &channels.index_notify_tx,
107        &channels.updates_tx,
108        updates_refresh_interval,
109        app.installed_packages_mode,
110        crate::theme::settings().get_announcement,
111        app.last_startup_timestamp.as_deref(),
112    );
113
114    // Spawn event reading thread
115    spawn_event_thread(
116        headless,
117        channels.event_tx.clone(),
118        channels.event_thread_cancelled.clone(),
119    );
120
121    // Trigger initial background resolutions if caches were missing/invalid
122    trigger_initial_resolutions(
123        &mut app,
124        &init_flags,
125        &channels.deps_req_tx,
126        &channels.files_req_tx,
127        &channels.services_req_tx,
128        &channels.sandbox_req_tx,
129    );
130
131    // Send initial query
132    send_query(&mut app, &channels.query_tx);
133
134    // Main event loop
135    run_event_loop(&mut terminal, &mut app, &mut channels).await;
136
137    // Cleanup on exit - this resets flags and flushes caches
138    cleanup_on_exit(&mut app, &channels);
139
140    // Drop channels to close request channels and stop workers from accepting new work
141    drop(channels);
142
143    // Restore terminal so user sees prompt
144    if !headless {
145        restore_terminal()?;
146    }
147
148    // Force immediate process exit to avoid waiting for background blocking tasks
149    // This is necessary because spawn_blocking tasks cannot be cancelled and would
150    // otherwise keep the tokio runtime alive until they complete
151    std::process::exit(0);
152}