diff --git a/crates/engine/src/cmd/library.rs b/crates/engine/src/cmd/library.rs index 3a1b40be6..f0e77af56 100644 --- a/crates/engine/src/cmd/library.rs +++ b/crates/engine/src/cmd/library.rs @@ -117,7 +117,7 @@ pub fn specs() -> Vec { ok() }), cmd!(noundo "conveyor.collect", "Collect", [], None, "{ids? (default: the selection)} — each object onto the Content Collector conveyor → {count}", has_selection, |s, p| { - let ids = super::ids_param(p, "ids").unwrap_or_else(|| s.active().map(|d| d.selection.items.clone()).unwrap_or_default()); + let ids = super::targets(s, p)?; let keep = s.active().map(|d| d.selection.clone()); for id in ids { let name = s.doc()?.doc.item(id).map(|it| it.default_label().trim_matches(|c| c == '<' || c == '>').to_string()).unwrap_or_default(); diff --git a/crates/engine/src/cmd/mod.rs b/crates/engine/src/cmd/mod.rs index ccc605c3a..941473561 100644 --- a/crates/engine/src/cmd/mod.rs +++ b/crates/engine/src/cmd/mod.rs @@ -269,8 +269,9 @@ pub(crate) fn named_targets(spec: &CommandSpec, p: &Value) -> Option if !spec.params.split(|c: char| !c.is_ascii_alphanumeric()).any(|w| w == "ids") { return None; } - if let Some(ids) = ids_param(p, "ids").filter(|v| !v.is_empty()) { - return Some(ids); + // As `targets` reads them: `ids`, when given, wins over `id` (an empty list names nothing). + if let Some(ids) = ids_param(p, "ids") { + return (!ids.is_empty()).then_some(ids); } id_param(p, "id").map(|i| vec![i]) } diff --git a/crates/engine/src/cmd/object.rs b/crates/engine/src/cmd/object.rs index 06e05cc59..60d5d1079 100644 --- a/crates/engine/src/cmd/object.rs +++ b/crates/engine/src/cmd/object.rs @@ -2413,5 +2413,11 @@ mod named_target_tests { let e = s.execute("object.textFrameOptions", &json!({"ids": [99999], "columns": 1})).unwrap_err().to_string(); assert!(e.contains("no object with id 99999"), "{e}"); assert_eq!(columns(&s), Some(2)); + // An empty `ids` names nothing (as `targets` reads it), even beside an `id`. + let e = s.execute("object.textFrameOptions", &json!({"ids": [], "id": r["id"], "columns": 3})).unwrap_err().to_string(); + assert!(e.contains("nothing selected"), "{e}"); + assert_eq!(columns(&s), Some(2)); + // Commands documented with `ids` honour `id` too. + assert_eq!(s.execute("conveyor.collect", &json!({"id": r["id"]})).unwrap()["count"], 1); } } diff --git a/crates/engine/src/cmd/states.rs b/crates/engine/src/cmd/states.rs index f9ebb874a..8c2a64cb8 100644 --- a/crates/engine/src/cmd/states.rs +++ b/crates/engine/src/cmd/states.rs @@ -3,7 +3,7 @@ use designcraft_doc::{ItemId, Shape}; use serde_json::{Value, json}; -use super::{CommandSpec, bad, cmd, has_selection, ids_param}; +use super::{CommandSpec, bad, cmd, has_selection}; use crate::{Result, Session}; fn target(s: &Session, p: &Value) -> Result { @@ -24,7 +24,7 @@ pub fn specs() -> Vec { "{ids? (default: the selection; one group's objects or several objects become states)} → {id, states}", has_selection, |s, p| { - let ids = ids_param(p, "ids").unwrap_or_else(|| s.active().map(|d| d.selection.items.clone()).unwrap_or_default()); + let ids = super::targets(s, p)?; let single_group = ids.len() == 1 && s.doc()?.doc.item(ids[0]).is_some_and(|it| it.shape == Shape::Group && it.children().len() > 1); let gid = if single_group { ids[0]