emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

char-granularity fine diff highlight in diff-mode


From: Stefan Monnier
Subject: char-granularity fine diff highlight in diff-mode
Date: Mon, 17 Jul 2006 01:18:38 -0400
User-agent: Gnus/5.11 (Gnus v5.11) Emacs/22.0.50 (gnu/linux)

Sorry to use this list for info not related to Emacs-22, but I can't
remember who asked for this feature.

Anyway, a while back, someone asked if diff-mode could highlight
fine-grained changes in the patch, kind of like the word-based fine
highlight in ediff.

At that time I pointed out that such a feature could probably be implemented
by reusing the code in smerge-mode which does just that for 2-way conflicts.

I just had the need for this feature, so here is the port of the smerge-mode
code to diff-mode.  It's crude and not meant for Emacs-22 (probably post-22,
tho it still needs more work), but I believe someone on this list might
be interested.


        Stefan


* auto-adding address@hidden/emacs--monnier--0--patch-372 to greedy revision 
library /home/monnier/tmp/arch-lib
* found immediate ancestor revision in library 
(address@hidden/emacs--monnier--0--patch-371)
* patching for this revision (address@hidden/emacs--monnier--0--patch-372)
--- orig/lisp/diff-mode.el
+++ mod/lisp/diff-mode.el
@@ -1498,6 +1498,108 @@
       (delete-file file1)
       (delete-file file2))))
 
+;;; Fine change highlighting.
+
+(defface diff-fine-change
+  '((t :background "yellow"))
+  "Face used for char-based changes shown by `diff-fine-highlight'.")
+
+(defun diff-fine-chopup-region (beg end file)
+  "Chopup the region into small elements, one per line."
+  ;; FIXME: see smerge-refine-chopup-region which duplicates most of this.
+  ;;
+  ;; ediff chops up into words, where the definition of a word is
+  ;; customizable.  Instead we here keep only one char per line.
+  ;; The advantages are that there's nothing to configure, that we get very
+  ;; fine results, and that it's trivial to map the line numbers in the
+  ;; output of diff back into buffer positions.  The disadvantage is that it
+  ;; can take more time to compute the diff and that the result is sometimes
+  ;; too fine.  I'm not too concerned about the slowdown because conflicts
+  ;; are usually significantly smaller than the whole file.  As for the
+  ;; problem of too-fine-refinement, I have found it to be unimportant
+  ;; especially when you consider the cases where the fine-grain is just
+  ;; what you want.
+  (let ((buf (current-buffer)))
+    (with-temp-buffer
+      (insert-buffer-substring buf beg end)
+      (goto-char (point-min))
+      (while (re-search-forward "^." nil t) (replace-match " "))
+      (goto-char (point-min))
+      (while (not (eobp))
+        (forward-char 1)
+        (unless (eq (char-before) ?\n) (insert ?\n)))
+      (let ((coding-system-for-write 'emacs-mule))
+        (write-region (point-min) (point-max) file nil 'nomessage)))))
+
+(defun diff-fine-highlight-change (buf beg match-num1 match-num2)
+  (let* ((startline (string-to-number (match-string match-num1)))
+         (ol (make-overlay
+              (+ beg startline -1)
+              (+ beg (if (match-end match-num2)
+                         (string-to-number (match-string match-num2))
+                       startline))
+              buf
+              'front-advance nil)))
+    (overlay-put ol 'diff-mode 'fine)
+    (overlay-put ol 'evaporate t)
+    (overlay-put ol 'face 'diff-fine-change)))
+
+
+(defun diff-fine-highlight ()
+  "Blabla."
+  ;; TODO:
+  ;; - Share code with smerge-refine
+  ;; - extend to context diffs (only the ! lines)
+  ;; - clean up
+  ;; - make more robust
+  ;; - maybe two different faces should be used here
+  ;; - provide a reasonable UI
+  ;; - do it hunk-wide rather than on a single substitution change
+  (interactive)
+  (if (re-search-backward "^[^+-]" nil 'move) (forward-line 1))
+  (let* ((buf (current-buffer))
+         (beg1 (point))
+         (end1 (if (re-search-forward "^[^-]" nil 'move)
+                   (match-beginning 0) (point-max)))
+         (beg2 end1)
+         (end2 (if (re-search-forward "^[^+]" nil 'move)
+                   (match-beginning 0) (point-max)))
+         (file1 (make-temp-file "diff1"))
+         (file2 (make-temp-file "diff2")))
+
+    ;; If the user makes edits, this may not be enough because some
+    ;; highlights may now be located outside of the change (e.g. the first
+    ;; char has been turned into a SPC).  Maybe we should remove overlays on
+    ;; the whole hunk?
+    (remove-overlays beg1 end1 'diff-mode 'fine)
+    (remove-overlays beg2 end2 'diff-mode 'fine)
+
+    ;; Chop up regions into smaller elements and save into files.
+    (diff-fine-chopup-region beg1 end1 file1)
+    (diff-fine-chopup-region beg2 end2 file2)
+
+    ;; Call diff on those files.
+    (unwind-protect
+        (with-temp-buffer
+          (let ((coding-system-for-read 'emacs-mule))
+            (call-process diff-command nil t nil file1 file2))
+          ;; Process diff's output.
+          (goto-char (point-min))
+          (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))))
+                (when (memq op '(?d ?c))
+                  (diff-fine-highlight-change buf beg1 1 2))
+                (when (memq op '(?a ?c))
+                  (diff-fine-highlight-change buf beg2 4 5)))
+              (forward-line 1)                            ;Skip hunk header.
+              (and (re-search-forward "^[0-9]" nil 'move) ;Skip hunk body.
+                   (goto-char (match-beginning 0))))))
+      (delete-file file1)
+      (delete-file file2))))
+
 ;; provide the package
 (provide 'diff-mode)
 




reply via email to

[Prev in Thread] Current Thread [Next in Thread]