1;;; diff.el --- Run `diff' in compilation-mode. 2 3;; Copyright (C) 1992, 1994 Free Software Foundation, Inc. 4 5;; Keywords: unix, tools 6 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 11;; the Free Software Foundation; either version 2, or (at your option) 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 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 29;;; Code: 30 31(require 'compile) 32 33;;; This is duplicated in vc.el. 34(defvar diff-switches "-c" 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 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 72;; See compilation-parse-errors-function (compile.el). 73(defun diff-parse-differences (limit-search find-at-least) 74 (setq compilation-error-list nil) 75 (message "Parsing differences...") 76 77 ;; Don't reparse diffs already seen at last parse. 78 (if compilation-parsing-end (goto-char compilation-parsing-end)) 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 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. 111 (let ((line (string-to-int 112 (buffer-substring 113 (match-beginning subexpr) 114 (match-end subexpr))))) 115 (save-excursion 116 (save-match-data 117 (set-buffer (find-file-noselect file))) 118 (save-excursion 119 (goto-line line) 120 (point-marker))))) 121 compilation-error-list))))) 122 123 (found-desired nil) 124 (num-loci-found 0) 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 144 (setq num-loci-found (1+ num-loci-found)) 145 (if (or (and find-at-least 146 (>= num-loci-found find-at-least)) 147 (and limit-search (>= (point) limit-search))) 148 ;; We have found as many new loci as the user wants, 149 ;; or the user wanted a specific diff, and we're past it. 150 (setq found-desired t))) 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. 155 (setq compilation-parsing-end (point-max))) 156 (message "Parsing differences...done")) 157 (setq compilation-error-list (nreverse compilation-error-list))) 158 159;;;###autoload 160(defun diff (old new &optional switches) 161 "Find and display the differences between OLD and NEW files. 162Interactively the current buffer's file name is the default for NEW 163and a backup file for NEW is the default for OLD. 164With prefix arg, prompt for diff switches." 165 (interactive 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))) 191 (setq new (expand-file-name new) 192 old (expand-file-name old)) 193 (let ((old-alt (file-local-copy old)) 194 (new-alt (file-local-copy new)) 195 buf) 196 (unwind-protect 197 (let ((command 198 (mapconcat 'identity 199 (append '("diff") 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))) 208 (if (or old-alt new-alt) 209 (list "-L" old "-L" new)) 210 (list 211 (shell-quote-argument (or old-alt old))) 212 (list 213 (shell-quote-argument (or new-alt new)))) 214 " "))) 215 (setq buf 216 (compile-internal command 217 "No more differences" "Diff" 218 'diff-parse-differences)) 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))))) 230 buf)))) 231 232;;;###autoload 233(defun diff-backup (file &optional switches) 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'." 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))) 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)) 253 (diff bak ori switches))) 254 255(defun diff-latest-backup-file (fn) ; actually belongs into files.el 256 "Return the latest existing backup of FILE, or nil." 257 (let ((handler (find-file-name-handler fn 'diff-latest-backup-file))) 258 (if handler 259 (funcall handler 'diff-latest-backup-file fn) 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)))))))))))) 280 281(provide 'diff) 282 283;;; diff.el ends here 284