aboutsummaryrefslogtreecommitdiff
path: root/tests/test-vterm-tmux-history.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-05 05:28:58 -0500
committerCraig Jennings <c@cjennings.net>2026-06-05 05:28:58 -0500
commit25da8650f12a90e622aa804d604a02e39f1867ef (patch)
tree2a4996867b3e99603821986b4798d2378327b45c /tests/test-vterm-tmux-history.el
parent7cd8cd8c1f163cd9cc9f377557bf44b788be4bff (diff)
downloaddotemacs-25da8650f12a90e622aa804d604a02e39f1867ef.tar.gz
dotemacs-25da8650f12a90e622aa804d604a02e39f1867ef.zip
feat(term): replace vterm with ghostel as the terminal engine
I swapped the terminal engine from vterm to ghostel (libghostty-vt) everywhere. term-config replaces vterm-config (the F12 terminal, the C-; x menu, tmux history capture), and ai-term replaces ai-vterm (the F9 Claude-agent launcher). ghostel renders the agent TUI without vterm's flicker under heavy streaming, and one engine now covers every terminal workflow. Two behavior changes fall out of the swap. F9 launches in a terminal frame now: ghostel renders in TTY frames, so the old GUI-only guard is gone. Terminal windows no longer dim when unfocused: ghostel resolves its palette into the native module per-terminal, so there's no per-window color hook to dim through the way vterm had. auto-dim drops its vterm color-advice path, the dashboard Terminal button launches ghostel, and the vterm and vterm-toggle packages are removed. The tmux pane-history and copy-mode machinery carried over unchanged. It keys on the pty tty, which ghostel exposes.
Diffstat (limited to 'tests/test-vterm-tmux-history.el')
-rw-r--r--tests/test-vterm-tmux-history.el383
1 files changed, 0 insertions, 383 deletions
diff --git a/tests/test-vterm-tmux-history.el b/tests/test-vterm-tmux-history.el
deleted file mode 100644
index 88bd5593..00000000
--- a/tests/test-vterm-tmux-history.el
+++ /dev/null
@@ -1,383 +0,0 @@
-;;; test-vterm-tmux-history.el --- Tests for tmux history capture UX -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Exercises the Emacs-owned history buffer used to copy text from the
-;; current tmux pane without entering tmux copy-mode.
-
-;;; Code:
-
-(require 'ert)
-(require 'cl-lib)
-
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-(add-to-list 'load-path (expand-file-name "tests" user-emacs-directory))
-(setq load-prefer-newer t)
-(defvar vterm-mode-map (make-sparse-keymap))
-(defvar vterm-copy-mode-map (make-sparse-keymap))
-(keymap-set vterm-mode-map "C-c C-t" #'ignore)
-(require 'vterm-config)
-(require 'testutil-vterm-buffers)
-
-(defmacro test-vterm-tmux-history--with-tmux-mock (responses &rest body)
- "Run BODY with `process-file' mocked for tmux RESPONSES.
-
-RESPONSES is an alist of (ARGS EXIT-CODE OUTPUT)."
- (declare (indent 1))
- `(let ((calls nil))
- (cl-letf (((symbol-function 'process-file)
- (lambda (program _infile destination _display &rest args)
- (push (cons program args) calls)
- (let* ((entry (seq-find
- (lambda (candidate)
- (equal (car candidate) args))
- ,responses))
- (exit-code (or (cadr entry) 1))
- (output (or (caddr entry) "")))
- (when destination
- (let ((buffer (cond
- ((eq destination t) (current-buffer))
- ((bufferp destination) destination)
- ((consp destination) (car destination)))))
- (when (bufferp buffer)
- (with-current-buffer buffer
- (insert output)))))
- exit-code))))
- ,@body)))
-
-(ert-deftest test-vterm-tmux-history--pane-id-for-tty-matches-client ()
- "Normal: current vterm pty maps to the active pane for that tmux client."
- (test-vterm-tmux-history--with-tmux-mock
- '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0
- "/dev/pts/1\t%1\n/dev/pts/8\t%8\n"))
- (should (equal (cj/vterm--tmux-pane-id-for-tty "/dev/pts/8") "%8"))))
-
-(ert-deftest test-vterm-tmux-history--capture-pane-uses-full-history ()
- "Normal: capture asks tmux for joined full pane history."
- (test-vterm-tmux-history--with-tmux-mock
- '((("capture-pane" "-p" "-J" "-S" "-" "-E" "-" "-t" "%8") 0
- "first line\nsecond line\n"))
- (should (equal (cj/vterm--tmux-capture-pane "%8")
- "first line\nsecond line\n"))))
-
-(ert-deftest test-vterm-tmux-history-open-renders-read-only-history-buffer ()
- "Normal: command renders tmux history in a normal Emacs buffer."
- (let ((origin (cj/test--make-fake-vterm-buffer "*test-vterm-history-origin*")))
- (unwind-protect
- (save-window-excursion
- (switch-to-buffer origin)
- (cl-letf (((symbol-function 'get-buffer-process)
- (lambda (_buffer) 'fake-process))
- ((symbol-function 'process-tty-name)
- (lambda (_process) "/dev/pts/8")))
- (test-vterm-tmux-history--with-tmux-mock
- '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0
- "/dev/pts/8\t%8\n")
- (("capture-pane" "-p" "-J" "-S" "-" "-E" "-" "-t" "%8") 0
- "history http://example.com\n"))
- (cj/vterm-tmux-history)
- (should (eq major-mode 'cj/vterm-tmux-history-mode))
- (should buffer-read-only)
- (should (string-match-p "history http://example.com"
- (buffer-string))))))
- (cj/test--kill-buffers-matching-prefix "*vterm tmux history")
- (when (buffer-live-p origin)
- (kill-buffer origin)))))
-
-(ert-deftest test-vterm-tmux-history-replaces-origin-buffer-in-same-window ()
- "Normal: the history view replaces the origin in the selected window.
-
-Before the in-place change, `cj/vterm-tmux-history' used `pop-to-buffer'
-which could split or hand the buffer to a different window. The fix
-uses `switch-to-buffer' so reading scrollback keeps the agent's frame
-slot."
- (let ((origin (cj/test--make-fake-vterm-buffer "*test-vterm-history-inplace*")))
- (unwind-protect
- (save-window-excursion
- (delete-other-windows)
- (switch-to-buffer origin)
- (let ((win (selected-window)))
- (should (eq (window-buffer win) origin))
- (should (one-window-p))
- (cl-letf (((symbol-function 'get-buffer-process)
- (lambda (_buffer) 'fake-process))
- ((symbol-function 'process-tty-name)
- (lambda (_process) "/dev/pts/8")))
- (test-vterm-tmux-history--with-tmux-mock
- '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0
- "/dev/pts/8\t%8\n")
- (("capture-pane" "-p" "-J" "-S" "-" "-E" "-" "-t" "%8") 0
- "scrollback line\n"))
- (cj/vterm-tmux-history)))
- ;; Same window, no split, history buffer now in the slot.
- (should (one-window-p))
- (should (eq (selected-window) win))
- (should (string-prefix-p
- "*vterm tmux history:"
- (buffer-name (window-buffer win))))))
- (cj/test--kill-buffers-matching-prefix "*vterm tmux history")
- (when (buffer-live-p origin)
- (kill-buffer origin)))))
-
-(ert-deftest test-vterm-tmux-history-quit-returns-to-origin ()
- "Normal: q / <escape> / C-g (cj/vterm-tmux-history-quit) kills the history
-buffer and restores the origin buffer, window, and point."
- (let ((origin (get-buffer-create "*test-vterm-history-return*")))
- (unwind-protect
- (let ((history (get-buffer-create "*vterm tmux history: test*")))
- (with-current-buffer origin
- (erase-buffer)
- (insert "origin")
- (goto-char (point-min)))
- (switch-to-buffer origin)
- (let ((origin-window (selected-window)))
- (with-current-buffer history
- (cj/vterm-tmux-history-mode)
- (let ((inhibit-read-only t))
- (insert "alpha\nbeta\ngamma\n"))
- (setq-local cj/vterm-tmux-history--origin-buffer origin)
- (setq-local cj/vterm-tmux-history--origin-window origin-window)
- (setq-local cj/vterm-tmux-history--origin-point (point-min))
- (cj/vterm-tmux-history-quit))
- (should-not (buffer-live-p history))
- (should (eq (current-buffer) origin))
- (should (= (point) (point-min)))))
- (when (buffer-live-p origin)
- (kill-buffer origin)))))
-
-(ert-deftest test-vterm-tmux-history-mode-keymap ()
- "Normal: in the history buffer M-w copies without quitting; q, <escape>,
-and C-g quit back to the vterm; RET is left unbound (no special exit)."
- (should (eq (keymap-lookup cj/vterm-tmux-history-mode-map "M-w")
- #'kill-ring-save))
- (should (eq (keymap-lookup cj/vterm-tmux-history-mode-map "q")
- #'cj/vterm-tmux-history-quit))
- (should (eq (keymap-lookup cj/vterm-tmux-history-mode-map "<escape>")
- #'cj/vterm-tmux-history-quit))
- (should (eq (keymap-lookup cj/vterm-tmux-history-mode-map "C-g")
- #'cj/vterm-tmux-history-quit))
- (should-not (keymap-lookup cj/vterm-tmux-history-mode-map "RET")))
-
-(ert-deftest test-vterm-keymap-includes-history-and-copy-bindings ()
- "Normal: personal vterm map owns the high-level vterm UX commands.
-`C-; x c' resolves to `cj/vterm-copy-mode-dwim' so the binding can pick
-the right copy-mode engine (tmux when attached, vterm otherwise)."
- (should (member "C-;" vterm-keymap-exceptions))
- (should-not (eq (keymap-lookup cj/custom-keymap "X c") #'vterm-copy-mode))
- (should (eq (keymap-lookup cj/custom-keymap "x h") #'cj/vterm-tmux-history))
- (should (eq (keymap-lookup cj/custom-keymap "x c") #'cj/vterm-copy-mode-dwim))
- (should (equal (keymap-lookup vterm-mode-map "C-;") cj/custom-keymap))
- (should (eq (keymap-lookup vterm-mode-map "C-; x h") #'cj/vterm-tmux-history))
- (should (eq (keymap-lookup vterm-mode-map "C-; x c") #'cj/vterm-copy-mode-dwim))
- (should-not (keymap-lookup vterm-mode-map "C-c C-t")))
-
-(ert-deftest test-vterm-keymap-prompt-navigation ()
- "Normal: n/p navigate prompts, capital N creates a new vterm buffer."
- (should (eq (keymap-lookup cj/custom-keymap "x n") #'vterm-next-prompt))
- (should (eq (keymap-lookup cj/custom-keymap "x p") #'vterm-previous-prompt))
- (should (eq (keymap-lookup cj/custom-keymap "x N") #'vterm)))
-
-(ert-deftest test-vterm-pause-not-bound-to-copy-mode ()
- "Normal: <pause> is no longer wired as a vterm-copy-mode entry point.
-The personal `C-; x c' binding is the canonical entry; <pause> is rare on
-modern keyboards and was redundant."
- (let ((binding (keymap-lookup vterm-mode-map "<pause>")))
- (should-not (eq binding #'vterm-copy-mode))))
-
-(ert-deftest test-vterm-copy-mode-keys ()
- "Normal: copy mode mirrors the history buffer -- M-w copies without
-leaving; C-g, <escape>, and q leave without copying; RET is unbound."
- (should (eq (keymap-lookup vterm-copy-mode-map "M-w")
- #'kill-ring-save))
- (should (eq (keymap-lookup vterm-copy-mode-map "C-g")
- #'cj/vterm-copy-mode-cancel))
- (should (eq (keymap-lookup vterm-copy-mode-map "<escape>")
- #'cj/vterm-copy-mode-cancel))
- (should (eq (keymap-lookup vterm-copy-mode-map "q")
- #'cj/vterm-copy-mode-cancel))
- (should-not (keymap-lookup vterm-copy-mode-map "RET"))
- (should-not (keymap-lookup vterm-copy-mode-map "<return>")))
-
-(ert-deftest test-vterm-copy-mode-cancel-errors-outside-copy-mode ()
- "Error: `cj/vterm-copy-mode-cancel' refuses to run when not in copy mode."
- (with-temp-buffer
- (should-error (cj/vterm-copy-mode-cancel) :type 'user-error)))
-
-(ert-deftest test-vterm-current-tmux-pane-id-rejects-non-vterm-buffer ()
- "Error: pane-id lookup refuses a buffer that is not in `vterm-mode'."
- (with-temp-buffer
- (should-error (cj/vterm--current-tmux-pane-id) :type 'user-error)))
-
-(ert-deftest test-vterm-current-tmux-pane-id-accepts-ai-vterm-named-buffer ()
- "Normal: an AI-vterm-named buffer still resolves by process TTY.
-
-The copy path belongs to `vterm-mode', not to `*vterm*'-named buffers.
-A buffer named like `agent [repo]' (ai-vterm.el's naming) is a
-`vterm-mode' buffer and must inherit tmux history copy. The pane lookup
-keys off the live process TTY, never the buffer name -- so the
-AI-vterm name neither helps nor blocks resolution."
- (let ((agent (cj/test--make-fake-vterm-buffer "agent [emacs.d]")))
- (unwind-protect
- (with-current-buffer agent
- (cl-letf (((symbol-function 'get-buffer-process)
- (lambda (_buffer) 'fake-process))
- ((symbol-function 'process-tty-name)
- (lambda (_process) "/dev/pts/8")))
- (test-vterm-tmux-history--with-tmux-mock
- '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0
- "/dev/pts/1\t%1\n/dev/pts/8\t%8\n"))
- (should (equal (cj/vterm--current-tmux-pane-id) "%8")))))
- (when (buffer-live-p agent)
- (kill-buffer agent)))))
-
-(ert-deftest test-vterm-in-tmux-p-true-when-client-attached ()
- "Normal: predicate returns t when tmux reports a client for our tty."
- (let ((agent (cj/test--make-fake-vterm-buffer "agent [emacs.d]")))
- (unwind-protect
- (with-current-buffer agent
- (cl-letf (((symbol-function 'get-buffer-process)
- (lambda (_buffer) 'fake-process))
- ((symbol-function 'process-tty-name)
- (lambda (_process) "/dev/pts/8")))
- (test-vterm-tmux-history--with-tmux-mock
- '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0
- "/dev/pts/8\t%8\n"))
- (should (cj/vterm--in-tmux-p)))))
- (when (buffer-live-p agent)
- (kill-buffer agent)))))
-
-(ert-deftest test-vterm-in-tmux-p-nil-when-no-matching-client ()
- "Boundary: predicate returns nil when tmux runs but our tty has no client."
- (let ((agent (cj/test--make-fake-vterm-buffer "agent [emacs.d]")))
- (unwind-protect
- (with-current-buffer agent
- (cl-letf (((symbol-function 'get-buffer-process)
- (lambda (_buffer) 'fake-process))
- ((symbol-function 'process-tty-name)
- (lambda (_process) "/dev/pts/8")))
- (test-vterm-tmux-history--with-tmux-mock
- '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0
- "/dev/pts/1\t%1\n"))
- (should-not (cj/vterm--in-tmux-p)))))
- (when (buffer-live-p agent)
- (kill-buffer agent)))))
-
-(ert-deftest test-vterm-in-tmux-p-nil-when-tmux-fails ()
- "Error: predicate swallows tmux failures and returns nil."
- (let ((agent (cj/test--make-fake-vterm-buffer "agent [emacs.d]")))
- (unwind-protect
- (with-current-buffer agent
- (cl-letf (((symbol-function 'get-buffer-process)
- (lambda (_buffer) 'fake-process))
- ((symbol-function 'process-tty-name)
- (lambda (_process) "/dev/pts/8")))
- (test-vterm-tmux-history--with-tmux-mock
- '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 1
- "no server running"))
- (should-not (cj/vterm--in-tmux-p)))))
- (when (buffer-live-p agent)
- (kill-buffer agent)))))
-
-(ert-deftest test-vterm-in-tmux-p-nil-when-not-vterm-mode ()
- "Boundary: predicate refuses non-vterm buffers without calling tmux."
- (with-temp-buffer
- (let ((tmux-called nil))
- (cl-letf (((symbol-function 'process-file)
- (lambda (&rest _) (setq tmux-called t) 0)))
- (should-not (cj/vterm--in-tmux-p))
- (should-not tmux-called)))))
-
-(ert-deftest test-vterm-copy-mode-dwim-sends-tmux-prefix-when-attached ()
- "Normal: with tmux attached, dwim writes C-b [ into the pty.
-The literal control-B + open-bracket bytes reach tmux which then enters
-its own copy-mode against the full pane history."
- (let ((agent (cj/test--make-fake-vterm-buffer "agent [emacs.d]"))
- (sent nil)
- (copy-mode-called nil))
- (unwind-protect
- (with-current-buffer agent
- (cl-letf (((symbol-function 'get-buffer-process)
- (lambda (_buffer) 'fake-process))
- ((symbol-function 'process-tty-name)
- (lambda (_process) "/dev/pts/8"))
- ((symbol-function 'vterm-send-string)
- (lambda (s &optional _paste-p) (push s sent)))
- ((symbol-function 'vterm-copy-mode)
- (lambda (&optional _arg) (setq copy-mode-called t))))
- (test-vterm-tmux-history--with-tmux-mock
- '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0
- "/dev/pts/8\t%8\n"))
- (cj/vterm-copy-mode-dwim)
- (should (equal sent '("\C-b[")))
- (should-not copy-mode-called))))
- (when (buffer-live-p agent)
- (kill-buffer agent)))))
-
-(ert-deftest test-vterm-copy-mode-dwim-falls-back-without-tmux ()
- "Boundary: without tmux, dwim calls `vterm-copy-mode' and sends nothing."
- (let ((agent (cj/test--make-fake-vterm-buffer "agent [emacs.d]"))
- (sent nil)
- (copy-mode-called nil))
- (unwind-protect
- (with-current-buffer agent
- (cl-letf (((symbol-function 'get-buffer-process)
- (lambda (_buffer) 'fake-process))
- ((symbol-function 'process-tty-name)
- (lambda (_process) "/dev/pts/8"))
- ((symbol-function 'vterm-send-string)
- (lambda (s &optional _paste-p) (push s sent)))
- ((symbol-function 'vterm-copy-mode)
- (lambda (&optional _arg) (setq copy-mode-called t))))
- (test-vterm-tmux-history--with-tmux-mock
- '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 1
- "no server running"))
- (cj/vterm-copy-mode-dwim)
- (should-not sent)
- (should copy-mode-called))))
- (when (buffer-live-p agent)
- (kill-buffer agent)))))
-
-(ert-deftest test-vterm-mouse-wheel-up-sends-sgr-button-64 ()
- "Normal: wheel-up emits the SGR mouse-wheel-up sequence (button 64)."
- (let ((sent nil))
- (cl-letf (((symbol-function 'vterm-send-string)
- (lambda (s &optional _paste-p) (push s sent))))
- (cj/vterm-mouse-wheel-up)
- (should (equal sent '("\e[<64;1;1M"))))))
-
-(ert-deftest test-vterm-mouse-wheel-down-sends-sgr-button-65 ()
- "Normal: wheel-down emits the SGR mouse-wheel-down sequence (button 65)."
- (let ((sent nil))
- (cl-letf (((symbol-function 'vterm-send-string)
- (lambda (s &optional _paste-p) (push s sent))))
- (cj/vterm-mouse-wheel-down)
- (should (equal sent '("\e[<65;1;1M"))))))
-
-(ert-deftest test-vterm-send-escape-writes-esc-byte ()
- "Normal: `cj/vterm-send-escape' forwards a literal ESC byte to the pty so
-tmux copy-mode, vi-mode exits, etc., can see the key past Emacs's global
-`<escape>' → `keyboard-escape-quit' binding."
- (let ((sent nil))
- (cl-letf (((symbol-function 'vterm-send-string)
- (lambda (s &optional _paste-p) (push s sent))))
- (cj/vterm-send-escape)
- (should (equal sent '("\e"))))))
-
-(ert-deftest test-vterm-escape-binding-installed-on-vterm-mode-map ()
- "Normal: `<escape>' in `vterm-mode-map' routes through `cj/vterm-send-escape'."
- (should (eq (keymap-lookup vterm-mode-map "<escape>")
- #'cj/vterm-send-escape)))
-
-(ert-deftest test-vterm-wheel-bindings-installed-on-vterm-mode-map ()
- "Normal: wheel-up / wheel-down (and X11 mouse-4 / mouse-5) route to the
-forwarding commands so tmux can see them via `set -g mouse on'."
- (should (eq (keymap-lookup vterm-mode-map "<wheel-up>")
- #'cj/vterm-mouse-wheel-up))
- (should (eq (keymap-lookup vterm-mode-map "<wheel-down>")
- #'cj/vterm-mouse-wheel-down))
- (should (eq (keymap-lookup vterm-mode-map "<mouse-4>")
- #'cj/vterm-mouse-wheel-up))
- (should (eq (keymap-lookup vterm-mode-map "<mouse-5>")
- #'cj/vterm-mouse-wheel-down)))
-
-(provide 'test-vterm-tmux-history)
-;;; test-vterm-tmux-history.el ends here