3

I want to mimic the behaviour of vim's do and dp in ediff mode. These two commands stand for "obtain diff" and "put diff". That is, if I'm on one difference in one buffer, pressing do would copy the difference from the other buffer, while pressing dp would copy the difference from the current buffer into the other buffer.

Now, it seems to me that various ediff- functions and variables do not work from within buffers, but only from ediff's control panel :(

Since I'm using spacemacs, I tried this:

(eval-after-load "ediff"
'(progn
(evil-define-key 'normal ediff-mode-map "do" 'spacemacs//ediff-get-changes)
(evil-define-key 'normal ediff-mode-map "dp" 'spacemacs//ediff-put-changes)))

(defun spacemacs//ediff-get-changes ()
"This function gets changes from the other window into the current window."
(cond ((eq (current-buffer) ediff-buffer-A) 'ediff-copy-B-to-A)
      ((eq (current-buffer) ediff-buffer-B) 'ediff-copy-A-to-B))
)

(defun spacemacs//ediff-put-changes ()
"This function puts changes from the current window into the other window."
(cond ((eq (current-buffer) ediff-buffer-A) 'ediff-copy-A-to-B)
      ((eq (current-buffer) ediff-buffer-B) 'ediff-copy-B-to-A))
)

Since I'm a complete emacs beginner, any pointers are more than appreciated, even just for vanilla Emacs. My goal is to execute one of these functions (ediff-get-changes/ediff-put-changes) from within a buffer, via a keybinding.

4
  • What don't you like about keeping point in the ediff control frame? You should be able to do what you need to do (for diffs) directly from that.
    – zck
    Commented Sep 16, 2015 at 23:42
  • 2
    Ediff is designed to work with point always being in the control buffer. I don't know about spacemacs bindings, but I use n/p to navigate to diff segments and then a to copy stuff from A buffer to B buffer or b to copy stuff from B to A buffer. These a and b are like the do and dp you mentioned. Commented Sep 17, 2015 at 2:46
  • Here's another question on ediff workflow: emacs.stackexchange.com/a/13034/115. It is not the same question as yours but it might help you as you look interested in learning emacs and ediff basics. Commented Sep 17, 2015 at 2:49
  • Thanks for the clarifying questions. I know ediff is designed to work from the control buffer, but my preferred way of doing diffing is to be in one of the buffers, because sometimes I have to modify what I just got from the other buffer. So, how could I check if I'm in window-A or window-B? Commented Sep 17, 2015 at 18:41

1 Answer 1

4

What you did would not work for various reasons.

Firstly, because indeed the ediff-* variables and functions are only local to the control window, you cannot call those functions from another window. But you can simply use with-selected-window to switch windows:

(with-selected-window
      (get-buffer-window "*Ediff Control Panel*")
  (ediff-copy-B-to-A))

But that doesn't help here because the check (eq (current-buffer) ediff-buffer-*) will never work as current-buffer is not anymore the buffer you point is but the control buffer. So we need something a bit more sophisticated:

(let ((b (current-buffer)))
  (with-selected-window
      (get-buffer-window "*Ediff Control Panel*")
    (eq b ediff-buffer-A)))

Secondly, the various ediff-copy-*-to-* require an argument. We need to give nil to use the current difference.

Thirdly, because ediff-mode-map is defined only in the control window but we want to modify the keymap in the A, B and C buffers.

I found we can do it using the ediff-prepare-buffer-hook. From the doc:

Hooks run after buffers A, B, and C are set up.

For each buffer, the hooks are run with that buffer made current.

That second part is important: the hook will be called once for every A, B and C buffer, using that buffer as the current one.

This is arguably brittle because it relies on the name of the buffer. It won't correctly if multiple ediff sessions are open at the same time. But it works if you only have one.


On top of the considerations above, I would use a minor mode to create that keymap. This makes it possible to remove the keymap easily when you exit ediff. And also it makes the keymaps local only to the buffers handled by ediff.

I went a bit overboard and added two useful features IMO which allows you to move to the next and previous difference from the a, b and c buffers. I also split the code into functions to hopefully make it more readable.

Finally, I used or bindings than do and dp as they play badly with evil-mode.

(defun ediff-buffer-mode-next-difference ()
  "Advance to the next difference."
  (interactive)
  (with-selected-window
      (get-buffer-window "*Ediff Control Panel*")
    (ediff-next-difference)))


(defun ediff-buffer-mode-previous-difference ()
  "Advance to the previous difference."
  (interactive)
  (with-selected-window
      (get-buffer-window "*Ediff Control Panel*")
    (ediff-previous-difference)))


(defun ediff-buffer-mode--get-current-buffer-char ()
  "Get char corresponding to current Ediff buffer."
  (let ((buff (current-buffer)))
    (with-selected-window
        (get-buffer-window "*Ediff Control Panel*")
      (cond ((eq buff ediff-buffer-A) ?a)
            ((eq buff ediff-buffer-B) ?b)
            ((eq buff ediff-buffer-C) ?c)))))


(defun ediff-buffer-mode--get-other-buffer-char (current-buffer-char)
  "For CURRENT-BUFFER-CHAR return other buffer chars."
  (remove current-buffer-char '(?a ?b ?c)))


(defun ediff-buffer-mode--prompt-other-source (current-buffer-char get-or-put)
  "For CURRENT-BUFFER-CHAR, prompt user for other buffer source with prompt adapting to GET-OR-PUT value."
  (let* ((prompt (if (string= get-or-put "get") "Select buffer to get changes from: "
                   "Select buffer to put changes to: "))
         (buffer-help (if (string= get-or-put "get") "Get from "
                        "Put to "))
         (choice
          (read-multiple-choice
           prompt
           (mapcar
            (lambda (char) (list char (concat buffer-help (string char))))
            (ediff-buffer-mode--get-other-buffer-char current-buffer-char)))))
    (car choice)))


(defun ediff-buffer-mode--validate-other-source (current-buffer-char other-buffer-char)
  "For CURRENT-BUFFER-CHAR, check OTHER-BUFFER-CHAR is an accepted char.

For example, for CURRENT-BUFFER-CHAR = ?a then the accepted chars are ?b and ?c."
  (let ((accepted-other-buffers (ediff-buffer-mode--get-other-buffer-char current-buffer-char)))
    (when (not (member other-buffer-char accepted-other-buffers))
      (error "Chosen buffer must be %s" (mapconcat (lambda (x) (string x)) accepted-other-buffers " or ")))))


(defun ediff-buffer-mode-get-changes (&optional get-src)
  "Get changes from the other window into the current window.

For 3-way jobs, the other window cannot be determined automatically.
In that case a prompt will ask the user to enter what buffer the change must come
from.  If GET-SRC is given, use that as the source and do not prompt the user."
  (interactive)
  (let ((buff-char (ediff-buffer-mode--get-current-buffer-char)))
    (with-selected-window
        (get-buffer-window "*Ediff Control Panel*")
      (if ediff-3way-job
          (progn
            (let* ((other-buff-char (ediff-buffer-mode--get-other-buffer-char buff-char))
                   (get-src (or get-src (ediff-buffer-mode--prompt-other-source buff-char "get"))))
              (ediff-buffer-mode--validate-other-source buff-char get-src)
              (cond ((and (char-equal buff-char ?a) (char-equal get-src ?b)) (ediff-copy-B-to-A nil))
                    ((and (char-equal buff-char ?a) (char-equal get-src ?c)) (ediff-copy-C-to-A nil))
                    ((and (char-equal buff-char ?b) (char-equal get-src ?a)) (ediff-copy-A-to-B nil))
                    ((and (char-equal buff-char ?b) (char-equal get-src ?c)) (ediff-copy-C-to-B nil))
                    ((and (char-equal buff-char ?c) (char-equal get-src ?a)) (ediff-copy-A-to-C nil))
                    ((and (char-equal buff-char ?c) (char-equal get-src ?b)) (ediff-copy-B-to-C nil)))))
        (progn
          (cond (char-equal buff-char ?a) (ediff-copy-B-to-A nil)
                (char-equal buff-char ?b) (ediff-copy-A-to-B nil)))))))


(defun ediff-buffer-mode-put-changes (&optional put-src)
  "Get changes from the other window into the current window.

For 3-way jobs, the other window cannot be determined automatically.
In that case a prompt will ask the user to enter what buffer the change must come
from.  If PUT-SRC is given, use that as the source and do not prompt the user."
  (interactive)
  (let ((buff-char (ediff-buffer-mode--get-current-buffer-char)))
    (with-selected-window
        (get-buffer-window "*Ediff Control Panel*")
      (if ediff-3way-job
          (progn
            (let* ((other-buff-char (ediff-buffer-mode--get-other-buffer-char buff-char))
                   (put-src (or put-src (ediff-buffer-mode--prompt-other-source buff-char "put"))))
              (ediff-buffer-mode--validate-other-source buff-char put-src)
              (cond ((and (char-equal buff-char ?a) (char-equal put-src ?b)) (ediff-copy-A-to-B nil))
                    ((and (char-equal buff-char ?a) (char-equal put-src ?c)) (ediff-copy-A-to-C nil))
                    ((and (char-equal buff-char ?b) (char-equal put-src ?a)) (ediff-copy-B-to-A nil))
                    ((and (char-equal buff-char ?b) (char-equal put-src ?c)) (ediff-copy-B-to-C nil))
                    ((and (char-equal buff-char ?c) (char-equal put-src ?a)) (ediff-copy-C-to-A nil))
                    ((and (char-equal buff-char ?c) (char-equal put-src ?b)) (ediff-copy-C-to-B nil)))))
        (progn
          (cond ((char-equal buff-char ?a) (ediff-copy-A-to-B nil))
                ((char-equal buff-char ?b) (ediff-copy-B-to-A nil))))))))


(defvar ediff-buffer-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map (kbd "C-j") 'ediff-buffer-mode-next-difference)
    (define-key map (kbd "C-k") 'ediff-buffer-mode-previous-difference)
    (define-key map (kbd "C-c o") 'ediff-buffer-mode-get-changes)
    (define-key map (kbd "C-c p") 'ediff-buffer-mode-put-changes)
    map))


(define-minor-mode ediff-buffer-mode
  "Minor mode enabled on buffers used in Ediff."
  :init-value nil)


(defun enable-ediff-buffer-mode ()
  "Enable ediff=buffer-mode."
  (ediff-buffer-mode t))


(defun disable-all-ediff-buffer-mode ()
  "Enable ediff=buffer-mode."
  (with-selected-window
      (get-buffer-window "*Ediff Control Panel*")
    (if ediff-buffer-A
        (with-selected-window (get-buffer-window ediff-buffer-A)
          (ediff-buffer-mode -1)))
    (if ediff-buffer-B
        (with-selected-window (get-buffer-window ediff-buffer-B)
          (ediff-buffer-mode -1)))
    (if ediff-buffer-C
        (with-selected-window (get-buffer-window ediff-buffer-C)
          (ediff-buffer-mode -1)))))


(add-hook 'ediff-prepare-buffer-hook 'enable-ediff-buffer-mode)
(add-hook 'ediff-cleanup-hook 'disable-all-ediff-buffer-mode)

Oh one last thing, I followed the advice in https://stackoverflow.com/a/21646988/1013628 to define the keymap separately, allowing to reload it easily.

1
  • 1
    I believe all those with-selected-window (get-buffer-window can advantageously be replaced by with-current-buffer. This should eventually make it possible for Ediff to work without having to display the control buffer.
    – Stefan
    Commented Oct 12, 2018 at 12:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.