]> git.eshelyaron.com Git - emacs.git/commitdiff
(diff-hunk-style): New fun.
authorStefan Monnier <monnier@iro.umontreal.ca>
Tue, 9 Oct 2007 04:12:24 +0000 (04:12 +0000)
committerStefan Monnier <monnier@iro.umontreal.ca>
Tue, 9 Oct 2007 04:12:24 +0000 (04:12 +0000)
(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.

etc/NEWS
lisp/ChangeLog
lisp/diff-mode.el

index 5de5c3b41fc8591fecc5575d71c97b462939e18c..5adc1bb886b70dab841e98d233831d74f568a574 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -152,6 +152,7 @@ its usage.
 \f
 * 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.
 
index 8ca6f4698e79def048b76f1608ff04f224e8d3eb..2c73e42344b204d9e43d4d122f7f67d5c1fa786b 100644 (file)
@@ -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.
index d4244126f235e3d645209e9b18ecac5b3adddca2..706a532938dab1bd69ec615ee18999154dd65a50 100644 (file)
@@ -386,12 +386,15 @@ when editing big diffs)."
 (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 @@ With a prefix argument, convert unified format to context format."
       (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 @@ For use in `add-log-current-defun-function'."
       (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)