unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Stefan Monnier <monnier@iro.umontreal.ca>
Subject: char-granularity fine diff highlight in diff-mode
Date: Mon, 17 Jul 2006 01:18:38 -0400	[thread overview]
Message-ID: <jwvejwkolvf.fsf-monnier+emacs@gnu.org> (raw)


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 monnier@iro.umontreal.ca--first/emacs--monnier--0--patch-372 to greedy revision library /home/monnier/tmp/arch-lib
* found immediate ancestor revision in library (monnier@iro.umontreal.ca--first/emacs--monnier--0--patch-371)
* patching for this revision (monnier@iro.umontreal.ca--first/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	other threads:[~2006-07-17  5:18 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=jwvejwkolvf.fsf-monnier+emacs@gnu.org \
    --to=monnier@iro.umontreal.ca \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).