Skip to content

Instantly share code, notes, and snippets.

@scottjad
Created October 10, 2012 06:26
Show Gist options
  • Save scottjad/3863483 to your computer and use it in GitHub Desktop.
Save scottjad/3863483 to your computer and use it in GitHub Desktop.
(defface diff-refine-change-add
'((t :background "#aaffaa" :foreground "black"))
"Face used for char-based changes shown by `diff-refine-hunk'."
:group 'diff-mode)
(defface diff-refine-change-del
'((t :background "#ffaaaa" :foreground "black"))
"Face used for char-based changes shown by `diff-refine-hunk'."
:group 'diff-mode)
(custom-set-faces
'(magit-diff-add ((t (:background "#ddffdd" :foreground "black"))))
'(magit-diff-del ((t (:background "#ffdddd" :foreground "black")))))
(defun jsj-smerge-refine-subst (beg1 end1 beg2 end2 props-add props-del &optional preproc)
"Show fine differences in the two regions BEG1..END1 and BEG2..END2.
PROPS is an alist of properties to put (via overlays) on the changes.
If non-nil, PREPROC is called with no argument in a buffer that contains
a copy of a region, just before preparing it to for `diff'. It can be
used to replace chars to try and eliminate some spurious differences."
(let* ((buf (current-buffer))
(pos (point))
deactivate-mark ; The code does not modify any visible buffer.
(file1 (make-temp-file "diff1"))
(file2 (make-temp-file "diff2")))
;; Chop up regions into smaller elements and save into files.
(smerge-refine-chopup-region beg1 end1 file1 preproc)
(smerge-refine-chopup-region beg2 end2 file2 preproc)
;; Call diff on those files.
(unwind-protect
(with-temp-buffer
(let ((coding-system-for-read 'emacs-mule))
(call-process diff-command nil t nil
(if (and smerge-refine-ignore-whitespace
(not smerge-refine-weight-hack))
;; Pass -a so diff treats it as a text file even
;; if it contains \0 and such.
;; Pass -d so as to get the smallest change, but
;; also and more importantly because otherwise it
;; may happen that diff doesn't behave like
;; smerge-refine-weight-hack expects it to.
;; See http://thread.gmane.org/gmane.emacs.devel/82685.
"-awd" "-ad")
file1 file2))
;; Process diff's output.
(goto-char (point-min))
(let ((last1 nil)
(last2 nil))
(while (not (eobp))
(if (not (looking-at "\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?\\([acd]\\)\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?$"))
(error "Unexpected patch hunk header: %s"
(buffer-substring (point) (line-end-position))))
(let ((op (char-after (match-beginning 3)))
(m1 (match-string 1))
(m2 (match-string 2))
(m4 (match-string 4))
(m5 (match-string 5)))
(when (memq op '(?d ?c))
(setq last1
(smerge-refine-highlight-change buf beg1 m1 m2 props-del)))
(when (memq op '(?a ?c))
(setq last2
(smerge-refine-highlight-change buf beg2 m4 m5 props-add))))
(forward-line 1) ;Skip hunk header.
(and (re-search-forward "^[0-9]" nil 'move) ;Skip hunk body.
(goto-char (match-beginning 0))))
;; (assert (or (null last1) (< (overlay-start last1) end1)))
;; (assert (or (null last2) (< (overlay-start last2) end2)))
(if smerge-refine-weight-hack
(progn
;; (assert (or (null last1) (<= (overlay-end last1) end1)))
;; (assert (or (null last2) (<= (overlay-end last2) end2)))
)
;; smerge-refine-forward-function when calling in chopup may
;; have stopped because it bumped into EOB whereas in
;; smerge-refine-weight-hack it may go a bit further.
(if (and last1 (> (overlay-end last1) end1))
(move-overlay last1 (overlay-start last1) end1))
(if (and last2 (> (overlay-end last2) end2))
(move-overlay last2 (overlay-start last2) end2))
)))
(goto-char pos)
(delete-file file1)
(delete-file file2))))
(defun diff-refine-hunk ()
"Highlight changes of hunk at point at a finer granularity."
(interactive)
(require 'smerge-mode)
(save-excursion
(diff-beginning-of-hunk 'try-harder)
(let* ((start (point))
(style (diff-hunk-style)) ;Skips the hunk header as well.
(beg (point))
(props-add '((diff-mode . fine) (face diff-refine-change-add)))
(props-del '((diff-mode . fine) (face diff-refine-change-del)))
;; Be careful to go back to `start' so diff-end-of-hunk gets
;; to read the hunk header's line info.
(end (progn (goto-char start) (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)
(jsj-smerge-refine-subst (match-beginning 0) (match-end 1)
(match-end 1) (match-end 0)
props-add props-del 'diff-refine-preproc)))
(context
(let* ((middle (save-excursion (re-search-forward "^---")))
(other middle))
(while (re-search-forward "^\\(?:!.*\n\\)+" middle t)
(jsj-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-add props-del '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.
(jsj-smerge-refine-subst beg1 (match-beginning 0)
(match-end 0) end
props-add props-del 'diff-refine-preproc))))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment