ebox/ebox-surface.el
Kinneyzhang cdf841232a refactor(ebox): publish roots through TP surfaces
Move ephemeral rendering, initial buffer mounts, declarative commits, and opaque handle updates onto retained TP surfaces while keeping Ebox layout planning and runtime indexes transactionally synchronized.

Verified with: make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
2026-08-06 05:49:22 +08:00

632 lines
28 KiB
EmacsLisp

;;; ebox-surface.el --- Retained projection and publication -*- lexical-binding: t; -*-
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Commentary:
;; Owns the boundary from an Ebox candidate runtime to a generic TP surface
;; plan, and the atomic publication participant that mirrors the committed TP
;; generation into Ebox's buffer-local runtime indexes.
;;; Code:
(require 'cl-lib)
(require 'ebox-tree)
(require 'ebox-layout)
(require 'ebox-incremental)
(require 'tp-surface)
(defvar ebox-region-types)
(defvar ebox--region-box-table)
(defvar ebox--scroll-global-state)
(defvar ebox--scroll-idle-prefetch-timers)
(defvar ebox--smooth-scroll-state-table)
(defvar ebox--box-extents)
(defvar ebox--box-extent-template)
(defvar ebox--buffer-render-state-table)
(defvar ebox--viewport-dependent-node-ids-cache)
(defvar ebox--viewport-dependent-subtree-cache)
(defvar ebox--viewport-height-dependent-subtree-cache)
(defvar ebox--render-runtime-revision)
(defvar ebox--render-cache-table)
(defvar ebox--render-cache-signature-cache)
(defvar ebox--flex-content-min-width-table)
(declare-function ebox--render-layout "ebox-layout" (node))
(declare-function ebox-buffer--build-region-role-span-template
"ebox-buffer-backend" (string))
(declare-function ebox-buffer-materialize-region-role-spans
"ebox-buffer-backend" (buffer))
(declare-function ebox--build-box-extent-template "ebox" (rendered))
(declare-function ebox--build-scroll-content-span-template "ebox" (string))
(declare-function ebox--set-box-extents "ebox" (region-id start end))
(declare-function ebox--clear-buffer-extents
"ebox" (&optional buffer preserve-template))
(declare-function ebox--clear-box-extents-for-region-ids
"ebox" (region-ids))
(declare-function ebox--install-box-extent-template
"ebox" (buffer template start))
(declare-function ebox--install-buffer-scroll-content-span-template
"ebox" (buffer template start &optional defer-window-region-set))
(declare-function ebox-incremental--hash-snapshot
"ebox-incremental" (table keys))
(declare-function ebox-incremental--restore-hash-snapshot
"ebox-incremental" (table entries))
(declare-function ebox-incremental--replace-hash-entries
"ebox-incremental" (target keys source))
(declare-function ebox-incremental--detach-scroll-state-table-markers
"ebox-incremental" (table))
(declare-function ebox--runtime-region-id-conflict
"ebox-incremental" (region-id-set target-buffer))
(declare-function ebox--viewport-dependent-node-id-axes
"ebox-incremental" (node))
(declare-function ebox-incremental--detach-region-role-span-table
"ebox-incremental" (table))
(declare-function ebox-incremental--finalize-declarative-scroll-publication
"ebox-incremental" (scroll-keys))
(declare-function ebox--scroll-schedule-idle-prefetch
"ebox" (region-id &optional delay))
(declare-function ebox--scroll-clear-state "ebox" (region-id))
(declare-function ebox--smooth-scroll-stop "ebox" (region-id))
(defvar-local ebox-surface--buffer-surface nil
"Live TP content surface mounted for the current Ebox buffer.")
(defun ebox-surface--live-buffer-surface (buffer)
"Return BUFFER's live Ebox TP surface, or nil."
(when (buffer-live-p buffer)
(let ((surface
(with-current-buffer buffer ebox-surface--buffer-surface)))
(and (tp-surface-live-p surface) surface))))
(defun ebox-surface--buffer-extent-snapshot (buffer)
"Return numeric live box extents owned by BUFFER."
(let (snapshot)
(maphash
(lambda (region-id extents)
(when (eq (marker-buffer (car extents)) buffer)
(push (list region-id
(marker-position (car extents))
(marker-position (cdr extents)))
snapshot)))
ebox--box-extents)
snapshot))
(defun ebox-surface--restore-buffer-extents (buffer snapshot template)
"Restore BUFFER box extents from SNAPSHOT and numeric TEMPLATE."
(ebox--clear-buffer-extents buffer)
(when (buffer-live-p buffer)
(with-current-buffer buffer
(setq-local ebox--box-extent-template template)
(dolist (entry snapshot)
(ebox--set-box-extents (nth 0 entry) (nth 1 entry) (nth 2 entry))))))
(defun ebox-surface--runtime-keys (state key)
"Return hash keys stored under KEY in runtime STATE."
(when-let ((table (plist-get state key)))
(ebox-surface--hash-keys table)))
(defun ebox-surface--commit-report (surface state report-base)
"Return Ebox's compact report for SURFACE, STATE, and REPORT-BASE."
(let* ((tp-report (tp-surface-report surface))
(text-operations (or (plist-get tp-report :text-operations) 0))
(property-operations
(or (plist-get tp-report :property-operations) 0))
(patch-count (+ text-operations property-operations))
(report (copy-sequence report-base))
(planned-publication-scope (plist-get report :publication-scope)))
(dolist (entry
`((:runtime-published . t)
(:runtime-revision . ,(plist-get state :runtime-revision))
(:surface-revision . ,(tp-surface-revision surface))
(:publication-scope . tp-surface)
(:planned-publication-scope . ,planned-publication-scope)
(:tp-render-scope . surface-plan)
(:tp-operation-count . ,patch-count)
(:tp-transaction-id . ,(plist-get tp-report :transaction-id))
(:tp-text-operations . ,text-operations)
(:tp-property-operations . ,property-operations)
(:reconciled-objects
. ,(plist-get tp-report :reconciled-objects))
(:created-objects . ,(plist-get tp-report :created-objects))
(:removed-objects . ,(plist-get tp-report :removed-objects))
(:moved-objects . ,(plist-get tp-report :moved-objects))))
(setq report (plist-put report (car entry) (cdr entry))))
(unless (plist-member report :strategy)
(setq report
(plist-put report :strategy
(if (zerop patch-count) 'no-op 'surface-commit))))
(unless (plist-member report :constraint-source)
(setq report (plist-put report :constraint-source 'declarative)))
(unless (plist-member report :constraint-root-node-id)
(setq report
(plist-put report :constraint-root-node-id
(plist-get (plist-get state :root-node) :node-id))))
(unless (plist-member report :dirty-count)
(setq report
(plist-put report :dirty-count
(if (zerop patch-count) 0 1))))
(unless (plist-member report :patch-count)
(setq report (plist-put report :patch-count patch-count)))
(unless (plist-member report :patch-ops)
(setq report
(plist-put report :patch-ops
(and (> patch-count 0) '(tp-surface)))))
report))
(defun ebox-surface--publish-runtime-state
(buffer surface old-state report-base after-publication)
"Register BUFFER runtime publication for SURFACE after OLD-STATE.
REPORT-BASE requests an Ebox commit report. AFTER-PUBLICATION, when non-nil,
runs after TP and Ebox point at the same candidate generation."
(let (new-state region-snapshot scroll-snapshot extent-snapshot
old-template old-surface old-mirror region-keys scroll-keys)
(tp-transaction-participate
(list 'ebox/runtime buffer)
(lambda ()
(setq new-state (tp-surface-client-state surface)
old-surface
(with-current-buffer buffer ebox-surface--buffer-surface)
old-mirror (gethash buffer ebox--buffer-render-state-table)
old-template (with-current-buffer buffer ebox--box-extent-template)
extent-snapshot (ebox-surface--buffer-extent-snapshot buffer)
region-keys
(delete-dups
(append (ebox-surface--runtime-keys old-state :region-id-set)
(ebox-surface--runtime-keys new-state :region-id-set)))
scroll-keys
(delete-dups
(append (copy-sequence (plist-get old-state :scroll-region-ids))
(copy-sequence (plist-get new-state :scroll-region-ids)))))
(setq region-snapshot
(ebox-incremental--hash-snapshot
ebox--region-box-table region-keys)
scroll-snapshot
(ebox-incremental--hash-snapshot
ebox--scroll-global-state scroll-keys))
(when new-state
(plist-put new-state :surface surface)
(plist-put new-state :runtime-revision
(1- (tp-surface-revision surface)))
(when report-base
(plist-put new-state :last-update-report
(ebox-surface--commit-report
surface new-state report-base))))
(with-current-buffer buffer
(setq-local ebox-surface--buffer-surface surface))
(if new-state
(puthash buffer new-state ebox--buffer-render-state-table)
(remhash buffer ebox--buffer-render-state-table))
(ebox-incremental--replace-hash-entries
ebox--region-box-table region-keys
(plist-get new-state :region-box-table))
(ebox-incremental--replace-hash-entries
ebox--scroll-global-state scroll-keys
(plist-get new-state :scroll-state-table))
(ebox--clear-buffer-extents buffer)
(when new-state
(ebox--install-box-extent-template
buffer (plist-get new-state :box-extent-template) 1)
(ebox--install-buffer-scroll-content-span-template
buffer (plist-get new-state :scroll-content-span-template) 1)
(ebox-buffer-materialize-region-role-spans buffer))
(when after-publication
(funcall after-publication
(plist-get new-state :last-update-report)))
(unless (buffer-live-p buffer)
(error "Ebox declarative target died during publication")))
(lambda ()
(when-let ((table (plist-get new-state :scroll-state-table)))
(ebox-incremental--detach-scroll-state-table-markers table))
(if (buffer-live-p buffer)
(progn
(when region-snapshot
(ebox-incremental--restore-hash-snapshot
ebox--region-box-table region-snapshot))
(when scroll-snapshot
(ebox-incremental--restore-hash-snapshot
ebox--scroll-global-state scroll-snapshot))
(with-current-buffer buffer
(setq-local ebox-surface--buffer-surface old-surface))
(ebox-surface--restore-buffer-extents
buffer extent-snapshot old-template)
(if old-mirror
(puthash buffer old-mirror ebox--buffer-render-state-table)
(remhash buffer ebox--buffer-render-state-table)))
(remhash buffer ebox--buffer-render-state-table)
(dolist (region-id region-keys)
(remhash region-id ebox--region-box-table))
(dolist (region-id scroll-keys)
(ebox--scroll-clear-state region-id)
(ebox--smooth-scroll-stop region-id))
(ebox--clear-box-extents-for-region-ids region-keys))))))
(defun ebox-surface--retire-state (old-state new-state)
"Retire marker and timer resources in OLD-STATE after NEW-STATE commits."
(when old-state
(let ((old-role-table (plist-get old-state :region-role-span-table))
(new-role-table (plist-get new-state :region-role-span-table)))
(unless (eq old-role-table new-role-table)
(ebox-incremental--detach-region-role-span-table old-role-table)))
(let ((old-scroll-table (plist-get old-state :scroll-state-table))
(new-scroll-table (plist-get new-state :scroll-state-table)))
(unless (eq old-scroll-table new-scroll-table)
(ebox-incremental--detach-scroll-state-table-markers old-scroll-table)))))
(defun ebox-surface-mount-buffer
(buffer source &optional report-base after-publication
preserve-identities-p state-overrides)
"Mount or atomically update BUFFER from Ebox SOURCE and return its surface.
REPORT-BASE requests a committed Ebox report. AFTER-PUBLICATION runs inside
the rollback-capable transaction. PRESERVE-IDENTITIES-P retains identities
already present in a logical candidate. STATE-OVERRIDES augments its runtime."
(let* ((surface (ebox-surface--live-buffer-surface buffer))
(old-state (and surface (tp-surface-client-state surface)))
(preserve-identities-p
(or preserve-identities-p
(and (null old-state)
(ebox-surface--source-identities-available-p
source buffer))))
(producer
(ebox-surface-producer
source old-state preserve-identities-p state-overrides)))
(tp-with-transaction
(if surface
(tp-surface-update surface producer)
(setq surface
(tp-surface-mount
buffer producer
'(:capability content :inhibit-read-only t))))
(ebox-surface--publish-runtime-state
buffer surface old-state report-base after-publication))
(let* ((new-state (tp-surface-client-state surface))
(scroll-keys
(delete-dups
(append (copy-sequence (plist-get old-state :scroll-region-ids))
(copy-sequence (plist-get new-state :scroll-region-ids))))))
(ebox-surface--retire-state old-state new-state)
(ebox-incremental--finalize-declarative-scroll-publication scroll-keys))
surface))
(defconst ebox-surface--root-key 'ebox/surface
"Stable TP key for an Ebox surface projection root.")
(defconst ebox-surface--nodes-key 'ebox/nodes
"Stable TP key for the retained logical Ebox node tree.")
(defconst ebox-surface--fragments-key 'ebox/fragments
"Stable TP key for linear rendered fragments.")
(defun ebox-surface--clear-runtime-attachments (root)
"Clear TP handles and render-cache attachments below ROOT."
(cl-labels
((visit (node)
(when (and (listp node) (not (stringp node)))
(when (plist-member node :surface-object)
(plist-put node :surface-object nil))
(when (plist-member node :render-cache)
(plist-put node :render-cache nil))
(dolist (child (ebox-tree--children-raw node))
(visit child)))))
(visit root))
root)
(defun ebox-surface--candidate-root
(source previous-state preserve-identities-p)
"Return an isolated runtime copy of SOURCE for PREVIOUS-STATE.
When PRESERVE-IDENTITIES-P is non-nil, retain existing Ebox node and region
identities while always discarding TP handles and render-cache attachments."
(unless (and (listp source) (not (stringp source)))
(error "Ebox surface source must be an Ebox node"))
(ebox-tree-validate-declarative-root source)
(let ((candidate (ebox-tree-copy-node-structure source)))
(if preserve-identities-p
(ebox-surface--clear-runtime-attachments candidate)
(ebox-tree-clear-runtime-identities candidate))
(when-let ((previous-root (plist-get previous-state :root-node)))
(ebox-tree-reconcile-runtime previous-root candidate))
candidate))
(defun ebox-surface--source-region-id-set (source)
"Return region ids already present in Ebox SOURCE without mutating it."
(let ((ids (make-hash-table :test 'equal)))
(cl-labels
((visit (node)
(when (and (listp node) (not (stringp node)))
(when-let ((region-id (plist-get node :region-id)))
(puthash region-id t ids))
(dolist (child (ebox-tree--children-raw node))
(visit child)))))
(visit source))
ids))
(defun ebox-surface--source-identities-available-p (source buffer)
"Return non-nil when SOURCE's migration ids are available for BUFFER."
(or (stringp source)
(not (ebox--runtime-region-id-conflict
(ebox-surface--source-region-id-set source) buffer))))
(defun ebox-surface--node-key (node)
"Return NODE's namespaced sibling key, or nil for positional identity."
(when-let ((key (plist-get node :key)))
(list 'ebox/key key)))
(defun ebox-surface--node-kind (node)
"Return the TP kind discriminator for Ebox NODE."
(list 'ebox/node (plist-get node :ebox-type)))
(defun ebox-surface--ensure-node-tree (context parent node table)
"Ensure NODE and descendants below PARENT in CONTEXT and fill TABLE."
(let ((object
(tp-object-ensure context parent
(ebox-surface--node-key node)
(ebox-surface--node-kind node))))
(tp-object-retain context object)
(plist-put node :surface-object object)
(puthash node object table)
(dolist (child (ebox-tree--children-raw node))
(ebox-surface--ensure-node-tree context object child table))
object))
(defun ebox-surface--node-object-table (objects-by-node)
"Return a node-id keyed table from OBJECTS-BY-NODE."
(let ((table (make-hash-table :test 'equal)))
(maphash
(lambda (node object)
(let ((node-id (plist-get node :node-id)))
(unless node-id
(error "Ebox rendered node has no runtime identity"))
(puthash node-id object table)))
objects-by-node)
table))
(defun ebox-surface--region-object-table (state node-objects)
"Return region-to-object table from candidate STATE and NODE-OBJECTS."
(let ((table (make-hash-table :test 'equal)))
(maphash
(lambda (region-id node-id)
(let ((object (gethash node-id node-objects)))
(unless object
(error "Ebox region %S has no candidate surface object" region-id))
(puthash region-id object table)))
(plist-get state :region-node-table))
table))
(defun ebox-surface--object-region-table (region-objects)
"Return an object-to-region table from REGION-OBJECTS."
(let ((table (make-hash-table :test 'eq)))
(maphash (lambda (region-id object)
(puthash object region-id table))
region-objects)
table))
(defun ebox-surface--node-editable-region-id (node)
"Return NODE's editable Ebox region id, or nil."
(pcase (and (listp node) (plist-get node :ebox-type))
('box (plist-get node :region-id))
((or 'flex 'grid)
(plist-get (plist-get node :box) :region-id))
('flex-item
(ebox-surface--node-editable-region-id (plist-get node :node)))
(_ nil)))
(defun ebox-surface--logical-id-region-table (state region-objects)
"Return root-scoped logical-id entries for STATE and REGION-OBJECTS."
(let ((table (make-hash-table :test 'equal)))
(maphash
(lambda (logical-id entries)
(dolist (entry entries)
(when-let* ((region-id
(ebox-surface--node-editable-region-id (car entry)))
(object (gethash region-id region-objects)))
(push (cons object region-id) (gethash logical-id table)))))
(plist-get state :selector-id-table))
(maphash (lambda (logical-id matches)
(puthash logical-id (nreverse matches) table))
table)
table))
(defun ebox-surface--hash-keys (table)
"Return TABLE keys in unspecified order."
(let (keys)
(maphash (lambda (key _value) (push key keys)) table)
keys))
(defun ebox-surface--render-candidate (state)
"Render candidate STATE in isolated Ebox side tables."
(let ((scroll-table (make-hash-table :test 'equal))
(timer-table (make-hash-table :test 'equal))
(smooth-table (make-hash-table :test 'equal)))
(let ((ebox--region-box-table (plist-get state :region-box-table))
(ebox--scroll-global-state scroll-table)
(ebox--scroll-idle-prefetch-timers timer-table)
(ebox--smooth-scroll-state-table smooth-table)
(ebox--render-runtime-revision
(plist-get state :runtime-revision))
(ebox--render-cache-table (plist-get state :render-cache))
(ebox--render-cache-signature-cache
(plist-get state :render-signature-cache))
(ebox--viewport-dependent-node-ids-cache
(make-hash-table :test 'eq))
(ebox--viewport-dependent-subtree-cache
(make-hash-table :test 'eq))
(ebox--viewport-height-dependent-subtree-cache
(plist-get state :viewport-height-dependent-subtree-cache))
(ebox--flex-content-min-width-table
(plist-get state :flex-content-min-widths)))
(cl-letf (((symbol-function 'ebox--scroll-schedule-idle-prefetch)
(lambda (&rest _) nil)))
(prog1 (let ((ebox--surface-materialization-active t))
(ebox--render-layout (plist-get state :root-node)))
(plist-put state :scroll-state-table scroll-table)
(plist-put state :scroll-region-ids
(ebox-surface--hash-keys scroll-table)))))))
(defun ebox-surface--finish-runtime-state (state)
"Install post-layout runtime indexes into candidate STATE."
(setq state
(ebox--render-state-install-index
state (ebox--runtime-index (plist-get state :root-node) t)))
(let* ((root (plist-get state :root-node))
(ebox--viewport-dependent-node-ids-cache
(make-hash-table :test 'eq))
(ebox--viewport-height-dependent-subtree-cache
(plist-get state :viewport-height-dependent-subtree-cache))
(axes (ebox--viewport-dependent-node-id-axes root)))
(plist-put state :viewport-dependent-node-id-axes axes)
(plist-put state :viewport-dependent-node-ids
(and axes
(delete-dups
(copy-sequence (append (car axes) (cdr axes))))))
(plist-put state :viewport-dependent-node-ids-ready t))
state)
(defun ebox-surface--role-ids-at (rendered position)
"Return namespaced Ebox role/id pairs at POSITION in RENDERED."
(let (roles)
(dolist (region-id
(get-text-property position 'ebox-content-owners rendered))
(cl-pushnew (cons 'content-owner region-id) roles :test #'equal))
(dolist (entry ebox-region-types)
(when-let ((region-id
(get-text-property position (cdr entry) rendered)))
(cl-pushnew (cons (car entry) region-id) roles :test #'equal)))
(nreverse roles)))
(defun ebox-surface--region-role-tags (region-id role-ids)
"Return projection tags for REGION-ID from ROLE-IDS."
(list :ebox/region-id region-id
:ebox/roles
(cl-loop for (role . owner) in role-ids
when (equal owner region-id) collect role)))
(defun ebox-surface--attach-fragment
(context fragment role-ids region-objects)
"Attach FRAGMENT to ROLE-IDS owners through REGION-OBJECTS in CONTEXT."
(dolist (region-id (delete-dups (mapcar #'cdr role-ids)))
(let ((object (gethash region-id region-objects)))
(unless object
(error "Ebox output references unknown region %S" region-id))
(tp-object-attach-fragment
context object fragment
(ebox-surface--region-role-tags region-id role-ids)))))
(defun ebox-surface--fragment-plan
(context parent rendered start end index region-objects)
"Return one plan below PARENT for RENDERED START..END at INDEX in CONTEXT."
(let* ((key (cons 'ebox/fragment index))
(object (tp-object-ensure context parent key 'ebox/fragment))
(role-ids (ebox-surface--role-ids-at rendered start))
(tags (list :ebox/fragment index :ebox/role-ids role-ids)))
(ebox-surface--attach-fragment
context object role-ids region-objects)
(tp-surface-plan-create
:key key :kind 'ebox/fragment
:text (substring rendered start end) :tags tags
:capability 'content)))
(defun ebox-surface--fragment-plans
(context parent rendered region-objects)
"Return property-interval plans in CONTEXT for RENDERED below PARENT."
(let ((position 0)
(limit (length rendered))
(index 0)
plans)
(while (< position limit)
(let ((next (or (next-property-change position rendered limit) limit)))
(push (ebox-surface--fragment-plan
context parent rendered position next index region-objects)
plans)
(setq position (max next (1+ position))
index (1+ index))))
(nreverse plans)))
(defun ebox-surface--surface-plan
(context surface-root rendered region-objects)
"Return CONTEXT's plan below SURFACE-ROOT for RENDERED and REGION-OBJECTS."
(let* ((fragment-root
(tp-object-ensure context surface-root
ebox-surface--fragments-key 'ebox/fragments))
(fragments
(ebox-surface--fragment-plans
context fragment-root rendered region-objects)))
(tp-surface-plan-create
:key ebox-surface--root-key :kind 'ebox/surface
:children
(list (tp-surface-plan-create
:key ebox-surface--fragments-key :kind 'ebox/fragments
:children fragments :capability 'content))
:capability 'content)))
(defun ebox-surface--apply-state-overrides (state overrides)
"Apply plist OVERRIDES to candidate runtime STATE and return STATE."
(while overrides
(setq state (plist-put state (pop overrides) (pop overrides))))
state)
(defun ebox-surface--project
(context source previous-state preserve-identities-p state-overrides)
"Project SOURCE in CONTEXT using PREVIOUS-STATE and identity policy.
PRESERVE-IDENTITIES-P retains existing Ebox node and region identities."
(if (stringp source)
(let ((object
(tp-object-ensure context nil ebox-surface--root-key
'ebox/string)))
(ignore object)
(tp-surface-result-create
(tp-surface-plan-create
:key ebox-surface--root-key :kind 'ebox/string
:text source :capability 'content)))
(let* ((root (ebox-surface--candidate-root
source previous-state preserve-identities-p))
(state
(ebox-surface--apply-state-overrides
(ebox--new-buffer-render-state root)
(copy-sequence state-overrides)))
(surface-root
(tp-object-ensure context nil ebox-surface--root-key 'ebox/surface))
(node-root
(tp-object-ensure context surface-root
ebox-surface--nodes-key 'ebox/nodes))
(objects-by-node (make-hash-table :test 'eq)))
(tp-object-retain context node-root)
(ebox-surface--ensure-node-tree context node-root root objects-by-node)
(plist-put state :region-box-table (make-hash-table :test 'equal))
(let ((rendered (ebox-surface--render-candidate state)))
(setq state (ebox-surface--finish-runtime-state state))
(plist-put state :region-role-span-table
(ebox-buffer--build-region-role-span-template rendered))
(plist-put state :box-extent-template
(ebox--build-box-extent-template rendered))
(plist-put state :scroll-content-span-template
(let ((ebox--scroll-global-state
(plist-get state :scroll-state-table)))
(ebox--build-scroll-content-span-template rendered)))
(let* ((node-objects (ebox-surface--node-object-table objects-by-node))
(region-objects
(ebox-surface--region-object-table state node-objects)))
(plist-put state :surface-node-object-table node-objects)
(plist-put state :region-surface-object-table region-objects)
(plist-put state :surface-object-region-table
(ebox-surface--object-region-table region-objects))
(plist-put state :logical-id-region-table
(ebox-surface--logical-id-region-table
state region-objects))
(tp-surface-result-create
(ebox-surface--surface-plan
context surface-root rendered region-objects)
state))))))
(defun ebox-surface-producer
(source &optional previous-state preserve-identities-p state-overrides)
"Return a TP producer for Ebox SOURCE and optional PREVIOUS-STATE.
PRESERVE-IDENTITIES-P retains existing Ebox node and region identities.
STATE-OVERRIDES augments the candidate Ebox runtime state."
(lambda (context)
(ebox-surface--project
context source previous-state preserve-identities-p state-overrides)))
(provide 'ebox-surface)
;;; ebox-surface.el ends here