Compare commits

..
Author SHA1 Message Date
Dylan M. Taylor e919382ac7 Extract the SCC pop step from components() to reduce cognitive complexity (S3776)
Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
2026-10-26 09:12:36 -04:00
Dylan M. Taylor a6319904d1 Restore the loop count that loops_among's refactor dropped
The S3776 split moved the loop-detection body into loops_among without
the 'let n = circ.len()' it reads, so calc failed to compile and the
Sonar build broke. Restore the binding from main.
2026-10-26 08:39:58 -04:00
Dylan M. Taylor 66ed962ad1 Merge branch 'main' into fix/sonar-complexity-batch-2b 2026-10-25 22:47:25 -04:00
Dylan M. Taylor 42bbf6baa2 refactor: split complex functions for Sonar S3776 (#2612)
Extract group_bracket from outline layout and move_marks from data
outline_settings. No behaviour change.
2026-10-25 21:39:17 -04:00
Dylan M. Taylor 52cf825395 refactor: split complex functions for Sonar S3776 (#2609)
Extract helpers from auto_sum (selection and single-cell guess), update_cursor
(drag, hover, freeze, and header-edge cursors) and read_styles (dxf reader).
No behaviour change.
2026-10-25 21:37:09 -04:00
Dylan M. Taylor bc881f6c4f refactor: split complex functions for Sonar S3776
Extract helpers from backstage options (general, calculation and iteration
sections), code_editor python (string scanning), xtask i18n literals
(escape and string readers) and calc recalc loops_among (SCC search).
No behaviour change.
2026-10-25 21:31:04 -04:00
3 changed files with 73 additions and 56 deletions
+14 -7
View File
@@ -927,6 +927,18 @@ impl Host for PassHost<'_> {
} }
} }
/// Pops one finished component off Tarjan's stack, labelling its nodes with `comp_index`. The
/// component is everything up to and including `root`.
fn pop_component(stack: &mut Vec<usize>, on_stack: &mut [bool], comp: &mut [usize], comp_index: usize, root: usize) {
while let Some(w) = stack.pop() {
on_stack[w] = false;
comp[w] = comp_index;
if w == root {
break;
}
}
}
/// Strongly connected components of a graph given as adjacency lists (Tarjan, iterative): each /// Strongly connected components of a graph given as adjacency lists (Tarjan, iterative): each
/// node's component index, and the number of nodes in each component. /// node's component index, and the number of nodes in each component.
fn components(edges: &[Vec<usize>]) -> (Vec<usize>, Vec<usize>) { fn components(edges: &[Vec<usize>]) -> (Vec<usize>, Vec<usize>) {
@@ -965,13 +977,7 @@ fn components(edges: &[Vec<usize>]) -> (Vec<usize>, Vec<usize>) {
low[u] = low[u].min(low[v]); low[u] = low[u].min(low[v]);
} }
if low[v] == num[v] { if low[v] == num[v] {
while let Some(w) = stack.pop() { pop_component(&mut stack, &mut on_stack, &mut comp, comps, v);
on_stack[w] = false;
comp[w] = comps;
if w == v {
break;
}
}
comps += 1; comps += 1;
} }
} }
@@ -1025,6 +1031,7 @@ impl Calc {
if circ.is_empty() { if circ.is_empty() {
return Vec::new(); return Vec::new();
} }
let n = circ.len();
let index: HashMap<Key, usize> = circ.iter().enumerate().map(|(i, k)| (*k, i)).collect(); let index: HashMap<Key, usize> = circ.iter().enumerate().map(|(i, k)| (*k, i)).collect();
// Edges from a cell to the `#CIRC!` formulas that read it; loops are the same either way. // Edges from a cell to the `#CIRC!` formulas that read it; loops are the same either way.
let mut deps = Vec::new(); let mut deps = Vec::new();
+29 -25
View File
@@ -1454,6 +1454,34 @@ fn show_levels(s: &mut Session, p: &Json) -> Result<Json> {
/// Data ▸ Outline ▸ Settings: whether summary rows sit below their detail and summary columns /// Data ▸ Outline ▸ Settings: whether summary rows sit below their detail and summary columns
/// right of it. A changed direction moves each collapsed group's mark (+) to its new summary line. /// right of it. A changed direction moves each collapsed group's mark (+) to its new summary line.
/// Moves the collapsed marks of a line axis to the summary line on its new side (`on` is the new
/// setting, `was_before` the old one). A line that nested groups share keeps its mark on the outermost
/// group whose detail is all hidden, or the outermost when none is.
fn move_marks(map: &mut std::collections::BTreeMap<u32, LineInfo>, was_before: bool, on: bool) {
let mut marked: Vec<outline::OutlineGroup> =
outline::groups_with(map, was_before).into_iter().filter(|g| g.has_summary() && map.get(&g.summary()).is_some_and(|e| e.collapsed)).collect();
// A mark on a line that nested groups share moves with the outermost of them whose
// detail is all hidden, or with the outermost when none is.
let mut owner: std::collections::BTreeMap<u32, (bool, u8)> = std::collections::BTreeMap::new();
for g in &marked {
let key = (!g.collapsed, g.level);
let best = owner.entry(g.summary()).or_insert(key);
*best = (*best).min(key);
}
marked.retain(|g| owner.get(&g.summary()) == Some(&(!g.collapsed, g.level)));
for g in &marked {
if let Some(e) = map.get_mut(&g.summary()) {
e.collapsed = false;
}
}
for g in marked {
let g = outline::OutlineGroup { summary_before: !on, ..g };
if g.has_summary() {
map.entry(g.summary()).or_default().collapsed = true;
}
}
}
fn outline_settings(s: &mut Session, p: &Json) -> Result<Json> { fn outline_settings(s: &mut Session, p: &Json) -> Result<Json> {
let si = target_sheet(s, p)?; let si = target_sheet(s, p)?;
edit(s, |cx| { edit(s, |cx| {
@@ -1469,31 +1497,7 @@ fn outline_settings(s: &mut Session, p: &Json) -> Result<Json> {
if was_before != on { if was_before != on {
continue; continue;
} }
let map = if rows { &mut sh.rows } else { &mut sh.cols }; move_marks(if rows { &mut sh.rows } else { &mut sh.cols }, was_before, on);
let mut marked: Vec<outline::OutlineGroup> = outline::groups_with(map, was_before)
.into_iter()
.filter(|g| g.has_summary() && map.get(&g.summary()).is_some_and(|e| e.collapsed))
.collect();
// A mark on a line that nested groups share moves with the outermost of them whose
// detail is all hidden, or with the outermost when none is.
let mut owner: std::collections::BTreeMap<u32, (bool, u8)> = std::collections::BTreeMap::new();
for g in &marked {
let key = (!g.collapsed, g.level);
let best = owner.entry(g.summary()).or_insert(key);
*best = (*best).min(key);
}
marked.retain(|g| owner.get(&g.summary()) == Some(&(!g.collapsed, g.level)));
for g in &marked {
if let Some(e) = map.get_mut(&g.summary()) {
e.collapsed = false;
}
}
for g in marked {
let g = outline::OutlineGroup { summary_before: !on, ..g };
if g.has_summary() {
map.entry(g.summary()).or_default().collapsed = true;
}
}
} }
Ok(json!({"summaryBelow": sh.summary_below, "summaryRight": sh.summary_right})) Ok(json!({"summaryBelow": sh.summary_below, "summaryRight": sh.summary_right}))
}) })
+30 -24
View File
@@ -165,6 +165,35 @@ impl Dir<'_> {
} }
/// The band's buttons and brackets for this frame. /// The band's buttons and brackets for this frame.
/// The bracket for one group's detail, and its tick, on the lane `across`; `button` is the group's
/// summary button when it is on screen.
fn group_bracket(out: &mut Layout, dir: &Dir, grp: &OutlineGroup, button: Option<f32>, across: f32, side: f32) {
let first = dir.first_visible(grp.start, grp.end);
let Some((first_line, (a0, _))) = first else { return };
if grp.summary_before {
// From the button down (or across) to the last detail line, with the tick there.
let a0 = button.map_or(a0, |mid| mid + side / 2.0);
let end = dir.start(grp.end) + dir.size(grp.end);
let a1 = end.min(dir.hi);
if a1 <= a0 {
return;
}
let tick = (end <= dir.hi).then(|| [dir.point(a1 - 0.5, across), dir.point(a1 - 0.5, across + side / 2.0)]);
out.brackets.push(Bracket { line: [dir.point(a0, across), dir.point(a1 - 0.5, across)], tick });
return;
}
let a1 = match button {
Some(mid) => mid - side / 2.0,
None => dir.start(grp.summary()).min(dir.hi),
};
if a1 <= a0 {
return;
}
let start_shown = first_line == grp.start && (a0 - dir.start(grp.start)).abs() < 0.5;
let tick = start_shown.then(|| [dir.point(a0 + 0.5, across), dir.point(a0 + 0.5, across + side / 2.0)]);
out.brackets.push(Bracket { line: [dir.point(a0 + 0.5, across), dir.point(a1, across)], tick });
}
pub fn layout(geo: &Geo, sh: &Sheet) -> Layout { pub fn layout(geo: &Geo, sh: &Sheet) -> Layout {
let mut out = Layout::default(); let mut out = Layout::default();
let zoom = (geo.z / DISPLAY_SCALE).max(0.1); let zoom = (geo.z / DISPLAY_SCALE).max(0.1);
@@ -212,30 +241,7 @@ pub fn layout(geo: &Geo, sh: &Sheet) -> Layout {
continue; continue;
} }
// The bracket, over the part of the detail that is on screen. // The bracket, over the part of the detail that is on screen.
let first = dir.first_visible(grp.start, grp.end); group_bracket(&mut out, dir, grp, button, across, side);
let Some((first_line, (a0, _))) = first else { continue };
if grp.summary_before {
// From the button down (or across) to the last detail line, with the tick there.
let a0 = button.map_or(a0, |mid| mid + side / 2.0);
let end = dir.start(grp.end) + dir.size(grp.end);
let a1 = end.min(dir.hi);
if a1 <= a0 {
continue;
}
let tick = (end <= dir.hi).then(|| [dir.point(a1 - 0.5, across), dir.point(a1 - 0.5, across + side / 2.0)]);
out.brackets.push(Bracket { line: [dir.point(a0, across), dir.point(a1 - 0.5, across)], tick });
continue;
}
let a1 = match button {
Some(mid) => mid - side / 2.0,
None => dir.start(grp.summary()).min(dir.hi),
};
if a1 <= a0 {
continue;
}
let start_shown = first_line == grp.start && (a0 - dir.start(grp.start)).abs() < 0.5;
let tick = start_shown.then(|| [dir.point(a0 + 0.5, across), dir.point(a0 + 0.5, across + side / 2.0)]);
out.brackets.push(Bracket { line: [dir.point(a0 + 0.5, across), dir.point(a1, across)], tick });
} }
} }
out out