3

I use auto-revert-mode and ideally I would like the auto-revert-interval to be very small, e.g. 0.5 seconds or less. But I am concerned about performance problems when I have many buffers open, especially as I would like to use auto-revert-mode for dired buffers too.

It occured to me, though, that auto-revert-mode only really needs to be active for the current buffer. After all, if I'm not looking at a buffer, who cares if it is reverted or not? Other buffers can wait to be reverted until I actually switch to them.

What's the easiest/most elegant way to make it so auto-revert-mode is only active (i.e. polling the filesystem) for the buffer currently being viewed?

Resigned June 2023
  • 1,502
  • 15
  • 20

4 Answers4

5

Since Emacs 24.4, auto-revert-mode does not poll anymore. Instead, it uses file notifications from your underlying OS. There shouldn't be any performance problem; people have reported 600+ buffers in parallel in auto-revert-mode, IIRC.

Michael Albinus
  • 6,647
  • 14
  • 20
  • This is more a comment than an answer to the actual question. From the comment of `autorevert.el` emacs 25.1: `If Emacs is compiled with file notification support, notifications are used instead of checking the time stamp of the files. You can disable this by setting the user option 'auto-revert-use-notify' to nil. Alternatively, a regular expression of directories to be excluded from file notifications can be specified by 'auto-revert-notify-exclude-dir-regexp'.` – Tobias Nov 24 '16 at 15:59
  • Does this mean that `auto-revert-interval` has no effect, then? – Resigned June 2023 Nov 24 '16 at 18:09
  • Actually, I can't accept this answer because it appears that OS X (my operating system) does not support file notifications. See [this comment](http://emacs.stackexchange.com/questions/10966/global-auto-revert-mode-doesnt-seem-to-work#comment17038_10966). – Resigned June 2023 Nov 24 '16 at 18:37
  • 1
    kqueue support (file notifications for OS X and *BSD) has been added in Emacs 25.1. – Michael Albinus Nov 24 '16 at 18:39
  • Hmmm… OK, time to upgrade I guess. Thanks! – Resigned June 2023 Nov 24 '16 at 18:45
  • Actually, there's another problem with this approach. As per [this Emacs bug](https://debbugs.gnu.org/cgi/bugreport.cgi?bug=22814), `auto-revert-use-notify` is disabled for `global-auto-revert-mode`. So if you have `global-auto-revert-mode` enabled, then `auto-revert` does indeed still poll. – Resigned June 2023 Nov 24 '16 at 20:39
  • Actually, this is re-enabled per default in Emacs 26. And if you know that you are not setting hundreds of files under notification, you could also enable this in your .emacs. – Michael Albinus Nov 25 '16 at 09:58
  • Although I don't usually have very many files open in Emacs, I am very uncomfortable with the possibility of nasty things happening on those rare occasions where I might open a few hundred files. Especially because I want my Emacs configuration to be usable by other people, who might have different workflows (which might involve opening lots of files). – Resigned June 2023 Nov 25 '16 at 17:33
1

The following lisp snippet installs a slight variation of what you want. It reverts all buffers that have windows in the current frame. So everything what you actually see is reverted.

If you insist that only the current buffer should be reverted you can change the filter to something like (lambda (buf) (eq (car (window-list)) (get-buffer-window buf))).

