Update FSF's address.
[bpt/emacs.git] / lisp / diff.el
CommitLineData
646bd331 1;;; diff.el --- Run `diff' in compilation-mode.
c0274f38 2
4cb4ed41 3;; Copyright (C) 1992, 1994, 1996 Free Software Foundation, Inc.
3a801d0c 4
e9571d2a 5;; Keywords: unix, tools
e5167999 6
b10a4379
JB
7;; This file is part of GNU Emacs.
8
9;; GNU Emacs is free software; you can redistribute it and/or modify
10;; it under the terms of the GNU General Public License as published by
e5167999 11;; the Free Software Foundation; either version 2, or (at your option)
b10a4379
JB
12;; any later version.
13
14;; GNU Emacs is distributed in the hope that it will be useful,
15;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;; GNU General Public License for more details.
18
19;; You should have received a copy of the GNU General Public License
b578f267
EN
20;; along with GNU Emacs; see the file COPYING. If not, write to the
21;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22;; Boston, MA 02111-1307, USA.
b10a4379 23
e41b2db1
ER
24;;; Commentary:
25
26;; This package helps you explore differences between files, using the
27;; UNIX command diff(1). The commands are `diff' and `diff-backup'.
28;; You can specify options with `diff-switches'.
29
e5167999
ER
30;;; Code:
31
646bd331 32(require 'compile)
aa228418 33
fa203a7c 34;;; This is duplicated in vc.el.
c21d3ee2 35(defvar diff-switches "-c"
646bd331
RM
36 "*A string or list of strings specifying switches to be be passed to diff.")
37
d009603c
RS
38(defvar diff-command "diff"
39 "*The command to use to run diff.")
40
646bd331
RM
41(defvar diff-regexp-alist
42 '(
43 ;; -u format: @@ -OLDSTART,OLDEND +NEWSTART,NEWEND @@
44 ("^@@ -\\([0-9]+\\),[0-9]+ \\+\\([0-9]+\\),[0-9]+ @@$" 1 2)
4cb4ed41 45
646bd331
RM
46 ;; -c format: *** OLDSTART,OLDEND ****
47 ("^\\*\\*\\* \\([0-9]+\\),[0-9]+ \\*\\*\\*\\*$" 1 nil)
48 ;; --- NEWSTART,NEWEND ----
49 ("^--- \\([0-9]+\\),[0-9]+ ----$" nil 1)
50
51 ;; plain diff format: OLDSTART[,OLDEND]{a,d,c}NEWSTART[,NEWEND]
52 ("^\\([0-9]+\\)\\(,[0-9]+\\)?[adc]\\([0-9]+\\)\\(,[0-9]+\\)?$" 1 3)
53
54 ;; -e (ed) format: OLDSTART[,OLDEND]{a,d,c}
55 ("^\\([0-9]+\\)\\(,[0-9]+\\)?[adc]$" 1)
56
57 ;; -f format: {a,d,c}OLDSTART[ OLDEND]
58 ;; -n format: {a,d,c}OLDSTART LINES-CHANGED
59 ("^[adc]\\([0-9]+\\)\\( [0-9]+\\)?$" 1)
60 )
4cb4ed41 61 "Alist (REGEXP OLD-IDX NEW-IDX) of regular expressions to match difference
646bd331
RM
62sections in \\[diff] output. If REGEXP matches, the OLD-IDX'th
63subexpression gives the line number in the old file, and NEW-IDX'th
64subexpression gives the line number in the new file. If OLD-IDX or NEW-IDX
65is nil, REGEXP matches only half a section.")
66
5d68c2c2
RS
67(defvar diff-old-file nil
68 "This is the old file name in the comparison in this buffer.")
69(defvar diff-new-file nil
70 "This is the new file name in the comparison in this buffer.")
71(defvar diff-old-temp-file nil
72 "This is the name of a temp file to be deleted after diff finishes.")
73(defvar diff-new-temp-file nil
74 "This is the name of a temp file to be deleted after diff finishes.")
75
646bd331 76;; See compilation-parse-errors-function (compile.el).
c540863c 77(defun diff-parse-differences (limit-search find-at-least)
646bd331
RM
78 (setq compilation-error-list nil)
79 (message "Parsing differences...")
80
81 ;; Don't reparse diffs already seen at last parse.
a4ef6584 82 (if compilation-parsing-end (goto-char compilation-parsing-end))
646bd331
RM
83
84 ;; Construct in REGEXP a regexp composed of all those in dired-regexp-alist.
85 (let ((regexp (mapconcat (lambda (elt)
86 (concat "\\(" (car elt) "\\)"))
87 diff-regexp-alist
88 "\\|"))
89 ;; (GROUP-IDX OLD-IDX NEW-IDX)
90 (groups (let ((subexpr 1))
91 (mapcar (lambda (elt)
92 (prog1
93 (cons subexpr
94 (mapcar (lambda (n)
95 (and n
96 (+ subexpr n)))
97 (cdr elt)))
98 (setq subexpr (+ subexpr 1
99 (count-regexp-groupings
100 (car elt))))))
101 diff-regexp-alist)))
102
103 (new-error
104 (function (lambda (file subexpr)
105 (setq compilation-error-list
106 (cons
c21d3ee2
RS
107 (cons (save-excursion
108 ;; Report location of message
109 ;; at beginning of line.
110 (goto-char
111 (match-beginning subexpr))
112 (beginning-of-line)
113 (point-marker))
114 ;; Report location of corresponding text.
646bd331
RM
115 (let ((line (string-to-int
116 (buffer-substring
117 (match-beginning subexpr)
118 (match-end subexpr)))))
119 (save-excursion
929cc9de
JB
120 (save-match-data
121 (set-buffer (find-file-noselect file)))
646bd331
RM
122 (save-excursion
123 (goto-line line)
124 (point-marker)))))
125 compilation-error-list)))))
126
127 (found-desired nil)
6d670a70 128 (num-loci-found 0)
646bd331
RM
129 g)
130
131 (while (and (not found-desired)
132 ;; We don't just pass LIMIT-SEARCH to re-search-forward
133 ;; because we want to find matches containing LIMIT-SEARCH
134 ;; but which extend past it.
135 (re-search-forward regexp nil t))
136
137 ;; Find which individual regexp matched.
138 (setq g groups)
139 (while (and g (null (match-beginning (car (car g)))))
140 (setq g (cdr g)))
141 (setq g (car g))
142
143 (if (nth 1 g) ;OLD-IDX
144 (funcall new-error diff-old-file (nth 1 g)))
145 (if (nth 2 g) ;NEW-IDX
146 (funcall new-error diff-new-file (nth 2 g)))
147
6d670a70 148 (setq num-loci-found (1+ num-loci-found))
5d68c2c2 149 (if (or (and find-at-least
6d670a70 150 (>= num-loci-found find-at-least))
c540863c 151 (and limit-search (>= (point) limit-search)))
6d670a70 152 ;; We have found as many new loci as the user wants,
c540863c
RM
153 ;; or the user wanted a specific diff, and we're past it.
154 (setq found-desired t)))
646bd331
RM
155 (if found-desired
156 (setq compilation-parsing-end (point))
157 ;; Set to point-max, not point, so we don't perpetually
158 ;; parse the last bit of text when it isn't a diff header.
6d670a70
RS
159 (setq compilation-parsing-end (point-max)))
160 (message "Parsing differences...done"))
646bd331 161 (setq compilation-error-list (nreverse compilation-error-list)))
b10a4379
JB
162
163;;;###autoload
646bd331 164(defun diff (old new &optional switches)
b10a4379 165 "Find and display the differences between OLD and NEW files.
4b00edc8 166Interactively the current buffer's file name is the default for NEW
646bd331
RM
167and a backup file for NEW is the default for OLD.
168With prefix arg, prompt for diff switches."
b10a4379 169 (interactive
646bd331
RM
170 (nconc
171 (let (oldf newf)
172 (nreverse
173 (list
174 (setq newf (buffer-file-name)
175 newf (if (and newf (file-exists-p newf))
176 (read-file-name
177 (concat "Diff new file: ("
178 (file-name-nondirectory newf) ") ")
179 nil newf t)
180 (read-file-name "Diff new file: " nil nil t)))
181 (setq oldf (file-newest-backup newf)
182 oldf (if (and oldf (file-exists-p oldf))
183 (read-file-name
184 (concat "Diff original file: ("
185 (file-name-nondirectory oldf) ") ")
186 (file-name-directory oldf) oldf t)
187 (read-file-name "Diff original file: "
188 (file-name-directory newf) nil t))))))
189 (if current-prefix-arg
190 (list (read-string "Diff switches: "
191 (if (stringp diff-switches)
192 diff-switches
193 (mapconcat 'identity diff-switches " "))))
194 nil)))
b10a4379
JB
195 (setq new (expand-file-name new)
196 old (expand-file-name old))
5d68c2c2
RS
197 (let ((old-alt (file-local-copy old))
198 (new-alt (file-local-copy new))
bfe81e78
RS
199 buf)
200 (unwind-protect
201 (let ((command
202 (mapconcat 'identity
d009603c 203 (append (list diff-command)
16776caa
RS
204 ;; Use explicitly specified switches
205 (if switches
206 (if (consp switches)
207 switches (list switches))
208 ;; If not specified, use default.
209 (if (consp diff-switches)
210 diff-switches
211 (list diff-switches)))
bfe81e78
RS
212 (if (or old-alt new-alt)
213 (list "-L" old "-L" new))
c9cebcb1
RS
214 (list
215 (shell-quote-argument (or old-alt old)))
216 (list
217 (shell-quote-argument (or new-alt new))))
bfe81e78
RS
218 " ")))
219 (setq buf
220 (compile-internal command
221 "No more differences" "Diff"
b7cceaf1 222 'diff-parse-differences))
a6609a36 223 (pop-to-buffer buf)
2f29e0c6
KH
224 ;; Avoid frightening people with "abnormally terminated"
225 ;; if diff finds differences.
226 (set (make-local-variable 'compilation-exit-message-function)
4cb4ed41
RM
227 (lambda (status code msg)
228 (cond ((not (eq status 'exit))
229 (cons msg code))
230 ((zerop code)
231 '("finished (no differences)\n" . "no differences"))
232 ((= code 1)
233 '("finished\n" . "differences found"))
234 (t
235 (cons msg code)))))
a6609a36
RS
236 (set (make-local-variable 'diff-old-file) old)
237 (set (make-local-variable 'diff-new-file) new)
238 (set (make-local-variable 'diff-old-temp-file) old-alt)
239 (set (make-local-variable 'diff-new-temp-file) new-alt)
240 (set (make-local-variable 'compilation-finish-function)
241 (function (lambda (buff msg)
242 (if diff-old-temp-file
243 (delete-file diff-old-temp-file))
244 (if diff-new-temp-file
245 (delete-file diff-new-temp-file)))))
5d68c2c2 246 buf))))
aa228418 247
646bd331
RM
248;;;###autoload
249(defun diff-backup (file &optional switches)
37c51f00
RS
250 "Diff this file with its backup file or vice versa.
251Uses the latest backup, if there are several numerical backups.
252If this file is a backup, diff it with its original.
253The backup file is the first file given to `diff'."
646bd331
RM
254 (interactive (list (read-file-name "Diff (file with backup): ")
255 (if current-prefix-arg
256 (read-string "Diff switches: "
257 (if (stringp diff-switches)
258 diff-switches
259 (mapconcat 'identity
260 diff-switches " ")))
261 nil)))
37c51f00
RS
262 (let (bak ori)
263 (if (backup-file-name-p file)
264 (setq bak file
265 ori (file-name-sans-versions file))
266 (setq bak (or (diff-latest-backup-file file)
267 (error "No backup found for %s" file))
268 ori file))
646bd331 269 (diff bak ori switches)))
37c51f00
RS
270
271(defun diff-latest-backup-file (fn) ; actually belongs into files.el
272 "Return the latest existing backup of FILE, or nil."
a617e913 273 (let ((handler (find-file-name-handler fn 'diff-latest-backup-file)))
34c46d87 274 (if handler
6d0d0e8d 275 (funcall handler 'diff-latest-backup-file fn)
34c46d87
RM
276 ;; First try simple backup, then the highest numbered of the
277 ;; numbered backups.
278 ;; Ignore the value of version-control because we look for existing
279 ;; backups, which maybe were made earlier or by another user with
280 ;; a different value of version-control.
281 (setq fn (file-chase-links (expand-file-name fn)))
282 (or
283 (let ((bak (make-backup-file-name fn)))
284 (if (file-exists-p bak) bak))
cf2d98df
RS
285 ;; We use BACKUPNAME to cope with backups stored in a different dir.
286 (let* ((backupname (car (find-backup-file-name fn)))
287 (dir (file-name-directory backupname))
288 (base-versions (concat (file-name-sans-versions
289 (file-name-nondirectory backupname))
290 ".~"))
34c46d87
RM
291 (bv-length (length base-versions)))
292 (concat dir
293 (car (sort
294 (file-name-all-completions base-versions dir)
295 ;; bv-length is a fluid var for backup-extract-version:
296 (function
297 (lambda (fn1 fn2)
298 (> (backup-extract-version fn1)
299 (backup-extract-version fn2))))))))))))
aa228418 300
a4ef6584
ER
301(provide 'diff)
302
c0274f38 303;;; diff.el ends here