ebox/ebox-viewport.el

113 lines
5.1 KiB
EmacsLisp

;;; ebox-viewport.el --- Event-loop viewport delivery -*- lexical-binding: t; -*-
;;; Commentary:
;; Emacs owns window events; Ebox owns the mounted viewport. This module is
;; the single bridge between them. Size hooks record frame intents; one task
;; samples current visible dimensions and publishes after the redisplay hook.
;;; Code:
(require 'cl-lib)
(require 'ebox-surface)
(declare-function ebox--buffer-render-state "ebox-incremental" (buffer))
(declare-function ebox-rerender-buffer-with-context
"ebox" (buffer viewport-width &optional viewport-height))
(defvar ebox--window-size-change-in-progress nil
"Non-nil while Ebox is draining one batch of viewport notifications.")
(defvar ebox--window-size-change-pending-frames nil
"Frames awaiting viewport synchronization, deduplicated by EQ.
The queue holds no buffers, runtime roots or sampled dimensions.")
(defvar ebox--window-size-change-timer nil
"The single owned task scheduled to drain viewport notifications.")
(defun ebox-viewport-window-width (window)
"Return Ebox's display-safe viewport width for live WINDOW."
(and (window-live-p window)
(ebox-surface--window-content-width window)))
(defun ebox--window-size-change-sync-frame (frame)
"Synchronize current mounted Ebox viewports whose canonical window is in FRAME."
(when (frame-live-p frame)
(let ((seen (make-hash-table :test #'eq)))
(dolist (window (window-list frame 'no-minibuf))
(when (window-live-p window)
(let ((buffer (window-buffer window)))
(unless (gethash buffer seen)
(puthash buffer t seen)
(let ((display-window
(and (buffer-live-p buffer)
(ebox-surface--buffer-display-window buffer))))
(when (and (window-live-p display-window)
(eq (window-frame display-window) frame)
(ebox-surface-buffer-mounted-p buffer))
(let* ((state (ebox--buffer-render-state buffer))
(width (ebox-viewport-window-width display-window))
(height (window-body-height display-window)))
(when (and width
state
(not (and
(= width
(or (plist-get state :viewport-width)
-1))
(= height
(or (plist-get state :viewport-height)
-1)))))
(ebox-rerender-buffer-with-context
buffer width height))))))))))))
(defun ebox--window-size-change-schedule ()
"Schedule one task for live pending frames outside the current batch."
(setq ebox--window-size-change-pending-frames
(cl-delete-if-not #'frame-live-p
ebox--window-size-change-pending-frames))
(when (and ebox--window-size-change-pending-frames
(not ebox--window-size-change-in-progress)
(not ebox--window-size-change-timer))
(setq ebox--window-size-change-timer
(run-at-time 0 nil #'ebox--window-size-change-drain))))
(defun ebox--window-size-change-drain ()
"Consume one frame batch, leaving nested notifications for a later task."
(unless ebox--window-size-change-in-progress
(let ((ebox--window-size-change-in-progress t)
(frames (nreverse ebox--window-size-change-pending-frames))
first-error)
(unwind-protect
(progn
(setq ebox--window-size-change-pending-frames nil
ebox--window-size-change-timer nil)
;; Timer dispatch inhibits quit by default. Enable C-g only after
;; cleanup is installed; TP retains its own final-accept protection.
(let ((inhibit-quit nil))
(while frames
(let ((frame (pop frames)))
(condition-case failure
(ebox--window-size-change-sync-frame frame)
(error (unless first-error (setq first-error failure))))))))
;; A quit may leave unstarted frames. Keep those intents, but do not
;; retry the frame whose publication already started and failed.
(dolist (frame frames)
(unless (memq frame ebox--window-size-change-pending-frames)
(push frame ebox--window-size-change-pending-frames)))
(let ((ebox--window-size-change-in-progress nil))
(ebox--window-size-change-schedule)))
(when first-error (signal (car first-error) (cdr first-error))))))
(defun ebox--window-size-change (frame)
"Record FRAME's latest viewport intent without rendering inside the hook."
(when (and (frame-live-p frame) (not noninteractive))
(unless (memq frame ebox--window-size-change-pending-frames)
(push frame ebox--window-size-change-pending-frames))
(ebox--window-size-change-schedule)))
(when (boundp 'window-size-change-functions)
(add-hook 'window-size-change-functions #'ebox--window-size-change))
(provide 'ebox-viewport)
;;; ebox-viewport.el ends here