aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CLAUDE.md2
-rw-r--r--docs/design/vterm-to-ghostel-migration-spec.org11
-rw-r--r--modules/ai-term.el13
-rw-r--r--modules/term-config.el12
-rw-r--r--tests/test-ai-term--f9-in-term.el11
-rw-r--r--tests/test-term-tmux-history.el9
6 files changed, 52 insertions, 6 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index aca495d7..79e68ada 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -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