diff-mode.el 90.4 KB
Newer Older
1
;;; diff-mode.el --- a mode for viewing/editing context diffs -*- lexical-binding: t -*-
2

Paul Eggert's avatar
Paul Eggert committed
3
;; Copyright (C) 1998-2016 Free Software Foundation, Inc.
4

Stefan Monnier's avatar
Stefan Monnier committed
5
;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
6
;; Keywords: convenience patch diff vc
7 8 9

;; This file is part of GNU Emacs.

10
;; GNU Emacs is free software: you can redistribute it and/or modify
11
;; it under the terms of the GNU General Public License as published by
12 13
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
14 15 16 17 18 19 20

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
21
;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
22 23 24

;;; Commentary:

25
;; Provides support for font-lock, outline, navigation
26 27 28
;; commands, editing and various conversions as well as jumping
;; to the corresponding source file.

29
;; Inspired by Pavel Machek's patch-mode.el (<pavel@@atrey.karlin.mff.cuni.cz>)
30
;; Some efforts were spent to have it somewhat compatible with XEmacs's
31 32 33 34
;; diff-mode as well as with compilation-minor-mode

;; Bugs:

35
;; - Reverse doesn't work with normal diffs.
36 37 38

;; Todo:

39 40
;; - Improve `diff-add-change-log-entries-other-window',
;;   it is very simplistic now.
Lute Kamstra's avatar
Lute Kamstra committed
41
;;
42 43 44 45 46 47 48
;; - Add a `delete-after-apply' so C-c C-a automatically deletes hunks.
;;   Also allow C-c C-a to delete already-applied hunks.
;;
;; - Try `diff <file> <hunk>' to try and fuzzily discover the source location
;;   of a hunk.  Show then the changes between <file> and <hunk> and make it
;;   possible to apply them to <file>, <hunk-src>, or <hunk-dst>.
;;   Or maybe just make it into a ".rej to diff3-markers converter".
49
;;   Maybe just use `wiggle' (by Neil Brown) to do it for us.
50
;;
51 52
;; - in diff-apply-hunk, strip context in replace-match to better
;;   preserve markers and spacing.
53
;; - Handle `diff -b' output in context->unified.
54 55

;;; Code:
Stefan Monnier's avatar
Stefan Monnier committed
56
(eval-when-compile (require 'cl-lib))
57

58 59
(defvar add-log-buffer-file-name-function)

60 61

(defgroup diff-mode ()
62
  "Major mode for viewing/editing diffs."
63
  :version "21.1"
64 65 66
  :group 'tools
  :group 'diff)

67
(defcustom diff-default-read-only nil
68 69 70 71
  "If non-nil, `diff-mode' buffers default to being read-only."
  :type 'boolean
  :group 'diff-mode)

