From 3fb6c4313657a2f755ab9bc99abd3cfff8a1ec0b Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Sun, 1 Nov 2026 00:08:51 +0100 Subject: [PATCH] =?UTF-8?q?Lowercase=20word-final=20=CE=A3=20as=20=CF=82?= =?UTF-8?q?=20in=20title=20and=20sentence=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found while testing crates/model/src/edit.rs:470-497 (change_case_parts, title/sentence branch) Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/model/src/edit.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/crates/model/src/edit.rs b/crates/model/src/edit.rs index d635c052..3e21e3bf 100644 --- a/crates/model/src/edit.rs +++ b/crates/model/src/edit.rs @@ -469,6 +469,9 @@ pub fn change_case(s: &str, mode: &str) -> String { /// piece is the changed form of the matching input piece, even where a character changes length. pub fn change_case_parts(parts: &[&str], mode: &str) -> Vec { let mut start = true; + // All characters of all pieces, so word-final sigma sees neighbours across piece boundaries. + let all: Vec = parts.iter().flat_map(|s| s.chars()).collect(); + let mut base = 0; parts .iter() .map(|s| match mode { @@ -479,10 +482,14 @@ pub fn change_case_parts(parts: &[&str], mode: &str) -> Vec { // `title` restarts after whitespace, sentence case after sentence punctuation. let title = mode == "title"; let mut out = String::new(); - for c in s.chars() { + for (j, c) in s.chars().enumerate() { + let i = base + j; if start && c.is_alphabetic() { out.extend(c.to_uppercase()); start = false; + } else if c == 'Σ' && i > 0 && all[i - 1].is_alphabetic() && !all.get(i + 1).is_some_and(|n| n.is_alphabetic()) { + // Word-final capital sigma lowercases to ς, as `str::to_lowercase` does. + out.push('ς'); } else { out.extend(c.to_lowercase()); } @@ -490,6 +497,7 @@ pub fn change_case_parts(parts: &[&str], mode: &str) -> Vec { start = true; } } + base += s.chars().count(); out } }) @@ -504,6 +512,20 @@ mod tests { TextBody::from_text(t) } + #[test] + fn title_and_sentence_final_sigma() { + assert_eq!(change_case("ΟΔΟΣ", "title"), "Οδος"); + assert_eq!(change_case("ΟΔΟΣ ΜΟΥ.", "sentence"), "Οδος μου."); + assert_eq!(change_case("ΟΔΟΣ", "lower"), "οδος"); + } + + #[test] + fn title_sigma_across_runs_is_not_final() { + assert_eq!(change_case_parts(&["ΟΔΟΣ", "ΑΣ"], "title"), ["Οδοσ", "ας"]); + assert_eq!(change_case_parts(&["ΟΔΟ", "ΣΑ"], "title"), ["Οδο", "σα"]); + assert_eq!(change_case_parts(&["ΟΔΟΣ", " ΜΟΥ"], "title"), ["Οδος", " Μου"]); + } + #[test] fn insert_and_split() { let mut b = body("Hello");