Fix spacing bug: stop retyping finished word on flush char
The flush-char handling backspaced and re-typed the already-on-screen word before/around the forwarded space. In the grabbed-device injection path this raced against the separately-forwarded space, eating spaces and merging finished words (e.g. "mất sự" -> "mấtsự", "đầu ngã xuống" -> "đầungãxuống"). The composed word is already correct on screen, so a non-macro flush now finalizes state without backspace+retype: - engine: process_key returns None on flush (macros still Replace) - daemon replay_and_inject: just types the flush char - daemon did_flush branch: clears state without retyping Add regression tests for flush behavior and multi-word spacing. Co-Authored-By: vndangkhoa <vonguyendangkhoa@gmail.com>
This commit is contained in:
parent
0770cc59cc
commit
bbd273bdd6
3 changed files with 60 additions and 22 deletions
|
|
@ -282,14 +282,11 @@ impl Daemon {
|
|||
fn replay_and_inject(&mut self, ch: char) -> Vec<OutputCommand> {
|
||||
let mut commands = Vec::new();
|
||||
|
||||
// Flush characters: commit current word, type the character, clear state
|
||||
// Flush characters: commit current word, type the character, clear state.
|
||||
// The composed word is already correctly on screen, so we must NOT
|
||||
// backspace and retype it — doing so eats the spacing and shifts the
|
||||
// finished word left. Just type the flush char and clear state.
|
||||
if is_flush_char(ch) {
|
||||
if !self.screen_output.is_empty() {
|
||||
let backspaces = self.screen_output.chars().count();
|
||||
commands.push(OutputCommand::Backspace(backspaces));
|
||||
commands.push(OutputCommand::Type(self.screen_output.clone()));
|
||||
}
|
||||
// Type the flush character itself
|
||||
commands.push(OutputCommand::Type(ch.to_string()));
|
||||
self.keystroke_history.clear();
|
||||
self.screen_output.clear();
|
||||
|
|
@ -311,14 +308,9 @@ impl Daemon {
|
|||
);
|
||||
|
||||
if did_flush {
|
||||
// Engine flushed a word — commit it and clear state
|
||||
// The flush char (space/period/etc) was NOT in history, so we need to
|
||||
// type whatever was on screen + the flush char
|
||||
if !self.screen_output.is_empty() {
|
||||
let backspaces = self.screen_output.chars().count();
|
||||
commands.push(OutputCommand::Backspace(backspaces));
|
||||
commands.push(OutputCommand::Type(self.screen_output.clone()));
|
||||
}
|
||||
// Engine flushed a word — it is already correctly on screen, so
|
||||
// just clear state without backspacing/retyping it (retyping eats
|
||||
// spacing and shifts the finished word left).
|
||||
self.keystroke_history.clear();
|
||||
self.screen_output.clear();
|
||||
return commands;
|
||||
|
|
|
|||
|
|
@ -172,13 +172,11 @@ impl Engine {
|
|||
}
|
||||
|
||||
self.reset();
|
||||
if prev_len > 0 {
|
||||
// Don't include flush char in insert — daemon forwards it separately
|
||||
return Some(EngineEvent::Replace {
|
||||
backspaces: prev_len,
|
||||
insert: previous,
|
||||
});
|
||||
}
|
||||
// The composed word is already correctly on screen — re-typing it
|
||||
// here would trigger a redundant backspace + clipboard-paste cycle
|
||||
// that races against the separately-forwarded flush char, eating
|
||||
// spaces and merging words. Just finalize and let the flush char
|
||||
// through untouched.
|
||||
return None;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -455,4 +455,52 @@ mod tests {
|
|||
e.set_method(InputMethod::Vni);
|
||||
assert_eq!(get_display(&process_input(&mut e, "a1")), "á");
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Spacing / flush behavior (regression)
|
||||
// ================================================================
|
||||
|
||||
// A space after a finished word must NOT re-emit the word as a Replace
|
||||
// (backspace + retype). Re-typing the already-on-screen word races with
|
||||
// the separately-forwarded space in the daemon, eating spaces and merging
|
||||
// words (e.g. "mất sự" -> "mấtsự"). The flush should produce no engine
|
||||
// event so the space simply passes through.
|
||||
#[test]
|
||||
fn flush_after_word_emits_no_replace() {
|
||||
let mut e = Engine::new(InputMethod::Telex);
|
||||
// Compose "chào".
|
||||
for ch in "chaof".chars() {
|
||||
e.process_key(ch);
|
||||
}
|
||||
// Space finalizes the word — engine must return None.
|
||||
assert_eq!(e.process_key(' '), None);
|
||||
}
|
||||
|
||||
// Punctuation flush chars behave the same as space.
|
||||
#[test]
|
||||
fn flush_punctuation_emits_no_replace() {
|
||||
let mut e = Engine::new(InputMethod::Telex);
|
||||
for ch in "chaof".chars() {
|
||||
e.process_key(ch);
|
||||
}
|
||||
assert_eq!(e.process_key('.'), None);
|
||||
}
|
||||
|
||||
// Full multi-word sentence keeps every space and never concatenates words.
|
||||
#[test]
|
||||
fn multi_word_keeps_spacing() {
|
||||
let mut e = Engine::new(InputMethod::Telex);
|
||||
// "toio is" with telex: "tooi" -> "tôi"; "ddi" -> "đi"
|
||||
let events = process_input(&mut e, "tooi ddi hocj ");
|
||||
assert_eq!(get_display(&events), "tôi đi học ");
|
||||
}
|
||||
|
||||
// A macro flush still expands (Replace) and keeps the trailing space.
|
||||
#[test]
|
||||
fn macro_flush_still_replaces() {
|
||||
let mut e = Engine::new(InputMethod::Telex);
|
||||
e.add_macro("vn".into(), "Việt Nam".into());
|
||||
let events = process_input(&mut e, "vn ");
|
||||
assert_eq!(get_display(&events), "Việt Nam ");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue