diff options
| -rw-r--r-- | CLAUDE.md | 2 | ||||
| -rw-r--r-- | docs/design/vterm-to-ghostel-migration-spec.org | 11 | ||||
| -rw-r--r-- | modules/ai-term.el | 13 | ||||
| -rw-r--r-- | modules/term-config.el | 12 | ||||
| -rw-r--r-- | tests/test-ai-term--f9-in-term.el | 11 | ||||
| -rw-r--r-- | tests/test-term-tmux-history.el | 9 |
6 files changed, 52 insertions, 6 deletions
@@ -83,3 +83,5 @@ Prefer Write over cumulative Edits for nontrivial new code. Small functions (und - **Emacs 30 batch mode: `provide` does not fire registered `eval-after-load` callbacks.** Only an actual `load` triggers them. Tests that drive lazy-loading via `(provide 'foo)` will see registered callbacks fail to run. Two robust alternatives: (a) `(load <temp-file-that-provides-foo>)`, or (b) assert against `after-load-alist` directly — stronger evidence anyway since it proves the hook is registered for the right feature, not just that the body happens to execute. See `tests/test-ai-config-gptel-magit-lazy-loading.el` for the after-load-alist inspection pattern. (`gotcha` — 2026-05-16) - **Warn at module load when an external tool path is configured but missing.** Calling `cj/executable-find-or-warn` (from `system-lib.el`) at `:config` time emits a `display-warning` if `prettier` / `pyright` / `pandoc` / etc. isn't on PATH, instead of letting the first format-on-save or LSP-attach fail with a confusing mid-edit error. Pattern in use: `modules/prog-webdev.el` (prettier), `modules/prog-python.el` (pyright). (`pattern` — 2026-05-16) + +- **ghostel F-key / prefix bindings need `ghostel-keymap-exceptions` + a rebuild, not just `ghostel-mode-map`.** In semi-char mode ghostel forwards every key not in `ghostel-keymap-exceptions` to the pty, and `ghostel-semi-char-mode-map` (rebuilt from that list, and outranking the major-mode map) wins. So binding F9 / F12 / C-; in `ghostel-mode-map` alone is silently dead inside agent/terminal buffers — the key reaches the shell, not Emacs. Fix: add the key to `ghostel-keymap-exceptions` AND call `ghostel--rebuild-semi-char-keymap` (`add-to-list` updates the list but not the already-built map). `term-config.el` (C-;, F12) and `ai-term.el` (F9 family) do this in their `with-eval-after-load 'ghostel`. This is the opposite of vterm, where binding in `vterm-mode-map` sufficed. (`gotcha` — 2026-06-05) diff --git a/docs/design/vterm-to-ghostel-migration-spec.org b/docs/design/vterm-to-ghostel-migration-spec.org index b7d61e23..5974445a 100644 --- a/docs/design/vterm-to-ghostel-migration-spec.org +++ b/docs/design/vterm-to-ghostel-migration-spec.org @@ -53,9 +53,14 @@ in the real config. Emacs 30.2 GTK, x86_64, modules supported. - *F8 / key forwarding (diagnostic)*: ghostel's default semi-char mode forwards unlisted keys to the terminal program; only =ghostel-keymap-exceptions= (default =C-c C-x C-u C-h M-x M-: C-\=) reach - Emacs. This is why F-key bindings (F9 family, F12) must be installed in - =ghostel-mode-map= for terminal buffers, exactly as the current config does - for =vterm-mode-map=. + Emacs. Unlike vterm, binding F9/F12 in =ghostel-mode-map= is NOT enough: + =ghostel-semi-char-mode-map= is rebuilt from =ghostel-keymap-exceptions= and + outranks the major-mode map, so a key not in the exceptions is sent to the + pty before the mode-map binding can fire. The F9 family, F12, and C-; must be + added to =ghostel-keymap-exceptions= AND the semi-char map rebuilt + (=ghostel--rebuild-semi-char-keymap=; =add-to-list= alone updates the list + but not the already-built map). (Shipped wrong in the first cut — F9 did + nothing in agent buffers until the keys were added to the exceptions.) - *GUI / TTY visual*: Craig confirmed the Claude Code TUI and a TTY frame both render great. dupre chrome applies; the 16 ANSI terminal faces are ghostel defaults (dupre does not theme them) — Decision D2. diff --git a/modules/ai-term.el b/modules/ai-term.el index 85b84a12..1384f812 100644 --- a/modules/ai-term.el +++ b/modules/ai-term.el @@ -74,6 +74,8 @@ (declare-function ghostel "ghostel" (&optional arg)) (declare-function ghostel-send-string "ghostel" (string)) +(declare-function ghostel--rebuild-semi-char-keymap "ghostel" ()) +(defvar ghostel-keymap-exceptions) (defvar ghostel-mode-map) (defvar ghostel-buffer-name) (defvar ghostel-buffer-name-function) @@ -923,7 +925,16 @@ interrupt work in progress. Bound to M-<f9> (primary) and C-S-<f9>." (keymap-set ghostel-mode-map "<f9>" #'cj/ai-term) (keymap-set ghostel-mode-map "C-<f9>" #'cj/ai-term-pick-project) (keymap-set ghostel-mode-map "M-<f9>" #'cj/ai-term-close) - (keymap-set ghostel-mode-map "C-S-<f9>" #'cj/ai-term-close)) + (keymap-set ghostel-mode-map "C-S-<f9>" #'cj/ai-term-close) + ;; The bindings above live in `ghostel-mode-map', but in semi-char mode + ;; ghostel's own `ghostel-semi-char-mode-map' forwards every key not in + ;; `ghostel-keymap-exceptions' to the pty -- and that map outranks the + ;; major-mode map, so it would swallow the F9 family before the bindings + ;; above fire. Add the family to the exceptions and rebuild the semi-char + ;; map so the keys fall through to `ghostel-mode-map' inside agent buffers. + (dolist (key '("<f9>" "C-<f9>" "M-<f9>" "C-S-<f9>")) + (add-to-list 'ghostel-keymap-exceptions key)) + (ghostel--rebuild-semi-char-keymap)) ;; ---------- emacsclient: keep opened files off the agent terminal ---------- ;; diff --git a/modules/term-config.el b/modules/term-config.el index 84ba7b3b..b327777a 100644 --- a/modules/term-config.el +++ b/modules/term-config.el @@ -53,6 +53,7 @@ (declare-function ghostel-next-prompt "ghostel" (&optional n)) (declare-function ghostel-previous-prompt "ghostel" (&optional n)) (declare-function ghostel-send-next-key "ghostel" ()) +(declare-function ghostel--rebuild-semi-char-keymap "ghostel" ()) (defvar ghostel-mode-map) (defvar ghostel-keymap-exceptions) (defvar ghostel-buffer-name) @@ -220,9 +221,16 @@ run its own project-named tmux session instead of a bare, auto-named one. :ensure t :commands (ghostel) :init - ;; C-; must reach Emacs so the personal prefix keymap works in terminals. + ;; C-; and F12 must reach Emacs (not the terminal program) inside ghostel + ;; buffers. In semi-char mode ghostel forwards every key NOT in + ;; `ghostel-keymap-exceptions' to the pty, and `ghostel-semi-char-mode-map' + ;; is rebuilt from that list by `ghostel--rebuild-semi-char-keymap' -- + ;; `add-to-list' alone updates the list but not the already-built map, so the + ;; rebuild is what actually lets the key through to `ghostel-mode-map'. (with-eval-after-load 'ghostel - (add-to-list 'ghostel-keymap-exceptions "C-;")) + (dolist (key '("C-;" "<f12>")) + (add-to-list 'ghostel-keymap-exceptions key)) + (ghostel--rebuild-semi-char-keymap)) :hook ((ghostel-mode . cj/turn-off-chrome-for-term) (ghostel-mode . cj/term-launch-tmux)) diff --git a/tests/test-ai-term--f9-in-term.el b/tests/test-ai-term--f9-in-term.el index 53e1c4e7..dad11ffc 100644 --- a/tests/test-ai-term--f9-in-term.el +++ b/tests/test-ai-term--f9-in-term.el @@ -41,5 +41,16 @@ agent; `M-<f9>' and `C-S-<f9>' close an agent via `cj/ai-term-close'." (should (eq (lookup-key (current-global-map) (kbd "M-<f9>")) #'cj/ai-term-close)) (should (eq (lookup-key (current-global-map) (kbd "C-S-<f9>")) #'cj/ai-term-close))) +(ert-deftest test-ai-term-f9-family-in-keymap-exceptions () + "Regression: the F9 family is in `ghostel-keymap-exceptions' so semi-char +mode lets it reach Emacs instead of forwarding it to the terminal program. +Binding in `ghostel-mode-map' alone is not enough -- the semi-char map outranks +it and forwards any key not in the exceptions to the pty." + (dolist (key '("<f9>" "C-<f9>" "M-<f9>" "C-S-<f9>")) + (should (member key ghostel-keymap-exceptions))) + ;; The rebuilt semi-char map must no longer forward <f9> to the pty. + (should-not (eq (keymap-lookup ghostel-semi-char-mode-map "<f9>") + 'ghostel--send-event))) + (provide 'test-ai-term--f9-in-term) ;;; test-ai-term--f9-in-term.el ends here diff --git a/tests/test-term-tmux-history.el b/tests/test-term-tmux-history.el index 2c9c38f8..49296b42 100644 --- a/tests/test-term-tmux-history.el +++ b/tests/test-term-tmux-history.el @@ -308,5 +308,14 @@ its own copy-mode against the full pane history." (when (buffer-live-p agent) (kill-buffer agent))))) +(ert-deftest test-term-prefix-and-f12-in-keymap-exceptions () + "Regression: C-; and F12 are in `ghostel-keymap-exceptions' and the rebuilt +semi-char map no longer forwards them to the pty, so the prefix keymap and the +F12 toggle reach Emacs inside ghostel buffers." + (dolist (key '("C-;" "<f12>")) + (should (member key ghostel-keymap-exceptions))) + (should-not (eq (keymap-lookup ghostel-semi-char-mode-map "<f12>") + 'ghostel--send-event))) + (provide 'test-term-tmux-history) ;;; test-term-tmux-history.el ends here |
