Merge pull request #1614 from Rics-Dev/feat/count-files-replace-all

feat: Show count of files affected when "Apply to all" is selected in copy/move operations
This commit is contained in:
Jeremy Soller 2026-02-24 10:25:16 -07:00 committed by GitHub
commit f645c55a38
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 29 additions and 7 deletions

View file

@ -578,6 +578,7 @@ pub enum DialogPage {
to: tab::Item, to: tab::Item,
multiple: bool, multiple: bool,
apply_to_all: bool, apply_to_all: bool,
conflict_count: usize,
tx: mpsc::Sender<ReplaceResult>, tx: mpsc::Sender<ReplaceResult>,
}, },
SetExecutableAndLaunch { SetExecutableAndLaunch {
@ -5857,6 +5858,7 @@ impl Application for App {
to, to,
multiple, multiple,
apply_to_all, apply_to_all,
conflict_count,
tx, tx,
} => { } => {
let military_time = self.config.tab.military_time; let military_time = self.config.tab.military_time;
@ -5881,13 +5883,18 @@ impl Application for App {
if *multiple { if *multiple {
dialog dialog
.control( .control(
widget::checkbox(fl!("apply-to-all"), *apply_to_all).on_toggle( widget::checkbox(
format!("{} ({})" ,fl!("apply-to-all"), *conflict_count),
*apply_to_all,
)
.on_toggle(
|apply_to_all| { |apply_to_all| {
Message::DialogUpdate(DialogPage::Replace { Message::DialogUpdate(DialogPage::Replace {
from: from.clone(), from: from.clone(),
to: to.clone(), to: to.clone(),
multiple: *multiple, multiple: *multiple,
apply_to_all, apply_to_all,
conflict_count: *conflict_count,
tx: tx.clone(), tx: tx.clone(),
}) })
}, },

View file

@ -32,6 +32,7 @@ async fn handle_replace(
file_from: PathBuf, file_from: PathBuf,
file_to: PathBuf, file_to: PathBuf,
multiple: bool, multiple: bool,
conflict_count: usize,
) -> ReplaceResult { ) -> ReplaceResult {
let item_from = match tab::item_from_path(file_from, IconSizes::default()) { let item_from = match tab::item_from_path(file_from, IconSizes::default()) {
Ok(ok) => ok, Ok(ok) => ok,
@ -59,6 +60,7 @@ async fn handle_replace(
to: item_to, to: item_to,
multiple, multiple,
apply_to_all: false, apply_to_all: false,
conflict_count,
tx, tx,
}, },
Some(REPLACE_BUTTON_ID.clone()), Some(REPLACE_BUTTON_ID.clone()),
@ -180,9 +182,9 @@ async fn copy_or_move(
{ {
let msg_tx = msg_tx.clone(); let msg_tx = msg_tx.clone();
context = context.on_replace(move |op| { context = context.on_replace(move |op, conflict_count| {
let msg_tx = msg_tx.clone(); let msg_tx = msg_tx.clone();
Box::pin(handle_replace(msg_tx, op.from.clone(), op.to.clone(), true)) Box::pin(handle_replace(msg_tx, op.from.clone(), op.to.clone(), true, conflict_count))
}); });
} }

View file

@ -23,17 +23,18 @@ pub struct Context {
on_replace: Pin<Box<dyn OnReplace>>, on_replace: Pin<Box<dyn OnReplace>>,
pub(crate) op_sel: OperationSelection, pub(crate) op_sel: OperationSelection,
replace_result_opt: Option<ReplaceResult>, replace_result_opt: Option<ReplaceResult>,
remaining_conflicts: usize,
} }
pub trait OnProgress: Fn(&Op, &Progress) + 'static {} pub trait OnProgress: Fn(&Op, &Progress) + 'static {}
impl<F> OnProgress for F where F: Fn(&Op, &Progress) + 'static {} impl<F> OnProgress for F where F: Fn(&Op, &Progress) + 'static {}
pub trait OnReplace: pub trait OnReplace:
for<'a> Fn(&'a Op) -> Pin<Box<dyn Future<Output = ReplaceResult> + 'a>> + 'static for<'a> Fn(&'a Op, usize) -> Pin<Box<dyn Future<Output = ReplaceResult> + 'a>> + 'static
{ {
} }
impl<F> OnReplace for F where impl<F> OnReplace for F where
F: for<'a> Fn(&'a Op) -> Pin<Box<dyn Future<Output = ReplaceResult> + 'a>> + 'static F: for<'a> Fn(&'a Op, usize) -> Pin<Box<dyn Future<Output = ReplaceResult> + 'a>> + 'static
{ {
} }
@ -44,9 +45,10 @@ impl Context {
buf: vec![0u8; 128 * 1024], buf: vec![0u8; 128 * 1024],
controller, controller,
on_progress: Box::new(|_op, _progress| {}), on_progress: Box::new(|_op, _progress| {}),
on_replace: Box::pin(|_op| Box::pin(async { ReplaceResult::Cancel })), on_replace: Box::pin(|_op, _count| Box::pin(async { ReplaceResult::Cancel })),
op_sel: OperationSelection::default(), op_sel: OperationSelection::default(),
replace_result_opt: None, replace_result_opt: None,
remaining_conflicts: 0,
} }
} }
@ -156,6 +158,17 @@ impl Context {
cleanup_ops.reverse(); cleanup_ops.reverse();
ops.append(&mut cleanup_ops); ops.append(&mut cleanup_ops);
// Count potential conflicts (files that would need replacement)
self.remaining_conflicts = ops
.iter()
.filter(|op| {
matches!(
op.kind,
OpKind::Copy | OpKind::Move { .. } | OpKind::Symlink { .. }
) && op.to.is_file()
})
.count();
let total_ops = ops.len(); let total_ops = ops.len();
for (current_ops, mut op) in ops.into_iter().enumerate() { for (current_ops, mut op) in ops.into_iter().enumerate() {
self.controller self.controller
@ -221,7 +234,7 @@ impl Context {
async fn replace(&mut self, op: &Op) -> Result<ControlFlow<bool, PathBuf>, Box<dyn Error>> { async fn replace(&mut self, op: &Op) -> Result<ControlFlow<bool, PathBuf>, Box<dyn Error>> {
let replace_result = match self.replace_result_opt { let replace_result = match self.replace_result_opt {
Some(result) => result, Some(result) => result,
None => (self.on_replace)(op).await, None => (self.on_replace)(op, self.remaining_conflicts).await,
}; };
match replace_result { match replace_result {