Commit | Line | Data |
---|---|---|
e8af40ee | 1 | ;;; autorevert.el --- revert buffers when files on disk change |
4a35aff3 | 2 | |
bf2150fa | 3 | ;; Copyright (C) 1997, 1998, 1999, 2001 Free Software Foundation, Inc. |
4a35aff3 | 4 | |
a468671a | 5 | ;; Author: Anders Lindgren <andersl@andersl.com> |
f5f727f8 | 6 | ;; Keywords: convenience |
a468671a GM |
7 | ;; Created: 1997-06-01 |
8 | ;; Date: 1999-11-30 | |
4a35aff3 RS |
9 | |
10 | ;; This file is part of GNU Emacs. | |
11 | ||
12 | ;; GNU Emacs is free software; you can redistribute it and/or modify | |
13 | ;; it under the terms of the GNU General Public License as published by | |
14 | ;; the Free Software Foundation; either version 2, or (at your option) | |
15 | ;; any later version. | |
16 | ||
17 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | ;; GNU General Public License for more details. | |
21 | ||
22 | ;; You should have received a copy of the GNU General Public License | |
23 | ;; along with GNU Emacs; see the file COPYING. If not, write to the | |
24 | ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
25 | ;; Boston, MA 02111-1307, USA. | |
26 | ||
27 | ;;; Commentary: | |
28 | ||
29 | ;; Introduction: | |
30 | ;; | |
31 | ;; Whenever a file that Emacs is editing has been changed by another | |
48764ae2 | 32 | ;; program the user normally has to execute the command `revert-buffer' |
4a35aff3 RS |
33 | ;; to load the new content of the file into Emacs. |
34 | ;; | |
35 | ;; This package contains two minor modes: Global Auto-Revert Mode and | |
48764ae2 | 36 | ;; Auto-Revert Mode. Both modes automatically revert buffers |
4a35aff3 RS |
37 | ;; whenever the corresponding files have been changed on disk. |
38 | ;; | |
39 | ;; Auto-Revert Mode can be activated for individual buffers. | |
40 | ;; Global Auto-Revert Mode applies to all file buffers. | |
41 | ;; | |
48764ae2 DL |
42 | ;; Both modes operate by checking the time stamp of all files at |
43 | ;; intervals of `auto-revert-interval'. The default is every five | |
44 | ;; seconds. The check is aborted whenever the user actually uses | |
45 | ;; Emacs. You should never even notice that this package is active | |
46 | ;; (except that your buffers will be reverted, of course). | |
4a35aff3 RS |
47 | |
48 | ;; Usage: | |
49 | ;; | |
50 | ;; Go to the appropriate buffer and press: | |
51 | ;; M-x auto-revert-mode RET | |
52 | ;; | |
53 | ;; To activate Global Auto-Revert Mode, press: | |
54 | ;; M-x global-auto-revert-mode RET | |
55 | ;; | |
48764ae2 DL |
56 | ;; To activate Global Auto-Revert Mode every time Emacs is started |
57 | ;; customise the option `global-auto-revert-mode' or the following | |
58 | ;; line could be added to your ~/.emacs: | |
4a35aff3 RS |
59 | ;; (global-auto-revert-mode 1) |
60 | ;; | |
61 | ;; The function `turn-on-auto-revert-mode' could be added to any major | |
62 | ;; mode hook to activate Auto-Revert Mode for all buffers in that | |
63 | ;; mode. For example, the following line will activate Auto-Revert | |
64 | ;; Mode in all C mode buffers: | |
65 | ;; | |
66 | ;; (add-hook 'c-mode-hook 'turn-on-auto-revert-mode) | |
67 | ||
68 | ;;; Code: | |
69 | ||
70 | ;; Dependencies: | |
71 | ||
72 | (require 'timer) | |
0f98bc23 | 73 | (autoload 'dired-get-filename "dired") |
4e664f62 EZ |
74 | (autoload 'vc-workfile-version "vc-hooks") |
75 | (autoload 'vc-mode-line "vc-hooks") | |
0f98bc23 EZ |
76 | |
77 | (eval-when-compile | |
78 | (defvar dired-directory) | |
4e664f62 | 79 | (defvar vc-mode) |
0f98bc23 | 80 | (require 'cl)) |
4a35aff3 RS |
81 | |
82 | ||
83 | ;; Custom Group: | |
84 | ;; | |
85 | ;; The two modes will be placed next to Auto Save Mode under the | |
86 | ;; Files group under Emacs. | |
87 | ||
88 | (defgroup auto-revert nil | |
48764ae2 | 89 | "Revert individual buffers when files on disk change. |
4a35aff3 RS |
90 | |
91 | Auto-Revert Mode can be activated for individual buffer. | |
92 | Global Auto-Revert Mode applies to all buffers." | |
f5f727f8 DN |
93 | :group 'files |
94 | :group 'convenience) | |
4a35aff3 RS |
95 | |
96 | ||
97 | ;; Variables: | |
98 | ||
00d0fda8 DL |
99 | ;; Autoload for the benefit of `make-mode-line-mouse-sensitive'. |
100 | ;;;###autoload | |
4a35aff3 RS |
101 | (defvar auto-revert-mode nil |
102 | "*Non-nil when Auto-Revert Mode is active. | |
0e4f9468 SM |
103 | Never set this variable directly, use the command `auto-revert-mode' instead.") |
104 | (put 'auto-revert-mode 'permanent-local t) | |
4a35aff3 RS |
105 | |
106 | (defcustom auto-revert-interval 5 | |
ad660075 EZ |
107 | "Time, in seconds, between Auto-Revert Mode file checks. |
108 | Setting this variable has no effect on buffers that are already in | |
109 | auto-revert-mode; it only affects buffers that are put into | |
110 | auto-revert-mode afterwards." | |
4a35aff3 RS |
111 | :group 'auto-revert |
112 | :type 'integer) | |
113 | ||
114 | (defcustom auto-revert-stop-on-user-input t | |
115 | "When non-nil Auto-Revert Mode stops checking files on user input." | |
116 | :group 'auto-revert | |
117 | :type 'boolean) | |
118 | ||
119 | (defcustom auto-revert-verbose t | |
120 | "When nil, Auto-Revert Mode will not generate any messages. | |
121 | ||
122 | Currently, messages are generated when the mode is activated or | |
123 | deactivated, and whenever a file is reverted." | |
124 | :group 'auto-revert | |
125 | :type 'boolean) | |
126 | ||
127 | (defcustom auto-revert-mode-text " ARev" | |
128 | "String to display in the mode line when Auto-Revert Mode is active. | |
129 | ||
130 | \(When the string is not empty, make sure that it has a leading space.)" | |
131 | :tag "Auto Revert Mode Text" ; To separate it from `global-...' | |
132 | :group 'auto-revert | |
133 | :type 'string) | |
134 | ||
135 | (defcustom auto-revert-mode-hook nil | |
136 | "Functions to run when Auto-Revert Mode is activated." | |
137 | :tag "Auto Revert Mode Hook" ; To separate it from `global-...' | |
138 | :group 'auto-revert | |
139 | :type 'hook) | |
140 | ||
141 | (defcustom global-auto-revert-mode-text "" | |
142 | "String to display when Global Auto-Revert Mode is active. | |
143 | ||
144 | The default is nothing since when this mode is active this text doesn't | |
48764ae2 | 145 | vary over time, or between buffers. Hence mode line text |
4a35aff3 RS |
146 | would only waste precious space." |
147 | :group 'auto-revert | |
148 | :type 'string) | |
149 | ||
150 | (defcustom global-auto-revert-mode-hook nil | |
151 | "Hook called when Global Auto-Revert Mode is activated." | |
152 | :group 'auto-revert | |
153 | :type 'hook) | |
154 | ||
155 | (defcustom global-auto-revert-non-file-buffers nil | |
0e4f9468 | 156 | "When nil only file buffers are reverted by Global Auto-Revert Mode. |
4a35aff3 RS |
157 | |
158 | When non-nil, both file buffers and buffers with a custom | |
0e4f9468 SM |
159 | `revert-buffer-function' are reverted by Global Auto-Revert Mode. |
160 | ||
161 | Use this option with care since it could lead to excessive reverts." | |
4a35aff3 RS |
162 | :group 'auto-revert |
163 | :type 'boolean) | |
164 | ||
4a35aff3 RS |
165 | (defcustom global-auto-revert-ignore-modes '() |
166 | "List of major modes Global Auto-Revert Mode should not check." | |
167 | :group 'auto-revert | |
168 | :type '(repeat sexp)) | |
169 | ||
170 | (defcustom auto-revert-load-hook nil | |
171 | "Functions to run when Auto-Revert Mode is first loaded." | |
172 | :tag "Load Hook" | |
173 | :group 'auto-revert | |
174 | :type 'hook) | |
175 | ||
176 | (defvar global-auto-revert-ignore-buffer nil | |
f1e3ff80 | 177 | "*When non-nil, Global Auto-Revert Mode will not revert this buffer. |
4a35aff3 | 178 | |
48764ae2 | 179 | This variable becomes buffer local when set in any fashion.") |
4a35aff3 RS |
180 | (make-variable-buffer-local 'global-auto-revert-ignore-buffer) |
181 | ||
182 | ||
183 | ;; Internal variables: | |
184 | ||
185 | (defvar auto-revert-buffer-list '() | |
186 | "List of buffers in Auto-Revert Mode. | |
187 | ||
188 | Note that only Auto-Revert Mode, never Global Auto-Revert Mode, adds | |
189 | buffers to this list. | |
190 | ||
191 | The timer function `auto-revert-buffers' is responsible for purging | |
192 | the list of old buffers.") | |
193 | ||
194 | (defvar auto-revert-timer nil | |
195 | "Timer used by Auto-Revert Mode.") | |
196 | ||
197 | (defvar auto-revert-remaining-buffers '() | |
198 | "Buffers not checked when user input stopped execution.") | |
199 | ||
200 | ||
201 | ;; Functions: | |
202 | ||
203 | ;;;###autoload | |
0e4f9468 | 204 | (define-minor-mode auto-revert-mode |
48764ae2 | 205 | "Toggle reverting buffer when file on disk changes. |
4a35aff3 | 206 | |
48764ae2 DL |
207 | With arg, turn Auto Revert mode on if and only if arg is positive. |
208 | This is a minor mode that affects only the current buffer. | |
4a35aff3 | 209 | Use `global-auto-revert-mode' to automatically revert all buffers." |
0e4f9468 | 210 | nil auto-revert-mode-text nil |
4a35aff3 RS |
211 | (if auto-revert-mode |
212 | (if (not (memq (current-buffer) auto-revert-buffer-list)) | |
213 | (push (current-buffer) auto-revert-buffer-list)) | |
214 | (setq auto-revert-buffer-list | |
215 | (delq (current-buffer) auto-revert-buffer-list))) | |
216 | (auto-revert-set-timer) | |
217 | (when auto-revert-mode | |
0e4f9468 | 218 | (auto-revert-buffers))) |
4a35aff3 RS |
219 | |
220 | ||
221 | ;;;###autoload | |
222 | (defun turn-on-auto-revert-mode () | |
223 | "Turn on Auto-Revert Mode. | |
224 | ||
225 | This function is designed to be added to hooks, for example: | |
226 | (add-hook 'c-mode-hook 'turn-on-auto-revert-mode)" | |
227 | (auto-revert-mode 1)) | |
228 | ||
229 | ||
230 | ;;;###autoload | |
0e4f9468 | 231 | (define-minor-mode global-auto-revert-mode |
4a35aff3 RS |
232 | "Revert any buffer when file on disk change. |
233 | ||
48764ae2 DL |
234 | With arg, turn Auto Revert mode on globally if and only if arg is positive. |
235 | This is a minor mode that affects all buffers. | |
4a35aff3 | 236 | Use `auto-revert-mode' to revert a particular buffer." |
0e4f9468 | 237 | :global t :group 'auto-revert :lighter global-auto-revert-mode-text |
4a35aff3 RS |
238 | (auto-revert-set-timer) |
239 | (when global-auto-revert-mode | |
0e4f9468 | 240 | (auto-revert-buffers))) |
4a35aff3 RS |
241 | |
242 | ||
243 | (defun auto-revert-set-timer () | |
244 | "Restart or cancel the timer." | |
245 | (if (timerp auto-revert-timer) | |
246 | (cancel-timer auto-revert-timer)) | |
0e4f9468 SM |
247 | (setq auto-revert-timer |
248 | (if (or global-auto-revert-mode auto-revert-buffer-list) | |
249 | (run-with-timer auto-revert-interval | |
250 | auto-revert-interval | |
251 | 'auto-revert-buffers) | |
252 | nil))) | |
4a35aff3 | 253 | |
ed35db71 EZ |
254 | (defun auto-revert-active-p () |
255 | "Check if auto-revert is active (in current buffer or globally)." | |
256 | (or auto-revert-mode | |
257 | (and | |
258 | global-auto-revert-mode | |
259 | (not global-auto-revert-ignore-buffer) | |
260 | (not (memq major-mode | |
261 | global-auto-revert-ignore-modes))))) | |
262 | ||
263 | (defun auto-revert-list-diff (a b) | |
264 | "Check if strings in list A differ from list B." | |
265 | (when (and a b) | |
266 | (setq a (sort a 'string-lessp)) | |
267 | (setq b (sort b 'string-lessp)) | |
268 | (let (elt1 elt2) | |
269 | (catch 'break | |
270 | (while (and (setq elt1 (and a (pop a))) | |
271 | (setq elt2 (and b (pop b)))) | |
272 | (if (not (string= elt1 elt2)) | |
273 | (throw 'break t))))))) | |
274 | ||
275 | (defun auto-revert-dired-file-list () | |
276 | "Return list of dired files." | |
0f98bc23 | 277 | (let (file list) |
ed35db71 EZ |
278 | (save-excursion |
279 | (goto-char (point-min)) | |
280 | (while (not (eobp)) | |
281 | (if (setq file (dired-get-filename t t)) | |
282 | (push file list)) | |
283 | (forward-line 1))) | |
284 | list)) | |
285 | ||
286 | (defun auto-revert-dired-changed-p () | |
287 | "Check if dired buffer has changed." | |
288 | (when (and (stringp dired-directory) | |
289 | ;; Exclude remote buffers, would be too slow for user | |
290 | ;; modem, timeouts, network lag ... all is possible | |
291 | (not (string-match "@" dired-directory)) | |
292 | (file-directory-p dired-directory)) | |
293 | (let ((files (directory-files dired-directory)) | |
294 | (dired (auto-revert-dired-file-list))) | |
295 | (or (not (eq (length files) (length dired))) | |
296 | (auto-revert-list-diff files dired))))) | |
297 | ||
298 | (defun auto-revert-buffer-p () | |
299 | "Check if current buffer should be reverted." | |
8a9825f7 | 300 | ;; - Always include dired buffers to list. It would be too expensive |
ed35db71 | 301 | ;; to test the "revert" status here each time timer launches. |
4e664f62 | 302 | ;; - Same for VC buffers. |
8a9825f7 LT |
303 | (or (and (eq major-mode 'dired-mode) |
304 | (or (and global-auto-revert-mode | |
305 | global-auto-revert-non-file-buffers) | |
306 | auto-revert-mode)) | |
4e664f62 EZ |
307 | (and (not (buffer-modified-p)) |
308 | (auto-revert-vc-buffer-p)) | |
ed35db71 EZ |
309 | (and (not (buffer-modified-p)) |
310 | (if (buffer-file-name) | |
311 | (and (file-readable-p (buffer-file-name)) | |
0f98bc23 | 312 | (not (verify-visited-file-modtime (current-buffer)))) |
ed35db71 EZ |
313 | (and revert-buffer-function |
314 | (or (and global-auto-revert-mode | |
315 | global-auto-revert-non-file-buffers) | |
316 | auto-revert-mode)))))) | |
317 | ||
4e664f62 EZ |
318 | (defun auto-revert-vc-cvs-file-version (file) |
319 | "Get version of FILE by reading control file on disk." | |
320 | (let* ((control "CVS/Entries") | |
321 | (name (file-name-nondirectory file)) | |
322 | (path (format "%s/%s" | |
323 | (file-name-directory file) | |
324 | control))) | |
325 | (when (file-exists-p path) | |
326 | (with-temp-buffer | |
327 | (insert-file-contents-literally path) | |
328 | (goto-char (point-min)) | |
329 | (when (re-search-forward | |
330 | ;; /file.txt/1.3/Mon Sep 15 18:43:20 2003// | |
331 | (format "%s/\\([.0-9]+\\)" (regexp-quote name)) | |
332 | nil t) | |
333 | (match-string 1)))))) | |
334 | ||
335 | (defun auto-revert-vc-buffer-p () | |
336 | "Check if buffer is version controlled." | |
337 | (and (boundp 'vc-mode) | |
338 | (string-match "[0-9]" (or vc-mode "")))) | |
339 | ||
340 | (defun auto-revert-handler-vc () | |
341 | "Check if version controlled buffer needs revert." | |
342 | ;; [Emacs 1] | |
343 | ;; 1. File is saved (*) | |
344 | ;; 2. checkin is done 1.1 -> 1.2 | |
345 | ;; 3. VC reverts, so that updated version number is shown in mode line | |
346 | ;; | |
347 | ;; Suppose the same file has been opened in another Emacs and | |
348 | ;; autorevert.el is on. | |
349 | ;; | |
350 | ;; [Emacs 2] | |
351 | ;; 1. Step (1) is detected and buffer is reverted. | |
352 | ;; 2. But check in does not always change the file in dis, but possibly only | |
353 | ;; control files like CVS/Entries | |
354 | ;; 3. The buffer is not reverted to update VC version line. | |
355 | ;; Incorrect version number 1.1 is shown in this Emacs | |
356 | ;; | |
357 | (when (featurep 'vc) | |
358 | (let* ((file (buffer-file-name)) | |
359 | (backend (vc-backend (buffer-file-name))) | |
360 | (version-buffer (vc-workfile-version file))) | |
361 | (when (stringp version-buffer) | |
362 | (cond | |
363 | ((eq backend 'CVS) | |
364 | (let ((version-file | |
365 | (auto-revert-vc-cvs-file-version (buffer-file-name)))) | |
366 | (and (stringp version-file) | |
367 | (not (string-match version-file version-buffer))))) | |
368 | ((eq backend 'RCS) | |
369 | ;; TODO: | |
370 | )))))) | |
371 | ||
ed35db71 EZ |
372 | (defun auto-revert-handler () |
373 | "Revert current buffer." | |
4e664f62 | 374 | (let (revert) |
ed35db71 EZ |
375 | (cond |
376 | ((eq major-mode 'dired-mode) | |
377 | ;; Dired includes revert-buffer-function | |
378 | (when (and revert-buffer-function | |
379 | (auto-revert-dired-changed-p)) | |
4e664f62 EZ |
380 | (setq revert t))) |
381 | ((auto-revert-vc-buffer-p) | |
382 | (when (auto-revert-handler-vc) | |
383 | (setq revert 'vc))) | |
ed35db71 EZ |
384 | ((or (buffer-file-name) |
385 | revert-buffer-function) | |
4e664f62 EZ |
386 | (setq revert t))) |
387 | (when revert | |
388 | (revert-buffer 'ignore-auto 'dont-ask 'preserve-modes) | |
389 | (if (eq revert 'vc) | |
390 | (vc-mode-line buffer-file-name)) | |
391 | (if auto-revert-verbose | |
392 | (message "Reverting buffer `%s'." (buffer-name)))))) | |
ed35db71 | 393 | |
4a35aff3 RS |
394 | (defun auto-revert-buffers () |
395 | "Revert buffers as specified by Auto-Revert and Global Auto-Revert Mode. | |
396 | ||
397 | Should `global-auto-revert-mode' be active all file buffers are checked. | |
398 | ||
399 | Should `auto-revert-mode' be active in some buffers, those buffers | |
400 | are checked. | |
401 | ||
402 | Non-file buffers that have a custom `revert-buffer-function' are | |
403 | reverted either when Auto-Revert Mode is active in that buffer, or | |
404 | when the variable `global-auto-revert-non-file-buffers' is non-nil | |
405 | and Global Auto-Revert Mode is active. | |
406 | ||
48764ae2 | 407 | This function stops whenever there is user input. The buffers not |
4a35aff3 RS |
408 | checked are stored in the variable `auto-revert-remaining-buffers'. |
409 | ||
410 | To avoid starvation, the buffers in `auto-revert-remaining-buffers' | |
411 | are checked first the next time this function is called. | |
412 | ||
48764ae2 | 413 | This function is also responsible for removing buffers no longer in |
4a35aff3 RS |
414 | Auto-Revert mode from `auto-revert-buffer-list', and for canceling |
415 | the timer when no buffers need to be checked." | |
416 | (let ((bufs (if global-auto-revert-mode | |
417 | (buffer-list) | |
418 | auto-revert-buffer-list)) | |
419 | (remaining '()) | |
420 | (new '())) | |
421 | ;; Partition `bufs' into two halves depending on whether or not | |
422 | ;; the buffers are in `auto-revert-remaining-buffers'. The two | |
423 | ;; halves are then re-joined with the "remaining" buffers at the | |
424 | ;; head of the list. | |
425 | (dolist (buf auto-revert-remaining-buffers) | |
426 | (if (memq buf bufs) | |
427 | (push buf remaining))) | |
428 | (dolist (buf bufs) | |
429 | (if (not (memq buf remaining)) | |
430 | (push buf new))) | |
431 | (setq bufs (nreverse (nconc new remaining))) | |
432 | (while (and bufs | |
433 | (not (and auto-revert-stop-on-user-input | |
434 | (input-pending-p)))) | |
435 | (let ((buf (car bufs))) | |
436 | (if (buffer-name buf) ; Buffer still alive? | |
0e4f9468 | 437 | (with-current-buffer buf |
4a35aff3 RS |
438 | ;; Test if someone has turned off Auto-Revert Mode in a |
439 | ;; non-standard way, for example by changing major mode. | |
440 | (if (and (not auto-revert-mode) | |
441 | (memq buf auto-revert-buffer-list)) | |
442 | (setq auto-revert-buffer-list | |
443 | (delq buf auto-revert-buffer-list))) | |
ed35db71 EZ |
444 | (when (and (auto-revert-active-p) |
445 | (auto-revert-buffer-p)) | |
446 | (auto-revert-handler) | |
0e4f9468 SM |
447 | ;; `preserve-modes' avoids changing the (minor) modes. But we |
448 | ;; do want to reset the mode for VC, so we do it explicitly. | |
449 | (vc-find-file-hook))) | |
4a35aff3 RS |
450 | ;; Remove dead buffer from `auto-revert-buffer-list'. |
451 | (setq auto-revert-buffer-list | |
452 | (delq buf auto-revert-buffer-list)))) | |
453 | (setq bufs (cdr bufs))) | |
454 | (setq auto-revert-remaining-buffers bufs) | |
455 | ;; Check if we should cancel the timer. | |
456 | (when (and (not global-auto-revert-mode) | |
457 | (null auto-revert-buffer-list)) | |
458 | (cancel-timer auto-revert-timer) | |
459 | (setq auto-revert-timer nil)))) | |
460 | ||
461 | ||
462 | ;; The end: | |
4a35aff3 RS |
463 | (provide 'autorevert) |
464 | ||
465 | (run-hooks 'auto-revert-load-hook) | |
466 | ||
ab5796a9 | 467 | ;;; arch-tag: f6bcb07b-4841-477e-9e44-b18678e58876 |
e8af40ee | 468 | ;;; autorevert.el ends here |