aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/diff-mode.el
diff options
context:
space:
mode:
authorStefan Monnier2004-03-23 20:50:36 +0000
committerStefan Monnier2004-03-23 20:50:36 +0000
commit22cd1973d54a817eb0b0a8e9a9bf6e07323817fa (patch)
tree383324e2ddd359fcc39151443a5c1adc45426f47 /lisp/diff-mode.el
parent5bd8d87b8563f93070292d95144b03793a930104 (diff)
downloademacs-22cd1973d54a817eb0b0a8e9a9bf6e07323817fa.tar.gz
emacs-22cd1973d54a817eb0b0a8e9a9bf6e07323817fa.zip
(diff-default-read-only): Change default.
(diff-mode-hook): Make it a defcustom. Add some options. (diff-mode-map): Bind diff-refine-hook. (diff-yank-handler): New var. (diff-yank-function): New fun. (diff-font-lock-keywords): Use them. (diff-end-of-file): Handle case where file-header looks like diff text. (diff-hunk-kill): Adjust to "new" hunk-next behavior. (diff-file-kill): Delete a subsequent empty line, if applicable. (diff-hunk-file-names): New fun, extracted from diff-tell-file-name. (diff-find-file-name): Use it. (diff-tell-file-name): New command. (diff-mode): Be careful with view-mode. (diff-delete-if-empty, diff-delete-empty-files, diff-make-unified): New functions, for use in diff-mode-hook. (diff-find-source-location): Catch "regex too large" errors. (diff-apply-hunk, diff-test-hunk): Go to old or new file. (diff-refine-hunk): New command.
Diffstat (limited to 'lisp/diff-mode.el')
-rw-r--r--lisp/diff-mode.el225
1 files changed, 178 insertions, 47 deletions
diff --git a/lisp/diff-mode.el b/lisp/diff-mode.el
index 14be2e841a3..9ae6bbee7c0 100644
--- a/lisp/diff-mode.el
+++ b/lisp/diff-mode.el
@@ -38,20 +38,19 @@
38 38
39;; Todo: 39;; Todo:
40 40
41;; - Improve narrowed-view support. 41;; - Add a `delete-after-apply' so C-c C-a automatically deletes hunks.
42;; - re-enable (conditionally) the `compile' support after improving it to use 42;; Also allow C-c C-a to delete already-applied hunks.
43;; the same code as diff-goto-source. 43;;
44;; - Support for # comments in context->unified. 44;; - Try `diff <file> <hunk>' to try and fuzzily discover the source location
45;; - Allow diff.el to use diff-mode. 45;; of a hunk. Show then the changes between <file> and <hunk> and make it
46;; This mostly means ability to jump from half-hunk to half-hunk 46;; possible to apply them to <file>, <hunk-src>, or <hunk-dst>.
47;; in context (and normal) diffs and to jump to the corresponding 47;; Or maybe just make it into a ".rej to diff3-markers converter".
48;; (i.e. new or old) file. 48;;
49;; - Refine hunk on a word-by-word basis.
50;;
51;; - Use the new next-error-function to allow C-x `.
49;; - Handle `diff -b' output in context->unified. 52;; - Handle `diff -b' output in context->unified.
50 53
51;; Low priority:
52;; - Spice up the minor-mode with font-lock support.
53;; - Recognize pcl-cvs' special string for `cvs-execute-single'.
54
55;;; Code: 54;;; Code:
56 55
57(eval-when-compile (require 'cl)) 56(eval-when-compile (require 'cl))
@@ -63,7 +62,7 @@
63 :group 'tools 62 :group 'tools
64 :group 'diff) 63 :group 'diff)
65 64
66(defcustom diff-default-read-only t 65(defcustom diff-default-read-only nil
67 "If non-nil, `diff-mode' buffers default to being read-only." 66 "If non-nil, `diff-mode' buffers default to being read-only."
68 :type 'boolean 67 :type 'boolean
69 :group 'diff-mode) 68 :group 'diff-mode)
@@ -87,8 +86,10 @@ when editing big diffs)."
87 :type 'boolean) 86 :type 'boolean)
88 87
89 88
90(defvar diff-mode-hook nil 89(defcustom diff-mode-hook nil
91 "Run after setting up the `diff-mode' major mode.") 90 "Run after setting up the `diff-mode' major mode."
91 :type 'hook
92 :options '(diff-delete-empty-files diff-make-unified))
92 93
93(defvar diff-outline-regexp 94(defvar diff-outline-regexp
94 "\\([*+][*+][*+] [^0-9]\\|@@ ...\\|\\*\\*\\* [0-9].\\|--- [0-9]..\\)") 95 "\\([*+][*+][*+] [^0-9]\\|@@ ...\\|\\*\\*\\* [0-9].\\|--- [0-9]..\\)")
@@ -136,6 +137,7 @@ when editing big diffs)."
136 ;; From compilation-minor-mode. 137 ;; From compilation-minor-mode.
137 ("\C-c\C-c" . diff-goto-source) 138 ("\C-c\C-c" . diff-goto-source)
138 ;; Misc operations. 139 ;; Misc operations.
140 ("\C-c\C-r" . diff-refine-hunk)
139 ("\C-c\C-s" . diff-split-hunk) 141 ("\C-c\C-s" . diff-split-hunk)
140 ("\C-c\C-a" . diff-apply-hunk) 142 ("\C-c\C-a" . diff-apply-hunk)
141 ("\C-c\C-t" . diff-test-hunk)) 143 ("\C-c\C-t" . diff-test-hunk))
@@ -241,8 +243,28 @@ when editing big diffs)."
241 "`diff-mode' face used to highlight nonexistent files in recursive diffs.") 243 "`diff-mode' face used to highlight nonexistent files in recursive diffs.")
242(defvar diff-nonexistent-face 'diff-nonexistent-face) 244(defvar diff-nonexistent-face 'diff-nonexistent-face)
243 245
246(defconst diff-yank-handler '(diff-yank-function))
247(defun diff-yank-function (text)
248 (let ((mixed (next-single-property-change 0 'yank-handler text))
249 (start (point)))
250 ;; First insert the text.
251 (insert text)
252 ;; If the text does not include any diff markers and if we're not
253 ;; yanking back into a diff-mode buffer, get rid of the prefixes.
254 (unless (or mixed (derived-mode-p 'diff-mode))
255 (undo-boundary) ; Just in case the user wanted the prefixes.
256 (let ((re (save-excursion
257 (if (re-search-backward "^[><!][ \t]" start t)
258 (if (eq (char-after) ?!)
259 "^[!+- ][ \t]" "^[<>][ \t]")
260 "^[ <>!+-]"))))
261 (save-excursion
262 (while (re-search-backward re start t)
263 (replace-match "" t t)))))))
264
265
244(defvar diff-font-lock-keywords 266(defvar diff-font-lock-keywords
245 '(("^\\(@@ -[0-9,]+ \\+[0-9,]+ @@\\)\\(.*\\)$" ;unified 267 `(("^\\(@@ -[0-9,]+ \\+[0-9,]+ @@\\)\\(.*\\)$" ;unified
246 (1 diff-hunk-header-face) 268 (1 diff-hunk-header-face)
247 (2 diff-function-face)) 269 (2 diff-function-face))
248 ("^--- .+ ----$" . diff-hunk-header-face) ;context 270 ("^--- .+ ----$" . diff-hunk-header-face) ;context
@@ -253,13 +275,14 @@ when editing big diffs)."
253 ("^\\(---\\|\\+\\+\\+\\|\\*\\*\\*\\) \\(\\S-+\\)\\(.*[^*-]\\)?\n" 275 ("^\\(---\\|\\+\\+\\+\\|\\*\\*\\*\\) \\(\\S-+\\)\\(.*[^*-]\\)?\n"
254 (0 diff-header-face) (2 diff-file-header-face prepend)) 276 (0 diff-header-face) (2 diff-file-header-face prepend))
255 ("^[0-9,]+[acd][0-9,]+$" . diff-hunk-header-face) 277 ("^[0-9,]+[acd][0-9,]+$" . diff-hunk-header-face)
256 ("^!.*\n" . diff-changed-face) ;context 278 ("^!.*\n" (0 '(face diff-changed-face yank-handler ,diff-yank-handler)))
257 ("^[+>].*\n" . diff-added-face) 279 ("^[+>].*\n" (0 '(face diff-added-face yank-handler ,diff-yank-handler)))
258 ("^[-<].*\n" . diff-removed-face) 280 ("^[-<].*\n" (0 '(face diff-removed-face yank-handler ,diff-yank-handler)))
259 ("^Index: \\(.+\\).*\n" (0 diff-header-face) (1 diff-index-face prepend)) 281 ("^Index: \\(.+\\).*\n" (0 diff-header-face) (1 diff-index-face prepend))
260 ("^Only in .*\n" . diff-nonexistent-face) 282 ("^Only in .*\n" . diff-nonexistent-face)
261 ("^#.*" . font-lock-string-face) 283 ("^#.*" . font-lock-string-face)
262 ("^[^-=+*!<>].*\n" . diff-context-face))) 284 ("^[^-=+*!<>].*\n"
285 (0 '(face diff-context-face yank-handler ,diff-yank-handler)))))
263 286
264(defconst diff-font-lock-defaults 287(defconst diff-font-lock-defaults
265 '(diff-font-lock-keywords t nil nil nil (font-lock-multiline . nil))) 288 '(diff-font-lock-keywords t nil nil nil (font-lock-multiline . nil)))
@@ -311,8 +334,11 @@ when editing big diffs)."
311 334
312(defun diff-end-of-file () 335(defun diff-end-of-file ()
313 (re-search-forward "^[-+#!<>0-9@* \\]" nil t) 336 (re-search-forward "^[-+#!<>0-9@* \\]" nil t)
314 (re-search-forward "^[^-+#!<>0-9@* \\]" nil 'move) 337 (re-search-forward (concat "^[^-+#!<>0-9@* \\]\\|" diff-file-header-re)
315 (beginning-of-line)) 338 nil 'move)
339 (if (match-beginning 1)
340 (goto-char (match-beginning 1))
341 (beginning-of-line)))
316 342
317;; Define diff-{hunk,file}-{prev,next} 343;; Define diff-{hunk,file}-{prev,next}
318(easy-mmode-define-navigation 344(easy-mmode-define-navigation
@@ -337,7 +363,8 @@ If the prefix ARG is given, restrict the view to the current file instead."
337 (interactive) 363 (interactive)
338 (diff-beginning-of-hunk) 364 (diff-beginning-of-hunk)
339 (let* ((start (point)) 365 (let* ((start (point))
340 (nexthunk (ignore-errors (diff-hunk-next) (point))) 366 (nexthunk (when (re-search-forward diff-hunk-header-re nil t)
367 (match-beginning 0)))
341 (firsthunk (ignore-errors 368 (firsthunk (ignore-errors
342 (goto-char start) 369 (goto-char start)
343 (diff-beginning-of-file) (diff-hunk-next) (point))) 370 (diff-beginning-of-file) (diff-hunk-next) (point)))
@@ -363,6 +390,7 @@ If the prefix ARG is given, restrict the view to the current file instead."
363 (re-search-backward "^Index: " prevhunk t)))) 390 (re-search-backward "^Index: " prevhunk t))))
364 (when index (setq start index)) 391 (when index (setq start index))
365 (diff-end-of-file) 392 (diff-end-of-file)
393 (if (looking-at "^\n") (forward-char 1)) ;`tla' generates such diffs.
366 (kill-region start (point)))) 394 (kill-region start (point))))
367 395
368(defun diff-kill-junk () 396(defun diff-kill-junk ()
@@ -439,31 +467,55 @@ like \(diff-merge-strings \"b/foo\" \"b/bar\" \"/a/c/foo\")."
439 (match-string 4 str) 467 (match-string 4 str)
440 (substring str (match-end 6) (match-end 5)))))) 468 (substring str (match-end 6) (match-end 5))))))
441 469
442(defun diff-find-file-name (&optional old) 470(defun diff-tell-file-name (old name)
443 "Return the file corresponding to the current patch. 471 "Tell Emacs where the find the source file of the current hunk.
444Non-nil OLD means that we want the old file." 472If the OLD prefix arg is passed, tell the file NAME of the old file."
473 (interactive
474 (let* ((old current-prefix-arg)
475 (fs (diff-hunk-file-names current-prefix-arg)))
476 (unless fs (error "No file name to look for"))
477 (list old (read-file-name (format "File for %s: " (car fs))
478 nil (diff-find-file-name old) t))))
479 (let ((fs (diff-hunk-file-names old)))
480 (unless fs (error "No file name to look for"))
481 (push (cons fs name) diff-remembered-files-alist)))
482
483(defun diff-hunk-file-names (&optional old)
484 "Give the list of file names textually mentioned for the current hunk."
445 (save-excursion 485 (save-excursion
446 (unless (looking-at diff-file-header-re) 486 (unless (looking-at diff-file-header-re)
447 (or (ignore-errors (diff-beginning-of-file)) 487 (or (ignore-errors (diff-beginning-of-file))
448 (re-search-forward diff-file-header-re nil t))) 488 (re-search-forward diff-file-header-re nil t)))
449 (let* ((limit (save-excursion 489 (let ((limit (save-excursion
450 (condition-case () 490 (condition-case ()
451 (progn (diff-hunk-prev) (point)) 491 (progn (diff-hunk-prev) (point))
452 (error (point-min))))) 492 (error (point-min)))))
453 (header-files 493 (header-files
454 (if (looking-at "[-*][-*][-*] \\(\\S-+\\)\\(\\s-.*\\)?\n[-+][-+][-+] \\(\\S-+\\)") 494 (if (looking-at "[-*][-*][-*] \\(\\S-+\\)\\(\\s-.*\\)?\n[-+][-+][-+] \\(\\S-+\\)")
455 (list (if old (match-string 1) (match-string 3)) 495 (list (if old (match-string 1) (match-string 3))
456 (if old (match-string 3) (match-string 1))) 496 (if old (match-string 3) (match-string 1)))
457 (forward-line 1) nil)) 497 (forward-line 1) nil)))
458 (fs (append 498 (delq nil
459 (when (save-excursion 499 (append
460 (re-search-backward "^Index: \\(.+\\)" limit t)) 500 (when (and (not old)
461 (list (match-string 1))) 501 (save-excursion
462 header-files 502 (re-search-backward "^Index: \\(.+\\)" limit t)))
463 (when (re-search-backward "^diff \\(-\\S-+ +\\)*\\(\\S-+\\)\\( +\\(\\S-+\\)\\)?" nil t) 503 (list (match-string 1)))
464 (list (if old (match-string 2) (match-string 4)) 504 header-files
465 (if old (match-string 4) (match-string 2)))))) 505 (when (re-search-backward
466 (fs (delq nil fs))) 506 "^diff \\(-\\S-+ +\\)*\\(\\S-+\\)\\( +\\(\\S-+\\)\\)?"
507 nil t)
508 (list (if old (match-string 2) (match-string 4))
509 (if old (match-string 4) (match-string 2)))))))))
510
511(defun diff-find-file-name (&optional old)
512 "Return the file corresponding to the current patch.
513Non-nil OLD means that we want the old file."
514 (save-excursion
515 (unless (looking-at diff-file-header-re)
516 (or (ignore-errors (diff-beginning-of-file))
517 (re-search-forward diff-file-header-re nil t)))
518 (let ((fs (diff-hunk-file-names old)))
467 (or 519 (or
468 ;; use any previously used preference 520 ;; use any previously used preference
469 (cdr (assoc fs diff-remembered-files-alist)) 521 (cdr (assoc fs diff-remembered-files-alist))
@@ -876,8 +928,14 @@ a diff with \\[diff-reverse-direction]."
876 (add-hook 'after-change-functions 'diff-after-change-function nil t) 928 (add-hook 'after-change-functions 'diff-after-change-function nil t)
877 (add-hook 'post-command-hook 'diff-post-command-hook nil t)) 929 (add-hook 'post-command-hook 'diff-post-command-hook nil t))
878 ;; Neat trick from Dave Love to add more bindings in read-only mode: 930 ;; Neat trick from Dave Love to add more bindings in read-only mode:
879 (add-to-list 'minor-mode-overriding-map-alist 931 (let ((ro-bind (cons 'buffer-read-only diff-mode-shared-map)))
880 (cons 'buffer-read-only diff-mode-shared-map)) 932 (add-to-list 'minor-mode-overriding-map-alist ro-bind)
933 ;; Turn off this little trick in case the buffer is put in view-mode.
934 (add-hook 'view-mode-hook
935 `(lambda ()
936 (setq minor-mode-overriding-map-alist
937 (delq ,ro-bind minor-mode-overriding-map-alist)))
938 nil t))
881 ;; add-log support 939 ;; add-log support
882 (set (make-local-variable 'add-log-current-defun-function) 940 (set (make-local-variable 'add-log-current-defun-function)
883 'diff-current-defun) 941 'diff-current-defun)
@@ -897,6 +955,29 @@ a diff with \\[diff-reverse-direction]."
897 (add-hook 'after-change-functions 'diff-after-change-function nil t) 955 (add-hook 'after-change-functions 'diff-after-change-function nil t)
898 (add-hook 'post-command-hook 'diff-post-command-hook nil t))) 956 (add-hook 'post-command-hook 'diff-post-command-hook nil t)))
899 957
958;;; Handy hook functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
959
960(defun diff-delete-if-empty ()
961 ;; An empty diff file means there's no more diffs to integrate, so we
962 ;; can just remove the file altogether. Very handy for .rej files if we
963 ;; remove hunks as we apply them.
964 (when (and buffer-file-name
965 (eq 0 (nth 7 (file-attributes buffer-file-name))))
966 (delete-file buffer-file-name)))
967
968(defun diff-delete-empty-files ()
969 "Arrange for empty diff files to be removed."
970 (add-hook 'after-save-hook 'diff-delete-if-empty nil t))
971
972(defun diff-make-unified ()
973 "Turn context diffs into unified diffs if applicable."
974 (if (save-excursion
975 (goto-char (point-min))
976 (looking-at "\\*\\*\\* "))
977 (let ((mod (buffer-modified-p)))
978 (unwind-protect
979 (diff-context->unified (point-min) (point-max))
980 (restore-buffer-modified-p mod)))))
900 981
901;;; 982;;;
902;;; Misc operations that have proved useful at some point. 983;;; Misc operations that have proved useful at some point.
@@ -1060,12 +1141,17 @@ SWITCHED is non-nil if the patch is already applied."
1060 (goto-line (string-to-number line)) 1141 (goto-line (string-to-number line))
1061 (let* ((orig-pos (point)) 1142 (let* ((orig-pos (point))
1062 (switched nil) 1143 (switched nil)
1144 ;; FIXME: Check for case where both OLD and NEW are found.
1063 (pos (or (diff-find-text (car old)) 1145 (pos (or (diff-find-text (car old))
1064 (progn (setq switched t) (diff-find-text (car new))) 1146 (progn (setq switched t) (diff-find-text (car new)))
1065 (progn (setq switched nil) 1147 (progn (setq switched nil)
1066 (diff-find-approx-text (car old))) 1148 (condition-case nil
1149 (diff-find-approx-text (car old))
1150 (invalid-regexp nil))) ;Regex too big.
1067 (progn (setq switched t) 1151 (progn (setq switched t)
1068 (diff-find-approx-text (car new))) 1152 (condition-case nil
1153 (diff-find-approx-text (car new))
1154 (invalid-regexp nil))) ;Regex too big.
1069 (progn (setq switched nil) nil)))) 1155 (progn (setq switched nil) nil))))
1070 (nconc 1156 (nconc
1071 (list buf) 1157 (list buf)
@@ -1096,7 +1182,8 @@ the value of this variable when given an appropriate prefix argument).
1096With a prefix argument, REVERSE the hunk." 1182With a prefix argument, REVERSE the hunk."
1097 (interactive "P") 1183 (interactive "P")
1098 (destructuring-bind (buf line-offset pos old new &optional switched) 1184 (destructuring-bind (buf line-offset pos old new &optional switched)
1099 (diff-find-source-location nil reverse) 1185 ;; If REVERSE go to the new file, otherwise go to the old.
1186 (diff-find-source-location (not reverse) reverse)
1100 (cond 1187 (cond
1101 ((null line-offset) 1188 ((null line-offset)
1102 (error "Can't find the text to patch")) 1189 (error "Can't find the text to patch"))
@@ -1128,7 +1215,8 @@ With a prefix argument, REVERSE the hunk."
1128With a prefix argument, try to REVERSE the hunk." 1215With a prefix argument, try to REVERSE the hunk."
1129 (interactive "P") 1216 (interactive "P")
1130 (destructuring-bind (buf line-offset pos src dst &optional switched) 1217 (destructuring-bind (buf line-offset pos src dst &optional switched)
1131 (diff-find-source-location nil reverse) 1218 ;; If REVERSE go to the new file, otherwise go to the old.
1219 (diff-find-source-location (not reverse) reverse)
1132 (set-window-point (display-buffer buf) (+ (car pos) (cdr src))) 1220 (set-window-point (display-buffer buf) (+ (car pos) (cdr src)))
1133 (diff-hunk-status-msg line-offset (diff-xor reverse switched) t))) 1221 (diff-hunk-status-msg line-offset (diff-xor reverse switched) t)))
1134 1222
@@ -1173,6 +1261,49 @@ For use in `add-log-current-defun-function'."
1173 (goto-char (+ (car pos) (cdr src))) 1261 (goto-char (+ (car pos) (cdr src)))
1174 (add-log-current-defun)))))) 1262 (add-log-current-defun))))))
1175 1263
1264(defun diff-refine-hunk ()
1265 "Refine the current hunk by ignoring space differences."
1266 (interactive)
1267 (let* ((char-offset (- (point) (progn (diff-beginning-of-hunk) (point))))
1268 (opts (case (char-after) (?@ "-bu") (?* "-bc") (t "-b")))
1269 (line-nb (and (or (looking-at "[^0-9]+\\([0-9]+\\)")
1270 (error "Can't find line number"))
1271 (string-to-number (match-string 1))))
1272 (hunk (delete-and-extract-region
1273 (point) (save-excursion (diff-end-of-hunk) (point))))
1274 (lead (make-string (1- line-nb) ?\n)) ;Line nums start at 1.
1275 (file1 (make-temp-file "diff1"))
1276 (file2 (make-temp-file "diff2"))
1277 (coding-system-for-read buffer-file-coding-system)
1278 old new)
1279 (unwind-protect
1280 (save-excursion
1281 (setq old (diff-hunk-text hunk nil char-offset))
1282 (setq new (diff-hunk-text hunk t char-offset))
1283 (write-region (concat lead (car old)) nil file1 nil 'nomessage)
1284 (write-region (concat lead (car new)) nil file2 nil 'nomessage)
1285 (with-temp-buffer
1286 (let ((status
1287 (call-process diff-command nil t nil
1288 opts file1 file2)))
1289 (case status
1290 (0 nil) ;Nothing to reformat.
1291 (1 (goto-char (point-min))
1292 ;; Remove the file-header.
1293 (when (re-search-forward diff-hunk-header-re nil t)
1294 (delete-region (point-min) (match-beginning 0))))
1295 (t (goto-char (point-max))
1296 (unless (bolp) (insert "\n"))
1297 (insert hunk)))
1298 (setq hunk (buffer-string))
1299 (unless (memq status '(0 1))
1300 (error "Diff returned: %s" status)))))
1301 ;; Whatever happens, put back some equivalent text: either the new
1302 ;; one or the original one in case some error happened.
1303 (insert hunk)
1304 (delete-file file1)
1305 (delete-file file2))))
1306
1176;; provide the package 1307;; provide the package
1177(provide 'diff-mode) 1308(provide 'diff-mode)
1178 1309