Skip to content

emacs-rime RET commit fails with "Buffer is read-only" — commit path bypasses ghostel-ime-mode (follow-up to #343/#510) #524

Description

@wd

Disclaimer

I'm not deeply familiar with the internals of either ghostel or emacs-rime, and I'm genuinely unsure whether this belongs here or in the emacs-rime tracker. I'm filing it here because ghostel already owns an IME-integration layer (ghostel-ime-mode) that deliberately handles Lisp IMEs which commit by inserting into the buffer — this looks like a second commit path that layer doesn't yet cover. If you feel this is really an emacs-rime problem, please redirect me and I'll refile.

Summary

With emacs-rime active in a ghostel buffer, pressing RET to commit fails with:

rime--return: Buffer is read-only: #<buffer *claude:...*>

Per-character input composes fine (thanks to ghostel-ime-mode — character input is forwarded to the PTY correctly). Only the commit-on-RET path breaks.

Repro

  • Emacs 31.0.90 (emacs-plus@31), macOS
  • ghostel 28b39a6 (2026-07-02), ghostel-ime-mode enabled via (add-hook 'ghostel-mode-hook #'ghostel-ime-mode)
  • emacs-rime f927d26
  1. Open a ghostel buffer, activate emacs-rime (toggle-input-method).
  2. Type nihao — the rime preedit/candidates show correctly.
  3. Press RET.
  4. rime--return: Buffer is read-only.

Root cause (best understanding)

This is adjacent to #343 and #510. #510 fixed the read-only interaction for the input-method-function path by binding buffer-read-only/inhibit-read-only around the input method inside ghostel-ime--wrap-input-method.

But emacs-rime commits via its own keymap command, not through input-method-function. When a composition is active, rime pushes rime-active-mode-map onto overriding-terminal-local-map, binding RETrime--returnrime--commit:

(defun rime--commit (value)
  "Insert VALUE, then clear state."
  (when (and value (rime--rime-lib-module-ready-p))
    (rime--clear-overlay)
    (insert value)            ; <-- hits the read-only barrier; also never reaches the PTY
    (rime-lib-clear-composition)
    ...))

Because this bypasses input-method-function, the ghostel-ime--wrap-input-method wrapper (and #510's read-only lifting) never runs. The (insert value) both trips buffer-read-only and, even if it didn't, would land in the buffer instead of being forwarded to the PTY.

So: same class of problem #343/#510 addressed (a Lisp IME inserting committed text into a protected buffer), but via a second path — a keymap command the IME installs itself — that the current wrapper doesn't intercept.

Workaround I'm using

An :around advice on rime--commit that, in ghostel buffers, forwards the committed text to the PTY instead of inserting:

(with-eval-after-load 'rime
  (defun my/rime-commit-to-pty (orig-fn value)
    (if (and (derived-mode-p 'ghostel-mode)
             (fboundp 'ghostel--send-string)
             value)
        (progn
          (when (fboundp 'rime--clear-overlay) (rime--clear-overlay))
          (ghostel--send-string (encode-coding-string value 'utf-8))
          (rime-lib-clear-composition)
          (rime--redisplay)
          (rime--refresh-mode-state))
      (funcall orig-fn value)))
  (advice-add 'rime--commit :around #'my/rime-commit-to-pty))

This works, but it hardcodes a private rime function (rime--commit), so it's clearly not a general fix. A robust solution probably belongs in ghostel — e.g. intercepting inserts into the protected region at the read-only-barrier level so that any IME committing through its own command path is forwarded to the PTY, not just those going through input-method-function. I'll defer to your judgment on the right layer.

Happy to test patches. Thanks for ghostel!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions