branch: externals/ement
commit 7418626abe79fce12f834251d4690dcdf4331dd7
Author: Phil Sainty <p...@catalyst.net.nz>
Commit: Phil Sainty <p...@catalyst.net.nz>
Fix: More robust auto-height behaviour for compose buffer windows
A variety of changes to address edge-cases which might cause compose
buffer windows to be the wrong height.
(ement-room-compose-buffer-window-auto-height-fixed) New variable.
(ement-room-compose-buffer-window-state-change-handler)
(ement-room-compose-buffer-window-buffer-change-handler) New handler
functions used in buffer-local window change hooks.
(ement-room-init-compose-buffer) Apply the fixes.
(ement-room-compose-buffer-window-auto-height) Prevent recursion.
---
ement-room.el | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 113 insertions(+), 4 deletions(-)
diff --git a/ement-room.el b/ement-room.el
index 8e79befb1e..ccd0bb8d26 100644
--- a/ement-room.el
+++ b/ement-room.el
@@ -508,9 +508,18 @@ See also
`ement-room-compose-buffer-window-auto-height-max' and
`ement-room-compose-buffer-window-auto-height-min'."
:type 'boolean)
+;; Experimental. Disabled by default. Set to 'height to use this.
+(defvar ement-room-compose-buffer-window-auto-height-fixed nil
+ "The buffer-local `window-size-fixed' value in compose buffers.")
+
(defvar ement-room-compose-buffer-window-auto-height-pixelwise t
"Whether to adjust the window height for pixel-precise lines.")
+;; This is a mutex to ensure that auto-height resizing cannot trigger itself
+;; recursively. This may prevent desirable resizing in certain cases, but we
+;; get the correct result in the majority of situations, and it is simple.
+(defvar ement-room-compose-buffer-window-auto-height-resizing-p)
+
(defvar ement-room-compose-buffer-window-auto-height-cache)
(defcustom ement-room-compose-buffer-window-auto-height-min nil
@@ -4355,15 +4364,46 @@ a copy of the local keymap, and sets
`header-line-format'."
;; Adjust the window height automatically.
(when ement-room-compose-buffer-window-auto-height
(add-hook 'post-command-hook
- #'ement-room-compose-buffer-window-auto-height nil :local)))
+ #'ement-room-compose-buffer-window-auto-height nil :local)
+ ;; Our `window-min-height' comprises header & mode line + body lines.
+ (setq-local window-min-height
+ (+ 2 (if ement-room-compose-buffer-window-auto-height-min
+ (max 1
ement-room-compose-buffer-window-auto-height-min)
+ 1)))
+ (when ement-room-compose-buffer-window-auto-height-fixed
+ (setq-local window-size-fixed
+ ement-room-compose-buffer-window-auto-height-fixed))
+ ;; The following helps when `window--sanitize-window-sizes' adjusts all
+ ;; windows in a frame (e.g. when splitting windows), as otherwise any
+ ;; existing compose buffer windows are liable to be resized line-wise,
+ ;; resulting in excess padding being introduced.
+ (when ement-room-compose-buffer-window-auto-height-pixelwise
+ (setq-local window-resize-pixelwise t)))
+ ;; Other compose buffer window behaviours.
+ (add-hook 'window-state-change-functions
+ #'ement-room-compose-buffer-window-state-change-handler nil :local)
+ (add-hook 'window-buffer-change-functions
+ #'ement-room-compose-buffer-window-buffer-change-handler nil
:local))
(defun ement-room-compose-buffer-window-auto-height ()
"Ensure that the compose buffer displays the whole message.
Called via `post-command-hook' if
`ement-room-compose-buffer-window-auto-height'
is non-nil."
- ;; Manipulate the window body height.
- (unless (window-full-height-p)
+ ;; We use `post-command-hook' (rather than, say, `after-change-functions'),
+ ;; because the required window height might change for reasons other than
text
+ ;; editing (e.g. changes to the window's width or the font size).
+ ;;
+ ;; Note that changes to the default face size (e.g. via `text-scale-adjust')
+ ;; affect `default-line-height', invalidating the cache even when the text
+ ;; itself didn't change.
+ ;;
+ ;; The following may also clear the cache in order to force a recalculation:
+ ;; - `ement-room-compose-buffer-window-state-change-handler'
+ ;; - `ement-room-compose-buffer-window-buffer-change-handler'
+ (unless (or (bound-and-true-p
ement-room-compose-buffer-window-auto-height-resizing-p)
+ (window-full-height-p))
+ ;; Manipulate the window body height.
(let* ((pixelwise (and
ement-room-compose-buffer-window-auto-height-pixelwise
(display-graphic-p)))
(lineheight (and pixelwise (default-line-height)))
@@ -4376,7 +4416,8 @@ is non-nil."
(eql cache
ement-room-compose-buffer-window-auto-height-cache))
;; Otherwise resize the window...
(setq-local ement-room-compose-buffer-window-auto-height-cache cache)
- (let* ((minheight (if ement-room-compose-buffer-window-auto-height-min
+ (let* ((ement-room-compose-buffer-window-auto-height-resizing-p t)
+ (minheight (if ement-room-compose-buffer-window-auto-height-min
(max 1
ement-room-compose-buffer-window-auto-height-min)
1))
(maxheight ement-room-compose-buffer-window-auto-height-max)
@@ -4403,6 +4444,13 @@ is non-nil."
(let ((delta (- reqlines (window-body-height))))
(when-let ((delta (window-resizable nil delta nil t)))
(window-resize nil delta nil t))))
+ ;; Ask Emacs to "preserve" the new height. So long as the window
+ ;; maintains this height and is displaying this specific buffer,
Emacs
+ ;; will avoid unnecessary height changes from side-effects of
commands
+ ;; such as `balance-windows'. Explicit height changes are allowed.
+ ;; We must update this parameter every time we change the height so
+ ;; that the "preserved" height value is always correct.
+ (window-preserve-size nil nil t)
;; In most cases we can fit the whole buffer in the resized window.
(set-window-start nil (point-min) :noforce)
;; The resizing might have obscured the room buffer's window point,
so
@@ -4411,6 +4459,67 @@ is non-nil."
(let ((scroll-conservatively 101))
(redisplay)))))))
+(defun ement-room-compose-buffer-window-state-change-handler (win)
+ "Called via buffer-local `window-state-change-functions' in compose buffers.
+
+Called for any window WIN showing a compose buffer if that window
+has been added or assigned another buffer, changed size, or been
+selected or deselected.
+
+This prevents a compose buffer window being stuck at the wrong
+height (until the number of lines changes again) if something
+other than the auto-height feature resizes the window. We simply
+flush the height cache for these state changes, thus ensuring the
+required height is recalculated on the next cycle).
+
+See also `ement-room-compose-buffer-window-buffer-change-handler'."
+ (when ement-room-compose-buffer-window-auto-height
+ ;; Ignore the window state changes triggered by our auto-height resizing.
+ (unless (bound-and-true-p
ement-room-compose-buffer-window-auto-height-resizing-p)
+ (let ((buf (current-buffer)))
+ ;; Do nothing if the state change happened while the compose buffer was
+ ;; current, as the buffer-local `post-command-hook' is already dealing
+ ;; with that case. We only care about window state changes which are
+ ;; triggered from elsewhere. This means that we skip the case whereby
+ ;; the selected window has just switched to the compose buffer, and so
+ ;; we use `window-buffer-change-functions' as well to capture that
case.
+ ;; (See `ement-room-compose-buffer-window-buffer-change-handler'.)
+ (with-selected-window win
+ (unless (eq (current-buffer) buf)
+ ;; Clear the height cache for this compose buffer.
+ (when (bound-and-true-p
ement-room-compose-buffer-window-auto-height-cache)
+ (setq-local ement-room-compose-buffer-window-auto-height-cache
nil))))))))
+
+(defun ement-room-compose-buffer-window-buffer-change-handler (win)
+ "Called via buffer-local `window-buffer-change-functions' in compose buffers.
+
+Called for any window WIN showing a compose buffer if that window
+has just been created or assigned that buffer.
+
+Like `ement-room-compose-buffer-window-state-change-handler' (see
+which) in purpose, but handling the case where WIN has just
+switched to displaying the compose buffer (which the other
+handler ignores as a side-effect of its conditional logic).
+
+This function also determines whether the composer buffer's
+window has been newly created, which affects the behaviour of
+`ement-room-compose-buffer-quit-restore-window'."
+ (when ement-room-compose-buffer-window-auto-height
+ (with-selected-window win
+ ;; Clear the height cache for this compose buffer.
+ (when (bound-and-true-p
ement-room-compose-buffer-window-auto-height-cache)
+ (setq-local ement-room-compose-buffer-window-auto-height-cache nil)
+ ;; `window-buffer-change-functions' runs /after/ `post-command-hook',
+ ;; so after flushing the cache we need to invoke the resize handler, as
+ ;; otherwise it wouldn't update until after the /subsequent/ command.
+ ;; We don't want to do this while this hook is still running though
+ ;; (the result is inaccurate), so defer it with a timer.
+ (unless (bound-and-true-p
ement-room-compose-buffer-window-auto-height-resizing-p)
+ (run-with-timer 0 nil (lambda (win)
+ (with-selected-window win
+
(ement-room-compose-buffer-window-auto-height)))
+ win))))))
+
(declare-function dabbrev--select-buffers "dabbrev")
(defun ement-compose-dabbrev-select-buffers ()