*** empty log message ***
[emacs.git] / lisp / diff.el
blobee49482cfe82368df680ae7c70bc261b967450c9
1 ;;; diff.el --- run `diff' in compilation-mode
3 ;; Copyright (C) 1992, 1994, 1996, 2001 Free Software Foundation, Inc.
5 ;; Maintainer: FSF
6 ;; Keywords: unix, tools
8 ;; This file is part of GNU Emacs.
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
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
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.
20 ;; You should have received a copy of the GNU General Public License
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.
25 ;;; Commentary:
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'.
31 ;;; Code:
33 (require 'compile)
35 (defgroup diff nil
36 "Comparing files with `diff'."
37 :group 'tools)
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)
45 ;;;###autoload
46 (defcustom diff-command "diff"
47 "*The command to use to run diff."
48 :type 'string
49 :group 'diff)
51 (defvar diff-regexp-alist
53 ;; -u format: @@ -OLDSTART,OLDEND +NEWSTART,NEWEND @@
54 ("^@@ -\\([0-9]+\\),[0-9]+ \\+\\([0-9]+\\),[0-9]+ @@$" 1 2)
56 ;; -c format: *** OLDSTART,OLDEND ****
57 ("^\\*\\*\\* \\([0-9]+\\),[0-9]+ \\*\\*\\*\\*$" 1 nil)
58 ;; --- NEWSTART,NEWEND ----
59 ("^--- \\([0-9]+\\),[0-9]+ ----$" nil 1)
61 ;; plain diff format: OLDSTART[,OLDEND]{a,d,c}NEWSTART[,NEWEND]
62 ("^\\([0-9]+\\)\\(,[0-9]+\\)?[adc]\\([0-9]+\\)\\(,[0-9]+\\)?$" 1 3)
64 ;; -e (ed) format: OLDSTART[,OLDEND]{a,d,c}
65 ("^\\([0-9]+\\)\\(,[0-9]+\\)?[adc]$" 1)
67 ;; -f format: {a,d,c}OLDSTART[ OLDEND]
68 ;; -n format: {a,d,c}OLDSTART LINES-CHANGED
69 ("^[adc]\\([0-9]+\\)\\( [0-9]+\\)?$" 1)
71 "Alist of regular expressions to match difference sections in \\[diff] output.
72 Each element has the form (REGEXP OLD-IDX NEW-IDX).
73 Any text that REGEXP matches identifies one difference hunk
74 or the header of a hunk.
76 The OLD-IDX'th subexpression of REGEXP gives the line number
77 in the old file, and NEW-IDX'th subexpression gives the line number
78 in the new file. If OLD-IDX or NEW-IDX
79 is nil, REGEXP matches only half a hunk.")
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.")
90 ;; See compilation-parse-errors-function (compile.el).
91 (defun diff-parse-differences (limit-search find-at-least)
92 (setq compilation-error-list nil)
93 (message "Parsing differences...")
95 ;; Don't reparse diffs already seen at last parse.
96 (if compilation-parsing-end (goto-char compilation-parsing-end))
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)))
117 (new-error
118 (function (lambda (file subexpr)
119 (setq compilation-error-list
120 (cons
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.
129 (let ((line (string-to-int
130 (buffer-substring
131 (match-beginning subexpr)
132 (match-end subexpr)))))
133 (save-excursion
134 (save-match-data
135 (set-buffer (find-file-noselect file)))
136 (save-excursion
137 (goto-line line)
138 (point-marker)))))
139 compilation-error-list)))))
141 (found-desired nil)
142 (num-loci-found 0)
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))
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))
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)))
162 (setq num-loci-found (1+ num-loci-found))
163 (if (or (and find-at-least
164 (>= num-loci-found find-at-least))
165 (and limit-search (>= (point) limit-search)))
166 ;; We have found as many new loci as the user wants,
167 ;; or the user wanted a specific diff, and we're past it.
168 (setq found-desired t)))
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)))
174 (message "Parsing differences...done"))
175 (setq compilation-error-list (nreverse compilation-error-list)))
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"))
190 (cons msg code))))))
192 ;;;###autoload
193 (defun diff (old new &optional switches)
194 "Find and display the differences between OLD and NEW files.
195 Interactively the current buffer's file name is the default for NEW
196 and a backup file for NEW is the default for OLD.
197 With prefix arg, prompt for diff switches."
198 (interactive
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
206 (concat "Diff new file: (default "
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
213 (concat "Diff original file: (default "
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)))
224 (setq new (expand-file-name new)
225 old (expand-file-name old))
226 (let ((old-alt (file-local-copy old))
227 (new-alt (file-local-copy new))
228 buf)
229 (save-excursion
230 (let ((compilation-process-setup-function 'diff-process-setup)
231 (command
232 (mapconcat 'identity
233 (append (list diff-command)
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)))
242 (if (or old-alt new-alt)
243 (list "-L" old "-L" new))
244 (list
245 (shell-quote-argument (or old-alt old)))
246 (list
247 (shell-quote-argument (or new-alt new))))
248 " ")))
249 (setq buf
250 (compile-internal command
251 "No more differences" "Diff"
252 'diff-parse-differences))
253 (set-buffer buf)
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)))))
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))
268 buf))))
270 ;;;###autoload
271 (defun diff-backup (file &optional switches)
272 "Diff this file with its backup file or vice versa.
273 Uses the latest backup, if there are several numerical backups.
274 If this file is a backup, diff it with its original.
275 The backup file is the first file given to `diff'."
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)))
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))
291 (diff bak ori switches)))
293 (defun diff-latest-backup-file (fn) ; actually belongs into files.el
294 "Return the latest existing backup of FILE, or nil."
295 (let ((handler (find-file-name-handler fn 'diff-latest-backup-file)))
296 (if handler
297 (funcall handler 'diff-latest-backup-file fn)
298 (file-newest-backup fn))))
300 (provide 'diff)
302 ;;; diff.el ends here