Slides: the range anchor is held by slide id, so moving, inserting or deleting slides can't shift it (#1105)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Faabyy
2026-10-25 13:47:18 +00:00
co-authored by Claude
parent abdf5f337b
commit 121441a079
2 changed files with 19 additions and 4 deletions
+16 -2
View File
@@ -386,7 +386,8 @@ fn select_range(s: &mut Session, p: &Value) -> Result<Value> {
let (from, count) = {
let st = s.doc()?;
let sel = &st.selection;
let anchor = sel.range_anchor.filter(|(_, end)| *end == sel.slide).map(|(a, _)| a);
let current = st.doc.slides.get(sel.slide).map(|x| x.id);
let anchor = sel.range_anchor.filter(|(_, end)| Some(*end) == current).and_then(|(a, _)| st.doc.slide_index(a));
(given.or(anchor).unwrap_or(sel.slide), st.doc.slides.len())
};
if to >= count || from >= count {
@@ -405,7 +406,7 @@ fn select_range(s: &mut Session, p: &Value) -> Result<Value> {
sel.slides = range;
}
sel.slide = to;
sel.range_anchor = Some((from, to));
sel.range_anchor = doc.slides.get(from).zip(doc.slides.get(to)).map(|(a, b)| (a.id, b.id));
sel.shapes.clear();
sel.text = None;
sel.cells = None;
@@ -815,6 +816,19 @@ mod tests {
assert_eq!(selected_indices(&s), vec![4, 5, 6]);
}
#[test]
fn moving_a_slide_drops_the_range_anchor() {
let mut s = deck_of(6);
s.execute("slide.go", &json!({"index": 0})).expect("go");
s.execute("slide.selectRange", &json!({"to": 2})).expect("range");
// Slide 0 moves to index 2 and stays current; index 0 now holds another slide, so the
// next Shift-click starts at the moved slide instead of at index 0.
s.execute("slide.move", &json!({"from": 0, "to": 2})).expect("move");
assert_eq!(s.doc().expect("doc").selection.slide, 2);
s.execute("slide.selectRange", &json!({"to": 4})).expect("range");
assert_eq!(selected_indices(&s), vec![2, 3, 4]);
}
#[test]
fn select_range_clears_a_table_cell_selection_and_returns_to_the_slides() {
let mut s = deck_of(3);
+3 -2
View File
@@ -156,9 +156,10 @@ pub struct Selection {
/// Selected table cells: (table, (r0, c0), (r1, c1)).
pub cells: Option<(ShapeId, (usize, usize), (usize, usize))>,
/// Slide-list range last made with Shift-click: `(anchor, end)`. A further Shift-click extends from
/// the anchor while the current slide is still `end`; moving to another slide drops it.
/// the anchor while the current slide is still `end`; moving to another slide drops it. Held as
/// slide ids, so moving, inserting or deleting slides can't make it name other slides.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub range_anchor: Option<(usize, usize)>,
pub range_anchor: Option<(SlideId, SlideId)>,
pub target: Target,
}