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