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?

4 Answers 4

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)))))
9
  • 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. Commented Nov 26, 2016 at 3: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. Commented Nov 26, 2016 at 3:55
  • @RadonRosborough For switch-to-buffer the line (add-hook 'buffer-list-update-hook #'auto-revert-handler) should do the trick.
    – Tobias
    Commented Nov 28, 2016 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? Commented Nov 28, 2016 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
    Commented Nov 28, 2016 at 20:18
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.

8
  • 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
    Commented Nov 24, 2016 at 15:59
  • Does this mean that auto-revert-interval has no effect, then? Commented Nov 24, 2016 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. Commented Nov 24, 2016 at 18:37
  • 1
    kqueue support (file notifications for OS X and *BSD) has been added in Emacs 25.1. Commented Nov 24, 2016 at 18:39
  • Hmmm… OK, time to upgrade I guess. Thanks! Commented Nov 24, 2016 at 18:45
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)))))
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)))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.