W29: Confirmation dialog (P1 audit gap)

The F9 audit found that Cmd::OptionsConfirm was a single-boolean
toggle (cycle on/off) instead of MC's proper confirmation dialog
with per-operation checkboxes. The ConfirmDialog already existed
in confirm_dialog.rs with 4 toggles (delete, overwrite, execute,
exit) but wasn't wired into the F9 menu.

Wire the existing ConfirmDialog:
- DialogState::Confirm(Box<ConfirmDialog>) variant
- Cmd::OptionsConfirm now opens the dialog pre-populated with
  the current safe_delete / safe_execute settings
- Dispatch handler processes ConfirmResult::Confirm by saving the
  settings to runtime.safe_delete / safe_execute
- Added RuntimeConfig::safe_execute field with default Some(false)
- Added dialog_title() and render() arms for DialogState::Confirm

Tests: 1393 pass, zero warnings.
This commit is contained in:
2026-07-06 21:02:26 +03:00
parent b018e777ee
commit e8950c2760
5 changed files with 32 additions and 0 deletions
@@ -306,6 +306,8 @@ pub struct RuntimeConfig {
pub auto_save_setup: Option<bool>,
/// Require an explicit confirmation before delete.
pub safe_delete: Option<bool>,
/// Require an explicit confirmation before executing commands.
pub safe_execute: Option<bool>,
/// Pause and display output after a shell command finishes.
pub pause_after_run: Option<bool>,
}
@@ -326,6 +328,7 @@ impl Default for RuntimeConfig {
verbose_ops: Some(false),
auto_save_setup: Some(false),
safe_delete: Some(true),
safe_execute: Some(false),
pause_after_run: Some(false),
}
}
@@ -985,6 +985,7 @@ Err(e) => self.status.set_error(format!("view: {e}")),
| Some(DialogState::Encoding(_))
| Some(DialogState::Connection(_))
| Some(DialogState::Sort(_))
| Some(DialogState::Confirm(_))
| Some(DialogState::Progress(_)) => {
// These are closed by their own apply_*_outcome
// helpers before this function is called; they
@@ -1420,6 +1421,7 @@ fn dialog_label(d: &DialogState) -> String {
DialogState::Encoding(_) => "Display encoding".to_string(),
DialogState::Connection(_) => "Network connection".to_string(),
DialogState::Sort(_) => "Sort order".to_string(),
DialogState::Confirm(_) => "Confirmation".to_string(),
DialogState::Progress(_) => "Progress".to_string(),
}
}
@@ -951,6 +951,29 @@ impl FileManager {
}
consumed = true;
}
Some(DialogState::Confirm(d)) => {
use crate::filemanager::confirm_dialog::ConfirmResult;
let r = d.handle_key(key);
match r {
ConfirmResult::Confirm(settings) => {
self.runtime.safe_delete = Some(settings.confirm_delete);
self.runtime.safe_execute = Some(settings.confirm_execute);
self.status.set_message(format!(
"Confirm: delete={} overwrite={} execute={} exit={}",
if settings.confirm_delete { "ON" } else { "off" },
if settings.confirm_overwrite { "ON" } else { "off" },
if settings.confirm_execute { "ON" } else { "off" },
if settings.confirm_exit { "ON" } else { "off" },
));
self.dialog = None;
}
ConfirmResult::Cancel => {
self.dialog = None;
}
ConfirmResult::Running => {}
}
consumed = true;
}
None => return false,
}
// Apply captured outcomes.
@@ -356,6 +356,8 @@ pub enum DialogState {
Connection(Box<ConnectionDialog>),
/// Alt-Shift-T — sort order dialog.
Sort(Box<sort_dialog::SortDialog>),
/// F9 → Options → Confirmation — per-operation confirm toggles.
Confirm(Box<confirm_dialog::ConfirmDialog>),
/// F5/F6/F8 — live progress dialog for background copy/move/delete.
Progress(Box<crate::ops::progress::ProgressDialog>),
}
@@ -418,6 +420,7 @@ impl DialogState {
DialogState::Encoding(_) => false,
DialogState::Connection(_) => false,
DialogState::Sort(_) => false,
DialogState::Confirm(_) => false,
DialogState::Progress(_) => false,
}
}
@@ -180,6 +180,7 @@ impl FileManager {
DialogState::Encoding(d) => d.render(frame, dialog_area, &self.theme),
DialogState::Connection(d) => d.render(frame, dialog_area, &self.theme),
DialogState::Sort(d) => d.render(frame, dialog_area, &self.theme),
DialogState::Confirm(d) => d.render(frame, dialog_area, &self.theme),
DialogState::Progress(d) => d.render(frame, dialog_area, &self.theme),
}
}