diff options
Diffstat (limited to 'modules/term-config.el')
| -rw-r--r-- | modules/term-config.el | 208 |
1 files changed, 164 insertions, 44 deletions
diff --git a/modules/term-config.el b/modules/term-config.el index f9c126357..7af465a71 100644 --- a/modules/term-config.el +++ b/modules/term-config.el @@ -59,6 +59,14 @@ (defvar ghostel-mode-map) (defvar ghostel-keymap-exceptions) (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.") @@ -206,6 +214,44 @@ start of the line for the same column-0 reason." (ghostel-copy-mode) (beginning-of-line))) +;; ----------------------------- copy-mode scroll ------------------------------ +;; +;; C-<up> both enters copy-mode and scrolls up one line, so a single stroke +;; lands in the scrollback already moving the right way. It joins +;; `ghostel-keymap-exceptions' so it reaches Emacs instead of the pty. Only the +;; up gesture is bound: C-<left>/<right> are readline word-motion at the shell +;; prompt and must pass through, and the other directions have no copy-mode use. +;; Pressed again while already in copy-mode it just moves up -- re-entering would +;; reset the cursor (tmux's prefix-[ + C-a, or ghostel's toggle exiting). + +(defun cj/term--tmux-pane-in-copy-mode-p (pane-id) + "Return non-nil when tmux PANE-ID is currently displaying a mode. +tmux's `pane_in_mode' is 1 while a pane is in any mode; copy-mode is the only +mode this config enters. tmux failures are treated as nil." + (condition-case nil + (equal "1" (string-trim + (cj/term--tmux-output + "display-message" "-p" "-t" pane-id "#{pane_in_mode}"))) + (error nil))) + +(defun cj/term-copy-mode-up () + "Enter copy-mode if needed, then scroll up one line. +A single C-<up> lands in the terminal's copy-mode already moving up. Pressed +again while already in copy-mode it just moves up another line, so it never +re-enters and resets the cursor. In tmux, writes the up-arrow escape sequence +into the pty; without tmux, moves point up in the `ghostel-copy-mode' buffer." + (interactive) + (let ((pane (ignore-errors (cj/term--current-tmux-pane-id)))) + (cond + (pane + (unless (cj/term--tmux-pane-in-copy-mode-p pane) + (cj/term-copy-mode-dwim)) + (ghostel-send-string "\e[A")) + (t + (unless (eq (bound-and-true-p ghostel--input-mode) 'copy) + (cj/term-copy-mode-dwim)) + (forward-line -1))))) + ;; ----------------------------- ghostel package ------------------------------- (defun cj/turn-off-chrome-for-term () @@ -226,6 +272,15 @@ run its own project-named tmux session instead of a bare, auto-named one. (ghostel-send-string "tmux\n")))) (use-package ghostel + ;; PINNED at module 0.33.0 (ghostel-20260604.2049, the last pre-rework June-4 + ;; build), installed directly into elpa/ rather than from MELPA. The 0.35.0-0.35.2 + ;; native-PTY rework (worker threads + mutex-outside-read-loop) hard-crashes the + ;; whole Emacs process when a ghostel buffer is displayed: on Linux/glibc a + ;; SIGSETXID handler calls malloc while the main thread holds the arena lock + ;; (ghostel upstream #422); on macOS a recursive os_unfair_lock via + ;; run_window_change_functions (#423). `:ensure t' is satisfied by the present + ;; 0.33.0 dir and will NOT upgrade it -- do NOT `package-upgrade' ghostel until + ;; #422/#423 are fixed upstream, or it returns to the crashing 0.35.x. :ensure t :commands (ghostel) :init @@ -236,15 +291,20 @@ run its own project-named tmux session instead of a bare, auto-named one. ;; `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' / the ;; global map. C-; and F12 are the prefix + toggle; the modified arrows are - ;; windmove (S-arrows, focus) and buffer-move (C-M-arrows, swap), which the - ;; ai-term workflow expects to work from inside an agent buffer. F8, F10 and - ;; C-F10 are global bindings (org agenda, music-playlist toggle, server - ;; shutdown) that reach Emacs by falling through to the global map once the - ;; semi-char map stops forwarding them. + ;; windmove (S-arrows, focus), buffer-move (C-M-arrows, swap), and copy-mode + ;; entry (C-<up> only, via `cj/term-copy-mode-up'), which the ai-term workflow + ;; expects to work from inside an agent buffer. C-<left>/<right> deliberately + ;; stay forwarding so readline word-motion works at the shell prompt. F8 and + ;; F10 are global bindings (org agenda, music-playlist toggle) that reach + ;; Emacs by falling through to the global map once the semi-char map stops + ;; forwarding them. (Server shutdown moved off C-F10 to C-x C, which is + ;; deliberately left forwarding to the terminal program inside an agent + ;; buffer.) (with-eval-after-load 'ghostel - (dolist (key '("C-;" "<f8>" "<f12>" "<f10>" "C-<f10>" + (dolist (key '("C-;" "<f8>" "<f12>" "<f10>" "S-<up>" "S-<down>" "S-<left>" "S-<right>" - "C-M-<up>" "C-M-<down>" "C-M-<left>" "C-M-<right>")) + "C-M-<up>" "C-M-<down>" "C-M-<left>" "C-M-<right>" + "C-<up>")) (add-to-list 'ghostel-keymap-exceptions key)) (ghostel--rebuild-semi-char-keymap)) :hook @@ -252,46 +312,95 @@ run its own project-named tmux session instead of a bare, auto-named one. (ghostel-mode . cj/term-launch-tmux)) :custom (ghostel-kill-buffer-on-exit t) + ;; Auto-download the prebuilt native module on first launch instead of the + ;; default `ask' prompt -- it fetches the platform release asset from GitHub + ;; (for the pinned 0.33.0 source this resolves to the matching v0.33.0 module). + ;; The compile-from-source fallback also works here: zig 0.15.2 is installed at + ;; /usr/local/bin/zig (see M-x ghostel-module-compile). + (ghostel-module-auto-install 'download) ;; 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." + "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 body size for the F12 terminal display. -Positive integer: body-cols (right/left) or body-lines (below/above). + "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 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." @@ -306,9 +415,10 @@ FRAME defaults to the selected frame. Minibuffer is excluded." (defun cj/--term-toggle-capture-state (window) "Capture WINDOW's direction + body size into module-level state. -Default direction is `below' to match F12's traditional bottom split." +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 'below + window (cj/--term-toggle-default-direction) 'cj/--term-toggle-last-direction 'cj/--term-toggle-last-size '(right below left))) @@ -316,11 +426,13 @@ Default direction is `below' to match F12's traditional bottom split." (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 `below' and `cj/term-toggle-window-height'." - (cj/window-toggle-display-saved - buffer alist - 'cj/--term-toggle-last-direction 'below - 'cj/--term-toggle-last-size cj/term-toggle-window-height)) +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. @@ -352,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) @@ -378,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) @@ -408,12 +527,13 @@ Forwarding NUL makes C-Space behave like a terminal key." (defun cj/term-install-keys () "Make `C-;' resolve as the personal keymap inside ghostel buffers, bind the -F12 toggle, and forward C-SPC so it reaches the terminal (see -`cj/term-send-C-SPC')." +F12 toggle, forward C-SPC so it reaches the terminal (see +`cj/term-send-C-SPC'), and bind C-<up> to enter copy-mode and scroll up." (when (boundp 'ghostel-mode-map) (keymap-set ghostel-mode-map "C-;" cj/custom-keymap) (keymap-set ghostel-mode-map "<f12>" #'cj/term-toggle) - (keymap-set ghostel-mode-map "C-SPC" #'cj/term-send-C-SPC))) + (keymap-set ghostel-mode-map "C-SPC" #'cj/term-send-C-SPC) + (keymap-set ghostel-mode-map "C-<up>" #'cj/term-copy-mode-up))) (cj/term-install-keys) (with-eval-after-load 'ghostel |
