comparison lisp/diff-mode.el @ 31538:46aca282e6b0

(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.
author Miles Bader <miles@gnu.org>
date Mon, 11 Sep 2000 13:49:38 +0000
parents d58461428926
children 74d7a9c42c2a
comparison
equal deleted inserted replaced
31537:6f330e666e31 31538:46aca282e6b0
2 2
3 ;; Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc. 3 ;; Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc.
4 4
5 ;; Author: Stefan Monnier <monnier@cs.yale.edu> 5 ;; Author: Stefan Monnier <monnier@cs.yale.edu>
6 ;; Keywords: patch diff 6 ;; Keywords: patch diff
7 ;; Revision: $Id: diff-mode.el,v 1.9 2000/08/16 19:56:10 monnier Exp $ 7 ;; Revision: $Id: diff-mode.el,v 1.11 2000/09/07 20:14:27 fx Exp $
8 8
9 ;; This file is part of GNU Emacs. 9 ;; This file is part of GNU Emacs.
10 10
11 ;; GNU Emacs is free software; you can redistribute it and/or modify 11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by 12 ;; it under the terms of the GNU General Public License as published by
40 40
41 ;; Bugs: 41 ;; Bugs:
42 42
43 ;; - Reverse doesn't work with normal diffs. 43 ;; - Reverse doesn't work with normal diffs.
44 ;; - (nitpick) The mark is not always quite right in diff-goto-source. 44 ;; - (nitpick) The mark is not always quite right in diff-goto-source.
45 ;; - diff-apply-hunk only works on unified diffs.
46 45
47 ;; Todo: 46 ;; Todo:
48 47
49 ;; - Add change-log support. 48 ;; - Add change-log support.
50 ;; - Spice up the minor-mode with font-lock support. 49 ;; - Spice up the minor-mode with font-lock support.
468 ;; Update the user preference if he so wished. 467 ;; Update the user preference if he so wished.
469 (when (> (prefix-numeric-value other-file) 8) 468 (when (> (prefix-numeric-value other-file) 8)
470 (setq diff-jump-to-old-file-flag old)) 469 (setq diff-jump-to-old-file-flag old))
471 (if (null file) (error "Can't find the file") 470 (if (null file) (error "Can't find the file")
472 (list file line span))))) 471 (list file line span)))))
473
474 (defun diff-goto-source (&optional other-file)
475 "Jump to the corresponding source line.
476 `diff-jump-to-old-file-flag' (or its opposite if the OTHER-FILE prefix arg
477 is give) determines whether to jump to the old or the new file.
478 If the prefix arg is bigger than 8 (for example with \\[universal-argument] \\[universal-argument])
479 then `diff-jump-to-old-file-flag' is also set, for the next invocations."
480 (interactive "P")
481 (save-excursion
482 (let ((loc (diff-find-source-location other-file)))
483 (pop-to-buffer (find-file-noselect (car loc)))
484 (ignore-errors
485 (goto-line (+ (cadr loc) (caddr loc)))
486 (push-mark (point) t t)
487 (goto-line (cadr loc))))))
488 472
489 (defun diff-mouse-goto-source (event) 473 (defun diff-mouse-goto-source (event)
490 "Run `diff-goto-source' for the diff at a mouse click." 474 "Run `diff-goto-source' for the diff at a mouse click."
491 (interactive "e") 475 (interactive "e")
492 (save-excursion 476 (save-excursion
894 (while 878 (while
895 (and (re-search-forward "^@@ [-0-9]+,\\([0-9]+\\) [+0-9]+,\\([0-9]+\\) @@" 879 (and (re-search-forward "^@@ [-0-9]+,\\([0-9]+\\) [+0-9]+,\\([0-9]+\\) @@"
896 nil t) 880 nil t)
897 (equal (match-string 1) (match-string 2))))) 881 (equal (match-string 1) (match-string 2)))))
898 882
899 883 (defun diff-hunk-text (hunk dest)
900 (defun diff-filter-lines (char) 884 "Returns the literal source text from HUNK, if DEST is nil, otherwise
885 the destination text."
886 (with-current-buffer "foo"
887 (erase-buffer)
888 (insert hunk)
889 (goto-char (point-min))
890 (let ((src nil)
891 (dst nil)
892 (divider nil)
893 (num-pfx-chars 2))
894 (cond ((looking-at "^@@")
895 ;; unified diff
896 (setq num-pfx-chars 1)
897 (forward-line 1)
898 (setq src (point) dst (point)))
899 ((looking-at "^\\*\\*")
900 ;; context diff
901 (forward-line 2)
902 (setq src (point))
903 (re-search-forward "^--- " nil t)
904 (forward-line 0)
905 (setq divider (point))
906 (forward-line 1)
907 (setq dst (point)))
908 ((looking-at "^[0-9]+a[0-9,]+$")
909 ;; normal diff, insert
910 (forward-line 1)
911 (setq dst (point)))
912 ((looking-at "^[0-9,]+d[0-9]+$")
913 ;; normal diff, delete
914 (forward-line 1)
915 (setq src (point)))
916 ((looking-at "^[0-9,]+c[0-9,]+$")
917 ;; normal diff, change
918 (forward-line 1)
919 (setq src (point))
920 (re-search-forward "^---$" nil t)
921 (forward-line 0)
922 (setq divider (point))
923 (forward-line 1)
924 (setq dst (point)))
925 (t
926 (error "Unknown diff hunk type")))
927
928 (if (if dest (null dst) (null src))
929 ;; Implied empty text
930 ""
931
932 ;; Explicit text
933
934 ;; Delete unused text region
935 (let ((keep (if dest dst src))
936 (kill (or divider (if dest src dst))))
937 (when (and kill (> kill keep))
938 (delete-region kill (point-max)))
939 (delete-region (point-min) keep))
940
941 ;; Remove line-prefix characters, and unneeded lines (for
942 ;; unified diffs).
943 (let ((kill-char (if dest ?- ?+)))
901 (goto-char (point-min)) 944 (goto-char (point-min))
902 (while (not (eobp)) 945 (while (not (eobp))
903 (if (eq (char-after) char) 946 (if (eq (char-after) kill-char)
904 (delete-region (point) (progn (forward-line 1) (point))) 947 (delete-region (point) (progn (forward-line 1) (point)))
905 (delete-char 1) 948 (delete-char num-pfx-chars)
906 (forward-line 1)))) 949 (forward-line 1))))
907 950
908 (defun diff-apply-hunk (&optional reverse) 951 (buffer-substring-no-properties (point-min) (point-max))))))
909 "Apply the current hunk. 952
910 With a prefix argument, REVERSE the hunk. 953 (defun diff-find-text (text line)
911 FIXME: Only works for unified diffs." 954 "Return the buffer position of the nearest occurance of TEXT to line LINE.
912 (interactive "P") 955 If TEXT isn't found, nil is returned."
913 (save-excursion 956 (goto-line line)
914 (let ((loc (diff-find-source-location nil))) 957 (let* ((orig (point))
915 (diff-beginning-of-hunk) 958 (forw (and (search-forward text nil t)
916 (unless (looking-at diff-hunk-header-re) (error "Help! Mom!"))
917 (goto-char (1+ (match-end 0)))
918 ;; Extract the SRC and DEST strings.
919 (let ((text (buffer-substring (point) (progn (diff-end-of-hunk) (point))))
920 src dest)
921 (with-temp-buffer
922 (insert text)
923 (diff-filter-lines ?+)
924 (setq src (buffer-string))
925 (erase-buffer)
926 (insert text)
927 (diff-filter-lines ?-)
928 (setq dest (buffer-string)))
929 ;; Exchange the two strings if we're reversing the patch.
930 (if reverse (let ((tmp src)) (setq src dest) (setq dest tmp)))
931 ;; Look for SRC in the file.
932 (pop-to-buffer (find-file-noselect (car loc)))
933 (goto-line (cadr loc))
934 (let* ((pos (point))
935 (forw (and (search-forward src nil t)
936 (match-beginning 0))) 959 (match-beginning 0)))
937 (back (and (goto-char (+ pos (length src))) 960 (back (and (goto-char (+ orig (length text)))
938 (search-backward src nil t) 961 (search-backward text nil t)
939 (match-beginning 0)))) 962 (match-beginning 0))))
940 ;; Choose the closest match. 963 ;; Choose the closest match.
941 (setq pos (if (and forw back) 964 (if (and forw back)
942 (if (> (- forw pos) (- pos back)) back forw) 965 (if (> (- forw orig) (- orig back)) back forw)
943 (or back forw))) 966 (or back forw))))
944 (unless pos (error "Can't find the text to patch")) 967
945 ;; Do it! 968 (defun diff-apply-hunk (&optional reverse other-file dry-run popup noerror)
946 (goto-char pos) 969 "Apply the current hunk to the source file.
947 (delete-char (length src)) 970 By default, the new source file is patched, but if the variable
948 (insert dest)))))) 971 `diff-jump-to-old-file-flag' is non-nil, then the old source file is
972 patched instead (some commands, such as `diff-goto-source' can change
973 the value of this variable when given an appropriate prefix argument).
974
975 With a prefix argument, REVERSE the hunk.
976 If OTHER-FILE is non-nil, patch the old file by default, and reverse the
977 sense of `diff-jump-to-old-file-flag'.
978 If DRY-RUN is non-nil, don't actually modify anything, just see whether
979 it's possible to do so.
980 If POPUP is non-nil, pop up the patched file in another window; if POPUP
981 is `select' then select the new window too.
982 If NOERROR is non-nil, then no error is signaled in the case where the hunk
983 cannot be found in the source file (other errors may still be signaled).
984
985 Return values are `t' if the hunk was sucessfully applied (or could be
986 applied, in the case where DRY-RUN was non-nil), `reversed' if the hunk
987 was applied backwards, or nil if the hunk couldn't be found and NOERROR
988 was non-nil."
989 (interactive (list current-prefix-arg nil nil t))
990
991 (when other-file
992 ;; OTHER-FILE inverts the sense of the hunk
993 (setq reverse (not reverse)))
994 (when diff-jump-to-old-file-flag
995 ;; The global variable `diff-jump-to-old-file-flag' inverts the
996 ;; sense of OTHER-FILE (in `diff-find-source-location')
997 (setq reverse (not reverse)))
998
999 (let* ((loc (diff-find-source-location other-file))
1000 (buf (find-file-noselect (car loc)))
1001 (patch-line (cadr loc))
1002 (hunk
1003 (save-excursion
1004 (diff-beginning-of-hunk)
1005 (unless (looking-at diff-hunk-header-re)
1006 (error "Malformed hunk"))
1007 (buffer-substring (point) (progn (diff-end-of-hunk) (point)))))
1008 (src (diff-hunk-text hunk reverse))
1009 (dst (diff-hunk-text hunk (not reverse)))
1010 (pos
1011 (with-current-buffer buf (diff-find-text src patch-line)))
1012 (reversed-pos
1013 (and (null pos)
1014 (with-current-buffer buf (diff-find-text dst patch-line)))))
1015
1016 (when (and reversed-pos popup)
1017 ;; A reversed patch was detected, perhaps apply it in reverse
1018 ;; (this is only done in `interactive' mode, when POPUP is non-nil).
1019 (if (or dry-run
1020 (save-window-excursion
1021 (pop-to-buffer buf)
1022 (goto-char reversed-pos)
1023 (if reverse
1024 (y-or-n-p
1025 "Hunk hasn't been applied yet, so can't reverse it; apply it now? ")
1026 (y-or-n-p "Hunk has already been applied; undo it? "))))
1027
1028 ;; Set up things to reverse the diff
1029 (let ((swap dst))
1030 (setq pos reversed-pos)
1031 (setq src dst)
1032 (setq dst swap))
1033
1034 ;; The user has chosen not to apply the reversed hunk, but we
1035 ;; don't want to given an error message, so set things up so
1036 ;; nothing else gets done down below
1037 (message "(Nothing done)")
1038 (setq noerror t)))
1039
1040 (if (null pos)
1041 ;; POS is nil, so we couldn't find the source text.
1042 (unless noerror
1043 (error "Can't find the text to patch"))
1044
1045 (let ((reversed (if reversed-pos (not reverse) reverse)))
1046 (unless dry-run
1047 ;; Apply the hunk
1048 (with-current-buffer buf
1049 (goto-char pos)
1050 (delete-char (length src))
1051 (insert dst)))
1052
1053 (when popup
1054 ;; Show a message describing what was done
1055 (let ((real-line
1056 (1+ (with-current-buffer buf (count-lines (point-min) pos))))
1057 (msg
1058 (if dry-run
1059 (if reversed "already applied" "not yet applied")
1060 (if reversed "undone" "applied"))))
1061 (cond ((= real-line patch-line)
1062 (message "Hunk %s" msg))
1063 ((= real-line (1+ patch-line))
1064 (message "Hunk %s at offset 1 line" msg))
1065 (t
1066 (message "Hunk %s at offset %d lines"
1067 msg
1068 (- real-line patch-line)))))
1069
1070 ;; Display BUF in a window, and maybe select it
1071 (cond ((eq popup 'select)
1072 (pop-to-buffer buf)
1073 (goto-char pos))
1074 (t
1075 (set-window-point (display-buffer buf) pos))))
1076
1077 ;; Return an appropriate indicator of success
1078 (if reversed 'reversed t)))))
949 1079
1080
1081 (defun diff-test-hunk (&optional reverse)
1082 "See whether it's possible to apply the current hunk.
1083 With a prefix argument, REVERSE the hunk."
1084 (interactive "P")
1085 (diff-apply-hunk reverse nil t t))
1086
1087 (defun diff-goto-source (&optional other-file)
1088 "Jump to the corresponding source line.
1089 `diff-jump-to-old-file-flag' (or its opposite if the OTHER-FILE prefix arg
1090 is give) determines whether to jump to the old or the new file.
1091 If the prefix arg is bigger than 8 (for example with \\[universal-argument] \\[universal-argument])
1092 then `diff-jump-to-old-file-flag' is also set, for the next invocations."
1093 (interactive "P")
1094 (or (diff-apply-hunk nil other-file t 'select t)
1095 ;; couldn't actually find the hunk, just obey the hunk line number
1096 (let ((loc (diff-find-source-location other-file)))
1097 (find-file-other-window (car loc))
1098 (goto-line (cadr loc))
1099 (error "Hunk text not found"))))
1100
950 1101
951 ;; provide the package 1102 ;; provide the package
952 (provide 'diff-mode) 1103 (provide 'diff-mode)
953 1104
954 ;;; Old Change Log from when diff-mode wasn't part of Emacs: 1105 ;;; Old Change Log from when diff-mode wasn't part of Emacs: