diff options
| -rw-r--r-- | init.el | 2 | ||||
| -rw-r--r-- | modules/ui-config.el | 43 | ||||
| -rw-r--r-- | tests/test-ui-cursor-color-integration.el | 164 |
3 files changed, 186 insertions, 23 deletions
@@ -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 |
