(require 'timer)
(eval-when-compile (require 'cl))
(defgroup auto-revert nil
"Revert individual buffers when files on disk change.
Auto-Revert Mode can be activated for individual buffer.
Global Auto-Revert Mode applies to all buffers."
:group 'files
:group 'convenience)
(defvar auto-revert-mode nil
"*Non-nil when Auto-Revert Mode is active.
Never set this variable directly, use the command `auto-revert-mode' instead.")
(put 'auto-revert-mode 'permanent-local t)
(defvar auto-revert-tail-mode nil
"*Non-nil when Auto-Revert Tail Mode is active.
Never set this variable directly, use the command
`auto-revert-tail-mode' instead.")
(put 'auto-revert-tail-mode 'permanent-local t)
(defvar auto-revert-timer nil
"Timer used by Auto-Revert Mode.")
(defcustom auto-revert-interval 5
"Time, in seconds, between Auto-Revert Mode file checks.
The value may be an integer or floating point number.
If a timer is already active, there are two ways to make sure
that the new value will take effect immediately. You can set
this variable through Custom or you can call the command
`auto-revert-set-timer' after setting the variable. Otherwise,
the new value will take effect the first time Auto Revert Mode
calls `auto-revert-set-timer' for internal reasons or in your
next editing session."
:group 'auto-revert
:type 'number
:set (lambda (variable value)
(set-default variable value)
(and (boundp 'auto-revert-timer)
auto-revert-timer
(auto-revert-set-timer))))
(defcustom auto-revert-stop-on-user-input t
"When non-nil, user input temporarily interrupts Auto-Revert Mode.
With this setting, Auto-Revert Mode checks for user input after
handling each buffer and does not process any further buffers
\(until the next run of the timer) if user input is available.
When nil, Auto-Revert Mode checks files and reverts buffers,
with quitting disabled, without paying attention to user input.
Thus, with this setting, Emacs might be non-responsive at times."
:group 'auto-revert
:type 'boolean)
(defcustom auto-revert-verbose t
"When nil, Auto-Revert Mode does not generate any messages.
When non-nil, a message is generated whenever a file is reverted."
:group 'auto-revert
:type 'boolean)
(defcustom auto-revert-mode-text " ARev"
"String to display in the mode line when Auto-Revert Mode is active.
\(When the string is not empty, make sure that it has a leading space.)"
:tag "Auto Revert Mode Text" :group 'auto-revert
:type 'string)
(defcustom auto-revert-tail-mode-text " Tail"
"String to display in the mode line when Auto-Revert Tail Mode is active.
\(When the string is not empty, make sure that it has a leading space.)"
:group 'auto-revert
:type 'string
:version "22.1")
(defcustom auto-revert-mode-hook nil
"Functions to run when Auto-Revert Mode is activated."
:tag "Auto Revert Mode Hook" :group 'auto-revert
:type 'hook)
(defcustom global-auto-revert-mode-text ""
"String to display when Global Auto-Revert Mode is active.
The default is nothing since when this mode is active this text doesn't
vary over time, or between buffers. Hence mode line text
would only waste precious space."
:group 'auto-revert
:type 'string)
(defcustom global-auto-revert-mode-hook nil
"Hook called when Global Auto-Revert Mode is activated."
:group 'auto-revert
:type 'hook)
(defcustom global-auto-revert-non-file-buffers nil
"When nil, Global Auto-Revert mode operates only on file-visiting buffers.
When non-nil, both file buffers and buffers with a custom
`revert-buffer-function' and a `buffer-stale-function' are
reverted by Global Auto-Revert mode. These include the Buffer
List buffer, and Dired buffers showing complete local
directories. Dired buffers do not auto-revert as a result of
changes in subdirectories or in the contents, size, modes, etc.,
of files. You may still sometimes want to revert them manually.
Use this option with care since it could lead to excessive auto-reverts.
For more information, see Info node `(emacs)Autorevert'."
:group 'auto-revert
:type 'boolean
:link '(info-link "(emacs)Autorevert"))
(defcustom global-auto-revert-ignore-modes ()
"List of major modes Global Auto-Revert Mode should not check."
:group 'auto-revert
:type '(repeat sexp))
(defcustom auto-revert-load-hook nil
"Functions to run when Auto-Revert Mode is first loaded."
:tag "Load Hook"
:group 'auto-revert
:type 'hook)
(defcustom auto-revert-check-vc-info nil
"If non-nil Auto Revert Mode reliably updates version control info.
Auto Revert Mode updates version control info whenever the buffer
needs reverting, regardless of the value of this variable.
However, the version control state can change without changes to
the work file. If the change is made from the current Emacs
session, all info is updated. But if, for instance, a new
version is checked in from outside the current Emacs session, the
version control number in the mode line, as well as other version
control related information, may not be properly updated. If you
are worried about this, set this variable to a non-nil value.
This currently works by automatically updating the version
control info every `auto-revert-interval' seconds. Nevertheless,
it should not cause excessive CPU usage on a reasonably fast
machine, if it does not apply to too many version controlled
buffers. CPU usage depends on the version control system."
:group 'auto-revert
:type 'boolean
:version "22.1")
(defvar global-auto-revert-ignore-buffer nil
"*When non-nil, Global Auto-Revert Mode will not revert this buffer.
This variable becomes buffer local when set in any fashion.")
(make-variable-buffer-local 'global-auto-revert-ignore-buffer)
(defvar auto-revert-buffer-list ()
"List of buffers in Auto-Revert Mode.
Note that only Auto-Revert Mode, never Global Auto-Revert Mode, adds
buffers to this list.
The timer function `auto-revert-buffers' is responsible for purging
the list of old buffers.")
(defvar auto-revert-remaining-buffers ()
"Buffers not checked when user input stopped execution.")
(defvar auto-revert-tail-pos 0
"Position of last known end of file.")
(add-hook 'find-file-hook
(lambda ()
(set (make-local-variable 'auto-revert-tail-pos)
(nth 7 (file-attributes buffer-file-name)))))
(define-minor-mode auto-revert-mode
"Toggle reverting buffer when file on disk changes.
With arg, turn Auto Revert mode on if and only if arg is positive.
This is a minor mode that affects only the current buffer.
Use `global-auto-revert-mode' to automatically revert all buffers.
Use `auto-revert-tail-mode' if you know that the file will only grow
without being changed in the part that is already in the buffer."
:group 'auto-revert :lighter auto-revert-mode-text
(if auto-revert-mode
(if (not (memq (current-buffer) auto-revert-buffer-list))
(push (current-buffer) auto-revert-buffer-list))
(setq auto-revert-buffer-list
(delq (current-buffer) auto-revert-buffer-list)))
(auto-revert-set-timer)
(when auto-revert-mode
(auto-revert-buffers)
(setq auto-revert-tail-mode nil)))
(defun turn-on-auto-revert-mode ()
"Turn on Auto-Revert Mode.
This function is designed to be added to hooks, for example:
(add-hook 'c-mode-hook 'turn-on-auto-revert-mode)"
(auto-revert-mode 1))
(define-minor-mode auto-revert-tail-mode
"Toggle reverting tail of buffer when file on disk grows.
With arg, turn Tail mode on iff arg is positive.
When Tail mode is enabled, the tail of the file is constantly
followed, as with the shell command `tail -f'. This means that
whenever the file grows on disk (presumably because some
background process is appending to it from time to time), this is
reflected in the current buffer.
You can edit the buffer and turn this mode off and on again as
you please. But make sure the background process has stopped
writing before you save the file!
Use `auto-revert-mode' for changes other than appends!"
:group 'find-file :lighter auto-revert-tail-mode-text
(when auto-revert-tail-mode
(unless buffer-file-name
(auto-revert-tail-mode 0)
(error "This buffer is not visiting a file"))
(if (and (buffer-modified-p)
(not auto-revert-tail-pos) (not (y-or-n-p "Buffer is modified, so tail offset may be wrong. Proceed? ")))
(auto-revert-tail-mode 0)
(add-hook 'before-save-hook (lambda () (auto-revert-tail-mode 0)) nil t)
(or (local-variable-p 'auto-revert-tail-pos) (set (make-local-variable 'auto-revert-tail-pos)
(nth 7 (file-attributes buffer-file-name))))
(or auto-revert-mode
(let ((auto-revert-tail-mode t))
(auto-revert-mode 1)))
(setq auto-revert-mode nil))))
(defun turn-on-auto-revert-tail-mode ()
"Turn on Auto-Revert Tail Mode.
This function is designed to be added to hooks, for example:
(add-hook 'my-logfile-mode-hook 'turn-on-auto-revert-tail-mode)"
(auto-revert-tail-mode 1))
(define-minor-mode global-auto-revert-mode
"Revert any buffer when file on disk changes.
With arg, turn Auto Revert mode on globally if and only if arg is positive.
This is a minor mode that affects all buffers.
Use `auto-revert-mode' to revert a particular buffer."
:global t :group 'auto-revert :lighter global-auto-revert-mode-text
(auto-revert-set-timer)
(when global-auto-revert-mode
(auto-revert-buffers)))
(defun auto-revert-set-timer ()
"Restart or cancel the timer used by Auto-Revert Mode.
If such a timer is active, cancel it. Start a new timer if
Global Auto-Revert Mode is active or if Auto-Revert Mode is active
in some buffer. Restarting the timer ensures that Auto-Revert Mode
will use an up-to-date value of `auto-revert-interval'"
(interactive)
(if (timerp auto-revert-timer)
(cancel-timer auto-revert-timer))
(setq auto-revert-timer
(if (or global-auto-revert-mode auto-revert-buffer-list)
(run-with-timer auto-revert-interval
auto-revert-interval
'auto-revert-buffers))))
(defun auto-revert-active-p ()
"Check if auto-revert is active (in current buffer or globally)."
(or auto-revert-mode
auto-revert-tail-mode
(and
global-auto-revert-mode
(not global-auto-revert-ignore-buffer)
(not (memq major-mode
global-auto-revert-ignore-modes)))))
(defun auto-revert-handler ()
"Revert current buffer, if appropriate.
This is an internal function used by Auto-Revert Mode."
(when (or auto-revert-tail-mode (not (buffer-modified-p)))
(let* ((buffer (current-buffer))
(revert
(or (and buffer-file-name
(not (file-remote-p buffer-file-name))
(file-readable-p buffer-file-name)
(not (verify-visited-file-modtime buffer)))
(and (or auto-revert-mode
global-auto-revert-non-file-buffers)
revert-buffer-function
(boundp 'buffer-stale-function)
(functionp buffer-stale-function)
(funcall buffer-stale-function t))))
eob eoblist)
(when revert
(when (and auto-revert-verbose
(not (eq revert 'fast)))
(message "Reverting buffer `%s'." (buffer-name)))
(when buffer-file-name
(setq eob (eobp))
(walk-windows
#'(lambda (window)
(and (eq (window-buffer window) buffer)
(= (window-point window) (point-max))
(push window eoblist)))
'no-mini t))
(if auto-revert-tail-mode
(auto-revert-tail-handler)
(let ((buffer-read-only buffer-read-only))
(revert-buffer 'ignore-auto 'dont-ask 'preserve-modes)))
(when buffer-file-name
(when eob (goto-char (point-max)))
(dolist (window eoblist)
(set-window-point window (point-max)))))
(when (or revert auto-revert-check-vc-info)
(vc-find-file-hook)))))
(defun auto-revert-tail-handler ()
(let ((size (nth 7 (file-attributes buffer-file-name)))
(modified (buffer-modified-p))
buffer-read-only (file buffer-file-name)
buffer-file-name) (when (> size auto-revert-tail-pos)
(undo-boundary)
(save-restriction
(widen)
(save-excursion
(goto-char (point-max))
(insert-file-contents file nil auto-revert-tail-pos size)))
(run-mode-hooks 'after-revert-hook)
(undo-boundary)
(setq auto-revert-tail-pos size)
(set-buffer-modified-p modified)))
(set-visited-file-modtime))
(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
a `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."
(save-match-data
(let ((bufs (if global-auto-revert-mode
(buffer-list)
auto-revert-buffer-list))
(remaining ())
(new ()))
(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-name buf) (with-current-buffer buf
(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) (auto-revert-handler)))
(setq auto-revert-buffer-list
(delq buf auto-revert-buffer-list))))
(setq bufs (cdr bufs)))
(setq auto-revert-remaining-buffers bufs)
(when (and (not global-auto-revert-mode)
(null auto-revert-buffer-list))
(cancel-timer auto-revert-timer)
(setq auto-revert-timer nil)))))
(provide 'autorevert)
(run-hooks 'auto-revert-load-hook)