diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-15 22:38:46 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-15 22:39:18 -0500 |
| commit | 50d1b1c0a8d026d3b74d36f2e59b2471db728754 (patch) | |
| tree | 8e4653423bb9507e44ddd9147e8fef45973b861e | |
| parent | 35c0d683574f1d705f8dfffd260baa626ddbf116 (diff) | |
| download | dotemacs-50d1b1c0a8d026d3b74d36f2e59b2471db728754.tar.gz dotemacs-50d1b1c0a8d026d3b74d36f2e59b2471db728754.zip | |
refactor(ui-config): drop buffer-state cursor coloring
A cursor that changed color by buffer state was confusing. Remove cj/set-cursor-color-according-to-mode, its two cache defvars, and the post-command / server-after-make-frame hook registrations; the cursor now uses the theme cursor face. The cj/buffer-status-state / cj/buffer-status-color classifier stays in user-constants.el for the modeline buffer-name indicator. Delete the cursor-function integration test; keep the classifier tests.
| -rw-r--r-- | modules/ui-config.el | 49 | ||||
| -rw-r--r-- | tests/test-ui-config--buffer-cursor-state.el | 35 | ||||
| -rw-r--r-- | tests/test-ui-cursor-color-integration.el | 182 | ||||
| -rw-r--r-- | todo.org | 5 |
4 files changed, 11 insertions, 260 deletions
diff --git a/modules/ui-config.el b/modules/ui-config.el index 86670b29d..05448b3c5 100644 --- a/modules/ui-config.el +++ b/modules/ui-config.el @@ -94,51 +94,10 @@ When `cj/enable-transparency' is nil, reset alpha to fully opaque." (if cj/enable-transparency "enabled" "disabled"))) ;; ----------------------------------- Cursor ---------------------------------- -;; Set the cursor color from the active theme's faces according to buffer state. -;; The state classifier and the state->face map live in user-constants.el -;; (cj/buffer-status-state / cj/buffer-status-faces, colored via the theme's -;; error / warning / success faces) and are shared with the modeline buffer-name -;; indicator, so the cursor and the modeline stay in sync. - -(defvar cj/-cursor-last-color nil - "Last color applied by `cj/set-cursor-color-according-to-mode'.") -(defvar cj/-cursor-last-buffer nil - "Last buffer name where cursor color was applied.") - -(defun cj/set-cursor-color-according-to-mode () - "Set the cursor color from the active theme according to buffer state. -The state and its theme face come from `cj/buffer-status-state' and -`cj/buffer-status-color' (shared with the modeline), so the color follows the -loaded theme. Only updates real user buffers, not internal/temporary ones; a -no-op on non-graphical frames -- TTY/batch sessions have no cursor color to set." - (when (display-graphic-p) - ;; Only update cursor for real buffers (not internal ones like *temp*, *Echo Area*). - (unless (string-prefix-p " " (buffer-name)) ; internal buffers start with a space - (let ((color (cj/buffer-status-color (cj/buffer-status-state)))) - ;; Skip only when BOTH color and buffer are unchanged (so the color still - ;; updates when the buffer state changes). - (when (and color - (not (and (equal color cj/-cursor-last-color) - (equal (buffer-name) cj/-cursor-last-buffer)))) - (set-cursor-color color) - (setq cj/-cursor-last-color color - cj/-cursor-last-buffer (buffer-name))))))) - -;; Use post-command-hook to update cursor color after every command -;; This ensures cursor color always matches the current buffer's state. -;; The hook only registers under a graphical session so batch / TTY runs -;; don't pay per-command overhead for a no-op. -(when (display-graphic-p) - (add-hook 'post-command-hook #'cj/set-cursor-color-according-to-mode)) -;; Daemon mode: the first frame may be created after this module loads. -;; Re-attempt the hook install once a GUI frame appears. -(add-hook 'server-after-make-frame-hook - (lambda () - (when (and (display-graphic-p) - (not (memq #'cj/set-cursor-color-according-to-mode - post-command-hook))) - (add-hook 'post-command-hook - #'cj/set-cursor-color-according-to-mode)))) +;; The cursor uses the theme's cursor face. Buffer-state cursor coloring was +;; removed -- a cursor that changed color by buffer state was confusing. The +;; cj/buffer-status-state / cj/buffer-status-color classifier stays in +;; user-constants.el; the modeline buffer-name indicator still uses it. ;; Don’t show a cursor in non-selected windows: (setq cursor-in-non-selected-windows nil) diff --git a/tests/test-ui-config--buffer-cursor-state.el b/tests/test-ui-config--buffer-cursor-state.el index 76b74c97f..99cfc4b9d 100644 --- a/tests/test-ui-config--buffer-cursor-state.el +++ b/tests/test-ui-config--buffer-cursor-state.el @@ -1,9 +1,9 @@ ;;; test-ui-config--buffer-cursor-state.el --- Tests for cursor-state classification -*- lexical-binding: t; -*- ;;; Commentary: -;; `cj/buffer-status-state' picks the buffer-state symbol that -;; `cj/set-cursor-color-according-to-mode' maps to a cursor color via -;; `cj/buffer-status-colors'. The subtle case: a live ghostel terminal is +;; `cj/buffer-status-state' picks the buffer-state symbol the modeline +;; buffer-name indicator maps to a face via `cj/buffer-status-color'. The +;; subtle case: a live ghostel terminal is ;; technically `buffer-read-only' but the user types into it -- keystrokes go ;; to the terminal process -- so it must report a writeable state, not ;; `read-only'. ghostel's `copy' / `emacs' input modes are the exception: @@ -70,34 +70,5 @@ the user navigates, so `read-only' (orange) is kept." (should (eq (cj/buffer-status-state) 'read-only))) (when (buffer-live-p buf) (kill-buffer buf))))) -(ert-deftest test-ui-config-set-cursor-color-live-ghostel-uses-writeable-color () - "Normal: in a live ghostel terminal the cursor-color hook applies the writeable -\(success) color, not the read-only (error) color, even though the buffer is -read-only. `error' and `success' are given known foregrounds so the resolver -returns concrete colors; `display-graphic-p' is stubbed t so the body runs in -batch (the live function no-ops on TTY frames by design)." - (let ((buf (cj/test--make-fake-ghostel-buffer "*test-ghostel-cursor-color*")) - (orig-err (face-attribute 'error :foreground nil t)) - (orig-suc (face-attribute 'success :foreground nil t)) - (applied 'unset)) - (unwind-protect - (progn - (set-face-foreground 'error "#ff0000") - (set-face-foreground 'success "#00ff00") - (with-current-buffer buf - (setq buffer-read-only t) - (setq-local ghostel--input-mode 'semi-char) - (let ((cj/-cursor-last-color nil) - (cj/-cursor-last-buffer nil)) - (cl-letf (((symbol-function 'display-graphic-p) (lambda () t)) - ((symbol-function 'set-cursor-color) - (lambda (c) (setq applied c)))) - (cj/set-cursor-color-according-to-mode)))) - (should (equal applied "#00ff00")) - (should-not (equal applied "#ff0000"))) - (when (stringp orig-err) (set-face-foreground 'error orig-err)) - (when (stringp orig-suc) (set-face-foreground 'success orig-suc)) - (when (buffer-live-p buf) (kill-buffer buf))))) - (provide 'test-ui-config--buffer-cursor-state) ;;; test-ui-config--buffer-cursor-state.el ends here diff --git a/tests/test-ui-cursor-color-integration.el b/tests/test-ui-cursor-color-integration.el deleted file mode 100644 index 0c2b4df86..000000000 --- a/tests/test-ui-cursor-color-integration.el +++ /dev/null @@ -1,182 +0,0 @@ -;;; test-ui-cursor-color-integration.el --- Integration tests for cursor color -*- lexical-binding: t; -*- - -;;; Commentary: -;; Integration tests for the cursor-color hook. The cursor color now comes from -;; the active theme's faces (error / warning / success) via -;; `cj/buffer-status-color', not hard-coded hexes, so these tests pin those three -;; faces to known foregrounds and assert the resolved color per buffer state: -;; read-only -> error, unmodified -> success, modified/overwrite -> warning. - -;;; Code: - -(require 'ert) -(require 'user-constants) - -;; `cj/set-cursor-color-according-to-mode' and the `post-command-hook' install -;; both gate on `display-graphic-p' -- a TTY / batch run is a no-op by design. -;; These integration tests exercise the work body, so pretend we're graphical -;; for the whole file. Stub BEFORE loading ui-config: the hook install reads -;; `display-graphic-p' at load time. -(advice-add 'display-graphic-p :around - (lambda (orig &rest args) (or (apply orig args) t))) - -(require 'ui-config) - -(defmacro test-cursor--with-status-colors (&rest body) - "Run BODY with error/success/warning foregrounds pinned to known hexes. -read-only -> error #ff0000, unmodified -> success #00ff00, -modified/overwrite -> warning #ffaa00. Restores the originals after." - `(let ((oe (face-attribute 'error :foreground nil t)) - (os (face-attribute 'success :foreground nil t)) - (ow (face-attribute 'warning :foreground nil t))) - (unwind-protect - (progn - (set-face-foreground 'error "#ff0000") - (set-face-foreground 'success "#00ff00") - (set-face-foreground 'warning "#ffaa00") - ,@body) - (when (stringp oe) (set-face-foreground 'error oe)) - (when (stringp os) (set-face-foreground 'success os)) - (when (stringp ow) (set-face-foreground 'warning ow))))) - -;;; Hook Integration Tests - -(ert-deftest test-cursor-color-integration-post-command-hook-installed () - "Test that post-command-hook is installed." - (should (member 'cj/set-cursor-color-according-to-mode post-command-hook))) - -(ert-deftest test-cursor-color-integration-function-runs-without-error () - "Test that cursor color function runs without error in various buffers." - (test-cursor--with-status-colors - (with-temp-buffer - (should-not (condition-case err - (progn (cj/set-cursor-color-according-to-mode) nil) - (error err)))) - (with-temp-buffer - (setq buffer-read-only t) - (should-not (condition-case err - (progn (cj/set-cursor-color-according-to-mode) nil) - (error err)))))) - -(ert-deftest test-cursor-color-integration-internal-buffers-ignored () - "Test that internal buffers (starting with space) are ignored." - (let ((internal-buf (get-buffer-create " *test-internal*")) - (cj/-cursor-last-color nil) - (cj/-cursor-last-buffer nil)) - (unwind-protect - (with-current-buffer internal-buf - (cj/set-cursor-color-according-to-mode) - (should-not cj/-cursor-last-buffer)) - (kill-buffer internal-buf)))) - -(ert-deftest test-cursor-color-integration-normal-buffers-processed () - "Test that normal buffers (not starting with space) are processed." - (test-cursor--with-status-colors - (let ((normal-buf (get-buffer-create "test-normal")) - (cj/-cursor-last-color nil) - (cj/-cursor-last-buffer nil)) - (unwind-protect - (with-current-buffer normal-buf - (cj/set-cursor-color-according-to-mode) - (should (equal cj/-cursor-last-buffer "test-normal"))) - (kill-buffer normal-buf))))) - -(ert-deftest test-cursor-color-integration-cache-prevents-redundant-updates () - "Test that the cache prevents redundant cursor color updates." - (test-cursor--with-status-colors - (let* ((normal-buf (generate-new-buffer "test-cache")) - (call-count 0) - (advice-fn (lambda (&rest _) (setq call-count (1+ call-count))))) - (unwind-protect - (progn - (advice-add 'set-cursor-color :before advice-fn) - (with-current-buffer normal-buf - ;; Clean buffer -> success (#00ff00); seed the cache with that color - ;; and this buffer so the call is a no-op. - (set-buffer-modified-p nil) - (let ((cj/-cursor-last-color "#00ff00") - (cj/-cursor-last-buffer (buffer-name))) - (cj/set-cursor-color-according-to-mode) - (should (= call-count 0))) - ;; Modify -> warning (#ffaa00); clear the buffer cache to force update. - (insert "test") - (let ((cj/-cursor-last-color "#00ff00") - (cj/-cursor-last-buffer nil)) - (cj/set-cursor-color-according-to-mode) - (should (= call-count 1))))) - (advice-remove 'set-cursor-color advice-fn) - (kill-buffer normal-buf))))) - -(ert-deftest test-cursor-color-integration-different-buffers-different-colors () - "Test that buffers in different states resolve to different theme colors." - (test-cursor--with-status-colors - (let ((buf1 (generate-new-buffer "test1")) - (buf2 (generate-new-buffer "test2")) - (cj/-cursor-last-color nil) - (cj/-cursor-last-buffer nil)) - (unwind-protect - (progn - (with-current-buffer buf1 - (setq buffer-read-only t) - (cj/set-cursor-color-according-to-mode) - (should (equal cj/-cursor-last-color "#ff0000"))) ; read-only -> error - (with-current-buffer buf2 - (setq buffer-read-only nil) - (set-buffer-modified-p nil) - (cj/set-cursor-color-according-to-mode) - (should (equal cj/-cursor-last-color "#00ff00")))) ; unmodified -> success - (kill-buffer buf1) - (kill-buffer buf2))))) - -(ert-deftest test-cursor-color-integration-buffer-modification-changes-color () - "Test that modifying a buffer moves the cursor from success to warning." - (test-cursor--with-status-colors - (let ((normal-buf (generate-new-buffer "test-mod")) - (cj/-cursor-last-color nil) - (cj/-cursor-last-buffer nil)) - (unwind-protect - (with-current-buffer normal-buf - (set-buffer-modified-p nil) - (cj/set-cursor-color-according-to-mode) - (should (equal cj/-cursor-last-color "#00ff00")) ; unmodified -> success - (insert "test") - (should (buffer-modified-p)) - (setq cj/-cursor-last-buffer nil) - (cj/set-cursor-color-according-to-mode) - (should (equal cj/-cursor-last-color "#ffaa00"))) ; modified -> warning - (kill-buffer normal-buf))))) - -(ert-deftest test-cursor-color-integration-save-changes-color-back () - "Test that saving a modified buffer moves the cursor from warning to success." - (test-cursor--with-status-colors - (let ((test-file (make-temp-file "test-cursor-")) - (cj/-cursor-last-color nil) - (cj/-cursor-last-buffer nil)) - (unwind-protect - (with-current-buffer (find-file-noselect test-file) - (insert "test") - (should (buffer-modified-p)) - (cj/set-cursor-color-according-to-mode) - (should (equal cj/-cursor-last-color "#ffaa00")) ; modified -> warning - (save-buffer) - (should-not (buffer-modified-p)) - (setq cj/-cursor-last-buffer nil) - (cj/set-cursor-color-according-to-mode) - (should (equal cj/-cursor-last-color "#00ff00")) ; unmodified -> success - (kill-buffer)) - (delete-file test-file))))) - -;;; Performance Tests - -(ert-deftest test-cursor-color-integration-multiple-calls-efficient () - "Test that multiple rapid calls don't cause performance issues." - (test-cursor--with-status-colors - (with-temp-buffer - (let ((start-time (current-time))) - (dotimes (_ 1000) - (cj/set-cursor-color-according-to-mode)) - (let ((elapsed (float-time (time-subtract (current-time) start-time)))) - (should (< elapsed 1.0))))))) - -(provide 'test-ui-cursor-color-integration) -;;; test-ui-cursor-color-integration.el ends here @@ -2745,7 +2745,10 @@ configuration (=text-config=, =diff-config=, =ledger-config=, From the 2026-06 config audit: =prog-go.el:64=, =prog-c.el:73=, =prog-shell.el:77= call global =(electric-pair-mode t)= from buffer setup hooks — one Go/C/shell buffer turns on pairing in org/text everywhere (python/webdev correctly use =electric-pair-local-mode=). =prog-general.el:79-80= — =display-line-numbers-type 'relative= setq/setq-default run from the hook AFTER the mode is enabled, so the first prog buffer of a session gets absolute numbers. Local-mode for the three; move the line-number setqs to top level. The global electric-pair this turns on also paired "<" in org, stranding a ">" after "<"-key snippets (=#+end_src>=, broke cj-scan). That symptom is fixed separately (=d9c90e83=, an =electric-pair-inhibit-predicate= for "<"). This task remains the root fix: pairing should not be global at all. -** TODO [#B] Remove buffer-state cursor coloring :refactor: +** DONE [#B] Remove buffer-state cursor coloring :refactor: +CLOSED: [2026-06-15 Mon] +Removed cj/set-cursor-color-according-to-mode + the two cache defvars + the post-command/server-after-make-frame hook registrations from ui-config.el; cursor now uses the theme cursor face. Kept cj/buffer-status-state / cj/buffer-status-color in user-constants.el (modeline still uses them). Deleted tests/test-ui-cursor-color-integration.el (all cursor-function tests) and dropped the one cursor-function test from test-ui-config--buffer-cursor-state.el (kept its 6 classifier tests). Live daemon cleaned (hook removed, fn unbound, cursor reset). + Craig directed removal (roam inbox, 2026-06-15): the cursor changing color by buffer state is confusing ("strange not knowing what your cursor should look like"). Remove cj/set-cursor-color-according-to-mode, the cj/-cursor-last-color / cj/-cursor-last-buffer defvars, and the post-command-hook + server-after-make-frame-hook registrations in ui-config.el, so the cursor uses the theme's cursor face. Keep the shared cj/buffer-status-state / cj/buffer-status-color classifier (the modeline buffer-name indicator still uses it). Before deleting tests/test-ui-cursor-color-integration.el and tests/test-ui-config--buffer-cursor-state.el, confirm which covers the shared classifier (keep) versus the cursor function (remove). Update the cursor header comment. This settles the earlier keep-vs-remove question: 7ccc3f5c's theme-driven rework is the thing to drop. ** TODO [#B] rulesets page-me notifications should name the source project :feature:quick: :PROPERTIES: |
