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}