From dda47d482d074f889d68789ccf310a84d0dd9591 Mon Sep 17 00:00:00 2001 From: ripdog Date: Mon, 12 Oct 2026 22:29:57 +1300 Subject: [PATCH] M7: ignore ellipses in sentence capitalization proofing (storytold/wordcraft#742) Co-authored-by: Brandon Thomas Co-authored-by: Claude Opus 5.5 --- crates/proof/src/lib.rs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/crates/proof/src/lib.rs b/crates/proof/src/lib.rs index 925ab7ec..71a0e4cb 100644 --- a/crates/proof/src/lib.rs +++ b/crates/proof/src/lib.rs @@ -539,8 +539,12 @@ pub fn check_grammar(text: &str) -> Vec { }); } let tail = text.get(*b..).unwrap_or(""); - let next_non_space = tail.trim_start_matches(['"', '”', ')', '\'']).chars().next(); - after_end = matches!(next_non_space, Some('.' | '!' | '?')) && tail.trim_start_matches(['.', '!', '?', '"', '”', ')']).starts_with(' '); + let punctuation = tail.trim_start_matches(['"', '”', ')', '\'']); + // Two or more dots are an ellipsis, not a full stop ending a sentence. + // A Unicode ellipsis (U+2026) does not match sentence-ending punctuation either. + after_end = matches!(punctuation.chars().next(), Some('.' | '!' | '?')) + && !punctuation.starts_with("..") + && punctuation.trim_start_matches(['.', '!', '?', '"', '”', ')']).starts_with(' '); } v.sort_by_key(|i| i.start); v @@ -655,6 +659,28 @@ mod tests { assert!(check_grammar("A university is an hour away.").is_empty(), "{:?}", check_grammar("A university is an hour away.")); } + #[test] + fn ellipses_do_not_start_sentences() { + for text in [ + "I saw... a figure right outside.", + "I saw… a figure right outside.", + "I saw.... a figure right outside.", + "I saw... \"a figure\" right outside.", + "I saw… “a figure” right outside.", + ] { + let capitals: Vec<_> = + check_grammar(text).into_iter().filter(|issue| issue.message == "Capitalize the first word of a sentence").collect(); + assert!(capitals.is_empty(), "{text}: {capitals:?}"); + } + + for text in ["I saw. a figure.", "I saw! a figure.", "I saw? a figure.", "I saw.\" a figure."] { + let capitals: Vec<_> = + check_grammar(text).into_iter().filter(|issue| issue.message == "Capitalize the first word of a sentence").collect(); + assert_eq!(capitals.len(), 1, "{text}: {capitals:?}"); + assert_eq!(capitals[0].suggestions, ["A"], "{text}"); + } + } + #[test] fn user_dictionary_works() { assert!(!is_correct("zxqwordcrafty"));