]> code.delx.au - gnu-emacs/blob - lisp/diff-mode.el
(echo_char): Use KEY_DESCRIPTION_SIZE to check free
[gnu-emacs] / 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 (header-files
369 (if (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 (forward-line 1) nil))
373 (fs (append
374 (when (save-excursion
375 (re-search-backward "^Index: \\(.+\\)" limit t))
376 (list (match-string 1)))
377 header-files
378 (when (re-search-backward "^diff \\(-\\S-+ +\\)*\\(\\S-+\\)\\( +\\(\\S-+\\)\\)?" nil t)
379 (list (if old (match-string 2) (match-string 4))
380 (if old (match-string 4) (match-string 2))))))
381 (fs (delq nil fs))
382 (file
383 ;; look for each file in turn. If none found, try again but
384 ;; ignoring the first level of directory, ...
385 (do* ((files fs (delq nil (mapcar 'diff-filename-drop-dir files)))
386 (file nil nil))
387 ((or (null files)
388 (setq file (do* ((files files (cdr files))
389 (file (car files) (car files)))
390 ((or (null file) (file-exists-p file))
391 file))))
392 file))))
393 (or
394 file
395 (and (string-match "\\.rej\\'" (or buffer-file-name ""))
396 (let ((file (substring buffer-file-name 0 (match-beginning 0))))
397 (when (file-exists-p file) file)))
398 ;; FIXME: use a more informative prompt
399 (let ((file (read-file-name "File: " nil (first fs) nil (first fs))))
400 ;; FIXME: remember for the next invocation
401 file)))))
402
403 (defun diff-goto-source (&optional other-file)
404 "Jump to the corresponding source line.
405 `diff-jump-to-old-file-flag' (or its opposite if the OTHER-FILE prefix arg
406 is give) determines whether to jump to the old or the new file.
407 If the prefix arg is bigger than 8 (for example with \\[universal-argument] \\[universal-argument])
408 then `diff-jump-to-old-file-flag' is also set, for the next invokations."
409 (interactive "P")
410 (save-excursion
411 (let ((old (if (not other-file) diff-jump-to-old-file-flag
412 (not diff-jump-to-old-file-flag))))
413 (when (> (prefix-numeric-value other-file) 8)
414 (setq diff-jump-to-old-file-flag old))
415 (diff-beginning-of-hunk)
416 (let* ((loc (if (not (looking-at "[-@*\n ]*\\([0-9,]+\\)\\([ acd+]+\\([0-9,]+\\)\\)?"))
417 (error "Can't find the hunk header")
418 (if old (match-string 1)
419 (if (match-end 3) (match-string 3)
420 (unless (re-search-forward "^--- \\([0-9,]+\\)" nil t)
421 (error "Can't find the hunk separator"))
422 (match-string 1)))))
423 (lines (if (string-match "^\\([0-9]*\\),\\([0-9]*\\)" loc)
424 (cons (string-to-number (match-string 1 loc))
425 (string-to-number (match-string 2 loc)))
426 (cons (string-to-number loc) nil)))
427 (file (diff-find-file-name old)))
428 (unless file (error "Can't find the file"))
429 (pop-to-buffer (find-file-noselect file))
430 (let* ((line (car lines))
431 (span (if (or (null (cdr lines)) (< (cdr lines) 0)) 0
432 (if (< (cdr lines) line) (cdr lines)
433 (- (cdr lines) line)))))
434 (ignore-errors
435 (goto-line line)
436 (forward-line span)
437 (push-mark (point) t t)
438 (goto-line line)))))))
439
440
441 (defun diff-ediff-patch ()
442 "Call `ediff-patch-file' on the current buffer."
443 (interactive)
444 (condition-case err
445 (ediff-patch-file (current-buffer))
446 (wrong-number-of-arguments (ediff-patch-file))))
447
448 ;;;;
449 ;;;; Conversion functions
450 ;;;;
451
452 ;;(defvar diff-inhibit-after-change nil
453 ;; "Non-nil means inhibit `diff-mode's after-change functions.")
454
455 (defun diff-unified->context (start end)
456 "Convert unified diffs to context diffs.
457 START and END are either taken from the region (if a prefix arg is given) or
458 else cover the whole bufer."
459 (interactive (if current-prefix-arg
460 (list (mark) (point))
461 (list (point-min) (point-max))))
462 (unless (markerp end) (setq end (copy-marker end)))
463 (let (;;(diff-inhibit-after-change t)
464 (inhibit-read-only t))
465 (save-excursion
466 (goto-char start)
467 (while (and (re-search-forward "^\\(\\(---\\) .+\n\\(\\+\\+\\+\\) .+\\|@@ -\\([0-9]+\\),\\([0-9]+\\) \\+\\([0-9]+\\),\\([0-9]+\\) @@\\)$" nil t)
468 (< (point) end))
469 (combine-after-change-calls
470 (if (match-beginning 2)
471 ;;we matched a file header
472 (progn
473 ;; use reverse order to make sure the indices are kept valid
474 (replace-match "---" t t nil 3)
475 (replace-match "***" t t nil 2))
476 ;; we matched a hunk header
477 (let ((line1 (match-string 4))
478 (lines1 (match-string 5))
479 (line2 (match-string 6))
480 (lines2 (match-string 7)))
481 (replace-match
482 (concat "***************\n*** " line1 ","
483 (number-to-string (+ (string-to-number line1)
484 (string-to-number lines1)
485 -1)) " ****"))
486 (forward-line 1)
487 (save-restriction
488 (narrow-to-region (point)
489 (progn (diff-end-of-hunk 'unified) (point)))
490 (let ((hunk (buffer-string)))
491 (goto-char (point-min))
492 (if (not (save-excursion (re-search-forward "^-" nil t)))
493 (delete-region (point) (point-max))
494 (goto-char (point-max))
495 (let ((modif nil) last-pt)
496 (while (progn (setq last-pt (point))
497 (= (forward-line -1) 0))
498 (case (char-after)
499 (? (insert " ") (setq modif nil) (backward-char 1))
500 (?+ (delete-region (point) last-pt) (setq modif t))
501 (?- (if (not modif)
502 (progn (forward-char 1)
503 (insert " "))
504 (delete-char 1)
505 (insert "! "))
506 (backward-char 2))
507 (?\\ (when (save-excursion (forward-line -1)
508 (= (char-after) ?+))
509 (delete-region (point) last-pt) (setq modif t)))
510 (t (setq modif nil))))))
511 (goto-char (point-max))
512 (save-excursion
513 (insert "--- " line2 ","
514 (number-to-string (+ (string-to-number line2)
515 (string-to-number lines2)
516 -1)) " ----\n" hunk))
517 ;;(goto-char (point-min))
518 (forward-line 1)
519 (if (not (save-excursion (re-search-forward "^+" nil t)))
520 (delete-region (point) (point-max))
521 (let ((modif nil) (delete nil))
522 (while (not (eobp))
523 (case (char-after)
524 (? (insert " ") (setq modif nil) (backward-char 1))
525 (?- (setq delete t) (setq modif t))
526 (?+ (if (not modif)
527 (progn (forward-char 1)
528 (insert " "))
529 (delete-char 1)
530 (insert "! "))
531 (backward-char 2))
532 (?\\ (when (save-excursion (forward-line 1)
533 (not (eobp)))
534 (setq delete t) (setq modif t)))
535 (t (setq modif nil)))
536 (let ((last-pt (point)))
537 (forward-line 1)
538 (when delete
539 (delete-region last-pt (point))
540 (setq delete nil)))))))))))))))
541
542 (defun diff-context->unified (start end)
543 "Convert context diffs to unified diffs.
544 START and END are either taken from the region (if a prefix arg is given) or
545 else cover the whole bufer."
546 (interactive (if current-prefix-arg
547 (list (mark) (point))
548 (list (point-min) (point-max))))
549 (unless (markerp end) (setq end (copy-marker end)))
550 (let (;;(diff-inhibit-after-change t)
551 (inhibit-read-only t))
552 (save-excursion
553 (goto-char start)
554 (while (and (re-search-forward "^\\(\\(\\*\\*\\*\\) .+\n\\(---\\) .+\\|\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\n\\*\\*\\* \\([0-9]+\\),\\(-?[0-9]+\\) \\*\\*\\*\\*\\)$" nil t)
555 (< (point) end))
556 (combine-after-change-calls
557 (if (match-beginning 2)
558 ;; we matched a file header
559 (progn
560 ;; use reverse order to make sure the indices are kept valid
561 (replace-match "+++" t t nil 3)
562 (replace-match "---" t t nil 2))
563 ;; we matched a hunk header
564 (let ((line1s (match-string 4))
565 (line1e (match-string 5))
566 (pt1 (match-beginning 0)))
567 (replace-match "")
568 (unless (re-search-forward
569 "^--- \\([0-9]+\\),\\(-?[0-9]+\\) ----$" nil t)
570 (error "Can't find matching `--- n1,n2 ----' line"))
571 (let ((line2s (match-string 1))
572 (line2e (match-string 2))
573 (pt2 (progn
574 (delete-region (progn (beginning-of-line) (point))
575 (progn (forward-line 1) (point)))
576 (point-marker))))
577 (goto-char pt1)
578 (forward-line 1)
579 (while (< (point) pt2)
580 (case (char-after)
581 ((?! ?-) (delete-char 2) (insert "-") (forward-line 1))
582 (?\ ;merge with the other half of the chunk
583 (let* ((endline2
584 (save-excursion
585 (goto-char pt2) (forward-line 1) (point)))
586 (c (char-after pt2)))
587 (case c
588 ((?! ?+)
589 (insert "+"
590 (prog1 (buffer-substring (+ pt2 2) endline2)
591 (delete-region pt2 endline2))))
592 (?\ ;FIXME: check consistency
593 (delete-region pt2 endline2)
594 (delete-char 1)
595 (forward-line 1))
596 (?\\ (forward-line 1))
597 (t (delete-char 1) (forward-line 1)))))
598 (t (forward-line 1))))
599 (while (looking-at "[+! ] ")
600 (if (/= (char-after) ?!) (forward-char 1)
601 (delete-char 1) (insert "+"))
602 (delete-char 1) (forward-line 1))
603 (save-excursion
604 (goto-char pt1)
605 (insert "@@ -" line1s ","
606 (number-to-string (- (string-to-number line1e)
607 (string-to-number line1s)
608 -1))
609 " +" line2s ","
610 (number-to-string (- (string-to-number line2e)
611 (string-to-number line2s)
612 -1)) " @@"))))))))))
613
614 (defun diff-reverse-direction (start end)
615 "Reverse the direction of the diffs.
616 START and END are either taken from the region (if a prefix arg is given) or
617 else cover the whole bufer."
618 (interactive (if current-prefix-arg
619 (list (mark) (point))
620 (list (point-min) (point-max))))
621 (unless (markerp end) (setq end (copy-marker end)))
622 (let (;;(diff-inhibit-after-change t)
623 (inhibit-read-only t))
624 (save-excursion
625 (goto-char start)
626 (while (and (re-search-forward "^\\(\\([-*][-*][-*] \\)\\(.+\\)\n\\([-+][-+][-+] \\)\\(.+\\)\\|\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\n\\*\\*\\* \\(.+\\) \\*\\*\\*\\*\\|@@ -\\(.+\\) \\+\\(.+\\) @@\\)$" nil t)
627 (< (point) end))
628 (combine-after-change-calls
629 (cond
630 ;; a file header
631 ((match-beginning 2) (replace-match "\\2\\5\n\\4\\3" nil))
632 ;; a context-diff hunk header
633 ((match-beginning 6)
634 (let ((pt-lines1 (match-beginning 6))
635 (lines1 (match-string 6)))
636 (replace-match "" nil nil nil 6)
637 (forward-line 1)
638 (let ((half1s (point)))
639 (while (looking-at "[-! \\][ \t]")
640 (when (= (char-after) ?-) (delete-char 1) (insert "+"))
641 (forward-line 1))
642 (let ((half1 (buffer-substring half1s (point))))
643 (delete-region half1s (point))
644 (unless (looking-at "^--- \\([0-9]+,-?[0-9]+\\) ----$")
645 (insert half1)
646 (error "Can't find matching `--- n1,n2 ----' line"))
647 (let ((str1 (match-string 1)))
648 (replace-match lines1 nil nil nil 1)
649 (forward-line 1)
650 (let ((half2s (point)))
651 (while (looking-at "[!+ \\][ \t]")
652 (when (= (char-after) ?+) (delete-char 1) (insert "-"))
653 (forward-line 1))
654 (let ((half2 (buffer-substring half2s (point))))
655 (delete-region half2s (point))
656 (insert half1)
657 (goto-char half1s)
658 (insert half2)))
659 (goto-char pt-lines1)
660 (insert str1))))))
661 ;; a unified-diff hunk header
662 ((match-beginning 7)
663 (replace-match "@@ -\\8 +\\7 @@" nil)
664 (forward-line 1)
665 (let ((c (char-after)) first last)
666 (while (case (setq c (char-after))
667 (?- (setq first (or first (point)))
668 (delete-char 1) (insert "+") t)
669 (?+ (setq last (or last (point)))
670 (delete-char 1) (insert "-") t)
671 (?\\ t)
672 (t (when (and first last (< first last))
673 (let ((str (buffer-substring first last)))
674 (save-excursion (delete-region first last))
675 (insert str)))
676 (setq first nil last nil)
677 (equal ?\ c)))
678 (forward-line 1))))))))))
679
680 (defun diff-fixup-modifs (start end)
681 "Fixup the hunk headers (in case the buffer was modified).
682 START and END are either taken from the region (if a prefix arg is given) or
683 else cover the whole bufer."
684 (interactive (if current-prefix-arg
685 (list (mark) (point))
686 (list (point-min) (point-max))))
687 (let ((inhibit-read-only t))
688 (save-excursion
689 (goto-char end) (diff-end-of-hunk)
690 (let ((plus 0) (minus 0) (space 0) (bang 0))
691 (while (and (= (forward-line -1) 0) (<= start (point)))
692 (if (not (looking-at "\\(@@ .+ @@\\|[-*][-*][-*] .+ [-*][-*][-*][-*]\\)$"))
693 (case (char-after)
694 (?\ (incf space))
695 (?+ (incf plus))
696 (?- (incf minus))
697 (?! (incf bang))
698 (?\\ nil)
699 (t (setq space 0 plus 0 minus 0 bang 0)))
700 (cond
701 ((looking-at "@@ -[0-9]+,\\([0-9]*\\) \\+[0-9]+,\\([0-9]*\\) @@$")
702 (let* ((old1 (match-string 1))
703 (old2 (match-string 2))
704 (new1 (number-to-string (+ space minus)))
705 (new2 (number-to-string (+ space plus))))
706 (unless (string= new2 old2) (replace-match new2 t t nil 2))
707 (unless (string= new1 old1) (replace-match new1 t t nil 1))))
708 ((looking-at "--- \\([0-9]+\\),\\([0-9]*\\) ----$")
709 (when (> (+ space bang plus) 0)
710 (let* ((old1 (match-string 1))
711 (old2 (match-string 2))
712 (new (number-to-string
713 (+ space bang plus -1 (string-to-number old1)))))
714 (unless (string= new old2) (replace-match new t t nil 2)))))
715 ((looking-at "\\*\\*\\* \\([0-9]+\\),\\(-?[0-9]*\\) \\*\\*\\*\\*$")
716 (when (> (+ space bang minus) 0)
717 (let* ((old (match-string 1))
718 (new (format
719 (concat "%0" (number-to-string (length old)) "d")
720 (+ space bang minus -1 (string-to-number old)))))
721 (unless (string= new old) (replace-match new t t nil 2))))))
722 (setq space 0 plus 0 minus 0 bang 0)))))))
723
724 ;;;;
725 ;;;; Hooks
726 ;;;;
727
728 (defun diff-write-contents-hooks ()
729 "Fixup hunk headers if necessary."
730 (if (buffer-modified-p) (diff-fixup-modifs (point-min) (point-max)))
731 nil)
732
733 ;; XEmacs doesn't seem to have this feature
734 (defvar undo-in-progress nil)
735
736 ;; It turns out that making changes in the buffer from within an
737 ;; *-change-function is asking for trouble, whereas making them
738 ;; from a post-command-hook doesn't pose much problems
739 (defvar diff-unhandled-changes nil)
740 (defun diff-after-change-function (beg end len)
741 "Remember to fixup the hunk header.
742 See `after-change-functions' for the meaning of BEG, END and LEN."
743 (when (and (not undo-in-progress) (not inhibit-read-only))
744 (if diff-unhandled-changes
745 (setq diff-unhandled-changes
746 (cons (min beg (car diff-unhandled-changes))
747 (max beg (cdr diff-unhandled-changes))))
748 (setq diff-unhandled-changes (cons beg end)))))
749
750 (defun diff-post-command-hook ()
751 "Fixup hunk headers if necessary."
752 (when (consp diff-unhandled-changes)
753 (ignore-errors
754 (save-excursion
755 (goto-char (car diff-unhandled-changes)) (diff-beginning-of-hunk)
756 (diff-fixup-modifs (point) (cdr diff-unhandled-changes))))
757 (setq diff-unhandled-changes nil)))
758
759 ;;;;
760 ;;;; The main function
761 ;;;;
762
763 ;;(autoload 'diff-mode "diff-mode" "Major mode for viewing context diffs." t)
764 ;;;###autoload
765 (defun diff-mode ()
766 "Major mode for viewing context diffs.
767 Supports unified and context diffs as well as (to a lesser extent) normal diffs.
768 When the buffer is read-only, the ESC prefix is not necessary.
769 This mode runs `diff-mode-hook'.
770 \\{diff-mode-map}"
771 (interactive)
772 (kill-all-local-variables)
773 (setq major-mode 'diff-mode)
774 (setq mode-name "Diff")
775 (use-local-map diff-mode-map)
776 (set (make-local-variable 'font-lock-defaults) diff-font-lock-defaults)
777 (set (make-local-variable 'outline-regexp) diff-outline-regexp)
778 ;; compile support
779 (set (make-local-variable 'compilation-file-regexp-alist)
780 diff-file-regexp-alist)
781 (set (make-local-variable 'compilation-error-regexp-alist)
782 diff-error-regexp-alist)
783 (when (string-match "\\.rej\\'" (or buffer-file-name ""))
784 (set (make-local-variable 'compilation-current-file)
785 (substring buffer-file-name 0 (match-beginning 0))))
786 (compilation-shell-minor-mode 1)
787 ;;
788 (setq buffer-read-only t)
789 (if (not diff-update-on-the-fly-flag)
790 (add-hook 'write-contents-hooks 'diff-write-contents-hooks)
791 (make-local-variable 'diff-unhandled-changes)
792 (make-local-hook 'after-change-functions)
793 (add-hook 'after-change-functions 'diff-after-change-function nil t)
794 (make-local-hook 'post-command-hook)
795 (add-hook 'post-command-hook 'diff-post-command-hook nil t))
796 ;; Neat trick from Dave Love to add more bindings in read-only mode:
797 (add-to-list (make-local-variable 'minor-mode-map-alist)
798 (cons 'buffer-read-only diff-mode-shared-map))
799 ;;
800 (run-hooks 'diff-mode-hook))
801
802 ;;;###autoload
803 (add-to-list 'auto-mode-alist '("\\.\\(diffs?\\|patch\\|rej\\)\\'" . diff-mode))
804
805 ;; provide the package
806 (provide 'diff-mode)
807
808 ;;; Change Log:
809 ;; diff-mode.el,v
810 ;; Revision 1.11 1999/10/09 23:38:29 monnier
811 ;; (diff-mode-load-hook): dropped.
812 ;; (auto-mode-alist): also catch *.diffs.
813 ;; (diff-find-file-name, diff-mode): add smarts to find the right file
814 ;; for *.rej files (that lack any file name indication).
815 ;;
816 ;; Revision 1.10 1999/09/30 15:32:11 monnier
817 ;; added support for "\ No newline at end of file".
818 ;;
819 ;; Revision 1.9 1999/09/15 00:01:13 monnier
820 ;; - added basic `compile' support.
821 ;; - have diff-kill-hunk call diff-kill-file if it's the only hunk.
822 ;; - diff-kill-file now tries to kill the leading garbage as well.
823 ;;
824 ;; Revision 1.8 1999/09/13 21:10:09 monnier
825 ;; - don't use CL in the autoloaded code
826 ;; - accept diffs using -T
827 ;;
828 ;; Revision 1.7 1999/09/05 20:53:03 monnier
829 ;; interface to ediff-patch
830 ;;
831 ;; Revision 1.6 1999/09/01 20:55:13 monnier
832 ;; (ediff=patch-file): add bindings to call ediff-patch.
833 ;; (diff-find-file-name): taken out of diff-goto-source.
834 ;; (diff-unified->context, diff-context->unified, diff-reverse-direction,
835 ;; diff-fixup-modifs): only use the region if a prefix arg is given.
836 ;;
837 ;; Revision 1.5 1999/08/31 19:18:52 monnier
838 ;; (diff-beginning-of-file, diff-prev-file): fixed wrong parenthesis.
839 ;;
840 ;; Revision 1.4 1999/08/31 13:01:44 monnier
841 ;; use `combine-after-change-calls' to minimize the slowdown of font-lock.
842 ;;
843
844 ;;; diff-mode.el ends here