(vc-backend-diff): get rid of the autoloaded advice.
[bpt/emacs.git] / lisp / diff-mode.el
1 ;;; diff-mode.el --- A mode for viewing/editing context diffs
2
3 ;; Copyright (C) 1998-1999 Free Software Foundation, Inc.
4
5 ;; Author: Stefan Monnier <monnier@cs.yale.edu>
6 ;; Keywords: patch diff
7 ;; Version: v1_8
8 ;; Revision: diff-mode.el,v 1.11 1999/10/09 23:38:29 monnier Exp
9
10 ;; This file is part of GNU Emacs.
11
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; any later version.
16
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
21
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING. If not, write to the
24 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 ;; Boston, MA 02111-1307, USA.
26
27 ;;; Commentary:
28
29 ;; Provides support for font-lock patterns, outline-regexps, navigation
30 ;; commands, editing and various conversions as well as jumping
31 ;; to the corresponding source file.
32
33 ;; History:
34
35 ;; inspired by Pavel Machek's patch-mode.el (<pavel@atrey.karlin.mff.cuni.cz>)
36 ;; some efforts were spent to have it somewhat compatible with XEmacs'
37 ;; diff-mode as well as with compilation-minor-mode
38
39 ;; to use it, simply add to your .emacs the following lines:
40 ;;
41 ;; (autoload 'diff-mode "diff-mode" "Diff major mode" t)
42 ;; (add-to-list 'auto-mode-alist '("\\.\\(diffs?\\|patch\\|rej\\)\\'" . diff-mode))
43
44 ;; Bugs:
45
46 ;; - reverse doesn't work with normal diffs.
47 ;; - (nitpick) the mark is not always quite right in diff-goto-source.
48
49 ;; Todo:
50
51 ;; - improve narrowed-view support.
52 ;; - improve diff-find-file-name.
53 ;; - improve the `compile' support.
54
55 ;;; Code:
56
57 (eval-when-compile (require 'cl))
58
59
60 (defgroup diff-mode ()
61 "Major-mode for viewing/editing diffs"
62 :group 'tools
63 :group 'diff)
64
65 (defcustom diff-jump-to-old-file-flag nil
66 "*Non-nil means `diff-goto-source' jumps to the old file.
67 Else, it jumps to the new file."
68 :group 'diff-mode
69 :type '(boolean))
70
71 (defcustom diff-update-on-the-fly-flag t
72 "*Non-nil means hunk headers are kept up-to-date on-the-fly.
73 When editing a diff file, the line numbers in the hunk headers
74 need to be kept consistent with the actual diff. This can
75 either be done on the fly (but this sometimes interacts poorly with the
76 undo mechanism) or whenever the file is written (can be slow
77 when editing big diffs)."
78 :group 'diff-mode
79 :type '(boolean))
80
81 (defvar diff-mode-hook nil
82 "Run after setting up the `diff-mode' major mode.")
83
84 (defvar diff-outline-regexp
85 "\\([*+][*+][*+] [^0-9]\\|@@ ...\\|\\*\\*\\* [0-9].\\|--- [0-9]..\\)")
86
87 ;;;;
88 ;;;; keymap, menu, ...
89 ;;;;
90
91 (defmacro diff-defmap (var bindings doc)
92 `(defvar ,var
93 (let ((m (make-keymap)))
94 (dolist (b ,bindings)
95 (define-key m (car b) (cdr b)))
96 m)
97 ,doc))
98
99 (diff-defmap diff-mode-shared-map
100 '(;; from Pavel Machek's patch-mode
101 ("n" . diff-next-hunk)
102 ("N" . diff-next-file)
103 ("p" . diff-prev-hunk)
104 ("P" . diff-prev-file)
105 ("k" . diff-kill-hunk)
106 ("K" . diff-kill-file)
107 ;; from compilation-minor-mode
108 ("}" . diff-next-file)
109 ("{" . diff-prev-file)
110 ("\C-m" . diff-goto-source)
111 ;; from XEmacs' diff-mode
112 ("W" . widen)
113 ;;("." . diff-goto-source) ;display-buffer
114 ;;("f" . diff-goto-source) ;find-file
115 ("o" . diff-goto-source) ;other-window
116 ;;("w" . diff-goto-source) ;other-frame
117 ;;("N" . diff-narrow)
118 ;;("h" . diff-show-header)
119 ;;("j" . diff-show-difference) ;jump to Nth diff
120 ;;("q" . diff-quit)
121 (" " . scroll-up)
122 ("\177" . scroll-down)
123 ;; our very own bindings
124 ("A" . diff-ediff-patch)
125 ("r" . diff-restrict-view)
126 ("R" . diff-reverse-direction)
127 ("U" . diff-context->unified)
128 ("C" . diff-unified->context))
129 "Keymap for read-only `diff-mode'. Only active in read-only mode.")
130
131 (diff-defmap diff-mode-map
132 `(("\e" . ,diff-mode-shared-map)
133 ;; from compilation-minor-mode
134 ("\C-c\C-c" . diff-goto-source))
135 "Keymap for `diff-mode'. See also `diff-mode-shared-map'.")
136
137 (easy-menu-define diff-mode-menu diff-mode-map
138 "Menu for `diff-mode'."
139 '("Diff"
140 ["Jump to Source" diff-goto-source t]
141 ["Apply with Ediff" diff-ediff-patch t]
142 ["-----" nil nil]
143 ["Reverse direction" diff-reverse-direction t]
144 ["Context -> Unified" diff-context->unified t]
145 ["Unified -> Context" diff-unified->context t]
146 ;;["Fixup Headers" diff-fixup-modifs (not buffer-read-only)]
147 ))
148
149
150 ;;;;
151 ;;;; font-lock support
152 ;;;;
153
154 (defface diff-file-header-face
155 '((((class color) (background light))
156 (:background "grey70" :bold t))
157 (t (:bold t)))
158 "diff-mode face used to highlight file header lines."
159 :group 'diff-mode)
160 (defvar diff-file-header-face 'diff-file-header-face)
161
162 (defface diff-index-face
163 '((((class color) (background light))
164 (:background "grey70" :bold t))
165 (t (:bold t)))
166 "diff-mode face used to highlight index header lines."
167 :group 'diff-mode)
168 (defvar diff-index-face 'diff-index-face)
169
170 (defface diff-hunk-header-face
171 '((((class color) (background light))
172 (:background "grey85"))
173 (t (:bold t)))
174 "diff-mode face used to highlight hunk header lines."
175 :group 'diff-mode)
176 (defvar diff-hunk-header-face 'diff-hunk-header-face)
177
178 (defface diff-removed-face
179 '((t ()))
180 "diff-mode face used to highlight removed lines."
181 :group 'diff-mode)
182 (defvar diff-removed-face 'diff-removed-face)
183
184 (defface diff-added-face
185 '((t ()))
186 "diff-mode face used to highlight added lines."
187 :group 'diff-mode)
188 (defvar diff-added-face 'diff-added-face)
189
190 (defface diff-changed-face
191 '((t ()))
192 "diff-mode face used to highlight changed lines."
193 :group 'diff-mode)
194 (defvar diff-changed-face 'diff-changed-face)
195
196 (defvar diff-font-lock-keywords
197 '(("^@@ .+ @@$" . diff-hunk-header-face) ;unified
198 ("^--- .+ ----$" . diff-hunk-header-face) ;context
199 ("^\\*\\*\\*.+\\*\\*\\*\n" . diff-hunk-header-face) ;context
200 ("^\\(---\\|\\+\\+\\+\\|\\*\\*\\*\\) .*\n" . diff-file-header-face)
201 ("^[0-9,]+[acd][0-9,]+$" . diff-hunk-header-face)
202 ("^!.*\n" . diff-changed-face) ;context
203 ("^[+>].*\n" . diff-added-face)
204 ("^[-<].*\n" . diff-removed-face)
205 ("^Index: .*\n" . diff-index-face)
206 ("^[^-=+*!<>].*\n" . font-lock-comment-face)))
207
208 (defconst diff-font-lock-defaults
209 '(diff-font-lock-keywords t nil nil nil))
210
211 ;;;;
212 ;;;; Compile support
213 ;;;;
214
215 (defvar diff-file-regexp-alist
216 '(("Index: \\(.+\\)" 1)))
217
218 (defvar diff-error-regexp-alist
219 '(("@@ -\\([0-9]+\\),[0-9]+ \\+\\([0-9]+\\),[0-9]+ @@" nil 2)
220 ("--- \\([0-9]+\\),[0-9]+ ----" nil 1)
221 ("\\([0-9]+\\)\\(,[0-9]+\\)?[adc]\\([0-9]+\\)" nil 3)))
222
223 ;;;;
224 ;;;; Movement
225 ;;;;
226
227 (defconst diff-hunk-header-re "^\\(@@ .+ @@\\|\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\n\\*\\*\\* .+ \\*\\*\\*\\*\\|[0-9]+\\(,[0-9]+\\)?[acd][0-9]+\\(,[0-9]+\\)?\\)$")
228 (defconst diff-file-header-re (concat "^\\(--- .+\n\\+\\+\\+\\|\\*\\*\\* .+\n---\\|[^-+!<>0-9@* ]\\).+\n" (substring diff-hunk-header-re 1)))
229 (defvar diff-narrowed-to nil)
230
231 (defun diff-end-of-hunk (&optional style)
232 (if (looking-at diff-hunk-header-re) (goto-char (match-end 0)))
233 (re-search-forward (case style
234 (unified "^[^-+ \\]")
235 (context "^\\([^-+! \\][ \t]\\|--- .+ ----\\)")
236 (normal "^\\([<>\\][ \t]\\|---\\)")
237 (t "^[^-+!<> \\]"))
238 nil 'move)
239 (beginning-of-line))
240
241 (defun diff-beginning-of-hunk ()
242 (beginning-of-line)
243 (unless (looking-at diff-hunk-header-re)
244 (forward-line 1)
245 (condition-case ()
246 (re-search-backward diff-hunk-header-re)
247 (error (error "Can't find the beginning of the hunk")))))
248
249 (defun diff-beginning-of-file ()
250 (beginning-of-line)
251 (unless (looking-at diff-file-header-re)
252 (forward-line 2)
253 (condition-case ()
254 (re-search-backward diff-file-header-re)
255 (error (error "Can't find the beginning of the file")))))
256
257 (defun diff-end-of-file ()
258 (re-search-forward "^[-+!<>0-9@* \\]" nil t)
259 (re-search-forward "^[^-+!<>0-9@* \\]" nil 'move)
260 (beginning-of-line))
261
262 (defun diff-next-hunk (&optional count)
263 "Move to next (COUNT'th) hunk."
264 (interactive "p")
265 (unless count (setq count 1))
266 (if (< count 0) (diff-prev-hunk (- count))
267 (when (looking-at diff-hunk-header-re) (incf count))
268 (condition-case ()
269 (re-search-forward diff-hunk-header-re nil nil count)
270 (error (error "Can't find next hunk")))
271 (goto-char (match-beginning 0))))
272
273 (defun diff-prev-hunk (&optional count)
274 "Move to previous (COUNT'th) hunk."
275 (interactive "p")
276 (unless count (setq count 1))
277 (if (< count 0) (diff-next-hunk (- count))
278 (condition-case ()
279 (re-search-backward diff-hunk-header-re nil nil count)
280 (error (error "Can't find previous hunk")))))
281
282 (defun diff-next-file (&optional count)
283 "Move to next (COUNT'th) file header."
284 (interactive "p")
285 (unless count (setq count 1))
286 (if (< count 0) (diff-prev-file (- count))
287 (when (looking-at diff-file-header-re) (incf count))
288 (condition-case ()
289 (re-search-forward diff-file-header-re nil nil count)
290 (error (error "Can't find next file")))
291 (goto-char (match-beginning 0))))
292
293 (defun diff-prev-file (&optional count)
294 "Move to (COUNT'th) previous file header."
295 (interactive "p")
296 (unless count (setq count 1))
297 (if (< count 0) (diff-next-file (- count))
298 (condition-case ()
299 (re-search-backward diff-file-header-re nil nil count)
300 (error (error "Can't find previous file")))))
301
302 (defun diff-restrict-view (&optional arg)
303 "Restrict the view to the current hunk.
304 If the prefix ARG is given, restrict the view to the current file instead."
305 (interactive "P")
306 (save-excursion
307 (if arg (diff-beginning-of-file) (diff-beginning-of-hunk))
308 (narrow-to-region (point)
309 (progn (if arg (diff-end-of-file) (diff-end-of-hunk))
310 (point)))
311 (set (make-local-variable 'diff-narrowed-to) (if arg 'file 'hunk))))
312
313
314 (defun diff-kill-hunk ()
315 "Kill current hunk."
316 (interactive)
317 (diff-beginning-of-hunk)
318 (let ((start (point))
319 (firsthunk (save-excursion
320 (ignore-errors
321 (diff-beginning-of-file) (diff-next-hunk) (point))))
322 (nexthunk (save-excursion
323 (ignore-errors
324 (diff-next-hunk) (point))))
325 (nextfile (save-excursion
326 (ignore-errors
327 (diff-next-file) (point)))))
328 (if (and firsthunk (= firsthunk start)
329 (or (null nexthunk)
330 (and nextfile (> nexthunk nextfile))))
331 ;; we're the only hunk for this file, so kill the file
332 (diff-kill-file)
333 (diff-end-of-hunk)
334 (kill-region start (point)))))
335
336 (defun diff-kill-file ()
337 "Kill current file's hunks."
338 (interactive)
339 (diff-beginning-of-file)
340 (let* ((start (point))
341 (prevhunk (save-excursion
342 (ignore-errors
343 (diff-prev-hunk) (point))))
344 (index (save-excursion
345 (re-search-backward "^Index: " prevhunk t))))
346 (when index (setq start index))
347 (diff-end-of-file)
348 (kill-region start (point))))
349
350 ;;;;
351 ;;;; jump to other buffers
352 ;;;;
353
354 (defun diff-filename-drop-dir (file)
355 (when (string-match "/" file) (substring file (match-end 0))))
356
357 (defun diff-find-file-name (&optional old)
358 "Return the file corresponding to the current patch.
359 Non-nil OLD means that we want the old file."
360 (save-excursion
361 (unless (looking-at diff-file-header-re)
362 (or (ignore-errors (diff-beginning-of-file))
363 (re-search-forward diff-file-header-re nil t)))
364 (let* ((limit (save-excursion
365 (condition-case ()
366 (progn (diff-prev-hunk) (point))
367 (error (point-min)))))
368 (fs (append
369 (when (looking-at "[-*][-*][-*] \\(\\S-+\\)\\s-.*\n[-+][-+][-+] \\(\\S-+\\)\\s-.*$")
370 (list (if old (match-string 1) (match-string 2))
371 (if old (match-string 2) (match-string 1))))
372 (progn (forward-line 1) nil)
373 (when (save-excursion
374 (re-search-backward "^Index: \\(.+\\)" limit t))
375 (list (match-string 1)))
376 (when (re-search-backward "^diff \\(-\\S-+ +\\)*\\(\\S-+\\)\\( +\\(\\S-+\\)\\)?" nil t)
377 (list (if old (match-string 2) (match-string 4))
378 (if old (match-string 4) (match-string 2))))))
379 (fs (delq nil fs))
380 (file
381 ;; look for each file in turn. If none found, try again but
382 ;; ignoring the first level of directory, ...
383 (do* ((files fs (delq nil (mapcar 'diff-filename-drop-dir files)))
384 (file nil nil))
385 ((or (null files)
386 (setq file (do* ((files files (cdr files))
387 (file (car files) (car files)))
388 ((or (null file) (file-exists-p file))
389 file))))
390 file))))
391 (or
392 file
393 (and (string-match "\\.rej\\'" (or buffer-file-name ""))
394 (let ((file (substring buffer-file-name 0 (match-beginning 0))))
395 (when (file-exists-p file) file)))
396 ;; FIXME: use a more informative prompt
397 (let ((file (read-file-name "File: " nil (first fs) nil (first fs))))
398 ;; FIXME: remember for the next invocation
399 file)))))
400
401 (defun diff-goto-source (&optional other-file)
402 "Jump to the corresponding source line.
403 `diff-jump-to-old-file-flag' (or its opposite if the OTHER-FILE prefix arg
404 is give) determines whether to jump to the old or the new file.
405 If the prefix arg is bigger than 8 (for example with \\[universal-argument] \\[universal-argument])
406 then `diff-jump-to-old-file-flag' is also set, for the next invokations."
407 (interactive "P")
408 (save-excursion
409 (let ((old (if (not other-file) diff-jump-to-old-file-flag
410 (not diff-jump-to-old-file-flag))))
411 (when (> (prefix-numeric-value other-file) 8)
412 (setq diff-jump-to-old-file-flag old))
413 (diff-beginning-of-hunk)
414 (let* ((loc (if (not (looking-at "[-@*\n ]*\\([0-9,]+\\)\\([ acd+]+\\([0-9,]+\\)\\)?"))
415 (error "Can't find the hunk header")
416 (if old (match-string 1)
417 (if (match-end 3) (match-string 3)
418 (unless (re-search-forward "^--- \\([0-9,]+\\)" nil t)
419 (error "Can't find the hunk separator"))
420 (match-string 1)))))
421 (lines (if (string-match "^\\([0-9]*\\),\\([0-9]*\\)" loc)
422 (cons (string-to-number (match-string 1 loc))
423 (string-to-number (match-string 2 loc)))
424 (cons (string-to-number loc) nil)))
425 (file (diff-find-file-name old)))
426 (unless file (error "Can't find the file"))
427 (pop-to-buffer (find-file-noselect file))
428 (let* ((line (car lines))
429 (span (if (or (null (cdr lines)) (< (cdr lines) 0)) 0
430 (if (< (cdr lines) line) (cdr lines)
431 (- (cdr lines) line)))))
432 (ignore-errors
433 (goto-line line)
434 (forward-line span)
435 (push-mark (point) t t)
436 (goto-line line)))))))
437
438
439 (defun diff-ediff-patch ()
440 "Call `ediff-patch-file' on the current buffer."
441 (interactive)
442 (condition-case err
443 (ediff-patch-file (current-buffer))
444 (wrong-number-of-arguments (ediff-patch-file))))
445
446 ;;;;
447 ;;;; Conversion functions
448 ;;;;
449
450 ;;(defvar diff-inhibit-after-change nil
451 ;; "Non-nil means inhibit `diff-mode's after-change functions.")
452
453 (defun diff-unified->context (start end)
454 "Convert unified diffs to context diffs.
455 START and END are either taken from the region (if a prefix arg is given) or
456 else cover the whole bufer."
457 (interactive (if current-prefix-arg
458 (list (mark) (point))
459 (list (point-min) (point-max))))
460 (unless (markerp end) (setq end (copy-marker end)))
461 (let (;;(diff-inhibit-after-change t)
462 (inhibit-read-only t))
463 (save-excursion
464 (goto-char start)
465 (while (and (re-search-forward "^\\(\\(---\\) .+\n\\(\\+\\+\\+\\) .+\\|@@ -\\([0-9]+\\),\\([0-9]+\\) \\+\\([0-9]+\\),\\([0-9]+\\) @@\\)$" nil t)
466 (< (point) end))
467 (combine-after-change-calls
468 (if (match-beginning 2)
469 ;;we matched a file header
470 (progn
471 ;; use reverse order to make sure the indices are kept valid
472 (replace-match "---" t t nil 3)
473 (replace-match "***" t t nil 2))
474 ;; we matched a hunk header
475 (let ((line1 (match-string 4))
476 (lines1 (match-string 5))
477 (line2 (match-string 6))
478 (lines2 (match-string 7)))
479 (replace-match
480 (concat "***************\n*** " line1 ","
481 (number-to-string (+ (string-to-number line1)
482 (string-to-number lines1)
483 -1)) " ****"))
484 (forward-line 1)
485 (save-restriction
486 (narrow-to-region (point)
487 (progn (diff-end-of-hunk 'unified) (point)))
488 (let ((hunk (buffer-string)))
489 (goto-char (point-min))
490 (if (not (save-excursion (re-search-forward "^-" nil t)))
491 (delete-region (point) (point-max))
492 (goto-char (point-max))
493 (let ((modif nil) last-pt)
494 (while (progn (setq last-pt (point))
495 (= (forward-line -1) 0))
496 (case (char-after)
497 (? (insert " ") (setq modif nil) (backward-char 1))
498 (?+ (delete-region (point) last-pt) (setq modif t))
499 (?- (if (not modif)
500 (progn (forward-char 1)
501 (insert " "))
502 (delete-char 1)
503 (insert "! "))
504 (backward-char 2))
505 (?\\ (when (save-excursion (forward-line -1)
506 (= (char-after) ?+))
507 (delete-region (point) last-pt) (setq modif t)))
508 (t (setq modif nil))))))
509 (goto-char (point-max))
510 (save-excursion
511 (insert "--- " line2 ","
512 (number-to-string (+ (string-to-number line2)
513 (string-to-number lines2)
514 -1)) " ----\n" hunk))
515 ;;(goto-char (point-min))
516 (forward-line 1)
517 (if (not (save-excursion (re-search-forward "^+" nil t)))
518 (delete-region (point) (point-max))
519 (let ((modif nil) (delete nil))
520 (while (not (eobp))
521 (case (char-after)
522 (? (insert " ") (setq modif nil) (backward-char 1))
523 (?- (setq delete t) (setq modif t))
524 (?+ (if (not modif)
525 (progn (forward-char 1)
526 (insert " "))
527 (delete-char 1)
528 (insert "! "))
529 (backward-char 2))
530 (?\\ (when (save-excursion (forward-line 1)
531 (not (eobp)))
532 (setq delete t) (setq modif t)))
533 (t (setq modif nil)))
534 (let ((last-pt (point)))
535 (forward-line 1)
536 (when delete
537 (delete-region last-pt (point))
538 (setq delete nil)))))))))))))))
539
540 (defun diff-context->unified (start end)
541 "Convert context diffs to unified diffs.
542 START and END are either taken from the region (if a prefix arg is given) or
543 else cover the whole bufer."
544 (interactive (if current-prefix-arg
545 (list (mark) (point))
546 (list (point-min) (point-max))))
547 (unless (markerp end) (setq end (copy-marker end)))
548 (let (;;(diff-inhibit-after-change t)
549 (inhibit-read-only t))
550 (save-excursion
551 (goto-char start)
552 (while (and (re-search-forward "^\\(\\(\\*\\*\\*\\) .+\n\\(---\\) .+\\|\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\n\\*\\*\\* \\([0-9]+\\),\\(-?[0-9]+\\) \\*\\*\\*\\*\\)$" nil t)
553 (< (point) end))
554 (combine-after-change-calls
555 (if (match-beginning 2)
556 ;; we matched a file header
557 (progn
558 ;; use reverse order to make sure the indices are kept valid
559 (replace-match "+++" t t nil 3)
560 (replace-match "---" t t nil 2))
561 ;; we matched a hunk header
562 (let ((line1s (match-string 4))
563 (line1e (match-string 5))
564 (pt1 (match-beginning 0)))
565 (replace-match "")
566 (unless (re-search-forward
567 "^--- \\([0-9]+\\),\\(-?[0-9]+\\) ----$" nil t)
568 (error "Can't find matching `--- n1,n2 ----' line"))
569 (let ((line2s (match-string 1))
570 (line2e (match-string 2))
571 (pt2 (progn
572 (delete-region (progn (beginning-of-line) (point))
573 (progn (forward-line 1) (point)))
574 (point-marker))))
575 (goto-char pt1)
576 (forward-line 1)
577 (while (< (point) pt2)
578 (case (char-after)
579 ((?! ?-) (delete-char 2) (insert "-") (forward-line 1))
580 (?\ ;merge with the other half of the chunk
581 (let* ((endline2
582 (save-excursion
583 (goto-char pt2) (forward-line 1) (point)))
584 (c (char-after pt2)))
585 (case c
586 ((?! ?+)
587 (insert "+"
588 (prog1 (buffer-substring (+ pt2 2) endline2)
589 (delete-region pt2 endline2))))
590 (?\ ;FIXME: check consistency
591 (delete-region pt2 endline2)
592 (delete-char 1)
593 (forward-line 1))
594 (?\\ (forward-line 1))
595 (t (delete-char 1) (forward-line 1)))))
596 (t (forward-line 1))))
597 (while (looking-at "[+! ] ")
598 (if (/= (char-after) ?!) (forward-char 1)
599 (delete-char 1) (insert "+"))
600 (delete-char 1) (forward-line 1))
601 (save-excursion
602 (goto-char pt1)
603 (insert "@@ -" line1s ","
604 (number-to-string (- (string-to-number line1e)
605 (string-to-number line1s)
606 -1))
607 " +" line2s ","
608 (number-to-string (- (string-to-number line2e)
609 (string-to-number line2s)
610 -1)) " @@"))))))))))
611
612 (defun diff-reverse-direction (start end)
613 "Reverse the direction of the diffs.
614 START and END are either taken from the region (if a prefix arg is given) or
615 else cover the whole bufer."
616 (interactive (if current-prefix-arg
617 (list (mark) (point))
618 (list (point-min) (point-max))))
619 (unless (markerp end) (setq end (copy-marker end)))
620 (let (;;(diff-inhibit-after-change t)
621 (inhibit-read-only t))
622 (save-excursion
623 (goto-char start)
624 (while (and (re-search-forward "^\\(\\([-*][-*][-*] \\)\\(.+\\)\n\\([-+][-+][-+] \\)\\(.+\\)\\|\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\n\\*\\*\\* \\(.+\\) \\*\\*\\*\\*\\|@@ -\\(.+\\) \\+\\(.+\\) @@\\)$" nil t)
625 (< (point) end))
626 (combine-after-change-calls
627 (cond
628 ;; a file header
629 ((match-beginning 2) (replace-match "\\2\\5\n\\4\\3" nil))
630 ;; a context-diff hunk header
631 ((match-beginning 6)
632 (let ((pt-lines1 (match-beginning 6))
633 (lines1 (match-string 6)))
634 (replace-match "" nil nil nil 6)
635 (forward-line 1)
636 (let ((half1s (point)))
637 (while (looking-at "[-! \\][ \t]")
638 (when (= (char-after) ?-) (delete-char 1) (insert "+"))
639 (forward-line 1))
640 (let ((half1 (buffer-substring half1s (point))))
641 (delete-region half1s (point))
642 (unless (looking-at "^--- \\([0-9]+,-?[0-9]+\\) ----$")
643 (insert half1)
644 (error "Can't find matching `--- n1,n2 ----' line"))
645 (let ((str1 (match-string 1)))
646 (replace-match lines1 nil nil nil 1)
647 (forward-line 1)
648 (let ((half2s (point)))
649 (while (looking-at "[!+ \\][ \t]")
650 (when (= (char-after) ?+) (delete-char 1) (insert "-"))
651 (forward-line 1))
652 (let ((half2 (buffer-substring half2s (point))))
653 (delete-region half2s (point))
654 (insert half1)
655 (goto-char half1s)
656 (insert half2)))
657 (goto-char pt-lines1)
658 (insert str1))))))
659 ;; a unified-diff hunk header
660 ((match-beginning 7)
661 (replace-match "@@ -\\8 +\\7 @@" nil)
662 (forward-line 1)
663 (let ((c (char-after)) first last)
664 (while (case (setq c (char-after))
665 (?- (setq first (or first (point)))
666 (delete-char 1) (insert "+") t)
667 (?+ (setq last (or last (point)))
668 (delete-char 1) (insert "-") t)
669 (?\\ t)
670 (t (when (and first last (< first last))
671 (let ((str (buffer-substring first last)))
672 (save-excursion (delete-region first last))
673 (insert str)))
674 (setq first nil last nil)
675 (equal ?\ c)))
676 (forward-line 1))))))))))
677
678 (defun diff-fixup-modifs (start end)
679 "Fixup the hunk headers (in case the buffer was modified).
680 START and END are either taken from the region (if a prefix arg is given) or
681 else cover the whole bufer."
682 (interactive (if current-prefix-arg
683 (list (mark) (point))
684 (list (point-min) (point-max))))
685 (let ((inhibit-read-only t))
686 (save-excursion
687 (goto-char end) (diff-end-of-hunk)
688 (let ((plus 0) (minus 0) (space 0) (bang 0))
689 (while (and (= (forward-line -1) 0) (<= start (point)))
690 (if (not (looking-at "\\(@@ .+ @@\\|[-*][-*][-*] .+ [-*][-*][-*][-*]\\)$"))
691 (case (char-after)
692 (?\ (incf space))
693 (?+ (incf plus))
694 (?- (incf minus))
695 (?! (incf bang))
696 (?\\ nil)
697 (t (setq space 0 plus 0 minus 0 bang 0)))
698 (cond
699 ((looking-at "@@ -[0-9]+,\\([0-9]*\\) \\+[0-9]+,\\([0-9]*\\) @@$")
700 (let* ((old1 (match-string 1))
701 (old2 (match-string 2))
702 (new1 (number-to-string (+ space minus)))
703 (new2 (number-to-string (+ space plus))))
704 (unless (string= new2 old2) (replace-match new2 t t nil 2))
705 (unless (string= new1 old1) (replace-match new1 t t nil 1))))
706 ((looking-at "--- \\([0-9]+\\),\\([0-9]*\\) ----$")
707 (when (> (+ space bang plus) 0)
708 (let* ((old1 (match-string 1))
709 (old2 (match-string 2))
710 (new (number-to-string
711 (+ space bang plus -1 (string-to-number old1)))))
712 (unless (string= new old2) (replace-match new t t nil 2)))))
713 ((looking-at "\\*\\*\\* \\([0-9]+\\),\\(-?[0-9]*\\) \\*\\*\\*\\*$")
714 (when (> (+ space bang minus) 0)
715 (let* ((old (match-string 1))
716 (new (format
717 (concat "%0" (number-to-string (length old)) "d")
718 (+ space bang minus -1 (string-to-number old)))))
719 (unless (string= new old) (replace-match new t t nil 2))))))
720 (setq space 0 plus 0 minus 0 bang 0)))))))
721
722 ;;;;
723 ;;;; Hooks
724 ;;;;
725
726 (defun diff-write-contents-hooks ()
727 "Fixup hunk headers if necessary."
728 (if (buffer-modified-p) (diff-fixup-modifs (point-min) (point-max)))
729 nil)
730
731 ;; XEmacs doesn't seem to have this feature
732 (defvar undo-in-progress nil)
733
734 ;; It turns out that making changes in the buffer from within an
735 ;; *-change-function is asking for trouble, whereas making them
736 ;; from a post-command-hook doesn't pose much problems
737 (defvar diff-unhandled-changes nil)
738 (defun diff-after-change-function (beg end len)
739 "Remember to fixup the hunk header.
740 See `after-change-functions' for the meaning of BEG, END and LEN."
741 (when (and (not undo-in-progress) (not inhibit-read-only))
742 (if diff-unhandled-changes
743 (setq diff-unhandled-changes
744 (cons (min beg (car diff-unhandled-changes))
745 (max beg (cdr diff-unhandled-changes))))
746 (setq diff-unhandled-changes (cons beg end)))))
747
748 (defun diff-post-command-hook ()
749 "Fixup hunk headers if necessary."
750 (when (consp diff-unhandled-changes)
751 (ignore-errors
752 (save-excursion
753 (goto-char (car diff-unhandled-changes)) (diff-beginning-of-hunk)
754 (diff-fixup-modifs (point) (cdr diff-unhandled-changes))))
755 (setq diff-unhandled-changes nil)))
756
757 ;;;;
758 ;;;; The main function
759 ;;;;
760
761 ;;(autoload 'diff-mode "diff-mode" "Major mode for viewing context diffs." t)
762 ;;;###autoload
763 (defun diff-mode ()
764 "Major mode for viewing context diffs.
765 Supports unified and context diffs as well as (to a lesser extent) normal diffs.
766 When the buffer is read-only, the ESC prefix is not necessary.
767 This mode runs `diff-mode-hook'.
768 \\{diff-mode-map}"
769 (interactive)
770 (kill-all-local-variables)
771 (setq major-mode 'diff-mode)
772 (setq mode-name "Diff")
773 (use-local-map diff-mode-map)
774 (set (make-local-variable 'font-lock-defaults) diff-font-lock-defaults)
775 (set (make-local-variable 'outline-regexp) diff-outline-regexp)
776 ;; compile support
777 (set (make-local-variable 'compilation-file-regexp-alist)
778 diff-file-regexp-alist)
779 (set (make-local-variable 'compilation-error-regexp-alist)
780 diff-error-regexp-alist)
781 (when (string-match "\\.rej\\'" (or buffer-file-name ""))
782 (set (make-local-variable 'compilation-current-file)
783 (substring buffer-file-name 0 (match-beginning 0))))
784 (compilation-shell-minor-mode 1)
785 ;;
786 (setq buffer-read-only t)
787 (if (not diff-update-on-the-fly-flag)
788 (add-hook 'write-contents-hooks 'diff-write-contents-hooks)
789 (make-local-variable 'diff-unhandled-changes)
790 (make-local-hook 'after-change-functions)
791 (add-hook 'after-change-functions 'diff-after-change-function nil t)
792 (make-local-hook 'post-command-hook)
793 (add-hook 'post-command-hook 'diff-post-command-hook nil t))
794 ;; Neat trick from Dave Love to add more bindings in read-only mode:
795 (add-to-list (make-local-variable 'minor-mode-map-alist)
796 (cons 'buffer-read-only diff-mode-shared-map))
797 ;;
798 (run-hooks 'diff-mode-hook))
799
800 ;;;###autoload
801 (add-to-list 'auto-mode-alist '("\\.\\(diffs?\\|patch\\|rej\\)\\'" . diff-mode))
802
803 ;; provide the package
804 (provide 'diff-mode)
805
806 ;;; Change Log:
807 ;; diff-mode.el,v
808 ;; Revision 1.11 1999/10/09 23:38:29 monnier
809 ;; (diff-mode-load-hook): dropped.
810 ;; (auto-mode-alist): also catch *.diffs.
811 ;; (diff-find-file-name, diff-mode): add smarts to find the right file
812 ;; for *.rej files (that lack any file name indication).
813 ;;
814 ;; Revision 1.10 1999/09/30 15:32:11 monnier
815 ;; added support for "\ No newline at end of file".
816 ;;
817 ;; Revision 1.9 1999/09/15 00:01:13 monnier
818 ;; - added basic `compile' support.
819 ;; - have diff-kill-hunk call diff-kill-file if it's the only hunk.
820 ;; - diff-kill-file now tries to kill the leading garbage as well.
821 ;;
822 ;; Revision 1.8 1999/09/13 21:10:09 monnier
823 ;; - don't use CL in the autoloaded code
824 ;; - accept diffs using -T
825 ;;
826 ;; Revision 1.7 1999/09/05 20:53:03 monnier
827 ;; interface to ediff-patch
828 ;;
829 ;; Revision 1.6 1999/09/01 20:55:13 monnier
830 ;; (ediff=patch-file): add bindings to call ediff-patch.
831 ;; (diff-find-file-name): taken out of diff-goto-source.
832 ;; (diff-unified->context, diff-context->unified, diff-reverse-direction,
833 ;; diff-fixup-modifs): only use the region if a prefix arg is given.
834 ;;
835 ;; Revision 1.5 1999/08/31 19:18:52 monnier
836 ;; (diff-beginning-of-file, diff-prev-file): fixed wrong parenthesis.
837 ;;
838 ;; Revision 1.4 1999/08/31 13:01:44 monnier
839 ;; use `combine-after-change-calls' to minimize the slowdown of font-lock.
840 ;;
841
842 ;;; diff-mode.el ends here