1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
|
;;; eat-config.el --- EAT terminal emulator and the F12 eshell toggle -*- lexical-binding: t; coding: utf-8; -*-
;;; Commentary:
;;
;; EAT (Emulate A Terminal, pure elisp) is the terminal emulator. Because EAT
;; renders entirely in elisp, its whole palette is real Emacs faces, so it themes
;; from the theme. This module owns the eat package configuration, the keymap
;; wiring that lets F12 and C-; reach Emacs from inside a terminal, and the F12
;; dock-and-remember toggle.
;;
;; F12 opens eshell, which runs through EAT (eat-eshell-mode, set up in
;; eshell-config.el): the shell is eshell -- elisp functions as commands, TRAMP
;; transparency -- and EAT renders its visual commands. eshell-config.el holds
;; the shell itself; this module holds the emulator and the toggle.
;;
;; The toggle reuses the geometry-preservation pattern from cj-window-toggle-lib:
;; capture direction + body size at toggle-off, replay them via a custom display
;; action using frame-edge directions and body-relative sizes, so the docked
;; terminal returns at the same size and the result is divider-independent.
;;; Code:
(require 'cj-window-geometry-lib)
(require 'cj-window-toggle-lib)
(declare-function eat "eat" (&optional program arg))
(declare-function eshell "eshell" (&optional arg))
(defvar eat-mode-map)
(defvar eat-semi-char-mode-map)
(defvar eshell-buffer-name)
(defvar cj/custom-keymap)
(defun cj/turn-off-chrome-for-term ()
"Turn off line numbers and hl-line in a terminal buffer."
(hl-line-mode -1)
(display-line-numbers-mode -1))
;; ------------------------------- eat package ---------------------------------
(use-package eat
:ensure t
:commands (eat)
:hook (eat-mode . cj/turn-off-chrome-for-term)
:custom
;; Close the EAT buffer when its shell exits.
(eat-kill-buffer-on-exit t)
;; Shell-integration UX. These are EAT defaults, set explicitly to document
;; intent and survive default changes. They only light up once the shell
;; sources EAT's integration script -- see the EAT block in the zsh rc.
(eat-enable-directory-tracking t) ; Emacs follows the terminal's cwd
(eat-enable-shell-prompt-annotation t) ; the success/running/failure prompt glyphs
(eat-enable-shell-command-history t) ; terminal history into EAT line-mode isearch
;; Interaction.
(eat-enable-mouse t) ; mouse clicks + selection in TUIs (default)
(eat-enable-kill-from-terminal t) ; terminal selection -> Emacs kill-ring (default)
(eat-enable-yank-to-terminal t) ; Emacs kill-ring -> the terminal (off by default)
;; Fidelity.
(eat-enable-alternative-display t) ; alt-screen so TUIs restore scrollback on exit (default)
(eat-term-scrollback-size (* 10 1024 1024)) ; ~10MB of scrollback, matching the old ghostel
;; Truecolor is already on: eat-term-name auto-selects the compiled eat-truecolor terminfo.
;; Niceties.
(eat-sixel-render-formats '(xpm svg half-block background none)) ; inline images (on by default)
(eat-query-before-killing-running-terminal 'auto) ; confirm before killing a terminal with a live process
: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. 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.
Used as the size fallback when F12 docks the terminal as a bottom split."
:type 'number
:group 'term)
(defcustom cj/term-toggle-window-width 0.5
"Default fraction of frame width for the F12 terminal window.
Used as the size fallback when F12 docks the terminal as a right-side
column (see `cj/--term-toggle-default-direction')."
:type 'number
:group 'term)
(defun cj/--term-toggle-default-direction ()
"Return the default dock direction for the F12 terminal: `right' or `below'.
Docks as a right-side column only when a side-by-side split would leave
both panes at least `cj/window-dock-min-columns' wide (the terminal's
share is `cj/term-toggle-window-width'); otherwise stacks below. See
`cj/preferred-dock-direction'."
(cj/preferred-dock-direction (frame-width) cj/term-toggle-window-width))
(defun cj/--term-toggle-default-size (direction)
"Return the default size fraction paired with DIRECTION for the F12 terminal.
`cj/term-toggle-window-width' for `right', `cj/term-toggle-window-height'
otherwise."
(if (eq direction 'right)
cj/term-toggle-window-width
cj/term-toggle-window-height))
(defvar cj/--term-toggle-last-direction nil
"Last user-chosen direction for the F12 terminal display.
Symbol: right, left, or below. `above' is never stored. nil means use the
default `below' for F12's traditional bottom split.")
(defvar cj/--term-toggle-last-size nil
"Last user-chosen size for the F12 terminal display.
Positive integer: body-cols (right/left) or total-lines (below/above) -- see
`cj/window-replay-size' for why the vertical axis uses total, not body.
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 an eshell terminal F12 should manage.
F12 opens eshell, which runs through EAT via eat-eshell-mode. ai-term's ghostel
agent buffers are managed separately via M-SPC, not F12."
(and (bufferp buffer)
(buffer-live-p buffer)
(with-current-buffer buffer
(derived-mode-p 'eshell-mode))))
(defun cj/--term-toggle-buffers ()
"Return live F12-managed terminal buffers in `buffer-list' (MRU) order."
(seq-filter #'cj/--term-toggle-buffer-p (buffer-list)))
(defun cj/--term-toggle-displayed-window (&optional frame)
"Return a window in FRAME currently displaying an F12 terminal buffer, or nil.
FRAME defaults to the selected frame. Minibuffer is excluded."
(seq-find (lambda (w)
(cj/--term-toggle-buffer-p (window-buffer w)))
(window-list (or frame (selected-frame)) 'never)))
(defun cj/--term-toggle-capture-state (window)
"Capture WINDOW's direction + body size into module-level state.
The default direction (used when WINDOW fills its frame) is the
column-rule choice from `cj/--term-toggle-default-direction'."
(cj/window-toggle-capture-state
window (cj/--term-toggle-default-direction)
'cj/--term-toggle-last-direction
'cj/--term-toggle-last-size
'(right below left)))
(defun cj/--term-toggle-display-saved (buffer alist)
"Display-buffer action: split per saved direction and body size.
Delegates to `cj/window-toggle-display-saved' against the F12 state vars,
falling back to the column-rule default direction
\(`cj/--term-toggle-default-direction') and its paired size."
(let ((dir (cj/--term-toggle-default-direction)))
(cj/window-toggle-display-saved
buffer alist
'cj/--term-toggle-last-direction dir
'cj/--term-toggle-last-size (cj/--term-toggle-default-size dir))))
(defun cj/--term-toggle-display-rule-list ()
"Return the `display-buffer-alist' entry list installed by F12.
Routes any terminal buffer satisfying `cj/--term-toggle-buffer-p' through
reuse-window then the saved-geometry action. Excludes agent buffers."
'(((lambda (buffer-or-name _)
(cj/--term-toggle-buffer-p (get-buffer buffer-or-name)))
(display-buffer-reuse-window
cj/--term-toggle-display-saved)
(inhibit-same-window . t))))
(dolist (entry (cj/--term-toggle-display-rule-list))
(add-to-list 'display-buffer-alist entry))
(defun cj/--term-toggle-dispatch ()
"Compute the F12 (`cj/term-toggle') action without performing it.
Returns one of:
- (toggle-off . WINDOW) -- terminal displayed in WINDOW; hide it.
- (show-recent . BUFFER) -- terminal alive but not shown; redisplay.
- (create-new) -- no terminal buffer alive; create one."
(let ((win (cj/--term-toggle-displayed-window)))
(cond
(win (cons 'toggle-off win))
(t
(let ((buffers (cj/--term-toggle-buffers)))
(cond
(buffers (cons 'show-recent (car buffers)))
(t '(create-new))))))))
(defun cj/term-toggle ()
"Toggle the F12 eshell terminal (the primary `*eshell*', run through EAT).
- If it 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 it is alive, display it via the saved-geometry action.
- Otherwise, open eshell, displaying it through the same saved-geometry action.
eshell runs through EAT via eat-eshell-mode, so visual commands render in a real
terminal. ai-term's ghostel agent buffers are managed separately via M-SPC."
(interactive)
(pcase (cj/--term-toggle-dispatch)
(`(toggle-off . ,win)
(cj/--term-toggle-capture-state win)
(if (one-window-p)
(bury-buffer (window-buffer win))
(delete-window win))
nil)
(`(show-recent . ,buf)
(display-buffer buf)
(let ((w (get-buffer-window buf)))
(when w (select-window w)))
buf)
(`(create-new)
;; Open the primary eshell without stealing the layout, then display it
;; through the saved-geometry dock rule (same path as show-recent).
(save-window-excursion (eshell))
(let ((buf (get-buffer (or (bound-and-true-p eshell-buffer-name) "*eshell*"))))
(when buf
(display-buffer buf)
(let ((w (get-buffer-window buf)))
(when w (select-window w))))
buf))))
(keymap-global-set "<f12>" #'cj/term-toggle)
(provide 'eat-config)
;;; eat-config.el ends here
|