;;; diff-mode.el --- A mode for viewing/editing context diffs
-;; Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc.
+;; Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc.
;; Author: Stefan Monnier <monnier@cs.yale.edu>
;; Keywords: patch diff
-;; Revision: $Id: diff-mode.el,v 1.32 2000/10/18 08:50:39 eliz Exp $
;; This file is part of GNU Emacs.
;;; Commentary:
-;; Provides support for font-lock patterns, outline-regexps, navigation
+;; Provides support for font-lock, outline, navigation
;; commands, editing and various conversions as well as jumping
;; to the corresponding source file.
;; Todo:
-;; - Spice up the minor-mode with font-lock support.
;; - Improve narrowed-view support.
-;; - Improve the `compile' support (?).
-;; - Recognize pcl-cvs' special string for `cvs-execute-single'.
+;; - re-enable (conditionally) the `compile' support after improving it to use
+;; the same code as diff-goto-source.
;; - Support for # comments in context->unified.
;; - Do a fuzzy search in diff-goto-source.
;; - Allow diff.el to use diff-mode.
;; (i.e. new or old) file.
;; - Handle `diff -b' output in context->unified.
+;; Low priority:
+;; - Spice up the minor-mode with font-lock support.
+;; - Recognize pcl-cvs' special string for `cvs-execute-single'.
+
;;; Code:
(eval-when-compile (require 'cl))
:group 'tools
:group 'diff)
-(defcustom diff-jump-to-old-file-flag nil
+(defcustom diff-default-read-only t
+ "If non-nil, `diff-mode' buffers default to being read-only."
+ :type 'boolean
+ :group 'diff-mode)
+
+(defcustom diff-jump-to-old-file nil
"*Non-nil means `diff-goto-source' jumps to the old file.
Else, it jumps to the new file."
:group 'diff-mode
:type '(boolean))
-(defcustom diff-update-on-the-fly-flag t
+(defcustom diff-update-on-the-fly t
"*Non-nil means hunk headers are kept up-to-date on-the-fly.
When editing a diff file, the line numbers in the hunk headers
need to be kept consistent with the actual diff. This can
(defvar diff-outline-regexp
"\\([*+][*+][*+] [^0-9]\\|@@ ...\\|\\*\\*\\* [0-9].\\|--- [0-9]..\\)")
-;;;;
+;;;;
;;;; keymap, menu, ...
-;;;;
+;;;;
(easy-mmode-defmap diff-mode-shared-map
'(;; From Pavel Machek's patch-mode.
;; From compilation-minor-mode.
("\C-c\C-c" . diff-goto-source)
;; Misc operations.
+ ("\C-c\C-s" . diff-split-hunk)
("\C-c\C-a" . diff-apply-hunk)
("\C-c\C-t" . diff-test-hunk))
"Keymap for `diff-mode'. See also `diff-mode-shared-map'.")
"Menu for `diff-mode'."
'("Diff"
["Jump to Source" diff-goto-source t]
- ["Apply with Ediff" diff-ediff-patch t]
+ ["Apply hunk" diff-apply-hunk t]
+ ["Apply diff with Ediff" diff-ediff-patch t]
["-----" nil nil]
["Reverse direction" diff-reverse-direction t]
["Context -> Unified" diff-context->unified t]
"Keymap for `diff-minor-mode'. See also `diff-mode-shared-map'.")
-;;;;
+;;;;
;;;; font-lock support
-;;;;
+;;;;
(defface diff-header-face
'((((type tty pc) (class color) (background light))
:group 'diff-mode)
(defvar diff-context-face 'diff-context-face)
+(defface diff-nonexistent-face
+ '((t (:inherit diff-file-header-face)))
+ "`diff-mode' face used to highlight nonexistent files in recursive diffs."
+ :group 'diff-mode)
+(defvar diff-nonexistent-face 'diff-nonexistent-face)
+
(defvar diff-font-lock-keywords
'(("^\\(@@ -[0-9,]+ \\+[0-9,]+ @@\\)\\(.*\\)$" ;unified
(1 diff-hunk-header-face)
(2 diff-function-face))
- ("^--- .+ ----$" ;context
- . diff-hunk-header-face)
- ("\\(\\*\\{15\\}\\)\\(.*\\)$" ;context
+ ("^--- .+ ----$" . diff-hunk-header-face) ;context
+ ("^\\(\\*\\{15\\}\\)\\(.*\\)$" ;context
(1 diff-hunk-header-face)
(2 diff-function-face))
("^\\*\\*\\* .+ \\*\\*\\*\\*". diff-hunk-header-face) ;context
("^[+>].*\n" . diff-added-face)
("^[-<].*\n" . diff-removed-face)
("^Index: \\(.+\\).*\n" (0 diff-header-face) (1 diff-index-face prepend))
+ ("^Only in .*\n" . diff-nonexistent-face)
("^#.*" . font-lock-string-face)
("^[^-=+*!<>].*\n" . diff-context-face)))
(defvar diff-imenu-generic-expression
;; Prefer second name as first is most likely to be a backup or
- ;; version-control name.
- '((nil "\\+\\+\\+\\ \\([^\t\n]+\\)\t" 1) ; unidiffs
+ ;; 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
(nil "^--- \\([^\t\n]+\\)\t.*\n\\*" 1))) ; context diffs
;;;;
("--- \\([0-9]+\\),[0-9]+ ----" nil 1)
("\\([0-9]+\\)\\(,[0-9]+\\)?[adc]\\([0-9]+\\)" nil 3)))
-;;;;
+;;;;
;;;; Movement
-;;;;
+;;;;
(defconst diff-hunk-header-re "^\\(@@ -[0-9,]+ \\+[0-9,]+ @@.*\\|\\*\\{15\\}.*\n\\*\\*\\* .+ \\*\\*\\*\\*\\|[0-9]+\\(,[0-9]+\\)?[acd][0-9]+\\(,[0-9]+\\)?\\)$")
(defconst diff-file-header-re (concat "^\\(--- .+\n\\+\\+\\+\\|\\*\\*\\* .+\n---\\|[^-+!<>0-9@* ]\\).+\n" (substring diff-hunk-header-re 1)))
(match-beginning 3))
(beginning-of-line)))))
+(defun diff-count-matches (re start end)
+ (save-excursion
+ (let ((n 0))
+ (goto-char start)
+ (while (re-search-forward re end t) (incf n))
+ n)))
+
+(defun diff-split-hunk ()
+ "Split the current (unified diff) hunk at point into two hunks."
+ (interactive)
+ (beginning-of-line)
+ (let ((pos (point))
+ (start (progn (diff-beginning-of-hunk) (point))))
+ (unless (looking-at "@@ -\\([0-9]+\\),[0-9]+ \\+\\([0-9]+\\),[0-9]+ @@")
+ (error "diff-split-hunk only works on unified context diffs"))
+ (forward-line 1)
+ (let* ((start1 (string-to-number (match-string 1)))
+ (start2 (string-to-number (match-string 2)))
+ (newstart1 (+ start1 (diff-count-matches "^[- \t]" (point) pos)))
+ (newstart2 (+ start2 (diff-count-matches "^[+ \t]" (point) pos))))
+ (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))))
+
+
;;;;
;;;; jump to other buffers
;;;;
;;;###autoload
(define-derived-mode diff-mode fundamental-mode "Diff"
"Major mode for viewing/editing context diffs.
-Supports unified and context diffs as well as (to a lesser extent) normal diffs.
-When the buffer is read-only, the ESC prefix is not necessary.
-This mode runs `diff-mode-hook'.
-\\{diff-mode-map}"
+Supports unified and context diffs as well as (to a lesser extent)
+normal diffs.
+When the buffer is read-only, the ESC prefix is not necessary."
(set (make-local-variable 'font-lock-defaults) diff-font-lock-defaults)
(set (make-local-variable 'outline-regexp) diff-outline-regexp)
(set (make-local-variable 'imenu-generic-expression)
;; (set (make-local-variable 'paragraph-separate) paragraph-start)
;; (set (make-local-variable 'page-delimiter) "--- [^\t]+\t")
;; compile support
- (set (make-local-variable 'compilation-file-regexp-alist)
- diff-file-regexp-alist)
- (set (make-local-variable 'compilation-error-regexp-alist)
- diff-error-regexp-alist)
- (when (string-match "\\.rej\\'" (or buffer-file-name ""))
- (set (make-local-variable 'compilation-current-file)
- (substring buffer-file-name 0 (match-beginning 0))))
- (compilation-shell-minor-mode 1)
+
+ ;;;; compile support is not good enough yet. Also it can be annoying
+ ;; and should thus only be enabled conditionally.
+ ;; (set (make-local-variable 'compilation-file-regexp-alist)
+ ;; diff-file-regexp-alist)
+ ;; (set (make-local-variable 'compilation-error-regexp-alist)
+ ;; diff-error-regexp-alist)
+ ;; (when (string-match "\\.rej\\'" (or buffer-file-name ""))
+ ;; (set (make-local-variable 'compilation-current-file)
+ ;; (substring buffer-file-name 0 (match-beginning 0))))
+ ;; (compilation-shell-minor-mode 1)
+
+ (when (and (> (point-max) (point-min)) diff-default-read-only)
+ (toggle-read-only t))
;; setup change hooks
- (toggle-read-only t)
- (if (not diff-update-on-the-fly-flag)
+ (if (not diff-update-on-the-fly)
(add-hook 'write-contents-hooks 'diff-write-contents-hooks)
(make-local-variable 'diff-unhandled-changes)
(add-hook 'after-change-functions 'diff-after-change-function nil t)
nil " Diff" nil
;; FIXME: setup font-lock
;; setup change hooks
- (if (not diff-update-on-the-fly-flag)
+ (if (not diff-update-on-the-fly)
(add-hook 'write-contents-hooks 'diff-write-contents-hooks)
(make-local-variable 'diff-unhandled-changes)
(add-hook 'after-change-functions 'diff-after-change-function nil t)
(defun diff-find-source-location (&optional other-file reverse)
"Find out (BUF LINE-OFFSET POS SRC DST SWITCHED)."
(save-excursion
- (let* ((other (diff-xor other-file diff-jump-to-old-file-flag))
+ (let* ((other (diff-xor other-file diff-jump-to-old-file))
(char-offset (- (point) (progn (diff-beginning-of-hunk) (point))))
(hunk (buffer-substring (point)
(save-excursion (diff-end-of-hunk) (point))))
(buf (find-file-noselect file)))
;; Update the user preference if he so wished.
(when (> (prefix-numeric-value other-file) 8)
- (setq diff-jump-to-old-file-flag other))
+ (setq diff-jump-to-old-file other))
(with-current-buffer buf
(goto-line (string-to-number line))
(let* ((orig-pos (point))
(defun diff-apply-hunk (&optional reverse)
"Apply the current hunk to the source file and go to the next.
By default, the new source file is patched, but if the variable
-`diff-jump-to-old-file-flag' is non-nil, then the old source file is
+`diff-jump-to-old-file' is non-nil, then the old source file is
patched instead (some commands, such as `diff-goto-source' can change
the value of this variable when given an appropriate prefix argument).
((null line-offset)
(error "Can't find the text to patch"))
((and switched
- ;; A reversed patch was detected, perhaps apply it in reverse
+ ;; A reversed patch was detected, perhaps apply it in reverse.
(not (save-window-excursion
(pop-to-buffer buf)
(goto-char (+ pos (cdr old)))
(defun diff-goto-source (&optional other-file)
"Jump to the corresponding source line.
-`diff-jump-to-old-file-flag' (or its opposite if the OTHER-FILE prefix arg
+`diff-jump-to-old-file' (or its opposite if the OTHER-FILE prefix arg
is given) determines whether to jump to the old or the new file.
If the prefix arg is bigger than 8 (for example with \\[universal-argument] \\[universal-argument])
- then `diff-jump-to-old-file-flag' is also set, for the next invocations."
+ then `diff-jump-to-old-file' is also set, for the next invocations."
(interactive "P")
;; When pointing at a removal line, we probably want to jump to
;; the old location, and else to the new (i.e. as if reverting).