From 482fd96ff8df9bdc1bd56bc221fc9b6623b0e924 Mon Sep 17 00:00:00 2001 From: Nathaniel Ledford Date: Wed, 21 Oct 2026 12:53:15 -0400 Subject: [PATCH] LRC-LIB-SMARTCOLL: an arrow key on a row that isn't there finds no place Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/panels/rules_move.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/ui-egui/src/panels/rules_move.rs b/crates/ui-egui/src/panels/rules_move.rs index 00dc7af59..de5ccc2ef 100644 --- a/crates/ui-egui/src/panels/rules_move.rs +++ b/crates/ui-egui/src/panels/rules_move.rs @@ -171,6 +171,8 @@ pub fn move_node(rs: &mut RuleSet, from: &[usize], dest: &[usize]) -> Option Option> { let (&i, parent) = from.split_last()?; let list = list_at(rs, parent)?; + // a row that isn't there has no place to go (and `i` is then below `len`, so `i + 1` can't overflow) + list.rules.get(i)?; let at = |n: usize| parent.iter().copied().chain([n]).collect::>(); match dir { Dir::Up if i > 0 => Some(at(i - 1)), @@ -338,6 +340,18 @@ mod tests { } } + /// Given a path whose last step is past the end of its list (`usize::MAX` too), when an arrow + /// key asks where the row goes, then there is no place, and no index arithmetic overflows. + #[test] + fn arrow_keys_on_a_path_that_leads_nowhere_find_no_place() { + let r = set(vec![rule(1), group(vec![rule(2)])]); + for from in [vec![2], vec![5], vec![usize::MAX], vec![1, 1], vec![1, usize::MAX]] { + for dir in [Dir::Up, Dir::Down, Dir::Out, Dir::In] { + assert_eq!(key_dest(&r, &from, dir), None, "{from:?} {dir:?}"); + } + } + } + #[test] fn arrow_keys_move_a_row_one_place() { let list = || vec![rule(1), group(vec![rule(2), rule(3)]), rule(4)];