mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-30 21:47:00 -06:00
Duplicate: number the copy from the shape count, not the count of distinct names
This commit is contained in:
@@ -330,7 +330,7 @@ fn paste(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
&& !n.is_empty()
|
||||
&& n.bytes().all(|b| b.is_ascii_digit())
|
||||
{
|
||||
sh.name = super::insert::first_free_name(base, &taken);
|
||||
sh.name = super::insert::first_free_name(base, &taken, taken.len());
|
||||
}
|
||||
taken.insert(sh.name.clone());
|
||||
if let Some(x) = sh.xfrm.as_mut() {
|
||||
@@ -512,11 +512,11 @@ fn paste_text(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
/// PowerPoint does, and keeps a name the user chose. PowerPoint tells the two apart by comparing
|
||||
/// the number with the shape's id; ids here are numbered across the deck, so any "{kind} {n}"
|
||||
/// counts as automatic.
|
||||
fn rename_automatic(sh: &mut Shape, taken: &mut std::collections::HashSet<String>) {
|
||||
fn rename_automatic(sh: &mut Shape, taken: &mut std::collections::HashSet<String>, count: usize) {
|
||||
let base = if sh.is_group() { "Group".to_string() } else { super::insert::auto_name_base(sh) };
|
||||
let n = sh.name.strip_prefix(&base).and_then(|r| r.strip_prefix(' '));
|
||||
if n.is_some_and(|n| !n.is_empty() && n.bytes().all(|b| b.is_ascii_digit())) {
|
||||
sh.name = super::insert::first_free_name(&base, taken);
|
||||
sh.name = super::insert::first_free_name(&base, taken, count);
|
||||
taken.insert(sh.name.clone());
|
||||
}
|
||||
}
|
||||
@@ -547,10 +547,15 @@ fn duplicate(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
reid("edit.duplicate", doc, &mut copies)?;
|
||||
// Every name on the slide, group members included, so a copy never repeats one.
|
||||
let mut taken = std::collections::HashSet::new();
|
||||
let mut count = 0;
|
||||
deckcraft_model::walk(&list, &mut |sh, _| {
|
||||
taken.insert(sh.name.clone());
|
||||
count += 1;
|
||||
});
|
||||
walk_mut(&mut copies, &mut |c| {
|
||||
rename_automatic(c, &mut taken, count);
|
||||
count += 1;
|
||||
});
|
||||
walk_mut(&mut copies, &mut |c| rename_automatic(c, &mut taken));
|
||||
for (c, b) in copies.iter_mut().zip(boxes) {
|
||||
let mut x = b;
|
||||
x.x += dx;
|
||||
|
||||
@@ -172,9 +172,10 @@ pub(crate) fn auto_name_base(shape: &Shape) -> String {
|
||||
}
|
||||
|
||||
/// "{base} n" with the first `n` from the shape count + 1 that no shape on the slide already uses,
|
||||
/// so a gap left by a deleted shape never makes a second shape with the same name.
|
||||
pub(crate) fn first_free_name(base: &str, taken: &std::collections::HashSet<String>) -> String {
|
||||
(taken.len() + 1..).map(|n| format!("{base} {n}")).find(|c| !taken.contains(c)).unwrap_or_default()
|
||||
/// so a gap left by a deleted shape never makes a second shape with the same name. `count` is the
|
||||
/// number of shapes, which exceeds the number of names when two shapes share one.
|
||||
pub(crate) fn first_free_name(base: &str, taken: &std::collections::HashSet<String>, count: usize) -> String {
|
||||
(count + 1..).map(|n| format!("{base} {n}")).find(|c| !taken.contains(c)).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) fn add_shape(s: &mut Session, cmd: &str, mut shape: Shape, select: bool) -> Result<ShapeId> {
|
||||
@@ -183,7 +184,7 @@ pub(crate) fn add_shape(s: &mut Session, cmd: &str, mut shape: Shape, select: bo
|
||||
if shape.name.is_empty() {
|
||||
let base = auto_name_base(&shape);
|
||||
let taken = super::current_names(doc, sel);
|
||||
shape.name = first_free_name(&base, &taken);
|
||||
shape.name = first_free_name(&base, &taken, taken.len());
|
||||
}
|
||||
let id = shape.id;
|
||||
let list = crate::shapes_mut(doc, sel).ok_or_else(|| bad("insert", "no slide"))?;
|
||||
|
||||
@@ -268,6 +268,26 @@ fn duplicate_gives_the_next_automatic_name_and_keeps_a_chosen_one() {
|
||||
assert_eq!(&all[n + 2..], ["Hero", "Hero"], "{all:?}");
|
||||
}
|
||||
|
||||
/// Two shapes sharing a chosen name are still two shapes: the next number follows the count.
|
||||
#[test]
|
||||
fn duplicate_numbers_from_the_shape_count_when_names_repeat() {
|
||||
let mut s = session();
|
||||
s.execute("shape.insert", &json!({"preset": "rect", "rect": [10, 10, 50, 50]})).unwrap();
|
||||
let rect = s.doc().unwrap().selection.shapes[0].0;
|
||||
s.execute("shape.insert", &json!({"preset": "rect", "rect": [100, 10, 50, 50]})).unwrap();
|
||||
let hero = s.doc().unwrap().selection.shapes[0].0;
|
||||
s.execute("shape.rename", &json!({"id": hero, "name": "Hero"})).unwrap();
|
||||
s.execute("edit.duplicate", &json!({"ids": [hero]})).unwrap();
|
||||
let before = s.doc().unwrap().current_slide().unwrap().shapes.len();
|
||||
s.execute("edit.duplicate", &json!({"ids": [rect]})).unwrap();
|
||||
let doc = s.doc().unwrap();
|
||||
let shapes = &doc.current_slide().unwrap().shapes;
|
||||
let source = shapes.iter().find(|x| x.id.0 == rect).unwrap();
|
||||
let base = source.name.rsplit_once(' ').unwrap().0;
|
||||
let names: Vec<_> = shapes.iter().map(|x| x.name.as_str()).collect();
|
||||
assert_eq!(shapes[before].name, format!("{base} {}", before + 1), "{names:?}");
|
||||
}
|
||||
|
||||
/// The next automatic name looks at the names inside groups too.
|
||||
#[test]
|
||||
fn duplicating_a_group_does_not_repeat_the_names_of_its_members() {
|
||||
|
||||
Reference in New Issue
Block a user