diff --git a/crates/engine/src/spell.rs b/crates/engine/src/spell.rs index 8927ade5..2dda5c6f 100644 --- a/crates/engine/src/spell.rs +++ b/crates/engine/src/spell.rs @@ -726,8 +726,14 @@ pub fn paragraph_words(p: &deckcraft_model::Paragraph, default: &str) -> Vec a && !chars[b - 1].0.is_alphanumeric() && !deckcraft_model::words::is_combining_mark(chars[b - 1].0) { - b -= 1; + // Trim back to the last letter or digit, keeping only the marks that sit on it + // (a mark on a trailing apostrophe goes with the apostrophe). + while b > a { + let k = (a..b).rev().find(|&k| !deckcraft_model::words::is_combining_mark(chars[k].0)).unwrap_or(a); + if chars[k].0.is_alphanumeric() { + break; + } + b = k; } let text: String = chars[a..b].iter().map(|c| c.0).collect(); if text.is_empty() || text.chars().any(|c| c.is_ascii_digit()) || !text.chars().any(char::is_alphabetic) { @@ -827,6 +833,13 @@ SFX D Y 1\nSFX D 0 ed/S .\n"; assert_eq!(w, ["Das", "na\u{ef}ve", "Caf\u{e9}", "und", "cafe\u{301}", "na\u{308}ive"]); } + #[test] + fn a_mark_on_trailing_punctuation_is_trimmed_with_it() { + let p = deckcraft_model::Paragraph::new("word'\u{301} cafe\u{301}' x"); + let w: Vec = paragraph_words(&p, "en-US").into_iter().map(|w| w.text).collect(); + assert_eq!(w, ["word", "cafe\u{301}", "x"]); + } + #[test] fn curly_apostrophe_matches_ascii_entry() { let d = Dictionary::parse(AFF.as_bytes(), "2\ndon't\nwork\n".as_bytes());