Fix flush spacing regression while preserving auto-restore
The auto-restore merge reintroduced the spacing bug: on every flush char the engine re-emitted a Replace and the daemon backspaced+retyped the already-on-screen composed word, racing against the separately-forwarded flush char and eating spaces (mất sự->mấtsự, đầu ngã xuống->đầungãxuống). Now flush only backspaces+retypes when auto-restore actually changes the word (English/invalid Vietnamese -> raw keystrokes). For a normal composed word the engine returns None and the daemon types only the flush char, leaving the correct word untouched on screen. Co-Authored-By: vndangkhoa <vonguyendangkhoa@gmail.com>
This commit is contained in:
parent
4a8d777744
commit
51949fe02b
3 changed files with 32 additions and 31 deletions
|
|
@ -285,15 +285,17 @@ impl Daemon {
|
||||||
fn replay_and_inject(&mut self, ch: char) -> Vec<OutputCommand> {
|
fn replay_and_inject(&mut self, ch: char) -> Vec<OutputCommand> {
|
||||||
let mut commands = Vec::new();
|
let mut commands = Vec::new();
|
||||||
|
|
||||||
// Flush characters: commit current word, type the character, clear state.
|
// Flush characters: commit the current word and type the flush char.
|
||||||
// The composed word is already correctly on screen, so we must NOT
|
// Only backspace + retype when auto-restore actually CHANGES the word
|
||||||
// backspace and retype it — doing so eats the spacing and shifts the
|
// (English / invalid Vietnamese). For a normal composed word it is
|
||||||
// finished word left. Just type the flush char and clear state.
|
// already correctly on screen, so retyping it would eat the spacing and
|
||||||
|
// shift the finished word left.
|
||||||
if is_flush_char(ch) {
|
if is_flush_char(ch) {
|
||||||
if !self.screen_output.is_empty() {
|
let to_commit = self.word_to_commit();
|
||||||
|
if !self.screen_output.is_empty() && to_commit != self.screen_output {
|
||||||
let backspaces = self.screen_output.chars().count();
|
let backspaces = self.screen_output.chars().count();
|
||||||
commands.push(OutputCommand::Backspace(backspaces));
|
commands.push(OutputCommand::Backspace(backspaces));
|
||||||
commands.push(OutputCommand::Type(self.word_to_commit()));
|
commands.push(OutputCommand::Type(to_commit));
|
||||||
}
|
}
|
||||||
// Type the flush character itself
|
// Type the flush character itself
|
||||||
commands.push(OutputCommand::Type(ch.to_string()));
|
commands.push(OutputCommand::Type(ch.to_string()));
|
||||||
|
|
@ -317,16 +319,15 @@ impl Daemon {
|
||||||
);
|
);
|
||||||
|
|
||||||
if did_flush {
|
if did_flush {
|
||||||
// Engine flushed a word — it is already correctly on screen, so
|
// Engine flushed a word. Only backspace + retype when auto-restore
|
||||||
// just clear state without backspacing/retyping it (retyping eats
|
// actually CHANGES the word; otherwise the composed word is already
|
||||||
// spacing and shifts the finished word left).
|
// correct on screen and retyping it eats spacing and shifts the
|
||||||
// Engine flushed a word — commit it and clear state
|
// finished word left.
|
||||||
// The flush char (space/period/etc) was NOT in history, so we need to
|
let to_commit = self.word_to_commit();
|
||||||
// type whatever was on screen + the flush char
|
if !self.screen_output.is_empty() && to_commit != self.screen_output {
|
||||||
if !self.screen_output.is_empty() {
|
|
||||||
let backspaces = self.screen_output.chars().count();
|
let backspaces = self.screen_output.chars().count();
|
||||||
commands.push(OutputCommand::Backspace(backspaces));
|
commands.push(OutputCommand::Backspace(backspaces));
|
||||||
commands.push(OutputCommand::Type(self.word_to_commit()));
|
commands.push(OutputCommand::Type(to_commit));
|
||||||
}
|
}
|
||||||
self.keystroke_history.clear();
|
self.keystroke_history.clear();
|
||||||
self.screen_output.clear();
|
self.screen_output.clear();
|
||||||
|
|
|
||||||
|
|
@ -213,25 +213,21 @@ impl Engine {
|
||||||
|
|
||||||
let raw = self.raw_buffer.clone();
|
let raw = self.raw_buffer.clone();
|
||||||
self.reset();
|
self.reset();
|
||||||
// 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.
|
|
||||||
if prev_len > 0 {
|
if prev_len > 0 {
|
||||||
// Auto-restore: if the committed word is English / not valid
|
// Auto-restore: if the committed word is English / not valid
|
||||||
// Vietnamese, revert to the raw keystrokes the user typed.
|
// Vietnamese, revert to the raw keystrokes the user typed. This
|
||||||
|
// genuinely changes the on-screen word, so a Replace is needed.
|
||||||
if self.auto_restore && Engine::should_restore_word(&previous, &raw) {
|
if self.auto_restore && Engine::should_restore_word(&previous, &raw) {
|
||||||
return Some(EngineEvent::Replace {
|
return Some(EngineEvent::Replace {
|
||||||
backspaces: prev_len,
|
backspaces: prev_len,
|
||||||
insert: raw,
|
insert: raw,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Don't include flush char in insert — daemon forwards it separately
|
// Normal case: the composed word is already correctly on screen.
|
||||||
return Some(EngineEvent::Replace {
|
// Re-typing it would trigger a redundant backspace + retype that
|
||||||
backspaces: prev_len,
|
// races against the separately-forwarded flush char, eating
|
||||||
insert: previous,
|
// spaces and merging words. Finalize and let the flush char
|
||||||
});
|
// through untouched.
|
||||||
}
|
}
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,11 +91,15 @@ fn auto_restore_can_be_disabled() {
|
||||||
for ch in "cargo".chars() {
|
for ch in "cargo".chars() {
|
||||||
engine.process_key(ch);
|
engine.process_key(ch);
|
||||||
}
|
}
|
||||||
|
// With auto-restore off the Vietnamese composition is kept on screen
|
||||||
|
// (no restore back to the raw English keystrokes).
|
||||||
|
assert_eq!(engine.buffer(), "cảgo");
|
||||||
|
// The composed word is already correct on screen, so flushing emits no
|
||||||
|
// Replace — re-typing it would race with the forwarded flush char and eat
|
||||||
|
// the spacing. (Contrast with auto-restore on, which emits Replace→"cargo".)
|
||||||
let event = engine.process_key(' ');
|
let event = engine.process_key(' ');
|
||||||
match event {
|
assert!(
|
||||||
Some(vietc_engine::EngineEvent::Replace { insert, .. }) => {
|
event.is_none(),
|
||||||
assert_eq!(insert, "cảgo", "with auto-restore off the VN form is kept");
|
"with auto-restore off the composed VN word stays untouched on flush, got {event:?}"
|
||||||
}
|
);
|
||||||
other => panic!("expected Replace to 'cảgo', got {other:?}"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue