drw@math.mit.edu (Dale R. Worley) (09/23/89)
;;; Emacs subsystem to merge two files ;;; Written by Dale R. Worley, drw@math.mit.edu. ;;; Code to run at load time. ;; Declare that we've got the subsystem loaded (provide 'emerge) ;;; Macros (defmacro eval-in-buffer (buffer &rest forms) "Macro to switch to BUFFER, evaluate FORMS, returns to original buffer." (` (let ((StartBuffer (current-buffer))) (unwind-protect (progn (set-buffer (, buffer)) (,@ forms)) (set-buffer StartBuffer))))) (defmacro defvar-local (var value doc) "Defines SYMBOL as an advertised variable. Performs exactly the same operations as defvar, then executes make-variable-buffer-local." (list 'progn (list 'defvar var value doc) (list 'make-variable-buffer-local (list 'quote var)))) ;; Add entries to minor-mode-alist so that emerge modes show correctly (if (not (assq 'emerge-mode minor-mode-alist)) (setq minor-mode-alist (append '((emerge-mode " Emerge") (emerge-fast-mode " F") (emerge-edit-mode " E") (emerge-auto-advance " A") (emerge-skip-prefers " S")) minor-mode-alist))) ;;; Emerge configuration variables ;; Commands that produce difference files ;; Note that although these appear to be configurable, the only thing ;; which is really configurable is the command name and options -- ;; even the order in which the file names are given is fixed. Also ;; the code which processes the diff/diff3 output depends on all the ;; finnicky details of their output, including the somewhat strange ;; way they number lines of a file. (defvar emerge-diff-command "diff %s %s" "Format that takes two file names and produces an appropriate diff command.") (defvar emerge-diff3-command "diff3 %s %s %s" "Format that takes the ancestor and the two variant file names and produces an appropriate diff3 command.") (defvar emerge-match-diff-line (let ((x "\\([0-9]+\\)\\(\\|,\\([0-9]+\\)\\)")) (concat "^" x "\\([acd]\\)" x "$")) "Pattern to match lines produced by diff that describe differences (as opposed to lines from the source files).") ;; The flags used to mark differences in the buffers. (defvar emerge-before-flag "vvvvvvvvvvvvvvvvvvvv\n" "Flag placed above the highlighted block of code. Must end with newline.") (defvar emerge-before-flag-length (length emerge-before-flag)) (defvar emerge-before-flag-match (regexp-quote emerge-before-flag)) (defvar emerge-after-flag "^^^^^^^^^^^^^^^^^^^^\n" "Flag placed below the highlighted block of code. Must end with newline.") (defvar emerge-after-flag-length (length emerge-after-flag)) (defvar emerge-after-flag-match (regexp-quote emerge-after-flag)) ;; Build keymaps (defvar emerge-fast-keymap (make-sparse-keymap) "Local keymap used in fast mode. Also, bound to \\C-c in edit mode.") (define-key emerge-fast-keymap "p" 'emerge-previous-difference) (define-key emerge-fast-keymap "n" 'emerge-next-difference) (define-key emerge-fast-keymap "a" 'emerge-select-A) (define-key emerge-fast-keymap "b" 'emerge-select-B) (define-key emerge-fast-keymap "j" 'emerge-jump-to-difference) (define-key emerge-fast-keymap "q" 'emerge-quit) (define-key emerge-fast-keymap "f" 'emerge-fast-mode) (define-key emerge-fast-keymap "e" 'emerge-edit-mode) (define-key emerge-fast-keymap "\C-a" 'emerge-set-auto-advance) (define-key emerge-fast-keymap "\C-s" 'emerge-set-skip-prefers) (define-key emerge-fast-keymap "\C-l" 'emerge-recenter) (define-key emerge-fast-keymap "-" 'digit-argument) (define-key emerge-fast-keymap "0" 'digit-argument) (define-key emerge-fast-keymap "1" 'digit-argument) (define-key emerge-fast-keymap "2" 'digit-argument) (define-key emerge-fast-keymap "3" 'digit-argument) (define-key emerge-fast-keymap "4" 'digit-argument) (define-key emerge-fast-keymap "5" 'digit-argument) (define-key emerge-fast-keymap "6" 'digit-argument) (define-key emerge-fast-keymap "7" 'digit-argument) (define-key emerge-fast-keymap "8" 'digit-argument) (define-key emerge-fast-keymap "9" 'digit-argument) (define-key emerge-fast-keymap "\ea" 'emerge-default-A) (define-key emerge-fast-keymap "\eb" 'emerge-default-B) ;; Variables which control each merge. They are local to the merge buffer. ;; Mode variables (defvar-local emerge-mode nil "Indicator for emerge-mode.") (defvar-local emerge-fast-mode nil "Indicator for emerge-mode fast submode.") (defvar-local emerge-edit-mode nil "Indicator for emerge-mode edit submode.") (defvar-local emerge-A-buffer nil "The buffer in which the A variant is stored.") (defvar-local emerge-B-buffer nil "The buffer in which the B variant is stored.") (defvar-local emerge-A-buffer-modified nil "Remembers whether emerge-A-buffer was modified.") (defvar-local emerge-B-buffer-modified nil "Remembers whether emerge-B-buffer was modified.") (defvar-local emerge-A-buffer-read-only nil "Remembers whether emerge-A-buffer was read-only.") (defvar-local emerge-B-buffer-read-only nil "Remembers whether emerge-B-buffer was read-only.") (defvar-local emerge-A-buffer-autosave nil "Remembers buffer-auto-save-file-name for emerge-A-buffer.") (defvar-local emerge-B-buffer-autosave nil "Remembers buffer-auto-save-file-name emerge-B-buffer.") (defvar-local emerge-difference-list nil "Vector of differences between the variants, and markers in the buffers to show where they are. Each difference is represented by a vector of seven elements. The first two are markers to the beginning and end of the difference section in the A buffer, the second two are markers for the B buffer, the third two are markers for the merge buffer, and the last element is the \"state\" of that difference in the merge buffer. A section of a buffer is described by two markers, one to the beginning of the first line of the section, and one to the beginning of the first line after the section. (If the section is empty, both markers point to the same point.) If the section is part of the selected difference, then the markers are moved into the flags, so the user can edit the section without disturbing the markers. The \"states\" are: A the merge buffer currently contains the A variant B the merge buffer currently contains the B variant default-A the merge buffer contains the A variant by default, but this difference hasn't been selected yet, so change-default commands can alter it default-B the merge buffer contains the B variant by default, but this difference hasn't been selected yet, so change-default commands can alter it prefer-A in a three-file merge, the A variant is the prefered choice prefer-B in a three-file merge, the B variant is the prefered choice") (defvar-local emerge-current-difference -1 "The difference that is currently selected.") (defvar-local emerge-number-of-differences nil "Number of differences found.") (defvar-local emerge-edit-keymap nil "The local keymap for the merge buffer, with the emerge commands defined in it. Used to save the local keymap during fast mode, when the local keymap is replaced by emerge-fast-keymap.") (defvar-local emerge-auto-advance nil "If non-nil, emerge-select-A and emerge-select-B automatically advance to the next difference.") (defvar-local emerge-skip-prefers nil "If non-nil, differences for which there is a preference are automatically skipped.") ;;; Top-level and setup functions for two-file mode. (defun emerge-files (file-A file-B) "Run Emerge on two files." (interactive "fFile A to merge: \nfFile B to merge: ") (let ((buffer-A (find-file-noselect file-A)) (buffer-B (find-file-noselect file-B))) (emerge-setup buffer-A file-A buffer-B file-B))) (defun emerge-buffers (buffer-A buffer-B) "Run Emerge on two buffers." (interactive "bBuffer A to merge: \nbBuffer B to merge: ") (let ((file-A (make-temp-name "/tmp/emergeA")) (file-B (make-temp-name "/tmp/emergeB"))) (eval-in-buffer buffer-A (write-region (point-min) (point-max) file-A nil 'no-message)) (eval-in-buffer buffer-B (write-region (point-min) (point-max) file-B nil 'no-message)) (emerge-setup (get-buffer buffer-A) file-A (get-buffer buffer-B) file-B))) ;; Start up Emerge on two files (defun emerge-setup (buffer-A file-A buffer-B file-B) (let* ((merge-buffer-name (emerge-unique-buffer-name "*merge" "*")) ;; create the merge buffer from buffer A, so it inherits buffer A's ;; default directory, etc. (merge-buffer (eval-in-buffer buffer-A (get-buffer-create merge-buffer-name))) (buffer-A-mode (eval-in-buffer buffer-A major-mode))) (eval-in-buffer merge-buffer (funcall buffer-A-mode) (setq buffer-read-only nil) (auto-save-mode 1) (setq emerge-mode t) (setq emerge-A-buffer buffer-A) (setq emerge-B-buffer buffer-B) (setq emerge-merge-buffer merge-buffer) (insert-buffer emerge-A-buffer) (emerge-set-keys) (setq emerge-difference-list (emerge-make-diff-list file-A file-B)) (setq emerge-number-of-differences (length emerge-difference-list)) (setq emerge-current-difference -1) (emerge-select-prefer-Bs) (emerge-remember-buffer-characteristics)) (emerge-setup-windows buffer-A buffer-B merge-buffer))) ;; Generate the Emerge difference list between two files (defun emerge-make-diff-list (file-A file-B) (setq emerge-diff-buffer (get-buffer-create "*emerge-diff*")) (eval-in-buffer emerge-diff-buffer (erase-buffer) (shell-command (format emerge-diff-command file-A file-B) t)) (emerge-convert-diffs-to-markers emerge-A-buffer emerge-B-buffer emerge-merge-buffer (emerge-extract-diffs emerge-diff-buffer))) (defun emerge-extract-diffs (diff-buffer) (let (list) (eval-in-buffer diff-buffer (goto-char (point-min)) (while (re-search-forward emerge-match-diff-line nil t) (let* ((a-begin (string-to-int (buffer-substring (match-beginning 1) (match-end 1)))) (a-end (let ((b (match-beginning 3)) (e (match-end 3))) (if b (string-to-int (buffer-substring b e)) a-begin))) (diff-type (buffer-substring (match-beginning 4) (match-end 4))) (b-begin (string-to-int (buffer-substring (match-beginning 5) (match-end 5)))) (b-end (let ((b (match-beginning 7)) (e (match-end 7))) (if b (string-to-int (buffer-substring b e)) b-begin)))) ;; fix the beginning and end numbers, because diff is somewhat ;; strange about how it numbers lines (if (string-equal diff-type "a") (progn (setq b-end (1+ b-end)) (setq a-begin (1+ a-begin)) (setq a-end a-begin)) (if (string-equal diff-type "d") (progn (setq a-end (1+ a-end)) (setq b-begin (1+ b-begin)) (setq b-end b-begin)) ;; (string-equal diff-type "c") (progn (setq a-end (1+ a-end)) (setq b-end (1+ b-end))))) (setq list (cons (vector a-begin a-end b-begin b-end 'default-A) list))))) (nreverse list))) ;;; Top-level and setup functions for three-file mode. (defun emerge-files-with-ancestor (file-A file-B file-ancestor) "Run Emerge on two files, giving another file as the ancestor." (interactive "fFile A to merge: \nfFile B to merge: \nfAncestor file: ") (let ((buffer-A (find-file-noselect file-A)) (buffer-B (find-file-noselect file-B)) (buffer-ancestor (find-file-noselect file-ancestor))) (emerge-setup-with-ancestor buffer-A file-A buffer-B file-B buffer-ancestor file-ancestor))) (defun emerge-buffers-with-ancestor (buffer-A buffer-B buffer-ancestor) "Run Emerge on two buffers, giving another buffer as the ancestor." (interactive "bBuffer A to merge: \nbBuffer B to merge: \nbAncestor buffer: ") (let ((file-A (make-temp-name "/tmp/emergeA")) (file-B (make-temp-name "/tmp/emergeB")) (file-ancestor (make-temp-name "/tmp/emergeanc"))) (eval-in-buffer buffer-A (write-region (point-min) (point-max) file-A nil 'no-message)) (eval-in-buffer buffer-B (write-region (point-min) (point-max) file-B nil 'no-message)) (eval-in-buffer buffer-ancestor (write-region (point-min) (point-max) file-ancestor nil 'no-message)) (emerge-setup-with-ancestor (get-buffer buffer-A) file-A (get-buffer buffer-B) file-B (get-buffer buffer-ancestor) file-ancestor))) ;; Start up Emerge on two files with an ancestor (defun emerge-setup-with-ancestor (buffer-A file-A buffer-B file-B buffer-ancestor file-ancestor) (let* ((merge-buffer-name (emerge-unique-buffer-name "*merge" "*")) ;; create the merge buffer from buffer A, so it inherits buffer A's ;; default directory, etc. (merge-buffer (eval-in-buffer buffer-A (get-buffer-create merge-buffer-name))) (buffer-A-mode (eval-in-buffer buffer-A major-mode))) (eval-in-buffer merge-buffer (funcall buffer-A-mode) (setq buffer-read-only nil) (auto-save-mode 1) (setq emerge-mode t) (setq emerge-A-buffer buffer-A) (setq emerge-B-buffer buffer-B) (setq emerge-merge-buffer merge-buffer) (insert-buffer emerge-A-buffer) (emerge-set-keys) (setq emerge-difference-list (emerge-make-diff3-list file-A file-B file-ancestor)) (setq emerge-number-of-differences (length emerge-difference-list)) (setq emerge-current-difference -1) (emerge-select-prefer-Bs) (emerge-remember-buffer-characteristics)) (emerge-setup-windows buffer-A buffer-B merge-buffer))) ;; Generate the Emerge difference list between two files with an ancestor (defun emerge-make-diff3-list (file-A file-B file-ancestor) (setq emerge-diff-buffer (get-buffer-create "*emerge-diff*")) (eval-in-buffer emerge-diff-buffer (erase-buffer) (shell-command (format emerge-diff3-command file-ancestor file-A file-B) t)) (emerge-convert-diffs-to-markers emerge-A-buffer emerge-B-buffer emerge-merge-buffer (emerge-extract-diffs3 emerge-diff-buffer))) (defun emerge-extract-diffs3 (diff-buffer) (let (list) (eval-in-buffer diff-buffer (while (re-search-forward "^====\\(.?\\)$" nil t) ;; leave point after matched line (beginning-of-line 2) (let ((agreement (buffer-substring (match-beginning 1) (match-end 1)))) ;; if the A and B files are the same, ignore the difference (if (not (string-equal agreement "1")) (setq list (cons (let ((group-2 (emerge-get-diff3-group "2")) (group-3 (emerge-get-diff3-group "3"))) (vector (car group-2) (car (cdr group-2)) (car group-3) (car (cdr group-3)) (cond ((string-equal agreement "2") 'prefer-A) ((string-equal agreement "3") 'prefer-B) (t 'default-A)))) list)))))) (nreverse list))) (defun emerge-get-diff3-group (file) (re-search-forward (concat "^" file ":\\([0-9]+\\)\\(,\\([0-9]+\\)\\)?\\([ac]\\)$")) (beginning-of-line 2) ;; treatment depends on whether it is an "a" group or a "c" group (if (string-equal (buffer-substring (match-beginning 4) (match-end 4)) "c") ;; it is a "c" group (if (match-beginning 2) ;; it has two numbers (list (string-to-int (buffer-substring (match-beginning 1) (match-end 1))) (1+ (string-to-int (buffer-substring (match-beginning 3) (match-end 3))))) ;; it has one number (let ((x (string-to-int (buffer-substring (match-beginning 1) (match-end 1))))) (list x (1+ x)))) ;; it is an "a" group (let ((x (1+ (string-to-int (buffer-substring (match-beginning 1) (match-end 1)))))) (list x x)))) ;;; Common setup routines ;; Set up the window configuration (defun emerge-setup-windows (buffer-A buffer-B merge-buffer) (delete-other-windows) (switch-to-buffer merge-buffer) (split-window-vertically) (split-window-horizontally) (switch-to-buffer buffer-A) (set-window-point (selected-window) (point-min)) (goto-char (point-min)) (other-window 1) (switch-to-buffer buffer-B) (set-window-point (selected-window) (point-min)) (goto-char (point-min)) (other-window 1) (goto-char (point-min))) ;; Set up the keymap in the merge buffer (defun emerge-set-keys () (setq emerge-edit-keymap (if (current-local-map) (copy-keymap (current-local-map)) '(keymap))) (use-local-map emerge-fast-keymap) (setq emerge-edit-mode nil) (setq emerge-fast-mode t) (define-key emerge-edit-keymap "\C-c" emerge-fast-keymap)) (defun emerge-remember-buffer-characteristics () "Must be called in the merge buffer. Remembers certain properties of the buffers being merged (read-only, modified, auto-save), and saves them in buffer-local-variables. Sets the buffers read-only and turns off auto-save. These characteristics are restored by emerge-restore-buffer-characteristics." ;; force auto-save, because we will turn off auto-saving in buffers for the ;; duration (do-auto-save) ;; remember buffer A's characteristics (setq emerge-A-buffer-modified (buffer-modified-p emerge-A-buffer)) (setq emerge-A-buffer-read-only (eval-in-buffer emerge-A-buffer buffer-read-only)) (setq emerge-A-buffer-autosave (eval-in-buffer emerge-A-buffer buffer-auto-save-file-name)) ;; remember buffer B's characteristics (setq emerge-B-buffer-modified (buffer-modified-p emerge-B-buffer)) (setq emerge-B-buffer-read-only (eval-in-buffer emerge-B-buffer buffer-read-only)) (setq emerge-B-buffer-autosave (eval-in-buffer emerge-B-buffer buffer-auto-save-file-name)) ;; set buffers read-only (eval-in-buffer emerge-A-buffer (setq buffer-read-only t) (auto-save-mode 0)) (eval-in-buffer emerge-B-buffer (setq buffer-read-only t) (auto-save-mode 0))) (defun emerge-restore-buffer-characteristics () "Restores the characteristics remembered by emerge-remember-buffer-characteristics." (let ((ro emerge-A-buffer-read-only) (mod emerge-A-buffer-modified) (as emerge-A-buffer-autosave)) (eval-in-buffer emerge-A-buffer (setq buffer-read-only ro) (set-buffer-modified-p mod) (setq buffer-auto-save-file-name as))) (let ((ro emerge-B-buffer-read-only) (mod emerge-B-buffer-modified) (as emerge-B-buffer-autosave)) (eval-in-buffer emerge-B-buffer (setq buffer-read-only ro) (set-buffer-modified-p mod) (setq buffer-auto-save-file-name as)))) (defun emerge-convert-diffs-to-markers (A-buffer B-buffer merge-buffer lineno-list) (let (marker-list (offset (1- (eval-in-buffer A-buffer (point-min))))) (while lineno-list (let* ((list-element (car lineno-list)) a-begin-marker a-end-marker b-begin-marker b-end-marker (a-begin (aref list-element 0)) (a-end (aref list-element 1)) (b-begin (aref list-element 2)) (b-end (aref list-element 3)) (state (aref list-element 4))) ;; place markers at the appropriate places in the buffers (eval-in-buffer A-buffer (goto-line a-begin) (setq a-begin-marker (point-marker)) (goto-line a-end) (setq a-end-marker (point-marker))) (eval-in-buffer B-buffer (goto-line b-begin) (setq b-begin-marker (point-marker)) (goto-line b-end) (setq b-end-marker (point-marker)) (setq merge-begin-marker (set-marker (make-marker) (- (marker-position a-begin-marker) offset) merge-buffer)) (setq merge-end-marker (set-marker (make-marker) (- (marker-position a-end-marker) offset) merge-buffer))) ;; record all the markers for this difference (setq marker-list (cons (vector a-begin-marker a-end-marker b-begin-marker b-end-marker merge-begin-marker merge-end-marker state) marker-list))) (setq lineno-list (cdr lineno-list))) ;; convert the list of difference information into a vector for fast access (setq emerge-difference-list (apply 'vector (nreverse marker-list))))) ;; If we have an ancestor, select all B variant that we prefer (defun emerge-select-prefer-Bs () (let ((n 0)) (while (< n (length emerge-difference-list)) (if (eq (aref (aref emerge-difference-list n) 6) 'prefer-B) (progn (emerge-unselect-and-select-difference n) (emerge-select-B) (aset (aref emerge-difference-list n) 6 'prefer-B))) (setq n (1+ n)))) (emerge-unselect-and-select-difference -1)) ;;; Commands (defun emerge-recenter (arg) "Perform recenter on all three windows. If an ARGUMENT is given, it is passed to recenter in all three windows." (interactive "P") (let ((merge-buffer emerge-merge-buffer) (buffer-A emerge-A-buffer) (buffer-B emerge-B-buffer)) (if arg ;; recenter each window in the requested manner (let ((window-A (get-buffer-window buffer-A)) (window-B (get-buffer-window buffer-B)) (merge-window (get-buffer-window merge-buffer))) (if window-A (progn (select-window window-A) (recenter arg))) (if window-B (progn (select-window window-B) (recenter arg))) (if merge-window (progn (select-window merge-window) (recenter arg)))) ;; redraw the screen entirely (delete-other-windows) (switch-to-buffer merge-buffer) (split-window-vertically) (split-window-horizontally) (switch-to-buffer buffer-A) (recenter) (other-window 1) (switch-to-buffer buffer-B) (recenter) (other-window 1) (recenter)))) (defun emerge-next-difference () "Advance to the next difference." (interactive) (if (< emerge-current-difference emerge-number-of-differences) (let ((n (1+ emerge-current-difference))) (while (and emerge-skip-prefers (< n emerge-number-of-differences) (memq (aref (aref emerge-difference-list n) 6) '(prefer-A prefer-B))) (setq n (1+ n))) (let ((buffer-read-only nil)) (emerge-unselect-and-select-difference n))) (ding))) (defun emerge-previous-difference () "Go to the previous difference." (interactive) (if (> emerge-current-difference -1) (let ((n (1- emerge-current-difference))) (while (and emerge-skip-prefers (> n -1) (memq (aref (aref emerge-difference-list n) 6) '(prefer-A prefer-B))) (setq n (1- n))) (let ((buffer-read-only nil)) (emerge-unselect-and-select-difference n))) (ding))) (defun emerge-jump-to-difference (difference-number) "Go to the N-th difference." (interactive "p") (let ((buffer-read-only nil)) (setq difference-number (1- difference-number)) (if (and (>= difference-number 0) (< difference-number emerge-number-of-differences)) (emerge-unselect-and-select-difference difference-number) (ding)))) (defun emerge-quit (arg) "Finish an emerge session. Prefix ARGUMENT means to write the merge buffer to disk under the name of the file in the A buffer. Unselects the selected difference, if any, restores the read-only and modified flags of the merged file buffers, restores the edit keymap of the merge buffer, and sets off various emerge flags. Using emerge buffer commands after this will cause serious problems. Thus this command removes the Emerge-specific key bindings." (interactive "P") (setq buffer-read-only nil) (emerge-unselect-and-select-difference -1) (emerge-restore-buffer-characteristics) ;; undefine the emerge commands (define-key emerge-edit-keymap "\C-c" nil) (use-local-map emerge-edit-keymap) ;; turn off all the emerge modes (setq emerge-mode nil) (setq emerge-fast-mode nil) (setq emerge-edit-mode nil) (setq emerge-auto-advance nil) (setq emerge-skip-prefers nil) ;; restore mode line (kill-local-variable 'mode-line-buffer-identification) ;; if argument was given, save to disk (if arg (let ((writable (eval-in-buffer emerge-A-buffer (not buffer-read-only))) (save-file-name (eval-in-buffer emerge-A-buffer buffer-file-name))) ;; A buffer must have file name and be writable (if (and save-file-name writable) (if (y-or-n-p (concat "Do you really want to save merged buffer as file " save-file-name "? ")) (progn ;; make A buffer not be buffer file (eval-in-buffer emerge-A-buffer (rename-buffer (emerge-unique-buffer-name "*merge-old" "*")) (setq buffer-file-name nil)) ;; make merge buffer visit file (write-file save-file-name))) (error "Buffer A not visiting file or read-only."))))) (defun emerge-select-A (&optional force) "Select the A variant of this difference. Refuses to function if this difference has been edited, i.e., if it is neither the A nor the B variant. An ARGUMENT forces the variant to be selected even if the differences has been edited." (interactive "P") (let ((buffer-read-only nil)) (let* ((diff-vector (aref emerge-difference-list emerge-current-difference)) (A-begin (1+ (aref diff-vector 0))) (A-end (1- (aref diff-vector 1))) (B-begin (1+ (aref diff-vector 2))) (B-end (1- (aref diff-vector 3))) (merge-begin (1+ (aref diff-vector 4))) (merge-end (1- (aref diff-vector 5)))) (if force (emerge-select-A-edit merge-begin merge-end A-begin A-end) (if (emerge-compare-buffers emerge-B-buffer B-begin B-end emerge-merge-buffer merge-begin merge-end) (progn (emerge-select-A-edit merge-begin merge-end A-begin A-end) (if emerge-auto-advance (emerge-next-difference))) (if (emerge-compare-buffers emerge-A-buffer A-begin A-end emerge-merge-buffer merge-begin merge-end) (if emerge-auto-advance (emerge-next-difference)) (ding) (message "This difference region has been edited."))))))) ;; Actually select the A variant (defun emerge-select-A-edit (merge-begin merge-end A-begin A-end) (eval-in-buffer emerge-merge-buffer (delete-region merge-begin merge-end) (goto-char merge-begin) (insert-buffer-substring emerge-A-buffer A-begin A-end) (aset diff-vector 6 'a) (emerge-refresh-mode-line))) (defun emerge-select-B (&optional force) "Select the B variant of this difference. Refuses to function if this difference has been edited, i.e., if it is neither the A nor the B variant. An ARGUMENT forces the variant to be selected even if the differences has been edited." (interactive "P") (let ((buffer-read-only nil)) (let* ((diff-vector (aref emerge-difference-list emerge-current-difference)) (A-begin (1+ (aref diff-vector 0))) (A-end (1- (aref diff-vector 1))) (B-begin (1+ (aref diff-vector 2))) (B-end (1- (aref diff-vector 3))) (merge-begin (1+ (aref diff-vector 4))) (merge-end (1- (aref diff-vector 5)))) (if force (emerge-select-B-edit merge-begin merge-end B-begin B-end) (if (emerge-compare-buffers emerge-A-buffer A-begin A-end emerge-merge-buffer merge-begin merge-end) (progn (emerge-select-B-edit merge-begin merge-end B-begin B-end) (if emerge-auto-advance (emerge-next-difference))) (if (emerge-compare-buffers emerge-B-buffer B-begin B-end emerge-merge-buffer merge-begin merge-end) (if emerge-auto-advance (emerge-next-difference)) (ding) (message "This difference region has been edited."))))))) ;; Actually select the B variant (defun emerge-select-B-edit (merge-begin merge-end B-begin B-end) (eval-in-buffer emerge-merge-buffer (delete-region merge-begin merge-end) (goto-char merge-begin) (insert-buffer-substring emerge-B-buffer B-begin B-end) (aset diff-vector 6 'b) (emerge-refresh-mode-line))) (defun emerge-default-A () "Selects the A variant for all differences from here down in the buffer which are still defaulted, i.e., which the user has not selected and for which there is no preference." (interactive) (let ((buffer-read-only nil)) (let ((selected-difference emerge-current-difference) (n (max emerge-current-difference 1))) (while (< n emerge-number-of-differences) (let ((diff-vector (aref emerge-difference-list n))) (if (eq (aref diff-vector 6) 'default-B) (progn (emerge-unselect-and-select-difference n) (emerge-select-A) (aset diff-vector 6 'default-A)))) (setq n (1+ n)) (if (= (* (/ n 10) 10) n) (message "Setting default to A...%d" n))) (emerge-unselect-and-select-difference selected-difference))) (message "")) (defun emerge-default-B () "Selects the B variant for all differences from here down in the buffer which are still defaulted, i.e., which the user has not selected and for which there is no preference." (interactive) (let ((buffer-read-only nil)) (let ((selected-difference emerge-current-difference) (n (max emerge-current-difference 1))) (while (< n emerge-number-of-differences) (let ((diff-vector (aref emerge-difference-list n))) (if (eq (aref diff-vector 6) 'default-A) (progn (emerge-unselect-and-select-difference n) (emerge-select-B) (aset diff-vector 6 'default-B)))) (setq n (1+ n)) (if (= (* (/ n 10) 10) n) (message "Setting default to A...%d" n))) (emerge-unselect-and-select-difference selected-difference))) (message "")) (defun emerge-fast-mode () "Set fast mode, in which ordinary Emacs commands are disabled, and Emerge commands are not prefixed with C-c." (interactive) (setq buffer-read-only t) (use-local-map emerge-fast-keymap) (setq emerge-mode t) (setq emerge-fast-mode t) (setq emerge-edit-mode nil) ;; force mode line redisplay (set-buffer-modified-p (buffer-modified-p))) (defun emerge-edit-mode () "Set edit mode, in which ordinary Emacs commands are available, and Emerge commands must be prefixed with C-c." (interactive) (setq buffer-read-only nil) (use-local-map emerge-edit-keymap) (setq emerge-mode t) (setq emerge-fast-mode nil) (setq emerge-edit-mode t) ;; force mode line redisplay (set-buffer-modified-p (buffer-modified-p))) (defun emerge-set-auto-advance (arg) "Toggle auto-advance mode, which causes emerge-select-A and emerge-select-B to automatically advance to the next difference. (See emerge-auto-advance.) If a positive ARGUMENT is given, it turns on auto-advance mode. If a negative ARGUMENT is given, it turns off auto-advance mode." (interactive "P") (setq emerge-auto-advance (if (null arg) (not emerge-auto-advance) (> (prefix-numeric-value arg) 0))) ;; force mode line redisplay (set-buffer-modified-p (buffer-modified-p))) (defun emerge-set-skip-prefers (arg) "Toggle skip-prefers mode, which causes emerge-next-difference and emerge-previous-difference to automatically skip over differences for which there is a preference. (See emerge-skip-prefers.) If a positive ARGUMENT is given, it turns on skip-prefers mode. If a negative ARGUMENT is given, it turns off skip-prefers mode." (interactive "P") (setq emerge-skip-prefers (if (null arg) (not emerge-skip-prefers) (> (prefix-numeric-value arg) 0))) ;; force mode line redisplay (set-buffer-modified-p (buffer-modified-p))) ;;; Support routines ;; Select a difference by placing the visual flags around the appropriate ;; group of lines in the A, B, and merge buffers (defun emerge-select-difference (n) (let ((diff-vector (aref emerge-difference-list n))) (emerge-place-flags-in-buffer emerge-A-buffer (aref diff-vector 0) (aref diff-vector 1)) (emerge-place-flags-in-buffer emerge-B-buffer (aref diff-vector 2) (aref diff-vector 3)) (emerge-place-flags-in-buffer emerge-merge-buffer (aref diff-vector 4) (aref diff-vector 5)))) (defun emerge-place-flags-in-buffer (buffer before after) (if (eq buffer emerge-merge-buffer) (emerge-place-flags-in-buffer1 buffer before after) (eval-in-buffer buffer (emerge-place-flags-in-buffer1 buffer before after)))) (defun emerge-place-flags-in-buffer1 (buffer before after) (let ((buffer-read-only nil)) ;; insert the flags (goto-char before) (insert-before-markers emerge-before-flag) (goto-char after) (insert emerge-after-flag) ;; put the markers into the flags, so alterations above or below won't move ;; them ;; before marker is one char before the end of the before flag ;; after marker is one char after the beginning of the after flag (set-marker before (1- before)) (set-marker after (1+ after))) ;; Set both the buffer's point and its window's point to the beginning of ;; the difference. ;; Note that this has to be done outside of save-excursion, and without ;; changing the current buffer. (let ((current-buffer buffer)) (goto-char (1+ before)) (let ((window (get-buffer-window buffer))) (if window (set-window-point window (1+ before)))))) ;; Unselect a difference by removing the visual flags in the buffers. (defun emerge-unselect-difference (n) (let ((diff-vector (aref emerge-difference-list n))) (emerge-remove-flags-in-buffer emerge-A-buffer (aref diff-vector 0) (aref diff-vector 1)) (emerge-remove-flags-in-buffer emerge-B-buffer (aref diff-vector 2) (aref diff-vector 3)) (emerge-remove-flags-in-buffer emerge-merge-buffer (aref diff-vector 4) (aref diff-vector 5)))) (defun emerge-remove-flags-in-buffer (buffer before after) (eval-in-buffer buffer (let ((buffer-read-only nil)) ;; put the markers at the beginning of the flags (set-marker before (- before (1- emerge-before-flag-length))) (set-marker after (1- after)) ;; remove the flags (goto-char before) (if (looking-at emerge-before-flag-match) (delete-char emerge-before-flag-length) ;; the flag isn't there (ding) (message "Trouble removing flag.")) (goto-char after) (if (looking-at emerge-after-flag-match) (delete-char emerge-after-flag-length) ;; the flag isn't there (ding) (message "Trouble removing flag."))))) ;; Select a difference, removing an flags that exist now. (defun emerge-unselect-and-select-difference (n) (if (and (>= emerge-current-difference 0) (< emerge-current-difference emerge-number-of-differences)) (emerge-unselect-difference emerge-current-difference)) (if (and (>= n 0) (< n emerge-number-of-differences)) (progn (emerge-select-difference n) (let* ((diff-vector (aref emerge-difference-list n)) (selection-type (aref diff-vector 6))) (if (eq selection-type 'default-A) (aset diff-vector 6 'a) (if (eq selection-type 'default-B) (aset diff-vector 6 'b)))))) (setq emerge-current-difference n) (emerge-refresh-mode-line)) ;; Revise the mode line to display which difference we have selected (defun emerge-refresh-mode-line () (setq mode-line-buffer-identification (list (format "Emerge: %%b diff %d of %d%s" (1+ emerge-current-difference) emerge-number-of-differences (if (and (>= emerge-current-difference 0) (< emerge-current-difference emerge-number-of-differences)) (cdr (assq (aref (aref emerge-difference-list emerge-current-difference) 6) '((a . " - A") (b . " - B") (prefer-A . " - A*") (prefer-B . " - B*")))) "")))) ;; Force mode-line redisplay (set-buffer-modified-p (buffer-modified-p))) ;; compare two regions in two buffers for containing the same text (defun emerge-compare-buffers (buffer-x x-begin x-end buffer-y y-begin y-end) ;; first check that the two regions are the same length (if (not (and (= (- x-end x-begin) (- y-end y-begin)))) nil (catch 'exit (while (< x-begin x-end) ;; bite off and compare no more than 1000 characters at a time (let* ((compare-length (min (- x-end x-begin) 1000)) (x-string (eval-in-buffer buffer-x (buffer-substring x-begin (+ x-begin compare-length)))) (y-string (eval-in-buffer buffer-y (buffer-substring y-begin (+ y-begin compare-length))))) (if (not (string-equal x-string y-string)) (throw 'exit nil) (setq x-begin (+ x-begin compare-length)) (setq y-begin (+ y-begin compare-length))))) t))) ;; Construct a unique buffer name. ;; The first one tried is prefixsuffix, then prefix<2>suffix, ;; prefix<3>suffix, etc. (defun emerge-unique-buffer-name (prefix suffix) (if (null (get-buffer (concat prefix suffix))) (concat prefix suffix) (let ((n 2)) (while (get-buffer (format "%s<%d>%s" prefix n suffix)) (setq n (1+ n))) (format "%s<%d>%s" prefix n suffix))))