Skip to content

Commit

Permalink
feat: prioritize paths that need to be processed first during bulk re…
Browse files Browse the repository at this point in the history
…naming (#1801)

Co-authored-by: sxyazi <[email protected]>
  • Loading branch information
yw1ee and sxyazi authored Oct 30, 2024
1 parent 5eabd7d commit 7f30231
Showing 1 changed file with 87 additions and 1 deletion.
88 changes: 87 additions & 1 deletion yazi-core/src/manager/commands/bulk_rename.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ impl Manager {
return Ok(());
}

let todo: Vec<_> = old.into_iter().zip(new).filter(|(o, n)| o != n).collect();
let todo = Self::prioritized_paths(old, new);
if todo.is_empty() {
return Ok(());
}
Expand Down Expand Up @@ -117,4 +117,90 @@ impl Manager {
stdin().read_exact(&mut [0]).await?;
Ok(())
}

fn prioritized_paths(old: Vec<PathBuf>, new: Vec<PathBuf>) -> Vec<(PathBuf, PathBuf)> {
let orders: HashMap<_, _> = old.iter().enumerate().map(|(i, p)| (p, i)).collect();
let mut incomes: HashMap<_, _> = old.iter().map(|p| (p, false)).collect();
let mut todos: HashMap<_, _> = old
.iter()
.zip(new)
.map(|(o, n)| {
incomes.get_mut(&n).map(|b| *b = true);
(o, n)
})
.collect();

let mut sorted = Vec::with_capacity(old.len());
while !todos.is_empty() {
// Paths that are non-incomes and don't need to be prioritized in this round
let mut outcomes: Vec<_> = incomes.iter().filter(|(_, &b)| !b).map(|(&p, _)| p).collect();
outcomes.sort_unstable_by(|a, b| orders[b].cmp(&orders[a]));

// If there're no outcomes, it means there are cycles in the renaming
if outcomes.is_empty() {
let mut remain: Vec<_> = todos.into_iter().map(|(o, n)| (o.clone(), n)).collect();
remain.sort_unstable_by(|(a, _), (b, _)| orders[a].cmp(&orders[b]));
sorted.reverse();
sorted.extend(remain);
return sorted;
}

for old in outcomes {
let Some(new) = todos.remove(old) else { unreachable!() };
incomes.remove(&old);
incomes.get_mut(&new).map(|b| *b = false);
sorted.push((old.clone(), new));
}
}
sorted.reverse();
sorted
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_sort() {
fn cmp(input: &[(&str, &str)], expected: &[(&str, &str)]) {
let sorted = Manager::prioritized_paths(
input.iter().map(|&(o, _)| o.into()).collect(),
input.iter().map(|&(_, n)| n.into()).collect(),
);
let sorted: Vec<_> =
sorted.iter().map(|(o, n)| (o.to_str().unwrap(), n.to_str().unwrap())).collect();
assert_eq!(sorted, expected);
}

#[rustfmt::skip]
cmp(
&[("2", "3"), ("1", "2"), ("3", "4")],
&[("3", "4"), ("2", "3"), ("1", "2")]
);

#[rustfmt::skip]
cmp(
&[("1", "3"), ("2", "3"), ("3", "4")],
&[("3", "4"), ("1", "3"), ("2", "3")]
);

#[rustfmt::skip]
cmp(
&[("2", "1"), ("1", "2")],
&[("2", "1"), ("1", "2")]
);

#[rustfmt::skip]
cmp(
&[("3", "2"), ("2", "1"), ("1", "3"), ("a", "b"), ("b", "c")],
&[("b", "c"), ("a", "b"), ("3", "2"), ("2", "1"), ("1", "3")]
);

#[rustfmt::skip]
cmp(
&[("b", "b_"), ("a", "a_"), ("c", "c_")],
&[("b", "b_"), ("a", "a_"), ("c", "c_")],
);
}
}

0 comments on commit 7f30231

Please sign in to comment.