113 lines
5.1 KiB
EmacsLisp
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
|