diff --git a/crates/engine/src/cmd/edit.rs b/crates/engine/src/cmd/edit.rs index 758a2a4a0..eca532039 100644 --- a/crates/engine/src/cmd/edit.rs +++ b/crates/engine/src/cmd/edit.rs @@ -989,7 +989,7 @@ pub fn specs() -> Vec { ["Edit"], Some("Cmd+D"), "{layers?} (without layers: during a text edit the edited layer, else the selected effects, shape items or text animators, else the selected layers)", - has_layers, + layers_or_text_edit, duplicate ), cmd!("edit.splitLayer", "Split Layer", ["Edit"], Some("Cmd+Shift+D"), "{layers?}", has_layers, split), @@ -1061,6 +1061,11 @@ fn layers_or_keys(s: &Session) -> std::result::Result<(), String> { if !s.state.selected_keys.is_empty() || !s.state.selected_vertices.is_empty() { super::has_comp(s) } else { has_layers(s) } } +/// Duplicate's unnamed target: the edited layer during a text edit, else the selection. +fn layers_or_text_edit(s: &Session) -> std::result::Result<(), String> { + if s.state.text_edit.is_some() { super::has_comp(s) } else { has_layers(s) } +} + fn always_ok(_: &Session) -> std::result::Result<(), String> { Ok(()) } diff --git a/crates/engine/src/tests/text_edit.rs b/crates/engine/src/tests/text_edit.rs index dfd2596cd..7a8598a72 100644 --- a/crates/engine/src/tests/text_edit.rs +++ b/crates/engine/src/tests/text_edit.rs @@ -1341,6 +1341,25 @@ fn duplicate_during_an_edit_copies_the_edited_layer_after_another_is_selected() assert_eq!(doc(&s, named).text, "Other", "a named layer is copied as named"); } +#[test] +fn duplicate_during_an_edit_with_nothing_selected_copies_the_edited_layer() { + use effectcraft_project::LayerId; + // An engine route can also clear the selection while the edit stays open. Duplicate stays + // available, since its unnamed target is the edited layer, not the selection. + let (mut s, _) = editing("Hello", 5); + s.execute("layer.select", json!({"layers": []})).unwrap(); + assert!(s.state.selected_layers.is_empty() && s.state.text_edit.is_some(), "the edit is open with nothing selected"); + let copy = s.execute("edit.duplicate", json!({})).unwrap()[0].as_u64().unwrap(); + assert!(s.state.text_edit.is_none(), "Duplicate ends the open edit"); + assert_eq!(doc(&s, copy).text, "Hello", "and copies the edited layer"); + assert_eq!(s.state.selected_layers, vec![LayerId(copy)], "which is selected"); + // With no edit open, nothing selected still means nothing to duplicate. + s.execute("layer.select", json!({"layers": []})).unwrap(); + let before = layer_count(&s); + assert!(s.execute("edit.duplicate", json!({})).is_err(), "outside an edit Duplicate needs a selection"); + assert_eq!(layer_count(&s), before, "and copies nothing"); +} + #[test] fn duplicate_ends_the_open_edit_and_selects_the_copy() { let (mut s, t) = editing("Hello", 5);