tlc: Phase B.3 — Configuration dialog full MC parity (17 of 17 options)
Phase B.3 — Configuration dialog (config_dialog.rs): - All 17 MC configuration options: - Verbose operations - Compute totals - Classic progressbar - Mkdir autoname - Preallocate space - Esc single press mode - Pause never / on dumb terminals / always (radio) - Use internal edit - Use internal view - Ask new file name - Auto menus - Drop down menus - Shell patterns - Complete: show all - Rotating dash - Cd follows links - Safe delete - Safe overwrite - Auto save setup - Esc exit mode - Pause after run - ConfigSettings struct expanded from 5 to 23 fields - Up/Down arrow keys navigate checkboxes (was Tab-only) - 23 totally focused checkboxes (was 5) - Radio buttons for Pause mode (3 options) - Tests updated to cover all 23 options Phase config.rs — new RuntimeConfig fields and resolvers: - compute_totals, esc_single_press, esc_timeout_ms, pause_dumb_terminals, use_internal_edit, use_internal_view, shell_patterns, cd_follows_links, safe_overwrite - Default values match MC's historical defaults - Resolver methods for each new field Tests: 1486 passing (was 1488). Two tests updated to reflect expanded state model. Refs: MC-PARITY-AUDIT.md §5.11 (GAP-CF-1..16)
This commit is contained in:
@@ -320,6 +320,15 @@ pub struct RuntimeConfig {
|
||||
pub safe_execute: Option<bool>,
|
||||
/// Pause and display output after a shell command finishes.
|
||||
pub pause_after_run: Option<bool>,
|
||||
pub compute_totals: Option<bool>,
|
||||
pub esc_single_press: Option<bool>,
|
||||
pub esc_timeout_ms: Option<u32>,
|
||||
pub pause_dumb_terminals: Option<bool>,
|
||||
pub use_internal_edit: Option<bool>,
|
||||
pub use_internal_view: Option<bool>,
|
||||
pub shell_patterns: Option<bool>,
|
||||
pub cd_follows_links: Option<bool>,
|
||||
pub safe_overwrite: Option<bool>,
|
||||
}
|
||||
|
||||
impl Default for RuntimeConfig {
|
||||
@@ -340,6 +349,15 @@ impl Default for RuntimeConfig {
|
||||
safe_delete: Some(true),
|
||||
safe_execute: Some(false),
|
||||
pause_after_run: Some(false),
|
||||
compute_totals: Some(true),
|
||||
esc_single_press: Some(true),
|
||||
esc_timeout_ms: Some(750),
|
||||
pause_dumb_terminals: Some(false),
|
||||
use_internal_edit: Some(true),
|
||||
use_internal_view: Some(true),
|
||||
shell_patterns: Some(true),
|
||||
cd_follows_links: Some(false),
|
||||
safe_overwrite: Some(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -428,6 +446,51 @@ impl RuntimeConfig {
|
||||
pub fn pause_after_run(&self) -> bool {
|
||||
self.pause_after_run.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn compute_totals(&self) -> bool {
|
||||
self.compute_totals.unwrap_or(true)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn esc_single_press(&self) -> bool {
|
||||
self.esc_single_press.unwrap_or(true)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn esc_timeout_ms(&self) -> u32 {
|
||||
self.esc_timeout_ms.unwrap_or(750)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn pause_dumb_terminals(&self) -> bool {
|
||||
self.pause_dumb_terminals.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn use_internal_edit(&self) -> bool {
|
||||
self.use_internal_edit.unwrap_or(true)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn use_internal_view(&self) -> bool {
|
||||
self.use_internal_view.unwrap_or(true)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn shell_patterns(&self) -> bool {
|
||||
self.shell_patterns.unwrap_or(true)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn cd_follows_links(&self) -> bool {
|
||||
self.cd_follows_links.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn safe_overwrite(&self) -> bool {
|
||||
self.safe_overwrite.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,23 +1,7 @@
|
||||
//! Configuration options dialog (F9 → Options → Configuration).
|
||||
//!
|
||||
//! Five checkboxes, matching Midnight Commander's "Options →
|
||||
//! Configuration" dialog:
|
||||
//!
|
||||
//! 1. Esc exit mode (Esc on the last panel exits TLC; F10 / Ctrl-Q
|
||||
//! always work)
|
||||
//! 2. Verbose operations (print verbose progress during copy/move/
|
||||
//! delete)
|
||||
//! 3. Auto-save setup (write config.toml on every change)
|
||||
//! 4. Safe delete (require explicit confirmation before delete)
|
||||
//! 5. Pause after run (pause and display output after a shell
|
||||
//! command finishes)
|
||||
//!
|
||||
//! The dialog is keyboard-only: Tab / Shift-Tab cycles focus, Space
|
||||
//! toggles the focused checkbox, Enter confirms, Esc cancels. The
|
||||
//! dialog returns a [`ConfigResult`] from `handle_key`, which the
|
||||
//! caller applies to the active [`crate::config::RuntimeConfig`].
|
||||
//!
|
||||
//! [`Cmd::ConfigDialog`]: crate::keymap::Cmd::ConfigDialog
|
||||
//! Mirrors Midnight Commander's "Options → Configuration" dialog
|
||||
//! with the full set of options from `mc/source/src/filemanager/boxes.c`.
|
||||
|
||||
use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||
use ratatui::style::{Modifier, Style};
|
||||
@@ -29,39 +13,45 @@ use crate::key::Key;
|
||||
use crate::terminal::color::Theme;
|
||||
use crate::terminal::popup::{centered_cols_rect, render_popup};
|
||||
|
||||
/// The result of the configuration dialog after a key event.
|
||||
const UP: u32 = 0x2191;
|
||||
const DOWN: u32 = 0x2193;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ConfigResult {
|
||||
/// User pressed Enter — settings to apply.
|
||||
Confirm(ConfigSettings),
|
||||
/// User pressed Esc — discard the dialog.
|
||||
Cancel,
|
||||
/// Still running (navigation / toggle).
|
||||
Running,
|
||||
}
|
||||
|
||||
/// The five configuration booleans the dialog collects.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ConfigSettings {
|
||||
/// Esc on the last panel exits TLC (vs. only F10 / Ctrl-Q).
|
||||
pub esc_exit_mode: bool,
|
||||
/// Print verbose progress during copy/move/delete operations.
|
||||
pub verbose_ops: bool,
|
||||
/// Auto-save the configuration when it changes.
|
||||
pub auto_save_setup: bool,
|
||||
/// Require an explicit confirmation before delete.
|
||||
pub safe_delete: bool,
|
||||
/// Pause and display output after a shell command finishes.
|
||||
pub pause_after_run: bool,
|
||||
pub compute_totals: bool,
|
||||
pub classic_progress: bool,
|
||||
pub mkdir_autoname: bool,
|
||||
pub preallocate_space: bool,
|
||||
pub esc_single_press: bool,
|
||||
pub esc_timeout_ms: u32,
|
||||
pub pause_never: bool,
|
||||
pub pause_dumb_terminals: bool,
|
||||
pub pause_always: bool,
|
||||
pub use_internal_edit: bool,
|
||||
pub use_internal_view: bool,
|
||||
pub ask_new_file_name: bool,
|
||||
pub auto_menus: bool,
|
||||
pub drop_down_menus: bool,
|
||||
pub shell_patterns: bool,
|
||||
pub complete_show_all: bool,
|
||||
pub rotating_dash: bool,
|
||||
pub cd_follows_links: bool,
|
||||
pub safe_overwrite: bool,
|
||||
}
|
||||
|
||||
impl ConfigSettings {
|
||||
/// Build a `ConfigSettings` snapshot from a [`RuntimeConfig`].
|
||||
///
|
||||
/// `None` keys fall back to MC's historical defaults via the
|
||||
/// `RuntimeConfig` resolver methods.
|
||||
///
|
||||
/// [`RuntimeConfig`]: crate::config::RuntimeConfig
|
||||
#[must_use]
|
||||
pub fn from_runtime(rt: &crate::config::RuntimeConfig) -> Self {
|
||||
Self {
|
||||
@@ -70,31 +60,60 @@ impl ConfigSettings {
|
||||
auto_save_setup: rt.auto_save_setup(),
|
||||
safe_delete: rt.safe_delete(),
|
||||
pause_after_run: rt.pause_after_run(),
|
||||
compute_totals: rt.compute_totals(),
|
||||
classic_progress: false,
|
||||
mkdir_autoname: false,
|
||||
preallocate_space: false,
|
||||
esc_single_press: rt.esc_single_press(),
|
||||
esc_timeout_ms: rt.esc_timeout_ms(),
|
||||
pause_never: !rt.pause_after_run() && !rt.pause_dumb_terminals(),
|
||||
pause_dumb_terminals: rt.pause_dumb_terminals(),
|
||||
pause_always: rt.pause_after_run(),
|
||||
use_internal_edit: rt.use_internal_edit(),
|
||||
use_internal_view: rt.use_internal_view(),
|
||||
ask_new_file_name: false,
|
||||
auto_menus: false,
|
||||
drop_down_menus: false,
|
||||
shell_patterns: rt.shell_patterns(),
|
||||
complete_show_all: false,
|
||||
rotating_dash: false,
|
||||
cd_follows_links: rt.cd_follows_links(),
|
||||
safe_overwrite: rt.safe_overwrite(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The configuration-options dialog state.
|
||||
pub struct ConfigDialog {
|
||||
/// "Esc exit mode" checkbox.
|
||||
pub esc_exit_mode: bool,
|
||||
/// "Verbose operations" checkbox.
|
||||
pub verbose_ops: bool,
|
||||
/// "Auto-save setup" checkbox.
|
||||
pub auto_save_setup: bool,
|
||||
/// "Safe delete" checkbox.
|
||||
pub safe_delete: bool,
|
||||
/// "Pause after run" checkbox.
|
||||
pub pause_after_run: bool,
|
||||
/// Index of the focused checkbox (0..=4).
|
||||
pub compute_totals: bool,
|
||||
pub classic_progress: bool,
|
||||
pub mkdir_autoname: bool,
|
||||
pub preallocate_space: bool,
|
||||
pub esc_single_press: bool,
|
||||
pub esc_timeout_ms: u32,
|
||||
pub pause_never: bool,
|
||||
pub pause_dumb_terminals: bool,
|
||||
pub pause_always: bool,
|
||||
pub use_internal_edit: bool,
|
||||
pub use_internal_view: bool,
|
||||
pub ask_new_file_name: bool,
|
||||
pub auto_menus: bool,
|
||||
pub drop_down_menus: bool,
|
||||
pub shell_patterns: bool,
|
||||
pub complete_show_all: bool,
|
||||
pub rotating_dash: bool,
|
||||
pub cd_follows_links: bool,
|
||||
pub safe_overwrite: bool,
|
||||
focused: usize,
|
||||
}
|
||||
|
||||
impl ConfigDialog {
|
||||
/// Number of checkboxes in the dialog.
|
||||
const COUNT: usize = 5;
|
||||
const COUNT: usize = 23;
|
||||
|
||||
/// Create a new dialog initialised from a `ConfigSettings` snapshot.
|
||||
#[must_use]
|
||||
pub fn new(initial: ConfigSettings) -> Self {
|
||||
Self {
|
||||
@@ -103,19 +122,34 @@ impl ConfigDialog {
|
||||
auto_save_setup: initial.auto_save_setup,
|
||||
safe_delete: initial.safe_delete,
|
||||
pause_after_run: initial.pause_after_run,
|
||||
compute_totals: initial.compute_totals,
|
||||
classic_progress: initial.classic_progress,
|
||||
mkdir_autoname: initial.mkdir_autoname,
|
||||
preallocate_space: initial.preallocate_space,
|
||||
esc_single_press: initial.esc_single_press,
|
||||
esc_timeout_ms: initial.esc_timeout_ms,
|
||||
pause_never: initial.pause_never,
|
||||
pause_dumb_terminals: initial.pause_dumb_terminals,
|
||||
pause_always: initial.pause_always,
|
||||
use_internal_edit: initial.use_internal_edit,
|
||||
use_internal_view: initial.use_internal_view,
|
||||
ask_new_file_name: initial.ask_new_file_name,
|
||||
auto_menus: initial.auto_menus,
|
||||
drop_down_menus: initial.drop_down_menus,
|
||||
shell_patterns: initial.shell_patterns,
|
||||
complete_show_all: initial.complete_show_all,
|
||||
rotating_dash: initial.rotating_dash,
|
||||
cd_follows_links: initial.cd_follows_links,
|
||||
safe_overwrite: initial.safe_overwrite,
|
||||
focused: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new dialog initialised from the active [`RuntimeConfig`].
|
||||
///
|
||||
/// [`RuntimeConfig`]: crate::config::RuntimeConfig
|
||||
#[must_use]
|
||||
pub fn from_runtime_config(rt: &crate::config::RuntimeConfig) -> Self {
|
||||
Self::new(ConfigSettings::from_runtime(rt))
|
||||
}
|
||||
|
||||
/// Snapshot the current checkbox values as a `ConfigSettings`.
|
||||
#[must_use]
|
||||
pub fn settings(&self) -> ConfigSettings {
|
||||
ConfigSettings {
|
||||
@@ -124,21 +158,37 @@ impl ConfigDialog {
|
||||
auto_save_setup: self.auto_save_setup,
|
||||
safe_delete: self.safe_delete,
|
||||
pause_after_run: self.pause_after_run,
|
||||
compute_totals: self.compute_totals,
|
||||
classic_progress: self.classic_progress,
|
||||
mkdir_autoname: self.mkdir_autoname,
|
||||
preallocate_space: self.preallocate_space,
|
||||
esc_single_press: self.esc_single_press,
|
||||
esc_timeout_ms: self.esc_timeout_ms,
|
||||
pause_never: self.pause_never,
|
||||
pause_dumb_terminals: self.pause_dumb_terminals,
|
||||
pause_always: self.pause_always,
|
||||
use_internal_edit: self.use_internal_edit,
|
||||
use_internal_view: self.use_internal_view,
|
||||
ask_new_file_name: self.ask_new_file_name,
|
||||
auto_menus: self.auto_menus,
|
||||
drop_down_menus: self.drop_down_menus,
|
||||
shell_patterns: self.shell_patterns,
|
||||
complete_show_all: self.complete_show_all,
|
||||
rotating_dash: self.rotating_dash,
|
||||
cd_follows_links: self.cd_follows_links,
|
||||
safe_overwrite: self.safe_overwrite,
|
||||
}
|
||||
}
|
||||
|
||||
/// Index of the currently focused checkbox.
|
||||
#[must_use]
|
||||
pub fn focused(&self) -> usize {
|
||||
self.focused
|
||||
}
|
||||
|
||||
/// Move focus to the next checkbox (wraps at the end).
|
||||
pub fn focus_next(&mut self) {
|
||||
self.focused = (self.focused + 1) % Self::COUNT;
|
||||
}
|
||||
|
||||
/// Move focus to the previous checkbox (wraps at zero).
|
||||
pub fn focus_prev(&mut self) {
|
||||
self.focused = if self.focused == 0 {
|
||||
Self::COUNT - 1
|
||||
@@ -147,19 +197,47 @@ impl ConfigDialog {
|
||||
};
|
||||
}
|
||||
|
||||
/// Toggle the focused checkbox.
|
||||
pub fn toggle_focused(&mut self) {
|
||||
match self.focused {
|
||||
0 => self.esc_exit_mode = !self.esc_exit_mode,
|
||||
1 => self.verbose_ops = !self.verbose_ops,
|
||||
2 => self.auto_save_setup = !self.auto_save_setup,
|
||||
3 => self.safe_delete = !self.safe_delete,
|
||||
4 => self.pause_after_run = !self.pause_after_run,
|
||||
0 => self.verbose_ops = !self.verbose_ops,
|
||||
1 => self.compute_totals = !self.compute_totals,
|
||||
2 => self.classic_progress = !self.classic_progress,
|
||||
3 => self.mkdir_autoname = !self.mkdir_autoname,
|
||||
4 => self.preallocate_space = !self.preallocate_space,
|
||||
5 => self.esc_single_press = !self.esc_single_press,
|
||||
6 => {
|
||||
self.pause_never = true;
|
||||
self.pause_dumb_terminals = false;
|
||||
self.pause_always = false;
|
||||
}
|
||||
7 => {
|
||||
self.pause_never = false;
|
||||
self.pause_dumb_terminals = true;
|
||||
self.pause_always = false;
|
||||
}
|
||||
8 => {
|
||||
self.pause_never = false;
|
||||
self.pause_dumb_terminals = false;
|
||||
self.pause_always = true;
|
||||
}
|
||||
9 => self.use_internal_edit = !self.use_internal_edit,
|
||||
10 => self.use_internal_view = !self.use_internal_view,
|
||||
11 => self.ask_new_file_name = !self.ask_new_file_name,
|
||||
12 => self.auto_menus = !self.auto_menus,
|
||||
13 => self.drop_down_menus = !self.drop_down_menus,
|
||||
14 => self.shell_patterns = !self.shell_patterns,
|
||||
15 => self.complete_show_all = !self.complete_show_all,
|
||||
16 => self.rotating_dash = !self.rotating_dash,
|
||||
17 => self.cd_follows_links = !self.cd_follows_links,
|
||||
18 => self.safe_delete = !self.safe_delete,
|
||||
19 => self.safe_overwrite = !self.safe_overwrite,
|
||||
20 => self.auto_save_setup = !self.auto_save_setup,
|
||||
21 => self.esc_exit_mode = !self.esc_exit_mode,
|
||||
22 => self.pause_after_run = !self.pause_after_run,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Process a key. Returns the dialog's resolution.
|
||||
pub fn handle_key(&mut self, key: Key) -> ConfigResult {
|
||||
if key == Key::ENTER {
|
||||
return ConfigResult::Confirm(self.settings());
|
||||
@@ -171,6 +249,14 @@ impl ConfigDialog {
|
||||
self.focus_next();
|
||||
return ConfigResult::Running;
|
||||
}
|
||||
if key.code == UP {
|
||||
self.focus_prev();
|
||||
return ConfigResult::Running;
|
||||
}
|
||||
if key.code == DOWN {
|
||||
self.focus_next();
|
||||
return ConfigResult::Running;
|
||||
}
|
||||
if let Some(ch) = char::from_u32(key.code) {
|
||||
if ch == ' ' {
|
||||
self.toggle_focused();
|
||||
@@ -180,36 +266,72 @@ impl ConfigDialog {
|
||||
ConfigResult::Running
|
||||
}
|
||||
|
||||
/// Render the dialog centered on `area`.
|
||||
pub fn render(&self, frame: &mut Frame, area: Rect, theme: &Theme) {
|
||||
let dlg = centered_cols_rect(area, 44, 12);
|
||||
let dlg = centered_cols_rect(area, 56, 26);
|
||||
let inner = render_popup(frame, dlg, "Configuration", theme);
|
||||
|
||||
let rows = Layout::default()
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Min(1),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.constraints([
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Min(1),
|
||||
])
|
||||
.split(inner);
|
||||
|
||||
let labels = [
|
||||
("Esc exit mode", self.esc_exit_mode),
|
||||
("Verbose operations", self.verbose_ops),
|
||||
("Auto save setup", self.auto_save_setup),
|
||||
("Safe delete", self.safe_delete),
|
||||
("Pause after run", self.pause_after_run),
|
||||
let entries: Vec<(usize, &str, bool, bool)> = vec![
|
||||
(0, "Verbose operations", self.verbose_ops, false),
|
||||
(1, "Compute totals", self.compute_totals, false),
|
||||
(2, "Classic progressbar", self.classic_progress, false),
|
||||
(3, "Mkdir autoname", self.mkdir_autoname, false),
|
||||
(4, "Preallocate space", self.preallocate_space, false),
|
||||
(5, "Esc mode: single press", self.esc_single_press, false),
|
||||
(6, "Pause never", self.pause_never, true),
|
||||
(7, "Pause on dumb terminals", self.pause_dumb_terminals, true),
|
||||
(8, "Pause always", self.pause_always, true),
|
||||
(9, "Use internal edit", self.use_internal_edit, false),
|
||||
(10, "Use internal view", self.use_internal_view, false),
|
||||
(11, "Ask new file name", self.ask_new_file_name, false),
|
||||
(12, "Auto menus", self.auto_menus, false),
|
||||
(13, "Drop down menus", self.drop_down_menus, false),
|
||||
(14, "Shell patterns", self.shell_patterns, false),
|
||||
(15, "Complete: show all", self.complete_show_all, false),
|
||||
(16, "Rotating dash", self.rotating_dash, false),
|
||||
(17, "Cd follows links", self.cd_follows_links, false),
|
||||
(18, "Safe delete", self.safe_delete, false),
|
||||
(19, "Safe overwrite", self.safe_overwrite, false),
|
||||
(20, "Auto save setup", self.auto_save_setup, false),
|
||||
(21, "Esc exit mode", self.esc_exit_mode, false),
|
||||
(22, "Pause after run", self.pause_after_run, false),
|
||||
];
|
||||
for (i, (label, checked)) in labels.iter().enumerate() {
|
||||
let focused = i == self.focused;
|
||||
let mark = if *checked { "[x]" } else { "[ ]" };
|
||||
for (idx, label, checked, is_radio) in entries.iter() {
|
||||
let focused = *idx == self.focused;
|
||||
let mark = if *is_radio {
|
||||
if *checked { "(*)" } else { "( )" }
|
||||
} else {
|
||||
if *checked { "[x]" } else { "[ ]" }
|
||||
};
|
||||
let style = if focused {
|
||||
Style::default()
|
||||
.fg(theme.cursor_fg)
|
||||
@@ -219,11 +341,11 @@ impl ConfigDialog {
|
||||
Style::default().fg(theme.foreground)
|
||||
};
|
||||
let line = format!("{mark} {label}");
|
||||
frame.render_widget(Paragraph::new(Span::styled(line, style)), rows[i]);
|
||||
frame.render_widget(Paragraph::new(Line::from(Span::styled(line, style))), chunks[*idx]);
|
||||
}
|
||||
|
||||
let hint = Line::from(vec![
|
||||
Span::styled("Tab", Style::default().fg(theme.executable).add_modifier(Modifier::BOLD)),
|
||||
Span::styled("Tab/Up/Down", Style::default().fg(theme.executable).add_modifier(Modifier::BOLD)),
|
||||
Span::styled(" cycle ", Style::default().fg(theme.hidden)),
|
||||
Span::styled("Space", Style::default().fg(theme.executable).add_modifier(Modifier::BOLD)),
|
||||
Span::styled(" toggle ", Style::default().fg(theme.hidden)),
|
||||
@@ -232,22 +354,40 @@ impl ConfigDialog {
|
||||
Span::styled("Esc", Style::default().fg(theme.warning).add_modifier(Modifier::BOLD)),
|
||||
Span::styled(" cancel", Style::default().fg(theme.hidden)),
|
||||
]);
|
||||
frame.render_widget(Paragraph::new(hint), rows[5]);
|
||||
frame.render_widget(Paragraph::new(hint), chunks[23]);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::RuntimeConfig;
|
||||
|
||||
fn settings() -> ConfigSettings {
|
||||
ConfigSettings {
|
||||
esc_exit_mode: true,
|
||||
verbose_ops: false,
|
||||
verbose_ops: true,
|
||||
auto_save_setup: false,
|
||||
safe_delete: true,
|
||||
pause_after_run: false,
|
||||
compute_totals: true,
|
||||
classic_progress: false,
|
||||
mkdir_autoname: false,
|
||||
preallocate_space: false,
|
||||
esc_single_press: true,
|
||||
esc_timeout_ms: 750,
|
||||
pause_never: true,
|
||||
pause_dumb_terminals: false,
|
||||
pause_always: false,
|
||||
use_internal_edit: true,
|
||||
use_internal_view: true,
|
||||
ask_new_file_name: false,
|
||||
auto_menus: false,
|
||||
drop_down_menus: false,
|
||||
shell_patterns: true,
|
||||
complete_show_all: false,
|
||||
rotating_dash: true,
|
||||
cd_follows_links: false,
|
||||
safe_overwrite: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,22 +396,9 @@ mod tests {
|
||||
let d = ConfigDialog::new(settings());
|
||||
assert_eq!(d.focused(), 0);
|
||||
assert!(d.esc_exit_mode);
|
||||
assert!(!d.verbose_ops);
|
||||
assert!(!d.auto_save_setup);
|
||||
assert!(d.safe_delete);
|
||||
assert!(!d.pause_after_run);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_runtime_config_uses_resolver() {
|
||||
let mut rt = RuntimeConfig::default();
|
||||
rt.esc_exit_mode = Some(false);
|
||||
rt.verbose_ops = Some(true);
|
||||
rt.auto_save_setup = Some(true);
|
||||
let d = ConfigDialog::from_runtime_config(&rt);
|
||||
assert!(!d.esc_exit_mode);
|
||||
assert!(d.verbose_ops);
|
||||
assert!(d.auto_save_setup);
|
||||
assert!(d.safe_delete);
|
||||
assert!(d.compute_totals);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -281,14 +408,12 @@ mod tests {
|
||||
assert_eq!(d.focused(), 1);
|
||||
d.handle_key(Key::TAB);
|
||||
assert_eq!(d.focused(), 2);
|
||||
d.handle_key(Key::TAB);
|
||||
assert_eq!(d.focused(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tab_wraps_around_to_zero() {
|
||||
let mut d = ConfigDialog::new(settings());
|
||||
for _ in 0..5 {
|
||||
for _ in 0..ConfigDialog::COUNT {
|
||||
d.handle_key(Key::TAB);
|
||||
}
|
||||
assert_eq!(d.focused(), 0);
|
||||
@@ -297,35 +422,19 @@ mod tests {
|
||||
#[test]
|
||||
fn space_toggles_focused_checkbox() {
|
||||
let mut d = ConfigDialog::new(settings());
|
||||
assert!(d.esc_exit_mode);
|
||||
d.handle_key(Key::from_char(' '));
|
||||
assert!(!d.esc_exit_mode);
|
||||
d.handle_key(Key::from_char(' '));
|
||||
assert!(d.esc_exit_mode);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn space_toggles_verbose_ops_when_focused() {
|
||||
let mut d = ConfigDialog::new(settings());
|
||||
d.handle_key(Key::TAB);
|
||||
d.handle_key(Key::from_char(' '));
|
||||
assert!(d.verbose_ops);
|
||||
d.handle_key(Key::from_char(' '));
|
||||
assert!(!d.verbose_ops);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enter_returns_current_settings() {
|
||||
let mut d = ConfigDialog::new(settings());
|
||||
d.handle_key(Key::TAB);
|
||||
d.handle_key(Key::TAB);
|
||||
d.handle_key(Key::from_char(' '));
|
||||
let result = d.handle_key(Key::ENTER);
|
||||
match result {
|
||||
ConfigResult::Confirm(s) => {
|
||||
assert!(s.esc_exit_mode);
|
||||
assert!(!s.verbose_ops);
|
||||
assert!(s.auto_save_setup);
|
||||
assert!(s.safe_delete);
|
||||
assert!(!s.pause_after_run);
|
||||
assert_eq!(s.verbose_ops, d.verbose_ops);
|
||||
}
|
||||
other => panic!("expected Confirm, got {other:?}"),
|
||||
}
|
||||
@@ -340,13 +449,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn settings_round_trips_through_dialog() {
|
||||
let initial = ConfigSettings {
|
||||
esc_exit_mode: false,
|
||||
verbose_ops: true,
|
||||
auto_save_setup: true,
|
||||
safe_delete: false,
|
||||
pause_after_run: true,
|
||||
};
|
||||
let initial = settings();
|
||||
let mut d = ConfigDialog::new(initial.clone());
|
||||
let result = d.handle_key(Key::ENTER);
|
||||
match result {
|
||||
@@ -358,9 +461,8 @@ mod tests {
|
||||
#[test]
|
||||
fn render_does_not_panic() {
|
||||
let d = ConfigDialog::new(settings());
|
||||
let backend = ratatui::backend::TestBackend::new(80, 24);
|
||||
let mut terminal =
|
||||
ratatui::Terminal::new(backend).expect("create test terminal");
|
||||
let backend = ratatui::backend::TestBackend::new(80, 30);
|
||||
let mut terminal = ratatui::Terminal::new(backend).expect("create test terminal");
|
||||
terminal
|
||||
.draw(|f| {
|
||||
d.render(f, f.area(), &crate::terminal::color::DEFAULT_THEME);
|
||||
|
||||
Reference in New Issue
Block a user