changeset 80151:533d83b5f687

Make it more robust in the presence of empty context lines in unified hunks. (diff-valid-unified-empty-line): New var. (diff-unified->context, diff-sanity-check-hunk): Obey it. (diff-end-of-hunk): Obey it. New arg `donttrustheader'. (diff-fixup-modifs, diff-post-command-hook): Use this new arg. (diff-hunk-header-re-unified): New const. (diff-font-lock-keywords, diff-hunk-header-re, diff-split-hunk) (diff-fixup-modifs, diff-unified->context, diff-next-complex-hunk) (diff-sanity-check-hunk): Use it.
author Stefan Monnier <monnier@iro.umontreal.ca>
date Tue, 19 Feb 2008 19:44:48 +0000
parents 761dbf0b9e96
children f200e5de5464
files lisp/ChangeLog lisp/diff-mode.el
diffstat 2 files changed, 93 insertions(+), 40 deletions(-) [+]
line wrap: on
line diff
--- a/lisp/ChangeLog	Tue Feb 19 18:14:05 2008 +0000
+++ b/lisp/ChangeLog	Tue Feb 19 19:44:48 2008 +0000
@@ -1,3 +1,16 @@
+2008-02-19  Stefan Monnier  <monnier@iro.umontreal.ca>
+
+	* diff-mode.el: Make it more robust in the presence of empty context
+	lines in unified hunks.
+	(diff-valid-unified-empty-line): New var.
+	(diff-unified->context, diff-sanity-check-hunk): Obey it.
+	(diff-end-of-hunk): Obey it.  New arg `donttrustheader'.
+	(diff-fixup-modifs, diff-post-command-hook): Use this new arg.
+	(diff-hunk-header-re-unified): New const.
+	(diff-font-lock-keywords, diff-hunk-header-re, diff-split-hunk)
+	(diff-fixup-modifs, diff-unified->context, diff-next-complex-hunk)
+	(diff-sanity-check-hunk): Use it.
+
 2008-02-19  Nick Roberts  <nickrob@snap.net.nz>
 
 	* progmodes/gdb-ui.el (gdba): Recreate as an alias for gdb.
--- a/lisp/diff-mode.el	Tue Feb 19 18:14:05 2008 +0000
+++ b/lisp/diff-mode.el	Tue Feb 19 19:44:48 2008 +0000
@@ -341,10 +341,12 @@
 	  (while (re-search-backward re start t)
 	    (replace-match "" t t)))))))
 