(require 'cl-lib)
(require 'autorevert)

(defvar auto-revert-some-buffers-filter #'get-buffer-window
  "Filter for the output of `buffer-list' in `auto-revert-buffers'.
The function is called with a buffer as argument.
It should return a non-nil value if this buffer should really be auto-reverted.")

(defun auto-revert-some-buffers-advice--buffer-list (ret)
  "Filter output of the first call of `buffer-list' in `auto-revert-buffers'.
This filter de-installs itself after this call."
  (advice-remove #'buffer-list #'auto-revert-some-buffers-advice--buffer-list)
  (cl-remove-if-not auto-revert-some-buffers-filter ret))

(defun auto-revert-some-buffers-advice (oldfun &rest args)
  "Filter the buffers to be auto-reverted through `auto-revert-some-buffers-filter' (which see)."
  (let (ret)
    (if global-auto-revert-mode
    (unwind-protect
        (progn
          (advice-add #'buffer-list :filter-return #'auto-revert-some-buffers-advice--buffer-list)
          (setq ret (apply oldfun args)))
      (advice-remove #'buffer-list #'auto-revert-some-buffers-advice--buffer-list) ;; being over-protective
      )
      (let ((old-auto-revert-buffer-list (cl-remove-if-not auto-revert-some-buffers-filter auto-revert-buffer-list))
        ;; Note: We interpret `auto-revert-remaining-buffers' as transient effect and don't filter this list.
        deleted-buffers)
    (let ((auto-revert-buffer-list old-auto-revert-buffer-list))
      (setq ret (apply oldfun args))
      (setq deleted-buffers (cl-set-difference old-auto-revert-buffer-list auto-revert-buffer-list)))
    (setq auto-revert-buffer-list (cl-set-difference auto-revert-buffer-list deleted-buffers))))
    ret))

(advice-add #'auto-revert-buffers :around #'auto-revert-some-buffers-advice)

An alternative would be to modify auto-revert-buffers. This would only make sense as a feature-request to the package maintainer since there might be incompatible changes in the future development of autorevert. The following example is based on autorevert.el shipped with GNU Emacs 25.1.1 (x86_64-unknown-cygwin, GTK+ Version 3.18.9) of 2016-09-17. The relevant small changes are the introduction of the variable auto-revert-buffer-filter (should maybe better be a defcustom) and the insertion of (cl-remove-if-not auto-revert-buffer-filter ...) into auto-revert-buffers.

(require 'cl-lib)
(require 'autorevert)

(defvar auto-revert-buffer-filter #'get-buffer-window
  "Filter for the output of `buffer-list' in `auto-revert-buffers'.
The function is called with a buffer as argument.
It should return a non-nil value if this buffer should really be auto-reverted.")

(defun auto-revert-buffers ()
  "Revert buffers as specified by Auto-Revert and Global Auto-Revert Mode.

Should `global-auto-revert-mode' be active all file buffers are checked.

Should `auto-revert-mode' be active in some buffers, those buffers
are checked.

Non-file buffers that have a custom `revert-buffer-function' and
`buffer-stale-function' are reverted either when Auto-Revert
Mode is active in that buffer, or when the variable
`global-auto-revert-non-file-buffers' is non-nil and Global
Auto-Revert Mode is active.

This function stops whenever there is user input.  The buffers not
checked are stored in the variable `auto-revert-remaining-buffers'.

To avoid starvation, the buffers in `auto-revert-remaining-buffers'
are checked first the next time this function is called.

This function is also responsible for removing buffers no longer in
Auto-Revert mode from `auto-revert-buffer-list', and for canceling
the timer when no buffers need to be checked."

  (setq auto-revert-buffers-counter
        (1+ auto-revert-buffers-counter))

  (save-match-data
    (let ((bufs (cl-remove-if-not auto-revert-buffer-filter (if global-auto-revert-mode
            (buffer-list)
            auto-revert-buffer-list)))
      remaining new)
      ;; Partition `bufs' into two halves depending on whether or not
      ;; the buffers are in `auto-revert-remaining-buffers'.  The two
      ;; halves are then re-joined with the "remaining" buffers at the
      ;; head of the list.
      (dolist (buf auto-revert-remaining-buffers)
    (if (memq buf bufs)
        (push buf remaining)))
      (dolist (buf bufs)
    (if (not (memq buf remaining))
        (push buf new)))
      (setq bufs (nreverse (nconc new remaining)))
      (while (and bufs
          (not (and auto-revert-stop-on-user-input
                (input-pending-p))))
    (let ((buf (car bufs)))
          (if (buffer-live-p buf)
          (with-current-buffer buf
        ;; Test if someone has turned off Auto-Revert Mode in a
        ;; non-standard way, for example by changing major mode.
        (if (and (not auto-revert-mode)
             (not auto-revert-tail-mode)
             (memq buf auto-revert-buffer-list))
            (setq auto-revert-buffer-list
              (delq buf auto-revert-buffer-list)))
        (when (auto-revert-active-p)
          ;; Enable file notification.
          (when (and auto-revert-use-notify
                 (not auto-revert-notify-watch-descriptor))
            (auto-revert-notify-add-watch))
          (auto-revert-handler)))
        ;; Remove dead buffer from `auto-revert-buffer-list'.
        (setq auto-revert-buffer-list
          (delq buf auto-revert-buffer-list))))
    (setq bufs (cdr bufs)))
      (setq auto-revert-remaining-buffers bufs)
      ;; Check if we should cancel the timer.
      (when (and (not global-auto-revert-mode)
         (null auto-revert-buffer-list))
    (cancel-timer auto-revert-timer)
    (setq auto-revert-timer nil)))))
Tobias
  • 32,569
  • 1
  • 34
  • 75
  • This works great, thanks! Is there a way to automatically revert a buffer when it is first made visible (e.g. by `find-file` or `switch-to-buffer`)? That way you wouldn't even have to wait for `auto-revert-interval` to pass before the buffer was reverted. – Resigned June 2023 Nov 26 '16 at 03:54
  • n.b. I was able to make `find-file` behave as desired by adding `(setq revert-without-query '(".*"))`, but `switch-to-buffer` still doesn't have the behavior I would like. – Resigned June 2023 Nov 26 '16 at 03:55
  • @RadonRosborough For `switch-to-buffer` the line `(add-hook 'buffer-list-update-hook #'auto-revert-handler)` should do the trick. – Tobias Nov 28 '16 at 11:47
  • What about `previous-buffer` and `next-buffer`? I can always add an advice, of course, but I was wondering if there is a general way that will work whenever a buffer is displayed? – Resigned June 2023 Nov 28 '16 at 20:06
  • @RadonRosborough `buffer-list-update-hook` is applied for all functions that visibly change the buffer list which may involve its `car`. That should be exactly what you want. – Tobias Nov 28 '16 at 20:18
  • @RadonRosborough Note that those functions that you mention call directly or indirectly `set-window-buffer` which in turn calls `record-window-buffer` and that one applies `buffer-list-update-hook`. This all works only for functions where the `norecord` argument of `select-window` is `nil`. But the `norecord` argument is for functions that only temporarily select a window. Thus the hook should work for all window changes visible to the user. – Tobias Nov 28 '16 at 20:28
  • Hmm… What I did to test was find file `init.el`, then switch to the `*scratch*` buffer, then `touch init.el`. If I used `C-x b` to switch back to `init.el`, then it was reverted immediately. But if I used `C-x ` to switch back, then it wasn't reverted immediately. – Resigned June 2023 Nov 28 '16 at 21:19
  • Also, there's a more serious problem: with that hook added, occasionally I get "Error running timer 'auto-revert-buffers': (error "Lisp nesting exceeds 'max-lisp-eval-depth'")" and Emacs refuses to do anything. – Resigned June 2023 Nov 29 '16 at 16:48
  • @RadonRosborough That is really interesting. The hooks in `buffer-list-update-hook` run at `next-buffer`. That can be checked by `(defun mymessage () (message "*** current: %s, selected-window-buffer: %s ***" (current-buffer) (window-buffer (selected-window)))) (add-hook 'buffer-list-update-hook #'mymessage)`. The problem is that the old buffer is still current and the old window is still active. I think this problem is worth a separate question here on SE: How should a reaction on the change of the visible current buffer/change of the active window be implemented? – Tobias Nov 30 '16 at 05:52
0

The second solution provided by @Tobias can be improved using my library el-patch:

(el-patch-defun auto-revert-buffers ()
  "Revert buffers as specified by Auto-Revert and Global Auto-Revert Mode.

Should `global-auto-revert-mode' be active all file buffers are checked.

Should `auto-revert-mode' be active in some buffers, those buffers
are checked.

Non-file buffers that have a custom `revert-buffer-function' and
`buffer-stale-function' are reverted either when Auto-Revert
Mode is active in that buffer, or when the variable
`global-auto-revert-non-file-buffers' is non-nil and Global
Auto-Revert Mode is active.

This function stops whenever there is user input.  The buffers not
checked are stored in the variable `auto-revert-remaining-buffers'.

To avoid starvation, the buffers in `auto-revert-remaining-buffers'
are checked first the next time this function is called.

This function is also responsible for removing buffers no longer in
Auto-Revert mode from `auto-revert-buffer-list', and for canceling
the timer when no buffers need to be checked."

  (setq auto-revert-buffers-counter
        (1+ auto-revert-buffers-counter))

  (save-match-data
    (let ((bufs (el-patch-wrap 2
                  (cl-remove-if-not
                   #'get-buffer-window
                   (if global-auto-revert-mode
                       (buffer-list)
                     auto-revert-buffer-list))))
          remaining new)
      ;; Partition `bufs' into two halves depending on whether or not
      ;; the buffers are in `auto-revert-remaining-buffers'.  The two
      ;; halves are then re-joined with the "remaining" buffers at the
      ;; head of the list.
      (dolist (buf auto-revert-remaining-buffers)
        (if (memq buf bufs)
            (push buf remaining)))
      (dolist (buf bufs)
        (if (not (memq buf remaining))
            (push buf new)))
      (setq bufs (nreverse (nconc new remaining)))
      (while (and bufs
                  (not (and auto-revert-stop-on-user-input
                            (input-pending-p))))
        (let ((buf (car bufs)))
          (if (buffer-live-p buf)
              (with-current-buffer buf
                ;; Test if someone has turned off Auto-Revert Mode in a
                ;; non-standard way, for example by changing major mode.
                (if (and (not auto-revert-mode)
                         (not auto-revert-tail-mode)
                         (memq buf auto-revert-buffer-list))
                    (setq auto-revert-buffer-list
                          (delq buf auto-revert-buffer-list)))
                (when (auto-revert-active-p)
                  ;; Enable file notification.
                  (when (and auto-revert-use-notify
                             (not auto-revert-notify-watch-descriptor))
                    (auto-revert-notify-add-watch))
                  (auto-revert-handler)))
            ;; Remove dead buffer from `auto-revert-buffer-list'.
            (setq auto-revert-buffer-list
                  (delq buf auto-revert-buffer-list))))
        (setq bufs (cdr bufs)))
      (setq auto-revert-remaining-buffers bufs)
      ;; Check if we should cancel the timer.
      (when (and (not global-auto-revert-mode)
                 (null auto-revert-buffer-list))
        (cancel-timer auto-revert-timer)
        (setq auto-revert-timer nil)))))
Resigned June 2023
  • 1,502
  • 15
  • 20
0

You can use something like this to toggle auto-revert (or some other minor mode) on and off when the current buffer changes:

(defun auto-revert-active-buffer (window)
  (with-current-buffer (window-buffer window)
    (auto-revert-mode
     (or (eq (current-buffer) (window-buffer (selected-window))) -1))))

(add-hook 'buffer-list-update-hook (lambda () (walk-windows #'auto-revert-active-buffer nil t)))
glucas
  • 20,175
  • 1
  • 51
  • 83