comparison lisp/packages/diff.el @ 70:131b0175ea99 r20-0b30

Import from CVS: tag r20-0b30
author cvs
date Mon, 13 Aug 2007 09:02:59 +0200
parents e04119814345
children 4be1180a9e89
comparison
equal deleted inserted replaced
69:804d1389bcd6 70:131b0175ea99
1 ;; -*-Emacs-Lisp-*- 1 ;;; diff.el --- Run `diff' in compilation-mode.
2 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 2
3 ;; 3 ;; Copyright (C) 1992, 1994 Free Software Foundation, Inc.
4 ;; File: diff.el 4
5 ;; Version: $Revision: 1.4 $ 5 ;; Keywords: unix, tools
6 ;; Author: This file is based on diff.el by 6
7 ;; sunpitt!wpmstr!fbresz@Sun.COM 1/27/89. 7 ;; This file is part of XEmacs.
8 ;; It has been completely rewritten in July 1994 by 8
9 ;; Sandy Rutherford <sandy@ibm550.sissa.it> 9 ;; XEmacs is free software; you can redistribute it and/or modify it
10 ;; RCS: 10 ;; under the terms of the GNU General Public License as published by
11 ;; Description: diff-mode for handling output from unix diff utility. 11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; Modified: Wed Jul 17 10:26:57 1996 (Andy Norman) ange@hplb.hpl.hp.com 12 ;; any later version.
13 ;; 13
14 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 14 ;; XEmacs is distributed in the hope that it will be useful, but
15 15 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;;; Copyright (C) 1990 Free Software Foundation, Inc. 16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 ;;; Copyright (C) 1994 Sandy Rutherford 17 ;; General Public License for more details.
18 18
19 ;;; This file is based on diff.el by sunpitt!wpmstr!fbresz@Sun.COM 1/27/89. 19 ;; You should have received a copy of the GNU General Public License
20 ;;; It has been completely rewritten in July 1994 by 20 ;; along with XEmacs; see the file COPYING. If not, write to the Free
21 ;;; Sandy Rutherford <sandy@ibm550.sissa.it> 21 ;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
22 22 ;; 02111-1307, USA.
23 ;;; This program is free software; you can redistribute it and/or modify 23
24 ;;; it under the terms of the GNU General Public License as published by 24 ;;; Synched up with: FSF 19.34.
25 ;;; the Free Software Foundation; either version 1, or (at your option) 25
26 ;;; any later version. 26 ;;; Commentary:
27 ;;; 27
28 ;;; This program is distributed in the hope that it will be useful, 28 ;; This package helps you explore differences between files, using the
29 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of 29 ;; UNIX command diff(1). The commands are `diff' and `diff-backup'.
30 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 30 ;; You can specify options with `diff-switches'.
31 ;;; GNU General Public License for more details. 31
32 ;;; 32 ;;; Code:
33 ;;; A copy of the GNU General Public License can be obtained from this 33
34 ;;; program's author (send electronic mail to sandy@ibm550.sissa.it) or 34 (require 'compile)
35 ;;; from the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, 35
36 ;;; MA 02139, USA. 36 ;;; This is duplicated in vc.el.
37
38 (provide 'diff)
39
40 ;;; User Variables
41
42 ;; should be in to loaddefs.el now.
43 ;;;###autoload 37 ;;;###autoload
44 (defvar diff-switches "-c" 38 (defvar diff-switches (purecopy "-c")
45 "*A list of switches (strings) to pass to the diff program.") 39 "*A string or list of strings specifying switches to be passed to diff.")
46 40
47 (defvar diff-do-narrow nil 41 (defvar diff-command "diff"
48 "*If non-nil diff buffers are initialized narrowed to each difference.") 42 "*The command to use to run diff.")
49 43
50 (defvar diff-load-hooks nil 44 (defvar diff-regexp-alist
51 "Hooks to run after loading diff.el") 45 '(
52 46 ;; -u format: @@ -OLDSTART,OLDEND +NEWSTART,NEWEND @@
53 ;;; Internal variables 47 ("^@@ -\\([0-9]+\\),[0-9]+ \\+\\([0-9]+\\),[0-9]+ @@$" 1 2)
54 48
55 (defconst diff-emacs-19-p 49 ;; -c format: *** OLDSTART,OLDEND ****
56 (let ((ver (string-to-int (substring emacs-version 0 2)))) 50 ("^\\*\\*\\* \\([0-9]+\\),[0-9]+ \\*\\*\\*\\*$" 1 nil)
57 (>= ver 19))) 51 ;; --- NEWSTART,NEWEND ----
58 52 ("^--- \\([0-9]+\\),[0-9]+ ----$" nil 1)
59 (or diff-emacs-19-p (require 'emacs-19)) 53
60 54 ;; plain diff format: OLDSTART[,OLDEND]{a,d,c}NEWSTART[,NEWEND]
61 (defvar diff-old-file nil) 55 ("^\\([0-9]+\\)\\(,[0-9]+\\)?[adc]\\([0-9]+\\)\\(,[0-9]+\\)?$" 1 3)
62 ;; A list whose car is the name of the old file, and whose cdr indicates 56
63 ;; whether we should delete the buffer on quit. 57 ;; -e (ed) format: OLDSTART[,OLDEND]{a,d,c}
64 (defvar diff-new-file nil) 58 ("^\\([0-9]+\\)\\(,[0-9]+\\)?[adc]$" 1)
65 ;; Same as diff-old-file, except for the new file. 59
66 (defvar diff-total-differences "0") 60 ;; -f format: {a,d,c}OLDSTART[ OLDEND]
67 ;; Total number of difference hunks as a string. 61 ;; -n format: {a,d,c}OLDSTART LINES-CHANGED
68 (defvar diff-current-difference "0") 62 ("^[adc]\\([0-9]+\\)\\( [0-9]+\\)?$" 1)
69 ;; Current difference hunk as a string. 63 )
70 (defvar diff-current-hunk 0) 64 "Alist (REGEXP OLD-IDX NEW-IDX) of regular expressions to match difference
71 ;; Current difference hunk as an integer. 65 sections in \\[diff] output. If REGEXP matches, the OLD-IDX'th
72 (defvar diff-total-hunks 0) 66 subexpression gives the line number in the old file, and NEW-IDX'th
73 ;; Total number of difference hunks as an integer. 67 subexpression gives the line number in the new file. If OLD-IDX or NEW-IDX
74 (defvar diff-hunk-vector (vector 0)) 68 is nil, REGEXP matches only half a section.")
75 ;; Vector storing the starting positions of the difference hunks. 69
76 (defvar diff-old-file-pattern nil) 70 (defvar diff-old-file nil
77 (defvar diff-new-file-pattern nil) 71 "This is the old file name in the comparison in this buffer.")
78 (defvar diff-hunk-pattern nil) 72 (defvar diff-new-file nil
79 ;; Regexps to use when parsing file lines in difference hunks. 73 "This is the new file name in the comparison in this buffer.")
80 74 (defvar diff-old-temp-file nil
81 75 "This is the name of a temp file to be deleted after diff finishes.")
82 (defvar diff-search-pattern-alist 76 (defvar diff-new-temp-file nil
83 (list 77 "This is the name of a temp file to be deleted after diff finishes.")
84 (list ?e "^[0-9]\\(,[0-9]+\\)?[acd]$" "^\\([0-9]+\\)" nil) 78
85 (list ?c "^\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\n" 79 ;; See compilation-parse-errors-function (compile.el).
86 "^\\*+ \\([0-9]+\\)" "^-+ \\([0-9]+\\)") 80 (defun diff-parse-differences (limit-search find-at-least)
87 (list ?u "^@@ " "^@@ -\\([0-9]+\\)" "^@@ -[0-9,]+ \\+\\([0-9]+\\)") 81 (setq compilation-error-list nil)
88 (list nil "^[0-9]+" "^\\([0-9]+\\)" "^[0-9,]+[acd]\\([0-9]+\\)"))) 82 (message "Parsing differences...")
89 83
90 (defvar diff-keymap-grokked nil) 84 ;; Don't reparse diffs already seen at last parse.
91 85 (if compilation-parsing-end (goto-char compilation-parsing-end))
92 (defvar diff-temp-template "/tmp/diff") 86
93 87 ;; Construct in REGEXP a regexp composed of all those in dired-regexp-alist.
94 ;; Initialize the keymap if it isn't already 88 (let ((regexp (mapconcat #'(lambda (elt)
95 89 (concat "\\(" (car elt) "\\)"))
96 (defvar diff-mode-map nil 90 diff-regexp-alist
97 "Keymap used in diff mode.") 91 "\\|"))
98 92 ;; (GROUP-IDX OLD-IDX NEW-IDX)
99 (if diff-mode-map 93 (groups (let ((subexpr 1))
100 nil 94 (mapcar #'(lambda (elt)
101 (setq diff-mode-map (make-keymap)) 95 (prog1
102 (suppress-keymap diff-mode-map) 96 (cons subexpr
103 (define-key diff-mode-map "?" 'describe-mode) 97 (mapcar #'(lambda (n)
104 (define-key diff-mode-map "." 'diff-display-file) 98 (and n
105 (define-key diff-mode-map "N" 'diff-narrow) 99 (+ subexpr n)))
106 (define-key diff-mode-map "W" 'widen) 100 (cdr elt)))
107 (define-key diff-mode-map "f" 'diff-find-file) 101 (setq subexpr (+ subexpr 1
108 (define-key diff-mode-map "h" 'diff-show-header) 102 ;;#### undefined??
109 (define-key diff-mode-map "j" 'diff-show-difference) 103 (count-regexp-groupings
110 (define-key diff-mode-map "n" 'diff-next-difference) 104 (car elt))))))
111 (define-key diff-mode-map "o" 'diff-find-file-other-window) 105 diff-regexp-alist)))
112 (define-key diff-mode-map "p" 'diff-previous-difference) 106
113 (define-key diff-mode-map "q" 'diff-quit) 107 (new-error
114 (define-key diff-mode-map "w" 'diff-find-file-other-frame) 108 (function (lambda (file subexpr)
115 (define-key diff-mode-map "\C-c\C-c" 'diff-find-file-other-window) 109 (setq compilation-error-list
116 (define-key diff-mode-map " " 'diff-advertised-scroll-up) 110 (cons
117 (define-key diff-mode-map "\177" 'diff-advertised-scroll-down) 111 (cons (save-excursion
118 (define-key diff-mode-map "\C-n" 'diff-next-line) 112 ;; Report location of message
119 (define-key diff-mode-map "\C-p" 'diff-previous-line) 113 ;; at beginning of line.
120 (define-key diff-mode-map "\M->" 'diff-end-of-buffer) 114 (goto-char
121 (define-key diff-mode-map "\M-<" 'diff-beginning-of-buffer)) 115 (match-beginning subexpr))
122 116 (beginning-of-line)
123 ;;; Internal functions 117 (point-marker))
124 118 ;; Report location of corresponding text.
125 (defun diff-grok-keys (to-command from-command) 119 (let ((line (string-to-int
126 ;; Assigns to TO-COMMAND the keys for the global binding of FROM-COMMAND. 120 (buffer-substring
127 ;; Does not clobber anything in the local keymap. 121 (match-beginning subexpr)
128 (let ((keys (where-is-internal from-command))) 122 (match-end subexpr)))))
129 (while keys 123 (save-excursion
130 (condition-case nil 124 (save-match-data
131 (if (eq (global-key-binding (car keys)) (key-binding (car keys))) 125 (set-buffer (find-file-noselect file)))
132 (local-set-key (car keys) to-command)) 126 (save-excursion
133 (error nil)) 127 (goto-line line)
134 (setq keys (cdr keys))))) 128 (point-marker)))))
135 129 compilation-error-list)))))
136 (defun diff-grok-keymap () 130
137 (if diff-keymap-grokked 131 (found-desired nil)
138 () 132 (num-loci-found 0)
139 (if (and term-setup-hook (boundp 'command-line-args-left)) 133 g)
140 (progn 134
141 (if diff-emacs-19-p 135 (while (and (not found-desired)
142 (run-hooks term-setup-hook) 136 ;; We don't just pass LIMIT-SEARCH to re-search-forward
143 (funcall term-setup-hook)) 137 ;; because we want to find matches containing LIMIT-SEARCH
144 (setq term-setup-hook nil))) 138 ;; but which extend past it.
145 (setq diff-keymap-grokked t) 139 (re-search-forward regexp nil t))
146 (diff-grok-keys 'diff-next-line 'next-line) 140
147 (diff-grok-keys 'diff-previous-line 'previous-line) 141 ;; Find which individual regexp matched.
148 (diff-grok-keys 'diff-forward-char 'forward-char) 142 (setq g groups)
149 (diff-grok-keys 'diff-backward-char 'backward-char) 143 (while (and g (null (match-beginning (car (car g)))))
150 (diff-grok-keys 'diff-scroll-up 'scroll-up) 144 (setq g (cdr g)))
151 (diff-grok-keys 'diff-scroll-down 'scroll-down) 145 (setq g (car g))
152 (diff-grok-keys 'diff-beginning-of-buffer 'beginning-of-buffer) 146
153 (diff-grok-keys 'diff-end-of-buffer 'end-of-buffer))) 147 (if (nth 1 g) ;OLD-IDX
154 148 (funcall new-error diff-old-file (nth 1 g)))
155 (defun diff-buffer-narrowed-p () 149 (if (nth 2 g) ;NEW-IDX
156 ;; Returns t if the current buffer is narrowed 150 (funcall new-error diff-new-file (nth 2 g)))
157 (save-restriction 151
158 (let ((min (point-min)) 152 (setq num-loci-found (1+ num-loci-found))
159 (max (point-max))) 153 (if (or (and find-at-least
160 (widen) 154 (>= num-loci-found find-at-least))
161 (not (and (= min (point-min)) (= max (point-max))))))) 155 (and limit-search (>= (point) limit-search)))
162 156 ;; We have found as many new loci as the user wants,
163 (defun diff-current-hunk () 157 ;; or the user wanted a specific diff, and we're past it.
164 ;; Returns the current diff hunk. 158 (setq found-desired t)))
165 (let ((pt (point)) 159 (if found-desired
166 (start 0) 160 (setq compilation-parsing-end (point))
167 (end (1+ diff-total-hunks)) 161 ;; Set to point-max, not point, so we don't perpetually
168 m) 162 ;; parse the last bit of text when it isn't a diff header.
169 (while (> (- end start) 1) 163 (setq compilation-parsing-end (point-max)))
170 (setq m (/ (+ start end) 2)) 164 (message "Parsing differences...done"))
171 (if (>= pt (aref diff-hunk-vector m)) 165 (setq compilation-error-list (nreverse compilation-error-list)))
172 (setq start m) 166
173 (setq end m))) 167 ;;;###autoload
174 (if (>= pt (aref diff-hunk-vector end)) 168 (defun diff (old new &optional switches)
175 (setq m end) 169 "Find and display the differences between OLD and NEW files.
176 (setq m start)) 170 Interactively the current buffer's file name is the default for NEW
177 ;; Don't treat end of buffer as start of next hunk 171 and a backup file for NEW is the default for OLD.
178 (if (eobp) (1- m) m))) 172 With prefix arg, prompt for diff switches."
179 173 (interactive
180 (defun diff-hunk-min (n) 174 (nconc
181 ;; Returns the start of the current diff hunk. 175 (let (oldf newf)
182 (aref diff-hunk-vector n)) 176 (nreverse
183 177 (list
184 (defun diff-hunk-max (n) 178 (setq newf (buffer-file-name)
185 ;; Returns the end of the current diff hunk.
186 (aref diff-hunk-vector (1+ n)))
187
188 (defun diff-parse-hunks ()
189 ;; Parses a buffer of diff output.
190 (save-excursion
191 (save-restriction
192 (message "Parsing differences...")
193 (widen)
194 (goto-char (point-min))
195 (let ((hunks (list 1)))
196 (while (re-search-forward diff-hunk-pattern nil t)
197 (setq hunks (cons (match-beginning 0) hunks)))
198 (setq diff-total-hunks (1- (length hunks))
199 diff-hunk-vector (apply 'vector
200 (nreverse (cons (point-max) hunks)))))))
201 (message "Parsing differences...done"))
202
203 (defun diff-update-modeline ()
204 ;; Updates the mode line to show current diff hunk.
205 (if (or (< (point) (diff-hunk-min diff-current-hunk))
206 (>= (point) (diff-hunk-max diff-current-hunk)))
207 (progn
208 (setq diff-current-hunk (diff-current-hunk)
209 diff-current-difference (int-to-string diff-current-hunk))
210 (set-buffer-modified-p (buffer-modified-p)))))
211
212 (defun diff-read-args (oldprompt newprompt switchprompt
213 &optional file-for-backup)
214 ;; Grab the args for diff. OLDPROMPT and NEWPROMPT are the prompts
215 ;; for the old & new filenames, SWITCHPROMPT for the list of
216 ;; switches. If FILE_FOR_BACKUP is provided (it must be a string if
217 ;; so), then it will be used to try & work out a file & backup to
218 ;; diff, & in this case the prompting order is backwards. %s in a
219 ;; prompt has a guess substituted into it. This is nasty.
220 (let (oldf newf)
221 (if file-for-backup
222 (setq newf file-for-backup
223 newf (if (and newf (file-exists-p newf)) 179 newf (if (and newf (file-exists-p newf))
224 (read-file-name 180 (read-file-name
225 (format newprompt (file-name-nondirectory newf)) 181 (concat "Diff new file: ("
182 (file-name-nondirectory newf) ") ")
226 nil newf t) 183 nil newf t)
227 (read-file-name (format newprompt "") nil nil t)) 184 (read-file-name "Diff new file: " nil nil t)))
228 oldf (file-newest-backup newf) 185 (setq oldf (file-newest-backup newf)
229 oldf (if (and oldf (file-exists-p oldf)) 186 oldf (if (and oldf (file-exists-p oldf))
230 (read-file-name 187 (read-file-name
231 (format oldprompt (file-name-nondirectory oldf)) 188 (concat "Diff original file: ("
232 nil oldf t) 189 (file-name-nondirectory oldf) ") ")
233 (read-file-name (format oldprompt "") 190 (file-name-directory oldf) oldf t)
234 (file-name-directory newf) nil t))) 191 (read-file-name "Diff original file: "
235 ;; Else we aren't trying to be bright... 192 (file-name-directory newf) nil t))))))
236 (setq oldf (read-file-name (format oldprompt "") nil nil t) 193 (if current-prefix-arg
237 newf (read-file-name 194 (list (read-string "Diff switches: "
238 (format newprompt (file-name-nondirectory oldf)) 195 (if (stringp diff-switches)
239 nil (file-name-directory oldf) t))) 196 diff-switches
240 (list oldf newf (diff-read-switches switchprompt)))) 197 (mapconcat 'identity diff-switches " "))))
241 198 nil)))
242 (defun diff-read-switches (switchprompt) 199 (setq new (expand-file-name new)
243 ;; Read and return a list of switches 200 old (expand-file-name old))
244 (if current-prefix-arg 201 ;; XEmacs addition -- allow (diff "../old/" "new-file.el") to work
245 (let ((default (if (listp diff-switches) 202 (cond ((file-directory-p old)
246 (mapconcat 'identity diff-switches " ") 203 (or (file-directory-p new)
247 diff-switches))) 204 (setq old (expand-file-name (file-name-nondirectory new)
248 (diff-fix-switches 205 (file-name-as-directory old)))))
249 (read-string (format switchprompt default) default))))) 206 ((file-directory-p new)
250 207 (setq new (expand-file-name (file-name-nondirectory old)
251 (defun diff-fix-switches (switch-spec) 208 (file-name-as-directory new)))))
252 ;; Parse a string into a list of switches or leave it be if it's 209 (let ((old-alt (file-local-copy old))
253 ;; not a string 210 (new-alt (file-local-copy new))
254 (if (stringp switch-spec) 211 buf)
255 (let (result (start 0)) 212 (unwind-protect
256 (while (string-match "\\(\\S-+\\)" switch-spec start) 213 (let ((command
257 (setq result (cons (substring switch-spec (match-beginning 1) 214 (mapconcat 'identity
258 (match-end 1)) 215 (append (list diff-command)
259 result) 216 ;; Use explicitly specified switches
260 start (match-end 0))) 217 (if switches
261 (nreverse result)) 218 (if (consp switches)
262 switch-spec)) 219 switches (list switches))
263 220 ;; If not specified, use default.
264 (defun diff-get-file-buffer (file) 221 (if (consp diff-switches)
265 ;; Returns \(BUFFER . DEL-P\), where DEL-P is t if diff is expected 222 diff-switches
266 ;; to delete the buffer, and nil otherwise. 223 (list diff-switches)))
267 (let* ((buff (get-file-buffer file)) 224 (if (or old-alt new-alt)
268 (del-p (null buff))) 225 (list "-L" old "-L" new))
269 (if (and buff (buffer-modified-p buff)) 226 (list
270 (progn 227 (shell-quote-argument (or old-alt old)))
271 (message 228 (list
272 "Buffer %s is modified. Diffing against buffer contents." 229 (shell-quote-argument (or new-alt new))))
273 (buffer-name buff)) 230 " ")))
274 (sit-for 1))) 231 (setq buf
275 ;; Call find-file-noselect even if we already have the buffer, 232 (compile-internal command
276 ;; as it will run verify-buffer-file-modtime. 233 "No more differences" "Diff"
277 (cons (find-file-noselect file) del-p))) 234 'diff-parse-differences))
278 235 (pop-to-buffer buf)
279 (defun diff-cleanup-buffers () 236 ;; Avoid frightening people with "abnormally terminated"
280 ;; Cleans up diff buffers by deleting buffers that we don't expect 237 ;; if diff finds differences.
281 ;; the user to care about. 238 (set (make-local-variable 'compilation-exit-message-function)
282 (let ((files (list diff-old-file diff-new-file))) 239 (lambda (status code msg)
283 (while files 240 (cond ((not (eq status 'exit))
284 (let ((ent (car files)) 241 (cons msg code))
285 buff) 242 ((zerop code)
286 (and (cdr ent) 243 '("finished (no differences)\n" . "no differences"))
287 (setq buff (get-file-buffer (car ent))) 244 ((= code 1)
288 (not (buffer-modified-p buff)) 245 '("finished\n" . "differences found"))
289 (kill-buffer buff))) 246 (t
290 (setq files (cdr files))) 247 (cons msg code)))))
291 (if (get-buffer "*Diff Header*") 248 (set (make-local-variable 'diff-old-file) old)
292 (kill-buffer "*Diff Header*")))) 249 (set (make-local-variable 'diff-new-file) new)
293 250 (set (make-local-variable 'diff-old-temp-file) old-alt)
294 (defun diff-latest-backup-file (file) 251 (set (make-local-variable 'diff-new-temp-file) new-alt)
252 (set (make-local-variable 'compilation-finish-function)
253 (function (lambda (buff msg)
254 (if diff-old-temp-file
255 (delete-file diff-old-temp-file))
256 (if diff-new-temp-file
257 (delete-file diff-new-temp-file)))))
258 buf))))
259
260 ;;;###autoload
261 (defun diff-backup (file &optional switches)
262 "Diff this file with its backup file or vice versa.
263 Uses the latest backup, if there are several numerical backups.
264 If this file is a backup, diff it with its original.
265 The backup file is the first file given to `diff'."
266 (interactive (list (read-file-name "Diff (file with backup): ")
267 (if current-prefix-arg
268 (read-string "Diff switches: "
269 (if (stringp diff-switches)
270 diff-switches
271 (mapconcat 'identity
272 diff-switches " ")))
273 nil)))
274 (let (bak ori)
275 (if (backup-file-name-p file)
276 (setq bak file
277 ori (file-name-sans-versions file))
278 (setq bak (or (diff-latest-backup-file file)
279 (error "No backup found for %s" file))
280 ori file))
281 (diff bak ori switches)))
282
283 (defun diff-latest-backup-file (fn) ; actually belongs into files.el
295 "Return the latest existing backup of FILE, or nil." 284 "Return the latest existing backup of FILE, or nil."
296 ;; First try simple backup, then the highest numbered of the 285 (let ((handler (find-file-name-handler fn 'diff-latest-backup-file)))
297 ;; numbered backups.
298 ;; Ignore the value of version-control because we look for existing
299 ;; backups, which maybe were made earlier or by another user with
300 ;; a different value of version-control.
301 (let* ((file (expand-file-name file))
302 (handler (find-file-name-handler file 'diff-latest-backup-file)))
303 (if handler 286 (if handler
304 (funcall handler 'diff-latest-backup-file file) 287 (funcall handler 'diff-latest-backup-file fn)
288 ;; First try simple backup, then the highest numbered of the
289 ;; numbered backups.
290 ;; Ignore the value of version-control because we look for existing
291 ;; backups, which maybe were made earlier or by another user with
292 ;; a different value of version-control.
293 (setq fn (file-chase-links (expand-file-name fn)))
305 (or 294 (or
306 (let ((bak (make-backup-file-name file))) 295 (let ((bak (make-backup-file-name fn)))
307 (if (file-exists-p bak) bak)) 296 (if (file-exists-p bak) bak))
308 (let* ((dir (file-name-directory file)) 297 ;; We use BACKUPNAME to cope with backups stored in a different dir.
309 (base-versions (concat (file-name-nondirectory file) ".~")) 298 (let* ((backupname (car (find-backup-file-name fn)))
299 (dir (file-name-directory backupname))
300 (base-versions (concat (file-name-sans-versions
301 (file-name-nondirectory backupname))
302 ".~"))
310 (bv-length (length base-versions))) 303 (bv-length (length base-versions)))
311 (concat dir 304 (concat dir
312 (car (sort 305 (car (sort
313 (file-name-all-completions base-versions dir) 306 (file-name-all-completions base-versions dir)
314 ;; bv-length is a fluid var for backup-extract-version: 307 ;; bv-length is a fluid var for backup-extract-version:
315 (function 308 (function
316 (lambda (fn1 fn2) 309 (lambda (fn1 fn2)
317 (> (backup-extract-version fn1) 310 (> (backup-extract-version fn1)
318 (backup-extract-version fn2)))))))))))) 311 (backup-extract-version fn2))))))))))))
319 312
320 (defun diff-file-line (&optional old-file-p) 313 (provide 'diff)
321 "Return line number of current hunk in `diff-new-file'. 314
322 With optional argument OLD-FILE-P, use `diff-old-file' instead." 315 ;;; diff.el ends here
323 (save-excursion
324 (let ((min (diff-hunk-min diff-current-hunk))
325 (max (diff-hunk-max diff-current-hunk))
326 (regexp (if old-file-p diff-old-file-pattern diff-new-file-pattern)))
327 (goto-char min)
328 (or (and regexp (re-search-forward regexp max t))
329 (error "Unable to locate a file line for %s file."
330 (if old-file-p "old" "new")))
331 (string-to-int (buffer-substring (match-beginning 1) (match-end 1))))))
332
333 (defun diff-run-diff (switches old old-temp new new-temp)
334 ;; Actually run the diff process with SWITCHES on OLD and NEW.
335 ;; OLD-TEMP and NEW-TEMP are names of temp files that can be used
336 ;; to dump the data out to.
337 (insert "diff " (mapconcat 'identity switches " ") " " old
338 " " new "\n")
339 (apply 'call-process "diff" nil t nil
340 (append switches (list old-temp new-temp))))
341
342
343 (defun diff-fix-file-names (old old-temp new new-temp pattern)
344 ;; Replaces any temp file names with the real names of files.
345 (save-excursion
346 (save-restriction
347 (let ((files (list old new))
348 (temps (list old-temp new-temp))
349 buffer-read-only case-fold-search)
350 (goto-char (point-min))
351 (if (re-search-forward pattern nil t)
352 (narrow-to-region (point-min) (match-beginning 0)))
353 (while files
354 (let ((regexp (concat "[ \t\n]\\("
355 (regexp-quote (car temps))
356 "\\)[ \t\n]")))
357 (goto-char (point-min))
358 (forward-line 1)
359 (while (re-search-forward regexp nil t)
360 (goto-char (match-beginning 1))
361 (delete-region (point) (match-end 1))
362 (insert (car files))))
363 (setq files (cdr files)
364 temps (cdr temps)))))))
365
366 ;;;; User commands
367
368 (defun diff-mode ()
369 "Diff Mode is used by \\[diff] for perusing the output from the diff program.
370 All normal editing commands are turned off. Instead, these are available:
371 \\<diff-mode-map>
372 \\[diff-advertised-scroll-up] Scroll to next screen of this difference.
373 \\[diff-advertised-scroll-down] Scroll to previous screen of this difference.
374 \\[diff-next-difference] Move to Next Difference.
375 \\[diff-previous-difference] Move to Previous Difference.
376 \\[diff-show-difference] Jump to difference specified by numeric position.
377 \\[diff-find-file] Find current diff in file
378 \\[diff-find-file-other-window] Find current diff in file in other window
379 \\[diff-display-file] Display file in other window
380 \\[diff-narrow] Narrow diff buffer to current difference
381 \\[widen] Widen diff buffer
382 \\[diff-show-header] Show diff header describing file name etc.
383 \\[diff-quit] Quit diff
384 "
385 (interactive)
386 (use-local-map diff-mode-map)
387 (diff-grok-keymap)
388 (setq buffer-read-only t
389 major-mode 'diff-mode
390 mode-name "Diff"
391 mode-line-modified "--- "
392 mode-line-process
393 '(" " diff-current-difference "/" diff-total-differences))
394 (diff-parse-hunks)
395 (setq diff-total-differences (int-to-string diff-total-hunks)))
396
397 ;;; Motion commands
398
399 (defun diff-next-difference (n)
400 "In diff-mode go the the beginning of the next difference hunk."
401 (interactive "p")
402 (if (zerop n)
403 (goto-char (diff-hunk-min diff-current-hunk))
404 (let ((narrow (diff-buffer-narrowed-p))
405 (max (point-max))
406 (min (point-min)))
407 (unwind-protect
408 (progn
409 (widen)
410 (setq diff-current-hunk (+ n diff-current-hunk))
411 (cond ((> diff-current-hunk diff-total-hunks)
412 (setq diff-current-hunk diff-total-hunks)
413 (message "No following difference hunks."))
414 ((< diff-current-hunk 0)
415 (setq diff-current-hunk 0)
416 (message "No preceding difference hunks.")))
417 (setq diff-current-difference (int-to-string diff-current-hunk)
418 min (goto-char (diff-hunk-min diff-current-hunk))
419 max (diff-hunk-max diff-current-hunk)))
420 (if narrow (narrow-to-region min max))))
421 (set-buffer-modified-p (buffer-modified-p))))
422
423 (defun diff-previous-difference (n)
424 "In diff-mode go the the beginning of the previous difference hunk."
425 (interactive "p")
426 (diff-next-difference (- n)))
427
428 (defun diff-next-line (n)
429 "In diff-mode go to the next line."
430 (interactive "p")
431 (condition-case nil
432 (next-line n)
433 (error (if (> n 0) (message "End of difference hunk"))))
434 (diff-update-modeline))
435
436 (defun diff-previous-line (n)
437 "In diff-mode go to the previous line."
438 (interactive "p")
439 (diff-next-line (- n)))
440
441 (defun diff-forward-char (n)
442 "In diff-mode move the point forward."
443 (interactive "p")
444 (forward-char n)
445 (diff-update-modeline))
446
447 (defun diff-backward-char (n)
448 "In diff-mode move the point backward."
449 (interactive "p")
450 (backward-char n)
451 (diff-update-modeline))
452
453 (defun diff-scroll-up (n)
454 "In diff-mode scroll the buffer up."
455 (interactive "P")
456 (scroll-up n)
457 (diff-update-modeline))
458
459 (fset 'diff-advertised-scroll-up 'diff-scroll-up)
460
461 (defun diff-scroll-down (n)
462 "In diff-mode scroll the buffer down."
463 (interactive "P")
464 (scroll-down n)
465 (diff-update-modeline))
466
467 (fset 'diff-advertised-scroll-down 'diff-scroll-down)
468
469 (defun diff-beginning-of-buffer (n)
470 "In diff-mode go to the beginning of the buffer."
471 (interactive "P")
472 (beginning-of-buffer n)
473 (diff-update-modeline))
474
475 (defun diff-end-of-buffer (n)
476 "In diff-mode go to the end of the buffer."
477 (interactive "P")
478 (end-of-buffer n)
479 (diff-update-modeline))
480
481 ;;; The main command.
482
483 ;;;###autoload
484 (defun diff (old new &optional switches)
485 "Find and display the differences between OLD and NEW files.
486 Interactively you are prompted with the current buffer's file name for NEW
487 and what appears to be its backup for OLD."
488 ;; Support for diffing directories is rather limited. It needs work.
489 (interactive (diff-read-args "Diff original file (%s) "
490 "Diff new file (%s) "
491 "Switches for diff (%s) "
492 (buffer-file-name)))
493 (setq switches (diff-fix-switches (or switches diff-switches))
494 old (expand-file-name old)
495 new (expand-file-name new))
496 (let ((curr-buff (current-buffer))
497 doing-dirs old-temp new-temp old-buffer new-buffer flag)
498 (let ((fdp-old (file-directory-p old))
499 (fdp-new (file-directory-p new)))
500 (cond
501 ((null (or fdp-new fdp-old)))
502 ((null fdp-new)
503 (setq old (expand-file-name (file-name-nondirectory new) old)))
504 ((null fdp-old)
505 (setq new (expand-file-name (file-name-nondirectory old) new)))
506 (t (setq doing-dirs t))))
507 ;; (message "diff %s %s %s..."
508 ;; (mapconcat (function identity) switches " ") new old)
509 (message "diff %s %s %s..."
510 (mapconcat (function identity) switches " ") old new)
511 (if doing-dirs
512 (setq diff-old-file nil
513 diff-new-file nil)
514 (setq old-temp (make-temp-name (concat diff-temp-template "1"))
515 new-temp (make-temp-name (concat diff-temp-template "2"))
516 old-buffer (diff-get-file-buffer old)
517 new-buffer (diff-get-file-buffer new)
518 diff-old-file (cons old (cdr old-buffer))
519 diff-new-file (cons new (cdr new-buffer))))
520 (let (case-fold-search)
521 (mapcar (function
522 (lambda (x)
523 (if (string-match "[ecu]" x)
524 (setq flag (aref x (match-beginning 0))))))
525 switches))
526 (unwind-protect
527 (let ((patterns (assq flag diff-search-pattern-alist)))
528 (set-buffer (get-buffer-create "*Diff Output*"))
529 (setq default-directory (file-name-directory new)
530 diff-old-file-pattern (nth 2 patterns)
531 diff-new-file-pattern (nth 3 patterns)
532 diff-hunk-pattern (nth 1 patterns))
533 (let (buffer-read-only)
534 (if (fboundp 'buffer-disable-undo)
535 (buffer-disable-undo (current-buffer))
536 ;; old style (Emacs 18.55 and earlier)
537 (buffer-disable-undo (current-buffer)))
538 (widen)
539 (erase-buffer)
540 (if doing-dirs
541 (progn
542 (diff-run-diff switches old old new new)
543 (setq diff-hunk-pattern (concat diff-hunk-pattern
544 "\\|^Only in ")))
545 (save-excursion
546 (set-buffer (car old-buffer))
547 (write-region (point-min) (point-max) old-temp nil 'quiet)
548 (set-buffer (car new-buffer))
549 (write-region (point-min) (point-max) new-temp nil 'quiet))
550 (diff-run-diff switches old old-temp new new-temp))
551 ;; Need to replace file names
552 (if (and (not doing-dirs) (memq flag '(?c ?u)))
553 (diff-fix-file-names old old-temp new new-temp
554 diff-hunk-pattern))
555 (diff-mode)
556 (goto-char (point-min))
557 (setq diff-current-difference "0"
558 diff-current-hunk 0)
559 (if (zerop diff-total-hunks)
560 (progn
561 (diff-cleanup-buffers)
562 (message "No differences"))
563 (if diff-do-narrow (narrow-to-region (point) (diff-hunk-max 0)))
564 (display-buffer (current-buffer))
565 (message "%s difference hunk%s" diff-total-differences
566 (if (= diff-total-hunks 1) "" "s")))))
567 (condition-case nil
568 (delete-file old-temp)
569 (error nil))
570 (condition-case nil
571 (delete-file new-temp)
572 (error nil))
573 (set-buffer curr-buff))))
574
575 ;;;###autoload
576 (defun diff-backup (file &optional switches)
577 "Diff this file with its backup file or vice versa.
578 Uses the latest backup, if there are several numerical backups.
579 If this file is a backup, diff it with its original.
580 The backup file is the first file given to `diff'."
581 (interactive (list (read-file-name "Diff (file with backup): ")
582 (and current-prefix-arg
583 (diff-read-switches "Diff switches: "))))
584 (let (bak ori)
585 (if (backup-file-name-p file)
586 (setq bak file
587 ori (file-name-sans-versions file))
588 (setq bak (or (diff-latest-backup-file file)
589 (error "No backup found for %s" file))
590 ori file))
591 (diff bak ori switches)))
592
593 (defun diff-show-difference (n)
594 "Show difference number N (prefix argument)."
595 (interactive "p")
596 (let ((narrowedp (diff-buffer-narrowed-p))
597 (min (diff-hunk-min diff-current-hunk))
598 (max (diff-hunk-max diff-current-hunk)))
599 (unwind-protect
600 (progn
601 (widen)
602 (cond
603 ((< n 0)
604 (message "No negative hunks.")
605 (setq n 0))
606 ((> n diff-total-hunks)
607 (message "No hunk %d." n)
608 (setq n diff-total-hunks)))
609 (setq diff-current-hunk n
610 diff-current-difference (int-to-string diff-current-hunk)
611 min (diff-hunk-min n)
612 max (diff-hunk-max n))
613 (goto-char min))
614 (if narrowedp (narrow-to-region min max))
615 (set-buffer-modified-p (buffer-modified-p)))))
616
617 (defun diff-show-header ()
618 "Show `diff-header'."
619 (interactive)
620 (with-output-to-temp-buffer "*Diff Header*"
621 (princ (save-restriction
622 (widen)
623 (buffer-substring (diff-hunk-min 0) (diff-hunk-max 0))))))
624
625
626 (defun diff-find-file (old-file-p)
627 "Visit diffed file, at the point corresponding to the current hunk.
628 Default is to visit the new file; prefix means visit old file instead."
629 (interactive "P")
630 (let ((line (diff-file-line old-file-p)))
631 (find-file
632 (if old-file-p
633 (car diff-old-file)
634 (car diff-new-file)))
635 (goto-line line)
636 (recenter 0)))
637
638 (defun diff-find-file-other-window (old-file-p)
639 "Visit the diffed file in other window, with the point at the current hunk.
640 Default is to visit the new file; prefix means visit the old file instead."
641 (interactive "P")
642 (let ((line (diff-file-line old-file-p)))
643 (find-file-other-window
644 (if old-file-p
645 (car diff-old-file)
646 (car diff-new-file)))
647 (goto-line line)
648 (recenter 0)))
649
650 (defun diff-find-file-other-frame (old-file-p)
651 "Visit the diffed file in other frame, with point at the current hunk.
652 Default is to visit the new file; prefix means visit the old file instead."
653 (interactive "P")
654 (let ((line (diff-file-line old-file-p)))
655 (find-file-other-frame
656 (if old-file-p
657 (car diff-old-file)
658 (car diff-new-file)))
659 (goto-line line)
660 (recenter 0)))
661
662 (defun diff-display-file (old-file-p)
663 "Display the diffed file in other window, with point at the current hunk.
664 Default is to visit the new file; prefix means visit the old file instead."
665 (interactive "P")
666 (let ((line (diff-file-line old-file-p))
667 (wind (display-buffer (find-file-noselect (if old-file-p
668 (car diff-old-file)
669 (car diff-new-file)))))
670 (curr-wind (selected-window)))
671 (unwind-protect
672 (progn
673 (select-window wind)
674 (goto-line line)
675 (recenter 0))
676 (select-window curr-wind))))
677
678 (defun diff-quit ()
679 "Quit diff by killing the diff buffer."
680 (interactive)
681 (kill-buffer "*Diff Output*")
682 (diff-cleanup-buffers))
683
684 (defun diff-narrow ()
685 "Narrow diff buffer to current difference hunk."
686 (interactive)
687 (narrow-to-region (diff-hunk-min diff-current-hunk)
688 (diff-hunk-max diff-current-hunk)))
689
690 ;;; Run any load hooks
691 (run-hooks 'diff-load-hook)
692
693 ;;; end of diff.el