X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/9f0c286d22c49f88af21e00532e2fd232f748c99..21f8fcfd24dc96790589ad578c7ee54593fcfd10:/lisp/smerge-mode.el diff --git a/lisp/smerge-mode.el b/lisp/smerge-mode.el index 8054782134..8d9df18887 100644 --- a/lisp/smerge-mode.el +++ b/lisp/smerge-mode.el @@ -1,9 +1,10 @@ ;;; smerge-mode.el --- Minor mode to resolve diff3 conflicts -;; Copyright (C) 1999, 2000, 01, 03, 2004 Free Software Foundation, Inc. +;; Copyright (C) 1999, 2000, 2001, 2002, 2003, +;; 2004, 2005, 2006 Free Software Foundation, Inc. -;; Author: Stefan Monnier -;; Keywords: revision-control merge diff3 cvs conflict +;; Author: Stefan Monnier +;; Keywords: tools revision-control merge diff3 cvs conflict ;; This file is part of GNU Emacs. @@ -19,8 +20,8 @@ ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs; see the file COPYING. If not, write to the -;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, -;; Boston, MA 02111-1307, USA. +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. ;;; Commentary: @@ -48,8 +49,11 @@ (eval-when-compile (require 'cl)) +;;; The real definition comes later. +(defvar smerge-mode) + (defgroup smerge () - "Minor mode to resolve diff3 conflicts." + "Minor mode to highlight and resolve diff3 conflicts." :group 'tools :prefix "smerge-") @@ -65,51 +69,70 @@ (defcustom smerge-diff-switches (append '("-d" "-b") (if (listp diff-switches) diff-switches (list diff-switches))) - "*A list of strings specifying switches to be be passed to diff. + "A list of strings specifying switches to be passed to diff. Used in `smerge-diff-base-mine' and related functions." :group 'smerge :type '(repeat string)) (defcustom smerge-auto-leave t - "*Non-nil means to leave `smerge-mode' when the last conflict is resolved." + "Non-nil means to leave `smerge-mode' when the last conflict is resolved." :group 'smerge :type 'boolean) -(defface smerge-mine-face - '((((background light)) +(defface smerge-mine + '((((min-colors 88) (background light)) + (:foreground "blue1")) + (((background light)) (:foreground "blue")) + (((min-colors 88) (background dark)) + (:foreground "cyan1")) (((background dark)) (:foreground "cyan"))) "Face for your code." :group 'smerge) -(defvar smerge-mine-face 'smerge-mine-face) +;; backward-compatibility alias +(put 'smerge-mine-face 'face-alias 'smerge-mine) +(defvar smerge-mine-face 'smerge-mine) -(defface smerge-other-face +(defface smerge-other '((((background light)) (:foreground "darkgreen")) (((background dark)) (:foreground "lightgreen"))) "Face for the other code." :group 'smerge) -(defvar smerge-other-face 'smerge-other-face) - -(defface smerge-base-face - '((((background light)) +;; backward-compatibility alias +(put 'smerge-other-face 'face-alias 'smerge-other) +(defvar smerge-other-face 'smerge-other) + +(defface smerge-base + '((((min-colors 88) (background light)) + (:foreground "red1")) + (((background light)) (:foreground "red")) (((background dark)) (:foreground "orange"))) "Face for the base code." :group 'smerge) -(defvar smerge-base-face 'smerge-base-face) +;; backward-compatibility alias +(put 'smerge-base-face 'face-alias 'smerge-base) +(defvar smerge-base-face 'smerge-base) -(defface smerge-markers-face +(defface smerge-markers '((((background light)) (:background "grey85")) (((background dark)) (:background "grey30"))) "Face for the conflict markers." :group 'smerge) -(defvar smerge-markers-face 'smerge-markers-face) +;; backward-compatibility alias +(put 'smerge-markers-face 'face-alias 'smerge-markers) +(defvar smerge-markers-face 'smerge-markers) + +(defface smerge-refined-change + '((t :background "yellow")) + "Face used for char-based changes shown by `smerge-refine'." + :group 'smerge) (easy-mmode-defmap smerge-basic-map `(("n" . smerge-next) @@ -120,6 +143,8 @@ Used in `smerge-diff-base-mine' and related functions." ("o" . smerge-keep-other) ("m" . smerge-keep-mine) ("E" . smerge-ediff) + ("C" . smerge-combine-with-next) + ("R" . smerge-refine) ("\C-m" . smerge-keep-current) ("=" . ,(make-sparse-keymap "Diff")) ("=<" "base-mine" . smerge-diff-base-mine) @@ -233,12 +258,14 @@ Can be nil if the style is undecided, or else: (defun smerge-ensure-match (n) (unless (match-end n) - (error (format "No `%s'" (aref smerge-match-names n))))) + (error "No `%s'" (aref smerge-match-names n)))) (defun smerge-auto-leave () (when (and smerge-auto-leave (save-excursion (goto-char (point-min)) (not (re-search-forward smerge-begin-re nil t)))) + (when (and (listp buffer-undo-list) smerge-mode) + (push (list 'apply 'smerge-mode 1) buffer-undo-list)) (smerge-mode -1))) @@ -256,6 +283,7 @@ Can be nil if the style is undecided, or else: (smerge-auto-leave))) (defun smerge-keep-n (n) + (smerge-remove-props (match-beginning 0) (match-end 0)) ;; We used to use replace-match, but that did not preserve markers so well. (delete-region (match-end n) (match-end 0)) (delete-region (match-beginning 0) (match-beginning n))) @@ -298,17 +326,26 @@ Can be nil if the style is undecided, or else: "Mode-specific merge function. The function is called with no argument and with the match data set according to `smerge-match-conflict'.") +(add-to-list 'debug-ignored-errors "Don't know how to resolve") (defvar smerge-text-properties `(help-echo "merge conflict: mouse-3 shows a menu" ;; mouse-face highlight keymap (keymap (down-mouse-3 . smerge-popup-context-menu)))) -(defun smerge-remove-props (&optional beg end) - (remove-text-properties - (or beg (match-beginning 0)) - (or end (match-end 0)) - smerge-text-properties)) +(defun smerge-remove-props (beg end) + (remove-overlays beg end 'smerge 'refine) + (remove-overlays beg end 'smerge 'conflict) + ;; Now that we use overlays rather than text-properties, this function + ;; does not cause refontification any more. It can be seen very clearly + ;; in buffers where jit-lock-contextually is not t, in which case deleting + ;; the "<<<<<<< foobar" leading line leaves the rest of the conflict + ;; highlighted as if it were still a valid conflict. Note that in many + ;; important cases (such as the previous example) we're actually called + ;; during font-locking so inhibit-modification-hooks is non-nil, so we + ;; can't just modify the buffer and expect font-lock to be triggered as in: + ;; (put-text-property beg end 'smerge-force-highlighting nil) + (remove-text-properties beg end '(fontified nil))) (defun smerge-popup-context-menu (event) "Pop up the Smerge mode context menu under mouse." @@ -324,7 +361,7 @@ according to `smerge-match-conflict'.") ;; Out of range (popup-menu smerge-mode-menu) ;; Install overlay. - (setq o (make-overlay (match-beginning i) (match-end i))) + (setq o (make-overlay (match-beginning i) (match-end i))) (unwind-protect (progn (overlay-put o 'face 'highlight) @@ -345,13 +382,21 @@ This relies on mode-specific knowledge and thus only works in some major modes. Uses `smerge-resolve-function' to do the actual work." (interactive) (smerge-match-conflict) - (smerge-remove-props) + (smerge-remove-props (match-beginning 0) (match-end 0)) (cond ;; Trivial diff3 -A non-conflicts. ((and (eq (match-end 1) (match-end 3)) (eq (match-beginning 1) (match-beginning 3))) - ;; FIXME: Add "if [ diff -b MINE OTHER ]; then select OTHER; fi" (smerge-keep-n 3)) + ;; Mode-specific conflict resolution. + ((condition-case nil + (atomic-change-group + (funcall smerge-resolve-function) + t) + (error nil)) + ;; Nothing to do: the resolution function has done it already. + nil) + ;; FIXME: Add "if [ diff -b MINE OTHER ]; then select OTHER; fi" ((and (match-end 2) ;; FIXME: Add "diff -b BASE MINE | patch OTHER". ;; FIXME: Add "diff -b BASE OTHER | patch MINE". @@ -362,8 +407,7 @@ some major modes. Uses `smerge-resolve-function' to do the actual work." nil) ) (t - ;; Mode-specific conflict resolution. - (funcall smerge-resolve-function))) + (error "Don't know how to resolve"))) (smerge-auto-leave)) (defun smerge-keep-base () @@ -371,7 +415,6 @@ some major modes. Uses `smerge-resolve-function' to do the actual work." (interactive) (smerge-match-conflict) (smerge-ensure-match 2) - (smerge-remove-props) (smerge-keep-n 2) (smerge-auto-leave)) @@ -380,7 +423,6 @@ some major modes. Uses `smerge-resolve-function' to do the actual work." (interactive) (smerge-match-conflict) ;;(smerge-ensure-match 3) - (smerge-remove-props) (smerge-keep-n 3) (smerge-auto-leave)) @@ -389,7 +431,6 @@ some major modes. Uses `smerge-resolve-function' to do the actual work." (interactive) (smerge-match-conflict) ;;(smerge-ensure-match 1) - (smerge-remove-props) (smerge-keep-n 1) (smerge-auto-leave)) @@ -407,7 +448,6 @@ some major modes. Uses `smerge-resolve-function' to do the actual work." (smerge-match-conflict) (let ((i (smerge-get-current))) (if (<= i 0) (error "Not inside a version") - (smerge-remove-props) (smerge-keep-n i) (smerge-auto-leave)))) @@ -417,7 +457,6 @@ some major modes. Uses `smerge-resolve-function' to do the actual work." (smerge-match-conflict) (let ((i (smerge-get-current))) (if (<= i 0) (error "Not inside a version") - (smerge-remove-props) (let ((left nil)) (dolist (n '(3 2 1)) (if (and (match-end n) (/= (match-end n) (match-end i))) @@ -479,9 +518,13 @@ An error is raised if not inside a conflict." (cond ((save-excursion (goto-char mine-start) - (re-search-forward smerge-begin-re nil end)) + (re-search-forward smerge-begin-re end t)) ;; There's a nested conflict and we're after the the beginning ;; of the outer one but before the beginning of the inner one. + ;; Of course, maybe this is not a nested conflict but in that + ;; case it can only be something nastier that we don't know how + ;; to handle, so may as well arbitrarily decide to treat it as + ;; a nested conflict. --Stef (error "There is a nested conflict")) ((re-search-backward smerge-base-re start t) @@ -506,13 +549,6 @@ An error is raised if not inside a conflict." (setq mine-start other-start) (setq mine-end other-end))) - (let ((inhibit-read-only t) - (inhibit-modification-hooks t) - (m (buffer-modified-p))) - (unwind-protect - (add-text-properties start end smerge-text-properties) - (restore-buffer-modified-p m))) - (store-match-data (list start end mine-start mine-end base-start base-end @@ -522,17 +558,138 @@ An error is raised if not inside a conflict." t) (search-failed (error "Point not in conflict region"))))) +(defun smerge-conflict-overlay (pos) + "Return the conflict overlay at POS if any." + (let ((ols (overlays-at pos)) + conflict) + (dolist (ol ols) + (if (and (eq (overlay-get ol 'smerge) 'conflict) + (> (overlay-end ol) pos)) + (setq conflict ol))) + conflict)) + (defun smerge-find-conflict (&optional limit) "Find and match a conflict region. Intended as a font-lock MATCHER. The submatches are the same as in `smerge-match-conflict'. -Returns non-nil if a match is found between the point and LIMIT. -The point is moved to the end of the conflict." - (when (re-search-forward smerge-begin-re limit t) - (condition-case err - (progn - (smerge-match-conflict) - (goto-char (match-end 0))) - (error (smerge-find-conflict limit))))) +Returns non-nil if a match is found between point and LIMIT. +Point is moved to the end of the conflict." + (let ((found nil) + (pos (point)) + conflict) + ;; First check to see if point is already inside a conflict, using + ;; the conflict overlays. + (while (and (not found) (setq conflict (smerge-conflict-overlay pos))) + ;; Check the overlay's validity and kill it if it's out of date. + (condition-case nil + (progn + (goto-char (overlay-start conflict)) + (smerge-match-conflict) + (goto-char (match-end 0)) + (if (<= (point) pos) + (error "Matching backward!") + (setq found t))) + (error (smerge-remove-props + (overlay-start conflict) (overlay-end conflict)) + (goto-char pos)))) + ;; If we're not already inside a conflict, look for the next conflict + ;; and add/update its overlay. + (while (and (not found) (re-search-forward smerge-begin-re limit t)) + (condition-case nil + (progn + (smerge-match-conflict) + (goto-char (match-end 0)) + (let ((conflict (smerge-conflict-overlay (1- (point))))) + (if conflict + ;; Update its location, just in case it got messed up. + (move-overlay conflict (match-beginning 0) (match-end 0)) + (setq conflict (make-overlay (match-beginning 0) (match-end 0) + nil 'front-advance nil)) + (overlay-put conflict 'evaporate t) + (overlay-put conflict 'smerge 'conflict) + (let ((props smerge-text-properties)) + (while props + (overlay-put conflict (pop props) (pop props)))))) + (setq found t)) + (error nil))) + found)) + +(defun smerge-refine-chopup-region (beg end file) + "Chopup the region into small elements, one per line." + ;; ediff chops up into words, where the definition of a word is + ;; customizable. Instead we here keep only one char per line. + ;; The advantages are that there's nothing to configure, that we get very + ;; fine results, and that it's trivial to map the line numbers in the + ;; output of diff back into buffer positions. The disadvantage is that it + ;; can take more time to compute the diff and that the result is sometimes + ;; too fine. I'm not too concerned about the slowdown because conflicts + ;; are usually significantly smaller than the whole file. As for the + ;; problem of too-fine-refinement, I have found it to be unimportant + ;; especially when you consider the cases where the fine-grain is just + ;; what you want. + (let ((buf (current-buffer))) + (with-temp-buffer + (insert-buffer-substring buf beg end) + (goto-char (point-min)) + (while (not (eobp)) + (forward-char 1) + (unless (eq (char-before) ?\n) (insert ?\n))) + (let ((coding-system-for-write 'emacs-mule)) + (write-region (point-min) (point-max) file nil 'nomessage))))) + +(defun smerge-refine-highlight-change (buf beg match-num1 match-num2) + (let* ((startline (string-to-number (match-string match-num1))) + (ol (make-overlay + (+ beg startline -1) + (+ beg (if (match-end match-num2) + (string-to-number (match-string match-num2)) + startline)) + buf + 'front-advance nil))) + (overlay-put ol 'smerge 'refine) + (overlay-put ol 'evaporate t) + (overlay-put ol 'face 'smerge-refined-change))) + + +(defun smerge-refine () + "Highlight the parts of the conflict that are different." + (interactive) + ;; FIXME: make it work with 3-way conflicts. + (smerge-match-conflict) + (remove-overlays (match-beginning 0) (match-end 0) 'smerge 'refine) + (smerge-ensure-match 1) + (smerge-ensure-match 3) + (let ((buf (current-buffer)) + ;; Read them before the match-data gets clobbered. + (beg1 (match-beginning 1)) (end1 (match-end 1)) + (beg2 (match-beginning 3)) (end2 (match-end 3)) + (file1 (make-temp-file "smerge1")) + (file2 (make-temp-file "smerge2"))) + + ;; Chop up regions into smaller elements and save into files. + (smerge-refine-chopup-region beg1 end1 file1) + (smerge-refine-chopup-region beg2 end2 file2) + + ;; Call diff on those files. + (unwind-protect + (with-temp-buffer + (let ((coding-system-for-read 'emacs-mule)) + (call-process diff-command nil t nil file1 file2)) + ;; Process diff's output. + (goto-char (point-min)) + (while (not (eobp)) + (if (not (looking-at "\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?\\([acd]\\)\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)?$")) + (error "Unexpected patch hunk header: %s" + (buffer-substring (point) (line-end-position))) + (let ((op (char-after (match-beginning 3)))) + (when (memq op '(?d ?c)) + (smerge-refine-highlight-change buf beg1 1 2)) + (when (memq op '(?a ?c)) + (smerge-refine-highlight-change buf beg2 4 5))) + (forward-line 1) ;Skip hunk header. + (and (re-search-forward "^[0-9]" nil 'move) ;Skip hunk body. + (goto-char (match-beginning 0)))))) + (delete-file file1) + (delete-file file2)))) (defun smerge-diff (n1 n2) (smerge-match-conflict) @@ -548,7 +705,12 @@ The point is moved to the end of the conflict." (file1 (make-temp-file "smerge1")) (file2 (make-temp-file "smerge2")) (dir default-directory) - (file (file-relative-name buffer-file-name)) + (file (if buffer-file-name (file-relative-name buffer-file-name))) + ;; We would want to use `emacs-mule-unix' for read&write, but we + ;; bump into problems with the coding-system used by diff to write + ;; the file names and the time stamps in the header. + ;; `buffer-file-coding-system' is not always correct either, but if + ;; the OS/user uses only one coding-system, then it works. (coding-system-for-read buffer-file-coding-system)) (write-region beg1 end1 file1 nil 'nomessage) (write-region beg2 end2 file2 nil 'nomessage) @@ -576,6 +738,8 @@ The point is moved to the end of the conflict." (defvar ediff-buffer-A) (defvar ediff-buffer-B) (defvar ediff-buffer-C) +(defvar ediff-ancestor-buffer) +(defvar ediff-quit-hook) ;;;###autoload (defun smerge-ediff (&optional name-mine name-other name-base) @@ -653,7 +817,7 @@ buffer names." (ediff-cleanup-mess) (with-current-buffer buf (erase-buffer) - (insert-buffer buffer-C) + (insert-buffer-substring buffer-C) (kill-buffer buffer-A) (kill-buffer buffer-B) (kill-buffer buffer-C) @@ -667,9 +831,8 @@ buffer names." (define-minor-mode smerge-mode "Minor mode to simplify editing output from the diff3 program. \\{smerge-mode-map}" - nil " SMerge" nil + :group 'smerge :lighter " SMerge" (when (and (boundp 'font-lock-mode) font-lock-mode) - (set (make-local-variable 'font-lock-multiline) t) (save-excursion (if smerge-mode (font-lock-add-keywords nil smerge-font-lock-keywords 'append) @@ -677,7 +840,9 @@ buffer names." (goto-char (point-min)) (while (smerge-find-conflict) (save-excursion - (font-lock-fontify-region (match-beginning 0) (match-end 0) nil)))))) + (font-lock-fontify-region (match-beginning 0) (match-end 0) nil))))) + (unless smerge-mode + (smerge-remove-props (point-min) (point-max)))) (provide 'smerge-mode)