*** empty log message ***
[emacs.git] / lisp / diff.el
blobbb33689c0f9f2561a4787f1e3f8bae33d74054a5
1 ;;; diff.el --- Run `diff' in compilation-mode.
3 ;; Copyright (C) 1992 Free Software Foundation, Inc.
5 ;; Keywords: unix, tools
7 ;; This file is part of GNU Emacs.
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
11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; any later version.
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.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING. If not, write to
21 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 ;;; Commentary:
25 ;; This package helps you explore differences between files, using the
26 ;; UNIX command diff(1). The commands are `diff' and `diff-backup'.
27 ;; You can specify options with `diff-switches'.
29 ;;; Code:
31 (require 'compile)
33 (defvar diff-switches "-c"
34 "*A string or list of strings specifying switches to be be passed to diff.")
36 (defvar diff-regexp-alist
38 ;; -u format: @@ -OLDSTART,OLDEND +NEWSTART,NEWEND @@
39 ("^@@ -\\([0-9]+\\),[0-9]+ \\+\\([0-9]+\\),[0-9]+ @@$" 1 2)
41 ;; -c format: *** OLDSTART,OLDEND ****
42 ("^\\*\\*\\* \\([0-9]+\\),[0-9]+ \\*\\*\\*\\*$" 1 nil)
43 ;; --- NEWSTART,NEWEND ----
44 ("^--- \\([0-9]+\\),[0-9]+ ----$" nil 1)
46 ;; plain diff format: OLDSTART[,OLDEND]{a,d,c}NEWSTART[,NEWEND]
47 ("^\\([0-9]+\\)\\(,[0-9]+\\)?[adc]\\([0-9]+\\)\\(,[0-9]+\\)?$" 1 3)
49 ;; -e (ed) format: OLDSTART[,OLDEND]{a,d,c}
50 ("^\\([0-9]+\\)\\(,[0-9]+\\)?[adc]$" 1)
52 ;; -f format: {a,d,c}OLDSTART[ OLDEND]
53 ;; -n format: {a,d,c}OLDSTART LINES-CHANGED
54 ("^[adc]\\([0-9]+\\)\\( [0-9]+\\)?$" 1)
56 "Alist (REGEXP OLD-IDX NEW-IDX) of regular expressions to match difference
57 sections in \\[diff] output. If REGEXP matches, the OLD-IDX'th
58 subexpression gives the line number in the old file, and NEW-IDX'th
59 subexpression gives the line number in the new file. If OLD-IDX or NEW-IDX
60 is nil, REGEXP matches only half a section.")
62 (defvar diff-old-file nil
63 "This is the old file name in the comparison in this buffer.")
64 (defvar diff-new-file nil
65 "This is the new file name in the comparison in this buffer.")
66 (defvar diff-old-temp-file nil
67 "This is the name of a temp file to be deleted after diff finishes.")
68 (defvar diff-new-temp-file nil
69 "This is the name of a temp file to be deleted after diff finishes.")
71 ;; See compilation-parse-errors-function (compile.el).
72 (defun diff-parse-differences (limit-search find-at-least)
73 (setq compilation-error-list nil)
74 (message "Parsing differences...")
76 ;; Don't reparse diffs already seen at last parse.
77 (goto-char compilation-parsing-end)
79 ;; Construct in REGEXP a regexp composed of all those in dired-regexp-alist.
80 (let ((regexp (mapconcat (lambda (elt)
81 (concat "\\(" (car elt) "\\)"))
82 diff-regexp-alist
83 "\\|"))
84 ;; (GROUP-IDX OLD-IDX NEW-IDX)
85 (groups (let ((subexpr 1))
86 (mapcar (lambda (elt)
87 (prog1
88 (cons subexpr
89 (mapcar (lambda (n)
90 (and n
91 (+ subexpr n)))
92 (cdr elt)))
93 (setq subexpr (+ subexpr 1
94 (count-regexp-groupings
95 (car elt))))))
96 diff-regexp-alist)))
98 (new-error
99 (function (lambda (file subexpr)
100 (setq compilation-error-list
101 (cons
102 (cons (save-excursion
103 ;; Report location of message
104 ;; at beginning of line.
105 (goto-char
106 (match-beginning subexpr))
107 (beginning-of-line)
108 (point-marker))
109 ;; Report location of corresponding text.
110 (let ((line (string-to-int
111 (buffer-substring
112 (match-beginning subexpr)
113 (match-end subexpr)))))
114 (save-excursion
115 (set-buffer (find-file-noselect file))
116 (save-excursion
117 (goto-line line)
118 (point-marker)))))
119 compilation-error-list)))))
121 (found-desired nil)
122 (num-loci-found 0)
125 (while (and (not found-desired)
126 ;; We don't just pass LIMIT-SEARCH to re-search-forward
127 ;; because we want to find matches containing LIMIT-SEARCH
128 ;; but which extend past it.
129 (re-search-forward regexp nil t))
131 ;; Find which individual regexp matched.
132 (setq g groups)
133 (while (and g (null (match-beginning (car (car g)))))
134 (setq g (cdr g)))
135 (setq g (car g))
137 (if (nth 1 g) ;OLD-IDX
138 (funcall new-error diff-old-file (nth 1 g)))
139 (if (nth 2 g) ;NEW-IDX
140 (funcall new-error diff-new-file (nth 2 g)))
142 (setq num-loci-found (1+ num-loci-found))
143 (if (or (and find-at-least
144 (>= num-loci-found find-at-least))
145 (and limit-search (>= (point) limit-search)))
146 ;; We have found as many new loci as the user wants,
147 ;; or the user wanted a specific diff, and we're past it.
148 (setq found-desired t)))
149 (if found-desired
150 (setq compilation-parsing-end (point))
151 ;; Set to point-max, not point, so we don't perpetually
152 ;; parse the last bit of text when it isn't a diff header.
153 (setq compilation-parsing-end (point-max)))
154 (message "Parsing differences...done"))
155 (setq compilation-error-list (nreverse compilation-error-list)))
157 ;;;###autoload
158 (defun diff (old new &optional switches)
159 "Find and display the differences between OLD and NEW files.
160 Interactively the current buffer's file name is the default for for NEW
161 and a backup file for NEW is the default for OLD.
162 With prefix arg, prompt for diff switches."
163 (interactive
164 (nconc
165 (let (oldf newf)
166 (nreverse
167 (list
168 (setq newf (buffer-file-name)
169 newf (if (and newf (file-exists-p newf))
170 (read-file-name
171 (concat "Diff new file: ("
172 (file-name-nondirectory newf) ") ")
173 nil newf t)
174 (read-file-name "Diff new file: " nil nil t)))
175 (setq oldf (file-newest-backup newf)
176 oldf (if (and oldf (file-exists-p oldf))
177 (read-file-name
178 (concat "Diff original file: ("
179 (file-name-nondirectory oldf) ") ")
180 (file-name-directory oldf) oldf t)
181 (read-file-name "Diff original file: "
182 (file-name-directory newf) nil t))))))
183 (if current-prefix-arg
184 (list (read-string "Diff switches: "
185 (if (stringp diff-switches)
186 diff-switches
187 (mapconcat 'identity diff-switches " "))))
188 nil)))
189 (setq new (expand-file-name new)
190 old (expand-file-name old))
191 (let ((old-alt (file-local-copy old))
192 (new-alt (file-local-copy new))
193 buf)
194 (unwind-protect
195 (let ((command
196 (mapconcat 'identity
197 (append '("diff")
198 (if (consp diff-switches)
199 diff-switches
200 (list diff-switches))
201 (if (or old-alt new-alt)
202 (list "-L" old "-L" new))
203 (list (or old-alt old))
204 (list (or new-alt new)))
205 " ")))
206 (setq buf
207 (compile-internal command
208 "No more differences" "Diff"
209 'diff-parse-differences))
210 (save-excursion
211 (set-buffer buf)
212 (set (make-local-variable 'diff-old-file) old)
213 (set (make-local-variable 'diff-new-file) new)
214 (set (make-local-variable 'diff-old-temp-file) old-alt)
215 (set (make-local-variable 'diff-new-temp-file) new-alt)
216 (set (make-local-variable 'compilation-finish-function)
217 (function (lambda (buff msg)
218 (if diff-old-temp-file
219 (delete-file diff-old-temp-file))
220 (if diff-new-temp-file
221 (delete-file diff-new-temp-file))))))
222 buf))))
224 ;;;###autoload
225 (defun diff-backup (file &optional switches)
226 "Diff this file with its backup file or vice versa.
227 Uses the latest backup, if there are several numerical backups.
228 If this file is a backup, diff it with its original.
229 The backup file is the first file given to `diff'."
230 (interactive (list (read-file-name "Diff (file with backup): ")
231 (if current-prefix-arg
232 (read-string "Diff switches: "
233 (if (stringp diff-switches)
234 diff-switches
235 (mapconcat 'identity
236 diff-switches " ")))
237 nil)))
238 (let (bak ori)
239 (if (backup-file-name-p file)
240 (setq bak file
241 ori (file-name-sans-versions file))
242 (setq bak (or (diff-latest-backup-file file)
243 (error "No backup found for %s" file))
244 ori file))
245 (diff bak ori switches)))
247 (defun diff-latest-backup-file (fn) ; actually belongs into files.el
248 "Return the latest existing backup of FILE, or nil."
249 ;; First try simple backup, then the highest numbered of the
250 ;; numbered backups.
251 ;; Ignore the value of version-control because we look for existing
252 ;; backups, which maybe were made earlier or by another user with
253 ;; a different value of version-control.
254 (setq fn (expand-file-name fn))
256 (let ((bak (make-backup-file-name fn)))
257 (if (file-exists-p bak) bak))
258 (let* ((dir (file-name-directory fn))
259 (base-versions (concat (file-name-nondirectory fn) ".~"))
260 (bv-length (length base-versions)))
261 (concat dir
262 (car (sort
263 (file-name-all-completions base-versions dir)
264 ;; bv-length is a fluid var for backup-extract-version:
265 (function
266 (lambda (fn1 fn2)
267 (> (backup-extract-version fn1)
268 (backup-extract-version fn2))))))))))
270 ;;; diff.el ends here