aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/ai-term.el87
-rw-r--r--modules/auto-dim-config.el9
-rw-r--r--modules/dashboard-config.el4
-rw-r--r--modules/eat-config.el (renamed from modules/term-config.el)610
-rw-r--r--modules/eshell-config.el100
-rw-r--r--modules/face-diagnostic.el2
-rw-r--r--modules/weather-config.el11
7 files changed, 365 insertions, 458 deletions
diff --git a/modules/ai-term.el b/modules/ai-term.el
index b463da90b..3beabe6b5 100644
--- a/modules/ai-term.el
+++ b/modules/ai-term.el
@@ -81,16 +81,12 @@
(require 'host-environment)
(require 'keybindings) ;; provides cj/register-prefix-map (C-; a)
-(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)
+(declare-function eat "eat" (&optional program arg))
+(defvar eat-buffer-name)
+(defvar eat-semi-char-mode-map)
(defgroup ai-term nil
- "In-Emacs AI-agent launcher with a vertical-split ghostel terminal."
+ "In-Emacs AI-agent launcher with a vertical-split EAT terminal."
:group 'tools)
(defcustom cj/ai-term-agent-command
@@ -102,15 +98,6 @@ agent you run (aider, an open-source LLM TUI, etc.)."
:type 'string
:group 'ai-term)
-(defvar cj/--ai-term-suppress-tmux nil
- "When non-nil, the generic ghostel tmux-launch hook skips its auto-tmux step.
-
-ai-term dynamically binds this around `(ghostel)' so the hook in
-term-config.el doesn't send a bare \"tmux\\n\" before the named
-session launch command runs. The hook reads the variable via
-`bound-and-true-p' so loading order between the two modules doesn't
-matter.")
-
(defcustom cj/ai-term-project-roots
(list (expand-file-name "~/.emacs.d"))
"Directories that are themselves AI-agent projects.
@@ -669,19 +656,26 @@ split) when the user is focused in agent and switches projects."
(dolist (entry (cj/--ai-term-display-rule-list))
(add-to-list 'display-buffer-alist entry))
+(defun cj/--ai-term-send-string (buffer string)
+ "Send STRING to BUFFER's terminal process (the agent's shell).
+Sends to the pty directly so the launch command reaches the shell EAT runs."
+ (let ((proc (get-buffer-process buffer)))
+ (when (process-live-p proc)
+ (process-send-string proc string))))
+
(defun cj/--ai-term-show-or-create (dir name)
"Show or create the AI-term buffer for project DIR with buffer NAME.
If a buffer named NAME exists with a live process, display it. If
the buffer exists but its process is dead, kill it and recreate. If
-no such buffer exists, create a new ghostel terminal in DIR and send
+no such buffer exists, create a new EAT terminal in DIR and send
the project's tmux launch command (see `cj/--ai-term-launch-command') so
the same project basename reattaches across Emacs restarts.
-The dynamic binding of `cj/--ai-term-suppress-tmux' around `(ghostel)'
-suppresses the generic tmux-launch hook in term-config.el so
-it doesn't fire a bare \"tmux\\n\" before the project-named launch
-command runs.
+EAT runs a plain shell with no auto-tmux hook, so the named
+`tmux new-session -A' launch command is the only thing that starts the
+session -- the spike confirmed EAT + tmux detach and reattach exactly
+like ghostel + tmux did.
Records DIR in `cj/--ai-term-mru' (whichever branch runs) so the
project picker can list recently-opened projects first. Returns the
@@ -695,28 +689,22 @@ buffer."
(t
(when existing
(kill-buffer existing))
- ;; `ghostel' switches to its buffer in the selected window before our
+ ;; `eat' switches to its buffer in the selected window before our
;; display-buffer-alist rule can route it; `save-window-excursion'
;; reverts that, and the explicit display-buffer below routes the buffer
- ;; through the alist into the agent slot. `ghostel-buffer-name' is bound
- ;; to NAME so the terminal is created under the agent name, and
- ;; `ghostel-buffer-name-function' is pinned nil (dynamically during
- ;; creation, then buffer-locally) so OSC title escapes from the agent
- ;; don't rename it out from under the "agent [" prefix that buffer
- ;; detection and the display rule key on.
+ ;; through the alist into the agent slot. `eat-buffer-name' is bound to
+ ;; NAME so the terminal is created under the agent name; EAT (unlike
+ ;; ghostel) does not rename the buffer from the terminal's OSC title, so
+ ;; the "agent [" prefix that buffer detection and the display rule key on
+ ;; stays put.
(save-window-excursion
(let ((default-directory dir)
- (ghostel-buffer-name name)
- (ghostel-buffer-name-function nil)
- (cj/--ai-term-suppress-tmux t))
- (let ((buf (ghostel)))
- (when (buffer-live-p buf)
- (with-current-buffer buf
- (setq-local ghostel-buffer-name-function nil))))))
+ (eat-buffer-name name))
+ (eat)))
(let ((buf (get-buffer name)))
(with-current-buffer buf
- (ghostel-send-string (cj/--ai-term-launch-command dir))
- (ghostel-send-string "\n"))
+ (cj/--ai-term-send-string
+ buf (concat (cj/--ai-term-launch-command dir) "\n")))
(display-buffer buf)
buf)))))
@@ -818,7 +806,7 @@ without firing real `display-buffer' or `quit-window' calls."
(t '(pick-project))))))))
(defun cj/ai-term-pick-project (&optional arg)
- "Pick an AI-agent project and open or reuse its ghostel terminal.
+ "Pick an AI-agent project and open or reuse its EAT terminal.
The project is picked from a filtered completing-read list of dirs
that contain .ai/protocols.org. The terminal buffer is named
@@ -831,8 +819,8 @@ With prefix ARG, display the buffer without selecting its window.
Bound to C-F9 -- always shows the project picker, even when an agent
buffer is currently displayed.
-ghostel renders in terminal frames as well as GUI frames, so this
-launches from either (only kitty inline-graphics degrade in a TTY)."
+EAT renders in terminal frames as well as GUI frames, so this
+launches from either."
(interactive "P")
(let* ((dir (cj/--ai-term-pick-project))
(name (cj/--ai-term-buffer-name dir))
@@ -1067,16 +1055,13 @@ picker and C-; a k closes an agent."
"C-; a k" "kill agent"
"M-SPC" "ai-term: next agent"))
-;; In ghostel's semi-char mode, keys not in `ghostel-keymap-exceptions' are
-;; forwarded to the pty, and `ghostel-semi-char-mode-map' outranks the major
-;; mode map. M-SPC (swap to the next agent) must reach Emacs from inside an
-;; agent buffer, so add it to the exceptions, rebuild the semi-char map, and
-;; bind it in `ghostel-mode-map'. C-; is already an exception (term-config),
-;; so the C-; a family resolves through the global prefix without extra wiring.
-(with-eval-after-load 'ghostel
- (keymap-set ghostel-mode-map "M-SPC" #'cj/ai-term-next)
- (add-to-list 'ghostel-keymap-exceptions "M-SPC")
- (ghostel--rebuild-semi-char-keymap))
+;; In EAT's semi-char mode, keys not bound in `eat-semi-char-mode-map' are
+;; forwarded to the pty. M-SPC (swap to the next agent) must reach Emacs from
+;; inside an agent buffer, so bind it in that map -- no exception-list or rebuild
+;; dance like ghostel needed. C-; is already bound there (eat-config), so the
+;; C-; a family resolves through the global prefix without extra wiring.
+(with-eval-after-load 'eat
+ (keymap-set eat-semi-char-mode-map "M-SPC" #'cj/ai-term-next))
;; ------------------- Wrap-it-up teardown + shutdown -------------------------
;;
diff --git a/modules/auto-dim-config.el b/modules/auto-dim-config.el
index a143f8fe0..efae5341b 100644
--- a/modules/auto-dim-config.el
+++ b/modules/auto-dim-config.el
@@ -19,11 +19,10 @@
;; auto-dim-other-buffers-hide) live in the active theme (the generated
;; theme-studio theme) so they track theme switches.
;;
-;; Terminal buffers (ghostel) do not participate in window dimming: ghostel
-;; bakes its color palette into the native module per-terminal, not per-window,
-;; so there is no per-window color hook to dim through (the vterm engine had
-;; one via `vterm--get-color', which this module used to advise). See the
-;; terminal-migration follow-up task in todo.org for revisiting this.
+;; EAT terminals render in real Emacs faces and use the `default' face for the
+;; terminal background, so -- unlike the old ghostel/vterm engines, which baked
+;; color per-terminal with no per-window hook -- they follow the per-window
+;; dimmed background like any other buffer.
;;; Code:
diff --git a/modules/dashboard-config.el b/modules/dashboard-config.el
index 96aaaf6a1..17a0e2c4a 100644
--- a/modules/dashboard-config.el
+++ b/modules/dashboard-config.el
@@ -21,7 +21,7 @@
(eval-when-compile (require 'undead-buffers))
(declare-function cj/make-buffer-undead "undead-buffers" (string))
(autoload 'cj/make-buffer-undead "undead-buffers" nil t)
-(declare-function ghostel "ghostel" (&optional arg))
+(declare-function cj/term-toggle "eat-config")
;; ------------------------------ Declarations -------------------------------
;; These functions and variables belong to lazily-loaded packages or to other
@@ -137,7 +137,7 @@ Adjust this if the title doesn't appear centered under the banner image.")
(list
(list "c" #'nerd-icons-faicon "nf-fa-code" "Code" "Switch Project" (lambda () (projectile-switch-project)))
(list "d" #'nerd-icons-faicon "nf-fa-folder_o" "Files" "Dirvish File Manager" (lambda () (dirvish user-home-dir)))
- (list "t" #'nerd-icons-devicon "nf-dev-terminal" "Terminal" "Launch Terminal" (lambda () (ghostel)))
+ (list "t" #'nerd-icons-devicon "nf-dev-terminal" "Terminal" "Launch Terminal" (lambda () (cj/term-toggle)))
(list "a" #'nerd-icons-mdicon "nf-md-calendar" "Agenda" "Main Org Agenda" (lambda () (cj/main-agenda-display)))
(list "r" #'nerd-icons-faicon "nf-fa-rss_square" "Feeds" "Elfeed Feed Reader" (lambda () (cj/elfeed-open)))
(list "b" #'nerd-icons-codicon "nf-cod-library" "Books" "Calibre Ebook Reader" (lambda () (calibredb)))
diff --git a/modules/term-config.el b/modules/eat-config.el
index 474a85c42..d08fb91e6 100644
--- a/modules/term-config.el
+++ b/modules/eat-config.el
@@ -1,338 +1,67 @@
-;;; term-config.el --- Settings for ghostel and the F12 toggle -*- lexical-binding: t; coding: utf-8; -*-
-;; author Craig Jennings <c@cjennings.net>
+;;; eat-config.el --- EAT terminal emulator and the F12 eshell toggle -*- lexical-binding: t; coding: utf-8; -*-
;;; Commentary:
;;
-;; Layer: 3 (Domain Workflow).
-;; Category: D/P.
-;; Load shape: eager.
-;; Eager reason: registers terminal keymaps and the F12 toggle.
-;; Top-level side effects: defines two keymaps (one under cj/custom-keymap), one
-;; global key, two add-hook, package config.
-;; Runtime requires: keybindings, seq, subr-x, cj-window-geometry-lib,
-;; cj-window-toggle-lib.
-;; Direct test load: yes (requires keybindings explicitly).
+;; 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.
;;
-;; GHOSTEL
-;; ghostel is a native Emacs terminal emulator over libghostty-vt (the Ghostty
-;; engine). Like a real terminal, in its default semi-char mode most keys are
-;; sent to the running program; `ghostel-keymap-exceptions' lists the keys that
-;; reach Emacs instead. We add C-; so the personal prefix keymap works inside
-;; ghostel buffers.
+;; 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 module degrades gracefully when ghostel is unavailable (D6 of the
-;; migration spec): the package installs via use-package, the native module
-;; auto-downloads on first use, and ghostel emits its own warning if the module
-;; cannot load. A machine without a prebuilt binary needs Zig to build it; the
-;; terminal commands stay defined either way.
-;;
-;; Two ways to lift text out of a terminal, both with the same key story:
-;; - C-; x c enters copy-mode via `cj/term-copy-mode-dwim'. When a tmux
-;; client is attached (typical -- `cj/term-launch-tmux' auto-starts tmux),
-;; sends tmux's prefix C-b [ then C-a, so the user lands in tmux's own
-;; copy-mode with the full pane history and the cursor at column 0 (so
-;; scrolling up runs up the left, not the right). Without tmux, falls back to
-;; `ghostel-copy-mode' (read-only standard-Emacs navigation over the
-;; scrollback; M-w copies and stays, q / C-g exit) and moves point to the
-;; start of the line for the same column-0 reason.
-;; - C-; x h captures the current tmux pane's full history into a temporary
-;; Emacs buffer.
-;; In both copy surfaces, M-w copies the active region and stays open so several
-;; pieces can be grabbed in a row; C-g / q leave without copying.
+;; 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 'keybindings)
-(require 'seq)
-(require 'subr-x)
(require 'cj-window-geometry-lib)
(require 'cj-window-toggle-lib)
-(declare-function ghostel "ghostel" (&optional directory))
-(declare-function ghostel-send-string "ghostel" (string))
-(declare-function ghostel-copy-mode "ghostel" ())
-(declare-function ghostel-clear-scrollback "ghostel" ())
-(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)
-(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)
+(declare-function eshell "eshell" (&optional arg))
(defvar eat-mode-map)
(defvar eat-semi-char-mode-map)
+(defvar eshell-buffer-name)
(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.
-(cj/register-prefix-map "x" cj/term-map)
-
-;; ----------------------------- tmux history ----------------------------------
-
-(defvar-local cj/term-tmux-history--origin-buffer nil
- "Buffer active before opening the tmux history buffer.")
-
-(defvar-local cj/term-tmux-history--origin-window nil
- "Window active before opening the tmux history buffer.")
-
-(defvar-local cj/term-tmux-history--origin-point nil
- "Point in the origin buffer before opening the tmux history buffer.")
-
-(defun cj/term--tmux-output (&rest args)
- "Run tmux with ARGS and return its stdout.
-Signal `user-error' when tmux exits with a non-zero status."
- (with-temp-buffer
- (let ((exit-code (apply #'process-file "tmux" nil t nil args)))
- (unless (zerop exit-code)
- (user-error "tmux failed: %s" (string-trim (buffer-string))))
- (buffer-string))))
-
-(defun cj/term--tmux-pane-id-for-tty (tty)
- "Return the tmux pane id for client TTY."
- (let* ((output (cj/term--tmux-output
- "list-clients" "-F" "#{client_tty}\t#{pane_id}"))
- (lines (split-string output "\n" t))
- (match (seq-find
- (lambda (line)
- (let ((fields (split-string line "\t")))
- (equal (car fields) tty)))
- lines)))
- (unless match
- (user-error "No tmux client found for terminal tty %s" tty))
- (cadr (split-string match "\t"))))
-
-(defun cj/term--tmux-capture-pane (pane-id)
- "Return full joined tmux history for PANE-ID."
- (cj/term--tmux-output
- "capture-pane" "-p" "-J" "-S" "-" "-E" "-" "-t" pane-id))
-
-(defun cj/term--current-tmux-pane-id ()
- "Return the tmux pane id for the current ghostel buffer."
- (unless (eq major-mode 'ghostel-mode)
- (user-error "Current buffer is not a ghostel buffer"))
- (let* ((proc (get-buffer-process (current-buffer)))
- (tty (and proc (process-tty-name proc))))
- (unless (and tty (not (string-empty-p tty)))
- (user-error "Could not determine terminal tty"))
- (cj/term--tmux-pane-id-for-tty tty)))
-
-(defvar-keymap cj/term-tmux-history-mode-map
- :doc "Keymap for `cj/term-tmux-history-mode'.
-M-w copies the active region without leaving the buffer; C-g, <escape>, or q
-returns to the terminal without copying. RET is left unbound."
- "M-w" #'kill-ring-save
- "C-g" #'cj/term-tmux-history-quit
- "<escape>" #'cj/term-tmux-history-quit
- "q" #'cj/term-tmux-history-quit)
-
-(define-derived-mode cj/term-tmux-history-mode special-mode "Tmux History"
- "Mode for copying captured tmux pane history with normal Emacs keys."
- (setq-local truncate-lines t)
- (goto-address-mode 1))
-
-(defun cj/term-tmux-history-quit ()
- "Quit tmux history and return to its origin buffer."
- (interactive)
- (let ((history-buffer (current-buffer))
- (origin-buffer cj/term-tmux-history--origin-buffer)
- (origin-window cj/term-tmux-history--origin-window)
- (origin-point cj/term-tmux-history--origin-point))
- (when (buffer-live-p origin-buffer)
- (if (window-live-p origin-window)
- (progn
- (set-window-buffer origin-window origin-buffer)
- (select-window origin-window))
- (pop-to-buffer origin-buffer))
- (with-current-buffer origin-buffer
- (when (integer-or-marker-p origin-point)
- (goto-char origin-point))))
- (when (buffer-live-p history-buffer)
- (kill-buffer history-buffer))))
-
-(defun cj/term-tmux-history ()
- "Open full tmux pane history in a temporary Emacs buffer.
-
-The history buffer uses normal Emacs navigation and selection. `M-w'
-copies the active region and stays open, so several pieces can be
-copied in a row; `q', `<escape>', or `C-g' returns point to the
-terminal buffer that launched it.
-
-The history view replaces the origin terminal buffer in the same window
-\(via `switch-to-buffer'), not a split or a popped-up window."
- (interactive)
- (let* ((origin-buffer (current-buffer))
- (origin-window (selected-window))
- (origin-point (point))
- (pane-id (cj/term--current-tmux-pane-id))
- (history (cj/term--tmux-capture-pane pane-id))
- (buffer (get-buffer-create
- (format "*terminal tmux history: %s*" (buffer-name origin-buffer)))))
- (with-current-buffer buffer
- (let ((inhibit-read-only t))
- (erase-buffer)
- (insert history))
- (cj/term-tmux-history-mode)
- (setq-local cj/term-tmux-history--origin-buffer origin-buffer)
- (setq-local cj/term-tmux-history--origin-window origin-window)
- (setq-local cj/term-tmux-history--origin-point origin-point)
- (goto-char (point-max)))
- (switch-to-buffer buffer)))
-
-;; ----------------------------- copy mode -------------------------------------
-
-(defun cj/term--in-tmux-p ()
- "Return non-nil when the current ghostel buffer has a tmux client attached.
-Errors from the pane-id lookup (not in ghostel-mode, no tty, no matching
-client, tmux not installed) are treated as nil so callers can use this as a
-cheap boolean predicate."
- (and (eq major-mode 'ghostel-mode)
- (condition-case _
- (and (cj/term--current-tmux-pane-id) t)
- (error nil))))
-
-(defun cj/term-copy-mode-dwim ()
- "Enter copy-mode using the engine appropriate to this terminal.
-
-When tmux is attached, write tmux's default prefix sequence (C-b [) into the
-pty so the user lands in tmux's copy-mode with the full pane history, then
-C-a to land the cursor at the start of the line. Without the trailing C-a
-the copy cursor inherits the live column (far right after a prompt) and
-scrolling up runs up the right edge; tmux's emacs copy-mode binds C-a to
-start-of-line, so column 0 makes it run up the left. Without tmux, falls
-through to `ghostel-copy-mode' (a read-only standard-Emacs view of the
-scrollback; M-w copies and stays, q / C-g exit), then moves point to the
-start of the line for the same column-0 reason."
- (interactive)
- (if (cj/term--in-tmux-p)
- (ghostel-send-string "\C-b[\C-a")
- (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 ()
"Turn off line numbers and hl-line in a terminal buffer."
(hl-line-mode -1)
(display-line-numbers-mode -1))
-(defun cj/term-launch-tmux ()
- "Auto-launch tmux in a ghostel buffer unless already inside tmux.
-
-Skipped when `cj/--ai-term-suppress-tmux' is non-nil so the AI-agent flow can
-run its own project-named tmux session instead of a bare, auto-named one.
-`bound-and-true-p' keeps this safe whether or not ai-term.el is loaded."
- (let ((proc (get-buffer-process (current-buffer))))
- (when (and proc
- (not (getenv "TMUX"))
- (not (bound-and-true-p cj/--ai-term-suppress-tmux)))
- (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
- ;; These keys 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' / the
- ;; global map. C-; and F12 are the prefix + toggle; the modified arrows are
- ;; 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>"
- "S-<up>" "S-<down>" "S-<left>" "S-<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
- ((ghostel-mode . cj/turn-off-chrome-for-term)
- (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)
:custom
- ;; Close the EAT buffer when its shell exits (mirrors ghostel-kill-buffer-on-exit).
+ ;; 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
@@ -392,18 +121,14 @@ 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 the EAT terminal F12 should manage.
+ "Return non-nil when BUFFER is an eshell terminal F12 should manage.
-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."
+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
- (or (eq major-mode 'eat-mode)
- (string-prefix-p (or (bound-and-true-p eat-buffer-name)
- "*eat*")
- (buffer-name buffer))))))
+ (derived-mode-p 'eshell-mode))))
(defun cj/--term-toggle-buffers ()
"Return live F12-managed terminal buffers in `buffer-list' (MRU) order."
@@ -467,17 +192,15 @@ Returns one of:
(t '(create-new))))))))
(defun cj/term-toggle ()
- "Toggle the EAT terminal buffer.
+ "Toggle the F12 eshell terminal (the primary `*eshell*', run through EAT).
-- 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.
+- 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.
-ai-term's ghostel agent buffers are managed separately via M-SPC, not F12."
+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)
@@ -492,10 +215,10 @@ ai-term's ghostel agent buffers are managed separately via M-SPC, not F12."
(when w (select-window w)))
buf)
(`(create-new)
- ;; Create the EAT buffer without stealing the layout, then display it
+ ;; 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 (eat))
- (let ((buf (get-buffer (or (bound-and-true-p eat-buffer-name) "*eat*"))))
+ (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)))
@@ -504,55 +227,200 @@ ai-term's ghostel agent buffers are managed separately via M-SPC, not F12."
(keymap-global-set "<f12>" #'cj/term-toggle)
-;; ----------------------------- prefix menu -----------------------------------
+;; ------------------- terminal copy mode + tmux history -----------------------
+;; Carried over from the ghostel era for the EAT agent terminals (ai-term).
+;; Agents run EAT over tmux, so copy-mode is tmux's own copy-mode -- the same UX
+;; ghostel-over-tmux had. C-<up> enters it and scrolls up in one stroke; C-; x c
+;; enters it via the menu, and C-; x h grabs the whole pane history into a buffer.
+
+(declare-function cj/register-prefix-map "keybindings")
+(declare-function eat-emacs-mode "eat")
+(defvar eat--semi-char-mode)
+(defvar eat--char-mode)
+(defvar eat--line-mode)
+
+(defun cj/--term-send-string (string)
+ "Send STRING to the current terminal buffer's process (the pty)."
+ (let ((proc (get-buffer-process (current-buffer))))
+ (when (process-live-p proc)
+ (process-send-string proc string))))
+
+(defun cj/term--tmux-output (&rest args)
+ "Run tmux with ARGS and return its stdout.
+Signal `user-error' when tmux exits with a non-zero status."
+ (with-temp-buffer
+ (let ((exit-code (apply #'process-file "tmux" nil t nil args)))
+ (unless (zerop exit-code)
+ (user-error "tmux failed: %s" (string-trim (buffer-string))))
+ (buffer-string))))
+
+(defun cj/term--tmux-pane-id-for-tty (tty)
+ "Return the tmux pane id for client TTY."
+ (let* ((output (cj/term--tmux-output
+ "list-clients" "-F" "#{client_tty}\t#{pane_id}"))
+ (lines (split-string output "\n" t))
+ (match (seq-find
+ (lambda (line)
+ (let ((fields (split-string line "\t")))
+ (equal (car fields) tty)))
+ lines)))
+ (unless match
+ (user-error "No tmux client found for terminal tty %s" tty))
+ (cadr (split-string match "\t"))))
+
+(defun cj/term--tmux-capture-pane (pane-id)
+ "Return full joined tmux history for PANE-ID."
+ (cj/term--tmux-output
+ "capture-pane" "-p" "-J" "-S" "-" "-E" "-" "-t" pane-id))
+
+(defun cj/term--current-tmux-pane-id ()
+ "Return the tmux pane id for the current EAT terminal buffer."
+ (unless (derived-mode-p 'eat-mode)
+ (user-error "Current buffer is not an EAT terminal"))
+ (let* ((proc (get-buffer-process (current-buffer)))
+ (tty (and proc (process-tty-name proc))))
+ (unless (and tty (not (string-empty-p tty)))
+ (user-error "Could not determine terminal tty"))
+ (cj/term--tmux-pane-id-for-tty tty)))
+
+(defvar-local cj/term-tmux-history--origin-buffer nil
+ "Buffer active before opening the tmux history buffer.")
+(defvar-local cj/term-tmux-history--origin-window nil
+ "Window active before opening the tmux history buffer.")
+(defvar-local cj/term-tmux-history--origin-point nil
+ "Point in the origin buffer before opening the tmux history buffer.")
+
+(defun cj/term-tmux-history-quit ()
+ "Quit tmux history and return to its origin buffer."
+ (interactive)
+ (let ((history-buffer (current-buffer))
+ (origin-buffer cj/term-tmux-history--origin-buffer)
+ (origin-window cj/term-tmux-history--origin-window)
+ (origin-point cj/term-tmux-history--origin-point))
+ (when (buffer-live-p origin-buffer)
+ (if (window-live-p origin-window)
+ (progn
+ (set-window-buffer origin-window origin-buffer)
+ (select-window origin-window))
+ (pop-to-buffer origin-buffer))
+ (with-current-buffer origin-buffer
+ (when (integer-or-marker-p origin-point)
+ (goto-char origin-point))))
+ (when (buffer-live-p history-buffer)
+ (kill-buffer history-buffer))))
+
+(defvar-keymap cj/term-tmux-history-mode-map
+ :doc "Keymap for `cj/term-tmux-history-mode'.
+M-w copies the active region without leaving the buffer; C-g, <escape>, or q
+returns to the terminal without copying. RET is left unbound."
+ "M-w" #'kill-ring-save
+ "C-g" #'cj/term-tmux-history-quit
+ "<escape>" #'cj/term-tmux-history-quit
+ "q" #'cj/term-tmux-history-quit)
+
+(define-derived-mode cj/term-tmux-history-mode special-mode "Tmux History"
+ "Mode for copying captured tmux pane history with normal Emacs keys."
+ (setq-local truncate-lines t)
+ (goto-address-mode 1))
+
+(defun cj/term-tmux-history ()
+ "Open full tmux pane history in a temporary Emacs buffer.
+
+The history buffer uses normal Emacs navigation and selection. `M-w' copies
+the active region and stays open, so several pieces can be copied in a row;
+`q', `<escape>', or `C-g' returns point to the terminal buffer that launched
+it. The history view replaces the origin terminal buffer in the same window."
+ (interactive)
+ (let* ((origin-buffer (current-buffer))
+ (origin-window (selected-window))
+ (origin-point (point))
+ (pane-id (cj/term--current-tmux-pane-id))
+ (history (cj/term--tmux-capture-pane pane-id))
+ (buffer (get-buffer-create
+ (format "*terminal tmux history: %s*" (buffer-name origin-buffer)))))
+ (with-current-buffer buffer
+ (let ((inhibit-read-only t))
+ (erase-buffer)
+ (insert history))
+ (cj/term-tmux-history-mode)
+ (setq-local cj/term-tmux-history--origin-buffer origin-buffer)
+ (setq-local cj/term-tmux-history--origin-window origin-window)
+ (setq-local cj/term-tmux-history--origin-point origin-point)
+ (goto-char (point-max)))
+ (switch-to-buffer buffer)))
+
+(defun cj/term--in-tmux-p ()
+ "Return non-nil when the current EAT buffer has a tmux client attached.
+Lookup errors (not eat-mode, no tty, no client, tmux absent) are treated as
+nil so callers can use this as a cheap boolean predicate."
+ (and (derived-mode-p 'eat-mode)
+ (condition-case _
+ (and (cj/term--current-tmux-pane-id) t)
+ (error nil))))
+(defun cj/--term-in-emacs-mode-p ()
+ "Return non-nil when the current EAT buffer is in emacs (navigation) mode.
+EAT has no dedicated emacs-mode flag; emacs mode is the absence of the
+semi-char, char, and line input modes."
+ (and (derived-mode-p 'eat-mode)
+ (not (or (bound-and-true-p eat--semi-char-mode)
+ (bound-and-true-p eat--char-mode)
+ (bound-and-true-p eat--line-mode)))))
+
+(defun cj/term-copy-mode-dwim ()
+ "Enter copy-mode using the engine appropriate to this terminal.
+
+When tmux is attached (an agent terminal), write tmux's prefix sequence (C-b [)
+into the pty so the user lands in tmux's copy-mode with the full pane history,
+then C-a to land the cursor at column 0 so scrolling up runs up the left edge.
+Without tmux, falls through to EAT's emacs mode (a navigable view of the
+scrollback) and moves point to the start of the line."
+ (interactive)
+ (if (cj/term--in-tmux-p)
+ (cj/--term-send-string "\C-b[\C-a")
+ (eat-emacs-mode)
+ (beginning-of-line)))
+
+(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 into the
+pty; without tmux, moves point up in EAT's emacs-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))
+ (cj/--term-send-string "\e[A"))
+ (t
+ (unless (cj/--term-in-emacs-mode-p)
+ (cj/term-copy-mode-dwim))
+ (forward-line -1)))))
+
+;; The C-; x terminal prefix (copy-mode, tmux history, the F12 toggle). C-<up>
+;; enters copy-mode + scrolls in one stroke; bound in EAT's semi-char map so it
+;; reaches Emacs from inside an agent terminal.
+(defvar-keymap cj/term-map
+ :doc "Personal terminal command map.")
+(cj/register-prefix-map "x" cj/term-map)
(keymap-set cj/term-map "c" #'cj/term-copy-mode-dwim)
(keymap-set cj/term-map "h" #'cj/term-tmux-history)
-(keymap-set cj/term-map "l" #'ghostel-clear-scrollback)
-(keymap-set cj/term-map "N" #'ghostel)
-(keymap-set cj/term-map "n" #'ghostel-next-prompt)
-(keymap-set cj/term-map "p" #'ghostel-previous-prompt)
-(keymap-set cj/term-map "q" #'ghostel-send-next-key)
(keymap-set cj/term-map "t" #'cj/term-toggle)
-(defun cj/term-send-C-SPC ()
- "Forward C-SPC (NUL) to the terminal instead of setting an Emacs mark.
+(with-eval-after-load 'eat
+ (keymap-set eat-semi-char-mode-map "C-<up>" #'cj/term-copy-mode-up))
-ghostel forwards the `C-@' event but not the distinct `C-SPC' event GUI
-Emacs produces, so a bare C-SPC in a ghostel buffer falls through to the
-global `set-mark-command'. That sets an Emacs region in the terminal buffer
-that follows point as output streams (a stuck \"selection\" C-g / Escape
-can't clear) and, worse, never reaches tmux -- so tmux copy-mode's
-begin-selection (C-Space) never starts and M-w then copies nothing.
-Forwarding NUL makes C-Space behave like a terminal key."
- (interactive)
- (ghostel-send-string "\C-@"))
-
-(defun cj/term-install-keys ()
- "Make `C-;' resolve as the personal keymap inside ghostel buffers, bind the
-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-<up>" #'cj/term-copy-mode-up)))
-
-(cj/term-install-keys)
-(with-eval-after-load 'ghostel
- (cj/term-install-keys))
-
-(with-eval-after-load 'which-key
- (which-key-add-key-based-replacements
- "C-; x" "terminal menu"
- "C-; x c" "copy mode (tmux/ghostel)"
- "C-; x h" "tmux scrollback history"
- "C-; x l" "clear scrollback"
- "C-; x N" "new terminal"
- "C-; x n" "next prompt"
- "C-; x p" "previous prompt"
- "C-; x q" "send next key to terminal"
- "C-; x t" "toggle terminal"))
-
-(provide 'term-config)
-;;; term-config.el ends here.
+(provide 'eat-config)
+;;; eat-config.el ends here
diff --git a/modules/eshell-config.el b/modules/eshell-config.el
index c2ec6d152..7379795d2 100644
--- a/modules/eshell-config.el
+++ b/modules/eshell-config.el
@@ -51,6 +51,9 @@
(declare-function eshell-send-input "esh-mode")
(declare-function eshell/pwd "em-dirs")
(declare-function eshell/alias "em-alias")
+(declare-function eshell/cd "em-dirs")
+(declare-function eshell-stringify "esh-util")
+(declare-function eat-eshell-mode "eat")
(defgroup cj/eshell nil
"Personal Eshell configuration."
@@ -83,6 +86,59 @@ pairs where COMMAND is the `cd' string `eshell/alias' should run."
(dolist (pair (cj/--eshell-ssh-alias-commands hosts))
(eshell/alias (car pair) (cdr pair))))
+;; ---------------------------- prompt segments --------------------------------
+
+(defun cj/--eshell-git-branch ()
+ "Return the current git branch for `default-directory', or nil.
+Reads .git/HEAD directly so it adds no subprocess per prompt, and skips remote
+directories so a TRAMP prompt stays fast."
+ (unless (file-remote-p default-directory)
+ (when-let* ((root (locate-dominating-file default-directory ".git"))
+ (head (expand-file-name ".git/HEAD" root)))
+ (when (file-readable-p head)
+ (with-temp-buffer
+ (insert-file-contents head)
+ (when (looking-at "ref: refs/heads/\\(.*\\)")
+ (string-trim (match-string 1))))))))
+
+(defun cj/--eshell-prompt-status-segment ()
+ "Return the eshell prompt's exit-status segment, or an empty string.
+Shows the last command's exit code in brackets when it was non-zero, mirroring
+the zsh prompt's failure indicator."
+ (let ((status (bound-and-true-p eshell-last-command-status)))
+ (if (or (null status) (zerop status))
+ ""
+ (format " [%d]" status))))
+
+;; ------------------------------- zoxide --------------------------------------
+;; Share the same frecency database as the zsh shell by calling the zoxide
+;; binary: `z' jumps to a remembered directory, and every eshell directory
+;; change feeds `zoxide add' so eshell visits accrue in the same database.
+
+(defun eshell/z (&rest args)
+ "Jump to a directory via zoxide, sharing the zsh zoxide database.
+With no ARGS, cd home. Otherwise query zoxide for the best match and cd there."
+ (if (null args)
+ (eshell/cd)
+ (let ((dir (string-trim
+ (shell-command-to-string
+ (concat "zoxide query -- "
+ (mapconcat #'shell-quote-argument
+ (mapcar #'eshell-stringify args) " "))))))
+ (if (and (not (string-empty-p dir)) (file-directory-p dir))
+ (eshell/cd dir)
+ (error "zoxide: no match for %s"
+ (string-join (mapcar #'eshell-stringify args) " "))))))
+
+(defun cj/--eshell-zoxide-add ()
+ "Record `default-directory' in the zoxide database (skips remote dirs)."
+ (when (and (not (file-remote-p default-directory))
+ (executable-find "zoxide"))
+ (call-process "zoxide" nil 0 nil "add" "--"
+ (expand-file-name default-directory))))
+
+(add-hook 'eshell-directory-change-hook #'cj/--eshell-zoxide-add)
+
(use-package eshell
:ensure nil ;; built-in
:commands (eshell)
@@ -108,6 +164,9 @@ pairs where COMMAND is the `cd' string `eshell/alias' should run."
(propertize (system-name) 'face 'default)
":"
(propertize (abbreviate-file-name (eshell/pwd)) 'face 'default)
+ (let ((branch (cj/--eshell-git-branch)))
+ (if branch (propertize (concat " (" branch ")") 'face 'default) ""))
+ (propertize (cj/--eshell-prompt-status-segment) 'face 'default)
"\n"
(propertize "%" 'face 'default)
" ")))
@@ -179,35 +238,20 @@ pairs where COMMAND is the `cd' string `eshell/alias' should run."
(delete-window)))
(advice-add 'eshell-life-is-too-much :after 'cj/eshell-delete-window-on-exit)
-(use-package eshell-toggle
- :custom
- (eshell-toggle-size-fraction 2)
- (eshell-toggle-run-command nil)
- (eshell-toggle-init-function #'eshell-toggle-init-eshell)
- :bind
- ("C-<f12>" . eshell-toggle))
+;; Run eshell's external commands through EAT (a real terminal): visual commands
+;; (vim, htop, less) render properly and ANSI output is faithful, while eshell
+;; stays the shell -- elisp functions as commands + TRAMP transparency. EAT
+;; handles color itself, so it supersedes xterm-color for eshell; the
+;; xterm-color block below stays for now and steps aside if colors double up.
+(with-eval-after-load 'esh-mode
+ (require 'eat)
+ (eat-eshell-mode 1))
-(use-package xterm-color
- :after eshell
- ;; Two hooks. eshell-before-prompt is the real hook name; use-package appends
- ;; "-hook", so writing eshell-before-prompt-hook here registered on a
- ;; nonexistent eshell-before-prompt-hook-hook and never ran. The eshell-mode
- ;; hook scopes TERM=xterm-256color to eshell-spawned processes only (a global
- ;; setenv would leak it to every start-process regardless of terminal).
- :hook
- ((eshell-before-prompt . (lambda ()
- (setq xterm-color-preserve-properties t)))
- (eshell-mode . (lambda ()
- (setq-local process-environment
- (cons "TERM=xterm-256color"
- process-environment)))))
- :config
- ;; Wire xterm-color into eshell's output pipeline (per its README): install
- ;; the filter and drop eshell's own ANSI handler. Without this the escapes are
- ;; never interpreted and TERM=xterm-256color only leaks raw codes.
- (add-to-list 'eshell-preoutput-filter-functions 'xterm-color-filter)
- (setq eshell-output-filter-functions
- (remove 'eshell-handle-ansi-color eshell-output-filter-functions)))
+;; eshell-toggle and xterm-color are retired. F12 opens eshell now (the
+;; dock-and-remember toggle in eat-config.el), and eat-eshell-mode renders
+;; eshell's output through EAT, which handles ANSI color natively -- so
+;; xterm-color's filter and its TERM=xterm-256color override are redundant and
+;; would fight EAT's own TERM=eat-truecolor.
(use-package eshell-syntax-highlighting
:after esh-mode
diff --git a/modules/face-diagnostic.el b/modules/face-diagnostic.el
index a2bfe2483..6f0722099 100644
--- a/modules/face-diagnostic.el
+++ b/modules/face-diagnostic.el
@@ -36,7 +36,7 @@ Return one of `theme-faced', `terminal-ansi', `document-shr', or
best-effort dump rather than a full provenance trace."
(with-current-buffer (or buffer (current-buffer))
(cond
- ((derived-mode-p 'term-mode 'comint-mode 'eshell-mode 'ghostel-mode)
+ ((derived-mode-p 'term-mode 'comint-mode 'eshell-mode 'eat-mode)
'terminal-ansi)
((derived-mode-p 'eww-mode 'nov-mode 'elfeed-show-mode 'mu4e-view-mode)
'document-shr)
diff --git a/modules/weather-config.el b/modules/weather-config.el
index 93b0a6148..04531350f 100644
--- a/modules/weather-config.el
+++ b/modules/weather-config.el
@@ -32,7 +32,18 @@
("M-S-w" . wttrin) ;; was M-W, overrides kill-ring-save
:config
(setopt wttrin-unit-system "u")
+ ;; Drop the "Follow @igor_chubin for wttr.in updates" footer. "F" is the
+ ;; wttr.in flag for "no Follow line"; everything else (forecast, header,
+ ;; colors) is unchanged.
+ (setopt wttrin-display-options "F")
(setopt wttrin-favorite-location "New Orleans, LA")
+ ;; Higher-accuracy geolocation via the whereami WiFi-scan script (Google-backed),
+ ;; far better than IP behind a VPN or cellular hotspot. Used by the picker's
+ ;; "Current location (detect)" entry; wttrin falls back to its IP provider if the
+ ;; command is missing or fails. setq (not setopt): wttrin-geolocation-command is
+ ;; defined in the lazily-loaded wttrin-geolocation sub-module, so it may be unbound
+ ;; at :config time; the later defcustom won't clobber an already-set value.
+ (setq wttrin-geolocation-command "/home/cjennings/.local/bin/whereami --json")
(setopt wttrin-mode-line-refresh-interval (* 30 60)) ;; thirty minutes
(setq wttrin-default-locations '(
"New Orleans, LA"