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