2009-02-19 Carsten Dominik <dominik@science.uva.nl>
[bpt/emacs.git] / lisp / diff-mode.el
CommitLineData
b532d575 1;;; diff-mode.el --- a mode for viewing/editing context diffs
610a6418 2
0d30b337 3;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
4c282a27 4;; 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
610a6418 5
cc1eecfd 6;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
b532d575 7;; Keywords: convenience patch diff
610a6418
SM
8
9;; This file is part of GNU Emacs.
10
eb3fa2cf 11;; GNU Emacs is free software: you can redistribute it and/or modify
610a6418 12;; it under the terms of the GNU General Public License as published by
eb3fa2cf
GM
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
610a6418
SM
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
eb3fa2cf 22;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
610a6418
SM
23
24;;; Commentary:
25
cdbb79c1 26;; Provides support for font-lock, outline, navigation
610a6418
SM
27;; commands, editing and various conversions as well as jumping
28;; to the corresponding source file.
29
3ccdb14e 30;; Inspired by Pavel Machek's patch-mode.el (<pavel@@atrey.karlin.mff.cuni.cz>)
b532d575 31;; Some efforts were spent to have it somewhat compatible with XEmacs'
610a6418
SM
32;; diff-mode as well as with compilation-minor-mode
33
610a6418
SM
34;; Bugs:
35
027ac3f8 36;; - Reverse doesn't work with normal diffs.
610a6418
SM
37
38;; Todo:
39
8330c175
SM
40;; - Improve `diff-add-change-log-entries-other-window',
41;; it is very simplistic now.
9201cc28 42;;
22cd1973
SM
43;; - Add a `delete-after-apply' so C-c C-a automatically deletes hunks.
44;; Also allow C-c C-a to delete already-applied hunks.
45;;
46;; - Try `diff <file> <hunk>' to try and fuzzily discover the source location
47;; of a hunk. Show then the changes between <file> and <hunk> and make it
48;; possible to apply them to <file>, <hunk-src>, or <hunk-dst>.
49;; Or maybe just make it into a ".rej to diff3-markers converter".
e78cf8e5 50;; Maybe just use `wiggle' (by Neil Brown) to do it for us.
22cd1973 51;;
e78cf8e5
SM
52;; - in diff-apply-hunk, strip context in replace-match to better
53;; preserve markers and spacing.
027ac3f8 54;; - Handle `diff -b' output in context->unified.
610a6418
SM
55
56;;; Code:
610a6418
SM
57(eval-when-compile (require 'cl))
58
6e0f362c
JB
59(defvar add-log-buffer-file-name-function)
60
610a6418
SM
61
62(defgroup diff-mode ()
aa753196 63 "Major mode for viewing/editing diffs."
ccce6558 64 :version "21.1"
610a6418
SM
65 :group 'tools
66 :group 'diff)
67
22cd1973 68(defcustom diff-default-read-only nil
769dd0f1
SM
69 "If non-nil, `diff-mode' buffers default to being read-only."
70 :type 'boolean
71 :group 'diff-mode)
72
e8a1ed31 73(defcustom diff-jump-to-old-file nil
44a07c5a 74 "Non-nil means `diff-goto-source' jumps to the old file.
610a6418 75Else, it jumps to the new file."
f5307782
JB
76 :type 'boolean
77 :group 'diff-mode)
610a6418 78
e8a1ed31 79(defcustom diff-update-on-the-fly t
9201cc28 80 "Non-nil means hunk headers are kept up-to-date on-the-fly.
610a6418
SM
81When editing a diff file, the line numbers in the hunk headers
82need to be kept consistent with the actual diff. This can
83either be done on the fly (but this sometimes interacts poorly with the
84undo mechanism) or whenever the file is written (can be slow
85when editing big diffs)."
f5307782
JB
86 :type 'boolean
87 :group 'diff-mode)
610a6418 88
00df919e 89(defcustom diff-advance-after-apply-hunk t
9201cc28 90 "Non-nil means `diff-apply-hunk' will move to the next hunk after applying."
f5307782
JB
91 :type 'boolean
92 :group 'diff-mode)
00df919e 93
22cd1973
SM
94(defcustom diff-mode-hook nil
95 "Run after setting up the `diff-mode' major mode."
96 :type 'hook
f5307782
JB
97 :options '(diff-delete-empty-files diff-make-unified)
98 :group 'diff-mode)
610a6418
SM
99
100(defvar diff-outline-regexp
101 "\\([*+][*+][*+] [^0-9]\\|@@ ...\\|\\*\\*\\* [0-9].\\|--- [0-9]..\\)")
102
cd632e57 103;;;;
610a6418 104;;;; keymap, menu, ...
cd632e57 105;;;;
610a6418 106
d87e5627 107(easy-mmode-defmap diff-mode-shared-map
027ac3f8 108 '(;; From Pavel Machek's patch-mode.
d87e5627
SM
109 ("n" . diff-hunk-next)
110 ("N" . diff-file-next)
111 ("p" . diff-hunk-prev)
112 ("P" . diff-file-prev)
25edda53
DN
113 ("\t" . diff-hunk-next)
114 ([backtab] . diff-hunk-prev)
d87e5627
SM
115 ("k" . diff-hunk-kill)
116 ("K" . diff-file-kill)
027ac3f8 117 ;; From compilation-minor-mode.
d87e5627
SM
118 ("}" . diff-file-next)
119 ("{" . diff-file-prev)
610a6418 120 ("\C-m" . diff-goto-source)
a1e2d7f7 121 ([mouse-2] . diff-goto-source)
027ac3f8 122 ;; From XEmacs' diff-mode.
fe39af99
SM
123 ;; Standard M-w is useful, so don't change M-W.
124 ;;("W" . widen)
610a6418
SM
125 ;;("." . diff-goto-source) ;display-buffer
126 ;;("f" . diff-goto-source) ;find-file
127 ("o" . diff-goto-source) ;other-window
128 ;;("w" . diff-goto-source) ;other-frame
129 ;;("N" . diff-narrow)
130 ;;("h" . diff-show-header)
131 ;;("j" . diff-show-difference) ;jump to Nth diff
132 ;;("q" . diff-quit)
e78cf8e5
SM
133 ;; Not useful if you have to metafy them.
134 ;;(" " . scroll-up)
135 ;;("\177" . scroll-down)
136 ;; Standard M-a is useful, so don't change M-A.
137 ;;("A" . diff-ediff-patch)
138 ;; Standard M-r is useful, so don't change M-r or M-R.
139 ;;("r" . diff-restrict-view)
140 ;;("R" . diff-reverse-direction)
c010ecfa 141 ("q" . quit-window))
0b82e382 142 "Basic keymap for `diff-mode', bound to various prefix keys.")
610a6418 143
d87e5627 144(easy-mmode-defmap diff-mode-map
610a6418 145 `(("\e" . ,diff-mode-shared-map)
027ac3f8
SM
146 ;; From compilation-minor-mode.
147 ("\C-c\C-c" . diff-goto-source)
8330c175
SM
148 ;; By analogy with the global C-x 4 a binding.
149 ("\C-x4A" . diff-add-change-log-entries-other-window)
027ac3f8 150 ;; Misc operations.
3cec9c57 151 ("\C-c\C-a" . diff-apply-hunk)
28408bfd
RS
152 ("\C-c\C-e" . diff-ediff-patch)
153 ("\C-c\C-n" . diff-restrict-view)
28408bfd 154 ("\C-c\C-s" . diff-split-hunk)
b711788a 155 ("\C-c\C-t" . diff-test-hunk)
2659df68 156 ("\C-c\C-r" . diff-reverse-direction)
28408bfd 157 ("\C-c\C-u" . diff-context->unified)
fe39af99
SM
158 ;; `d' because it duplicates the context :-( --Stef
159 ("\C-c\C-d" . diff-unified->context)
2659df68
SM
160 ("\C-c\C-w" . diff-ignore-whitespace-hunk)
161 ("\C-c\C-b" . diff-refine-hunk) ;No reason for `b' :-(
677c0382 162 ("\C-c\C-f" . next-error-follow-minor-mode))
610a6418
SM
163 "Keymap for `diff-mode'. See also `diff-mode-shared-map'.")
164
165(easy-menu-define diff-mode-menu diff-mode-map
166 "Menu for `diff-mode'."
167 '("Diff"
fb4dfdd2
DN
168 ["Jump to Source" diff-goto-source
169 :help "Jump to the corresponding source line"]
170 ["Apply hunk" diff-apply-hunk
171 :help "Apply the current hunk to the source file and go to the next"]
172 ["Test applying hunk" diff-test-hunk
173 :help "See whether it's possible to apply the current hunk"]
174 ["Apply diff with Ediff" diff-ediff-patch
175 :help "Call `ediff-patch-file' on the current buffer"]
8330c175 176 ["Create Change Log entries" diff-add-change-log-entries-other-window
8a72c7f8 177 :help "Create ChangeLog entries for the changes in the diff buffer"]
8f2d38de 178 "-----"
fb4dfdd2
DN
179 ["Reverse direction" diff-reverse-direction
180 :help "Reverse the direction of the diffs"]
181 ["Context -> Unified" diff-context->unified
182 :help "Convert context diffs to unified diffs"]
183 ["Unified -> Context" diff-unified->context
184 :help "Convert unified diffs to context diffs"]
610a6418 185 ;;["Fixup Headers" diff-fixup-modifs (not buffer-read-only)]
ac7020b3 186 ["Show trailing whitespace" whitespace-mode
4c6de5a5 187 :style toggle :selected (bound-and-true-p whitespace-mode)
ac7020b3 188 :help "Show trailing whitespace in modified lines"]
8f2d38de 189 "-----"
fb4dfdd2
DN
190 ["Split hunk" diff-split-hunk
191 :active (diff-splittable-p)
192 :help "Split the current (unified diff) hunk at point into two hunks"]
193 ["Ignore whitespace changes" diff-ignore-whitespace-hunk
194 :help "Re-diff the current hunk, ignoring whitespace differences"]
195 ["Highlight fine changes" diff-refine-hunk
196 :help "Highlight changes of hunk at point at a finer granularity"]
197 ["Kill current hunk" diff-hunk-kill
198 :help "Kill current hunk"]
199 ["Kill current file's hunks" diff-file-kill
200 :help "Kill all current file's hunks"]
8f2d38de 201 "-----"
fb4dfdd2
DN
202 ["Previous Hunk" diff-hunk-prev
203 :help "Go to the previous count'th hunk"]
204 ["Next Hunk" diff-hunk-next
205 :help "Go to the next count'th hunk"]
206 ["Previous File" diff-file-prev
207 :help "Go to the previous count'th file"]
208 ["Next File" diff-file-next
209 :help "Go to the next count'th file"]
610a6418
SM
210 ))
211
3cec9c57 212(defcustom diff-minor-mode-prefix "\C-c="
0b82e382 213 "Prefix key for `diff-minor-mode' commands."
f5307782
JB
214 :type '(choice (string "\e") (string "C-c=") string)
215 :group 'diff-mode)
0b82e382 216
d87e5627
SM
217(easy-mmode-defmap diff-minor-mode-map
218 `((,diff-minor-mode-prefix . ,diff-mode-shared-map))
0b82e382
SM
219 "Keymap for `diff-minor-mode'. See also `diff-mode-shared-map'.")
220
7381be9d
TTN
221(define-minor-mode diff-auto-refine-mode
222 "Automatically highlight changes in detail as the user visits hunks.
223When transitioning from disabled to enabled,
224try to refine the current hunk, as well."
4542adfb 225 :group 'diff-mode :init-value t :lighter nil ;; " Auto-Refine"
7381be9d
TTN
226 (when diff-auto-refine-mode
227 (condition-case-no-debug nil (diff-refine-hunk) (error nil))))
610a6418 228
cd632e57 229;;;;
610a6418 230;;;; font-lock support
cd632e57 231;;;;
610a6418 232
221711eb 233(defface diff-header
55f2eb7e 234 '((((class color) (min-colors 88) (background light))
9f4e4f5b 235 :background "grey80")
55f2eb7e 236 (((class color) (min-colors 88) (background dark))
4f815065 237 :background "grey45")
55f2eb7e 238 (((class color) (background light))
4f815065 239 :foreground "blue1" :weight bold)
55f2eb7e 240 (((class color) (background dark))
4f815065
SM
241 :foreground "green" :weight bold)
242 (t :weight bold))
d676819e
LK
243 "`diff-mode' face inherited by hunk and index header faces."
244 :group 'diff-mode)
221711eb
MB
245;; backward-compatibility alias
246(put 'diff-header-face 'face-alias 'diff-header)
247(defvar diff-header-face 'diff-header)
c0aa75f7 248
221711eb 249(defface diff-file-header
55f2eb7e 250 '((((class color) (min-colors 88) (background light))
4f815065 251 :background "grey70" :weight bold)
55f2eb7e 252 (((class color) (min-colors 88) (background dark))
4f815065 253 :background "grey60" :weight bold)
55f2eb7e 254 (((class color) (background light))
f3abba99 255 :foreground "green" :weight bold)
55f2eb7e 256 (((class color) (background dark))
4f815065
SM
257 :foreground "cyan" :weight bold)
258 (t :weight bold)) ; :height 1.3
d676819e
LK
259 "`diff-mode' face used to highlight file header lines."
260 :group 'diff-mode)
221711eb
MB
261;; backward-compatibility alias
262(put 'diff-file-header-face 'face-alias 'diff-file-header)
263(defvar diff-file-header-face 'diff-file-header)
610a6418 264
221711eb
MB
265(defface diff-index
266 '((t :inherit diff-file-header))
d676819e
LK
267 "`diff-mode' face used to highlight index header lines."
268 :group 'diff-mode)
221711eb
MB
269;; backward-compatibility alias
270(put 'diff-index-face 'face-alias 'diff-index)
271(defvar diff-index-face 'diff-index)
610a6418 272
221711eb
MB
273(defface diff-hunk-header
274 '((t :inherit diff-header))
d676819e
LK
275 "`diff-mode' face used to highlight hunk header lines."
276 :group 'diff-mode)
221711eb
MB
277;; backward-compatibility alias
278(put 'diff-hunk-header-face 'face-alias 'diff-hunk-header)
279(defvar diff-hunk-header-face 'diff-hunk-header)
610a6418 280
221711eb
MB
281(defface diff-removed
282 '((t :inherit diff-changed))
d676819e
LK
283 "`diff-mode' face used to highlight removed lines."
284 :group 'diff-mode)
221711eb
MB
285;; backward-compatibility alias
286(put 'diff-removed-face 'face-alias 'diff-removed)
287(defvar diff-removed-face 'diff-removed)
610a6418 288
221711eb
MB
289(defface diff-added
290 '((t :inherit diff-changed))
d676819e
LK
291 "`diff-mode' face used to highlight added lines."
292 :group 'diff-mode)
221711eb
MB
293;; backward-compatibility alias
294(put 'diff-added-face 'face-alias 'diff-added)
295(defvar diff-added-face 'diff-added)
610a6418 296
221711eb 297(defface diff-changed
86a27a3a 298 '((((type tty pc) (class color) (background light))
4f815065 299 :foreground "magenta" :weight bold :slant italic)
86a27a3a 300 (((type tty pc) (class color) (background dark))
4f815065 301 :foreground "yellow" :weight bold :slant italic))
d676819e
LK
302 "`diff-mode' face used to highlight changed lines."
303 :group 'diff-mode)
221711eb
MB
304;; backward-compatibility alias
305(put 'diff-changed-face 'face-alias 'diff-changed)
306(defvar diff-changed-face 'diff-changed)
610a6418 307
f3abba99
JL
308(defface diff-indicator-removed
309 '((t :inherit diff-removed))
310 "`diff-mode' face used to highlight indicator of removed lines (-, <)."
311 :group 'diff-mode
312 :version "22.1")
313(defvar diff-indicator-removed-face 'diff-indicator-removed)
314
315(defface diff-indicator-added
316 '((t :inherit diff-added))
317 "`diff-mode' face used to highlight indicator of added lines (+, >)."
318 :group 'diff-mode
319 :version "22.1")
320(defvar diff-indicator-added-face 'diff-indicator-added)
321
322(defface diff-indicator-changed
323 '((t :inherit diff-changed))
324 "`diff-mode' face used to highlight indicator of changed lines."
325 :group 'diff-mode
326 :version "22.1")
327(defvar diff-indicator-changed-face 'diff-indicator-changed)
328
221711eb 329(defface diff-function
ea5d66db 330 '((t :inherit diff-header))
d676819e
LK
331 "`diff-mode' face used to highlight function names produced by \"diff -p\"."
332 :group 'diff-mode)
221711eb
MB
333;; backward-compatibility alias
334(put 'diff-function-face 'face-alias 'diff-function)
335(defvar diff-function-face 'diff-function)
34460354 336
221711eb 337(defface diff-context
f3abba99 338 '((((class color grayscale) (min-colors 88)) :inherit shadow))
d676819e
LK
339 "`diff-mode' face used to highlight context and other side-information."
340 :group 'diff-mode)
221711eb
MB
341;; backward-compatibility alias
342(put 'diff-context-face 'face-alias 'diff-context)
343(defvar diff-context-face 'diff-context)
c0aa75f7 344
221711eb
MB
345(defface diff-nonexistent
346 '((t :inherit diff-file-header))
d676819e
LK
347 "`diff-mode' face used to highlight nonexistent files in recursive diffs."
348 :group 'diff-mode)
221711eb
MB
349;; backward-compatibility alias
350(put 'diff-nonexistent-face 'face-alias 'diff-nonexistent)
351(defvar diff-nonexistent-face 'diff-nonexistent)
469fc0a2 352
22cd1973
SM
353(defconst diff-yank-handler '(diff-yank-function))
354(defun diff-yank-function (text)
1ec7bd14
SM
355 ;; FIXME: the yank-handler is now called separately on each piece of text
356 ;; with a yank-handler property, so the next-single-property-change call
357 ;; below will always return nil :-( --stef
22cd1973
SM
358 (let ((mixed (next-single-property-change 0 'yank-handler text))
359 (start (point)))
360 ;; First insert the text.
361 (insert text)
362 ;; If the text does not include any diff markers and if we're not
363 ;; yanking back into a diff-mode buffer, get rid of the prefixes.
364 (unless (or mixed (derived-mode-p 'diff-mode))
365 (undo-boundary) ; Just in case the user wanted the prefixes.
366 (let ((re (save-excursion
367 (if (re-search-backward "^[><!][ \t]" start t)
368 (if (eq (char-after) ?!)
369 "^[!+- ][ \t]" "^[<>][ \t]")
370 "^[ <>!+-]"))))
371 (save-excursion
372 (while (re-search-backward re start t)
373 (replace-match "" t t)))))))
cfc80227 374
f52d2f9c 375(defconst diff-hunk-header-re-unified
43392d12 376 "^@@ -\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? \\+\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? @@")
bf6970a5
SM
377(defconst diff-context-mid-hunk-header-re
378 "--- \\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? ----$")
22cd1973 379
610a6418 380(defvar diff-font-lock-keywords
f52d2f9c
SM
381 `((,(concat "\\(" diff-hunk-header-re-unified "\\)\\(.*\\)$")
382 (1 diff-hunk-header-face) (6 diff-function-face))
f3abba99
JL
383 ("^\\(\\*\\{15\\}\\)\\(.*\\)$" ;context
384 (1 diff-hunk-header-face) (2 diff-function-face))
370d860c 385 ("^\\*\\*\\* .+ \\*\\*\\*\\*". diff-hunk-header-face) ;context
bf6970a5 386 (,diff-context-mid-hunk-header-re . diff-hunk-header-face) ;context
f3abba99
JL
387 ("^[0-9,]+[acd][0-9,]+$" . diff-hunk-header-face) ;normal
388 ("^---$" . diff-hunk-header-face) ;normal
4c969f97
SM
389 ;; For file headers, accept files with spaces, but be careful to rule
390 ;; out false-positives when matching hunk headers.
391 ("^\\(---\\|\\+\\+\\+\\|\\*\\*\\*\\) \\([^\t\n]+?\\)\\(?:\t.*\\| \\(\\*\\*\\*\\*\\|----\\)\\)?\n"
392 (0 diff-header-face)
393 (2 (if (not (match-end 3)) diff-file-header-face) prepend))
f3abba99
JL
394 ("^\\([-<]\\)\\(.*\n\\)"
395 (1 diff-indicator-removed-face) (2 diff-removed-face))
396 ("^\\([+>]\\)\\(.*\n\\)"
397 (1 diff-indicator-added-face) (2 diff-added-face))
398 ("^\\(!\\)\\(.*\n\\)"
399 (1 diff-indicator-changed-face) (2 diff-changed-face))
400 ("^Index: \\(.+\\).*\n"
401 (0 diff-header-face) (1 diff-index-face prepend))
7dfb000f 402 ("^Only in .*\n" . diff-nonexistent-face)
f3abba99 403 ("^\\(#\\)\\(.*\\)"
050dcc13
JL
404 (1 font-lock-comment-delimiter-face)
405 (2 font-lock-comment-face))
f3abba99 406 ("^[^-=+*!<>#].*\n" (0 diff-context-face))))
610a6418
SM
407
408(defconst diff-font-lock-defaults
0e104800 409 '(diff-font-lock-keywords t nil nil nil (font-lock-multiline . nil)))
610a6418 410
ccce6558
DL
411(defvar diff-imenu-generic-expression
412 ;; Prefer second name as first is most likely to be a backup or
824693e7
DL
413 ;; version-control name. The [\t\n] at the end of the unidiff pattern
414 ;; catches Debian source diff files (which lack the trailing date).
415 '((nil "\\+\\+\\+\\ \\([^\t\n]+\\)[\t\n]" 1) ; unidiffs
ccce6558
DL
416 (nil "^--- \\([^\t\n]+\\)\t.*\n\\*" 1))) ; context diffs
417
cd632e57 418;;;;
610a6418 419;;;; Movement
cd632e57 420;;;;
610a6418 421
f52d2f9c
SM
422(defvar diff-valid-unified-empty-line t
423 "If non-nil, empty lines are valid in unified diffs.
424Some versions of diff replace all-blank context lines in unified format with
425empty lines. This makes the format less robust, but is tolerated.
426See http://lists.gnu.org/archive/html/emacs-devel/2007-11/msg01990.html")
427
428(defconst diff-hunk-header-re
429 (concat "^\\(?:" diff-hunk-header-re-unified ".*\\|\\*\\{15\\}.*\n\\*\\*\\* .+ \\*\\*\\*\\*\\|[0-9]+\\(,[0-9]+\\)?[acd][0-9]+\\(,[0-9]+\\)?\\)$"))
365bdf63 430(defconst diff-file-header-re (concat "^\\(--- .+\n\\+\\+\\+ \\|\\*\\*\\* .+\n--- \\|[^-+!<>0-9@* \n]\\).+\n" (substring diff-hunk-header-re 1)))
610a6418
SM
431(defvar diff-narrowed-to nil)
432
be36f934 433(defun diff-hunk-style (&optional style)
996884b2 434 (when (looking-at diff-hunk-header-re)
be36f934 435 (setq style (cdr (assq (char-after) '((?@ . unified) (?* . context)))))
996884b2 436 (goto-char (match-end 0)))
be36f934
SM
437 style)
438
f52d2f9c
SM
439(defun diff-end-of-hunk (&optional style donttrustheader)
440 (let (end)
441 (when (looking-at diff-hunk-header-re)
442 ;; Especially important for unified (because headers are ambiguous).
443 (setq style (diff-hunk-style style))
444 (goto-char (match-end 0))
445 (when (and (not donttrustheader) (match-end 2))
43392d12
SM
446 (let* ((nold (string-to-number (or (match-string 2) "1")))
447 (nnew (string-to-number (or (match-string 4) "1")))
aec1ef07 448 (endold
f52d2f9c
SM
449 (save-excursion
450 (re-search-forward (if diff-valid-unified-empty-line
451 "^[- \n]" "^[- ]")
aec1ef07
SM
452 nil t nold)
453 (line-beginning-position 2)))
454 (endnew
455 ;; The hunk may end with a bunch of "+" lines, so the `end' is
456 ;; then further than computed above.
457 (save-excursion
458 (re-search-forward (if diff-valid-unified-empty-line
459 "^[+ \n]" "^[+ ]")
460 nil t nnew)
461 (line-beginning-position 2))))
462 (setq end (max endold endnew)))))
f52d2f9c
SM
463 ;; We may have a first evaluation of `end' thanks to the hunk header.
464 (unless end
465 (setq end (and (re-search-forward
466 (case style
467 (unified (concat (if diff-valid-unified-empty-line
468 "^[^-+# \\\n]\\|" "^[^-+# \\]\\|")
469 ;; A `unified' header is ambiguous.
470 diff-file-header-re))
471 (context "^[^-+#! \\]")
472 (normal "^[^<>#\\]")
473 (t "^[^-+#!<> \\]"))
474 nil t)
475 (match-beginning 0)))
476 (when diff-valid-unified-empty-line
477 ;; While empty lines may be valid inside hunks, they are also likely
478 ;; to be unrelated to the hunk.
479 (goto-char (or end (point-max)))
480 (while (eq ?\n (char-before (1- (point))))
481 (forward-char -1)
482 (setq end (point)))))
0e104800
SM
483 ;; The return value is used by easy-mmode-define-navigation.
484 (goto-char (or end (point-max)))))
610a6418 485
fd691799
SM
486(defun diff-beginning-of-hunk (&optional try-harder)
487 "Move back to beginning of hunk.
488If TRY-HARDER is non-nil, try to cater to the case where we're not in a hunk
489but in the file header instead, in which case move forward to the first hunk."
610a6418
SM
490 (beginning-of-line)
491 (unless (looking-at diff-hunk-header-re)
492 (forward-line 1)
493 (condition-case ()
494 (re-search-backward diff-hunk-header-re)
fd691799
SM
495 (error
496 (if (not try-harder)
497 (error "Can't find the beginning of the hunk")
498 (diff-beginning-of-file-and-junk)
499 (diff-hunk-next))))))
610a6418 500
f151b310
SM
501(defun diff-unified-hunk-p ()
502 (save-excursion
503 (ignore-errors
504 (diff-beginning-of-hunk)
505 (looking-at "^@@"))))
506
610a6418
SM
507(defun diff-beginning-of-file ()
508 (beginning-of-line)
509 (unless (looking-at diff-file-header-re)
4c969f97
SM
510 (let ((start (point))
511 res)
512 ;; diff-file-header-re may need to match up to 4 lines, so in case
513 ;; we're inside the header, we need to move up to 3 lines forward.
514 (forward-line 3)
515 (if (and (setq res (re-search-backward diff-file-header-re nil t))
516 ;; Maybe the 3 lines forward were too much and we matched
517 ;; a file header after our starting point :-(
518 (or (<= (point) start)
519 (setq res (re-search-backward diff-file-header-re nil t))))
520 res
521 (goto-char start)
522 (error "Can't find the beginning of the file")))))
9201cc28 523
610a6418
SM
524
525(defun diff-end-of-file ()
0e104800 526 (re-search-forward "^[-+#!<>0-9@* \\]" nil t)
22cd1973
SM
527 (re-search-forward (concat "^[^-+#!<>0-9@* \\]\\|" diff-file-header-re)
528 nil 'move)
529 (if (match-beginning 1)
530 (goto-char (match-beginning 1))
531 (beginning-of-line)))
610a6418 532
d87e5627
SM
533;; Define diff-{hunk,file}-{prev,next}
534(easy-mmode-define-navigation
2659df68 535 diff-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view
7381be9d 536 (if diff-auto-refine-mode
2659df68
SM
537 (condition-case-no-debug nil (diff-refine-hunk) (error nil))))
538
d87e5627
SM
539(easy-mmode-define-navigation
540 diff-file diff-file-header-re "file" diff-end-of-hunk)
610a6418
SM
541
542(defun diff-restrict-view (&optional arg)
543 "Restrict the view to the current hunk.
544If the prefix ARG is given, restrict the view to the current file instead."
545 (interactive "P")
546 (save-excursion
fd691799 547 (if arg (diff-beginning-of-file) (diff-beginning-of-hunk 'try-harder))
610a6418
SM
548 (narrow-to-region (point)
549 (progn (if arg (diff-end-of-file) (diff-end-of-hunk))
550 (point)))
551 (set (make-local-variable 'diff-narrowed-to) (if arg 'file 'hunk))))
552
553
d87e5627 554(defun diff-hunk-kill ()
610a6418
SM
555 "Kill current hunk."
556 (interactive)
557 (diff-beginning-of-hunk)
1eabc5e6 558 (let* ((start (point))
22cd1973
SM
559 (nexthunk (when (re-search-forward diff-hunk-header-re nil t)
560 (match-beginning 0)))
1eabc5e6
SM
561 (firsthunk (ignore-errors
562 (goto-char start)
563 (diff-beginning-of-file) (diff-hunk-next) (point)))
dd24cb37
CY
564 (nextfile (ignore-errors (diff-file-next) (point)))
565 (inhibit-read-only t))
1eabc5e6 566 (goto-char start)
610a6418
SM
567 (if (and firsthunk (= firsthunk start)
568 (or (null nexthunk)
569 (and nextfile (> nexthunk nextfile))))
1eabc5e6 570 ;; It's the only hunk for this file, so kill the file.
d87e5627 571 (diff-file-kill)
610a6418
SM
572 (diff-end-of-hunk)
573 (kill-region start (point)))))
574
8d73b84e
DN
575;; "index ", "old mode", "new mode", "new file mode" and
576;; "deleted file mode" are output by git-diff.
9201cc28 577(defconst diff-file-junk-re
8d73b84e 578 "diff \\|index \\|\\(?:deleted file\\|new\\(?: file\\)?\\|old\\) mode")
f52d2f9c 579
fd691799
SM
580(defun diff-beginning-of-file-and-junk ()
581 "Go to the beginning of file-related diff-info.
582This is like `diff-beginning-of-file' except it tries to skip back over leading
583data such as \"Index: ...\" and such."
f52d2f9c
SM
584 (let* ((orig (point))
585 ;; Skip forward over what might be "leading junk" so as to get
586 ;; closer to the actual diff.
587 (_ (progn (beginning-of-line)
588 (while (looking-at diff-file-junk-re)
589 (forward-line 1))))
590 (start (point))
4c969f97
SM
591 (prevfile (condition-case err
592 (save-excursion (diff-beginning-of-file) (point))
593 (error err)))
594 (err (if (consp prevfile) prevfile))
595 (nextfile (ignore-errors
596 (save-excursion
597 (goto-char start) (diff-file-next) (point))))
598 ;; prevhunk is one of the limits.
599 (prevhunk (save-excursion
600 (ignore-errors
601 (if (numberp prevfile) (goto-char prevfile))
602 (diff-hunk-prev) (point))))
603 (previndex (save-excursion
f52d2f9c 604 (forward-line 1) ;In case we're looking at "Index:".
4c969f97
SM
605 (re-search-backward "^Index: " prevhunk t))))
606 ;; If we're in the junk, we should use nextfile instead of prevfile.
607 (if (and (numberp nextfile)
608 (or (not (numberp prevfile))
609 (and previndex (> previndex prevfile))))
610 (setq prevfile nextfile))
611 (if (and previndex (numberp prevfile) (< previndex prevfile))
612 (setq prevfile previndex))
613 (if (and (numberp prevfile) (<= prevfile start))
f52d2f9c
SM
614 (progn
615 (goto-char prevfile)
616 ;; Now skip backward over the leading junk we may have before the
617 ;; diff itself.
618 (while (save-excursion
619 (and (zerop (forward-line -1))
620 (looking-at diff-file-junk-re)))
621 (forward-line -1)))
4c969f97
SM
622 ;; File starts *after* the starting point: we really weren't in
623 ;; a file diff but elsewhere.
f52d2f9c 624 (goto-char orig)
4c969f97 625 (signal (car err) (cdr err)))))
9201cc28 626
d87e5627 627(defun diff-file-kill ()
610a6418
SM
628 "Kill current file's hunks."
629 (interactive)
f52d2f9c
SM
630 (let ((orig (point))
631 (start (progn (diff-beginning-of-file-and-junk) (point)))
dd24cb37 632 (inhibit-read-only t))
610a6418 633 (diff-end-of-file)
22cd1973 634 (if (looking-at "^\n") (forward-char 1)) ;`tla' generates such diffs.
f52d2f9c 635 (if (> orig (point)) (error "Not inside a file diff"))
610a6418
SM
636 (kill-region start (point))))
637
fef8c55b
SM
638(defun diff-kill-junk ()
639 "Kill spurious empty diffs."
640 (interactive)
641 (save-excursion
642 (let ((inhibit-read-only t))
643 (goto-char (point-min))
644 (while (re-search-forward (concat "^\\(Index: .*\n\\)"
645 "\\([^-+!* <>].*\n\\)*?"
646 "\\(\\(Index:\\) \\|"
647 diff-file-header-re "\\)")
648 nil t)
649 (delete-region (if (match-end 4) (match-beginning 0) (match-end 1))
650 (match-beginning 3))
651 (beginning-of-line)))))
652
cd632e57
SM
653(defun diff-count-matches (re start end)
654 (save-excursion
655 (let ((n 0))
656 (goto-char start)
657 (while (re-search-forward re end t) (incf n))
658 n)))
659
f151b310
SM
660(defun diff-splittable-p ()
661 (save-excursion
662 (beginning-of-line)
663 (and (looking-at "^[-+ ]")
664 (progn (forward-line -1) (looking-at "^[-+ ]"))
665 (diff-unified-hunk-p))))
666
cd632e57
SM
667(defun diff-split-hunk ()
668 "Split the current (unified diff) hunk at point into two hunks."
669 (interactive)
670 (beginning-of-line)
671 (let ((pos (point))
672 (start (progn (diff-beginning-of-hunk) (point))))
f52d2f9c 673 (unless (looking-at diff-hunk-header-re-unified)
cd632e57
SM
674 (error "diff-split-hunk only works on unified context diffs"))
675 (forward-line 1)
676 (let* ((start1 (string-to-number (match-string 1)))
f52d2f9c 677 (start2 (string-to-number (match-string 3)))
cd632e57 678 (newstart1 (+ start1 (diff-count-matches "^[- \t]" (point) pos)))
dd24cb37
CY
679 (newstart2 (+ start2 (diff-count-matches "^[+ \t]" (point) pos)))
680 (inhibit-read-only t))
cd632e57
SM
681 (goto-char pos)
682 ;; Hopefully the after-change-function will not screw us over.
683 (insert "@@ -" (number-to-string newstart1) ",1 +"
684 (number-to-string newstart2) ",1 @@\n")
685 ;; Fix the original hunk-header.
686 (diff-fixup-modifs start pos))))
71296446 687
cd632e57 688
610a6418
SM
689;;;;
690;;;; jump to other buffers
691;;;;
692
0b82e382 693(defvar diff-remembered-files-alist nil)
adf4cc7e 694(defvar diff-remembered-defdir nil)
0b82e382 695
610a6418
SM
696(defun diff-filename-drop-dir (file)
697 (when (string-match "/" file) (substring file (match-end 0))))
698
0b82e382
SM
699(defun diff-merge-strings (ancestor from to)
700 "Merge the diff between ANCESTOR and FROM into TO.
701Returns the merged string if successful or nil otherwise.
d87e5627 702The strings are assumed not to contain any \"\\n\" (i.e. end of line).
0b82e382
SM
703If ANCESTOR = FROM, returns TO.
704If ANCESTOR = TO, returns FROM.
705The heuristic is simplistic and only really works for cases
706like \(diff-merge-strings \"b/foo\" \"b/bar\" \"/a/c/foo\")."
707 ;; Ideally, we want:
708 ;; AMB ANB CMD -> CND
709 ;; but that's ambiguous if `foo' or `bar' is empty:
710 ;; a/foo a/foo1 b/foo.c -> b/foo1.c but not 1b/foo.c or b/foo.c1
d87e5627 711 (let ((str (concat ancestor "\n" from "\n" to)))
0b82e382 712 (when (and (string-match (concat
d87e5627
SM
713 "\\`\\(.*?\\)\\(.*\\)\\(.*\\)\n"
714 "\\1\\(.*\\)\\3\n"
0b82e382
SM
715 "\\(.*\\(\\2\\).*\\)\\'") str)
716 (equal to (match-string 5 str)))
717 (concat (substring str (match-beginning 5) (match-beginning 6))
718 (match-string 4 str)
719 (substring str (match-end 6) (match-end 5))))))
720
22cd1973
SM
721(defun diff-tell-file-name (old name)
722 "Tell Emacs where the find the source file of the current hunk.
723If the OLD prefix arg is passed, tell the file NAME of the old file."
724 (interactive
725 (let* ((old current-prefix-arg)
726 (fs (diff-hunk-file-names current-prefix-arg)))
727 (unless fs (error "No file name to look for"))
728 (list old (read-file-name (format "File for %s: " (car fs))
68035b97 729 nil (diff-find-file-name old 'noprompt) t))))
22cd1973
SM
730 (let ((fs (diff-hunk-file-names old)))
731 (unless fs (error "No file name to look for"))
732 (push (cons fs name) diff-remembered-files-alist)))
cfc80227 733
22cd1973
SM
734(defun diff-hunk-file-names (&optional old)
735 "Give the list of file names textually mentioned for the current hunk."
610a6418
SM
736 (save-excursion
737 (unless (looking-at diff-file-header-re)
738 (or (ignore-errors (diff-beginning-of-file))
739 (re-search-forward diff-file-header-re nil t)))
22cd1973 740 (let ((limit (save-excursion
610a6418 741 (condition-case ()
d87e5627 742 (progn (diff-hunk-prev) (point))
610a6418 743 (error (point-min)))))
22cd1973
SM
744 (header-files
745 (if (looking-at "[-*][-*][-*] \\(\\S-+\\)\\(\\s-.*\\)?\n[-+][-+][-+] \\(\\S-+\\)")
746 (list (if old (match-string 1) (match-string 3))
747 (if old (match-string 3) (match-string 1)))
748 (forward-line 1) nil)))
749 (delq nil
750 (append
751 (when (and (not old)
752 (save-excursion
753 (re-search-backward "^Index: \\(.+\\)" limit t)))
754 (list (match-string 1)))
755 header-files
756 (when (re-search-backward
757 "^diff \\(-\\S-+ +\\)*\\(\\S-+\\)\\( +\\(\\S-+\\)\\)?"
758 nil t)
759 (list (if old (match-string 2) (match-string 4))
760 (if old (match-string 4) (match-string 2)))))))))
761
68035b97 762(defun diff-find-file-name (&optional old noprompt prefix)
22cd1973 763 "Return the file corresponding to the current patch.
e78cf8e5 764Non-nil OLD means that we want the old file.
68035b97 765Non-nil NOPROMPT means to prefer returning nil than to prompt the user.
e78cf8e5 766PREFIX is only used internally: don't use it."
adf4cc7e
SM
767 (unless (equal diff-remembered-defdir default-directory)
768 ;; Flush diff-remembered-files-alist if the default-directory is changed.
769 (set (make-local-variable 'diff-remembered-defdir) default-directory)
770 (set (make-local-variable 'diff-remembered-files-alist) nil))
22cd1973
SM
771 (save-excursion
772 (unless (looking-at diff-file-header-re)
773 (or (ignore-errors (diff-beginning-of-file))
774 (re-search-forward diff-file-header-re nil t)))
775 (let ((fs (diff-hunk-file-names old)))
e78cf8e5 776 (if prefix (setq fs (mapcar (lambda (f) (concat prefix f)) fs)))
610a6418 777 (or
0b82e382
SM
778 ;; use any previously used preference
779 (cdr (assoc fs diff-remembered-files-alist))
780 ;; try to be clever and use previous choices as an inspiration
781 (dolist (rf diff-remembered-files-alist)
782 (let ((newfile (diff-merge-strings (caar rf) (car fs) (cdr rf))))
783 (if (and newfile (file-exists-p newfile)) (return newfile))))
784 ;; look for each file in turn. If none found, try again but
785 ;; ignoring the first level of directory, ...
786 (do* ((files fs (delq nil (mapcar 'diff-filename-drop-dir files)))
787 (file nil nil))
788 ((or (null files)
789 (setq file (do* ((files files (cdr files))
790 (file (car files) (car files)))
f9274544
GM
791 ;; Use file-regular-p to avoid
792 ;; /dev/null, directories, etc.
793 ((or (null file) (file-regular-p file))
0b82e382
SM
794 file))))
795 file))
796 ;; <foo>.rej patches implicitly apply to <foo>
610a6418
SM
797 (and (string-match "\\.rej\\'" (or buffer-file-name ""))
798 (let ((file (substring buffer-file-name 0 (match-beginning 0))))
799 (when (file-exists-p file) file)))
e78cf8e5
SM
800 ;; If we haven't found the file, maybe it's because we haven't paid
801 ;; attention to the PCL-CVS hint.
802 (and (not prefix)
803 (boundp 'cvs-pcl-cvs-dirchange-re)
804 (save-excursion
805 (re-search-backward cvs-pcl-cvs-dirchange-re nil t))
68035b97 806 (diff-find-file-name old noprompt (match-string 1)))
0b82e382 807 ;; if all else fails, ask the user
68035b97
SM
808 (unless noprompt
809 (let ((file (read-file-name (format "Use file %s: "
810 (or (first fs) ""))
811 nil (first fs) t (first fs))))
812 (set (make-local-variable 'diff-remembered-files-alist)
813 (cons (cons fs file) diff-remembered-files-alist))
814 file))))))
610a6418 815
1b1b5dae 816
610a6418
SM
817(defun diff-ediff-patch ()
818 "Call `ediff-patch-file' on the current buffer."
819 (interactive)
820 (condition-case err
0e104800 821 (ediff-patch-file nil (current-buffer))
610a6418
SM
822 (wrong-number-of-arguments (ediff-patch-file))))
823
71296446 824;;;;
610a6418 825;;;; Conversion functions
71296446 826;;;;
610a6418
SM
827
828;;(defvar diff-inhibit-after-change nil
829;; "Non-nil means inhibit `diff-mode's after-change functions.")
830
831(defun diff-unified->context (start end)
832 "Convert unified diffs to context diffs.
833START and END are either taken from the region (if a prefix arg is given) or
dfbd373d 834else cover the whole buffer."
a70b0ff8
JL
835 (interactive (if (or current-prefix-arg (and transient-mark-mode mark-active))
836 (list (region-beginning) (region-end))
610a6418 837 (list (point-min) (point-max))))
e78cf8e5 838 (unless (markerp end) (setq end (copy-marker end t)))
610a6418
SM
839 (let (;;(diff-inhibit-after-change t)
840 (inhibit-read-only t))
841 (save-excursion
842 (goto-char start)
f52d2f9c
SM
843 (while (and (re-search-forward
844 (concat "^\\(\\(---\\) .+\n\\(\\+\\+\\+\\) .+\\|"
845 diff-hunk-header-re-unified ".*\\)$")
846 nil t)
610a6418
SM
847 (< (point) end))
848 (combine-after-change-calls
849 (if (match-beginning 2)
0e104800 850 ;; we matched a file header
610a6418
SM
851 (progn
852 ;; use reverse order to make sure the indices are kept valid
853 (replace-match "---" t t nil 3)
854 (replace-match "***" t t nil 2))
855 ;; we matched a hunk header
856 (let ((line1 (match-string 4))
43392d12 857 (lines1 (or (match-string 5) "1"))
610a6418 858 (line2 (match-string 6))
341dd15a 859 (lines2 (or (match-string 7) "1"))
3dc04e83
SM
860 ;; Variables to use the special undo function.
861 (old-undo buffer-undo-list)
862 (old-end (marker-position end))
863 (start (match-beginning 0))
864 (reversible t))
610a6418
SM
865 (replace-match
866 (concat "***************\n*** " line1 ","
867 (number-to-string (+ (string-to-number line1)
868 (string-to-number lines1)
341dd15a
MB
869 -1))
870 " ****"))
610a6418 871 (save-restriction
f52d2f9c
SM
872 (narrow-to-region (line-beginning-position 2)
873 ;; Call diff-end-of-hunk from just before
874 ;; the hunk header so it can use the hunk
875 ;; header info.
610a6418
SM
876 (progn (diff-end-of-hunk 'unified) (point)))
877 (let ((hunk (buffer-string)))
878 (goto-char (point-min))
879 (if (not (save-excursion (re-search-forward "^-" nil t)))
880 (delete-region (point) (point-max))
881 (goto-char (point-max))
882 (let ((modif nil) last-pt)
883 (while (progn (setq last-pt (point))
884 (= (forward-line -1) 0))
885 (case (char-after)
bdad2e31 886 (?\s (insert " ") (setq modif nil) (backward-char 1))
610a6418
SM
887 (?+ (delete-region (point) last-pt) (setq modif t))
888 (?- (if (not modif)
889 (progn (forward-char 1)
890 (insert " "))
891 (delete-char 1)
892 (insert "! "))
893 (backward-char 2))
894 (?\\ (when (save-excursion (forward-line -1)
895 (= (char-after) ?+))
896 (delete-region (point) last-pt) (setq modif t)))
f52d2f9c
SM
897 ;; diff-valid-unified-empty-line.
898 (?\n (insert " ") (setq modif nil) (backward-char 2))
610a6418
SM
899 (t (setq modif nil))))))
900 (goto-char (point-max))
901 (save-excursion
902 (insert "--- " line2 ","
903 (number-to-string (+ (string-to-number line2)
904 (string-to-number lines2)
341dd15a 905 -1))
f52d2f9c 906 " ----\n" hunk))
610a6418
SM
907 ;;(goto-char (point-min))
908 (forward-line 1)
909 (if (not (save-excursion (re-search-forward "^+" nil t)))
910 (delete-region (point) (point-max))
911 (let ((modif nil) (delete nil))
3dc04e83
SM
912 (if (save-excursion (re-search-forward "^\\+.*\n-" nil t))
913 ;; Normally, lines in a substitution come with
914 ;; first the removals and then the additions, and
915 ;; the context->unified function follows this
916 ;; convention, of course. Yet, other alternatives
917 ;; are valid as well, but they preclude the use of
918 ;; context->unified as an undo command.
919 (setq reversible nil))
610a6418
SM
920 (while (not (eobp))
921 (case (char-after)
bdad2e31 922 (?\s (insert " ") (setq modif nil) (backward-char 1))
610a6418
SM
923 (?- (setq delete t) (setq modif t))
924 (?+ (if (not modif)
925 (progn (forward-char 1)
926 (insert " "))
927 (delete-char 1)
928 (insert "! "))
929 (backward-char 2))
930 (?\\ (when (save-excursion (forward-line 1)
931 (not (eobp)))
932 (setq delete t) (setq modif t)))
f52d2f9c
SM
933 ;; diff-valid-unified-empty-line.
934 (?\n (insert " ") (setq modif nil) (backward-char 2)
935 (setq reversible nil))
610a6418
SM
936 (t (setq modif nil)))
937 (let ((last-pt (point)))
938 (forward-line 1)
939 (when delete
940 (delete-region last-pt (point))
3dc04e83
SM
941 (setq delete nil)))))))
942 (unless (or (not reversible) (eq buffer-undo-list t))
943 ;; Drop the many undo entries and replace them with
944 ;; a single entry that uses diff-context->unified to do
945 ;; the work.
946 (setq buffer-undo-list
947 (cons (list 'apply (- old-end end) start (point-max)
948 'diff-context->unified start (point-max))
949 old-undo)))))))))))
610a6418 950
28408bfd 951(defun diff-context->unified (start end &optional to-context)
610a6418 952 "Convert context diffs to unified diffs.
28408bfd
RS
953START and END are either taken from the region
954\(when it is highlighted) or else cover the whole buffer.
955With a prefix argument, convert unified format to context format."
956 (interactive (if (and transient-mark-mode mark-active)
a70b0ff8 957 (list (region-beginning) (region-end) current-prefix-arg)
28408bfd
RS
958 (list (point-min) (point-max) current-prefix-arg)))
959 (if to-context
960 (diff-unified->context start end)
e78cf8e5 961 (unless (markerp end) (setq end (copy-marker end t)))
28408bfd 962 (let ( ;;(diff-inhibit-after-change t)
be36f934 963 (inhibit-read-only t))
28408bfd 964 (save-excursion
be36f934
SM
965 (goto-char start)
966 (while (and (re-search-forward "^\\(\\(\\*\\*\\*\\) .+\n\\(---\\) .+\\|\\*\\{15\\}.*\n\\*\\*\\* \\([0-9]+\\),\\(-?[0-9]+\\) \\*\\*\\*\\*\\)$" nil t)
967 (< (point) end))
968 (combine-after-change-calls
969 (if (match-beginning 2)
970 ;; we matched a file header
971 (progn
972 ;; use reverse order to make sure the indices are kept valid
973 (replace-match "+++" t t nil 3)
974 (replace-match "---" t t nil 2))
975 ;; we matched a hunk header
976 (let ((line1s (match-string 4))
977 (line1e (match-string 5))
978 (pt1 (match-beginning 0))
979 ;; Variables to use the special undo function.
980 (old-undo buffer-undo-list)
981 (old-end (marker-position end))
982 (reversible t))
983 (replace-match "")
984 (unless (re-search-forward
bf6970a5 985 diff-context-mid-hunk-header-re nil t)
be36f934
SM
986 (error "Can't find matching `--- n1,n2 ----' line"))
987 (let ((line2s (match-string 1))
988 (line2e (match-string 2))
989 (pt2 (progn
990 (delete-region (progn (beginning-of-line) (point))
991 (progn (forward-line 1) (point)))
992 (point-marker))))
993 (goto-char pt1)
994 (forward-line 1)
995 (while (< (point) pt2)
996 (case (char-after)
997 (?! (delete-char 2) (insert "-") (forward-line 1))
998 (?- (forward-char 1) (delete-char 1) (forward-line 1))
999 (?\s ;merge with the other half of the chunk
1000 (let* ((endline2
1001 (save-excursion
1002 (goto-char pt2) (forward-line 1) (point))))
1003 (case (char-after pt2)
1004 ((?! ?+)
1005 (insert "+"
1006 (prog1 (buffer-substring (+ pt2 2) endline2)
1007 (delete-region pt2 endline2))))
1008 (?\s
1009 (unless (= (- endline2 pt2)
1010 (- (line-beginning-position 2) (point)))
1011 ;; If the two lines we're merging don't have the
1012 ;; same length (can happen with "diff -b"), then
1013 ;; diff-unified->context will not properly undo
1014 ;; this operation.
1015 (setq reversible nil))
1016 (delete-region pt2 endline2)
1017 (delete-char 1)
1018 (forward-line 1))
1019 (?\\ (forward-line 1))
1020 (t (setq reversible nil)
1021 (delete-char 1) (forward-line 1)))))
1022 (t (setq reversible nil) (forward-line 1))))
1023 (while (looking-at "[+! ] ")
1024 (if (/= (char-after) ?!) (forward-char 1)
1025 (delete-char 1) (insert "+"))
1026 (delete-char 1) (forward-line 1))
1027 (save-excursion
1028 (goto-char pt1)
1029 (insert "@@ -" line1s ","
1030 (number-to-string (- (string-to-number line1e)
1031 (string-to-number line1s)
1032 -1))
1033 " +" line2s ","
1034 (number-to-string (- (string-to-number line2e)
1035 (string-to-number line2s)
1036 -1)) " @@"))
1037 (set-marker pt2 nil)
1038 ;; The whole procedure succeeded, let's replace the myriad
1039 ;; of undo elements with just a single special one.
1040 (unless (or (not reversible) (eq buffer-undo-list t))
1041 (setq buffer-undo-list
1042 (cons (list 'apply (- old-end end) pt1 (point)
1043 'diff-unified->context pt1 (point))
1044 old-undo)))
1045 )))))))))
610a6418
SM
1046
1047(defun diff-reverse-direction (start end)
1048 "Reverse the direction of the diffs.
1049START and END are either taken from the region (if a prefix arg is given) or
dfbd373d 1050else cover the whole buffer."
a70b0ff8
JL
1051 (interactive (if (or current-prefix-arg (and transient-mark-mode mark-active))
1052 (list (region-beginning) (region-end))
610a6418 1053 (list (point-min) (point-max))))
e78cf8e5 1054 (unless (markerp end) (setq end (copy-marker end t)))
610a6418
SM
1055 (let (;;(diff-inhibit-after-change t)
1056 (inhibit-read-only t))
1057 (save-excursion
1058 (goto-char start)
c0078a04 1059 (while (and (re-search-forward "^\\(\\([-*][-*][-*] \\)\\(.+\\)\n\\([-+][-+][-+] \\)\\(.+\\)\\|\\*\\{15\\}.*\n\\*\\*\\* \\(.+\\) \\*\\*\\*\\*\\|@@ -\\([0-9,]+\\) \\+\\([0-9,]+\\) @@.*\\)$" nil t)
610a6418
SM
1060 (< (point) end))
1061 (combine-after-change-calls
1062 (cond
1063 ;; a file header
1064 ((match-beginning 2) (replace-match "\\2\\5\n\\4\\3" nil))
1065 ;; a context-diff hunk header
1066 ((match-beginning 6)
1067 (let ((pt-lines1 (match-beginning 6))
1068 (lines1 (match-string 6)))
1069 (replace-match "" nil nil nil 6)
1070 (forward-line 1)
1071 (let ((half1s (point)))
0e104800 1072 (while (looking-at "[-! \\][ \t]\\|#")
610a6418
SM
1073 (when (= (char-after) ?-) (delete-char 1) (insert "+"))
1074 (forward-line 1))
fef8c55b 1075 (let ((half1 (delete-and-extract-region half1s (point))))
bf6970a5 1076 (unless (looking-at diff-context-mid-hunk-header-re)
610a6418
SM
1077 (insert half1)
1078 (error "Can't find matching `--- n1,n2 ----' line"))
bf6970a5
SM
1079 (let* ((str1end (or (match-end 2) (match-end 1)))
1080 (str1 (buffer-substring (match-beginning 1) str1end)))
1081 (goto-char str1end)
1082 (insert lines1)
1083 (delete-region (match-beginning 1) str1end)
610a6418
SM
1084 (forward-line 1)
1085 (let ((half2s (point)))
0e104800 1086 (while (looking-at "[!+ \\][ \t]\\|#")
610a6418
SM
1087 (when (= (char-after) ?+) (delete-char 1) (insert "-"))
1088 (forward-line 1))
fef8c55b 1089 (let ((half2 (delete-and-extract-region half2s (point))))
0e104800 1090 (insert (or half1 ""))
610a6418 1091 (goto-char half1s)
0e104800 1092 (insert (or half2 ""))))
610a6418
SM
1093 (goto-char pt-lines1)
1094 (insert str1))))))
1095 ;; a unified-diff hunk header
1096 ((match-beginning 7)
1097 (replace-match "@@ -\\8 +\\7 @@" nil)
1098 (forward-line 1)
1099 (let ((c (char-after)) first last)
1100 (while (case (setq c (char-after))
1101 (?- (setq first (or first (point)))
1102 (delete-char 1) (insert "+") t)
1103 (?+ (setq last (or last (point)))
1104 (delete-char 1) (insert "-") t)
0e104800 1105 ((?\\ ?#) t)
610a6418 1106 (t (when (and first last (< first last))
cb4e8176 1107 (insert (delete-and-extract-region first last)))
610a6418 1108 (setq first nil last nil)
f52d2f9c
SM
1109 (memq c (if diff-valid-unified-empty-line
1110 '(?\s ?\n) '(?\s)))))
610a6418
SM
1111 (forward-line 1))))))))))
1112
1113(defun diff-fixup-modifs (start end)
1114 "Fixup the hunk headers (in case the buffer was modified).
1115START and END are either taken from the region (if a prefix arg is given) or
dfbd373d 1116else cover the whole buffer."
a70b0ff8
JL
1117 (interactive (if (or current-prefix-arg (and transient-mark-mode mark-active))
1118 (list (region-beginning) (region-end))
610a6418
SM
1119 (list (point-min) (point-max))))
1120 (let ((inhibit-read-only t))
1121 (save-excursion
f52d2f9c 1122 (goto-char end) (diff-end-of-hunk nil 'donttrustheader)
610a6418
SM
1123 (let ((plus 0) (minus 0) (space 0) (bang 0))
1124 (while (and (= (forward-line -1) 0) (<= start (point)))
bd9d7d76 1125 (if (not (looking-at
f52d2f9c 1126 (concat diff-hunk-header-re-unified
bd9d7d76
SM
1127 "\\|[-*][-*][-*] [0-9,]+ [-*][-*][-*][-*]$"
1128 "\\|--- .+\n\\+\\+\\+ ")))
610a6418 1129 (case (char-after)
bd9d7d76 1130 (?\s (incf space))
610a6418
SM
1131 (?+ (incf plus))
1132 (?- (incf minus))
1133 (?! (incf bang))
0e104800 1134 ((?\\ ?#) nil)
610a6418
SM
1135 (t (setq space 0 plus 0 minus 0 bang 0)))
1136 (cond
f52d2f9c
SM
1137 ((looking-at diff-hunk-header-re-unified)
1138 (let* ((old1 (match-string 2))
1139 (old2 (match-string 4))
610a6418
SM
1140 (new1 (number-to-string (+ space minus)))
1141 (new2 (number-to-string (+ space plus))))
f52d2f9c
SM
1142 (if old2
1143 (unless (string= new2 old2) (replace-match new2 t t nil 4))
1144 (goto-char (match-end 4)) (insert "," new2))
1145 (if old1
1146 (unless (string= new1 old1) (replace-match new1 t t nil 2))
1147 (goto-char (match-end 2)) (insert "," new1))))
bf6970a5 1148 ((looking-at diff-context-mid-hunk-header-re)
610a6418
SM
1149 (when (> (+ space bang plus) 0)
1150 (let* ((old1 (match-string 1))
1151 (old2 (match-string 2))
1152 (new (number-to-string
1153 (+ space bang plus -1 (string-to-number old1)))))
1154 (unless (string= new old2) (replace-match new t t nil 2)))))
1155 ((looking-at "\\*\\*\\* \\([0-9]+\\),\\(-?[0-9]*\\) \\*\\*\\*\\*$")
1156 (when (> (+ space bang minus) 0)
1157 (let* ((old (match-string 1))
1158 (new (format
1159 (concat "%0" (number-to-string (length old)) "d")
1160 (+ space bang minus -1 (string-to-number old)))))
1161 (unless (string= new old) (replace-match new t t nil 2))))))
1162 (setq space 0 plus 0 minus 0 bang 0)))))))
1163
71296446 1164;;;;
610a6418 1165;;;; Hooks
71296446 1166;;;;
610a6418
SM
1167
1168(defun diff-write-contents-hooks ()
1169 "Fixup hunk headers if necessary."
1170 (if (buffer-modified-p) (diff-fixup-modifs (point-min) (point-max)))
1171 nil)
1172
610a6418
SM
1173;; It turns out that making changes in the buffer from within an
1174;; *-change-function is asking for trouble, whereas making them
1175;; from a post-command-hook doesn't pose much problems
1176(defvar diff-unhandled-changes nil)
1177(defun diff-after-change-function (beg end len)
1178 "Remember to fixup the hunk header.
1179See `after-change-functions' for the meaning of BEG, END and LEN."
c0078a04
SM
1180 ;; Ignoring changes when inhibit-read-only is set is strictly speaking
1181 ;; incorrect, but it turns out that inhibit-read-only is normally not set
1182 ;; inside editing commands, while it tends to be set when the buffer gets
1183 ;; updated by an async process or by a conversion function, both of which
1184 ;; would rather not be uselessly slowed down by this hook.
610a6418
SM
1185 (when (and (not undo-in-progress) (not inhibit-read-only))
1186 (if diff-unhandled-changes
1187 (setq diff-unhandled-changes
1188 (cons (min beg (car diff-unhandled-changes))
cb4e8176 1189 (max end (cdr diff-unhandled-changes))))
610a6418
SM
1190 (setq diff-unhandled-changes (cons beg end)))))
1191
1192(defun diff-post-command-hook ()
1193 "Fixup hunk headers if necessary."
1194 (when (consp diff-unhandled-changes)
1195 (ignore-errors
1196 (save-excursion
fef8c55b 1197 (goto-char (car diff-unhandled-changes))
cb4e8176
SM
1198 ;; Maybe we've cut the end of the hunk before point.
1199 (if (and (bolp) (not (bobp))) (backward-char 1))
bf6970a5
SM
1200 ;; We used to fixup modifs on all the changes, but it turns out that
1201 ;; it's safer not to do it on big changes, e.g. when yanking a big
1202 ;; diff, or when the user edits the header, since we might then
1203 ;; screw up perfectly correct values. --Stef
1eabc5e6 1204 (diff-beginning-of-hunk)
bf6970a5
SM
1205 (let* ((style (if (looking-at "\\*\\*\\*") 'context))
1206 (start (line-beginning-position (if (eq style 'context) 3 2)))
1207 (mid (if (eq style 'context)
1208 (save-excursion
1209 (re-search-forward diff-context-mid-hunk-header-re
1210 nil t)))))
1211 (when (and ;; Don't try to fixup changes in the hunk header.
1212 (> (car diff-unhandled-changes) start)
1213 ;; Don't try to fixup changes in the mid-hunk header either.
1214 (or (not mid)
1215 (< (cdr diff-unhandled-changes) (match-beginning 0))
1216 (> (car diff-unhandled-changes) (match-end 0)))
1217 (save-excursion
f52d2f9c 1218 (diff-end-of-hunk nil 'donttrustheader)
bf6970a5
SM
1219 ;; Don't try to fixup changes past the end of the hunk.
1220 (>= (point) (cdr diff-unhandled-changes))))
1eabc5e6 1221 (diff-fixup-modifs (point) (cdr diff-unhandled-changes)))))
bf6970a5 1222 (setq diff-unhandled-changes nil))))
610a6418 1223
6fc049f6
SM
1224(defun diff-next-error (arg reset)
1225 ;; Select a window that displays the current buffer so that point
1226 ;; movements are reflected in that window. Otherwise, the user might
1227 ;; never see the hunk corresponding to the source she's jumping to.
1228 (pop-to-buffer (current-buffer))
1229 (if reset (goto-char (point-min)))
1230 (diff-hunk-next arg)
1231 (diff-goto-source))
610a6418 1232
e8f642e7
GM
1233(defvar whitespace-style)
1234(defvar whitespace-trailing-regexp)
1235
610a6418 1236;;;###autoload
d87e5627 1237(define-derived-mode diff-mode fundamental-mode "Diff"
0b82e382 1238 "Major mode for viewing/editing context diffs.
769dd0f1 1239Supports unified and context diffs as well as (to a lesser extent)
6035b97e 1240normal diffs.
b64d66b5 1241
df52e0e7 1242When the buffer is read-only, the ESC prefix is not necessary.
3b696504 1243If you edit the buffer manually, diff-mode will try to update the hunk
df52e0e7
SM
1244headers for you on-the-fly.
1245
1246You can also switch between context diff and unified diff with \\[diff-context->unified],
72147c53 1247or vice versa with \\[diff-unified->context] and you can also reverse the direction of
e78cf8e5 1248a diff with \\[diff-reverse-direction].
b64d66b5 1249
b64d66b5
RS
1250 \\{diff-mode-map}"
1251
610a6418
SM
1252 (set (make-local-variable 'font-lock-defaults) diff-font-lock-defaults)
1253 (set (make-local-variable 'outline-regexp) diff-outline-regexp)
ccce6558
DL
1254 (set (make-local-variable 'imenu-generic-expression)
1255 diff-imenu-generic-expression)
19e713d8
DL
1256 ;; These are not perfect. They would be better done separately for
1257 ;; context diffs and unidiffs.
1258 ;; (set (make-local-variable 'paragraph-start)
1259 ;; (concat "@@ " ; unidiff hunk
1260 ;; "\\|\\*\\*\\* " ; context diff hunk or file start
1261 ;; "\\|--- [^\t]+\t")) ; context or unidiff file
1262 ;; ; start (first or second line)
1263 ;; (set (make-local-variable 'paragraph-separate) paragraph-start)
1264 ;; (set (make-local-variable 'page-delimiter) "--- [^\t]+\t")
1265 ;; compile support
6fc049f6 1266 (set (make-local-variable 'next-error-function) 'diff-next-error)
cdbb79c1 1267
19446c41
DN
1268 (set (make-local-variable 'beginning-of-defun-function)
1269 'diff-beginning-of-file-and-junk)
1270 (set (make-local-variable 'end-of-defun-function)
1271 'diff-end-of-file)
1272
ac7020b3
DN
1273 ;; Set up `whitespace-mode' so that turning it on will show trailing
1274 ;; whitespace problems on the modified lines of the diff.
1275 (set (make-local-variable 'whitespace-style) '(trailing))
1276 (set (make-local-variable 'whitespace-trailing-regexp)
1277 "^[-\+!<>].*?\\([\t ]+\\)$")
1278
53dd481c 1279 (setq buffer-read-only diff-default-read-only)
769dd0f1 1280 ;; setup change hooks
e8a1ed31 1281 (if (not diff-update-on-the-fly)
3ccdb14e 1282 (add-hook 'write-contents-functions 'diff-write-contents-hooks nil t)
610a6418 1283 (make-local-variable 'diff-unhandled-changes)
3cec9c57
SM
1284 (add-hook 'after-change-functions 'diff-after-change-function nil t)
1285 (add-hook 'post-command-hook 'diff-post-command-hook nil t))
610a6418 1286 ;; Neat trick from Dave Love to add more bindings in read-only mode:
e78cf8e5 1287 (lexical-let ((ro-bind (cons 'buffer-read-only diff-mode-shared-map)))
22cd1973
SM
1288 (add-to-list 'minor-mode-overriding-map-alist ro-bind)
1289 ;; Turn off this little trick in case the buffer is put in view-mode.
1290 (add-hook 'view-mode-hook
e78cf8e5
SM
1291 (lambda ()
1292 (setq minor-mode-overriding-map-alist
1293 (delq ro-bind minor-mode-overriding-map-alist)))
22cd1973 1294 nil t))
281096ed
SM
1295 ;; add-log support
1296 (set (make-local-variable 'add-log-current-defun-function)
1297 'diff-current-defun)
1b1b5dae 1298 (set (make-local-variable 'add-log-buffer-file-name-function)
68035b97 1299 (lambda () (diff-find-file-name nil 'noprompt))))
610a6418
SM
1300
1301;;;###autoload
0b82e382
SM
1302(define-minor-mode diff-minor-mode
1303 "Minor mode for viewing/editing context diffs.
1304\\{diff-minor-mode-map}"
cfc80227 1305 :group 'diff-mode :lighter " Diff"
0b82e382 1306 ;; FIXME: setup font-lock
fef8c55b 1307 ;; setup change hooks
e8a1ed31 1308 (if (not diff-update-on-the-fly)
3ccdb14e 1309 (add-hook 'write-contents-functions 'diff-write-contents-hooks nil t)
fef8c55b 1310 (make-local-variable 'diff-unhandled-changes)
3cec9c57
SM
1311 (add-hook 'after-change-functions 'diff-after-change-function nil t)
1312 (add-hook 'post-command-hook 'diff-post-command-hook nil t)))
0b82e382 1313
e78cf8e5 1314;;; Handy hook functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
22cd1973
SM
1315
1316(defun diff-delete-if-empty ()
1317 ;; An empty diff file means there's no more diffs to integrate, so we
1318 ;; can just remove the file altogether. Very handy for .rej files if we
1319 ;; remove hunks as we apply them.
1320 (when (and buffer-file-name
1321 (eq 0 (nth 7 (file-attributes buffer-file-name))))
1322 (delete-file buffer-file-name)))
1323
1324(defun diff-delete-empty-files ()
1325 "Arrange for empty diff files to be removed."
1326 (add-hook 'after-save-hook 'diff-delete-if-empty nil t))
1327
1328(defun diff-make-unified ()
1329 "Turn context diffs into unified diffs if applicable."
1330 (if (save-excursion
1331 (goto-char (point-min))
4c174fb4 1332 (and (looking-at diff-hunk-header-re) (eq (char-after) ?*)))
22cd1973
SM
1333 (let ((mod (buffer-modified-p)))
1334 (unwind-protect
1335 (diff-context->unified (point-min) (point-max))
1336 (restore-buffer-modified-p mod)))))
610a6418 1337
027ac3f8
SM
1338;;;
1339;;; Misc operations that have proved useful at some point.
1340;;;
1341
1342(defun diff-next-complex-hunk ()
1343 "Jump to the next \"complex\" hunk.
1344\"Complex\" is approximated by \"the hunk changes the number of lines\".
1345Only works for unified diffs."
1346 (interactive)
1347 (while
f52d2f9c
SM
1348 (and (re-search-forward diff-hunk-header-re-unified nil t)
1349 (equal (match-string 2) (match-string 4)))))
027ac3f8 1350
3a349573
SM
1351(defun diff-sanity-check-context-hunk-half (lines)
1352 (let ((count lines))
1353 (while
1354 (cond
1355 ((and (memq (char-after) '(?\s ?! ?+ ?-))
1356 (memq (char-after (1+ (point))) '(?\s ?\t)))
1357 (decf count) t)
1358 ((or (zerop count) (= count lines)) nil)
1359 ((memq (char-after) '(?! ?+ ?-))
1360 (if (not (and (eq (char-after (1+ (point))) ?\n)
1361 (y-or-n-p "Try to auto-fix whitespace loss damage? ")))
1362 (error "End of hunk ambiguously marked")
1363 (forward-char 1) (insert " ") (forward-line -1) t))
1364 ((< lines 0)
1365 (error "End of hunk ambiguously marked"))
1366 ((not (y-or-n-p "Try to auto-fix whitespace loss and word-wrap damage? "))
1367 (error "Abort!"))
1368 ((eolp) (insert " ") (forward-line -1) t)
1369 (t (insert " ") (delete-region (- (point) 2) (- (point) 1)) t))
1370 (forward-line))))
1371
1372(defun diff-sanity-check-hunk ()
1373 (let (;; Every modification is protected by a y-or-n-p, so it's probably
1374 ;; OK to override a read-only setting.
1375 (inhibit-read-only t))
1376 (save-excursion
1377 (cond
1378 ((not (looking-at diff-hunk-header-re))
1379 (error "Not recognizable hunk header"))
1380
1381 ;; A context diff.
1382 ((eq (char-after) ?*)
7fb6ce6e 1383 (if (not (looking-at "\\*\\{15\\}\\(?: .*\\)?\n\\*\\*\\* \\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? \\*\\*\\*\\*"))
3a349573
SM
1384 (error "Unrecognized context diff first hunk header format")
1385 (forward-line 2)
1386 (diff-sanity-check-context-hunk-half
f52d2f9c 1387 (if (match-end 2)
7fb6ce6e
TTN
1388 (1+ (- (string-to-number (match-string 2))
1389 (string-to-number (match-string 1))))
1390 1))
bf6970a5 1391 (if (not (looking-at diff-context-mid-hunk-header-re))
3a349573
SM
1392 (error "Unrecognized context diff second hunk header format")
1393 (forward-line)
1394 (diff-sanity-check-context-hunk-half
f52d2f9c 1395 (if (match-end 2)
7fb6ce6e
TTN
1396 (1+ (- (string-to-number (match-string 2))
1397 (string-to-number (match-string 1))))
1398 1)))))
3a349573
SM
1399
1400 ;; A unified diff.
1401 ((eq (char-after) ?@)
f52d2f9c 1402 (if (not (looking-at diff-hunk-header-re-unified))
3a349573 1403 (error "Unrecognized unified diff hunk header format")
43392d12
SM
1404 (let ((before (string-to-number (or (match-string 2) "1")))
1405 (after (string-to-number (or (match-string 4) "1"))))
3a349573
SM
1406 (forward-line)
1407 (while
1408 (case (char-after)
1409 (?\s (decf before) (decf after) t)
80a01d97
SM
1410 (?-
1411 (if (and (looking-at diff-file-header-re)
1412 (zerop before) (zerop after))
1413 ;; No need to query: this is a case where two patches
1414 ;; are concatenated and only counting the lines will
1415 ;; give the right result. Let's just add an empty
1416 ;; line so that our code which doesn't count lines
1417 ;; will not get confused.
1418 (progn (save-excursion (insert "\n")) nil)
1419 (decf before) t))
3a349573
SM
1420 (?+ (decf after) t)
1421 (t
1422 (cond
f52d2f9c
SM
1423 ((and diff-valid-unified-empty-line
1424 ;; Not just (eolp) so we don't infloop at eob.
79fd6168
SM
1425 (eq (char-after) ?\n)
1426 (> before 0) (> after 0))
f52d2f9c 1427 (decf before) (decf after) t)
3a349573
SM
1428 ((and (zerop before) (zerop after)) nil)
1429 ((or (< before 0) (< after 0))
1430 (error (if (or (zerop before) (zerop after))
1431 "End of hunk ambiguously marked"
1432 "Hunk seriously messed up")))
f52d2f9c 1433 ((not (y-or-n-p (concat "Try to auto-fix " (if (eolp) "whitespace loss" "word-wrap damage") "? ")))
3a349573
SM
1434 (error "Abort!"))
1435 ((eolp) (insert " ") (forward-line -1) t)
1436 (t (insert " ")
1437 (delete-region (- (point) 2) (- (point) 1)) t))))
1438 (forward-line)))))
1439
1440 ;; A plain diff.
1441 (t
1442 ;; TODO.
1443 )))))
1444
1eabc5e6
SM
1445(defun diff-hunk-text (hunk destp char-offset)
1446 "Return the literal source text from HUNK as (TEXT . OFFSET).
a70b0ff8 1447If DESTP is nil, TEXT is the source, otherwise the destination text.
1eabc5e6
SM
1448CHAR-OFFSET is a char-offset in HUNK, and OFFSET is the corresponding
1449char-offset in TEXT."
3017133f 1450 (with-temp-buffer
6e4e8a3b
SM
1451 (insert hunk)
1452 (goto-char (point-min))
1453 (let ((src-pos nil)
1454 (dst-pos nil)
1455 (divider-pos nil)
1456 (num-pfx-chars 2))
1457 ;; Set the following variables:
1458 ;; SRC-POS buffer pos of the source part of the hunk or nil if none
1459 ;; DST-POS buffer pos of the destination part of the hunk or nil
1460 ;; DIVIDER-POS buffer pos of any divider line separating the src & dst
1461 ;; NUM-PFX-CHARS number of line-prefix characters used by this format"
1462 (cond ((looking-at "^@@")
1463 ;; unified diff
1464 (setq num-pfx-chars 1)
1465 (forward-line 1)
1466 (setq src-pos (point) dst-pos (point)))
1467 ((looking-at "^\\*\\*")
1468 ;; context diff
1469 (forward-line 2)
1470 (setq src-pos (point))
bf6970a5 1471 (re-search-forward diff-context-mid-hunk-header-re nil t)
6e4e8a3b
SM
1472 (forward-line 0)
1473 (setq divider-pos (point))
1474 (forward-line 1)
1475 (setq dst-pos (point)))
1476 ((looking-at "^[0-9]+a[0-9,]+$")
1477 ;; normal diff, insert
1478 (forward-line 1)
1479 (setq dst-pos (point)))
1480 ((looking-at "^[0-9,]+d[0-9]+$")
1481 ;; normal diff, delete
1482 (forward-line 1)
1483 (setq src-pos (point)))
1484 ((looking-at "^[0-9,]+c[0-9,]+$")
1485 ;; normal diff, change
1486 (forward-line 1)
1487 (setq src-pos (point))
1488 (re-search-forward "^---$" nil t)
1489 (forward-line 0)
1490 (setq divider-pos (point))
1491 (forward-line 1)
1492 (setq dst-pos (point)))
1493 (t
1494 (error "Unknown diff hunk type")))
1495
1496 (if (if destp (null dst-pos) (null src-pos))
1497 ;; Implied empty text
1498 (if char-offset '("" . 0) "")
1499
1500 ;; For context diffs, either side can be empty, (if there's only
1501 ;; added or only removed text). We should then use the other side.
1502 (cond ((equal src-pos divider-pos) (setq src-pos dst-pos))
1503 ((equal dst-pos (point-max)) (setq dst-pos src-pos)))
1504
1505 (when char-offset (goto-char (+ (point-min) char-offset)))
1506
1507 ;; Get rid of anything except the desired text.
1508 (save-excursion
1509 ;; Delete unused text region
1510 (let ((keep (if destp dst-pos src-pos)))
1511 (when (and divider-pos (> divider-pos keep))
1512 (delete-region divider-pos (point-max)))
1513 (delete-region (point-min) keep))
1514 ;; Remove line-prefix characters, and unneeded lines (unified diffs).
1515 (let ((kill-char (if destp ?- ?+)))
1516 (goto-char (point-min))
1517 (while (not (eobp))
1518 (if (eq (char-after) kill-char)
1519 (delete-region (point) (progn (forward-line 1) (point)))
1520 (delete-char num-pfx-chars)
1521 (forward-line 1)))))
1522
1523 (let ((text (buffer-substring-no-properties (point-min) (point-max))))
1524 (if char-offset (cons text (- (point) (point-min))) text))))))
370d860c 1525
7530b6da 1526
c0aa75f7 1527(defun diff-find-text (text)
1eabc5e6 1528 "Return the buffer position (BEG . END) of the nearest occurrence of TEXT.
7530b6da 1529If TEXT isn't found, nil is returned."
7530b6da
MB
1530 (let* ((orig (point))
1531 (forw (and (search-forward text nil t)
1eabc5e6 1532 (cons (match-beginning 0) (match-end 0))))
7530b6da
MB
1533 (back (and (goto-char (+ orig (length text)))
1534 (search-backward text nil t)
1eabc5e6
SM
1535 (cons (match-beginning 0) (match-end 0)))))
1536 ;; Choose the closest match.
1537 (if (and forw back)
1538 (if (> (- (car forw) orig) (- orig (car back))) back forw)
1539 (or back forw))))
1540
1541(defun diff-find-approx-text (text)
1542 "Return the buffer position (BEG . END) of the nearest occurrence of TEXT.
1543Whitespace differences are ignored."
1544 (let* ((orig (point))
1545 (re (concat "^[ \t\n\f]*"
1546 (mapconcat 'regexp-quote (split-string text) "[ \t\n\f]+")
1547 "[ \t\n\f]*\n"))
1548 (forw (and (re-search-forward re nil t)
1549 (cons (match-beginning 0) (match-end 0))))
1550 (back (and (goto-char (+ orig (length text)))
1551 (re-search-backward re nil t)
1552 (cons (match-beginning 0) (match-end 0)))))
1553 ;; Choose the closest match.
7530b6da 1554 (if (and forw back)
1eabc5e6 1555 (if (> (- (car forw) orig) (- orig (car back))) back forw)
7530b6da
MB
1556 (or back forw))))
1557
44a07c5a 1558(defsubst diff-xor (a b) (if a (if (not b) a) b))
370d860c 1559
68035b97 1560(defun diff-find-source-location (&optional other-file reverse noprompt)
1eabc5e6
SM
1561 "Find out (BUF LINE-OFFSET POS SRC DST SWITCHED).
1562BUF is the buffer corresponding to the source file.
1563LINE-OFFSET is the offset between the expected and actual positions
1564 of the text of the hunk or nil if the text was not found.
1565POS is a pair (BEG . END) indicating the position of the text in the buffer.
1566SRC and DST are the two variants of text as returned by `diff-hunk-text'.
1567 SRC is the variant that was found in the buffer.
68035b97
SM
1568SWITCHED is non-nil if the patch is already applied.
1569NOPROMPT, if non-nil, means not to prompt the user."
7b91e0f2 1570 (save-excursion
e8a1ed31 1571 (let* ((other (diff-xor other-file diff-jump-to-old-file))
fd691799
SM
1572 (char-offset (- (point) (progn (diff-beginning-of-hunk 'try-harder)
1573 (point))))
3a349573
SM
1574 ;; Check that the hunk is well-formed. Otherwise diff-mode and
1575 ;; the user may disagree on what constitutes the hunk
1576 ;; (e.g. because an empty line truncates the hunk mid-course),
1577 ;; leading to potentially nasty surprises for the user.
1578 (_ (diff-sanity-check-hunk))
68035b97
SM
1579 (hunk (buffer-substring
1580 (point) (save-excursion (diff-end-of-hunk) (point))))
370d860c
SM
1581 (old (diff-hunk-text hunk reverse char-offset))
1582 (new (diff-hunk-text hunk (not reverse) char-offset))
7b91e0f2 1583 ;; Find the location specification.
d868b3bd 1584 (line (if (not (looking-at "\\(?:\\*\\{15\\}.*\n\\)?[-@* ]*\\([0-9,]+\\)\\([ acd+]+\\([0-9,]+\\)\\)?"))
370d860c
SM
1585 (error "Can't find the hunk header")
1586 (if other (match-string 1)
1587 (if (match-end 3) (match-string 3)
bf6970a5
SM
1588 (unless (re-search-forward
1589 diff-context-mid-hunk-header-re nil t)
370d860c
SM
1590 (error "Can't find the hunk separator"))
1591 (match-string 1)))))
68035b97
SM
1592 (file (or (diff-find-file-name other noprompt)
1593 (error "Can't find the file")))
370d860c 1594 (buf (find-file-noselect file)))
7b91e0f2
SM
1595 ;; Update the user preference if he so wished.
1596 (when (> (prefix-numeric-value other-file) 8)
e8a1ed31 1597 (setq diff-jump-to-old-file other))
d868b3bd
SM
1598 (with-current-buffer buf
1599 (goto-line (string-to-number line))
1600 (let* ((orig-pos (point))
1eabc5e6 1601 (switched nil)
22cd1973 1602 ;; FIXME: Check for case where both OLD and NEW are found.
1eabc5e6
SM
1603 (pos (or (diff-find-text (car old))
1604 (progn (setq switched t) (diff-find-text (car new)))
1605 (progn (setq switched nil)
22cd1973
SM
1606 (condition-case nil
1607 (diff-find-approx-text (car old))
1608 (invalid-regexp nil))) ;Regex too big.
1eabc5e6 1609 (progn (setq switched t)
22cd1973
SM
1610 (condition-case nil
1611 (diff-find-approx-text (car new))
1612 (invalid-regexp nil))) ;Regex too big.
1eabc5e6 1613 (progn (setq switched nil) nil))))
370d860c
SM
1614 (nconc
1615 (list buf)
1eabc5e6
SM
1616 (if pos
1617 (list (count-lines orig-pos (car pos)) pos)
1618 (list nil (cons orig-pos (+ orig-pos (length (car old))))))
370d860c
SM
1619 (if switched (list new old t) (list old new))))))))
1620
7b91e0f2 1621
370d860c
SM
1622(defun diff-hunk-status-msg (line-offset reversed dry-run)
1623 (let ((msg (if dry-run
1624 (if reversed "already applied" "not yet applied")
1625 (if reversed "undone" "applied"))))
1626 (message (cond ((null line-offset) "Hunk text not found")
1627 ((= line-offset 0) "Hunk %s")
1628 ((= line-offset 1) "Hunk %s at offset %d line")
1629 (t "Hunk %s at offset %d lines"))
1630 msg line-offset)))
1631
cb9a30c8 1632(defvar diff-apply-hunk-to-backup-file nil)
370d860c
SM
1633
1634(defun diff-apply-hunk (&optional reverse)
6e4e8a3b 1635 "Apply the current hunk to the source file and go to the next.
7530b6da 1636By default, the new source file is patched, but if the variable
e8a1ed31 1637`diff-jump-to-old-file' is non-nil, then the old source file is
7530b6da
MB
1638patched instead (some commands, such as `diff-goto-source' can change
1639the value of this variable when given an appropriate prefix argument).
1640
4eaa6852 1641With a prefix argument, REVERSE the hunk."
370d860c 1642 (interactive "P")
281096ed 1643 (destructuring-bind (buf line-offset pos old new &optional switched)
44a07c5a
SM
1644 ;; Sometimes we'd like to have the following behavior: if REVERSE go
1645 ;; to the new file, otherwise go to the old. But that means that by
1646 ;; default we use the old file, which is the opposite of the default
1647 ;; for diff-goto-source, and is thus confusing. Also when you don't
1648 ;; know about it it's pretty surprising.
1649 ;; TODO: make it possible to ask explicitly for this behavior.
9201cc28 1650 ;;
44a07c5a
SM
1651 ;; This is duplicated in diff-test-hunk.
1652 (diff-find-source-location nil reverse)
370d860c 1653 (cond
4eaa6852
MB
1654 ((null line-offset)
1655 (error "Can't find the text to patch"))
cb9a30c8
SM
1656 ((with-current-buffer buf
1657 (and buffer-file-name
1658 (backup-file-name-p buffer-file-name)
1659 (not diff-apply-hunk-to-backup-file)
1660 (not (set (make-local-variable 'diff-apply-hunk-to-backup-file)
1661 (yes-or-no-p (format "Really apply this hunk to %s? "
1662 (file-name-nondirectory
1663 buffer-file-name)))))))
593b4fa8
AS
1664 (error "%s"
1665 (substitute-command-keys
cb9a30c8
SM
1666 (format "Use %s\\[diff-apply-hunk] to apply it to the other file"
1667 (if (not reverse) "\\[universal-argument] ")))))
370d860c 1668 ((and switched
cdbb79c1 1669 ;; A reversed patch was detected, perhaps apply it in reverse.
370d860c
SM
1670 (not (save-window-excursion
1671 (pop-to-buffer buf)
1eabc5e6 1672 (goto-char (+ (car pos) (cdr old)))
370d860c
SM
1673 (y-or-n-p
1674 (if reverse
1675 "Hunk hasn't been applied yet; apply it now? "
1676 "Hunk has already been applied; undo it? ")))))
4eaa6852 1677 (message "(Nothing done)"))
370d860c 1678 (t
4eaa6852
MB
1679 ;; Apply the hunk
1680 (with-current-buffer buf
1eabc5e6
SM
1681 (goto-char (car pos))
1682 (delete-region (car pos) (cdr pos))
4eaa6852
MB
1683 (insert (car new)))
1684 ;; Display BUF in a window
1eabc5e6 1685 (set-window-point (display-buffer buf) (+ (car pos) (cdr new)))
4eaa6852
MB
1686 (diff-hunk-status-msg line-offset (diff-xor switched reverse) nil)
1687 (when diff-advance-after-apply-hunk
1688 (diff-hunk-next))))))
370d860c 1689
7530b6da 1690
7530b6da
MB
1691(defun diff-test-hunk (&optional reverse)
1692 "See whether it's possible to apply the current hunk.
4eaa6852 1693With a prefix argument, try to REVERSE the hunk."
7530b6da 1694 (interactive "P")
370d860c 1695 (destructuring-bind (buf line-offset pos src dst &optional switched)
44a07c5a 1696 (diff-find-source-location nil reverse)
1eabc5e6 1697 (set-window-point (display-buffer buf) (+ (car pos) (cdr src)))
4eaa6852 1698 (diff-hunk-status-msg line-offset (diff-xor reverse switched) t)))
370d860c 1699
7530b6da 1700
a1e2d7f7
SM
1701(defalias 'diff-mouse-goto-source 'diff-goto-source)
1702
1703(defun diff-goto-source (&optional other-file event)
7530b6da 1704 "Jump to the corresponding source line.
e8a1ed31 1705`diff-jump-to-old-file' (or its opposite if the OTHER-FILE prefix arg
281096ed 1706is given) determines whether to jump to the old or the new file.
7530b6da 1707If the prefix arg is bigger than 8 (for example with \\[universal-argument] \\[universal-argument])
a70b0ff8 1708then `diff-jump-to-old-file' is also set, for the next invocations."
a1e2d7f7 1709 (interactive (list current-prefix-arg last-input-event))
a6373340
SM
1710 ;; When pointing at a removal line, we probably want to jump to
1711 ;; the old location, and else to the new (i.e. as if reverting).
1712 ;; This is a convenient detail when using smerge-diff.
a1e2d7f7 1713 (if event (posn-set-point (event-end event)))
a6373340
SM
1714 (let ((rev (not (save-excursion (beginning-of-line) (looking-at "[-<]")))))
1715 (destructuring-bind (buf line-offset pos src dst &optional switched)
1716 (diff-find-source-location other-file rev)
1717 (pop-to-buffer buf)
1eabc5e6 1718 (goto-char (+ (car pos) (cdr src)))
55d5d717 1719 (diff-hunk-status-msg line-offset (diff-xor rev switched) t))))
370d860c 1720
7530b6da 1721
281096ed 1722(defun diff-current-defun ()
370d860c
SM
1723 "Find the name of function at point.
1724For use in `add-log-current-defun-function'."
ef63ea1c
SM
1725 ;; Kill change-log-default-name so it gets recomputed each time, since
1726 ;; each hunk may belong to another file which may belong to another
1727 ;; directory and hence have a different ChangeLog file.
1728 (kill-local-variable 'change-log-default-name)
2f9edc8b
KG
1729 (save-excursion
1730 (when (looking-at diff-hunk-header-re)
1731 (forward-line 1)
4f815065 1732 (re-search-forward "^[^ ]" nil t))
68035b97
SM
1733 (destructuring-bind (&optional buf line-offset pos src dst switched)
1734 ;; Use `noprompt' since this is used in which-func-mode and such.
1735 (ignore-errors ;Signals errors in place of prompting.
1736 (diff-find-source-location nil nil 'noprompt))
1737 (when buf
1738 (beginning-of-line)
1739 (or (when (memq (char-after) '(?< ?-))
1740 ;; Cursor is pointing at removed text. This could be a removed
1741 ;; function, in which case, going to the source buffer will
1742 ;; not help since the function is now removed. Instead,
1743 ;; try to figure out the function name just from the
1744 ;; code-fragment.
1745 (let ((old (if switched dst src)))
1746 (with-temp-buffer
1747 (insert (car old))
1748 (funcall (buffer-local-value 'major-mode buf))
1749 (goto-char (+ (point-min) (cdr old)))
1750 (add-log-current-defun))))
1751 (with-current-buffer buf
1752 (goto-char (+ (car pos) (cdr src)))
1753 (add-log-current-defun)))))))
027ac3f8 1754
2659df68
SM
1755(defun diff-ignore-whitespace-hunk ()
1756 "Re-diff the current hunk, ignoring whitespace differences."
22cd1973 1757 (interactive)
fd691799
SM
1758 (let* ((char-offset (- (point) (progn (diff-beginning-of-hunk 'try-harder)
1759 (point))))
22cd1973
SM
1760 (opts (case (char-after) (?@ "-bu") (?* "-bc") (t "-b")))
1761 (line-nb (and (or (looking-at "[^0-9]+\\([0-9]+\\)")
1762 (error "Can't find line number"))
1763 (string-to-number (match-string 1))))
14cf0430 1764 (inhibit-read-only t)
22cd1973
SM
1765 (hunk (delete-and-extract-region
1766 (point) (save-excursion (diff-end-of-hunk) (point))))
1767 (lead (make-string (1- line-nb) ?\n)) ;Line nums start at 1.
1768 (file1 (make-temp-file "diff1"))
1769 (file2 (make-temp-file "diff2"))
1770 (coding-system-for-read buffer-file-coding-system)
1771 old new)
1772 (unwind-protect
1773 (save-excursion
1774 (setq old (diff-hunk-text hunk nil char-offset))
1775 (setq new (diff-hunk-text hunk t char-offset))
1776 (write-region (concat lead (car old)) nil file1 nil 'nomessage)
1777 (write-region (concat lead (car new)) nil file2 nil 'nomessage)
1778 (with-temp-buffer
1779 (let ((status
1780 (call-process diff-command nil t nil
1781 opts file1 file2)))
1782 (case status
1783 (0 nil) ;Nothing to reformat.
1784 (1 (goto-char (point-min))
1785 ;; Remove the file-header.
1786 (when (re-search-forward diff-hunk-header-re nil t)
1787 (delete-region (point-min) (match-beginning 0))))
1788 (t (goto-char (point-max))
1789 (unless (bolp) (insert "\n"))
1790 (insert hunk)))
1791 (setq hunk (buffer-string))
1792 (unless (memq status '(0 1))
1793 (error "Diff returned: %s" status)))))
1794 ;; Whatever happens, put back some equivalent text: either the new
1795 ;; one or the original one in case some error happened.
1796 (insert hunk)
1797 (delete-file file1)
1798 (delete-file file2))))
1799
be36f934
SM
1800;;; Fine change highlighting.
1801
2659df68
SM
1802(defface diff-refine-change
1803 '((((class color) (min-colors 88) (background light))
9f4e4f5b 1804 :background "grey85")
2659df68 1805 (((class color) (min-colors 88) (background dark))
5e15554d 1806 :background "grey60")
2659df68
SM
1807 (((class color) (background light))
1808 :background "yellow")
1809 (((class color) (background dark))
1810 :background "green")
1811 (t :weight bold))
1812 "Face used for char-based changes shown by `diff-refine-hunk'."
5eee3c95 1813 :group 'diff-mode)
be36f934 1814
2659df68 1815(defun diff-refine-preproc ()
8872469d
SM
1816 (while (re-search-forward "^[+>]" nil t)
1817 ;; Remove spurious changes due to the fact that one side of the hunk is
1818 ;; marked with leading + or > and the other with leading - or <.
1819 ;; We used to replace all the prefix chars with " " but this only worked
1820 ;; when we did char-based refinement (or when using
1821 ;; smerge-refine-weight-hack) since otherwise, the `forward' motion done
1822 ;; in chopup do not necessarily do the same as the ones in highlight
1823 ;; since the "_" is not treated the same as " ".
1824 (replace-match (cdr (assq (char-before) '((?+ . "-") (?> . "<"))))))
1825 )
be36f934 1826
2659df68 1827(defun diff-refine-hunk ()
be36f934
SM
1828 "Highlight changes of hunk at point at a finer granularity."
1829 (interactive)
2f42c75f 1830 (eval-and-compile (require 'smerge-mode))
8872469d
SM
1831 (save-excursion
1832 (diff-beginning-of-hunk 'try-harder)
1833 (let* ((style (diff-hunk-style)) ;Skips the hunk header as well.
1834 (beg (point))
2659df68 1835 (props '((diff-mode . fine) (face diff-refine-change)))
8872469d
SM
1836 (end (progn (diff-end-of-hunk) (point))))
1837
1838 (remove-overlays beg end 'diff-mode 'fine)
1839
1840 (goto-char beg)
1841 (case style
1842 (unified
1843 (while (re-search-forward "^\\(?:-.*\n\\)+\\(\\)\\(?:\\+.*\n\\)+"
1844 end t)
1845 (smerge-refine-subst (match-beginning 0) (match-end 1)
1846 (match-end 1) (match-end 0)
2659df68 1847 props 'diff-refine-preproc)))
8872469d
SM
1848 (context
1849 (let* ((middle (save-excursion (re-search-forward "^---")))
1850 (other middle))
1851 (while (re-search-forward "^\\(?:!.*\n\\)+" middle t)
1852 (smerge-refine-subst (match-beginning 0) (match-end 0)
1853 (save-excursion
1854 (goto-char other)
1855 (re-search-forward "^\\(?:!.*\n\\)+" end)
1856 (setq other (match-end 0))
1857 (match-beginning 0))
1858 other
2659df68 1859 props 'diff-refine-preproc))))
8872469d
SM
1860 (t ;; Normal diffs.
1861 (let ((beg1 (1+ (point))))
1862 (when (re-search-forward "^---.*\n" end t)
1863 ;; It's a combined add&remove, so there's something to do.
1864 (smerge-refine-subst beg1 (match-beginning 0)
1865 (match-end 0) end
2659df68 1866 props 'diff-refine-preproc))))))))
be36f934
SM
1867
1868
8330c175
SM
1869(defun diff-add-change-log-entries-other-window ()
1870 "Iterate through the current diff and create ChangeLog entries.
1871I.e. like `add-change-log-entry-other-window' but applied to all hunks."
8a72c7f8
DN
1872 (interactive)
1873 ;; XXX: Currently add-change-log-entry-other-window is only called
1874 ;; once per hunk. Some hunks have multiple changes, it would be
1875 ;; good to call it for each change.
1876 (save-excursion
1877 (goto-char (point-min))
1878 (let ((orig-buffer (current-buffer)))
1879 (condition-case nil
1880 ;; Call add-change-log-entry-other-window for each hunk in
1881 ;; the diff buffer.
8330c175
SM
1882 (while (progn
1883 (diff-hunk-next)
1884 ;; Move to where the changes are,
1885 ;; `add-change-log-entry-other-window' works better in
1886 ;; that case.
02e3336d
SM
1887 (re-search-forward
1888 (concat "\n[!+-<>]"
1889 ;; If the hunk is a context hunk with an empty first
1890 ;; half, recognize the "--- NNN,MMM ----" line
1891 "\\(-- [0-9]+\\(,[0-9]+\\)? ----\n"
1892 ;; and skip to the next non-context line.
1893 "\\( .*\n\\)*[+]\\)?")
1894 nil t))
8330c175 1895 (save-excursion
2a520399 1896 (add-change-log-entry nil nil t nil t)))
8330c175 1897 ;; When there's no more hunks, diff-hunk-next signals an error.
8a72c7f8
DN
1898 (error nil)))))
1899
610a6418
SM
1900;; provide the package
1901(provide 'diff-mode)
1902
027ac3f8 1903;;; Old Change Log from when diff-mode wasn't part of Emacs:
610a6418
SM
1904;; Revision 1.11 1999/10/09 23:38:29 monnier
1905;; (diff-mode-load-hook): dropped.
1906;; (auto-mode-alist): also catch *.diffs.
1907;; (diff-find-file-name, diff-mode): add smarts to find the right file
1908;; for *.rej files (that lack any file name indication).
1909;;
1910;; Revision 1.10 1999/09/30 15:32:11 monnier
1911;; added support for "\ No newline at end of file".
1912;;
1913;; Revision 1.9 1999/09/15 00:01:13 monnier
1914;; - added basic `compile' support.
1915;; - have diff-kill-hunk call diff-kill-file if it's the only hunk.
1916;; - diff-kill-file now tries to kill the leading garbage as well.
1917;;
1918;; Revision 1.8 1999/09/13 21:10:09 monnier
1919;; - don't use CL in the autoloaded code
1920;; - accept diffs using -T
1921;;
1922;; Revision 1.7 1999/09/05 20:53:03 monnier
1923;; interface to ediff-patch
1924;;
1925;; Revision 1.6 1999/09/01 20:55:13 monnier
1926;; (ediff=patch-file): add bindings to call ediff-patch.
1927;; (diff-find-file-name): taken out of diff-goto-source.
1928;; (diff-unified->context, diff-context->unified, diff-reverse-direction,
1929;; diff-fixup-modifs): only use the region if a prefix arg is given.
1930;;
1931;; Revision 1.5 1999/08/31 19:18:52 monnier
1932;; (diff-beginning-of-file, diff-prev-file): fixed wrong parenthesis.
1933;;
1934;; Revision 1.4 1999/08/31 13:01:44 monnier
1935;; use `combine-after-change-calls' to minimize the slowdown of font-lock.
1936;;
1937
4f815065 1938;; arch-tag: 2571d7ff-bc28-4cf9-8585-42e21890be66
610a6418 1939;;; diff-mode.el ends here