diff options
| -rw-r--r-- | modules/mousetrap-mode.el | 45 | ||||
| -rw-r--r-- | tests/test-integration-mousetrap-mode-lighter-click.el | 14 |
2 files changed, 41 insertions, 18 deletions
diff --git a/modules/mousetrap-mode.el b/modules/mousetrap-mode.el index 0df08d7c..d7f422de 100644 --- a/modules/mousetrap-mode.el +++ b/modules/mousetrap-mode.el @@ -15,6 +15,9 @@ ;; change profiles or mode mappings and re-enable the mode without reloading ;; your Emacs configuration. ;; +;; Keymaps are buffer-local via `emulation-mode-map-alists', so each buffer +;; gets the correct profile for its major mode independently. +;; ;; Inspired by this blog post from Malabarba ;; https://endlessparentheses.com/disable-mouse-only-inside-emacs.html ;; @@ -127,11 +130,19 @@ the mode is toggled, allowing dynamic behavior without reloading config." (define-key map (kbd (format "<%s%s-%d>" pref type button)) #'ignore))))))))) map)) -;;; Minor Mode Definition +;;; Buffer-local keymap via emulation-mode-map-alists (defvar-local mouse-trap-mode-map nil "Keymap for `mouse-trap-mode'. Built dynamically per buffer.") +(defvar-local mouse-trap--emulation-alist nil + "Buffer-local alist for mouse-trap keymap lookup. +Used via `emulation-mode-map-alists' so each buffer gets its own keymap.") + +(add-to-list 'emulation-mode-map-alists 'mouse-trap--emulation-alist) + +;;; Minor Mode Definition + (defvar mouse-trap--lighter-keymap (let ((map (make-sparse-keymap))) (define-key map [mode-line mouse-1] @@ -174,20 +185,17 @@ See `mouse-trap-profiles' for available profiles and `mouse-trap-mode-profiles' for mode mappings." :lighter nil ; We use mode-line-misc-info instead :group 'convenience - ;; Build keymap dynamically when mode is activated (if mouse-trap-mode (progn + ;; Build buffer-local keymap and register via emulation layer (setq mouse-trap-mode-map (mouse-trap--build-keymap)) - ;; Register keymap so Emacs actually uses it for key dispatch - (let ((entry (assq 'mouse-trap-mode minor-mode-map-alist))) - (if entry - (setcdr entry mouse-trap-mode-map) - (push (cons 'mouse-trap-mode mouse-trap-mode-map) minor-mode-map-alist))) + (setq mouse-trap--emulation-alist + `((mouse-trap-mode . ,mouse-trap-mode-map))) ;; Add dynamic lighter to mode-line-misc-info (always visible) (unless (member '(:eval (mouse-trap--lighter-string)) mode-line-misc-info) (push '(:eval (mouse-trap--lighter-string)) mode-line-misc-info))) - ;; When disabling, remove keymap from minor-mode-map-alist - (setq minor-mode-map-alist (assq-delete-all 'mouse-trap-mode minor-mode-map-alist)) + ;; When disabling, clear the buffer-local emulation alist and keymap + (setq mouse-trap--emulation-alist nil) (setq mouse-trap-mode-map nil) ;; Note: We keep the lighter in mode-line-misc-info so it shows 🐭 when disabled )) @@ -200,15 +208,30 @@ These modes are excluded from automatic activation via hooks, but you can still manually enable mouse-trap-mode in these buffers if desired.") (defun mouse-trap-maybe-enable () - "Enable `mouse-trap-mode' unless in an excluded mode." + "Enable `mouse-trap-mode' unless in an excluded mode. +If already enabled, rebuild the keymap for the current major mode. +This handles derived modes whose parent hook fires before `major-mode' +is set to the child (e.g. special-mode-hook runs before dashboard-mode)." (unless (apply #'derived-mode-p mouse-trap-excluded-modes) - (mouse-trap-mode 1))) + (if mouse-trap-mode + ;; Already on — rebuild keymap for the (possibly changed) major mode + (progn + (setq mouse-trap-mode-map (mouse-trap--build-keymap)) + (setq mouse-trap--emulation-alist + `((mouse-trap-mode . ,mouse-trap-mode-map)))) + (mouse-trap-mode 1)))) ;; Enable in text, prog, and special modes (add-hook 'text-mode-hook #'mouse-trap-maybe-enable) (add-hook 'prog-mode-hook #'mouse-trap-maybe-enable) (add-hook 'special-mode-hook #'mouse-trap-maybe-enable) +;; Derived modes with custom profiles need their own hooks to rebuild the +;; keymap after major-mode is set (parent hooks fire before the child sets it). +(dolist (mode (mapcar #'car mouse-trap-mode-profiles)) + (let ((hook (intern (format "%s-hook" mode)))) + (add-hook hook #'mouse-trap-maybe-enable))) + (keymap-global-set "C-c M" #'mouse-trap-mode) (with-eval-after-load 'which-key diff --git a/tests/test-integration-mousetrap-mode-lighter-click.el b/tests/test-integration-mousetrap-mode-lighter-click.el index b9f32fda..252e0cf5 100644 --- a/tests/test-integration-mousetrap-mode-lighter-click.el +++ b/tests/test-integration-mousetrap-mode-lighter-click.el @@ -35,7 +35,7 @@ Dashboard uses scroll+primary profile which allows scrolling and mouse-1." (should (eq (lookup-key mouse-trap-mode-map (kbd "<wheel-up>")) nil)) ;; Keymap should be in minor-mode-map-alist - (should (assq 'mouse-trap-mode minor-mode-map-alist))))) + (should (assq 'mouse-trap-mode mouse-trap--emulation-alist))))) (ert-deftest test-integration-lighter-click-disables-mode () "Test clicking lighter when mode is enabled disables it and removes keymap." @@ -43,7 +43,7 @@ Dashboard uses scroll+primary profile which allows scrolling and mouse-1." (emacs-lisp-mode) (mouse-trap-mode 1) (should mouse-trap-mode) - (should (assq 'mouse-trap-mode minor-mode-map-alist)) + (should (assq 'mouse-trap-mode mouse-trap--emulation-alist)) ;; Simulate clicking lighter to disable (mouse-trap-mode -1) @@ -52,7 +52,7 @@ Dashboard uses scroll+primary profile which allows scrolling and mouse-1." (should-not mouse-trap-mode) ;; Keymap should be removed from minor-mode-map-alist - (should-not (assq 'mouse-trap-mode minor-mode-map-alist)))) + (should-not (assq 'mouse-trap-mode mouse-trap--emulation-alist)))) (ert-deftest test-integration-lighter-click-toggle-updates-keymap () "Test toggling mode via lighter click rebuilds keymap for current mode. @@ -142,12 +142,12 @@ Auto-enable is blocked, but manual toggle should still work." ;; But manual toggle should work (mouse-trap-mode 1) (should mouse-trap-mode) - (should (assq 'mouse-trap-mode minor-mode-map-alist)) + (should (assq 'mouse-trap-mode mouse-trap--emulation-alist)) ;; Toggle off (mouse-trap-mode -1) (should-not mouse-trap-mode) - (should-not (assq 'mouse-trap-mode minor-mode-map-alist)))) + (should-not (assq 'mouse-trap-mode mouse-trap--emulation-alist)))) (ert-deftest test-integration-lighter-click-multiple-rapid-toggles () "Test rapid clicking (multiple toggles) is stable and doesn't corrupt state." @@ -162,12 +162,12 @@ Auto-enable is blocked, but manual toggle should still work." ;; Should end in disabled state (even number of toggles) (should-not mouse-trap-mode) - (should-not (assq 'mouse-trap-mode minor-mode-map-alist)) + (should-not (assq 'mouse-trap-mode mouse-trap--emulation-alist)) ;; Enable one more time to end enabled (mouse-trap-mode 1) (should mouse-trap-mode) - (should (assq 'mouse-trap-mode minor-mode-map-alist)) + (should (assq 'mouse-trap-mode mouse-trap--emulation-alist)) (should (keymapp mouse-trap-mode-map)))) (provide 'test-integration-mousetrap-mode-lighter-click) |
