diff --git a/crates/engine/src/cmd/edit.rs b/crates/engine/src/cmd/edit.rs index 30437b15..19225abb 100644 --- a/crates/engine/src/cmd/edit.rs +++ b/crates/engine/src/cmd/edit.rs @@ -18,7 +18,7 @@ pub fn specs() -> Vec { 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 { +fn select_all(s: &mut Session, p: &Value) -> Result { + 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 { 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 { + 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 = 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 { s.painter = None; s.anim_painter = None; diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 9aefd0b7..0fa8357d 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -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()); +}