diff options
| -rw-r--r-- | modules/vterm-config.el | 17 | ||||
| -rw-r--r-- | tests/test-vterm-copy-mode-cursor.el | 51 |
2 files changed, 68 insertions, 0 deletions
diff --git a/modules/vterm-config.el b/modules/vterm-config.el index 8442c65f..eb472c57 100644 --- a/modules/vterm-config.el +++ b/modules/vterm-config.el @@ -381,6 +381,23 @@ C-F9 / M-F9 dispatch via `cj/ai-vterm'." (cj/vterm-install-prefix-key) (cj/vterm-install-copy-mode-cancel-keys)) +(defun cj/--vterm-copy-mode-restore-cursor () + "Force a visible cursor on entry to `vterm-copy-mode'. + +The vterm C module sets `cursor-type' to nil whenever the underlying +TUI sends DECTCEM (`\\e[?25l') to hide the terminal cursor — typical +for full-screen TUIs like Claude Code. In `vterm-copy-mode' the user +is navigating the buffer, not watching the TUI, so the cursor must +be visible. Switches to a 3-pixel bar (drawn between characters +rather than inverting one) so face-heavy TUI output doesn't hide it +either. On exit, kills the buffer-local override so vterm's normal +cursor-visibility tracking resumes." + (if vterm-copy-mode + (setq-local cursor-type '(bar . 3)) + (kill-local-variable 'cursor-type))) + +(add-hook 'vterm-copy-mode-hook #'cj/--vterm-copy-mode-restore-cursor) + (add-hook 'vterm-mode-hook #'goto-address-mode) (with-eval-after-load 'which-key diff --git a/tests/test-vterm-copy-mode-cursor.el b/tests/test-vterm-copy-mode-cursor.el new file mode 100644 index 00000000..ee72a0bb --- /dev/null +++ b/tests/test-vterm-copy-mode-cursor.el @@ -0,0 +1,51 @@ +;;; test-vterm-copy-mode-cursor.el --- Tests for cursor visibility in vterm-copy-mode -*- lexical-binding: t; -*- + +;;; Commentary: +;; vterm's C module sets `cursor-type' to nil when the underlying TUI +;; sends DECTCEM (`\e[?25l'). Most full-screen TUIs (Claude Code, htop, +;; etc.) hide the cursor on startup. In `vterm-copy-mode' the user is +;; navigating the buffer, not watching the TUI, so the cursor must be +;; forced visible -- the hook in `vterm-config.el' handles that. On +;; exit, the buffer-local override is killed so the live terminal goes +;; back to the TUI's chosen cursor state. + +;;; Code: + +(require 'ert) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(defvar vterm-copy-mode nil) +(require 'vterm-config) + +(ert-deftest test-vterm-copy-mode-cursor-restored-on-enter () + "Normal: entering copy-mode with cursor-type nil sets a visible cursor." + (with-temp-buffer + (setq-local cursor-type nil) + (let ((vterm-copy-mode t)) + (cj/--vterm-copy-mode-restore-cursor)) + (should (equal cursor-type '(bar . 3))))) + +(ert-deftest test-vterm-copy-mode-cursor-restored-when-prior-was-box () + "Boundary: entering copy-mode overrides any prior cursor-type with the bar." + (with-temp-buffer + (setq-local cursor-type 'box) + (let ((vterm-copy-mode t)) + (cj/--vterm-copy-mode-restore-cursor)) + (should (equal cursor-type '(bar . 3))))) + +(ert-deftest test-vterm-copy-mode-cursor-override-killed-on-exit () + "Normal: exiting copy-mode kills the buffer-local cursor-type override." + (with-temp-buffer + (setq-local cursor-type '(bar . 3)) + (should (local-variable-p 'cursor-type)) + (let ((vterm-copy-mode nil)) + (cj/--vterm-copy-mode-restore-cursor)) + (should-not (local-variable-p 'cursor-type)))) + +(ert-deftest test-vterm-copy-mode-cursor-hook-installed () + "Normal: the cursor-restoration hook is registered on vterm-copy-mode-hook." + (should (memq #'cj/--vterm-copy-mode-restore-cursor + vterm-copy-mode-hook))) + +(provide 'test-vterm-copy-mode-cursor) +;;; test-vterm-copy-mode-cursor.el ends here |
