changeset 85100:c04703192cb6

(diff-hunk-style): New fun. (diff-end-of-hunk): Use it. (diff-context->unified): Use the new `apply' undo element, if applicable, so as to save undo-log space. (diff-fine-change): New face. (diff-fine-highlight-preproc): New function. (diff-fine-highlight): New command.
author Stefan Monnier <monnier@iro.umontreal.ca>
date Tue, 09 Oct 2007 04:12:24 +0000
parents ccdbfad065e3
children edbef40c9bbb
files etc/NEWS lisp/ChangeLog lisp/diff-mode.el
diffstat 3 files changed, 155 insertions(+), 65 deletions(-) [+]
line wrap: on
line diff
--- a/etc/NEWS	Tue Oct 09 03:38:57 2007 +0000
+++ b/etc/NEWS	Tue Oct 09 04:12:24 2007 +0000
@@ -152,6 +152,7 @@
 
 * Changes in Specialized Modes and Packages in Emacs 23.1
 
+** diff-fine-highlight highlights char-level details of changes in a diff hunk.
 ** archive-mode has basic support to browse Rar archives.
 ** talk.el has been extended for multiple tty support.
 
--- a/lisp/ChangeLog	Tue Oct 09 03:38:57 2007 +0000
+++ b/lisp/ChangeLog	Tue Oct 09 04:12:24 2007 +0000
@@ -1,5 +1,13 @@
 2007-10-09  Stefan Monnier  <monnier@iro.umontreal.ca>
 
+	* diff-mode.el (diff-hunk-style): New fun.
+	(diff-end-of-hunk): Use it.
+	(diff-context->unified): Use the new `apply' undo element,
+	if applicable, so as to save undo-log space.
+	(diff-fine-change): New face.
+	(diff-fine-highlight-preproc): New function.
+	(diff-fine-highlight): New command.
+
 	* smerge-mode.el (smerge-refine-chopup-region): Add `preproc' argument.
 	(smerge-refine-highlight-change): Add `props' argument.
 	(smerge-refine-subst): New function holding most of smerge-refine.
--- a/lisp/diff-mode.el	Tue Oct 09 03:38:57 2007 +0000
+++ b/lisp/diff-mode.el	Tue Oct 09 04:12:24 2007 +0000
@@ -386,12 +386,15 @@
 (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)
+(defun diff-hunk-style (&optional style)
   (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))))))
