Forward IME-committed text and guard redraws during composition (CJK lisp IME)#343
Forward IME-committed text and guard redraws during composition (CJK lisp IME)#343junghan0611 wants to merge 1 commit into
Conversation
|
Hi @junghan0611, I'm a co-maintainer with @dakra. Thanks for the PR. I will need some time to review this more carefully since I'm pretty keen on avoiding increased complexity in this particular area, but here are some initial thoughts:
I do think proper support for Emacs IMEs is something we should have so would love to work with you to get this in. I don't have time right now for a more thorough review :) |
|
Hi @emil-e — thanks for the direction and the warm reception. Both points received. I'll read On the wordy comments — agreed, I'll trim to a single small block and move the race-window narrative to the commit body where it stops aging. I'll work at my own pace and force-push to this branch when the rewrite is ready. No need to ping me — just take a look whenever you have bandwidth. Thanks for the path. |
…intainer feedback PR submitted to upstream dakra/ghostel as draft (2026-05-28). Co-maintainer @emil-e gave initial architectural direction: - Fold lisp-IME guard into existing terminal-state machinery (e.g. `ghostel--terminal-live-p`) instead of adding a parallel predicate and splitting `ghostel--delayed-redraw`. - Trim in-source commentary; move diagnostic narrative to commit body. Promote PR + body + feedback into NEXT.md TOP so the refactor plan is traceable from the repo root. Existing 2026-05-26 diagnostic record kept as Background section. Refs: - PR: dakra/ghostel#343 - @emil-e: dakra/ghostel#343 (comment) - reply: dakra/ghostel#343 (comment)
|
Hi @junghan0611, thanks for the kind words (and @emil-e has to get all the praise for the glyph handling) ❤️ In addition to @emil-e's comment, I had a very quick peek. The ime changes look very small but also besides the 1 redraw inhibit line completely independent. I'm also never a fan of adding hooks or changing someones Emacs just by loading a file. Anyway, you can force push on this branch. Thanks 🙏 |
2f996ba to
2a9fd7d
Compare
|
Thanks both — this is the rewrite you asked for, force-pushed as a single commit on top of What changed vs. the first draft
Tests — Confirmed working in daily use with both pi and Claude Code — both heavy streaming TUIs, which is the exact race trigger this addresses. Take your time — no rush. |
- term-config.el: opt-in :hook (ghostel-mode . ghostel-ime-mode) - packages.el: ghostel/evil-ghostel recipe restored to fix/korean-ime-commit (dakra/ghostel#343 rewrite, commit 2a9fd7d) - NEXT.md: PR sent, awaiting maintainer review
|
I haven't tested it but since the ghostel.el change is minimal and looks fine and ghostel-ime is completely independent and user opt-in I would give it a 👍 . @emil-e you can check and merge it whenever you have time. |
|
I'll have a look as soon as I have the time! |
|
Sorry for the late review. It looks good to me, but needs a rebase. I have done quite some refactoring here to reduce complexity and your PR is unfortunately one of the casualties :) |
2a9fd7d to
f1a7060
Compare
Some Emacs Lisp input methods, such as `hangul-input-method', commit text by calling `self-insert-command' directly instead of returning events from `input-method-function'. A direct call bypasses ghostel's key remapping, so the character lands in the buffer but is never sent to the PTY and the next redraw erases it. Separately, when a TUI streams output while the user composes via a Quail-style method, the renderer can rewrite the buffer mid-composition and corrupt the in-flight syllable. Add `ghostel-ime-mode', an opt-in buffer-local minor mode in a new `ghostel-ime.el'. When the input method commits by buffer insertion the mode forwards the committed text to the PTY as UTF-8 and lets the shell echo it back through the normal redraw path. Core gains a single generic hook, `ghostel-inhibit-redraw-functions', checked once in `ghostel--redraw-now': if any function returns non-nil the redraw is deferred and rescheduled. ghostel-ime registers its composition predicate there, so core never calls into the IME code directly. GUI native preedit handling is unchanged. Users opt in with `:hook (ghostel-mode . ghostel-ime-mode)'. The predicate keys on `quail-overlay' (Emacs core), so it generalizes to any Lisp input method - Korean Hangul, Japanese, Chinese. The mode also re-asserts the wrapper from `post-command-hook' so an external input-method/state manager (Evil, for example) that restores the raw translator without firing `input-method-activate-hook' does not leave commits unforwarded. Add ghostel-ime-test.el covering the deferral hook, the minor-mode wiring, the list-sentinel guard, re-activation and external-reset re-wrapping, CJK commit-forwarding, the quail-overlay probe, and input-mode-switch survival.
f1a7060 to
e7dd8f3
Compare
|
Rebased onto current Your Core stays IME-agnostic — Validation:
|
Hi @dakra,
Before anything else — thank you for ghostel. This project is genuinely precious to me, and I mean that.
A bit about who I am
I'm a Doom Emacs user and a CJK (Korean — Hangul) speaker, and I work daily in both GUI Emacs and
-nw(terminal) Emacs. The-nwpath matters to me as much as the GUI path. For years, getting Korean IME to behave reliably inside terminal-style buffers inside Emacs has been a constant low-grade pain. ghostel solved enough of that — and was clean enough underneath — that I rebuilt my terminal workflow around it.I'm also a ghostty user, and honestly: I had been quietly thinking I might have to attempt this kind of project myself at some point. I'm very glad someone with your skill level got there first and built it properly. Truly grateful.
How I use ghostel
-nw): https://github.com/junghan0611/doomemacs-config/blob/main/lisp/term-config.elpi(an ACP-based agent harness I develop) and Claude Code. Both are heavy streaming TUIs that emit small PTY chunks at high frequency — which turns out to be the exact race-condition trigger this PR addresses.hangul2-input-methodvia Emacs' built-inquail.What this PR proposes
Two related changes, ~170 lines total, all in
lisp/ghostel.el, zero native zig changes.1. Forward IME-committed text to PTY for Emacs input methods
Some Emacs input methods commit text by directly inserting into the buffer (
hangul-insert-character→(self-insert-command 1)as a function call withlast-command-eventbound) instead of returning events frominput-method-function. A function call bypasses[remap self-insert-command], so the character lands in the ghostel buffer but is never sent to the PTY — the next redraw erases it.Fix: locally wrap
input-method-functionin ghostel buffers. Around the original IME call, observe whether point advanced; if so the IME committed text by buffer insertion. Capture that text, delete it from the buffer, and forward it to the PTY as UTF-8. The shell echoes it back through the normal redraw path, so the buffer ends up consistent without racing the redraw. IMEs that already return events (most quail packages) pass straight through. Restricted to PTY-forwarding modes (semi-char, char); line mode untouched.2. Guard
ghostel--redrawagainst running mid-compositionWhen a TUI streams output via ~256B PTY chunks while the user composes Korean via
hangul2-input-method, syllables vanish — or 30+ byte chaos sequences (literally like자갈ㅓㅏㅓㅏㅏㅏㅓㅏㅓㅏㅓ) get forwarded to the PTY.Race:
hangul2-input-methodruns aread-key-sequenceloop inside the IME wrapper, using the ghostel buffer as a scratch area; each key mutates the buffer viahangul-insert-character→self-insert-command. In parallel, agent output triggersghostel--filter's immediate-redraw path, which callsghostel--delayed-redrawsynchronously; that body invokes the nativeghostel--redrawto erase + reinsert the buffer from libghostty's VT grid.quail-overlaymarkers get invalidated mid-loop whileinhibit-modification-hooks tsilences quail's notifications, and the wrapper'sbuffer-substringthen captures stale text.Existing limit:
ghostel--active-preedit-overlayonly tracksx-preedit-overlay/pgtk-preedit-overlay— GUI native IME. Emacs-lisp IMEs (quail-overlayfamily: hangul, anthy, …) were never in scope;--capture-preedit-state/--restore-preedit-statedon't see them.Fix: a small predicate
ghostel--ime-lisp-composing-p(a dynamic flag set by the IME wrapper + a livequail-overlaycheck) guards every code path that can end inghostel--redrawrewriting the buffer:ghostel--filter— falls through to the bulk path during composition.ghostel--delayed-redraw— split intoghostel--delayed-redraw-body, with the guard living in the renamed wrapper. Catches every caller (theme sync, PTY filter immediate path, PTY filter bulk timer,M-x ghostel-force-redraw, window resize) in one place.GUI native IME streaming behavior is intentionally unchanged — the predicate excludes
x-preedit-overlay/pgtk-preedit-overlay. Line mode is unaffected. While the symptom that surfaced this is Korean hangul, the fix usesquail-overlay(Emacs core), so it generalizes to any lisp IME — Japanese anthy, Chinese, …Detailed race-window analysis, invariant, and refactor guidance for future-you are documented inline as commentary blocks (the
;; Lisp IME composition guardsection).Why this is a draft
I don't send PRs the moment I write something. I keep the patch on a fork branch, pull upstream into it on a regular cadence, and use the result as my daily driver — that's how I shake bugs out. ghostel is your main project; it deserves to be treated that way.
By now this series has survived two upstream merges including the v0.30.0 release plus 118 commits, including refactors that touched the exact same area (
9ff243f Centralize ghostel buffer terminal initialization,22e535a Break out invalidation logic out of redraw) — with no merge conflicts and no behavioral regressions in daily use. That gives me reasonable confidence the placement is structural rather than coincidental.That said, it stays as a draft until you weigh in. Things I'd love your judgement on:
lisp/ghostel.el, or move to a newlisp/ghostel-ime.el. I left it inline because every call site lives inghostel.el, but I have no strong preference.Branch / commits
junghan0611/ghostelfix/korean-ime-commit8d3320d Forward IME-committed text to PTY for Emacs input methods(2026-05-07)d9c5b11 Restrict IME commit forwarding to PTY-forwarding input modes3b518a0(helpers, no behavior change) →83f110c(immediate-redraw) →88cdb7a(delayed-redraw safety net) →db2484d(commentary fix)다시 한 번, 진심으로 감사합니다.
— @junghan0611