use std::collections::HashMap;
use crate::state::AppState;
use crate::state::modal::ServiceRestartDecision;
use super::context::{EnterOrSpaceContext, PreflightKeyContext};
use super::tab_handlers::handle_enter_or_space;
use crate::events::preflight::modal::close_preflight_modal;
pub(super) fn handle_esc_key(app: &mut AppState) -> bool {
let service_info = if let crate::state::Modal::Preflight { service_info, .. } = &app.modal {
service_info.clone()
} else {
Vec::new()
};
close_preflight_modal(app, &service_info);
false
}
pub(super) fn handle_enter_key(app: &mut AppState) -> bool {
let should_close = if let crate::state::Modal::Preflight {
tab,
items,
dependency_info,
dep_selected,
dep_tree_expanded,
file_info,
file_selected,
file_tree_expanded,
sandbox_info,
sandbox_selected,
sandbox_tree_expanded,
selected_optdepends,
service_info,
service_selected,
..
} = &mut app.modal
{
handle_enter_or_space(EnterOrSpaceContext {
tab,
items,
dependency_info,
dep_selected: *dep_selected,
dep_tree_expanded,
file_info,
file_selected: *file_selected,
file_tree_expanded,
sandbox_info,
sandbox_selected: *sandbox_selected,
sandbox_tree_expanded,
selected_optdepends,
service_info,
service_selected: *service_selected,
})
} else {
false
};
if should_close {
let (items_clone, action_clone, header_chips_clone, cascade_mode) =
if let crate::state::Modal::Preflight {
items,
action,
header_chips,
cascade_mode,
..
} = &app.modal
{
(items.clone(), *action, header_chips.clone(), *cascade_mode)
} else {
let service_info =
if let crate::state::Modal::Preflight { service_info, .. } = &app.modal {
service_info.clone()
} else {
Vec::new()
};
close_preflight_modal(app, &service_info);
return false;
};
let service_info = if let crate::state::Modal::Preflight { service_info, .. } = &app.modal {
service_info.clone()
} else {
Vec::new()
};
close_preflight_modal(app, &service_info);
match action_clone {
crate::state::PreflightAction::Install => {
use super::command_keys;
command_keys::handle_proceed_install(app, items_clone, header_chips_clone);
}
crate::state::PreflightAction::Remove => {
use super::command_keys;
command_keys::handle_proceed_remove(
app,
items_clone,
cascade_mode,
header_chips_clone,
);
}
crate::state::PreflightAction::Downgrade => {
let username = std::env::var("USER").unwrap_or_else(|_| "user".to_string());
if let Some(lockout_msg) =
crate::logic::faillock::get_lockout_message_if_locked(&username, app)
{
app.modal = crate::state::Modal::Alert {
message: lockout_msg,
};
return false;
}
let settings = crate::theme::settings();
if crate::logic::password::should_use_interactive_auth_handoff(&settings) {
crate::events::spawn_downgrade_in_terminal(app, &items_clone);
} else {
app.modal = crate::state::Modal::PasswordPrompt {
purpose: crate::state::modal::PasswordPurpose::Downgrade,
items: items_clone,
input: crate::state::SecureString::default(),
cursor: 0,
error: None,
};
app.pending_exec_header_chips = Some(header_chips_clone);
}
}
}
return false;
}
false
}
#[allow(clippy::needless_pass_by_value)] pub fn start_execution(
app: &mut AppState,
items: &[crate::state::PackageItem],
action: crate::state::PreflightAction,
header_chips: crate::state::modal::PreflightHeaderChips,
password: Option<crate::state::SecureString>,
) {
use crate::install::ExecutorRequest;
tracing::debug!(
action = ?action,
item_count = items.len(),
header_chips = ?header_chips,
has_password = password.is_some(),
"[Preflight] Transitioning modal: Preflight -> PreflightExec"
);
app.modal = crate::state::Modal::PreflightExec {
items: items.to_vec(),
action,
tab: crate::state::PreflightTab::Summary,
verbose: false,
log_lines: Vec::new(),
abortable: false,
header_chips,
success: None,
};
app.pending_executor_request = Some(match action {
crate::state::PreflightAction::Install => ExecutorRequest::Install {
items: items.to_vec(),
password,
dry_run: app.dry_run,
},
crate::state::PreflightAction::Remove => {
let names: Vec<String> = items.iter().map(|p| p.name.clone()).collect();
ExecutorRequest::Remove {
names,
password,
cascade: app.remove_cascade_mode,
dry_run: app.dry_run,
}
}
crate::state::PreflightAction::Downgrade => {
let names: Vec<String> = items.iter().map(|p| p.name.clone()).collect();
ExecutorRequest::Downgrade {
names,
password,
dry_run: app.dry_run,
}
}
});
}
pub(super) fn handle_space_key(ctx: &mut PreflightKeyContext<'_>) -> bool {
handle_enter_or_space(EnterOrSpaceContext {
tab: ctx.tab,
items: ctx.items,
dependency_info: ctx.dependency_info,
dep_selected: *ctx.dep_selected,
dep_tree_expanded: ctx.dep_tree_expanded,
file_info: ctx.file_info,
file_selected: *ctx.file_selected,
file_tree_expanded: ctx.file_tree_expanded,
sandbox_info: ctx.sandbox_info,
sandbox_selected: *ctx.sandbox_selected,
sandbox_tree_expanded: ctx.sandbox_tree_expanded,
selected_optdepends: ctx.selected_optdepends,
service_info: ctx.service_info,
service_selected: *ctx.service_selected,
});
false
}
pub(super) fn handle_shift_r_key(app: &mut AppState) -> bool {
tracing::info!("Shift+R pressed: Re-running all preflight analyses");
let (items, action) = if let crate::state::Modal::Preflight { items, action, .. } = &app.modal {
(items.clone(), *action)
} else {
return false;
};
if let crate::state::Modal::Preflight {
dependency_info,
deps_error,
file_info,
files_error,
service_info,
services_error,
services_loaded,
sandbox_info,
sandbox_error,
sandbox_loaded,
summary,
dep_selected,
file_selected,
service_selected,
sandbox_selected,
dep_tree_expanded,
file_tree_expanded,
sandbox_tree_expanded,
..
} = &mut app.modal
{
*dependency_info = Vec::new();
*deps_error = None;
*file_info = Vec::new();
*files_error = None;
*service_info = Vec::new();
*services_error = None;
*services_loaded = false;
*sandbox_info = Vec::new();
*sandbox_error = None;
*sandbox_loaded = false;
*summary = None;
*dep_selected = 0;
*file_selected = 0;
*service_selected = 0;
*sandbox_selected = 0;
dep_tree_expanded.clear();
file_tree_expanded.clear();
sandbox_tree_expanded.clear();
}
app.preflight_cancelled
.store(false, std::sync::atomic::Ordering::Relaxed);
app.preflight_summary_items = Some((items.clone(), action));
app.preflight_summary_resolving = true;
if matches!(action, crate::state::PreflightAction::Install) {
app.preflight_deps_items = Some((items.clone(), crate::state::PreflightAction::Install));
app.preflight_deps_resolving = true;
app.preflight_files_items = Some(items.clone());
app.preflight_files_resolving = true;
app.preflight_services_items = Some(items.clone());
app.preflight_services_resolving = true;
let aur_items: Vec<_> = items
.iter()
.filter(|p| matches!(p.source, crate::state::Source::Aur))
.cloned()
.collect();
if aur_items.is_empty() {
app.preflight_sandbox_items = None;
app.preflight_sandbox_resolving = false;
if let crate::state::Modal::Preflight { sandbox_loaded, .. } = &mut app.modal {
*sandbox_loaded = true;
}
} else {
app.preflight_sandbox_items = Some(aur_items);
app.preflight_sandbox_resolving = true;
}
}
app.toast_message = Some("Re-running all preflight analyses...".to_string());
app.toast_expires_at = Some(std::time::Instant::now() + std::time::Duration::from_secs(3));
false
}
pub(super) fn handle_r_key(ctx: &mut PreflightKeyContext<'_>) -> bool {
if *ctx.tab == crate::state::PreflightTab::Services && !ctx.service_info.is_empty() {
if *ctx.service_selected >= ctx.service_info.len() {
*ctx.service_selected = ctx.service_info.len().saturating_sub(1);
}
if let Some(service) = ctx.service_info.get_mut(*ctx.service_selected) {
service.restart_decision = ServiceRestartDecision::Restart;
}
} else if *ctx.tab == crate::state::PreflightTab::Deps
&& matches!(*ctx.action, crate::state::PreflightAction::Install)
{
*ctx.deps_error = None;
*ctx.dependency_info = crate::logic::deps::resolve_dependencies(ctx.items);
*ctx.dep_selected = 0;
} else if *ctx.tab == crate::state::PreflightTab::Files {
*ctx.files_error = None;
*ctx.file_info = crate::logic::files::resolve_file_changes(ctx.items, *ctx.action);
*ctx.file_selected = 0;
} else if *ctx.tab == crate::state::PreflightTab::Services {
*ctx.services_error = None;
*ctx.services_loaded = false;
*ctx.service_info = crate::logic::services::resolve_service_impacts(ctx.items, *ctx.action);
*ctx.service_selected = 0;
*ctx.services_loaded = true;
}
false
}
pub(super) fn handle_d_key(ctx: &mut PreflightKeyContext<'_>) -> bool {
if *ctx.tab == crate::state::PreflightTab::Services && !ctx.service_info.is_empty() {
if *ctx.service_selected >= ctx.service_info.len() {
*ctx.service_selected = ctx.service_info.len().saturating_sub(1);
}
if let Some(service) = ctx.service_info.get_mut(*ctx.service_selected) {
service.restart_decision = ServiceRestartDecision::Defer;
}
}
false
}
pub(super) fn handle_a_key(ctx: &mut PreflightKeyContext<'_>) -> bool {
if *ctx.tab == crate::state::PreflightTab::Deps && !ctx.dependency_info.is_empty() {
let mut grouped: HashMap<String, Vec<&crate::state::modal::DependencyInfo>> =
HashMap::new();
for dep in ctx.dependency_info.iter() {
for req_by in &dep.required_by {
grouped.entry(req_by.clone()).or_default().push(dep);
}
}
let all_expanded = ctx
.items
.iter()
.all(|p| ctx.dep_tree_expanded.contains(&p.name));
if all_expanded {
ctx.dep_tree_expanded.clear();
} else {
for pkg_name in ctx.items.iter().map(|p| &p.name) {
ctx.dep_tree_expanded.insert(pkg_name.clone());
}
}
} else if *ctx.tab == crate::state::PreflightTab::Files && !ctx.file_info.is_empty() {
let all_expanded = ctx
.file_info
.iter()
.filter(|p| !p.files.is_empty())
.all(|p| ctx.file_tree_expanded.contains(&p.name));
if all_expanded {
ctx.file_tree_expanded.clear();
} else {
for pkg_info in ctx.file_info.iter() {
if !pkg_info.files.is_empty() {
ctx.file_tree_expanded.insert(pkg_info.name.clone());
}
}
}
}
false
}