From 0137a5157afed480f1588b0ac98f8af672970afd Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Fri, 16 Oct 2026 04:15:02 +0200 Subject: [PATCH] Fix numbered list levels that start at 0 or are overridden to 1 Found while testing crates/doc/src/numbering.rs:319 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/doc/src/numbering.rs | 65 ++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/crates/doc/src/numbering.rs b/crates/doc/src/numbering.rs index ac04c0a026..2d41563d79 100644 --- a/crates/doc/src/numbering.rs +++ b/crates/doc/src/numbering.rs @@ -285,6 +285,16 @@ pub struct Counters { state: HashMap, /// Which nums have started (to apply start overrides once). seen: HashMap, + /// Per abstract list: whether each level has counted yet (a value of 0 can be a real count when a + /// level starts at 0). + flags: HashMap, +} + +/// Whether a level has counted yet, and the value its next item takes if a start override says so. +#[derive(Default, Debug, Clone, Copy)] +struct LevelFlags { + started: bool, + first: Option, } impl Counters { @@ -307,29 +317,31 @@ impl Counters { return None; } let st = self.state.entry(abs.id).or_insert([0; 9]); + let fl = self.flags.entry(abs.id).or_default(); if let std::collections::hash_map::Entry::Vacant(e) = self.seen.entry(num) { e.insert(true); for (l, start) in &n.start_overrides { - if let Some(s) = st.get_mut(*l as usize) { - *s = start.saturating_sub(1); + if let (Some(s), Some(f)) = (st.get_mut(*l as usize), fl.get_mut(*l as usize)) { + *s = 0; + *f = LevelFlags { started: false, first: Some(*start) }; } } } - if let Some(v) = st.get_mut(lv) { - *v = if *v == 0 { def.start.max(if def.start == 0 { 0 } else { 1 }) } else { v.saturating_add(1) }; - if *v == 0 { - *v = def.start; - } + if let (Some(v), Some(f)) = (st.get_mut(lv), fl.get_mut(lv)) { + *v = if f.started { v.saturating_add(1) } else { f.first.unwrap_or(def.start) }; + *f = LevelFlags { started: true, first: None }; } // Deeper levels restart, unless they never restart or only restart after a level above // this one (Word's lvlRestart). - for (d, deeper) in st.iter_mut().enumerate().skip(lv + 1) { + for (d, (deeper, f)) in st.iter_mut().zip(fl.iter_mut()).enumerate().skip(lv + 1) { let restarts = level_def(d).is_none_or(|l| l.restart && l.restart_after.is_none_or(|k| lv < k as usize)); if restarts { *deeper = 0; + *f = LevelFlags::default(); } } let vals = *st; + let started = *fl; if def.format == NumFormat::Bullet { return Some((def.text.clone(), def)); } @@ -342,7 +354,8 @@ impl Counters { chars.next(); let k = (d as usize).saturating_sub(1).min(8); let val = vals.get(k).copied().unwrap_or(0); - let val = if val == 0 { level_def(k).map(|l| l.start).unwrap_or(1) } else { val }; + let counted = started.get(k).is_some_and(|f| f.started); + let val = if counted { val } else { level_def(k).map(|l| l.start).unwrap_or(1) }; // Legal numbering (w:isLgl) shows every level in Arabic digits, whatever its own // format (二 of a Chinese outline becomes 2 in "2.1"). let fmt = match level_def(k).map(|l| l.format).unwrap_or(NumFormat::Decimal) { @@ -406,6 +419,40 @@ mod tests { assert_eq!(l(2), "(1)", "level 1 restarts level 3"); } + #[test] + fn a_level_starting_at_zero_counts_up() { + let mut n = Numbering::default(); + let id = n.add_list(ListKind::Numbered); + if let Some(a) = n.abstracts.first_mut() { + a.levels[0].start = 0; + } + let mut c = Counters::default(); + let labels: Vec = (0..4).map(|_| c.next_label(&n, id, 0).unwrap().0).collect(); + assert_eq!(labels, ["0.", "1.", "2.", "3."]); + } + + #[test] + fn a_start_override_of_one_or_zero_is_honoured() { + let mut n = Numbering::default(); + let id = n.add_list(ListKind::Numbered); + if let Some(a) = n.abstracts.first_mut() { + a.levels[0].start = 5; + } + if let Some(num) = n.nums.iter_mut().find(|x| x.id == id) { + num.start_overrides = vec![(0, 1)]; + } + let mut c = Counters::default(); + let labels: Vec = (0..2).map(|_| c.next_label(&n, id, 0).unwrap().0).collect(); + assert_eq!(labels, ["1.", "2."]); + + if let Some(num) = n.nums.iter_mut().find(|x| x.id == id) { + num.start_overrides = vec![(0, 0)]; + } + let mut c = Counters::default(); + let labels: Vec = (0..3).map(|_| c.next_label(&n, id, 0).unwrap().0).collect(); + assert_eq!(labels, ["0.", "1.", "2."]); + } + #[test] fn numbered_labels() { let mut n = Numbering::default();