+    (setq style (cdr (assq (char-after) '((?@ . unified) (?* . context)))))
     (goto-char (match-end 0)))
+  style)
+
+(defun diff-end-of-hunk (&optional style)
+  ;; Especially important for unified (because headers are ambiguous).
+  (setq style (diff-hunk-style style))
   (let ((end (and (re-search-forward (case style
 				       ;; A `unified' header is ambiguous.
 				       (unified (concat "^[^-+# \\]\\|"
@@ -843,68 +846,89 @@
       (diff-unified->context start end)
     (unless (markerp end) (setq end (copy-marker end t)))
     (let ( ;;(diff-inhibit-after-change t)
-	  (inhibit-read-only t))
+          (inhibit-read-only t))
       (save-excursion
-	(goto-char start)
-	(while (and (re-search-forward "^\\(\\(\\*\\*\\*\\) .+\n\\(---\\) .+\\|\\*\\{15\\}.*\n\\*\\*\\* \\([0-9]+\\),\\(-?[0-9]+\\) \\*\\*\\*\\*\\)$" nil t)
-		    (< (point) end))
-	  (combine-after-change-calls
-	    (if (match-beginning 2)
-		;; we matched a file header
-		(progn
-		  ;; use reverse order to make sure the indices are kept valid
-		  (replace-match "+++" t t nil 3)
-		  (replace-match "---" t t nil 2))
-	      ;; we matched a hunk header
-	      (let ((line1s (match-string 4))
-		    (line1e (match-string 5))
-		    (pt1 (match-beginning 0)))
-		(replace-match "")
-		(unless (re-search-forward
-			 "^--- \\([0-9]+\\),\\(-?[0-9]+\\) ----$" nil t)
-		  (error "Can't find matching `--- n1,n2 ----' line"))
-		(let ((line2s (match-string 1))
-		      (line2e (match-string 2))
-		      (pt2 (progn
-			     (delete-region (progn (beginning-of-line) (point))
-					    (progn (forward-line 1) (point)))
-			     (point-marker))))
-		  (goto-char pt1)
-		  (forward-line 1)
-		  (while (< (point) pt2)
-		    (case (char-after)
-		      ((?! ?-) (delete-char 2) (insert "-") (forward-line 1))
-		      (?\s     ;merge with the other half of the chunk
-		       (let* ((endline2
-			       (save-excursion
-				 (goto-char pt2) (forward-line 1) (point)))
-			      (c (char-after pt2)))
-			 (case c
-			   ((?! ?+)
-			    (insert "+"
-				    (prog1 (buffer-substring (+ pt2 2) endline2)
-				      (delete-region pt2 endline2))))
-			   (?\s		;FIXME: check consistency
-			    (delete-region pt2 endline2)
-			    (delete-char 1)
-			    (forward-line 1))
-			   (?\\ (forward-line 1))
-			   (t (delete-char 1) (forward-line 1)))))
-		      (t (forward-line 1))))
-		  (while (looking-at "[+! ] ")
-		    (if (/= (char-after) ?!) (forward-char 1)
-		      (delete-char 1) (insert "+"))
-		    (delete-char 1) (forward-line 1))
-		  (save-excursion
-		    (goto-char pt1)
-		    (insert "@@ -" line1s ","
-			    (number-to-string (- (string-to-number line1e)
-						 (string-to-number line1s)
-						 -1))
-			    " +" line2s ","
-			    (number-to-string (- (string-to-number line2e)
-						 (string-to-number line2s)
-						 -1)) " @@")))))))))))
+        (goto-char start)
+        (while (and (re-search-forward "^\\(\\(\\*\\*\\*\\) .+\n\\(---\\) .+\\|\\*\\{15\\}.*\n\\*\\*\\* \\([0-9]+\\),\\(-?[0-9]+\\) \\*\\*\\*\\*\\)$" nil t)
+                    (< (point) end))
+          (combine-after-change-calls
+            (if (match-beginning 2)
+                ;; we matched a file header
+                (progn
+                  ;; use reverse order to make sure the indices are kept valid
+                  (replace-match "+++" t t nil 3)
+                  (replace-match "---" t t nil 2))
+              ;; we matched a hunk header
+              (let ((line1s (match-string 4))
+                    (line1e (match-string 5))
+                    (pt1 (match-beginning 0))
+                    ;; Variables to use the special undo function.
+                    (old-undo buffer-undo-list)
+                    (old-end (marker-position end))
+                    (reversible t))
+                (replace-match "")
+                (unless (re-search-forward
+                         "^--- \\([0-9]+\\),\\(-?[0-9]+\\) ----$" nil t)
+                  (error "Can't find matching `--- n1,n2 ----' line"))
+                (let ((line2s (match-string 1))
+                      (line2e (match-string 2))
+                      (pt2 (progn
+                             (delete-region (progn (beginning-of-line) (point))
+                                            (progn (forward-line 1) (point)))
+                             (point-marker))))
+                  (goto-char pt1)
+                  (forward-line 1)
+                  (while (< (point) pt2)
+                    (case (char-after)
+                      (?! (delete-char 2) (insert "-") (forward-line 1))
+                      (?- (forward-char 1) (delete-char 1) (forward-line 1))
+                      (?\s           ;merge with the other half of the chunk
+                       (let* ((endline2
+                               (save-excursion
+                                 (goto-char pt2) (forward-line 1) (point))))
+                         (case (char-after pt2)
+                           ((?! ?+)
+                            (insert "+"
+                                    (prog1 (buffer-substring (+ pt2 2) endline2)
+                                      (delete-region pt2 endline2))))
+                           (?\s
+                            (unless (= (- endline2 pt2)
+                                       (- (line-beginning-position 2) (point)))
+                              ;; If the two lines we're merging don't have the
+                              ;; same length (can happen with "diff -b"), then
+                              ;; diff-unified->context will not properly undo
+                              ;; this operation.
+                              (setq reversible nil))
+                            (delete-region pt2 endline2)
+                            (delete-char 1)
+                            (forward-line 1))
+                           (?\\ (forward-line 1))
+                           (t (setq reversible nil)
+                              (delete-char 1) (forward-line 1)))))
+                      (t (setq reversible nil) (forward-line 1))))
+                  (while (looking-at "[+! ] ")
+                    (if (/= (char-after) ?!) (forward-char 1)
+                      (delete-char 1) (insert "+"))
+                    (delete-char 1) (forward-line 1))
+                  (save-excursion
+                    (goto-char pt1)
+                    (insert "@@ -" line1s ","
+                            (number-to-string (- (string-to-number line1e)
+                                                 (string-to-number line1s)
+                                                 -1))
+                            " +" line2s ","
+                            (number-to-string (- (string-to-number line2e)
+                                                 (string-to-number line2s)
+                                                 -1)) " @@"))
+                  (set-marker pt2 nil)
+                  ;; The whole procedure succeeded, let's replace the myriad
+                  ;; of undo elements with just a single special one.
+                  (unless (or (not reversible) (eq buffer-undo-list t))
+                    (setq buffer-undo-list
+                          (cons (list 'apply (- old-end end) pt1 (point)
+                                      'diff-unified->context pt1 (point))
+                                old-undo)))
+                  )))))))))
 
 (defun diff-reverse-direction (start end)
   "Reverse the direction of the diffs.
@@ -1610,6 +1634,63 @@
       (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-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 ()
+  "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)))))))
+
+
 ;; provide the package
 (provide 'diff-mode)