X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/71c8db4cd53ac3da32bd4fd2f76b9f1124675026..1b74c4346e92c9ac1ae0575c2ad69f8d81126d7e:/lisp/autorevert.el diff --git a/lisp/autorevert.el b/lisp/autorevert.el index 6e74a96eff..9892dca2d4 100644 --- a/lisp/autorevert.el +++ b/lisp/autorevert.el @@ -1,6 +1,7 @@ ;;; autorevert.el --- revert buffers when files on disk change -;; Copyright (C) 1997, 1998, 1999, 2001 Free Software Foundation, Inc. +;; Copyright (C) 1997, 1998, 1999, 2001, 2002, 2003, 2004, +;; 2005 Free Software Foundation, Inc. ;; Author: Anders Lindgren ;; Keywords: convenience @@ -21,8 +22,8 @@ ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs; see the file COPYING. If not, write to the -;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, -;; Boston, MA 02111-1307, USA. +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. ;;; Commentary: @@ -34,21 +35,45 @@ ;; ;; This package contains two minor modes: Global Auto-Revert Mode and ;; Auto-Revert Mode. Both modes automatically revert buffers -;; whenever the corresponding files have been changed on disk. +;; whenever the corresponding files have been changed on disk and the +;; buffer contains no unsaved changes. ;; -;; Auto-Revert Mode can be activated for individual buffers. -;; Global Auto-Revert Mode applies to all file buffers. +;; Auto-Revert Mode can be activated for individual buffers. Global +;; Auto-Revert Mode applies to all file buffers. (If the user option +;; `global-auto-revert-non-file-buffers' is non-nil, it also applies +;; to some non-file buffers. This option is disabled by default.) +;; Since checking a remote file is too slow, these modes do not check +;; or revert remote files. ;; ;; Both modes operate by checking the time stamp of all files at ;; intervals of `auto-revert-interval'. The default is every five ;; seconds. The check is aborted whenever the user actually uses ;; Emacs. You should never even notice that this package is active ;; (except that your buffers will be reverted, of course). +;; +;; After reverting a file buffer, Auto Revert Mode normally puts point +;; at the same position that a regular manual revert would. However, +;; there is one exception to this rule. If point is at the end of the +;; buffer before reverting, it stays at the end. Similarly if point +;; is displayed at the end of a file buffer in any window, it will stay +;; at the end of the buffer in that window, even if the window is not +;; selected. This way, you can use Auto Revert Mode to `tail' a file. +;; Just put point at the end of the buffer and it will stay there. +;; These rules apply to file buffers. For non-file buffers, the +;; behavior may be mode dependent. +;; +;; While you can use Auto Revert Mode to tail a file, this package +;; contains a third minor mode, Auto Revert Tail Mode, which does so +;; more efficiently, as long as you are sure that the file will only +;; change by growing at the end. It only appends the new output, +;; instead of reverting the entire buffer. It does so even if the +;; buffer contains unsaved changes. (Because they will not be lost.) ;; Usage: ;; -;; Go to the appropriate buffer and press: +;; Go to the appropriate buffer and press either of: ;; M-x auto-revert-mode RET +;; M-x auto-revert-tail-mode RET ;; ;; To activate Global Auto-Revert Mode, press: ;; M-x global-auto-revert-mode RET @@ -90,13 +115,19 @@ Global Auto-Revert Mode applies to all buffers." ;; Variables: -;; Autoload for the benefit of `make-mode-line-mouse-sensitive'. -;;;###autoload +;;; What's this?: ;; Autoload for the benefit of `make-mode-line-mouse-sensitive'. +;;; What's this?: ;;;###autoload (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.") @@ -120,12 +151,18 @@ next editing session." (auto-revert-set-timer)))) (defcustom auto-revert-stop-on-user-input t - "When non-nil Auto-Revert Mode stops checking files on user input." + "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 will not generate any messages. + "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) @@ -138,6 +175,14 @@ When non-nil, a message is generated whenever a file is reverted." :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" ; To separate it from `global-...' @@ -159,22 +204,23 @@ would only waste precious space." :type 'hook) (defcustom global-auto-revert-non-file-buffers nil - "When nil only file buffers are reverted by Global Auto-Revert Mode. + "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. - -Use this option with care since it could lead to excessive reverts. -Note also that for some non-file buffers the check whether the -buffer needs updating may be imperfect, due to efficiency -considerations, and may not take all information listed in the -buffer into account. Hence, a non-nil value for this option does -not necessarily make manual updates useless for non-file buffers." +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-xtra)Autorevert'." :group 'auto-revert - :type 'boolean) + :type 'boolean + :link '(info-link "(emacs-xtra)Autorevert")) -(defcustom global-auto-revert-ignore-modes '() +(defcustom global-auto-revert-ignore-modes () "List of major modes Global Auto-Revert Mode should not check." :group 'auto-revert :type '(repeat sexp)) @@ -201,10 +247,10 @@ 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" +buffers. CPU usage depends on the version control system." :group 'auto-revert :type 'boolean - :version "21.4") + :version "22.1") (defvar global-auto-revert-ignore-buffer nil "*When non-nil, Global Auto-Revert Mode will not revert this buffer. @@ -214,7 +260,7 @@ This variable becomes buffer local when set in any fashion.") ;; Internal variables: -(defvar auto-revert-buffer-list '() +(defvar auto-revert-buffer-list () "List of buffers in Auto-Revert Mode. Note that only Auto-Revert Mode, never Global Auto-Revert Mode, adds @@ -223,9 +269,16 @@ buffers to this list. The timer function `auto-revert-buffers' is responsible for purging the list of old buffers.") -(defvar auto-revert-remaining-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) + (save-restriction (widen) (1- (point-max)))))) ;; Functions: @@ -235,8 +288,10 @@ the list of old buffers.") 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." - nil auto-revert-mode-text nil +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)) @@ -244,7 +299,8 @@ Use `global-auto-revert-mode' to automatically revert all buffers." (delq (current-buffer) auto-revert-buffer-list))) (auto-revert-set-timer) (when auto-revert-mode - (auto-revert-buffers))) + (auto-revert-buffers) + (setq auto-revert-tail-mode nil))) ;;;###autoload @@ -256,6 +312,52 @@ This function is designed to be added to hooks, for example: (auto-revert-mode 1)) +;;;###autoload +(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) ; library was loaded only after finding file + (not (y-or-n-p "Buffer is modified, so tail offset may be wrong. Proceed? "))) + (auto-revert-tail-mode 0) + ;; else we might reappend our own end when we save + (add-hook 'before-save-hook (lambda () (auto-revert-tail-mode 0)) nil t) + (or (local-variable-p 'auto-revert-tail-pos) ; don't lose prior position + (set (make-local-variable 'auto-revert-tail-pos) + (save-restriction (widen) (1- (point-max))))) + ;; let auto-revert-mode set up the mechanism for us if it isn't already + (or auto-revert-mode + (let ((auto-revert-tail-mode t)) + (auto-revert-mode 1))) + (setq auto-revert-mode nil)))) + + +;;;###autoload +(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)) + + ;;;###autoload (define-minor-mode global-auto-revert-mode "Revert any buffer when file on disk changes. @@ -282,12 +384,12 @@ will use an up-to-date value of `auto-revert-interval'" (if (or global-auto-revert-mode auto-revert-buffer-list) (run-with-timer auto-revert-interval auto-revert-interval - 'auto-revert-buffers) - nil))) + '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) @@ -297,27 +399,69 @@ will use an up-to-date value of `auto-revert-interval'" (defun auto-revert-handler () "Revert current buffer, if appropriate. This is an internal function used by Auto-Revert Mode." - (unless (buffer-modified-p) - (let (revert) - (or (and (buffer-file-name) - (file-readable-p (buffer-file-name)) - (not (verify-visited-file-modtime (current-buffer))) - (setq revert t)) - (and (or auto-revert-mode global-auto-revert-non-file-buffers) - revert-buffer-function - (boundp 'buffer-stale-function) - (functionp buffer-stale-function) - (setq revert (funcall buffer-stale-function t)))) + (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))) - (revert-buffer 'ignore-auto 'dont-ask 'preserve-modes)) + ;; If point (or a window point) is at the end of the buffer, + ;; we want to keep it at the end after reverting. This allows + ;; to tail a file. + (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) + ;; Bind buffer-read-only in case user has done C-x C-q, + ;; so as not to forget that. This gives undesirable results + ;; when the file's mode changes, but that is less common. + (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))))) ;; `preserve-modes' avoids changing the (minor) modes. But we ;; do want to reset the mode for VC, so we do it manually. (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 ; ignore + (file buffer-file-name) + buffer-file-name) ; ignore that file has changed + (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))) + (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. @@ -341,45 +485,47 @@ 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." - (let ((bufs (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-name buf) ; Buffer still alive? - (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) - (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))) - ;; 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)))) + (save-match-data + (let ((bufs (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-name buf) ; Buffer still alive? + (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) (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))))) ;; The end: