1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2026-01-05 19:31:02 -08:00

Adapt ediff to nonstandard layouts

Make ediff cope with having some of its windows (especially the control
window) not shown by a custom ediff-window-setup-function.
Modernize relevant adjacent code. After this change, one can write a
custom ediff-window-setup-function that doesn't show the control window.

* doc/misc/ediff.texi (Notes on Heavy-duty Customization): Refine
language to explain that the window setup function doesn't have to show
all windows.
* lisp/vc/ediff-util.el (ediff-select-control-window-on-setup):
New variable.
(ediff-setup, ediff-recenter, ediff-recenter-one-window)
(ediff-recenter-ancestor, ediff-toggle-read-only)
(ediff-operate-on-windows, ediff-jump-to-difference-at-point)
(ediff-default-suspend-function)
(ediff-clone-buffer-for-region-comparison)
(ediff-clone-buffer-for-window-comparison): Modernize control flow;
select only windows that exist.
* lisp/vc/ediff-wind.el (ediff-with-live-window): New convenience macro.
(ediff-window-setup-function): Explain relaxed contract.
This commit is contained in:
Daniel Colascione 2025-03-21 19:46:08 -04:00
parent b21636580b
commit e5ee1d2a74
4 changed files with 124 additions and 141 deletions

View file

@ -2399,10 +2399,12 @@ The window displaying buffer A@. If buffer A is not visible, this variable
is @code{nil} or it may be a dead window. is @code{nil} or it may be a dead window.
@item ediff-window-B @item ediff-window-B
The window displaying buffer B. The window displaying buffer B. If buffer B is not visible, this variable
is @code{nil} or it may be a dead window.
@item ediff-window-C @item ediff-window-C
The window displaying buffer C, if any. The window displaying buffer C, if any. If buffer C is not visible,
this variable is @code{nil} or it may be a dead window.
@item ediff-control-frame @item ediff-control-frame
A dedicated frame displaying the control buffer, if it exists. It is A dedicated frame displaying the control buffer, if it exists. It is

View file

@ -1202,6 +1202,14 @@ changes when supplied with a universal prefix argument via 'C-u':
- 'C-u c a' copies all changes from buffer C to buffer A. - 'C-u c a' copies all changes from buffer C to buffer A.
- 'C-u c b' copies all changes from buffer C to buffer B. - 'C-u c b' copies all changes from buffer C to buffer B.
+++
*** Ediff now supports more flexible custom window layouts
Custom implementations of 'ediff-window-setup-function' no
longer need to display *all* ediff windows. Any of the A, B, C,
and control windows can be left undisplayed and the corresponding
variable set to nil. This change enables custom layouts without
a control panel window.
** Dired ** Dired
+++ +++

View file

@ -36,6 +36,15 @@
(require 'ediff-diff) (require 'ediff-diff)
(require 'ediff-merg) (require 'ediff-merg)
(eval-when-compile
(require 'cl-lib))
(ediff-defvar-local ediff-select-control-window-on-setup t
"Select the control window after window setup.
`t' for compatibility. Custom `ediff-window-setup-function'
implementations may want to set it to `nil' to fully control
window setup.")
;;; Functions ;;; Functions
@ -472,20 +481,20 @@ to invocation.")
(ediff-get-value-according-to-buffer-type (ediff-get-value-according-to-buffer-type
'C ediff-narrow-bounds)))) 'C ediff-narrow-bounds))))
;; position point in buf A ;; position point in buf A
(save-excursion (when (window-live-p ediff-window-A)
(select-window ediff-window-A) (with-selected-window ediff-window-A
(goto-char shift-A)) (goto-char shift-A)))
;; position point in buf B ;; position point in buf B
(save-excursion (when (window-live-p ediff-window-B)
(select-window ediff-window-B) (with-selected-window ediff-window-B
(goto-char shift-B)) (goto-char shift-B)))
(if ediff-3way-job (if (and ediff-3way-job (window-live-p ediff-window-C))
(save-excursion (with-selected-window ediff-window-C
(select-window ediff-window-C) (goto-char shift-C))))
(goto-char shift-C)))
)
(select-window ediff-control-window) (when (and ediff-select-control-window-on-setup
(window-live-p ediff-control-window))
(select-window ediff-control-window))
(ediff-visible-region) (ediff-visible-region)
(mapc #'funcall startup-hooks) (mapc #'funcall startup-hooks)
@ -776,16 +785,19 @@ buffers."
(or (not ediff-3way-job) (or (not ediff-3way-job)
(ediff-buffer-live-p ediff-buffer-C))) (ediff-buffer-live-p ediff-buffer-C)))
(progn (progn
(or no-rehighlight (or no-rehighlight
(ediff-select-difference ediff-current-difference)) (ediff-select-difference ediff-current-difference))
(ediff-recenter-one-window 'A) (save-current-buffer
(ediff-recenter-one-window 'B) (ediff-recenter-one-window 'A))
(if ediff-3way-job (save-current-buffer
(ediff-recenter-one-window 'C)) (ediff-recenter-one-window 'B))
(if ediff-3way-job
(save-current-buffer
(ediff-recenter-one-window 'C)))
(ediff-with-current-buffer control-buf (ediff-with-current-buffer control-buf
(ediff-recenter-ancestor) ; check if ancestor is alive (ediff-recenter-ancestor) ; check if ancestor is alive
(if (and (ediff-multiframe-setup-p) (if (and (ediff-multiframe-setup-p)
(not ediff-use-long-help-message) (not ediff-use-long-help-message)
@ -801,13 +813,11 @@ buffers."
(ediff-with-current-buffer control-buf (ediff-refresh-mode-lines)) (ediff-with-current-buffer control-buf (ediff-refresh-mode-lines))
)) ))
;; this function returns to the window it was called from ;; this function does not change current window
;; (which was the control window)
(defun ediff-recenter-one-window (buf-type) (defun ediff-recenter-one-window (buf-type)
(if (ediff-valid-difference-p) (if (ediff-valid-difference-p)
;; context must be saved before switching to windows A/B/C ;; context must be saved before switching to windows A/B/C
(let* ((ctl-wind (selected-window)) (let* ((shift (ediff-overlay-start
(shift (ediff-overlay-start
(ediff-get-value-according-to-buffer-type (ediff-get-value-according-to-buffer-type
buf-type ediff-narrow-bounds))) buf-type ediff-narrow-bounds)))
(job-name ediff-job-name) (job-name ediff-job-name)
@ -817,20 +827,16 @@ buffers."
(window (if (window-live-p (symbol-value window-name)) (window (if (window-live-p (symbol-value window-name))
(symbol-value window-name)))) (symbol-value window-name))))
(if (and window ediff-windows-job) (when (and window ediff-windows-job)
(set-window-start window shift)) (set-window-start window shift))
(if window (when window
(progn (with-selected-window window
(select-window window) (deactivate-mark)
(deactivate-mark) (ediff-position-region
(ediff-position-region
(ediff-get-diff-posn buf-type 'beg nil control-buf) (ediff-get-diff-posn buf-type 'beg nil control-buf)
(ediff-get-diff-posn buf-type 'end nil control-buf) (ediff-get-diff-posn buf-type 'end nil control-buf)
(ediff-get-diff-posn buf-type 'beg nil control-buf) (ediff-get-diff-posn buf-type 'beg nil control-buf)
job-name job-name))))))
)))
(select-window ctl-wind)
)))
(defun ediff-recenter-ancestor () (defun ediff-recenter-ancestor ()
;; do half-hearted job by recentering the ancestor buffer, if it is alive and ;; do half-hearted job by recentering the ancestor buffer, if it is alive and
@ -838,21 +844,17 @@ buffers."
(if (and (ediff-buffer-live-p ediff-ancestor-buffer) (if (and (ediff-buffer-live-p ediff-ancestor-buffer)
(ediff-valid-difference-p)) (ediff-valid-difference-p))
(let ((window (ediff-get-visible-buffer-window ediff-ancestor-buffer)) (let ((window (ediff-get-visible-buffer-window ediff-ancestor-buffer))
(ctl-wind (selected-window))
(job-name ediff-job-name) (job-name ediff-job-name)
(ctl-buf ediff-control-buffer)) (ctl-buf ediff-control-buffer))
(ediff-with-current-buffer ediff-ancestor-buffer (ediff-with-current-buffer ediff-ancestor-buffer
(goto-char (ediff-get-diff-posn 'Ancestor 'beg nil ctl-buf)) (goto-char (ediff-get-diff-posn 'Ancestor 'beg nil ctl-buf))
(if window (when (window-live-p window)
(progn (with-selected-window window
(select-window window) (ediff-position-region
(ediff-position-region
(ediff-get-diff-posn 'Ancestor 'beg nil ctl-buf) (ediff-get-diff-posn 'Ancestor 'beg nil ctl-buf)
(ediff-get-diff-posn 'Ancestor 'end nil ctl-buf) (ediff-get-diff-posn 'Ancestor 'end nil ctl-buf)
(ediff-get-diff-posn 'Ancestor 'beg nil ctl-buf) (ediff-get-diff-posn 'Ancestor 'beg nil ctl-buf)
job-name)))) job-name)))))))
(select-window ctl-wind)
)))
;; This will have to be refined for 3way jobs ;; This will have to be refined for 3way jobs
@ -1064,10 +1066,9 @@ of the current buffer."
(sit-for 3)))) ; let the user see the warning (sit-for 3)))) ; let the user see the warning
(if (and toggle-ro-cmd (if (and toggle-ro-cmd
(string-match "read-only-mode" (symbol-name toggle-ro-cmd))) (string-match "read-only-mode" (symbol-name toggle-ro-cmd)))
(save-excursion (save-window-excursion
(save-window-excursion (ediff-with-live-window (ediff-get-visible-buffer-window buf)
(select-window (ediff-get-visible-buffer-window buf)) (command-execute toggle-ro-cmd)))
(command-execute toggle-ro-cmd)))
(user-error "Don't know how to toggle read-only in buffer %S" buf)) (user-error "Don't know how to toggle read-only in buffer %S" buf))
;; Check if we made the current buffer updatable, but its file is RO. ;; Check if we made the current buffer updatable, but its file is RO.
@ -1413,8 +1414,8 @@ Used in ediff-windows/regions only."
(defun ediff-operate-on-windows (operation arg) (defun ediff-operate-on-windows (operation arg)
;; make sure windows aren't dead ;; make sure windows aren't dead
(if (not (and (window-live-p ediff-window-A) (window-live-p ediff-window-B))) (unless (and (window-live-p ediff-window-A) (window-live-p ediff-window-B))
(ediff-recenter 'no-rehighlight)) (ediff-recenter 'no-rehighlight))
(if (not (and (ediff-buffer-live-p ediff-buffer-A) (if (not (and (ediff-buffer-live-p ediff-buffer-A)
(ediff-buffer-live-p ediff-buffer-B) (ediff-buffer-live-p ediff-buffer-B)
(or (not ediff-3way-job) (ediff-buffer-live-p ediff-buffer-C)) (or (not ediff-3way-job) (ediff-buffer-live-p ediff-buffer-C))
@ -1424,8 +1425,7 @@ Used in ediff-windows/regions only."
)) ))
(error ediff-KILLED-VITAL-BUFFER)) (error ediff-KILLED-VITAL-BUFFER))
(let* ((wind (selected-window)) (let* ((wind-A ediff-window-A)
(wind-A ediff-window-A)
(wind-B ediff-window-B) (wind-B ediff-window-B)
(wind-C ediff-window-C) (wind-C ediff-window-C)
(wind-Anc ediff-window-Ancestor) (wind-Anc ediff-window-Ancestor)
@ -1438,26 +1438,16 @@ Used in ediff-windows/regions only."
(coefAnc (if with-Ancestor (coefAnc (if with-Ancestor
(ediff-get-region-size-coefficient 'Ancestor operation)))) (ediff-get-region-size-coefficient 'Ancestor operation))))
(select-window wind-A) (ediff-with-live-window wind-A
(condition-case nil (ignore-errors (funcall operation (round (* coefA arg)))))
(funcall operation (round (* coefA arg))) (ediff-with-live-window wind-B
(error)) (ignore-errors (funcall operation (round (* coefB arg)))))
(select-window wind-B) (when three-way
(condition-case nil (ediff-with-live-window wind-C
(funcall operation (round (* coefB arg))) (ignore-errors (funcall operation (round (* coefC arg))))))
(error))
(if three-way
(progn
(select-window wind-C)
(condition-case nil
(funcall operation (round (* coefC arg)))
(error))))
(when with-Ancestor (when with-Ancestor
(select-window wind-Anc) (ediff-with-live-window wind-Anc
(condition-case nil (ignore-errors (funcall operation (round (* coefAnc arg))))))))
(funcall operation (round (* coefAnc arg)))
(error)))
(select-window wind)))
(defun ediff-scroll-vertically (&optional arg) (defun ediff-scroll-vertically (&optional arg)
"Vertically scroll buffers A, B (and C if appropriate). "Vertically scroll buffers A, B (and C if appropriate).
@ -1817,44 +1807,29 @@ current point position in the specified buffer."
(beg (if past-last-diff (beg (if past-last-diff
(ediff-with-current-buffer buffer (point-max)) (ediff-with-current-buffer buffer (point-max))
(ediff-get-diff-posn buf-type 'beg (1- diff-no)))) (ediff-get-diff-posn buf-type 'beg (1- diff-no))))
ctl-wind wind-A wind-B wind-C wind-A wind-B wind-C
shift) shift)
(if past-last-diff (if past-last-diff
(ediff-jump-to-difference -1) (ediff-jump-to-difference -1)
(ediff-jump-to-difference diff-no)) (ediff-jump-to-difference diff-no))
(setq ctl-wind (selected-window) (setq wind-A ediff-window-A
wind-A ediff-window-A
wind-B ediff-window-B wind-B ediff-window-B
wind-C ediff-window-C) wind-C ediff-window-C)
(if arg (if arg
(progn (save-selected-window
(ediff-with-current-buffer buffer (setq shift (- beg pt))
(setq shift (- beg pt))) (ediff-with-live-window wind-A
(select-window wind-A) (when past-last-diff (goto-char (point-max)))
(if past-last-diff (goto-char (point-max))) (ignore-errors (backward-char shift))
(condition-case nil (recenter))
(backward-char shift) ; noerror, if beginning of buffer (ediff-with-live-window wind-B
(error)) (when past-last-diff (goto-char (point-max)))
(recenter) (ignore-errors (backward-char shift))
(select-window wind-B) (recenter))
(if past-last-diff (goto-char (point-max))) (ediff-with-live-window wind-C
(condition-case nil (when past-last-diff (goto-char (point-max)))
(backward-char shift) ; noerror, if beginning of buffer (ignore-errors (backward-char shift))
(error)) (recenter))))))
(recenter)
(if (window-live-p wind-C)
(progn
(select-window wind-C)
(if past-last-diff (goto-char (point-max)))
(condition-case nil
(backward-char shift) ; noerror, if beginning of buffer
(error))
(recenter)
))
(select-window ctl-wind)
))
))
;; find region most related to the current point position (or POS, if given) ;; find region most related to the current point position (or POS, if given)
;; returns diff number as seen by the user (i.e., 1+ the internal ;; returns diff number as seen by the user (i.e., 1+ the internal
@ -2725,10 +2700,7 @@ only if this merge job is part of a group, i.e., was invoked from within
(let* ((buf-A ediff-buffer-A) (let* ((buf-A ediff-buffer-A)
(buf-B ediff-buffer-B) (buf-B ediff-buffer-B)
(buf-C ediff-buffer-C) (buf-C ediff-buffer-C)
(buf-A-wind (ediff-get-visible-buffer-window buf-A)) (buf-patch (if (boundp 'ediff-patchbufer) ediff-patchbufer nil))
(buf-B-wind (ediff-get-visible-buffer-window buf-B))
(buf-C-wind (ediff-get-visible-buffer-window buf-C))
(buf-patch (if (boundp 'ediff-patchbufer) ediff-patchbufer nil))
(buf-patch-diag (if (boundp 'ediff-patch-diagnostics) (buf-patch-diag (if (boundp 'ediff-patch-diagnostics)
ediff-patch-diagnostics nil)) ediff-patch-diagnostics nil))
(buf-err ediff-error-buffer) (buf-err ediff-error-buffer)
@ -2746,35 +2718,18 @@ only if this merge job is part of a group, i.e., was invoked from within
(if buf-fine-diff (bury-buffer buf-fine-diff)) (if buf-fine-diff (bury-buffer buf-fine-diff))
(if buf-patch (bury-buffer buf-patch)) (if buf-patch (bury-buffer buf-patch))
(if buf-patch-diag (bury-buffer buf-patch-diag)) (if buf-patch-diag (bury-buffer buf-patch-diag))
(if (window-live-p buf-A-wind) (cl-loop
(progn with buffers = (list buf-A buf-B buf-C)
(select-window buf-A-wind) with windows = (mapcar #'ediff-get-visible-buffer-window buffers)
(delete-other-windows) for buffer in buffers
(bury-buffer)) for window in windows
(if (ediff-buffer-live-p buf-A) do (cond ((window-live-p window)
(progn (select-window window)
(set-buffer buf-A) (delete-other-windows)
(bury-buffer)))) (bury-buffer))
(if (window-live-p buf-B-wind) (buffer
(progn (set-buffer buffer)
(select-window buf-B-wind) (bury-buffer))))))
(delete-other-windows)
(bury-buffer))
(if (ediff-buffer-live-p buf-B)
(progn
(set-buffer buf-B)
(bury-buffer))))
(if (window-live-p buf-C-wind)
(progn
(select-window buf-C-wind)
(delete-other-windows)
(bury-buffer))
(if (ediff-buffer-live-p buf-C)
(progn
(set-buffer buf-C)
(bury-buffer))))
))
(defun ediff-suspend () (defun ediff-suspend ()
"Suspend Ediff. "Suspend Ediff.
@ -3274,8 +3229,9 @@ Without an argument, it saves customized diff argument, if available
(setq ediff-temp-indirect-buffer t)) (setq ediff-temp-indirect-buffer t))
(pop-to-buffer cloned-buff) (pop-to-buffer cloned-buff)
(setq wind (ediff-get-visible-buffer-window cloned-buff)) (setq wind (ediff-get-visible-buffer-window cloned-buff))
(select-window wind) (when (window-live-p wind)
(delete-other-windows) (select-window wind)
(delete-other-windows))
(or (mark) (push-mark)) (or (mark) (push-mark))
(setq mark-active 'ediff-util) (setq mark-active 'ediff-util)
(setq-local transient-mark-mode t) (setq-local transient-mark-mode t)
@ -3310,7 +3266,8 @@ Without an argument, it saves customized diff argument, if available
(let ((cloned-buff (ediff-make-cloned-buffer buff region-name))) (let ((cloned-buff (ediff-make-cloned-buffer buff region-name)))
(ediff-with-current-buffer cloned-buff (ediff-with-current-buffer cloned-buff
(setq ediff-temp-indirect-buffer t)) (setq ediff-temp-indirect-buffer t))
(set-window-buffer wind cloned-buff) (when (window-live-p wind)
(set-window-buffer wind cloned-buff))
cloned-buff)) cloned-buff))
(defun ediff-buffer-type (buffer) (defun ediff-buffer-type (buffer)

View file

@ -53,16 +53,21 @@ frame.
If you don't like any of the two provided functions, write your own one. If you don't like any of the two provided functions, write your own one.
The basic guidelines: The basic guidelines:
1. It should leave the control buffer current and the control window 1. It should leave the control buffer current and, if showing,
selected. the control window selected if showing these windows.
2. It should set `ediff-window-A', `ediff-window-B', `ediff-window-C', 2. It should set `ediff-window-A', `ediff-window-B', `ediff-window-C',
and `ediff-control-window' to contain window objects that display and `ediff-control-window' to contain window objects that display
the corresponding buffers. the corresponding buffers or `nil' if the corresponding window
is not shown.
3. It should accept the following arguments: 3. It should accept the following arguments:
buffer-A, buffer-B, buffer-C, control-buffer buffer-A, buffer-B, buffer-C, control-buffer
Buffer C may not be used in jobs that compare only two buffers. Buffer C may not be used in jobs that compare only two buffers.
If you plan to do something fancy, take a close look at how the two If you plan to do something fancy, take a close look at how the two
provided functions are written." provided functions are written.
Set `ediff-select-control-window-on-setup' to nil to prevent the window
`ediff-control-window' being selected by ediff after this
function returns. "
:type '(choice (const :tag "Choose Automatically" ediff-setup-windows-default) :type '(choice (const :tag "Choose Automatically" ediff-setup-windows-default)
(const :tag "Multi Frame" ediff-setup-windows-multiframe) (const :tag "Multi Frame" ediff-setup-windows-multiframe)
(const :tag "Single Frame" ediff-setup-windows-plain) (const :tag "Single Frame" ediff-setup-windows-plain)
@ -247,6 +252,17 @@ keyboard input to go into icons."
;;; Functions ;;; Functions
(defmacro ediff-with-live-window (window &rest body)
"Like `with-selected-window' but only if WINDOW is live.
If WINDOW is not live (or not a window) do nothing and don't evaluate
BODY, instead returning nil."
(declare (indent 1) (debug (form body)))
(let ((w (gensym "window")))
`(let ((,w ,window))
(when (window-live-p ,w)
(with-selected-window ,w
,@body)))))
(defun ediff-get-window-by-clicking (_wind _prev-wind wind-number) (defun ediff-get-window-by-clicking (_wind _prev-wind wind-number)
(let (event) (let (event)
(message (message