Refuse pages in edit.copy and edit.cut

Found while testing crates/engine/src/cmd/edit.rs:499 (copy_to_clipboard, called from copy at :536 and cut at :542)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-16 13:47:07 +00:00
co-authored by Claude Haiku 5.5
parent 23bdebd973
commit 0e25cf4c26
2 changed files with 26 additions and 2 deletions
+9 -2
View File
@@ -499,7 +499,12 @@ fn bump_name(n: &str) -> String {
pub fn copy_to_clipboard(s: &mut Session, ids: &[NodeId]) -> Result<()> {
let ds = s.ds()?;
let doc = &ds.doc;
let ids = z_sorted(doc, &roots(doc, ids));
// Pages are not layers: they can't be pasted anywhere.
let layers: Vec<NodeId> = ids.iter().copied().filter(|i| !doc.node(*i).is_some_and(Node::is_page)).collect();
if layers.is_empty() && !ids.is_empty() {
return Err(EngineError::Disabled("edit.copy".into(), "pages can't be copied; use page.duplicate".into()));
}
let ids = z_sorted(doc, &roots(doc, &layers));
let mut nodes = Vec::new();
let mut images = BTreeMap::new();
for i in &ids {
@@ -542,7 +547,9 @@ fn copy(s: &mut Session, p: &Value) -> Result<Value> {
fn cut(s: &mut Session, p: &Value) -> Result<Value> {
let ids = req_targets("edit.cut", s, p)?;
copy_to_clipboard(s, &ids)?;
delete(s, &json!({"ids": ids}))
let doc = s.doc()?;
let layers: Vec<NodeId> = ids.into_iter().filter(|i| !doc.node(*i).is_some_and(Node::is_page)).collect();
delete(s, &json!({"ids": layers}))
}
/// Paste the clipboard under `parent` with the copied top-left moved to `at` (page coords).
+17
View File
@@ -621,3 +621,20 @@ fn find_replace_changing_only_the_case_replaces_every_occurrence() {
assert_eq!(s.doc().unwrap().node(t).unwrap().kind.text().unwrap().characters, "FigCraft and FigCraft, FigCraft");
assert_eq!(s.doc().unwrap().node(f).unwrap().name, "FigCraft / FigCraft");
}
#[test]
fn copy_and_cut_ignore_pages_so_paste_keeps_the_document_valid() {
for cmd in ["edit.copy", "edit.cut"] {
let mut s = sess();
let page = s.page().unwrap();
s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap();
let r = s.execute(cmd, &json!({"ids": [page.0]}));
if let Ok(v) = &r {
assert!(v.get("deleted").is_none_or(|d| d == &json!([])), "{cmd} reported the page as deleted: {v}");
}
let _ = s.execute("edit.paste", &json!({}));
let doc = s.doc().unwrap();
assert!(doc.validate().is_empty(), "{cmd}: {:?}", doc.validate());
assert!(doc.node(page).is_some(), "{cmd} removed the page");
}
}