From e182c29d7090f12d90d457e464bc932783af55a1 Mon Sep 17 00:00:00 2001 From: Kevin Boos Date: Fri, 11 Sep 2026 10:53:22 -0700 Subject: [PATCH] speech: don't assume byte ranges are always at a char boundary --- crates/speech/src/dictation.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/crates/speech/src/dictation.rs b/crates/speech/src/dictation.rs index 0918fdb..5f41ce6 100644 --- a/crates/speech/src/dictation.rs +++ b/crates/speech/src/dictation.rs @@ -156,11 +156,12 @@ impl Dictation { return text == self.draft && *selection == (self.start..self.end); }; let caret = self.start + shown.len(); + // `get`, not indexing: after an edit, our offsets may fall inside one of the field's characters. *selection == (caret..caret) && text.len() == self.draft.len() - (self.end - self.start) + shown.len() - && text[..self.start] == self.draft[..self.start] - && text[self.start..caret] == **shown - && text[caret..] == self.draft[self.end..] + && text.get(..self.start) == Some(&self.draft[..self.start]) + && text.get(self.start..caret) == Some(shown.as_str()) + && text.get(caret..) == Some(&self.draft[self.end..]) } /// Everything said since the anchor, spaced off the draft around it. @@ -539,6 +540,22 @@ mod tests { assert_eq!(field.text, "firstX second one"); } + #[test] + fn an_edit_that_splits_a_character_at_the_anchor_is_still_an_edit() { + // Same length and caret as what we wrote, but the anchor now falls inside the `é`. + let mut dictation = Dictation::new("abc", 1..1); + let mut field = Field::new("abc", 1); + field.say(&mut dictation, "X", false); + assert_eq!((field.text.as_str(), field.caret), ("a X bc", 4)); + dictation.interrupt(); + field.text = "éX bc".into(); + field.settle(&mut dictation); + assert!(dictation.is_interrupted()); + field.say(&mut dictation, "X", true); + field.settle(&mut dictation); + assert_eq!(field.text, "éX bc"); + } + #[test] fn words_beyond_only_returns_what_was_not_already_shown() { assert_eq!(words_beyond("hello world", "hello world and more"), "and more");