+(defconst diff-hunk-header-re-unified
+  "^@@ -\\([0-9]+\\),\\([0-9]+\\) \\+\\([0-9]+\\),\\([0-9]+\\) @@")
 
 (defvar diff-font-lock-keywords
-  `(("^\\(@@ -[0-9,]+ \\+[0-9,]+ @@\\)\\(.*\\)$"          ;unified
-     (1 diff-hunk-header-face) (2 diff-function-face))
+  `((,(concat "\\(" diff-hunk-header-re-unified "\\)\\(.*\\)$")
+     (1 diff-hunk-header-face) (6 diff-function-face))
     ("^\\(\\*\\{15\\}\\)\\(.*\\)$"                        ;context
      (1 diff-hunk-header-face) (2 diff-function-face))
     ("^\\*\\*\\* .+ \\*\\*\\*\\*". diff-hunk-header-face) ;context
@@ -381,25 +383,51 @@
 ;;;; Movement
 ;;;;
 
-(defconst diff-hunk-header-re "^\\(@@ -[0-9,]+ \\+[0-9,]+ @@.*\\|\\*\\{15\\}.*\n\\*\\*\\* .+ \\*\\*\\*\\*\\|[0-9]+\\(,[0-9]+\\)?[acd][0-9]+\\(,[0-9]+\\)?\\)$")
+(defvar diff-valid-unified-empty-line t
+  "If non-nil, empty lines are valid in unified diffs.
+Some versions of diff replace all-blank context lines in unified format with
+empty lines.  This makes the format less robust, but is tolerated.
+See http://lists.gnu.org/archive/html/emacs-devel/2007-11/msg01990.html")
+
+(defconst diff-hunk-header-re
+  (concat "^\\(?:" diff-hunk-header-re-unified ".*\\|\\*\\{15\\}.*\n\\*\\*\\* .+ \\*\\*\\*\\*\\|[0-9]+\\(,[0-9]+\\)?[acd][0-9]+\\(,[0-9]+\\)?\\)$"))
 (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)
-  (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))))))
-    (goto-char (match-end 0)))
-  (let ((end (and (re-search-forward (case style
-				       ;; A `unified' header is ambiguous.
-				       (unified (concat "^[^-+# \\]\\|"
-							diff-file-header-re))
-				       (context "^[^-+#! \\]")
-				       (normal "^[^<>#\\]")
-				       (t "^[^-+#!<> \\]"))
-				     nil t)
-		  (match-beginning 0))))
+(defun diff-end-of-hunk (&optional style donttrustheader)
+  (let (end)
+    (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))))))
+      (goto-char (match-end 0))
+      (when (and (not donttrustheader) (match-end 2))
+        (save-excursion
+          (re-search-forward (if diff-valid-unified-empty-line
+                                 "^[- \n]" "^[- ]")
+                             nil t
+                             (string-to-number (match-string 2)))
+          (setq end (line-beginning-position 2)))))
+    ;; We may have a first evaluation of `end' thanks to the hunk header.
+    (unless end
+      (setq end (and (re-search-forward
+                      (case style
+                        (unified (concat (if diff-valid-unified-empty-line
+                                             "^[^-+# \\\n]\\|" "^[^-+# \\]\\|")
+                                         ;; A `unified' header is ambiguous.
+                                         diff-file-header-re))
+                        (context "^[^-+#! \\]")
+                        (normal "^[^<>#\\]")
+                        (t "^[^-+#!<> \\]"))
+                      nil t)
+                     (match-beginning 0)))
+      (when diff-valid-unified-empty-line
+        ;; While empty lines may be valid inside hunks, they are also likely
+        ;; to be unrelated to the hunk.
+        (goto-char (or end (point-max)))
+        (while (eq ?\n (char-before (1- (point))))
+          (forward-char -1)
+          (setq end (point)))))
     ;; The return value is used by easy-mmode-define-navigation.
     (goto-char (or end (point-max)))))
 
@@ -537,11 +565,11 @@
   (beginning-of-line)
   (let ((pos (point))
 	(start (progn (diff-beginning-of-hunk) (point))))
-    (unless (looking-at "@@ -\\([0-9]+\\),[0-9]+ \\+\\([0-9]+\\),[0-9]+ @@")
+    (unless (looking-at diff-hunk-header-re-unified)
       (error "diff-split-hunk only works on unified context diffs"))
     (forward-line 1)
     (let* ((start1 (string-to-number (match-string 1)))
-	   (start2 (string-to-number (match-string 2)))
+	   (start2 (string-to-number (match-string 3)))
 	   (newstart1 (+ start1 (diff-count-matches "^[- \t]" (point) pos)))
 	   (newstart2 (+ start2 (diff-count-matches "^[+ \t]" (point) pos)))
 	   (inhibit-read-only t))
@@ -699,7 +727,10 @@
 	(inhibit-read-only t))
     (save-excursion
       (goto-char start)
-      (while (and (re-search-forward "^\\(\\(---\\) .+\n\\(\\+\\+\\+\\) .+\\|@@ -\\([0-9]+\\),\\([0-9]+\\) \\+\\([0-9]+\\),\\([0-9]+\\) @@.*\\)$" nil t)
+      (while (and (re-search-forward
+                   (concat "^\\(\\(---\\) .+\n\\(\\+\\+\\+\\) .+\\|"
+                           diff-hunk-header-re-unified ".*\\)$")
+                   nil t)
 		  (< (point) end))
 	(combine-after-change-calls
 	  (if (match-beginning 2)
@@ -718,9 +749,11 @@
 		       (number-to-string (+ (string-to-number line1)
 					    (string-to-number lines1)
 					    -1)) " ****"))
-	      (forward-line 1)
 	      (save-restriction
-		(narrow-to-region (point)
+		(narrow-to-region (line-beginning-position 2)
+                                  ;; Call diff-end-of-hunk from just before
+                                  ;; the hunk header so it can use the hunk
+                                  ;; header info.
 				  (progn (diff-end-of-hunk 'unified) (point)))
 		(let ((hunk (buffer-string)))
 		  (goto-char (point-min))
@@ -742,6 +775,8 @@
 			  (?\\ (when (save-excursion (forward-line -1)
 						     (= (char-after) ?+))
 				 (delete-region (point) last-pt) (setq modif t)))
+                          ;; diff-valid-unified-empty-line.
+                          (?\n (insert "  ") (setq modif nil) (backward-char 2))
 			  (t (setq modif nil))))))
 		  (goto-char (point-max))
 		  (save-excursion
@@ -767,6 +802,8 @@
 			  (?\\ (when (save-excursion (forward-line 1)
 						     (not (eobp)))
 				 (setq delete t) (setq modif t)))
+                          ;; diff-valid-unified-empty-line.
+                          (?\n (insert "  ") (setq modif nil) (backward-char 2))
 			  (t (setq modif nil)))
 			(let ((last-pt (point)))
 			  (forward-line 1)
@@ -908,7 +945,8 @@
 		       (t (when (and first last (< first last))
 			    (insert (delete-and-extract-region first last)))
 			  (setq first nil last nil)
-			  (equal ?\s c)))
+			  (memq c (if diff-valid-unified-empty-line
+                                      '(?\s ?\n) '(?\s)))))
 		(forward-line 1))))))))))
 
 (defun diff-fixup-modifs (start end)
@@ -920,11 +958,11 @@
 		 (list (point-min) (point-max))))
   (let ((inhibit-read-only t))
     (save-excursion
-      (goto-char end) (diff-end-of-hunk)
+      (goto-char end) (diff-end-of-hunk nil 'donttrustheader)
       (let ((plus 0) (minus 0) (space 0) (bang 0))
 	(while (and (= (forward-line -1) 0) (<= start (point)))
 	  (if (not (looking-at
-		    (concat "@@ -[0-9,]+ \\+[0-9,]+ @@"
+		    (concat diff-hunk-header-re-unified
 			    "\\|[-*][-*][-*] [0-9,]+ [-*][-*][-*][-*]$"
 			    "\\|--- .+\n\\+\\+\\+ ")))
 	      (case (char-after)
@@ -935,13 +973,13 @@
 		((?\\ ?#) nil)
 		(t  (setq space 0 plus 0 minus 0 bang 0)))
 	    (cond
-	     ((looking-at "@@ -[0-9]+,\\([0-9]*\\) \\+[0-9]+,\\([0-9]*\\) @@.*$")
-	      (let* ((old1 (match-string 1))
-		     (old2 (match-string 2))
+	     ((looking-at diff-hunk-header-re-unified)
+	      (let* ((old1 (match-string 2))
+		     (old2 (match-string 4))
 		     (new1 (number-to-string (+ space minus)))
 		     (new2 (number-to-string (+ space plus))))
-		(unless (string= new2 old2) (replace-match new2 t t nil 2))
-		(unless (string= new1 old1) (replace-match new1 t t nil 1))))
+		(unless (string= new2 old2) (replace-match new2 t t nil 4))
+		(unless (string= new1 old1) (replace-match new1 t t nil 2))))
 	     ((looking-at "--- \\([0-9]+\\),\\([0-9]*\\) ----$")
 	      (when (> (+ space bang plus) 0)
 		(let* ((old1 (match-string 1))
@@ -1009,7 +1047,7 @@
 	;; (diff-fixup-modifs (point) (cdr diff-unhandled-changes))
 	(diff-beginning-of-hunk)
 	(when (save-excursion
-		(diff-end-of-hunk)
+		(diff-end-of-hunk nil 'donttrustheader)
 		(>= (point) (cdr diff-unhandled-changes)))
 	  (diff-fixup-modifs (point) (cdr diff-unhandled-changes)))))
     (setq diff-unhandled-changes nil)))
@@ -1124,9 +1162,8 @@
 Only works for unified diffs."
   (interactive)
   (while
-      (and (re-search-forward "^@@ [-0-9]+,\\([0-9]+\\) [+0-9]+,\\([0-9]+\\) @@"
-			      nil t)
-	   (equal (match-string 1) (match-string 2)))))
+      (and (re-search-forward diff-hunk-header-re-unified nil t)
+	   (equal (match-string 2) (match-string 4)))))
 
 (defun diff-sanity-check-context-hunk-half (lines)
   (let ((count lines))
@@ -1175,11 +1212,10 @@
 
        ;; A unified diff.
        ((eq (char-after) ?@)
-        (if (not (looking-at
-                  "@@ -[0-9]+,\\([0-9]+\\) \\+[0-9]+,\\([0-9]+\\) @@"))
+        (if (not (looking-at diff-hunk-header-re-unified))
             (error "Unrecognized unified diff hunk header format")
-          (let ((before (string-to-number (match-string 1)))
-                (after (string-to-number (match-string 2))))
+          (let ((before (string-to-number (match-string 2)))
+                (after (string-to-number (match-string 4))))
             (forward-line)
             (while
                 (case (char-after)
@@ -1197,12 +1233,16 @@
                   (?+ (decf after) t)
                   (t
                    (cond
+                    ((and diff-valid-unified-empty-line
+                          ;; Not just (eolp) so we don't infloop at eob.
+                          (eq (char-after) ?\n))
+                     (decf before) (decf after) t)
                     ((and (zerop before) (zerop after)) nil)
                     ((or (< before 0) (< after 0))
                      (error (if (or (zerop before) (zerop after))
                                 "End of hunk ambiguously marked"
                               "Hunk seriously messed up")))
-                    ((not (y-or-n-p "Try to auto-fix whitespace loss and word-wrap damage? "))
+                    ((not (y-or-n-p (concat "Try to auto-fix " (if (eolp) "whitespace loss" "word-wrap damage") "? ")))
                      (error "Abort!"))
                     ((eolp) (insert " ") (forward-line -1) t)
                     (t (insert " ")