;; Or maybe just make it into a ".rej to diff3-markers converter".
;; Maybe just use `wiggle' (by Neil Brown) to do it for us.
;;
-;; - Refine hunk on a word-by-word basis.
-;;
;; - in diff-apply-hunk, strip context in replace-match to better
;; preserve markers and spacing.
;; - Handle `diff -b' output in context->unified.
:type 'boolean
:group 'diff-mode)
+(defcustom diff-auto-refine t
+ "Automatically highlight changes in detail as the user visits hunks."
+ :type 'boolean
+ :group 'diff-mode)
(defcustom diff-mode-hook nil
"Run after setting up the `diff-mode' major mode."
("N" . diff-file-next)
("p" . diff-hunk-prev)
("P" . diff-file-prev)
+ ("\t" . diff-hunk-next)
+ ([backtab] . diff-hunk-prev)
("k" . diff-hunk-kill)
("K" . diff-file-kill)
;; From compilation-minor-mode.
("\C-c\C-a" . diff-apply-hunk)
("\C-c\C-e" . diff-ediff-patch)
("\C-c\C-n" . diff-restrict-view)
- ("\C-c\C-r" . diff-reverse-direction)
("\C-c\C-s" . diff-split-hunk)
("\C-c\C-t" . diff-test-hunk)
+ ("\C-c\C-r" . diff-reverse-direction)
("\C-c\C-u" . diff-context->unified)
;; `d' because it duplicates the context :-( --Stef
("\C-c\C-d" . diff-unified->context)
- ("\C-c\C-w" . diff-refine-ignore-spaces-hunk)
+ ("\C-c\C-w" . diff-ignore-whitespace-hunk)
+ ("\C-c\C-b" . diff-refine-hunk) ;No reason for `b' :-(
("\C-c\C-f" . next-error-follow-minor-mode))
"Keymap for `diff-mode'. See also `diff-mode-shared-map'.")
;;["Fixup Headers" diff-fixup-modifs (not buffer-read-only)]
"-----"
["Split hunk" diff-split-hunk (diff-splittable-p)]
- ["Refine hunk" diff-refine-ignore-spaces-hunk t]
+ ["Ignore whitespace changes" diff-ignore-whitespace-hunk t]
+ ["Highlight fine changes" diff-refine-hunk t]
["Kill current hunk" diff-hunk-kill t]
["Kill current file's hunks" diff-file-kill t]
"-----"
;; Define diff-{hunk,file}-{prev,next}
(easy-mmode-define-navigation
- diff-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view)
+ diff-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view
+ (if diff-auto-refine
+ (condition-case-no-debug nil (diff-refine-hunk) (error nil))))
+
(easy-mmode-define-navigation
diff-file diff-file-header-re "file" diff-end-of-hunk)
(while
(case (char-after)
(?\s (decf before) (decf after) t)
- (?- (decf before) t)
+ (?-
+ (if (and (looking-at diff-file-header-re)
+ (zerop before) (zerop after))
+ ;; No need to query: this is a case where two patches
+ ;; are concatenated and only counting the lines will
+ ;; give the right result. Let's just add an empty
+ ;; line so that our code which doesn't count lines
+ ;; will not get confused.
+ (progn (save-excursion (insert "\n")) nil)
+ (decf before) t))
(?+ (decf after) t)
(t
(cond
(defun diff-current-defun ()
"Find the name of function at point.
For use in `add-log-current-defun-function'."
+ ;; Kill change-log-default-name so it gets recomputed each time, since
+ ;; each hunk may belong to another file which may belong to another
+ ;; directory and hence have a different ChangeLog file.
+ (kill-local-variable 'change-log-default-name)
(save-excursion
(when (looking-at diff-hunk-header-re)
(forward-line 1)
(goto-char (+ (car pos) (cdr src)))
(add-log-current-defun))))))
-(defun diff-refine-ignore-spaces-hunk ()
- "Refine the current hunk by ignoring space differences."
+(defun diff-ignore-whitespace-hunk ()
+ "Re-diff the current hunk, ignoring whitespace differences."
(interactive)
(let* ((char-offset (- (point) (progn (diff-beginning-of-hunk 'try-harder)
(point))))
;;; Fine change highlighting.
-(defface diff-fine-change
- '((t :background "yellow"))
- "Face used for char-based changes shown by `diff-fine-highlight'.")
-
-(defun diff-fine-highlight-preproc ()
- (while (re-search-forward "^." nil t)
- ;; Replace the hunk's leading prefix (+, -, !, <, or >) on each line
- ;; with something constant, otherwise it'll be flagged as changes
- ;; (since it's typically "-" on one side and "+" on the other).
- ;; Note that we keep the same number of chars: we treat the prefix
- ;; as part of the texts-to-diff, so that finding the right char
- ;; afterwards will be easier. This only makes sense because we make
- ;; diffs at char-granularity.
- (replace-match " ")))
-
-(defun diff-fine-highlight ()
+(defface diff-refine-change
+ '((((class color) (min-colors 88) (background light))
+ :background "grey90")
+ (((class color) (min-colors 88) (background dark))
+ :background "grey40")
+ (((class color) (background light))
+ :background "yellow")
+ (((class color) (background dark))
+ :background "green")
+ (t :weight bold))
+ "Face used for char-based changes shown by `diff-refine-hunk'."
+ :group 'diff-mode)
+
+(defun diff-refine-preproc ()
+ (while (re-search-forward "^[+>]" nil t)
+ ;; Remove spurious changes due to the fact that one side of the hunk is
+ ;; marked with leading + or > and the other with leading - or <.
+ ;; We used to replace all the prefix chars with " " but this only worked
+ ;; when we did char-based refinement (or when using
+ ;; smerge-refine-weight-hack) since otherwise, the `forward' motion done
+ ;; in chopup do not necessarily do the same as the ones in highlight
+ ;; since the "_" is not treated the same as " ".
+ (replace-match (cdr (assq (char-before) '((?+ . "-") (?> . "<"))))))
+ )
+
+(defun diff-refine-hunk ()
"Highlight changes of hunk at point at a finer granularity."
(interactive)
- (require 'smerge-mode)
- (diff-beginning-of-hunk 'try-harder)
- (let* ((style (diff-hunk-style)) ;Skips the hunk header as well.
- (beg (point))
- (props '((diff-mode . fine) (face diff-fine-change)))
- (end (progn (diff-end-of-hunk) (point))))
-
- (remove-overlays beg end 'diff-mode 'fine)
-
- (goto-char beg)
- (case style
- (unified
- (while (re-search-forward "^\\(?:-.*\n\\)+\\(\\)\\(?:\\+.*\n\\)+" end t)
- (smerge-refine-subst (match-beginning 0) (match-end 1)
- (match-end 1) (match-end 0)
- props 'diff-fine-highlight-preproc)))
- (context
- (let* ((middle (save-excursion (re-search-forward "^---")))
- (other middle))
- (while (re-search-forward "^\\(?:!.*\n\\)+" middle t)
- (smerge-refine-subst (match-beginning 0) (match-end 0)
- (save-excursion
- (goto-char other)
- (re-search-forward "^\\(?:!.*\n\\)+" end)
- (setq other (match-end 0))
- (match-beginning 0))
- other
- props 'diff-fine-highlight-preproc))))
- (t ;; Normal diffs.
- (let ((beg1 (1+ (point))))
- (when (re-search-forward "^---.*\n" end t)
- ;; It's a combined add&remove, so there's something to do.
- (smerge-refine-subst beg1 (match-beginning 0)
- (match-end 0) end
- props 'diff-fine-highlight-preproc)))))))
+ (eval-and-compile (require 'smerge-mode))
+ (save-excursion
+ (diff-beginning-of-hunk 'try-harder)
+ (let* ((style (diff-hunk-style)) ;Skips the hunk header as well.
+ (beg (point))
+ (props '((diff-mode . fine) (face diff-refine-change)))
+ (end (progn (diff-end-of-hunk) (point))))
+
+ (remove-overlays beg end 'diff-mode 'fine)
+
+ (goto-char beg)
+ (case style
+ (unified
+ (while (re-search-forward "^\\(?:-.*\n\\)+\\(\\)\\(?:\\+.*\n\\)+"
+ end t)
+ (smerge-refine-subst (match-beginning 0) (match-end 1)
+ (match-end 1) (match-end 0)
+ props 'diff-refine-preproc)))
+ (context
+ (let* ((middle (save-excursion (re-search-forward "^---")))
+ (other middle))
+ (while (re-search-forward "^\\(?:!.*\n\\)+" middle t)
+ (smerge-refine-subst (match-beginning 0) (match-end 0)
+ (save-excursion
+ (goto-char other)
+ (re-search-forward "^\\(?:!.*\n\\)+" end)
+ (setq other (match-end 0))
+ (match-beginning 0))
+ other
+ props 'diff-refine-preproc))))
+ (t ;; Normal diffs.
+ (let ((beg1 (1+ (point))))
+ (when (re-search-forward "^---.*\n" end t)
+ ;; It's a combined add&remove, so there's something to do.
+ (smerge-refine-subst beg1 (match-beginning 0)
+ (match-end 0) end
+ props 'diff-refine-preproc))))))))
;; provide the package