summaryrefslogtreecommitdiff
path: root/lisp/diff-mode.el
diff options
context:
space:
mode:
authorMiles Bader <miles@gnu.org>2000-09-11 13:51:21 +0000
committerMiles Bader <miles@gnu.org>2000-09-11 13:51:21 +0000
commit7530b6da4e245c83407efdddfa87b496246ab80d (patch)
tree51cba920e2b6b35a3d07f981f3e341f187698ba1 /lisp/diff-mode.el
parentf74b07059d4bd017536a77a792b0575d6c2f1a1b (diff)
downloademacs-7530b6da4e245c83407efdddfa87b496246ab80d.tar.gz
(diff-apply-hunk): Function basically rewritten. Now understands
non-unified diffs. Some functionality moved into `diff-hunk-text' and `diff-find-text'. Add OTHER-FILE, DRY-RUN, POPUP, and NOERROR arguments. If DRY-RUN is true, don't actually modify anything. Only reposition point in the patched file if the patch succeeds. Only pop up another window if POPUP is true. Emit a message describing what happened if successful, and at what line-offset. Automatically detect reversed hunks and do something appropriate. (diff-hunk-text, diff-find-text): New functions. (diff-filter-lines): Function removed. (diff-test-hunk): New function. (diff-goto-source): Rewritten in terms of diff-apply-hunk.
Diffstat (limited to 'lisp/diff-mode.el')
-rw-r--r--lisp/diff-mode.el269
1 files changed, 210 insertions, 59 deletions
diff --git a/lisp/diff-mode.el b/lisp/diff-mode.el
index b737754f3db..5e5d4d7e625 100644
--- a/lisp/diff-mode.el
+++ b/lisp/diff-mode.el
@@ -4,7 +4,7 @@
;; Author: Stefan Monnier <monnier@cs.yale.edu>
;; Keywords: patch diff
-;; Revision: $Id: diff-mode.el,v 1.9 2000/08/16 19:56:10 monnier Exp $
+;; Revision: $Id: diff-mode.el,v 1.11 2000/09/07 20:14:27 fx Exp $
;; This file is part of GNU Emacs.
@@ -42,7 +42,6 @@
;; - Reverse doesn't work with normal diffs.
;; - (nitpick) The mark is not always quite right in diff-goto-source.
-;; - diff-apply-hunk only works on unified diffs.
;; Todo:
@@ -471,21 +470,6 @@ Non-nil OLD means that we want the old file."
(if (null file) (error "Can't find the file")
(list file line span)))))
-(defun diff-goto-source (&optional other-file)
- "Jump to the corresponding source line.
-`diff-jump-to-old-file-flag' (or its opposite if the OTHER-FILE prefix arg
-is give) determines whether to jump to the old or the new file.
-If the prefix arg is bigger than 8 (for example with \\[universal-argument] \\[universal-argument])
- then `diff-jump-to-old-file-flag' is also set, for the next invocations."
- (interactive "P")
- (save-excursion
- (let ((loc (diff-find-source-location other-file)))
- (pop-to-buffer (find-file-noselect (car loc)))
- (ignore-errors
- (goto-line (+ (cadr loc) (caddr loc)))
- (push-mark (point) t t)
- (goto-line (cadr loc))))))
-
(defun diff-mouse-goto-source (event)
"Run `diff-goto-source' for the diff at a mouse click."
(interactive "e")
@@ -896,57 +880,224 @@ Only works for unified diffs."
nil t)
(equal (match-string 1) (match-string 2)))))
-
-(defun diff-filter-lines (char)
+(defun diff-hunk-text (hunk dest)
+ "Returns the literal source text from HUNK, if DEST is nil, otherwise
+the destination text."
+ (with-current-buffer "foo"
+ (erase-buffer)
+ (insert hunk)
+ (goto-char (point-min))
+ (let ((src nil)
+ (dst nil)
+ (divider nil)
+ (num-pfx-chars 2))
+ (cond ((looking-at "^@@")
+ ;; unified diff
+ (setq num-pfx-chars 1)
+ (forward-line 1)
+ (setq src (point) dst (point)))
+ ((looking-at "^\\*\\*")
+ ;; context diff
+ (forward-line 2)
+ (setq src (point))
+ (re-search-forward "^--- " nil t)
+ (forward-line 0)
+ (setq divider (point))
+ (forward-line 1)
+ (setq dst (point)))
+ ((looking-at "^[0-9]+a[0-9,]+$")
+ ;; normal diff, insert
+ (forward-line 1)
+ (setq dst (point)))
+ ((looking-at "^[0-9,]+d[0-9]+$")
+ ;; normal diff, delete
+ (forward-line 1)
+ (setq src (point)))
+ ((looking-at "^[0-9,]+c[0-9,]+$")
+ ;; normal diff, change
+ (forward-line 1)
+ (setq src (point))
+ (re-search-forward "^---$" nil t)
+ (forward-line 0)
+ (setq divider (point))
+ (forward-line 1)
+ (setq dst (point)))
+ (t
+ (error "Unknown diff hunk type")))
+
+ (if (if dest (null dst) (null src))
+ ;; Implied empty text
+ ""
+
+ ;; Explicit text
+
+ ;; Delete unused text region
+ (let ((keep (if dest dst src))
+ (kill (or divider (if dest src dst))))
+ (when (and kill (> kill keep))
+ (delete-region kill (point-max)))
+ (delete-region (point-min) keep))
+
+ ;; Remove line-prefix characters, and unneeded lines (for
+ ;; unified diffs).
+ (let ((kill-char (if dest ?- ?+)))
(goto-char (point-min))
(while (not (eobp))
- (if (eq (char-after) char)
+ (if (eq (char-after) kill-char)
(delete-region (point) (progn (forward-line 1) (point)))
- (delete-char 1)
+ (delete-char num-pfx-chars)
(forward-line 1))))
-(defun diff-apply-hunk (&optional reverse)
- "Apply the current hunk.
-With a prefix argument, REVERSE the hunk.
-FIXME: Only works for unified diffs."
- (interactive "P")
- (save-excursion
- (let ((loc (diff-find-source-location nil)))
- (diff-beginning-of-hunk)
- (unless (looking-at diff-hunk-header-re) (error "Help! Mom!"))
- (goto-char (1+ (match-end 0)))
- ;; Extract the SRC and DEST strings.
- (let ((text (buffer-substring (point) (progn (diff-end-of-hunk) (point))))
- src dest)
- (with-temp-buffer
- (insert text)
- (diff-filter-lines ?+)
- (setq src (buffer-string))
- (erase-buffer)
- (insert text)
- (diff-filter-lines ?-)
- (setq dest (buffer-string)))
- ;; Exchange the two strings if we're reversing the patch.
- (if reverse (let ((tmp src)) (setq src dest) (setq dest tmp)))
- ;; Look for SRC in the file.
- (pop-to-buffer (find-file-noselect (car loc)))
- (goto-line (cadr loc))
- (let* ((pos (point))
- (forw (and (search-forward src nil t)
+ (buffer-substring-no-properties (point-min) (point-max))))))
+
+(defun diff-find-text (text line)
+ "Return the buffer position of the nearest occurance of TEXT to line LINE.
+If TEXT isn't found, nil is returned."
+ (goto-line line)
+ (let* ((orig (point))
+ (forw (and (search-forward text nil t)
(match-beginning 0)))
- (back (and (goto-char (+ pos (length src)))
- (search-backward src nil t)
+ (back (and (goto-char (+ orig (length text)))
+ (search-backward text nil t)
(match-beginning 0))))
;; Choose the closest match.
- (setq pos (if (and forw back)
- (if (> (- forw pos) (- pos back)) back forw)
- (or back forw)))
- (unless pos (error "Can't find the text to patch"))
- ;; Do it!
- (goto-char pos)
- (delete-char (length src))
- (insert dest))))))
+ (if (and forw back)
+ (if (> (- forw orig) (- orig back)) back forw)
+ (or back forw))))
+
+(defun diff-apply-hunk (&optional reverse other-file dry-run popup noerror)
+ "Apply the current hunk to the source file.
+By default, the new source file is patched, but if the variable
+`diff-jump-to-old-file-flag' is non-nil, then the old source file is
+patched instead (some commands, such as `diff-goto-source' can change
+the value of this variable when given an appropriate prefix argument).
+
+With a prefix argument, REVERSE the hunk.
+If OTHER-FILE is non-nil, patch the old file by default, and reverse the
+ sense of `diff-jump-to-old-file-flag'.
+If DRY-RUN is non-nil, don't actually modify anything, just see whether
+ it's possible to do so.
+If POPUP is non-nil, pop up the patched file in another window; if POPUP
+ is `select' then select the new window too.
+If NOERROR is non-nil, then no error is signaled in the case where the hunk
+ cannot be found in the source file (other errors may still be signaled).
+
+Return values are `t' if the hunk was sucessfully applied (or could be
+applied, in the case where DRY-RUN was non-nil), `reversed' if the hunk
+was applied backwards, or nil if the hunk couldn't be found and NOERROR
+was non-nil."
+ (interactive (list current-prefix-arg nil nil t))
+
+ (when other-file
+ ;; OTHER-FILE inverts the sense of the hunk
+ (setq reverse (not reverse)))
+ (when diff-jump-to-old-file-flag
+ ;; The global variable `diff-jump-to-old-file-flag' inverts the
+ ;; sense of OTHER-FILE (in `diff-find-source-location')
+ (setq reverse (not reverse)))
+
+ (let* ((loc (diff-find-source-location other-file))
+ (buf (find-file-noselect (car loc)))
+ (patch-line (cadr loc))
+ (hunk
+ (save-excursion
+ (diff-beginning-of-hunk)
+ (unless (looking-at diff-hunk-header-re)
+ (error "Malformed hunk"))
+ (buffer-substring (point) (progn (diff-end-of-hunk) (point)))))
+ (src (diff-hunk-text hunk reverse))
+ (dst (diff-hunk-text hunk (not reverse)))
+ (pos
+ (with-current-buffer buf (diff-find-text src patch-line)))
+ (reversed-pos
+ (and (null pos)
+ (with-current-buffer buf (diff-find-text dst patch-line)))))
+
+ (when (and reversed-pos popup)
+ ;; A reversed patch was detected, perhaps apply it in reverse
+ ;; (this is only done in `interactive' mode, when POPUP is non-nil).
+ (if (or dry-run
+ (save-window-excursion
+ (pop-to-buffer buf)
+ (goto-char reversed-pos)
+ (if reverse
+ (y-or-n-p
+ "Hunk hasn't been applied yet, so can't reverse it; apply it now? ")
+ (y-or-n-p "Hunk has already been applied; undo it? "))))
+
+ ;; Set up things to reverse the diff
+ (let ((swap dst))
+ (setq pos reversed-pos)
+ (setq src dst)
+ (setq dst swap))
+
+ ;; The user has chosen not to apply the reversed hunk, but we
+ ;; don't want to given an error message, so set things up so
+ ;; nothing else gets done down below
+ (message "(Nothing done)")
+ (setq noerror t)))
+
+ (if (null pos)
+ ;; POS is nil, so we couldn't find the source text.
+ (unless noerror
+ (error "Can't find the text to patch"))
+
+ (let ((reversed (if reversed-pos (not reverse) reverse)))
+ (unless dry-run
+ ;; Apply the hunk
+ (with-current-buffer buf
+ (goto-char pos)
+ (delete-char (length src))
+ (insert dst)))
+
+ (when popup
+ ;; Show a message describing what was done
+ (let ((real-line
+ (1+ (with-current-buffer buf (count-lines (point-min) pos))))
+ (msg
+ (if dry-run
+ (if reversed "already applied" "not yet applied")
+ (if reversed "undone" "applied"))))
+ (cond ((= real-line patch-line)
+ (message "Hunk %s" msg))
+ ((= real-line (1+ patch-line))
+ (message "Hunk %s at offset 1 line" msg))
+ (t
+ (message "Hunk %s at offset %d lines"
+ msg
+ (- real-line patch-line)))))
+
+ ;; Display BUF in a window, and maybe select it
+ (cond ((eq popup 'select)
+ (pop-to-buffer buf)
+ (goto-char pos))
+ (t
+ (set-window-point (display-buffer buf) pos))))
+
+ ;; Return an appropriate indicator of success
+ (if reversed 'reversed t)))))
+
+(defun diff-test-hunk (&optional reverse)
+ "See whether it's possible to apply the current hunk.
+With a prefix argument, REVERSE the hunk."
+ (interactive "P")
+ (diff-apply-hunk reverse nil t t))
+
+(defun diff-goto-source (&optional other-file)
+ "Jump to the corresponding source line.
+`diff-jump-to-old-file-flag' (or its opposite if the OTHER-FILE prefix arg
+is give) determines whether to jump to the old or the new file.
+If the prefix arg is bigger than 8 (for example with \\[universal-argument] \\[universal-argument])
+ then `diff-jump-to-old-file-flag' is also set, for the next invocations."
+ (interactive "P")
+ (or (diff-apply-hunk nil other-file t 'select t)
+ ;; couldn't actually find the hunk, just obey the hunk line number
+ (let ((loc (diff-find-source-location other-file)))
+ (find-file-other-window (car loc))
+ (goto-line (cadr loc))
+ (error "Hunk text not found"))))
+
;; provide the package
(provide 'diff-mode)