# HG changeset patch # User Stefan Monnier # Date 1203450288 0 # Node ID 533d83b5f68767b722dec47f9a2da96e209c36d6 # Parent 761dbf0b9e96a2f1aca3dcec82beaafec57be9ff 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. diff -r 761dbf0b9e96 -r 533d83b5f687 lisp/ChangeLog --- 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 + + * 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 * progmodes/gdb-ui.el (gdba): Recreate as an alias for gdb. diff -r 761dbf0b9e96 -r 533d83b5f687 lisp/diff-mode.el --- 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 " ")