72
(defcustom diff-jump-to-old-file nil
73
  "Non-nil means `diff-goto-source' jumps to the old file.
74
Else, it jumps to the new file."
75 76
  :type 'boolean
  :group 'diff-mode)
77

78
(defcustom diff-update-on-the-fly t
Lute Kamstra's avatar
Lute Kamstra committed
79
  "Non-nil means hunk headers are kept up-to-date on-the-fly.
80 81 82 83 84
When editing a diff file, the line numbers in the hunk headers
need to be kept consistent with the actual diff.  This can
either be done on the fly (but this sometimes interacts poorly with the
undo mechanism) or whenever the file is written (can be slow
when editing big diffs)."
85 86
  :type 'boolean
  :group 'diff-mode)
87

88
(defcustom diff-advance-after-apply-hunk t
Lute Kamstra's avatar
Lute Kamstra committed
89
  "Non-nil means `diff-apply-hunk' will move to the next hunk after applying."
90 91
  :type 'boolean
  :group 'diff-mode)
92

93 94 95
(defcustom diff-mode-hook nil
  "Run after setting up the `diff-mode' major mode."
  :type 'hook
96 97
  :options '(diff-delete-empty-files diff-make-unified)
  :group 'diff-mode)
98

99 100 101
(defvar diff-vc-backend nil
  "The VC backend that created the current Diff buffer, if any.")

102 103 104
(defvar diff-outline-regexp
  "\\([*+][*+][*+] [^0-9]\\|@@ ...\\|\\*\\*\\* [0-9].\\|--- [0-9]..\\)")

105
;;;;
106
;;;; keymap, menu, ...
107
;;;;
108

109
(easy-mmode-defmap diff-mode-shared-map
110
  '(("n" . diff-hunk-next)
111 112 113
    ("N" . diff-file-next)
    ("p" . diff-hunk-prev)
    ("P" . diff-file-prev)
114 115
    ("\t" . diff-hunk-next)
    ([backtab] . diff-hunk-prev)
116 117
    ("k" . diff-hunk-kill)
    ("K" . diff-file-kill)
118
    ("}" . diff-file-next)	; From compilation-minor-mode.
119
    ("{" . diff-file-prev)
120
    ("\C-m" . diff-goto-source)
121
    ([mouse-2] . diff-goto-source)
122
    ("W" . widen)
123
    ("o" . diff-goto-source)	; other-window
124 125
    ("A" . diff-ediff-patch)
    ("r" . diff-restrict-view)
126 127
    ("R" . diff-reverse-direction)
    ([remap undo] . diff-undo))
128 129
  "Basic keymap for `diff-mode', bound to various prefix keys."
  :inherit special-mode-map)
130

131
(easy-mmode-defmap diff-mode-map
132 133 134 135 136
  `(("\e" . ,(let ((map (make-sparse-keymap)))
               ;; We want to inherit most bindings from diff-mode-shared-map,
               ;; but not all since they may hide useful M-<foo> global
               ;; bindings when editing.
               (set-keymap-parent map diff-mode-shared-map)
137
               (dolist (key '("A" "r" "R" "g" "q" "W" "z"))
138 139
                 (define-key map key nil))
               map))
140 141
    ;; From compilation-minor-mode.
    ("\C-c\C-c" . diff-goto-source)
142 143
    ;; By analogy with the global C-x 4 a binding.
    ("\C-x4A" . diff-add-change-log-entries-other-window)
144
    ;; Misc operations.
145
    ("\C-c\C-a" . diff-apply-hunk)
146 147 148
    ("\C-c\C-e" . diff-ediff-patch)
    ("\C-c\C-n" . diff-restrict-view)
    ("\C-c\C-s" . diff-split-hunk)
149
    ("\C-c\C-t" . diff-test-hunk)
Stefan Monnier's avatar
Stefan Monnier committed
150
    ("\C-c\C-r" . diff-reverse-direction)
151
    ("\C-c\C-u" . diff-context->unified)
152 153
    ;; `d' because it duplicates the context :-(  --Stef
    ("\C-c\C-d" . diff-unified->context)
Stefan Monnier's avatar
Stefan Monnier committed
154 155
    ("\C-c\C-w" . diff-ignore-whitespace-hunk)
    ("\C-c\C-b" . diff-refine-hunk)  ;No reason for `b' :-(
156
    ("\C-c\C-f" . next-error-follow-minor-mode))
157 158 159 160 161
  "Keymap for `diff-mode'.  See also `diff-mode-shared-map'.")

(easy-menu-define diff-mode-menu diff-mode-map
  "Menu for `diff-mode'."
  '("Diff"
Dan Nicolaescu's avatar
Dan Nicolaescu committed
162 163 164 165 166 167 168 169
    ["Jump to Source"		diff-goto-source
     :help "Jump to the corresponding source line"]
    ["Apply hunk"		diff-apply-hunk
     :help "Apply the current hunk to the source file and go to the next"]
    ["Test applying hunk"	diff-test-hunk
     :help "See whether it's possible to apply the current hunk"]
    ["Apply diff with Ediff"	diff-ediff-patch
     :help "Call `ediff-patch-file' on the current buffer"]
170
    ["Create Change Log entries" diff-add-change-log-entries-other-window
Dan Nicolaescu's avatar
Dan Nicolaescu committed
171
     :help "Create ChangeLog entries for the changes in the diff buffer"]
Stefan Monnier's avatar
Stefan Monnier committed
172
    "-----"
Dan Nicolaescu's avatar
Dan Nicolaescu committed
173 174 175 176 177 178
    ["Reverse direction"	diff-reverse-direction
     :help "Reverse the direction of the diffs"]
    ["Context -> Unified"	diff-context->unified
     :help "Convert context diffs to unified diffs"]
    ["Unified -> Context"	diff-unified->context
     :help "Convert unified diffs to context diffs"]
179
    ;;["Fixup Headers"		diff-fixup-modifs	(not buffer-read-only)]
180
    ["Remove trailing whitespace" diff-delete-trailing-whitespace
181
     :help "Remove trailing whitespace problems introduced by the diff"]
182
    ["Show trailing whitespace" whitespace-mode
183
     :style toggle :selected (bound-and-true-p whitespace-mode)
184
     :help "Show trailing whitespace in modified lines"]
Stefan Monnier's avatar
Stefan Monnier committed
185
    "-----"
Dan Nicolaescu's avatar
Dan Nicolaescu committed
186 187 188 189 190 191 192 193 194 195 196
    ["Split hunk"		diff-split-hunk
     :active (diff-splittable-p)
     :help "Split the current (unified diff) hunk at point into two hunks"]
    ["Ignore whitespace changes" diff-ignore-whitespace-hunk
     :help "Re-diff the current hunk, ignoring whitespace differences"]
    ["Highlight fine changes"	diff-refine-hunk
     :help "Highlight changes of hunk at point at a finer granularity"]
    ["Kill current hunk"	diff-hunk-kill
     :help "Kill current hunk"]
    ["Kill current file's hunks" diff-file-kill
     :help "Kill all current file's hunks"]
Stefan Monnier's avatar
Stefan Monnier committed
197
    "-----"
Dan Nicolaescu's avatar
Dan Nicolaescu committed
198 199 200 201 202 203 204 205
    ["Previous Hunk"		diff-hunk-prev
     :help "Go to the previous count'th hunk"]
    ["Next Hunk"		diff-hunk-next
     :help "Go to the next count'th hunk"]
    ["Previous File"		diff-file-prev
     :help "Go to the previous count'th file"]
    ["Next File"		diff-file-next
     :help "Go to the next count'th file"]
206 207
    ))

208
(defcustom diff-minor-mode-prefix "\C-c="
209
  "Prefix key for `diff-minor-mode' commands."
210 211
  :type '(choice (string "\e") (string "C-c=") string)
  :group 'diff-mode)
212

213 214
(easy-mmode-defmap diff-minor-mode-map
  `((,diff-minor-mode-prefix . ,diff-mode-shared-map))
215 216
  "Keymap for `diff-minor-mode'.  See also `diff-mode-shared-map'.")

217
(define-minor-mode diff-auto-refine-mode
Chong Yidong's avatar
Chong Yidong committed
218 219 220 221 222 223 224 225 226 227
  "Toggle automatic diff hunk highlighting (Diff Auto Refine mode).
With a prefix argument ARG, enable Diff Auto Refine mode if ARG
is positive, and disable it otherwise.  If called from Lisp,
enable the mode if ARG is omitted or nil.

Diff Auto Refine mode is a buffer-local minor mode used with
`diff-mode'.  When enabled, Emacs automatically highlights
changes in detail as the user visits hunks.  When transitioning
from disabled to enabled, it tries to refine the current hunk, as
well."
228
  :group 'diff-mode :init-value t :lighter nil ;; " Auto-Refine"
229
  (when diff-auto-refine-mode
230
    (condition-case-unless-debug nil (diff-refine-hunk) (error nil))))
231

232
;;;;
233
;;;; font-lock support
234
;;;;
235

236
(defface diff-header
237
  '((((class color) (min-colors 88) (background light))
238
     :background "grey80")
239
    (((class color) (min-colors 88) (background dark))
240
     :background "grey45")
241
    (((class color))
242 243
     :foreground "blue1" :weight bold)
    (t :weight bold))
244 245
  "`diff-mode' face inherited by hunk and index header faces."
  :group 'diff-mode)
Stefan Monnier's avatar
Stefan Monnier committed
246

247
(defface diff-file-header
248
  '((((class color) (min-colors 88) (background light))
249
     :background "grey70" :weight bold)
250
    (((class color) (min-colors 88) (background dark))
251
     :background "grey60" :weight bold)
252
    (((class color))
253 254
     :foreground "cyan" :weight bold)
    (t :weight bold))			; :height 1.3
255 256
  "`diff-mode' face used to highlight file header lines."
  :group 'diff-mode)
257

258 259
(defface diff-index
  '((t :inherit diff-file-header))
260 261
  "`diff-mode' face used to highlight index header lines."
  :group 'diff-mode)
262

263 264
(defface diff-hunk-header
  '((t :inherit diff-header))
265 266
  "`diff-mode' face used to highlight hunk header lines."
  :group 'diff-mode)
267

268
(defface diff-removed
269 270 271 272 273 274 275 276
  '((default
     :inherit diff-changed)
    (((class color) (min-colors 88) (background light))
     :background "#ffdddd")
    (((class color) (min-colors 88) (background dark))
     :background "#553333")
    (((class color))
     :foreground "red"))
277 278
  "`diff-mode' face used to highlight removed lines."
  :group 'diff-mode)
279

280
(defface diff-added
281 282 283 284 285 286 287 288
  '((default
     :inherit diff-changed)
    (((class color) (min-colors 88) (background light))
     :background "#ddffdd")
    (((class color) (min-colors 88) (background dark))
     :background "#335533")
    (((class color))
     :foreground "green"))
289 290
  "`diff-mode' face used to highlight added lines."
  :group 'diff-mode)
291

292
(defface diff-changed
293
  '((t nil))
294
  "`diff-mode' face used to highlight changed lines."
Stefan Monnier's avatar
Stefan Monnier committed
295
  :version "25.1"
296
  :group 'diff-mode)
297

298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
(defface diff-indicator-removed
  '((t :inherit diff-removed))
  "`diff-mode' face used to highlight indicator of removed lines (-, <)."
  :group 'diff-mode
  :version "22.1")
(defvar diff-indicator-removed-face 'diff-indicator-removed)

(defface diff-indicator-added
  '((t :inherit diff-added))
  "`diff-mode' face used to highlight indicator of added lines (+, >)."
  :group 'diff-mode
  :version "22.1")
(defvar diff-indicator-added-face 'diff-indicator-added)

(defface diff-indicator-changed
  '((t :inherit diff-changed))
  "`diff-mode' face used to highlight indicator of changed lines."
  :group 'diff-mode
  :version "22.1")
(defvar diff-indicator-changed-face 'diff-indicator-changed)

319
(defface diff-function
320
  '((t :inherit diff-header))
321 322
  "`diff-mode' face used to highlight function names produced by \"diff -p\"."
  :group 'diff-mode)
323

324
(defface diff-context
325 326 327 328
  '((((class color grayscale) (min-colors 88) (background light))
     :foreground "#333333")
    (((class color grayscale) (min-colors 88) (background dark))
     :foreground "#dddddd"))
329
  "`diff-mode' face used to highlight context and other side-information."
Stefan Monnier's avatar
Stefan Monnier committed
330
  :version "25.1"
331
  :group 'diff-mode)
Stefan Monnier's avatar
Stefan Monnier committed
332

333 334
(defface diff-nonexistent
  '((t :inherit diff-file-header))
335 336
  "`diff-mode' face used to highlight nonexistent files in recursive diffs."
  :group 'diff-mode)
337

338 339
(defconst diff-yank-handler '(diff-yank-function))
(defun diff-yank-function (text)
340 341 342
  ;; FIXME: the yank-handler is now called separately on each piece of text
  ;; with a yank-handler property, so the next-single-property-change call
  ;; below will always return nil :-(   --stef
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
  (let ((mixed (next-single-property-change 0 'yank-handler text))
	(start (point)))
    ;; First insert the text.
    (insert text)
    ;; If the text does not include any diff markers and if we're not
    ;; yanking back into a diff-mode buffer, get rid of the prefixes.
    (unless (or mixed (derived-mode-p 'diff-mode))
      (undo-boundary)		; Just in case the user wanted the prefixes.
      (let ((re (save-excursion
		  (if (re-search-backward "^[><!][ \t]" start t)
		      (if (eq (char-after) ?!)
			  "^[!+- ][ \t]" "^[<>][ \t]")
		    "^[ <>!+-]"))))
	(save-excursion
	  (while (re-search-backward re start t)
	    (replace-match "" t t)))))))
359

360
(defconst diff-hunk-header-re-unified
361
  "^@@ -\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? \\+\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? @@")
362 363
(defconst diff-context-mid-hunk-header-re
  "--- \\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? ----$")
364

365 366 367
(defvar diff-use-changed-face (and (face-differs-from-default-p 'diff-changed)
				   (not (face-equal 'diff-changed 'diff-added))
				   (not (face-equal 'diff-changed 'diff-removed)))
368 369 370 371
  "If non-nil, use the face `diff-changed' for changed lines in context diffs.
Otherwise, use the face `diff-removed' for removed lines,
and the face `diff-added' for added lines.")

372
(defvar diff-font-lock-keywords
373
  `((,(concat "\\(" diff-hunk-header-re-unified "\\)\\(.*\\)$")
374
     (1 'diff-hunk-header) (6 'diff-function))
375
    ("^\\(\\*\\{15\\}\\)\\(.*\\)$"                        ;context
376 377 378 379 380
     (1 'diff-hunk-header) (2 'diff-function))
    ("^\\*\\*\\* .+ \\*\\*\\*\\*". 'diff-hunk-header) ;context
    (,diff-context-mid-hunk-header-re . 'diff-hunk-header) ;context
    ("^[0-9,]+[acd][0-9,]+$"     . 'diff-hunk-header) ;normal
    ("^---$"                     . 'diff-hunk-header) ;normal
381 382 383
    ;; For file headers, accept files with spaces, but be careful to rule
    ;; out false-positives when matching hunk headers.
    ("^\\(---\\|\\+\\+\\+\\|\\*\\*\\*\\) \\([^\t\n]+?\\)\\(?:\t.*\\| \\(\\*\\*\\*\\*\\|----\\)\\)?\n"
384 385
     (0 'diff-header)
     (2 (if (not (match-end 3)) 'diff-file-header) prepend))
386
    ("^\\([-<]\\)\\(.*\n\\)"
387
     (1 diff-indicator-removed-face) (2 'diff-removed))
388
    ("^\\([+>]\\)\\(.*\n\\)"
389
     (1 diff-indicator-added-face) (2 'diff-added))
390
    ("^\\(!\\)\\(.*\n\\)"
391 392 393
     (1 (if diff-use-changed-face
	    diff-indicator-changed-face
	  ;; Otherwise, search for `diff-context-mid-hunk-header-re' and
394 395
	  ;; if the line of context diff is above, use `diff-removed';
	  ;; if below, use `diff-added'.
396 397 398 399 400 401
	  (save-match-data
	    (let ((limit (save-excursion (diff-beginning-of-hunk))))
	      (if (save-excursion (re-search-backward diff-context-mid-hunk-header-re limit t))
		  diff-indicator-added-face
		diff-indicator-removed-face)))))
     (2 (if diff-use-changed-face
402
	    'diff-changed
403 404 405 406
	  ;; Otherwise, use the same method as above.
	  (save-match-data
	    (let ((limit (save-excursion (diff-beginning-of-hunk))))
	      (if (save-excursion (re-search-backward diff-context-mid-hunk-header-re limit t))
407 408
		  'diff-added
		'diff-removed))))))
409
    ("^\\(?:Index\\|revno\\): \\(.+\\).*\n"
410 411
     (0 'diff-header) (1 'diff-index prepend))
    ("^Only in .*\n" . 'diff-nonexistent)
412
    ("^\\(#\\)\\(.*\\)"
413 414
     (1 font-lock-comment-delimiter-face)
     (2 font-lock-comment-face))
415
    ("^[^-=+*!<>#].*\n" (0 'diff-context))))
416 417

(defconst diff-font-lock-defaults
418
  '(diff-font-lock-keywords t nil nil nil (font-lock-multiline . nil)))
419

420 421
(defvar diff-imenu-generic-expression
  ;; Prefer second name as first is most likely to be a backup or
422 423 424
  ;; version-control name.  The [\t\n] at the end of the unidiff pattern
  ;; catches Debian source diff files (which lack the trailing date).
  '((nil "\\+\\+\\+\\ \\([^\t\n]+\\)[\t\n]" 1) ; unidiffs
425 426
    (nil "^--- \\([^\t\n]+\\)\t.*\n\\*" 1))) ; context diffs

427
;;;;
428
;;;; Movement
429
;;;;
430

431 432 433 434 435 436 437 438
(defvar diff-valid-unified-empty-line t
  "If non-nil, empty lines are valid in unified diffs.
Some versions of diff replace all-blank context lines in unified format with
empty lines.  This makes the format less robust, but is tolerated.
See http://lists.gnu.org/archive/html/emacs-devel/2007-11/msg01990.html")

(defconst diff-hunk-header-re
  (concat "^\\(?:" diff-hunk-header-re-unified ".*\\|\\*\\{15\\}.*\n\\*\\*\\* .+ \\*\\*\\*\\*\\|[0-9]+\\(,[0-9]+\\)?[acd][0-9]+\\(,[0-9]+\\)?\\)$"))
439
(defconst diff-file-header-re (concat "^\\(--- .+\n\\+\\+\\+ \\|\\*\\*\\* .+\n--- \\|[^-+!<>0-9@* \n]\\).+\n" (substring diff-hunk-header-re 1)))
440 441
(defvar diff-narrowed-to nil)

Stefan Monnier's avatar
Stefan Monnier committed
442
(defun diff-hunk-style (&optional style)
443
  (when (looking-at diff-hunk-header-re)
Stefan Monnier's avatar
Stefan Monnier committed
444
    (setq style (cdr (assq (char-after) '((?@ . unified) (?* . context)))))
445
    (goto-char (match-end 0)))
Stefan Monnier's avatar
Stefan Monnier committed
446 447
  style)

448
(defun diff-end-of-hunk (&optional style donttrustheader)
Chong Yidong's avatar
Chong Yidong committed
449
  "Advance to the end of the current hunk, and return its position."
450 451 452 453 454 455
  (let (end)
    (when (looking-at diff-hunk-header-re)
      ;; Especially important for unified (because headers are ambiguous).
      (setq style (diff-hunk-style style))
      (goto-char (match-end 0))
      (when (and (not donttrustheader) (match-end 2))
456 457
        (let* ((nold (string-to-number (or (match-string 2) "1")))
               (nnew (string-to-number (or (match-string 4) "1")))
458
               (endold
459 460 461
                (save-excursion
                  (re-search-forward (if diff-valid-unified-empty-line
                                         "^[- \n]" "^[- ]")
462
                                     nil t nold)
463 464 465
                  (line-beginning-position
                   ;; Skip potential "\ No newline at end of file".
                   (if (looking-at ".*\n\\\\") 3 2))))
466 467 468 469 470 471 472
               (endnew
                ;; The hunk may end with a bunch of "+" lines, so the `end' is
                ;; then further than computed above.
                (save-excursion
                  (re-search-forward (if diff-valid-unified-empty-line
                                         "^[+ \n]" "^[+ ]")
                                     nil t nnew)
473 474 475
                  (line-beginning-position
                   ;; Skip potential "\ No newline at end of file".
                   (if (looking-at ".*\n\\\\") 3 2)))))
476
          (setq end (max endold endnew)))))
477 478 479
    ;; We may have a first evaluation of `end' thanks to the hunk header.
    (unless end
      (setq end (and (re-search-forward
Stefan Monnier's avatar
Stefan Monnier committed
480 481 482 483 484 485 486 487 488
                      (pcase style
                        (`unified
                         (concat (if diff-valid-unified-empty-line
                                     "^[^-+# \\\n]\\|" "^[^-+# \\]\\|")
                                 ;; A `unified' header is ambiguous.
                                 diff-file-header-re))
                        (`context "^[^-+#! \\]")
                        (`normal "^[^<>#\\]")
                        (_ "^[^-+#!<> \\]"))
489 490 491 492 493 494 495 496 497
                      nil t)
                     (match-beginning 0)))
      (when diff-valid-unified-empty-line
        ;; While empty lines may be valid inside hunks, they are also likely
        ;; to be unrelated to the hunk.
        (goto-char (or end (point-max)))
        (while (eq ?\n (char-before (1- (point))))
          (forward-char -1)
          (setq end (point)))))
498 499
    ;; The return value is used by easy-mmode-define-navigation.
    (goto-char (or end (point-max)))))
500

501
(defun diff-beginning-of-hunk (&optional try-harder)
Chong Yidong's avatar
Chong Yidong committed
502 503 504
  "Move back to the previous hunk beginning, and return its position.
If point is in a file header rather than a hunk, advance to the
next hunk if TRY-HARDER is non-nil; otherwise signal an error."
505
  (beginning-of-line)
Chong Yidong's avatar
Chong Yidong committed
506 507
  (if (looking-at diff-hunk-header-re)
      (point)
508 509 510
    (forward-line 1)
    (condition-case ()
	(re-search-backward diff-hunk-header-re)
511
      (error
Chong Yidong's avatar
Chong Yidong committed
512 513 514 515 516
       (unless try-harder
	 (error "Can't find the beginning of the hunk"))
       (diff-beginning-of-file-and-junk)
       (diff-hunk-next)
       (point)))))
517

518 519 520 521 522 523
(defun diff-unified-hunk-p ()
  (save-excursion
    (ignore-errors
      (diff-beginning-of-hunk)
      (looking-at "^@@"))))

524 525 526
(defun diff-beginning-of-file ()
  (beginning-of-line)
  (unless (looking-at diff-file-header-re)
527 528 529 530 531 532 533 534 535 536 537 538 539
    (let ((start (point))
          res)
      ;; diff-file-header-re may need to match up to 4 lines, so in case
      ;; we're inside the header, we need to move up to 3 lines forward.
      (forward-line 3)
      (if (and (setq res (re-search-backward diff-file-header-re nil t))
               ;; Maybe the 3 lines forward were too much and we matched
               ;; a file header after our starting point :-(
               (or (<= (point) start)
                   (setq res (re-search-backward diff-file-header-re nil t))))
          res
        (goto-char start)
        (error "Can't find the beginning of the file")))))
Lute Kamstra's avatar
Lute Kamstra committed
540

541 542

(defun diff-end-of-file ()
543
  (re-search-forward "^[-+#!<>0-9@* \\]" nil t)
544 545 546 547 548
  (re-search-forward (concat "^[^-+#!<>0-9@* \\]\\|" diff-file-header-re)
		     nil 'move)
  (if (match-beginning 1)
      (goto-char (match-beginning 1))
    (beginning-of-line)))
549

550 551
(defvar diff--auto-refine-data nil)

552 553
;; Define diff-{hunk,file}-{prev,next}
(easy-mmode-define-navigation
554
 diff--internal-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view
555
 (when diff-auto-refine-mode
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570
   (unless (prog1 diff--auto-refine-data
             (setq diff--auto-refine-data
                   (cons (current-buffer) (point-marker))))
     (run-at-time 0.0 nil
                  (lambda ()
                    (when diff--auto-refine-data
                      (let ((buffer (car diff--auto-refine-data))
                            (point (cdr diff--auto-refine-data)))
                        (setq diff--auto-refine-data nil)
                        (with-local-quit
                          (when (buffer-live-p buffer)
                            (with-current-buffer buffer
                              (save-excursion
                                (goto-char point)
                                (diff-refine-hunk))))))))))))
Stefan Monnier's avatar
Stefan Monnier committed
571

572
(easy-mmode-define-navigation
573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668
 diff--internal-file diff-file-header-re "file" diff-end-of-file)

(defun diff--wrap-navigation (skip-hunk-start
                              what orig
                              header-re goto-start-func count)
  "Wrap diff-{hunk,file}-{next,prev} for more intuitive behavior.
Override the default diff-{hunk,file}-{next,prev} implementation
by skipping any lines that are associated with this hunk/file but
precede the hunk-start marker.  For instance, a diff file could
contain

diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 923de9a..6b1c24f 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -590,6 +590,22 @@
.......

If a point is on 'index', then the point is considered to be in
this first hunk.  Move the point to the @@... marker before
executing the default diff-hunk-next/prev implementation to move
to the NEXT marker."
  (if (not skip-hunk-start)
      (funcall orig count)

    (let ((start (point)))
      (funcall goto-start-func)

      ;; Trap the error.
      (condition-case nil
          (funcall orig count)
        (error nil))

      (when (not (looking-at header-re))
        (goto-char start)
        (user-error (format "No %s" what))))))

;; These functions all take a skip-hunk-start argument which controls
;; whether we skip pre-hunk-start text or not.  In interactive uses we
;; always want to do this, but the simple behavior is still necessary
;; to, for example, avoid an infinite loop:
;;
;;   diff-hunk-next         calls
;;   diff--wrap-navigation  calls
;;   diff-bounds-of-hunk    calls
;;   diff-beginning-of-hunk calls
;;   diff-hunk-next
;;
;; Here the outer diff-hunk-next has skip-hunk-start set to t, but the
;; inner one does not, which breaks the loop.
(defun diff-hunk-prev (&optional count skip-hunk-start)
  "Go to the previous COUNT'th hunk."
  (interactive (list (prefix-numeric-value current-prefix-arg) t))
  (diff--wrap-navigation
   skip-hunk-start
   "prev hunk"
   'diff--internal-hunk-prev
   diff-hunk-header-re
   (lambda () (goto-char (car (diff-bounds-of-hunk))))
   count))

(defun diff-hunk-next (&optional count skip-hunk-start)
  "Go to the next COUNT'th hunk."
  (interactive (list (prefix-numeric-value current-prefix-arg) t))
  (diff--wrap-navigation
   skip-hunk-start
   "next hunk"
   'diff--internal-hunk-next
   diff-hunk-header-re
   (lambda () (goto-char (car (diff-bounds-of-hunk))))
   count))

(defun diff-file-prev (&optional count skip-hunk-start)
  "Go to the previous COUNT'th file."
  (interactive (list (prefix-numeric-value current-prefix-arg) t))
  (diff--wrap-navigation
   skip-hunk-start
   "prev file"
   'diff--internal-file-prev
   diff-file-header-re
   (lambda () (goto-char (car (diff-bounds-of-file))) (diff--internal-hunk-next))
   count))

(defun diff-file-next (&optional count skip-hunk-start)
  "Go to the next COUNT'th file."
  (interactive (list (prefix-numeric-value current-prefix-arg) t))
  (diff--wrap-navigation
   skip-hunk-start
   "next file"
   'diff--internal-file-next
   diff-file-header-re
   (lambda () (goto-char (car (diff-bounds-of-file))) (diff--internal-hunk-next))
   count))



669

Chong Yidong's avatar
Chong Yidong committed
670 671 672 673 674 675 676 677 678
(defun diff-bounds-of-hunk ()
  "Return the bounds of the diff hunk at point.
The return value is a list (BEG END), which are the hunk's start
and end positions.  Signal an error if no hunk is found.  If
point is in a file header, return the bounds of the next hunk."
  (save-excursion
    (let ((pos (point))
	  (beg (diff-beginning-of-hunk t))
	  (end (diff-end-of-hunk)))
679
      (cond ((> end pos)
Chong Yidong's avatar
Chong Yidong committed
680 681 682 683
	     (list beg end))
	    ;; If this hunk ends above POS, consider the next hunk.
	    ((re-search-forward diff-hunk-header-re nil t)
	     (list (match-beginning 0) (diff-end-of-hunk)))
684 685
	    ;; There's no next hunk, so just take the one we have.
	    (t (list beg end))))))
Chong Yidong's avatar
Chong Yidong committed
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702

(defun diff-bounds-of-file ()
  "Return the bounds of the file segment at point.
The return value is a list (BEG END), which are the segment's
start and end positions."
  (save-excursion
    (let ((pos (point))
	  (beg (progn (diff-beginning-of-file-and-junk)
		      (point))))
      (diff-end-of-file)
      ;; bzr puts a newline after the last hunk.
      (while (looking-at "^\n")
	(forward-char 1))
      (if (> pos (point))
	  (error "Not inside a file diff"))
      (list beg (point)))))

703 704 705 706
(defun diff-restrict-view (&optional arg)
  "Restrict the view to the current hunk.
If the prefix ARG is given, restrict the view to the current file instead."
  (interactive "P")
Chong Yidong's avatar
Chong Yidong committed
707 708 709
  (apply 'narrow-to-region
	 (if arg (diff-bounds-of-file) (diff-bounds-of-hunk)))
  (set (make-local-variable 'diff-narrowed-to) (if arg 'file 'hunk)))
710

711
(defun diff-hunk-kill ()
Chong Yidong's avatar
Chong Yidong committed
712
  "Kill the hunk at point."
713
  (interactive)
Chong Yidong's avatar
Chong Yidong committed
714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
  (let* ((hunk-bounds (diff-bounds-of-hunk))
	 (file-bounds (ignore-errors (diff-bounds-of-file)))
	 ;; If the current hunk is the only one for its file, kill the
	 ;; file header too.
	 (bounds (if (and file-bounds
			  (progn (goto-char (car file-bounds))
				 (= (progn (diff-hunk-next) (point))
				    (car hunk-bounds)))
			  (progn (goto-char (cadr hunk-bounds))
				 ;; bzr puts a newline after the last hunk.
				 (while (looking-at "^\n")
				   (forward-char 1))
				 (= (point) (cadr file-bounds))))
		     file-bounds
		   hunk-bounds))
729
	 (inhibit-read-only t))
Chong Yidong's avatar
Chong Yidong committed
730 731
    (apply 'kill-region bounds)
    (goto-char (car bounds))))
732

733 734
;; "index ", "old mode", "new mode", "new file mode" and
;; "deleted file mode" are output by git-diff.
Lute Kamstra's avatar
Lute Kamstra committed
735
(defconst diff-file-junk-re
Chong Yidong's avatar
Chong Yidong committed
736
  "diff \\|index \\|\\(?:deleted file\\|new\\(?: file\\)?\\|old\\) mode\\|=== modified file")
737

738 739 740 741
(defun diff-beginning-of-file-and-junk ()
  "Go to the beginning of file-related diff-info.
This is like `diff-beginning-of-file' except it tries to skip back over leading
data such as \"Index: ...\" and such."
742 743 744 745 746 747 748
  (let* ((orig (point))
         ;; Skip forward over what might be "leading junk" so as to get
         ;; closer to the actual diff.
         (_ (progn (beginning-of-line)
                   (while (looking-at diff-file-junk-re)
                     (forward-line 1))))
         (start (point))
749 750 751 752 753 754 755 756 757 758 759 760 761
         (prevfile (condition-case err
                       (save-excursion (diff-beginning-of-file) (point))
                     (error err)))
         (err (if (consp prevfile) prevfile))
         (nextfile (ignore-errors
                     (save-excursion
                       (goto-char start) (diff-file-next) (point))))
         ;; prevhunk is one of the limits.
         (prevhunk (save-excursion
                     (ignore-errors
                       (if (numberp prevfile) (goto-char prevfile))
                       (diff-hunk-prev) (point))))
         (previndex (save-excursion
762
                      (forward-line 1)  ;In case we're looking at "Index:".
763 764 765 766 767 768 769 770
                      (re-search-backward "^Index: " prevhunk t))))
    ;; If we're in the junk, we should use nextfile instead of prevfile.
    (if (and (numberp nextfile)
             (or (not (numberp prevfile))
                 (and previndex (> previndex prevfile))))
        (setq prevfile nextfile))
    (if (and previndex (numberp prevfile) (< previndex prevfile))
        (setq prevfile previndex))
771
    (if (numberp prevfile)
772 773 774 775 776 777 778 779
          (progn
            (goto-char prevfile)
            ;; Now skip backward over the leading junk we may have before the
            ;; diff itself.
            (while (save-excursion
                     (and (zerop (forward-line -1))
                          (looking-at diff-file-junk-re)))
              (forward-line -1)))
780 781
      ;; File starts *after* the starting point: we really weren't in
      ;; a file diff but elsewhere.
782
      (goto-char orig)
783
      (signal (car err) (cdr err)))))
Lute Kamstra's avatar
Lute Kamstra committed
784

785
(defun diff-file-kill ()
786 787
  "Kill current file's hunks."
  (interactive)
Chong Yidong's avatar
Chong Yidong committed
788 789
  (let ((inhibit-read-only t))
    (apply 'kill-region (diff-bounds-of-file))))
790

791 792 793 794 795 796 797 798 799 800 801 802 803 804 805
(defun diff-kill-junk ()
  "Kill spurious empty diffs."
  (interactive)
  (save-excursion
    (let ((inhibit-read-only t))
      (goto-char (point-min))
      (while (re-search-forward (concat "^\\(Index: .*\n\\)"
					"\\([^-+!* <>].*\n\\)*?"
					"\\(\\(Index:\\) \\|"
					diff-file-header-re "\\)")
				nil t)
	(delete-region (if (match-end 4) (match-beginning 0) (match-end 1))
		       (match-beginning 3))
	(beginning-of-line)))))

806 807 808 809
(defun diff-count-matches (re start end)
  (save-excursion
    (let ((n 0))
      (goto-char start)
Stefan Monnier's avatar
Stefan Monnier committed
810
      (while (re-search-forward re end t) (cl-incf n))
811 812
      n)))

813 814 815 816 817 818 819
(defun diff-splittable-p ()
  (save-excursion
    (beginning-of-line)
    (and (looking-at "^[-+ ]")
         (progn (forward-line -1) (looking-at "^[-+ ]"))
         (diff-unified-hunk-p))))

820 821 822 823 824
(defun diff-split-hunk ()
  "Split the current (unified diff) hunk at point into two hunks."
  (interactive)
  (beginning-of-line)
  (let ((pos (point))
Chong Yidong's avatar
Chong Yidong committed
825
	(start (diff-beginning-of-hunk)))
826
    (unless (looking-at diff-hunk-header-re-unified)
827 828 829
      (error "diff-split-hunk only works on unified context diffs"))
    (forward-line 1)
    (let* ((start1 (string-to-number (match-string 1)))
830
	   (start2 (string-to-number (match-string 3)))
831
	   (newstart1 (+ start1 (diff-count-matches "^[- \t]" (point) pos)))
832 833
	   (newstart2 (+ start2 (diff-count-matches "^[+ \t]" (point) pos)))
	   (inhibit-read-only t))
834 835 836 837 838 839
      (goto-char pos)
      ;; Hopefully the after-change-function will not screw us over.
      (insert "@@ -" (number-to-string newstart1) ",1 +"
	      (number-to-string newstart2) ",1 @@\n")
      ;; Fix the original hunk-header.
      (diff-fixup-modifs start pos))))
840

841

842 843 844 845
;;;;
;;;; jump to other buffers
;;;;

846
(defvar diff-remembered-files-alist nil)
847
(defvar diff-remembered-defdir nil)
848

849 850 851
(defun diff-filename-drop-dir (file)
  (when (string-match "/" file) (substring file (match-end 0))))

852 853 854
(defun diff-merge-strings (ancestor from to)
  "Merge the diff between ANCESTOR and FROM into TO.
Returns the merged string if successful or nil otherwise.
855
The strings are assumed not to contain any \"\\n\" (i.e. end of line).
856 857 858 859 860 861 862 863
If ANCESTOR = FROM, returns TO.
If ANCESTOR = TO, returns FROM.
The heuristic is simplistic and only really works for cases
like \(diff-merge-strings \"b/foo\" \"b/bar\" \"/a/c/foo\")."
  ;; Ideally, we want:
  ;;   AMB ANB CMD -> CND
  ;; but that's ambiguous if `foo' or `bar' is empty:
  ;; a/foo a/foo1 b/foo.c -> b/foo1.c but not 1b/foo.c or b/foo.c1
864
  (let ((str (concat ancestor "\n" from "\n" to)))
865
    (when (and (string-match (concat
866 867
			      "\\`\\(.*?\\)\\(.*\\)\\(.*\\)\n"
			      "\\1\\(.*\\)\\3\n"
868 869 870 871 872 873
			      "\\(.*\\(\\2\\).*\\)\\'") str)
	       (equal to (match-string 5 str)))
      (concat (substring str (match-beginning 5) (match-beginning 6))
	      (match-string 4 str)
	      (substring str (match-end 6) (match-end 5))))))

874 875 876 877 878 879 880 881
(defun diff-tell-file-name (old name)
  "Tell Emacs where the find the source file of the current hunk.
If the OLD prefix arg is passed, tell the file NAME of the old file."
  (interactive
   (let* ((old current-prefix-arg)
	  (fs (diff-hunk-file-names current-prefix-arg)))
     (unless fs (error "No file name to look for"))
     (list old (read-file-name (format "File for %s: " (car fs))
882
			       nil (diff-find-file-name old 'noprompt) t))))
883 884 885
  (let ((fs (diff-hunk-file-names old)))
    (unless fs (error "No file name to look for"))
    (push (cons fs name) diff-remembered-files-alist)))
886

887 888
(defun diff-hunk-file-names (&optional old)
  "Give the list of file names textually mentioned for the current hunk."
889 890 891 892
  (save-excursion
    (unless (looking-at diff-file-header-re)
      (or (ignore-errors (diff-beginning-of-file))
	  (re-search-forward diff-file-header-re nil t)))
893
    (let ((limit (save-excursion
894
		   (condition-case ()
895
		       (progn (diff-hunk-prev) (point))
896
		     (error (point-min)))))
897
	  (header-files
898
           ;; handle filenames with spaces;
899
           ;; cf. diff-font-lock-keywords / diff-file-header
900
	   (if (looking-at "[-*][-*][-*] \\([^\t\n]+\\).*\n[-+][-+][-+] \\([^\t\n]+\\)")
901 902
	       (list (if old (match-string 1) (match-string 2))
		     (if old (match-string 2) (match-string 1)))
903 904 905 906 907 908 909 910
	     (forward-line 1) nil)))
      (delq nil
	    (append
	     (when (and (not old)
			(save-excursion
			  (re-search-backward "^Index: \\(.+\\)" limit t)))
	       (list (match-string 1)))
	     header-files
911
             ;; this assumes that there are no spaces in filenames
912 913 914 915 916 917
	     (when (re-search-backward
		    "^diff \\(-\\S-+ +\\)*\\(\\S-+\\)\\( +\\(\\S-+\\)\\)?"
		    nil t)
	       (list (if old (match-string 2) (match-string 4))
		     (if old (match-string 4) (match-string 2)))))))))

918
(defun diff-find-file-name (&optional old noprompt prefix)
919
  "Return the file corresponding to the current patch.
920
Non-nil OLD means that we want the old file.
921
Non-nil NOPROMPT means to prefer returning nil than to prompt the user.
922
PREFIX is only used internally: don't use it."
923 924 925 926
  (unless (equal diff-remembered-defdir default-directory)
    ;; Flush diff-remembered-files-alist if the default-directory is changed.
    (set (make-local-variable 'diff-remembered-defdir) default-directory)
    (set (make-local-variable 'diff-remembered-files-alist) nil))
927 928 929 930 931
  (save-excursion
    (unless (looking-at diff-file-header-re)
      (or (ignore-errors (diff-beginning-of-file))
	  (re-search-forward diff-file-header-re nil t)))
    (let ((fs (diff-hunk-file-names old)))
932
      (if prefix (setq fs (mapcar (lambda (f) (concat prefix f)) fs)))
933
      (or
934 935 936
       ;; use any previously used preference
       (cdr (assoc fs diff-remembered-files-alist))
       ;; try to be clever and use previous choices as an inspiration
Stefan Monnier's avatar
Stefan Monnier committed
937
       (cl-dolist (rf diff-remembered-files-alist)
938
	 (let ((newfile (diff-merge-strings (caar rf) (car fs) (cdr rf))))
Stefan Monnier's avatar
Stefan Monnier committed
939
	   (if (and newfile (file-exists-p newfile)) (cl-return newfile))))
940 941
       ;; look for each file in turn.  If none found, try again but
       ;; ignoring the first level of directory, ...
Stefan Monnier's avatar
Stefan Monnier committed
942 943
       (cl-do* ((files fs (delq nil (mapcar 'diff-filename-drop-dir files)))
                (file nil nil))
944
	   ((or (null files)
Stefan Monnier's avatar
Stefan Monnier committed
945 946
		(setq file (cl-do* ((files files (cdr files))
                                    (file (car files) (car files)))
947 948 949
			       ;; Use file-regular-p to avoid
			       ;; /dev/null, directories, etc.
			       ((or (null file) (file-regular-p file))
950 951 952
				file))))
	    file))
       ;; <foo>.rej patches implicitly apply to <foo>
953 954 955
       (and (string-match "\\.rej\\'" (or buffer-file-name ""))
	    (let ((file (substring buffer-file-name 0 (match-beginning 0))))
	      (when (file-exists-p file) file)))
956 957 958 959 960 961
       ;; If we haven't found the file, maybe it's because we haven't paid
       ;; attention to the PCL-CVS hint.
       (and (not prefix)
	    (boundp 'cvs-pcl-cvs-dirchange-re)
	    (save-excursion
	      (re-search-backward cvs-pcl-cvs-dirchange-re nil t))
962
	    (diff-find-file-name old noprompt (match-string 1)))
963
       ;; if all else fails, ask the user
964
       (unless noprompt
Stefan Monnier's avatar
Stefan Monnier committed
965
         (let ((file (expand-file-name (or (car fs) ""))))
966 967 968 969
	   (setq file
		 (read-file-name (format "Use file %s: " file)
				 (file-name-directory file) file t
				 (file-name-nondirectory file)))
970 971 972
           (set (make-local-variable 'diff-remembered-files-alist)
                (cons (cons fs file) diff-remembered-files-alist))
           file))))))
973

974

975 976 977
(defun diff-ediff-patch ()
  "Call `ediff-patch-file' on the current buffer."
  (interactive)
978
  (condition-case nil
979
      (ediff-patch-file nil (current-buffer))
980 981
    (wrong-number-of-arguments (ediff-patch-file))))

982
;;;;
983
;;;; Conversion functions
984
;;;;
985 986 987 988 989 990 991

;;(defvar diff-inhibit-after-change nil
;;  "Non-nil means inhibit `diff-mode's after-change functions.")

(defun diff-unified->context (start end)
  "Convert unified diffs to context diffs.
START and END are either taken from the region (if a prefix arg is given) or
992
else cover the whole buffer."
993
  (interactive (if (or current-prefix-arg (use-region-p))
994
		   (list (region-beginning) (region-end))
995
		 (list (point-min) (point-max))))
996
  (unless (markerp end) (setq end (copy-marker end t)))
997 998 999 1000
  (let (;;(diff-inhibit-after-change t)
	(inhibit-read-only t))
    (save-excursion
      (goto-char start)
1001 1002 1003 1004
      (while (and (re-search-forward
                   (concat "^\\(\\(---\\) .+\n\\(\\+\\+\\+\\) .+\\|"
                           diff-hunk-header-re-unified ".*\\)$")
                   nil t)
1005 1006 1007
		  (< (point) end))
	(combine-after-change-calls
	  (if (match-beginning 2)
1008
	      ;; we matched a file header
1009 1010 1011 1012 1013 1014
	      (progn
		;; use reverse order to make sure the indices are kept valid
		(replace-match "---" t t nil 3)
		(replace-match "***" t t nil 2))
	    ;; we matched a hunk header
	    (let ((line1 (match-string 4))