summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--init.el2
-rw-r--r--modules/ui-config.el43
-rw-r--r--tests/test-ui-cursor-color-integration.el164
3 files changed, 186 insertions, 23 deletions
diff --git a/init.el b/init.el
index 17d5e315..cb84f985 100644
--- a/init.el
+++ b/init.el
@@ -56,7 +56,7 @@
(require 'font-config) ;; font and emoji configuration
(require 'selection-framework) ;; menu config
(require 'modeline-config) ;; modeline (status-bar) config
-(require 'mousetrap-mode) ;; disables trackpad/mouse input only in Emacs
+(require 'mousetrap-mode) ;; prevent accidental mouse/trackpad modifications
(require 'popper-config) ;; moving logs, help, and other buffers to popup
;; ----------------- Emacs Built-In Functionality Configuration ----------------
diff --git a/modules/ui-config.el b/modules/ui-config.el
index 3e065370..775aefb2 100644
--- a/modules/ui-config.el
+++ b/modules/ui-config.el
@@ -97,28 +97,27 @@ When `cj/enable-transparency' is nil, reset alpha to fully opaque."
"Last buffer name where cursor color was applied.")
(defun cj/set-cursor-color-according-to-mode ()
- "Change cursor color according to buffer state (modified, read-only, overwrite)."
- (let* ((state (cond
- (buffer-read-only 'read-only)
- (overwrite-mode 'overwrite)
- ((buffer-modified-p) 'modified)
- (t 'unmodified)))
- (color (alist-get state cj/buffer-status-colors)))
- (unless (and (string= color cj/-cursor-last-color)
- (string= (buffer-name) cj/-cursor-last-buffer))
- (set-cursor-color color)
- (setq cj/-cursor-last-color color
- cj/-cursor-last-buffer (buffer-name)))))
-
-;; Use more efficient hooks instead of post-command-hook for better performance
-(add-hook 'window-buffer-change-functions
- (lambda (_window) (cj/set-cursor-color-according-to-mode)))
-(add-hook 'read-only-mode-hook #'cj/set-cursor-color-according-to-mode)
-(add-hook 'overwrite-mode-hook #'cj/set-cursor-color-according-to-mode)
-;; Add hook to update cursor color when buffer is modified/saved
-(add-hook 'after-change-functions
- (lambda (&rest _) (cj/set-cursor-color-according-to-mode)))
-(add-hook 'after-save-hook #'cj/set-cursor-color-according-to-mode)
+ "Change cursor color according to buffer state (modified, read-only, overwrite).
+Only updates for real user buffers, not internal/temporary buffers."
+ ;; Only update cursor for real buffers (not internal ones like *temp*, *Echo Area*, etc.)
+ (unless (string-prefix-p " " (buffer-name)) ; Internal buffers start with space
+ (let* ((state (cond
+ (buffer-read-only 'read-only)
+ (overwrite-mode 'overwrite)
+ ((buffer-modified-p) 'modified)
+ (t 'unmodified)))
+ (color (alist-get state cj/buffer-status-colors)))
+ ;; Only skip if BOTH color AND buffer are the same (optimization)
+ ;; This allows color to update when buffer state changes
+ (unless (and (string= color cj/-cursor-last-color)
+ (string= (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
+(add-hook 'post-command-hook #'cj/set-cursor-color-according-to-mode)
;; Don’t show a cursor in non-selected windows:
(setq cursor-in-non-selected-windows nil)
diff --git a/tests/test-ui-cursor-color-integration.el b/tests/test-ui-cursor-color-integration.el
new file mode 100644
index 00000000..00b7f57b
--- /dev/null
+++ b/tests/test-ui-cursor-color-integration.el
@@ -0,0 +1,164 @@
+;;; test-ui-cursor-color-integration.el --- Integration tests for cursor color -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Integration tests for cursor color hook behavior.
+;; Tests that cursor color actually updates when switching buffers,
+;; modifying files, etc.
+
+;;; Code:
+
+(require 'ert)
+(require 'user-constants)
+(require 'ui-config)
+
+;;; 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."
+ (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)
+ ;; Cursor state should not have been updated
+ (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."
+ (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)
+ ;; Cursor state should have been updated
+ (should (equal cj/-cursor-last-buffer "test-normal")))
+ (kill-buffer normal-buf))))
+
+(ert-deftest test-cursor-color-integration-cache-prevents-redundant-updates ()
+ "Test that cache prevents redundant cursor color updates."
+ (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
+ ;; First call - cache matches, no update
+ (let ((cj/-cursor-last-color "#ffffff")
+ (cj/-cursor-last-buffer (buffer-name)))
+ (cj/set-cursor-color-according-to-mode)
+ (should (= call-count 0))) ; Cached, no update needed
+
+ ;; Modify buffer and clear cache - should update
+ (insert "test")
+ (let ((cj/-cursor-last-buffer nil)) ; Force update
+ (cj/set-cursor-color-according-to-mode)
+ (should (= call-count 1))))) ; New state, should update
+ (advice-remove 'set-cursor-color advice-fn)
+ (kill-buffer normal-buf))))
+
+(ert-deftest test-cursor-color-integration-different-buffers-different-colors ()
+ "Test that switching between buffers with different states updates cursor."
+ (let ((buf1 (generate-new-buffer "test1"))
+ (buf2 (generate-new-buffer "test2"))
+ (cj/-cursor-last-color nil)
+ (cj/-cursor-last-buffer nil))
+ (unwind-protect
+ (progn
+ ;; Set buf1 to read-only
+ (with-current-buffer buf1
+ (setq buffer-read-only t)
+ (cj/set-cursor-color-according-to-mode)
+ (should (equal cj/-cursor-last-color "#f06a3f"))) ; Red
+
+ ;; Set buf2 to normal
+ (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 "#ffffff")))) ; White
+ (kill-buffer buf1)
+ (kill-buffer buf2))))
+
+(ert-deftest test-cursor-color-integration-buffer-modification-changes-color ()
+ "Test that modifying a buffer changes cursor from white to green."
+ (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
+ ;; Start unmodified
+ (set-buffer-modified-p nil)
+ (cj/set-cursor-color-according-to-mode)
+ (should (equal cj/-cursor-last-color "#ffffff")) ; White
+
+ ;; Modify buffer
+ (insert "test")
+ (should (buffer-modified-p))
+ ;; Reset last buffer to force update
+ (setq cj/-cursor-last-buffer nil)
+ (cj/set-cursor-color-according-to-mode)
+ (should (equal cj/-cursor-last-color "#64aa0f"))) ; Green
+ (kill-buffer normal-buf))))
+
+(ert-deftest test-cursor-color-integration-save-changes-color-back ()
+ "Test that saving a modified buffer changes cursor from green to white."
+ (let ((test-file (make-temp-file "test-cursor-"))
+ (cj/-cursor-last-color nil)
+ (cj/-cursor-last-buffer nil))
+ (unwind-protect
+ (progn
+ ;; Create and modify file
+ (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 "#64aa0f")) ; Green
+
+ ;; Save file
+ (save-buffer)
+ (should-not (buffer-modified-p))
+ (cj/set-cursor-color-according-to-mode)
+ (should (equal cj/-cursor-last-color "#ffffff")) ; White
+ (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."
+ (with-temp-buffer
+ (let ((start-time (current-time)))
+ ;; Call 1000 times
+ (dotimes (_ 1000)
+ (cj/set-cursor-color-according-to-mode))
+ (let ((elapsed (float-time (time-subtract (current-time) start-time))))
+ ;; Should complete in less than 1 second (cache makes this very fast)
+ (should (< elapsed 1.0))))))
+
+(provide 'test-ui-cursor-color-integration)
+;;; test-ui-cursor-color-integration.el ends here