C14.13 r1: query and line fold case the same way; final sigma folds to sigma

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-12 09:42:16 -04:00
1 parent cd0c740ded
commit b80bf48ccd
3 files changed
+27 -8

No files matched your search

+23 -7
View File
@@ -54,22 +54,27 @@ pub struct Hit {
pub text: String,
}
/// Where lowercase `query` starts in `line`, in characters.
/// `ch` without case; final sigma folds as Unicode does.
fn fold(ch: char) -> impl Iterator<Item = char> {
ch.to_lowercase().map(|low| if low == 'ς' { 'σ' } else { low })
}
/// Where folded `query` starts in `line`, in characters.
fn column_of(line: &str, query: &str) -> Option<usize> {
// Each lowered byte keeps its character's place.
let (mut lowered, mut places) = (String::new(), Vec::new());
// Each folded byte keeps its character's place.
let (mut folded, mut places) = (String::new(), Vec::new());
for (place, ch) in line.chars().enumerate() {
for low in ch.to_lowercase() {
lowered.push(low);
for low in fold(ch) {
folded.push(low);
places.extend(std::iter::repeat_n(place, low.len_utf8()));
}
}
lowered.find(query).map(|at| places[at])
folded.find(query).map(|at| places[at])
}
/// Lines of `text` holding `query`, any case.
pub fn lines_with(file: &Path, text: &str, query: &str, hits: &mut Vec<Hit>) {
let query = query.to_lowercase();
let query: String = query.chars().flat_map(fold).collect();
for (at, line) in text.lines().enumerate() {
if hits.len() == MOST_HITS {
return;
@@ -136,4 +141,15 @@ mod tests {
assert_eq!(super::column_of("中文 搜索", "搜索"), Some(3));
assert_eq!(super::column_of("none", "word"), None);
}
#[test]
fn sigma_matches_in_every_form() {
for line in ["ΟΣ", "οσ", "ος"] {
for query in ["ΟΣ", "οσ", "ος"] {
let mut hits = Vec::new();
super::lines_with(Path::new("a.md"), line, query, &mut hits);
assert_eq!(hits.len(), 1, "{query} in {line}");
}
}
}
}