mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-05 01:58:58 +00:00
#1442: Ctrl+. skips corners the sheet can't select and stops once on a merged corner
Addresses CodeRabbit review on #3226: - "Check the next corner against protected-sheet selection rules": on a sheet protected without Select Locked Cells, Ctrl+. could make a locked corner active. It now goes on to the next corner it may select. - "Keep the active cell at a merged cell's anchor": a corner inside a merged cell made that cell active instead of the merge's top-left cell. The corner now lands on the top-left cell, and corners in the same merged cell are one stop. Selection::next_corner_by takes where each corner leads (or None to skip it); next_corner keeps its plain behaviour. Tests: next_corner_skips_corners_the_sheet_protection_does_not_let_you_select, next_corner_stops_once_on_a_merged_corner_at_its_top_left_cell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8b2550c9cf
commit
5acb4af789
3 files changed
+48
-6
No files matched your search
@@ -139,7 +139,13 @@ pub(super) fn selection_next(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
|
||||
pub(super) fn selection_next_corner(s: &mut Session, _: &Json) -> Result<Json> {
|
||||
let d = s.doc_mut()?;
|
||||
d.selection.next_corner();
|
||||
let Some(sheet) = d.wb.active() else { return Err(EngineError::NoDocument) };
|
||||
// A corner in a merged cell lands on its top-left cell; one the sheet's protection doesn't
|
||||
// let you select is skipped.
|
||||
let stop = |c: CellRef| Some(sheet.merge_at(c).map_or(c, |m| m.start)).filter(|c| cell_selectable(&d.wb, sheet, *c));
|
||||
let mut next = d.selection.clone();
|
||||
next.next_corner_by(stop);
|
||||
d.selection = next;
|
||||
Ok(json!({"active": d.selection.active.a1(), "selection": d.selection.a1()}))
|
||||
}
|
||||
|
||||
|
||||
@@ -397,3 +397,30 @@ fn next_corner_of_selection_goes_clockwise_and_anchors_there() {
|
||||
s.execute("selection.set", json!({"range": "C5"})).unwrap();
|
||||
assert_eq!(s.execute("selection.nextCorner", json!({})).unwrap()["active"], "C5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_corner_skips_corners_the_sheet_protection_does_not_let_you_select() {
|
||||
// With locked cells unselectable, Ctrl+. goes on to the next corner it may select.
|
||||
let mut s = Session::new();
|
||||
s.new_workbook();
|
||||
s.execute("home.lockCell", json!({"range": "B2:C3", "on": false})).unwrap();
|
||||
s.execute("selection.set", json!({"range": "B2:D3", "active": "B2"})).unwrap();
|
||||
s.execute("review.protectSheet", json!({"selectLockedCells": false})).unwrap();
|
||||
let r = s.execute("selection.nextCorner", json!({})).unwrap();
|
||||
assert_eq!((r["active"].as_str(), r["selection"].as_str()), (Some("B3"), Some("B2:D3")), "D2 and D3 are locked");
|
||||
assert_eq!(s.execute("selection.nextCorner", json!({})).unwrap()["active"], "B2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_corner_stops_once_on_a_merged_corner_at_its_top_left_cell() {
|
||||
// A corner inside a merged cell is that merged cell, whose active cell is its top-left one.
|
||||
let mut s = Session::new();
|
||||
s.new_workbook();
|
||||
s.execute("home.mergeCells", json!({"range": "C2:D2"})).unwrap();
|
||||
s.execute("selection.set", json!({"range": "B2:D3", "active": "B2"})).unwrap();
|
||||
let seen: Vec<String> = (0..4).map(|_| s.execute("selection.nextCorner", json!({})).unwrap()["active"].as_str().unwrap().to_string()).collect();
|
||||
assert_eq!(seen, ["C2", "D3", "B3", "B2"]);
|
||||
// A selection that is one merged cell has no other corner.
|
||||
s.execute("selection.set", json!({"range": "C2:D2", "active": "C2"})).unwrap();
|
||||
assert_eq!(s.execute("selection.nextCorner", json!({})).unwrap()["active"], "C2");
|
||||
}
|
||||
@@ -134,14 +134,23 @@ impl Selection {
|
||||
/// so Shift+arrows then move the opposite corner. The area stays selected. From a cell that
|
||||
/// isn't a corner the next corner is the top-left one; a single cell stays where it is.
|
||||
pub fn next_corner(&mut self) {
|
||||
self.next_corner_by(Some);
|
||||
}
|
||||
|
||||
/// [`Selection::next_corner`] where `stop` says where each corner takes the active cell: the
|
||||
/// top-left cell of a merged cell the corner is in, or `None` for a corner that can't be
|
||||
/// selected (it is skipped). Corners that end up on the active cell are one stop with it.
|
||||
pub fn next_corner_by(&mut self, stop: impl Fn(CellRef) -> Option<CellRef>) {
|
||||
let r = self.current();
|
||||
let corners = [r.start, CellRef::new(r.start.row, r.end.col), r.end, CellRef::new(r.end.row, r.start.col)];
|
||||
let next = match corners.iter().position(|c| *c == self.active) {
|
||||
Some(i) => (1..=4).filter_map(|k| corners.get((i + k) % 4)).copied().find(|c| *c != self.active).unwrap_or(self.active),
|
||||
None => r.start,
|
||||
let next = match corners.iter().rposition(|c| stop(*c) == Some(self.active)) {
|
||||
Some(i) => (1..=4).filter_map(|k| corners.get((i + k) % 4)).filter_map(|c| stop(*c)).find(|c| *c != self.active),
|
||||
None => corners.iter().find_map(|c| stop(*c)),
|
||||
};
|
||||
self.active = next;
|
||||
self.anchor = next;
|
||||
if let Some(next) = next {
|
||||
self.active = next;
|
||||
self.anchor = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user