From e9ffc71c6aed505ad27f0fe241c7433bf7a71f54 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Mon, 19 Oct 2026 12:27:46 +0000 Subject: [PATCH] Search from the end for negative TEXTBEFORE/TEXTAFTER instances Found while testing crates/functions/src/text.rs:511-552 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Opus 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/functions/src/text.rs | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/crates/functions/src/text.rs b/crates/functions/src/text.rs index 91962d6e7..621c9b53b 100644 --- a/crates/functions/src/text.rs +++ b/crates/functions/src/text.rs @@ -505,16 +505,33 @@ fn delimiters(v: &Value) -> R>> { as_array(v).iter().map(|d| d.to_text().map(|s| chars(&s))).collect() } -/// Non-overlapping delimiter matches (start, end) in char positions, scanning left to right and -/// preferring the longest delimiter at each position. Empty delimiters are ignored unless all +/// Non-overlapping delimiter matches (start, end) in char positions, in ascending order. Scans left +/// to right preferring the longest delimiter starting at each position, or (`from_end`) right to +/// left preferring the longest delimiter ending at each position. Empty delimiters are ignored unless all /// delimiters are empty, in which case every position matches with zero length. -fn find_matches(text: &[char], delims: &[Vec], insensitive: bool) -> Vec<(usize, usize)> { +fn find_matches(text: &[char], delims: &[Vec], insensitive: bool, from_end: bool) -> Vec<(usize, usize)> { let t = if insensitive { fold(text) } else { text.to_vec() }; let ds: Vec> = delims.iter().filter(|d| !d.is_empty()).map(|d| if insensitive { fold(d) } else { d.clone() }).collect(); if ds.is_empty() { return (0..=t.len()).map(|i| (i, i)).collect(); } let mut out = Vec::new(); + if from_end { + let mut j = t.len(); + while j > 0 { + let best = + ds.iter().filter(|d| j.checked_sub(d.len()).and_then(|st| t.get(st..j)).is_some_and(|w| w == d.as_slice())).map(|d| d.len()).max(); + match best { + Some(l) => { + out.push((j - l, j)); + j -= l; + } + None => j -= 1, + } + } + out.reverse(); + return out; + } let mut i = 0; while i < t.len() { let best = ds.iter().filter(|d| t.get(i..i + d.len()).is_some_and(|w| w == d.as_slice())).map(|d| d.len()).max(); @@ -538,7 +555,7 @@ fn text_before_after(a: &[Arg], before: bool) -> R { if !(0..=1).contains(&mode) || inst == 0 || inst.unsigned_abs() as usize > s.len().max(1) { return Err(CellError::Value); } - let mut m = find_matches(&s, &delims, mode == 1); + let mut m = find_matches(&s, &delims, mode == 1, inst < 0); if match_end { if inst > 0 { if m.last().is_none_or(|x| x.0 != s.len()) { @@ -573,7 +590,7 @@ fn split_by(s: &[char], delims: &[Vec], insensitive: bool) -> Vec