aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-25 16:17:26 -0400
committerCraig Jennings <c@cjennings.net>2026-06-25 16:17:26 -0400
commitfe7aa6585fc065b4c90e5f7f3c69ae5e647378b3 (patch)
tree2510f100949f4c3d87d58601a7acff49c15ebfcd
parent4205e8e3f73b09a36099b91c741944f4e1a10296 (diff)
downloaddotemacs-fe7aa6585fc065b4c90e5f7f3c69ae5e647378b3.tar.gz
dotemacs-fe7aa6585fc065b4c90e5f7f3c69ae5e647378b3.zip
feat(term): toggle EAT instead of ghostel on F12
F12 now creates and toggles a single EAT terminal (pure-elisp, fully themeable) instead of a ghostel one, reusing the existing dock-and-remember geometry toggle. ghostel stays for ai-term on M-SPC. The EAT terminal runs a plain shell with no tmux. F12 and C-; are bound in EAT's semi-char and mode keymaps so they reach Emacs from inside the terminal (EAT forwards unbound keys to the shell otherwise). Retargeted the toggle's buffer predicate and create-new path from ghostel to EAT, and updated the buffer-filter tests to the EAT semantics.
-rw-r--r--modules/term-config.el80
-rw-r--r--tests/test-term-toggle--buffer-filter.el49
-rw-r--r--tests/testutil-ghostel-buffers.el11
3 files changed, 96 insertions, 44 deletions
diff --git a/modules/term-config.el b/modules/term-config.el
index 7fb02af2c..7af465a71 100644
--- a/modules/term-config.el
+++ b/modules/term-config.el
@@ -61,6 +61,13 @@
(defvar ghostel-buffer-name)
(defvar ghostel--input-mode)
+;; eat backs the F12 toggle (see the eat package + F12 toggle sections below).
+(declare-function eat "eat" (&optional program arg))
+(defvar eat-buffer-name)
+(defvar eat-mode-map)
+(defvar eat-semi-char-mode-map)
+(defvar cj/custom-keymap)
+
(defvar-keymap cj/term-map
:doc "Personal terminal command map.")
;; Lowercase x picked over T for fewer Shift presses; t is the toggle leaf.
@@ -314,13 +321,32 @@ run its own project-named tmux session instead of a bare, auto-named one.
;; Byte analog of the prior 100000-line vterm setting (~100 bytes/line) -- D7.
(ghostel-max-scrollback (* 10 1024 1024)))
+;; ------------------------------- eat package ---------------------------------
+;; EAT (pure-elisp terminal) backs the F12 toggle: its whole palette is real
+;; Emacs faces, so it themes from the theme. ghostel stays for ai-term (M-SPC).
+;; No tmux here -- F12's EAT runs a plain $SHELL (decision 2026-06-25).
+
+(use-package eat
+ :ensure t
+ :commands (eat)
+ :hook (eat-mode . cj/turn-off-chrome-for-term)
+ :config
+ ;; F12 and C-; must reach Emacs from inside EAT. In semi-char mode (EAT's
+ ;; default) EAT forwards unbound keys to the terminal -- a letter runs
+ ;; `eat-self-input' -- so bind these explicitly or they never reach Emacs:
+ ;; F12 toggles the terminal window, C-; opens the global prefix map.
+ (keymap-set eat-semi-char-mode-map "<f12>" #'cj/term-toggle)
+ (keymap-set eat-semi-char-mode-map "C-;" cj/custom-keymap)
+ (keymap-set eat-mode-map "<f12>" #'cj/term-toggle)
+ (keymap-set eat-mode-map "C-;" cj/custom-keymap))
+
;; ----------------------- F12 toggle (custom) -----------------------
;;
;; Mirrors the geometry-preservation pattern shared with ai-term.el: capture
;; direction + body size at toggle-off, replay them via a custom display action
;; using frame-edge directions and body-relative sizes so the result is
-;; divider-independent and layout-stable. Excludes agent-prefixed buffers,
-;; which ai-term.el owns via F9.
+;; divider-independent and layout-stable. Manages the EAT terminal only;
+;; ai-term.el's ghostel agent buffers are separate (M-SPC).
(defcustom cj/term-toggle-window-height 0.7
"Default fraction of frame height for the F12 terminal window.
@@ -363,19 +389,18 @@ Positive integer: body-cols (right/left) or total-lines (below/above) -- see
nil means fall back to `cj/term-toggle-window-height' as a fraction.")
(defun cj/--term-toggle-buffer-p (buffer)
- "Return non-nil when BUFFER is a terminal buffer F12 should manage.
+ "Return non-nil when BUFFER is the EAT terminal F12 should manage.
-Qualifies when BUFFER is alive and has `ghostel-mode' (or its name starts with
-the ghostel buffer-name prefix), AND its name does NOT start with the agent
-prefix used by ai-term.el."
+Qualifies when BUFFER is alive and has `eat-mode' (or its name starts with the
+EAT buffer-name prefix). ai-term's ghostel agent buffers never match -- they
+are managed separately via M-SPC, not F12."
(and (bufferp buffer)
(buffer-live-p buffer)
(with-current-buffer buffer
- (and (or (eq major-mode 'ghostel-mode)
- (string-prefix-p (or (bound-and-true-p ghostel-buffer-name)
- "*ghostel*")
- (buffer-name buffer)))
- (not (string-prefix-p "agent [" (buffer-name buffer)))))))
+ (or (eq major-mode 'eat-mode)
+ (string-prefix-p (or (bound-and-true-p eat-buffer-name)
+ "*eat*")
+ (buffer-name buffer))))))
(defun cj/--term-toggle-buffers ()
"Return live F12-managed terminal buffers in `buffer-list' (MRU) order."
@@ -439,18 +464,17 @@ Returns one of:
(t '(create-new))))))))
(defun cj/term-toggle ()
- "Toggle a normal (non-agent) ghostel terminal buffer.
-
-- If an F12-managed terminal is displayed in this frame, capture its geometry
- and delete its window (toggle off). Falls back to burying when it is the
- only window in the frame.
-- Otherwise, if any F12-managed terminal buffer is alive, display the most
- recent one via the saved-geometry action.
-- Otherwise, create a new terminal via `(ghostel)' which routes through the
- same display action.
-
-Excludes agent-prefixed buffers; those have their own F9 dispatch via
-`cj/ai-term'."
+ "Toggle the EAT terminal buffer.
+
+- If the EAT terminal is displayed in this frame, capture its geometry and
+ delete its window (toggle off). Falls back to burying when it is the only
+ window in the frame.
+- Otherwise, if the EAT terminal buffer is alive, display it via the
+ saved-geometry action.
+- Otherwise, create a new EAT terminal, displaying it through the same
+ saved-geometry action.
+
+ai-term's ghostel agent buffers are managed separately via M-SPC, not F12."
(interactive)
(pcase (cj/--term-toggle-dispatch)
(`(toggle-off . ,win)
@@ -465,7 +489,15 @@ Excludes agent-prefixed buffers; those have their own F9 dispatch via
(when w (select-window w)))
buf)
(`(create-new)
- (ghostel))))
+ ;; Create the EAT buffer without stealing the layout, then display it
+ ;; through the saved-geometry dock rule (same path as show-recent).
+ (save-window-excursion (eat))
+ (let ((buf (get-buffer (or (bound-and-true-p eat-buffer-name) "*eat*"))))
+ (when buf
+ (display-buffer buf)
+ (let ((w (get-buffer-window buf)))
+ (when w (select-window w))))
+ buf))))
(keymap-global-set "<f12>" #'cj/term-toggle)
diff --git a/tests/test-term-toggle--buffer-filter.el b/tests/test-term-toggle--buffer-filter.el
index 2c96ecb38..44f30aad6 100644
--- a/tests/test-term-toggle--buffer-filter.el
+++ b/tests/test-term-toggle--buffer-filter.el
@@ -1,11 +1,12 @@
;;; test-term-toggle--buffer-filter.el --- Tests for F12's buffer filter -*- lexical-binding: t; -*-
;;; Commentary:
-;; Three closely-related helpers determine which terminal buffers F12
-;; manages: the predicate `cj/--term-toggle-buffer-p', the MRU list
+;; Three closely-related helpers determine which terminal buffer F12
+;; manages: the predicate `cj/--term-toggle-buffer-p', the list
;; `cj/--term-toggle-buffers', and the per-frame window finder
-;; `cj/--term-toggle-displayed-window'. All three exclude agent-
-;; prefixed buffers so agent has its own F9 surface.
+;; `cj/--term-toggle-displayed-window'. F12 manages the EAT terminal;
+;; ghostel buffers (including ai-term's agent buffers) are NOT F12-managed --
+;; they live on M-SPC.
;;; Code:
@@ -21,16 +22,24 @@
(cj/test--kill-agent-buffers)
(cj/test--kill-test-term-buffers))
-(ert-deftest test-term-toggle--buffer-p-accepts-ghostel-mode ()
- "Normal: a ghostel-mode buffer with non-agent name qualifies."
+(ert-deftest test-term-toggle--buffer-p-accepts-eat-mode ()
+ "Normal: an eat-mode buffer qualifies as the F12 terminal."
(test-term-toggle--cleanup)
- (let ((buf (cj/test--make-fake-ghostel-buffer "*test-term-1*")))
+ (let ((buf (cj/test--make-fake-eat-buffer "*test-term-1*")))
(unwind-protect
(should (cj/--term-toggle-buffer-p buf))
(kill-buffer buf))))
+(ert-deftest test-term-toggle--buffer-p-rejects-ghostel ()
+ "Boundary: a ghostel buffer is NOT F12-managed (ghostel is ai-term's, M-SPC)."
+ (test-term-toggle--cleanup)
+ (let ((buf (cj/test--make-fake-ghostel-buffer "*test-term-ghostel*")))
+ (unwind-protect
+ (should-not (cj/--term-toggle-buffer-p buf))
+ (kill-buffer buf))))
+
(ert-deftest test-term-toggle--buffer-p-rejects-agent ()
- "Boundary: agent-prefixed terminal buffers are excluded from F12's set."
+ "Boundary: ai-term agent buffers are excluded from F12's set."
(test-term-toggle--cleanup)
(let ((buf (cj/test--make-fake-ghostel-buffer "agent [project-a]")))
(unwind-protect
@@ -38,7 +47,7 @@
(kill-buffer buf))))
(ert-deftest test-term-toggle--buffer-p-rejects-non-terminal ()
- "Boundary: a regular buffer (not ghostel-mode, no terminal name prefix) -> nil."
+ "Boundary: a regular buffer (not eat-mode, no terminal name prefix) -> nil."
(test-term-toggle--cleanup)
(let ((buf (get-buffer-create "*test-term-regular*")))
(unwind-protect
@@ -48,35 +57,35 @@
(ert-deftest test-term-toggle--buffer-p-rejects-dead-buffer ()
"Boundary: nil and dead buffers -> nil."
(should-not (cj/--term-toggle-buffer-p nil))
- (let ((buf (cj/test--make-fake-ghostel-buffer "*test-term-dead*")))
+ (let ((buf (cj/test--make-fake-eat-buffer "*test-term-dead*")))
(kill-buffer buf)
(should-not (cj/--term-toggle-buffer-p buf))))
-(ert-deftest test-term-toggle--buffers-filters-agent ()
- "Normal: returns terminal buffers but excludes agent-prefixed ones."
+(ert-deftest test-term-toggle--buffers-returns-eat-excludes-others ()
+ "Normal: returns the EAT terminal but not ghostel/agent buffers."
(test-term-toggle--cleanup)
- (let ((normal (cj/test--make-fake-ghostel-buffer "*test-term-normal*"))
+ (let ((eat (cj/test--make-fake-eat-buffer "*test-term-eat*"))
(agent (cj/test--make-fake-ghostel-buffer "agent [for-test]")))
(unwind-protect
(let ((result (cj/--term-toggle-buffers)))
- (should (memq normal result))
+ (should (memq eat result))
(should-not (memq agent result)))
- (kill-buffer normal)
+ (kill-buffer eat)
(kill-buffer agent))))
(ert-deftest test-term-toggle--displayed-window-finds-terminal ()
- "Normal: terminal in a window -> returns that window."
+ "Normal: the EAT terminal in a window -> returns that window."
(test-term-toggle--cleanup)
- (let ((vt (cj/test--make-fake-ghostel-buffer "*test-term-shown*")))
+ (let ((eat (cj/test--make-fake-eat-buffer "*test-term-shown*")))
(unwind-protect
(save-window-excursion
(delete-other-windows)
(let ((win (split-window-right)))
- (set-window-buffer win vt)
+ (set-window-buffer win eat)
(let ((result (cj/--term-toggle-displayed-window)))
(should (windowp result))
- (should (eq (window-buffer result) vt)))))
- (kill-buffer vt))))
+ (should (eq (window-buffer result) eat)))))
+ (kill-buffer eat))))
(ert-deftest test-term-toggle--displayed-window-skips-agent ()
"Boundary: only an agent terminal is displayed -> nil (agent not F12-managed)."
diff --git a/tests/testutil-ghostel-buffers.el b/tests/testutil-ghostel-buffers.el
index 52fb27e00..3c8d75d00 100644
--- a/tests/testutil-ghostel-buffers.el
+++ b/tests/testutil-ghostel-buffers.el
@@ -45,5 +45,16 @@ ghostel-mode predicate without the side-effects of `(ghostel)'."
(setq-local major-mode 'ghostel-mode))
buf))
+(defun cj/test--make-fake-eat-buffer (name)
+ "Return a buffer named NAME with `major-mode' set to `eat-mode'.
+
+Avoids actually launching an EAT process by setting the mode buffer-locally.
+Used by the F12 toggle tests that need a buffer satisfying the eat-mode
+predicate without the side-effects of `(eat)'."
+ (let ((buf (get-buffer-create name)))
+ (with-current-buffer buf
+ (setq-local major-mode 'eat-mode))
+ buf))
+
(provide 'testutil-ghostel-buffers)
;;; testutil-ghostel-buffers.el ends here