;;; diff-mode.el --- a mode for viewing/editing context diffs
-;; Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc.
+;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
+;; 2005 Free Software Foundation, Inc.
;; Author: Stefan Monnier <monnier@cs.yale.edu>
;; Keywords: convenience patch diff
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
-;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-;; Boston, MA 02111-1307, USA.
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
;;; Commentary:
;; commands, editing and various conversions as well as jumping
;; to the corresponding source file.
-;; Inspired by Pavel Machek's patch-mode.el (<pavel@atrey.karlin.mff.cuni.cz>)
+;; Inspired by Pavel Machek's patch-mode.el (<pavel@@atrey.karlin.mff.cuni.cz>)
;; Some efforts were spent to have it somewhat compatible with XEmacs'
;; diff-mode as well as with compilation-minor-mode
;; Todo:
-;; - Improve narrowed-view support.
-;; - re-enable (conditionally) the `compile' support after improving it to use
-;; the same code as diff-goto-source.
-;; - Support for # comments in context->unified.
-;; - Allow diff.el to use diff-mode.
-;; This mostly means ability to jump from half-hunk to half-hunk
-;; in context (and normal) diffs and to jump to the corresponding
-;; (i.e. new or old) file.
+;; - 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".
+;;
+;; - Refine hunk on a word-by-word basis.
+;;
;; - 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))
+(defvar add-log-buffer-file-name-function)
+
(defgroup diff-mode ()
- "Major mode for viewing/editing diffs"
+ "Major mode for viewing/editing diffs."
:version "21.1"
:group 'tools
:group 'diff)
-(defcustom diff-default-read-only t
+(defcustom diff-default-read-only nil
"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."
- :type '(boolean))
+ :type 'boolean
+ :group 'diff-mode)
(defcustom diff-update-on-the-fly t
"*Non-nil means hunk headers are kept up-to-date on-the-fly.
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)."
- :type '(boolean))
+ :type 'boolean
+ :group 'diff-mode)
(defcustom diff-advance-after-apply-hunk t
"*Non-nil means `diff-apply-hunk' will move to the next hunk after applying."
- :type 'boolean)
+ :type 'boolean
+ :group 'diff-mode)
-(defvar diff-mode-hook nil
- "Run after setting up the `diff-mode' major mode.")
+(defcustom diff-mode-hook nil
+ "Run after setting up the `diff-mode' major mode."
+ :type 'hook
+ :options '(diff-delete-empty-files diff-make-unified)
+ :group 'diff-mode)
(defvar diff-outline-regexp
"\\([*+][*+][*+] [^0-9]\\|@@ ...\\|\\*\\*\\* [0-9].\\|--- [0-9]..\\)")
("}" . diff-file-next)
("{" . diff-file-prev)
("\C-m" . diff-goto-source)
- ([mouse-2] . diff-mouse-goto-source)
+ ([mouse-2] . diff-goto-source)
;; From XEmacs' diff-mode.
("W" . widen)
;;("." . diff-goto-source) ;display-buffer
("r" . diff-restrict-view)
("R" . diff-reverse-direction)
("U" . diff-context->unified)
- ("C" . diff-unified->context))
+ ("C" . diff-unified->context)
+ ("q" . quit-window))
"Basic keymap for `diff-mode', bound to various prefix keys.")
(easy-mmode-defmap diff-mode-map
;; From compilation-minor-mode.
("\C-c\C-c" . diff-goto-source)
;; Misc operations.
+ ("\C-c\C-r" . diff-refine-hunk)
("\C-c\C-s" . diff-split-hunk)
("\C-c\C-a" . diff-apply-hunk)
- ("\C-c\C-t" . diff-test-hunk))
+ ("\C-c\C-t" . diff-test-hunk)
+ ("\C-c\C-f" . next-error-follow-minor-mode))
"Keymap for `diff-mode'. See also `diff-mode-shared-map'.")
(easy-menu-define diff-mode-menu diff-mode-map
(defcustom diff-minor-mode-prefix "\C-c="
"Prefix key for `diff-minor-mode' commands."
- :type '(choice (string "\e") (string "C-c=") string))
+ :type '(choice (string "\e") (string "C-c=") string)
+ :group 'diff-mode)
(easy-mmode-defmap diff-minor-mode-map
`((,diff-minor-mode-prefix . ,diff-mode-shared-map))
;;;; font-lock support
;;;;
-(defface diff-header-face
- '((((type tty pc) (class color) (background light))
- (:foreground "blue1" :weight bold))
- (((type tty pc) (class color) (background dark))
- (:foreground "green" :weight bold))
+(defface diff-header
+ '((((class color) (min-colors 88) (background light))
+ :background "grey85")
+ (((class color) (min-colors 88) (background dark))
+ :background "grey45")
(((class color) (background light))
- (:background "grey85"))
+ :foreground "blue1" :weight bold)
(((class color) (background dark))
- (:background "grey45"))
- (t (:weight bold)))
- "`diff-mode' face inherited by hunk and index header faces.")
-(defvar diff-header-face 'diff-header-face)
-
-(defface diff-file-header-face
- '((((type tty pc) (class color) (background light))
- (:foreground "yellow" :weight bold))
- (((type tty pc) (class color) (background dark))
- (:foreground "cyan" :weight bold))
+ :foreground "green" :weight bold)
+ (t :weight bold))
+ "`diff-mode' face inherited by hunk and index header faces."
+ :group 'diff-mode)
+;; backward-compatibility alias
+(put 'diff-header-face 'face-alias 'diff-header)
+(defvar diff-header-face 'diff-header)
+
+(defface diff-file-header
+ '((((class color) (min-colors 88) (background light))
+ :background "grey70" :weight bold)
+ (((class color) (min-colors 88) (background dark))
+ :background "grey60" :weight bold)
(((class color) (background light))
- (:background "grey70" :weight bold))
+ :foreground "green" :weight bold)
(((class color) (background dark))
- (:background "grey60" :weight bold))
- (t (:weight bold))) ; :height 1.3
- "`diff-mode' face used to highlight file header lines.")
-(defvar diff-file-header-face 'diff-file-header-face)
-
-(defface diff-index-face
- '((t (:inherit diff-file-header-face)))
- "`diff-mode' face used to highlight index header lines.")
-(defvar diff-index-face 'diff-index-face)
-
-(defface diff-hunk-header-face
- '((t (:inherit diff-header-face)))
- "`diff-mode' face used to highlight hunk header lines.")
-(defvar diff-hunk-header-face 'diff-hunk-header-face)
-
-(defface diff-removed-face
- '((t (:inherit diff-changed-face)))
- "`diff-mode' face used to highlight removed lines.")
-(defvar diff-removed-face 'diff-removed-face)
-
-(defface diff-added-face
- '((t (:inherit diff-changed-face)))
- "`diff-mode' face used to highlight added lines.")
-(defvar diff-added-face 'diff-added-face)
-
-(defface diff-changed-face
+ :foreground "cyan" :weight bold)
+ (t :weight bold)) ; :height 1.3
+ "`diff-mode' face used to highlight file header lines."
+ :group 'diff-mode)
+;; backward-compatibility alias
+(put 'diff-file-header-face 'face-alias 'diff-file-header)
+(defvar diff-file-header-face 'diff-file-header)
+
+(defface diff-index
+ '((t :inherit diff-file-header))
+ "`diff-mode' face used to highlight index header lines."
+ :group 'diff-mode)
+;; backward-compatibility alias
+(put 'diff-index-face 'face-alias 'diff-index)
+(defvar diff-index-face 'diff-index)
+
+(defface diff-hunk-header
+ '((t :inherit diff-header))
+ "`diff-mode' face used to highlight hunk header lines."
+ :group 'diff-mode)
+;; backward-compatibility alias
+(put 'diff-hunk-header-face 'face-alias 'diff-hunk-header)
+(defvar diff-hunk-header-face 'diff-hunk-header)
+
+(defface diff-removed
+ '((t :inherit diff-changed))
+ "`diff-mode' face used to highlight removed lines."
+ :group 'diff-mode)
+;; backward-compatibility alias
+(put 'diff-removed-face 'face-alias 'diff-removed)
+(defvar diff-removed-face 'diff-removed)
+
+(defface diff-added
+ '((t :inherit diff-changed))
+ "`diff-mode' face used to highlight added lines."
+ :group 'diff-mode)
+;; backward-compatibility alias
+(put 'diff-added-face 'face-alias 'diff-added)
+(defvar diff-added-face 'diff-added)
+
+(defface diff-changed
'((((type tty pc) (class color) (background light))
- (:foreground "magenta" :weight bold :slant italic))
+ :foreground "magenta" :weight bold :slant italic)
(((type tty pc) (class color) (background dark))
- (:foreground "yellow" :weight bold :slant italic))
- (t ()))
- "`diff-mode' face used to highlight changed lines.")
-(defvar diff-changed-face 'diff-changed-face)
-
-(defface diff-function-face
- '((t (:inherit diff-context-face)))
- "`diff-mode' face used to highlight function names produced by \"diff -p\".")
-(defvar diff-function-face 'diff-function-face)
-
-(defface diff-context-face
- '((((class color) (background light))
- (:foreground "grey50"))
- (((class color) (background dark))
- (:foreground "grey70"))
- (t ))
- "`diff-mode' face used to highlight context and other side-information.")
-(defvar diff-context-face 'diff-context-face)
+ :foreground "yellow" :weight bold :slant italic))
+ "`diff-mode' face used to highlight changed lines."
+ :group 'diff-mode)
+;; backward-compatibility alias
+(put 'diff-changed-face 'face-alias 'diff-changed)
+(defvar diff-changed-face 'diff-changed)
+
+(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)
+
+(defface diff-function
+ '((t :inherit diff-context))
+ "`diff-mode' face used to highlight function names produced by \"diff -p\"."
+ :group 'diff-mode)
+;; backward-compatibility alias
+(put 'diff-function-face 'face-alias 'diff-function)
+(defvar diff-function-face 'diff-function)
+
+(defface diff-context
+ '((((class color grayscale) (min-colors 88)) :inherit shadow))
+ "`diff-mode' face used to highlight context and other side-information."
+ :group 'diff-mode)
+;; backward-compatibility alias
+(put 'diff-context-face 'face-alias 'diff-context)
+(defvar diff-context-face 'diff-context)
+
+(defface diff-nonexistent
+ '((t :inherit diff-file-header))
+ "`diff-mode' face used to highlight nonexistent files in recursive diffs."
+ :group 'diff-mode)
+;; backward-compatibility alias
+(put 'diff-nonexistent-face 'face-alias 'diff-nonexistent)
+(defvar diff-nonexistent-face 'diff-nonexistent)
+
+(defconst diff-yank-handler '(diff-yank-function))
+(defun diff-yank-function (text)
+ ;; 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
+ (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)))))))
-(defface diff-nonexistent-face
- '((t (:inherit diff-file-header-face)))
- "`diff-mode' face used to highlight nonexistent files in recursive diffs.")
-(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))
- ("^--- .+ ----$" . diff-hunk-header-face) ;context
- ("^\\(\\*\\{15\\}\\)\\(.*\\)$" ;context
- (1 diff-hunk-header-face)
- (2 diff-function-face))
+ `(("^\\(@@ -[0-9,]+ \\+[0-9,]+ @@\\)\\(.*\\)$" ;unified
+ (1 diff-hunk-header-face) (2 diff-function-face))
+ ("^\\(\\*\\{15\\}\\)\\(.*\\)$" ;context
+ (1 diff-hunk-header-face) (2 diff-function-face))
("^\\*\\*\\* .+ \\*\\*\\*\\*". diff-hunk-header-face) ;context
+ ("^--- .+ ----$" . diff-hunk-header-face) ;context
+ ("^[0-9,]+[acd][0-9,]+$" . diff-hunk-header-face) ;normal
+ ("^---$" . diff-hunk-header-face) ;normal
("^\\(---\\|\\+\\+\\+\\|\\*\\*\\*\\) \\(\\S-+\\)\\(.*[^*-]\\)?\n"
(0 diff-header-face) (2 diff-file-header-face prepend))
- ("^[0-9,]+[acd][0-9,]+$" . diff-hunk-header-face)
- ("^!.*\n" . diff-changed-face) ;context
- ("^[+>].*\n" . diff-added-face)
- ("^[-<].*\n" . diff-removed-face)
- ("^Index: \\(.+\\).*\n" (0 diff-header-face) (1 diff-index-face prepend))
+ ("^\\([-<]\\)\\(.*\n\\)"
+ (1 diff-indicator-removed-face) (2 diff-removed-face))
+ ("^\\([+>]\\)\\(.*\n\\)"
+ (1 diff-indicator-added-face) (2 diff-added-face))
+ ("^\\(!\\)\\(.*\n\\)"
+ (1 diff-indicator-changed-face) (2 diff-changed-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)))
+ ("^\\(#\\)\\(.*\\)"
+ (1 font-lock-comment-delimiter-face)
+ (2 font-lock-comment-face))
+ ("^[^-=+*!<>#].*\n" (0 diff-context-face))))
(defconst diff-font-lock-defaults
'(diff-font-lock-keywords t nil nil nil (font-lock-multiline . nil)))
'((nil "\\+\\+\\+\\ \\([^\t\n]+\\)[\t\n]" 1) ; unidiffs
(nil "^--- \\([^\t\n]+\\)\t.*\n\\*" 1))) ; context diffs
-;;;;
-;;;; Compile support
-;;;;
-
-(defvar diff-file-regexp-alist
- '(("Index: \\(.+\\)" 1)))
-
-(defvar diff-error-regexp-alist
- '(("@@ -\\([0-9]+\\),[0-9]+ \\+\\([0-9]+\\),[0-9]+ @@" nil 2)
- ("--- \\([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)))
+(defconst diff-file-header-re (concat "^\\(--- .+\n\\+\\+\\+ \\|\\*\\*\\* .+\n--- \\|[^-+!<>0-9@* ]\\).+\n" (substring diff-hunk-header-re 1)))
(defvar diff-narrowed-to nil)
(defun diff-end-of-hunk (&optional style)
- (if (looking-at diff-hunk-header-re) (goto-char (match-end 0)))
+ (when (looking-at diff-hunk-header-re)
+ (unless style
+ ;; Especially important for unified (because headers are ambiguous).
+ (setq style (cdr (assq (char-after) '((?@ . unified) (?* . context))))))
+ (goto-char (match-end 0)))
(let ((end (and (re-search-forward (case style
;; A `unified' header is ambiguous.
(unified (concat "^[^-+# \\]\\|"
(defun diff-end-of-file ()
(re-search-forward "^[-+#!<>0-9@* \\]" nil t)
- (re-search-forward "^[^-+#!<>0-9@* \\]" nil 'move)
- (beginning-of-line))
+ (re-search-forward (concat "^[^-+#!<>0-9@* \\]\\|" diff-file-header-re)
+ nil 'move)
+ (if (match-beginning 1)
+ (goto-char (match-beginning 1))
+ (beginning-of-line)))
;; Define diff-{hunk,file}-{prev,next}
(easy-mmode-define-navigation
- diff-hunk diff-hunk-header-re "hunk" diff-end-of-hunk)
+ diff-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view)
(easy-mmode-define-navigation
diff-file diff-file-header-re "file" diff-end-of-hunk)
(interactive)
(diff-beginning-of-hunk)
(let* ((start (point))
- (nexthunk (ignore-errors (diff-hunk-next) (point)))
+ (nexthunk (when (re-search-forward diff-hunk-header-re nil t)
+ (match-beginning 0)))
(firsthunk (ignore-errors
(goto-char start)
(diff-beginning-of-file) (diff-hunk-next) (point)))
(re-search-backward "^Index: " prevhunk t))))
(when index (setq start index))
(diff-end-of-file)
+ (if (looking-at "^\n") (forward-char 1)) ;`tla' generates such diffs.
(kill-region start (point))))
(defun diff-kill-junk ()
(number-to-string newstart2) ",1 @@\n")
;; Fix the original hunk-header.
(diff-fixup-modifs start pos))))
-
+
;;;;
;;;; jump to other buffers
(match-string 4 str)
(substring str (match-end 6) (match-end 5))))))
-(defun diff-find-file-name (&optional old)
- "Return the file corresponding to the current patch.
-Non-nil OLD means that we want the old file."
+(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))
+ nil (diff-find-file-name old) t))))
+ (let ((fs (diff-hunk-file-names old)))
+ (unless fs (error "No file name to look for"))
+ (push (cons fs name) diff-remembered-files-alist)))
+
+(defun diff-hunk-file-names (&optional old)
+ "Give the list of file names textually mentioned for the current hunk."
(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* ((limit (save-excursion
+ (let ((limit (save-excursion
(condition-case ()
(progn (diff-hunk-prev) (point))
(error (point-min)))))
- (header-files
- (if (looking-at "[-*][-*][-*] \\(\\S-+\\)\\(\\s-.*\\)?\n[-+][-+][-+] \\(\\S-+\\)")
- (list (if old (match-string 1) (match-string 3))
- (if old (match-string 3) (match-string 1)))
- (forward-line 1) nil))
- (fs (append
- (when (save-excursion
- (re-search-backward "^Index: \\(.+\\)" limit t))
- (list (match-string 1)))
- header-files
- (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))))))
- (fs (delq nil fs)))
+ (header-files
+ (if (looking-at "[-*][-*][-*] \\(\\S-+\\)\\(\\s-.*\\)?\n[-+][-+][-+] \\(\\S-+\\)")
+ (list (if old (match-string 1) (match-string 3))
+ (if old (match-string 3) (match-string 1)))
+ (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
+ (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)))))))))
+
+(defun diff-find-file-name (&optional old)
+ "Return the file corresponding to the current patch.
+Non-nil OLD means that we want the old file."
+ (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)))
(or
;; use any previously used preference
(cdr (assoc fs diff-remembered-files-alist))
file)))))
-(defun diff-mouse-goto-source (event)
- "Run `diff-goto-source' for the diff at a mouse click."
- (interactive "e")
- (save-excursion
- (mouse-set-point event)
- (diff-goto-source)))
-
-
(defun diff-ediff-patch ()
"Call `ediff-patch-file' on the current buffer."
(interactive)
(ediff-patch-file nil (current-buffer))
(wrong-number-of-arguments (ediff-patch-file))))
-;;;;
+;;;;
;;;; Conversion functions
-;;;;
+;;;;
;;(defvar diff-inhibit-after-change nil
;; "Non-nil means inhibit `diff-mode's after-change functions.")
(while (progn (setq last-pt (point))
(= (forward-line -1) 0))
(case (char-after)
- (? (insert " ") (setq modif nil) (backward-char 1))
+ (?\s (insert " ") (setq modif nil) (backward-char 1))
(?+ (delete-region (point) last-pt) (setq modif t))
(?- (if (not modif)
(progn (forward-char 1)
(let ((modif nil) (delete nil))
(while (not (eobp))
(case (char-after)
- (? (insert " ") (setq modif nil) (backward-char 1))
+ (?\s (insert " ") (setq modif nil) (backward-char 1))
(?- (setq delete t) (setq modif t))
(?+ (if (not modif)
(progn (forward-char 1)
(while (< (point) pt2)
(case (char-after)
((?! ?-) (delete-char 2) (insert "-") (forward-line 1))
- (?\ ;merge with the other half of the chunk
+ (?\s ;merge with the other half of the chunk
(let* ((endline2
(save-excursion
(goto-char pt2) (forward-line 1) (point)))
(insert "+"
(prog1 (buffer-substring (+ pt2 2) endline2)
(delete-region pt2 endline2))))
- (?\ ;FIXME: check consistency
+ (?\s ;FIXME: check consistency
(delete-region pt2 endline2)
(delete-char 1)
(forward-line 1))
(t (when (and first last (< first last))
(insert (delete-and-extract-region first last)))
(setq first nil last nil)
- (equal ?\ c)))
+ (equal ?\s c)))
(forward-line 1))))))))))
(defun diff-fixup-modifs (start end)
(goto-char end) (diff-end-of-hunk)
(let ((plus 0) (minus 0) (space 0) (bang 0))
(while (and (= (forward-line -1) 0) (<= start (point)))
- (if (not (looking-at "\\(@@ -[0-9,]+ \\+[0-9,]+ @@.*\\|[-*][-*][-*] .+ [-*][-*][-*][-*]\\)$"))
+ (if (not (looking-at
+ (concat "@@ -[0-9,]+ \\+[0-9,]+ @@"
+ "\\|[-*][-*][-*] [0-9,]+ [-*][-*][-*][-*]$"
+ "\\|--- .+\n\\+\\+\\+ ")))
(case (char-after)
- (?\ (incf space))
+ (?\s (incf space))
(?+ (incf plus))
(?- (incf minus))
(?! (incf bang))
(unless (string= new old) (replace-match new t t nil 2))))))
(setq space 0 plus 0 minus 0 bang 0)))))))
-;;;;
+;;;;
;;;; Hooks
-;;;;
+;;;;
(defun diff-write-contents-hooks ()
"Fixup hunk headers if necessary."
(diff-fixup-modifs (point) (cdr diff-unhandled-changes)))))
(setq diff-unhandled-changes nil)))
-;;;;
-;;;; The main function
-;;;;
+(defun diff-next-error (arg reset)
+ ;; Select a window that displays the current buffer so that point
+ ;; movements are reflected in that window. Otherwise, the user might
+ ;; never see the hunk corresponding to the source she's jumping to.
+ (pop-to-buffer (current-buffer))
+ (if reset (goto-char (point-min)))
+ (diff-hunk-next arg)
+ (diff-goto-source))
;;;###autoload
(define-derived-mode diff-mode fundamental-mode "Diff"
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.
-IF you edit the buffer manually, diff-mode will try to update the hunk
+If you edit the buffer manually, diff-mode will try to update the hunk
headers for you on-the-fly.
You can also switch between context diff and unified diff with \\[diff-context->unified],
-or vice versa with \\[diff-unified->context] and you can also revert the direction of
+or vice versa with \\[diff-unified->context] and you can also reverse the direction of
a diff with \\[diff-reverse-direction]."
(set (make-local-variable 'font-lock-defaults) diff-font-lock-defaults)
(set (make-local-variable 'outline-regexp) diff-outline-regexp)
;; (set (make-local-variable 'paragraph-separate) paragraph-start)
;; (set (make-local-variable 'page-delimiter) "--- [^\t]+\t")
;; compile support
-
- ;;;; 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)
+ (set (make-local-variable 'next-error-function) 'diff-next-error)
(when (and (> (point-max) (point-min)) diff-default-read-only)
(toggle-read-only t))
;; setup change hooks
(if (not diff-update-on-the-fly)
- (add-hook 'write-contents-hooks 'diff-write-contents-hooks)
+ (add-hook 'write-contents-functions 'diff-write-contents-hooks nil t)
(make-local-variable 'diff-unhandled-changes)
(add-hook 'after-change-functions 'diff-after-change-function nil t)
(add-hook 'post-command-hook 'diff-post-command-hook nil t))
;; Neat trick from Dave Love to add more bindings in read-only mode:
- (add-to-list (make-local-variable 'minor-mode-overriding-map-alist)
- (cons 'buffer-read-only diff-mode-shared-map))
+ (let ((ro-bind (cons 'buffer-read-only diff-mode-shared-map)))
+ (add-to-list 'minor-mode-overriding-map-alist ro-bind)
+ ;; Turn off this little trick in case the buffer is put in view-mode.
+ (add-hook 'view-mode-hook
+ `(lambda ()
+ (setq minor-mode-overriding-map-alist
+ (delq ',ro-bind minor-mode-overriding-map-alist)))
+ nil t))
;; add-log support
(set (make-local-variable 'add-log-current-defun-function)
'diff-current-defun)
(define-minor-mode diff-minor-mode
"Minor mode for viewing/editing context diffs.
\\{diff-minor-mode-map}"
- nil " Diff" nil
+ :group 'diff-mode :lighter " Diff"
;; FIXME: setup font-lock
;; setup change hooks
(if (not diff-update-on-the-fly)
- (add-hook 'write-contents-hooks 'diff-write-contents-hooks)
+ (add-hook 'write-contents-functions 'diff-write-contents-hooks nil t)
(make-local-variable 'diff-unhandled-changes)
(add-hook 'after-change-functions 'diff-after-change-function nil t)
(add-hook 'post-command-hook 'diff-post-command-hook nil t)))
+;;; Handy hook functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun diff-delete-if-empty ()
+ ;; An empty diff file means there's no more diffs to integrate, so we
+ ;; can just remove the file altogether. Very handy for .rej files if we
+ ;; remove hunks as we apply them.
+ (when (and buffer-file-name
+ (eq 0 (nth 7 (file-attributes buffer-file-name))))
+ (delete-file buffer-file-name)))
+
+(defun diff-delete-empty-files ()
+ "Arrange for empty diff files to be removed."
+ (add-hook 'after-save-hook 'diff-delete-if-empty nil t))
+
+(defun diff-make-unified ()
+ "Turn context diffs into unified diffs if applicable."
+ (if (save-excursion
+ (goto-char (point-min))
+ (and (looking-at diff-hunk-header-re) (eq (char-after) ?*)))
+ (let ((mod (buffer-modified-p)))
+ (unwind-protect
+ (diff-context->unified (point-min) (point-max))
+ (restore-buffer-modified-p mod)))))
;;;
;;; Misc operations that have proved useful at some point.
(goto-line (string-to-number line))
(let* ((orig-pos (point))
(switched nil)
+ ;; FIXME: Check for case where both OLD and NEW are found.
(pos (or (diff-find-text (car old))
(progn (setq switched t) (diff-find-text (car new)))
(progn (setq switched nil)
- (diff-find-approx-text (car old)))
+ (condition-case nil
+ (diff-find-approx-text (car old))
+ (invalid-regexp nil))) ;Regex too big.
(progn (setq switched t)
- (diff-find-approx-text (car new)))
+ (condition-case nil
+ (diff-find-approx-text (car new))
+ (invalid-regexp nil))) ;Regex too big.
(progn (setq switched nil) nil))))
(nconc
(list buf)
With a prefix argument, REVERSE the hunk."
(interactive "P")
(destructuring-bind (buf line-offset pos old new &optional switched)
- (diff-find-source-location nil reverse)
+ ;; If REVERSE go to the new file, otherwise go to the old.
+ (diff-find-source-location (not reverse) reverse)
(cond
((null line-offset)
(error "Can't find the text to patch"))
With a prefix argument, try to REVERSE the hunk."
(interactive "P")
(destructuring-bind (buf line-offset pos src dst &optional switched)
- (diff-find-source-location nil reverse)
+ ;; If REVERSE go to the new file, otherwise go to the old.
+ (diff-find-source-location (not reverse) reverse)
(set-window-point (display-buffer buf) (+ (car pos) (cdr src)))
(diff-hunk-status-msg line-offset (diff-xor reverse switched) t)))
-(defun diff-goto-source (&optional other-file)
+(defalias 'diff-mouse-goto-source 'diff-goto-source)
+
+(defun diff-goto-source (&optional other-file event)
"Jump to the corresponding source line.
`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' is also set, for the next invocations."
- (interactive "P")
+ (interactive (list current-prefix-arg last-input-event))
;; 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).
;; This is a convenient detail when using smerge-diff.
+ (if event (posn-set-point (event-end event)))
(let ((rev (not (save-excursion (beginning-of-line) (looking-at "[-<]")))))
(destructuring-bind (buf line-offset pos src dst &optional switched)
(diff-find-source-location other-file rev)
(defun diff-current-defun ()
"Find the name of function at point.
For use in `add-log-current-defun-function'."
- (destructuring-bind (buf line-offset pos src dst &optional switched)
- (diff-find-source-location)
- (save-excursion
+ (save-excursion
+ (when (looking-at diff-hunk-header-re)
+ (forward-line 1)
+ (re-search-forward "^[^ ]" nil t))
+ (destructuring-bind (buf line-offset pos src dst &optional switched)
+ (diff-find-source-location)
(beginning-of-line)
(or (when (memq (char-after) '(?< ?-))
;; Cursor is pointing at removed text. This could be a removed
(goto-char (+ (car pos) (cdr src)))
(add-log-current-defun))))))
+(defun diff-refine-hunk ()
+ "Refine the current hunk by ignoring space differences."
+ (interactive)
+ (let* ((char-offset (- (point) (progn (diff-beginning-of-hunk) (point))))
+ (opts (case (char-after) (?@ "-bu") (?* "-bc") (t "-b")))
+ (line-nb (and (or (looking-at "[^0-9]+\\([0-9]+\\)")
+ (error "Can't find line number"))
+ (string-to-number (match-string 1))))
+ (hunk (delete-and-extract-region
+ (point) (save-excursion (diff-end-of-hunk) (point))))
+ (lead (make-string (1- line-nb) ?\n)) ;Line nums start at 1.
+ (file1 (make-temp-file "diff1"))
+ (file2 (make-temp-file "diff2"))
+ (coding-system-for-read buffer-file-coding-system)
+ old new)
+ (unwind-protect
+ (save-excursion
+ (setq old (diff-hunk-text hunk nil char-offset))
+ (setq new (diff-hunk-text hunk t char-offset))
+ (write-region (concat lead (car old)) nil file1 nil 'nomessage)
+ (write-region (concat lead (car new)) nil file2 nil 'nomessage)
+ (with-temp-buffer
+ (let ((status
+ (call-process diff-command nil t nil
+ opts file1 file2)))
+ (case status
+ (0 nil) ;Nothing to reformat.
+ (1 (goto-char (point-min))
+ ;; Remove the file-header.
+ (when (re-search-forward diff-hunk-header-re nil t)
+ (delete-region (point-min) (match-beginning 0))))
+ (t (goto-char (point-max))
+ (unless (bolp) (insert "\n"))
+ (insert hunk)))
+ (setq hunk (buffer-string))
+ (unless (memq status '(0 1))
+ (error "Diff returned: %s" status)))))
+ ;; Whatever happens, put back some equivalent text: either the new
+ ;; one or the original one in case some error happened.
+ (insert hunk)
+ (delete-file file1)
+ (delete-file file2))))
+
;; provide the package
(provide 'diff-mode)
;; use `combine-after-change-calls' to minimize the slowdown of font-lock.
;;
+;; arch-tag: 2571d7ff-bc28-4cf9-8585-42e21890be66
;;; diff-mode.el ends here