aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/vterm-config.el17
-rw-r--r--tests/test-vterm-copy-mode-cursor.el51
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