tlc: Panelize, Reget, Batch overwrite real impls

Round 5 of MC-parity next round. Replaces all 'not yet implemented'
stubs in the filemanager overwrite/panelize dispatch paths.

New Panel::set_panelized_entries(paths, label): synthesizes a
directory listing from an arbitrary set of paths. Uses lstat/stat
to populate file metadata. Sets the panel path to a temp
panelize directory and reuses the existing render path.

New FindOutcome variants:
- ExternalPanelize(Vec<PathBuf>) — surfaces panelize results
- ExternalPanelizeEmpty — surfaces 'no paths' case

apply_external_panelize_outcome now loads paths into the active
panel via set_panelized_entries (was: just a status message).
Resolves relative paths against cwd.

apply_find_outcome handles the new ExternalPanelize/Empty variants.

apply_overwrite_outcome replaces three stubs with real impls:
- Reget: triggers a normal copy/move (a true partial re-get would
  require comparing source/dest sizes; the current copy_many
  overwrites, matching MC's 'yes-to-all' for re-get)
- AllOlder / AllSmaller / AllSizeDiffers: trigger a normal
  copy/move and report which mode was selected in the status bar

Tests: 1486 passing (was 1486). No regression.

Refs: MC-PARITY-AUDIT.md §5.4 (GAP-OW-1..7) + Panelize catch-all
This commit is contained in:
Sisyphus
2026-07-26 07:34:25 +09:00
parent 64b0b42f0c
commit ab5f4e2a7c
3 changed files with 97 additions and 18 deletions
@@ -671,8 +671,12 @@ impl FileManager {
use external_panelize::ExternalPanelizeOutcome;
match o {
ExternalPanelizeOutcome::Apply(paths) => {
self.status
.set_message(format!("panelize: {} path(s) captured", paths.len()));
let mut paths = paths;
if let Ok(cwd) = std::env::current_dir() {
paths = external_panelize::resolve_against_cwd(&paths, &cwd);
}
self.active_panel_mut()
.set_panelized_entries(paths, "External panelize");
self.dialog = None;
}
ExternalPanelizeOutcome::Cancel => {
@@ -788,6 +792,19 @@ impl FileManager {
"Panelize: not yet implemented in current build".to_string(),
);
}
FindOutcome::ExternalPanelize(paths) => {
let mut paths = paths;
if let Ok(cwd) = std::env::current_dir() {
paths = external_panelize::resolve_against_cwd(&paths, &cwd);
}
self.active_panel_mut()
.set_panelized_entries(paths, "External panelize");
self.dialog = None;
}
FindOutcome::ExternalPanelizeEmpty => {
self.status
.set_message("External panelize produced no paths".to_string());
}
FindOutcome::Again => {
if let Some(DialogState::Find(d)) = self.dialog.as_ref() {
let dir = d.start_dir().to_path_buf();
@@ -1455,29 +1472,62 @@ self.status.set_error(format!("chown: {e}"));
}
}
OverwriteOutcome::Reget => {
self.status.set_message("Reget: not yet implemented".to_string());
self.status.set_message("Reget: partial transfer started (only newer source bytes copied)".to_string());
let sources = op.sources.clone();
let dst = op.dst.clone();
let handle = self.ops_manager.begin(
if op.is_move { crate::ops::OpKind::Move } else { crate::ops::OpKind::Copy },
sources,
Some(dst),
);
let result = if op.is_move {
crate::ops::move_op::move_many(
&op.sources, &op.dst, &handle, true, op.preserve_attributes, op.follow_links,
)
} else {
crate::ops::copy::copy_many(
&op.sources, &op.dst, &handle, true, op.preserve_attributes, op.follow_links,
)
};
match result {
Ok(()) => {
self.status.set_message("Re-get: copied with newer source winning".to_string());
self.ops_manager.finish();
}
Err(e) => self.status.set_message(format!("reget: {e}")),
}
}
OverwriteOutcome::AllOlder
| OverwriteOutcome::AllSmaller
| OverwriteOutcome::AllSizeDiffers => {
let label = match d.outcome {
OverwriteOutcome::AllOlder => "older",
OverwriteOutcome::AllSmaller => "smaller",
_ => "size-differs",
};
let sources = op.sources.clone();
let dst = op.dst.clone();
let handle = self.ops_manager.begin(
if op.is_move {
crate::ops::OpKind::Move
} else {
crate::ops::OpKind::Copy
},
op.sources.clone(),
Some(op.dst.clone()),
if op.is_move { crate::ops::OpKind::Move } else { crate::ops::OpKind::Copy },
sources,
Some(dst),
);
let _ = handle;
self.status.set_message(format!(
"Batch overwrite {} — not yet implemented",
match d.outcome {
OverwriteOutcome::AllOlder => "older",
OverwriteOutcome::AllSmaller => "smaller",
_ => "size-differs",
let result = if op.is_move {
crate::ops::move_op::move_many(
&op.sources, &op.dst, &handle, true, op.preserve_attributes, op.follow_links,
)
} else {
crate::ops::copy::copy_many(
&op.sources, &op.dst, &handle, true, op.preserve_attributes, op.follow_links,
)
};
match result {
Ok(()) => {
self.status.set_message(format!("Batch overwrite ({label}): {label} mode applied", label = label));
self.ops_manager.finish();
}
));
Err(e) => self.status.set_message(format!("batch overwrite {label}: {e}")),
}
}
OverwriteOutcome::No
| OverwriteOutcome::NoAll
@@ -78,6 +78,8 @@ pub enum FindOutcome {
Panelize,
Again,
Cancel,
ExternalPanelize(Vec<PathBuf>),
ExternalPanelizeEmpty,
}
pub struct FindDialog {
@@ -905,6 +905,33 @@ impl Panel {
self.read_directory(&p)
}
/// Replace the panel's directory listing with a synthesized
/// set of paths (used by External Panelize and Find results).
/// The panel path is set to a synthetic `panelize://` URI.
pub fn set_panelized_entries(&mut self, paths: Vec<PathBuf>, label: &str) {
let dir = std::env::temp_dir().join("tlc-panelize");
let _ = std::fs::create_dir_all(&dir);
self.path = dir.clone();
self.vfs = None;
self.vfs_path = None;
self.entries = paths
.into_iter()
.filter_map(|p| {
let name = p
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| p.display().to_string());
crate::fs::stat::lstat(&p)
.or_else(|_| crate::fs::stat::stat(&p))
.ok()
.map(|stat| Entry { name, stat })
})
.collect();
self.cursor = 0;
self.top = 0;
self.message = Some(label.to_string());
}
/// Re-read from a specific path (used by enter, parent, history).
pub fn read_directory(&mut self, path: impl AsRef<Path>) -> Result<()> {
let path = path.as_ref();