diff --git a/lisp/doom-editor.el b/lisp/doom-editor.el index f3ec6efe8..6f33b1a94 100644 --- a/lisp/doom-editor.el +++ b/lisp/doom-editor.el @@ -255,10 +255,10 @@ tell you about it. Very annoying. This prevents that." (use-package! autorevert ;; revert buffers when their files/state have changed - :hook (focus-in . doom-auto-revert-buffers-h) :hook (after-save . doom-auto-revert-buffers-h) :hook (doom-switch-buffer . doom-auto-revert-buffer-h) :hook (doom-switch-window . doom-auto-revert-buffer-h) + :hook (doom-switch-frame . doom-auto-revert-buffers-h) :config (setq auto-revert-verbose t ; let us know when it happens auto-revert-use-notify nil diff --git a/lisp/doom-ui.el b/lisp/doom-ui.el index 7f8424347..52c4d6bbb 100644 --- a/lisp/doom-ui.el +++ b/lisp/doom-ui.el @@ -91,22 +91,60 @@ want to change your symbol font, use `doom-symbol-font'.") "A list of hooks run after changing the focused windows.") (defcustom doom-switch-frame-hook nil - "A list of hooks run after changing the focused frame.") + "A list of hooks run after changing the focused frame. + +This also serves as an analog for `focus-in-hook' or +`after-focus-change-function', but also preforms debouncing (see +`doom-switch-frame-hook-debounce-delay'). It's possible for this hook to be +triggered multiple times (because there are edge cases where Emacs can have +multiple frames focused at once).") (defun doom-run-switch-buffer-hooks-h (&optional _) - (let ((gc-cons-threshold most-positive-fixnum) - (inhibit-redisplay t)) + "Trigger `doom-switch-buffer-hook' when selecting a new buffer." + (let ((gc-cons-threshold most-positive-fixnum)) (run-hooks 'doom-switch-buffer-hook))) -(defun doom-run-switch-window-or-frame-hooks-h (&optional _) - (let ((gc-cons-threshold most-positive-fixnum) - (inhibit-redisplay t)) - (unless (equal (old-selected-frame) (selected-frame)) - (run-hooks 'doom-switch-frame-hook)) - (unless (or (minibufferp) - (equal (old-selected-window) (minibuffer-window))) +(defun doom-run-switch-window-hooks-h (&optional _) + "Trigger `doom-switch-window-hook' when selecting a window in the same frame." + (unless (or (minibufferp) + (not (equal (old-selected-frame) (selected-frame))) + (equal (old-selected-window) (minibuffer-window))) + (let ((gc-cons-threshold most-positive-fixnum)) (run-hooks 'doom-switch-window-hook)))) +(defvar doom-switch-frame-hook-debounce-delay 2.0 + "The delay for which `doom-switch-frame-hook' won't trigger again. + +This exists to prevent switch-frame hooks getting triggered too aggressively due +to misbehaving desktop environments, packages incorrectly frame switching in +non-interactive code, or the user accidentally (and rapidly) un-and-refocusing +the frame through some other means.") + +(defun doom--run-switch-frame-hooks-fn (_) + (remove-hook 'pre-redisplay-functions #'doom--run-switch-frame-hooks) + (let ((gc-cons-threshold most-positive-fixnum)) + (dolist (fr (visible-frame-list)) + (let ((state (frame-focus-state fr))) + (when (and state (not (eq state 'unknown))) + (let ((last-update (frame-parameter fr '+last-focus))) + (when (or (null last-update) + (> (float-time (time-subtract (current-time) last-update)) + doom-switch-frame-hook-debounce-delay)) + (with-selected-frame fr + (unwind-protect + (run-hooks 'doom-switch-frame-hook) + (set-frame-parameter fr '+last-focus (current-time))))))))))) + +(let (last-focus-state) + (defun doom-run-switch-frame-hooks-fn () + "Trigger `doom-switch-frame-hook' once per frame focus change." + (let ((inhibit-redisplay t)) + (or (equal last-focus-state + (setq last-focus-state + (mapcar #'frame-focus-state (frame-list)))) + ;; Defer until next redisplay + (add-hook 'pre-redisplay-functions #'doom--run-switch-frame-hooks-fn))))) + (defun doom-protect-fallback-buffer-h () "Don't kill the scratch buffer. Meant for `kill-buffer-query-functions'." (not (eq (current-buffer) (doom-fallback-buffer)))) @@ -669,9 +707,9 @@ triggering hooks during startup." ;; Make `next-buffer', `other-buffer', etc. ignore unreal buffers. (push '(buffer-predicate . doom-buffer-frame-predicate) default-frame-alist) - ;; Initialize `doom-switch-window-hook' and `doom-switch-frame-hook' - (add-hook 'window-selection-change-functions #'doom-run-switch-window-or-frame-hooks-h) - ;; Initialize `doom-switch-buffer-hook' + ;; Initialize `doom-switch-*-hook' hooks. + (add-function :after after-focus-change-function #'doom-run-switch-frame-hooks-fn) + (add-hook 'window-selection-change-functions #'doom-run-switch-window-hooks-h) (add-hook 'window-buffer-change-functions #'doom-run-switch-buffer-hooks-h) ;; `window-buffer-change-functions' doesn't trigger for files visited via the server. (add-hook 'server-visit-hook #'doom-run-switch-buffer-hooks-h)) diff --git a/modules/tools/magit/config.el b/modules/tools/magit/config.el index 5f4231f31..d60e73510 100644 --- a/modules/tools/magit/config.el +++ b/modules/tools/magit/config.el @@ -50,7 +50,7 @@ Only has an effect in GUI Emacs.") ;; ...then refresh the rest only when we switch to them or refocus the active ;; frame, not all at once. (add-hook 'doom-switch-buffer-hook #'+magit-revert-buffer-maybe-h) - (add-hook 'focus-in-hook #'+magit-mark-stale-buffers-h) + (add-hook 'doom-switch-frame-hook #'+magit-mark-stale-buffers-h) ;; The default location for git-credential-cache is in ;; ~/.cache/git/credential. However, if ~/.git-credential-cache/ exists, then diff --git a/modules/ui/modeline/+light.el b/modules/ui/modeline/+light.el index dacff3b7f..41d67b2f2 100644 --- a/modules/ui/modeline/+light.el +++ b/modules/ui/modeline/+light.el @@ -368,7 +368,7 @@ Requires `anzu', also `evil-anzu' if using `evil-mode' for compatibility with ;; In case the user saves the file to a new location after-save-hook ;; ...or makes external changes then returns to Emacs - focus-in-hook + doom-switch-frame-hook ;; ...or when we change the current project! projectile-after-switch-project-hook ;; ...when the visited file changes (e.g. it's renamed) diff --git a/modules/ui/vc-gutter/config.el b/modules/ui/vc-gutter/config.el index 13eba6aad..9f6e08df2 100644 --- a/modules/ui/vc-gutter/config.el +++ b/modules/ui/vc-gutter/config.el @@ -118,8 +118,9 @@ Respects `diff-hl-disable-on-remote'." :n "{" #'diff-hl-show-hunk-previous :n "}" #'diff-hl-show-hunk-next :n "S" #'diff-hl-show-hunk-stage-hunk)) - ;; UX: Refresh gutter on ESC or refocusing the Emacs frame. - (add-hook! '(doom-escape-hook doom-switch-window-hook) :append + ;; UX: Refresh gutter in the selected buffer on ESC, switching windows, or + ;; refocusing the frame. + (add-hook! '(doom-escape-hook doom-switch-window-hook doom-switch-frame-hook) :append (defun +vc-gutter-update-h (&rest _) "Return nil to prevent shadowing other `doom-escape-hook' hooks." (ignore (or inhibit-redisplay