Commit | Line | Data |
---|---|---|
e95a67dc | 1 | ;;; diff.el --- run `diff' -*- lexical-binding: t -*- |
c0274f38 | 2 | |
ba318903 | 3 | ;; Copyright (C) 1992, 1994, 1996, 2001-2014 Free Software Foundation, |
ab422c4d | 4 | ;; Inc. |
3a801d0c | 5 | |
90324359 GM |
6 | ;; Author: Frank Bresz |
7 | ;; (according to authors.el) | |
34dc21db | 8 | ;; Maintainer: emacs-devel@gnu.org |
9766adfb | 9 | ;; Keywords: unix, vc, tools |
e5167999 | 10 | |
b10a4379 JB |
11 | ;; This file is part of GNU Emacs. |
12 | ||
eb3fa2cf | 13 | ;; GNU Emacs is free software: you can redistribute it and/or modify |
b10a4379 | 14 | ;; it under the terms of the GNU General Public License as published by |
eb3fa2cf GM |
15 | ;; the Free Software Foundation, either version 3 of the License, or |
16 | ;; (at your option) any later version. | |
b10a4379 JB |
17 | |
18 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
21 | ;; GNU General Public License for more details. | |
22 | ||
23 | ;; You should have received a copy of the GNU General Public License | |
eb3fa2cf | 24 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. |
b10a4379 | 25 | |
e41b2db1 ER |
26 | ;;; Commentary: |
27 | ||
28 | ;; This package helps you explore differences between files, using the | |
29 | ;; UNIX command diff(1). The commands are `diff' and `diff-backup'. | |
30 | ;; You can specify options with `diff-switches'. | |
31 | ||
e5167999 ER |
32 | ;;; Code: |
33 | ||
07875ee7 CY |
34 | (declare-function diff-setup-whitespace "diff-mode" ()) |
35 | ||
85b6275f RS |
36 | (defgroup diff nil |
37 | "Comparing files with `diff'." | |
38 | :group 'tools) | |
39 | ||
40 | ;;;###autoload | |
6bdad9ae | 41 | (defcustom diff-switches (purecopy "-c") |
9201cc28 | 42 | "A string or list of strings specifying switches to be passed to diff." |
85b6275f RS |
43 | :type '(choice string (repeat string)) |
44 | :group 'diff) | |
45 | ||
46 | ;;;###autoload | |
6bdad9ae | 47 | (defcustom diff-command (purecopy "diff") |
9201cc28 | 48 | "The command to use to run diff." |
660d4800 | 49 | :type 'string |
85b6275f | 50 | :group 'diff) |
d009603c | 51 | |
4c11f6a8 SM |
52 | ;; prompt if prefix arg present |
53 | (defun diff-switches () | |
54 | (if current-prefix-arg | |
55 | (read-string "Diff switches: " | |
56 | (if (stringp diff-switches) | |
57 | diff-switches | |
58 | (mapconcat 'identity diff-switches " "))))) | |
59 | ||
c879436a | 60 | (defun diff-sentinel (code &optional old-temp-file new-temp-file) |
540d0666 | 61 | "Code run when the diff process exits. |
4837b516 | 62 | CODE is the exit code of the process. It should be 0 only if no diffs |
e7c1dca8 CY |
63 | were found. |
64 | If optional args OLD-TEMP-FILE and/or NEW-TEMP-FILE are non-nil, | |
65 | delete the temporary files so named." | |
b2e44819 SM |
66 | (if old-temp-file (delete-file old-temp-file)) |
67 | (if new-temp-file (delete-file new-temp-file)) | |
07875ee7 | 68 | (diff-setup-whitespace) |
d1d2e2e8 | 69 | (goto-char (point-min)) |
540d0666 SM |
70 | (save-excursion |
71 | (goto-char (point-max)) | |
53dd481c CY |
72 | (let ((inhibit-read-only t)) |
73 | (insert (format "\nDiff finished%s. %s\n" | |
d02b0e30 CY |
74 | (cond ((equal 0 code) " (no differences)") |
75 | ((equal 2 code) " (diff error)") | |
76 | (t "")) | |
53dd481c | 77 | (current-time-string)))))) |
540d0666 | 78 | |
b10a4379 | 79 | ;;;###autoload |
0f7b0f88 | 80 | (defun diff (old new &optional switches no-async) |
b10a4379 | 81 | "Find and display the differences between OLD and NEW files. |
fc233c9d LMI |
82 | When called interactively, read NEW, then OLD, using the |
83 | minibuffer. The default for NEW is the current buffer's file | |
84 | name, and the default for OLD is a backup file for NEW, if one | |
85 | exists. If NO-ASYNC is non-nil, call diff synchronously. | |
55fdbbb5 CY |
86 | |
87 | When called interactively with a prefix argument, prompt | |
88 | interactively for diff switches. Otherwise, the switches | |
9fc9a531 | 89 | specified in the variable `diff-switches' are passed to the diff command." |
b10a4379 | 90 | (interactive |
5a973d51 | 91 | (let* ((newf (if (and buffer-file-name (file-exists-p buffer-file-name)) |
4c11f6a8 | 92 | (read-file-name |
5b76833f | 93 | (concat "Diff new file (default " |
5a973d51 SM |
94 | (file-name-nondirectory buffer-file-name) "): ") |
95 | nil buffer-file-name t) | |
4c11f6a8 | 96 | (read-file-name "Diff new file: " nil nil t))) |
5a973d51 | 97 | (oldf (file-newest-backup newf))) |
b2e44819 | 98 | (setq oldf (if (and oldf (file-exists-p oldf)) |
4c11f6a8 | 99 | (read-file-name |
5b76833f RF |
100 | (concat "Diff original file (default " |
101 | (file-name-nondirectory oldf) "): ") | |
4c11f6a8 SM |
102 | (file-name-directory oldf) oldf t) |
103 | (read-file-name "Diff original file: " | |
104 | (file-name-directory newf) nil t))) | |
105 | (list oldf newf (diff-switches)))) | |
b2e44819 SM |
106 | (display-buffer |
107 | (diff-no-select old new switches no-async))) | |
108 | ||
109 | (defun diff-file-local-copy (file-or-buf) | |
110 | (if (bufferp file-or-buf) | |
111 | (with-current-buffer file-or-buf | |
112 | (let ((tempfile (make-temp-file "buffer-content-"))) | |
113 | (write-region nil nil tempfile nil 'nomessage) | |
114 | tempfile)) | |
115 | (file-local-copy file-or-buf))) | |
116 | ||
5109429f GM |
117 | (defvar diff-use-labels 'check |
118 | "Whether `diff-command' understands the \"--label\" option. | |
119 | Possible values are: | |
120 | t -- yes, it does | |
121 | nil -- no, it does not | |
122 | check -- try to probe whether it does") | |
123 | ||
b2e44819 SM |
124 | (defun diff-no-select (old new &optional switches no-async buf) |
125 | ;; Noninteractive helper for creating and reverting diff buffers | |
f48fdaad CY |
126 | (unless (bufferp new) (setq new (expand-file-name new))) |
127 | (unless (bufferp old) (setq old (expand-file-name old))) | |
17c91d79 | 128 | (or switches (setq switches diff-switches)) ; If not specified, use default. |
b2e44819 | 129 | (unless (listp switches) (setq switches (list switches))) |
6a7662bb | 130 | (or buf (setq buf (get-buffer-create "*Diff*"))) |
5109429f GM |
131 | (when (eq 'check diff-use-labels) |
132 | (setq diff-use-labels | |
133 | (with-temp-buffer | |
134 | (when (ignore-errors (call-process diff-command nil t nil "--help")) | |
135 | (if (search-backward "--label" nil t) t))))) | |
b2e44819 SM |
136 | (let* ((old-alt (diff-file-local-copy old)) |
137 | (new-alt (diff-file-local-copy new)) | |
540d0666 SM |
138 | (command |
139 | (mapconcat 'identity | |
140 | `(,diff-command | |
141 | ;; Use explicitly specified switches | |
b2e44819 SM |
142 | ,@switches |
143 | ,@(mapcar #'shell-quote-argument | |
144 | (nconc | |
5109429f GM |
145 | (and (or old-alt new-alt) |
146 | (eq diff-use-labels t) | |
147 | (list "--label" | |
148 | (if (stringp old) old | |
149 | (prin1-to-string old)) | |
150 | "--label" | |
151 | (if (stringp new) new | |
152 | (prin1-to-string new)))) | |
b2e44819 SM |
153 | (list (or old-alt old) |
154 | (or new-alt new))))) | |
540d0666 | 155 | " ")) |
b2e44819 SM |
156 | (thisdir default-directory)) |
157 | (with-current-buffer buf | |
158 | (setq buffer-read-only t) | |
540d0666 | 159 | (buffer-disable-undo (current-buffer)) |
53dd481c CY |
160 | (let ((inhibit-read-only t)) |
161 | (erase-buffer)) | |
540d0666 SM |
162 | (buffer-enable-undo (current-buffer)) |
163 | (diff-mode) | |
164 | (set (make-local-variable 'revert-buffer-function) | |
e95a67dc SM |
165 | (lambda (_ignore-auto _noconfirm) |
166 | (diff-no-select old new switches no-async (current-buffer)))) | |
de9dc5e5 | 167 | (setq default-directory thisdir) |
53dd481c CY |
168 | (let ((inhibit-read-only t)) |
169 | (insert command "\n")) | |
540d0666 | 170 | (if (and (not no-async) (fboundp 'start-process)) |
b2e44819 SM |
171 | (let ((proc (start-process "Diff" buf shell-file-name |
172 | shell-command-switch command))) | |
53dd481c | 173 | (set-process-filter proc 'diff-process-filter) |
e95a67dc SM |
174 | (set-process-sentinel |
175 | proc (lambda (proc _msg) | |
176 | (with-current-buffer (process-buffer proc) | |
177 | (diff-sentinel (process-exit-status proc) | |
178 | old-alt new-alt))))) | |
540d0666 | 179 | ;; Async processes aren't available. |
53dd481c CY |
180 | (let ((inhibit-read-only t)) |
181 | (diff-sentinel | |
182 | (call-process shell-file-name nil buf nil | |
b2e44819 SM |
183 | shell-command-switch command) |
184 | old-alt new-alt)))) | |
540d0666 | 185 | buf)) |
aa228418 | 186 | |
53dd481c CY |
187 | (defun diff-process-filter (proc string) |
188 | (with-current-buffer (process-buffer proc) | |
189 | (let ((moving (= (point) (process-mark proc)))) | |
190 | (save-excursion | |
191 | ;; Insert the text, advancing the process marker. | |
192 | (goto-char (process-mark proc)) | |
193 | (let ((inhibit-read-only t)) | |
194 | (insert string)) | |
195 | (set-marker (process-mark proc) (point))) | |
196 | (if moving (goto-char (process-mark proc)))))) | |
197 | ||
646bd331 RM |
198 | ;;;###autoload |
199 | (defun diff-backup (file &optional switches) | |
37c51f00 RS |
200 | "Diff this file with its backup file or vice versa. |
201 | Uses the latest backup, if there are several numerical backups. | |
202 | If this file is a backup, diff it with its original. | |
4c11f6a8 SM |
203 | The backup file is the first file given to `diff'. |
204 | With prefix arg, prompt for diff switches." | |
646bd331 | 205 | (interactive (list (read-file-name "Diff (file with backup): ") |
4c11f6a8 | 206 | (diff-switches))) |
37c51f00 RS |
207 | (let (bak ori) |
208 | (if (backup-file-name-p file) | |
209 | (setq bak file | |
210 | ori (file-name-sans-versions file)) | |
211 | (setq bak (or (diff-latest-backup-file file) | |
212 | (error "No backup found for %s" file)) | |
213 | ori file)) | |
646bd331 | 214 | (diff bak ori switches))) |
37c51f00 | 215 | |
9f7c28f0 CY |
216 | ;;;###autoload |
217 | (defun diff-latest-backup-file (fn) | |
37c51f00 | 218 | "Return the latest existing backup of FILE, or nil." |
a617e913 | 219 | (let ((handler (find-file-name-handler fn 'diff-latest-backup-file))) |
34c46d87 | 220 | (if handler |
6d0d0e8d | 221 | (funcall handler 'diff-latest-backup-file fn) |
a8090e38 | 222 | (file-newest-backup fn)))) |
aa228418 | 223 | |
b2e44819 SM |
224 | ;;;###autoload |
225 | (defun diff-buffer-with-file (&optional buffer) | |
226 | "View the differences between BUFFER and its associated file. | |
227 | This requires the external program `diff' to be in your `exec-path'." | |
228 | (interactive "bBuffer: ") | |
229 | (with-current-buffer (get-buffer (or buffer (current-buffer))) | |
230 | (diff buffer-file-name (current-buffer) nil 'noasync))) | |
231 | ||
a4ef6584 ER |
232 | (provide 'diff) |
233 | ||
c0274f38 | 234 | ;;; diff.el ends here |