Slides: edit.selectAll takes scope slides to select every slide (#188)

The current slide stays put and any shape or text selection is cleared. A scope other than slides is an error; without a scope the command still selects shapes.
This commit is contained in:
Isaac Levine
2026-10-11 22:53:29 -04:00
parent be14248577
commit 96abeba71b
2 changed files with 63 additions and 2 deletions
+23 -2
View File
@@ -18,7 +18,7 @@ pub fn specs() -> Vec<CommandSpec> {
cmd!("edit.pasteText", "Paste and Match Formatting", ["Edit"], Some("Cmd+Shift+V"), "{text?}", has_doc, paste_text),
cmd!("edit.duplicate", "Duplicate", ["Edit"], Some("Cmd+D"), "{ids?}", has_selection, duplicate),
cmd!("edit.delete", "Delete", ["Edit"], Some("Delete"), "{ids?, scope?: slides}", has_doc, delete),
cmd!(noundo "edit.selectAll", "Select All", ["Edit"], Some("Cmd+A"), "{}", has_slide, select_all),
cmd!(noundo "edit.selectAll", "Select All", ["Edit"], Some("Cmd+A"), "{scope?: slides}", has_slide, select_all),
cmd!(noundo "edit.deselect", "Deselect", [], Some("Escape"), "{}", has_doc, deselect),
cmd!(noundo "edit.select", "Select Objects", [], None, "{ids: [id], add?: bool, toggle?: bool}", has_slide, select),
]
@@ -323,7 +323,12 @@ fn count(v: &[Shape]) -> usize {
n
}
fn select_all(s: &mut Session, _p: &Value) -> Result<Value> {
fn select_all(s: &mut Session, p: &Value) -> Result<Value> {
match p.get("scope") {
None | Some(Value::Null) => {}
Some(Value::String(v)) if v == "slides" => return select_all_slides(s),
Some(_) => return Err(bad("edit.selectAll", "scope must be \"slides\"")),
}
if s.doc()?.selection.text.is_some() {
return s.execute("text.selectAll", &json!({}));
}
@@ -335,6 +340,22 @@ fn select_all(s: &mut Session, _p: &Value) -> Result<Value> {
Ok(json!({"ids": ids}))
}
/// Thumbnail-pane / sorter select all: every slide, hidden ones included. The current
/// slide stays where it is (the canvas must not jump), so this is not `slide.selectSlides`.
fn select_all_slides(s: &mut Session) -> Result<Value> {
if s.doc()?.selection.target != crate::Target::Slides {
return Err(bad("edit.selectAll", "the slide list is not shown while editing a master"));
}
let ids: Vec<SlideId> = s.doc()?.doc.slides.iter().map(|x| x.id).collect();
s.select(|_, sel| {
sel.slides = ids.clone();
sel.shapes.clear();
sel.text = None;
sel.cells = None;
})?;
Ok(json!({"selected": ids}))
}
fn deselect(s: &mut Session, _p: &Value) -> Result<Value> {
s.painter = None;
s.anim_painter = None;
+40
View File
@@ -543,3 +543,43 @@ fn inserted_hyperlinks_look_like_links() {
s.execute("format.underline", &json!({})).unwrap();
assert_eq!(s.execute("format.state", &json!({})).unwrap()["underline"], false);
}
#[test]
fn select_all_slides_scope() {
let mut s = session();
// One slide: selects it, no panic.
let r = s.execute("edit.selectAll", &json!({"scope": "slides"})).unwrap();
assert_eq!(r["selected"].as_array().map(Vec::len), Some(1));
for _ in 0..3 {
s.execute("slide.new", &json!({})).unwrap();
}
s.execute("slide.hide", &json!({"index": 1})).unwrap();
s.execute("slide.go", &json!({"index": 1})).unwrap();
let before = s.doc().unwrap().selection.slide;
s.execute("edit.selectAll", &json!({"scope": "slides"})).unwrap();
let st = s.doc().unwrap();
let all: Vec<_> = st.doc.slides.iter().map(|x| x.id).collect();
assert_eq!(st.selection.slides, all);
assert_eq!(st.selection.slide, before, "the current slide must not jump");
assert!(st.selection.shapes.is_empty() && st.selection.text.is_none());
assert_eq!(st.doc.slides.len(), 4);
}
#[test]
fn select_all_slides_leaves_shape_select_all_alone() {
let mut s = session();
s.execute("shape.insert", &json!({"preset": "rect", "rect": [10, 10, 50, 50]})).unwrap();
s.execute("slide.new", &json!({})).unwrap();
s.execute("slide.go", &json!({"index": 0})).unwrap();
// No scope: still selects shapes on the current slide.
s.execute("edit.selectAll", &json!({})).unwrap();
assert!(!s.doc().unwrap().selection.shapes.is_empty());
assert!(s.doc().unwrap().selection.slides.len() <= 1);
}
#[test]
fn select_all_slides_bad_scope_is_an_error() {
let mut s = session();
assert!(s.execute("edit.selectAll", &json!({"scope": "everything"})).is_err());
assert!(s.execute("edit.selectAll", &json!({"scope": 3})).is_err());
}