Fix interactive Ebox publication and viewport bounds

This commit is contained in:
Kinneyzhang 2026-08-22 08:21:08 +08:00
parent 3fd63989e8
commit 1edc6d5bb6
5 changed files with 146 additions and 54 deletions

View File

@ -223,6 +223,18 @@ immediately after BODY can trigger GC before the updated buffer is visible."
(or (plist-get style :background-color)
(plist-get style :bgcolor))))
(defun ebox-buffer--default-foreground-face ()
"Return a face that resets only foreground to the resolved default.
Fall back to inheriting `default' when a headless frame has no concrete
foreground color."
(let ((foreground (face-attribute 'default :foreground nil t)))
(if (or (null foreground)
(eq foreground 'unspecified)
(and (stringp foreground)
(string-prefix-p "unspecified" foreground)))
'(:inherit default)
(list :foreground foreground))))
(defun ebox-buffer-paint-text-properties (style role)
"Return Emacs text properties for computed STYLE in paint ROLE.
This backend mapper intentionally accepts computed style facts and emits only
@ -231,7 +243,7 @@ buffer-facing paint properties. Layout-only properties never pass through."
(when-let ((foreground (ebox-buffer--paint-color style role)))
(setq face
(if (eq foreground 'ebox/default-foreground)
(plist-put face :inherit 'default)
(append (ebox-buffer--default-foreground-face) face)
(plist-put face :foreground foreground))))
(when-let ((background (ebox-buffer--paint-background-color style role)))
(setq face (plist-put face :background background)))
@ -282,7 +294,7 @@ buffer-facing paint properties. Layout-only properties never pass through."
(ebox--add-render-face!
string 0 length
(if (eq color 'ebox/default-foreground)
'(:inherit default)
(ebox-buffer--default-foreground-face)
`(:foreground ,color))
t)
(ebox--register-render-owned-face-values source string)))
@ -296,7 +308,7 @@ buffer-facing paint properties. Layout-only properties never pass through."
(ebox--add-render-face!
string 0 length
(if (eq color 'ebox/default-foreground)
'(:inherit default)
(ebox-buffer--default-foreground-face)
`(:foreground ,color))
t))
(when bgcolor

View File

@ -305,8 +305,8 @@ construction time and can use the static projection path."
Some Emacs GUI builds return a column-like half-width even when
`window-body-width' is called with PIXELWISE non-nil. When the result is
clearly inconsistent with the outer pixel width, prefer the outer width and
keep a one-pixel exclusive boundary. Headless/test windows retain the body
width fallback."
reserve two character columns for Emacs continuation/truncation display.
Headless/test windows retain the body width fallback."
(let ((body
(condition-case nil
(window-body-width window t)
@ -315,11 +315,16 @@ width fallback."
(condition-case nil
(window-pixel-width window)
(error nil))))
(max 0
(1-
(if (and body outer (> outer (* 1.8 body)))
outer
(or body outer 0))))))
(let* ((char-width
(condition-case nil
(frame-char-width (window-frame window))
(error 1)))
(reserve (max 2 (* 2 (max 1 char-width)))))
(max 0
(- (if (and body outer (> outer (* 1.8 body)))
outer
(or body outer 0))
reserve)))))
(defun ebox-surface--ensure-signals (buffer values)
"Return BUFFER's context signals for VALUES and whether they were created."

89
ebox.el
View File

@ -4519,46 +4519,55 @@ FRAMEWORK-ROLLBACK, when supplied, receives the same report if publication or
a later transaction phase fails, and is contained if it violates no-throw.
Return the successful publication report stored by `ebox-buffer-update-report'."
(unless (or (null framework-publish) (functionp framework-publish))
(signal 'wrong-type-argument (list 'functionp framework-publish)))
(unless (or (null framework-rollback) (functionp framework-rollback))
(signal 'wrong-type-argument (list 'functionp framework-rollback)))
(when (and framework-rollback (null framework-publish))
(error "Ebox framework rollback requires framework publish"))
(let ((buffer (get-buffer buffer-or-name)))
(unless (buffer-live-p buffer)
(error "Ebox declarative commit requires an existing live buffer: %S"
buffer-or-name))
(let* ((commit-input
(if (ebox-candidate-p next-root)
(ebox-incremental-consume-candidate buffer next-root)
(ebox-incremental-prepare-root-commit buffer next-root)))
(source (plist-get commit-input :root))
(callback
(or framework-publish
ebox-incremental--after-declarative-publication))
(participant
(ebox-surface--make-framework-participant
:publish callback :rollback framework-rollback
:state 'unpublished :diagnostics nil))
(_surface
(if-let ((scope-node-ids
(plist-get commit-input :scope-node-ids)))
(ebox-surface-update-buffer-scoped
buffer source scope-node-ids
(plist-get commit-input :report-base)
(plist-get commit-input :state-overrides)
callback nil nil
(plist-get commit-input :projection-kind)
t participant)
(ebox-surface-mount-buffer
buffer source
(plist-get commit-input :report-base)
callback
(plist-get commit-input :preserve-identities-p)
(plist-get commit-input :state-overrides)
participant))))
(ebox-surface--framework-participant-report participant))))
(let ((execute
(lambda ()
(unless (or (null framework-publish) (functionp framework-publish))
(signal 'wrong-type-argument (list 'functionp framework-publish)))
(unless (or (null framework-rollback)
(functionp framework-rollback))
(signal 'wrong-type-argument (list 'functionp framework-rollback)))
(when (and framework-rollback (null framework-publish))
(error "Ebox framework rollback requires framework publish"))
(let ((buffer (get-buffer buffer-or-name)))
(unless (buffer-live-p buffer)
(error
"Ebox declarative commit requires an existing live buffer: %S"
buffer-or-name))
(let* ((commit-input
(if (ebox-candidate-p next-root)
(ebox-incremental-consume-candidate buffer next-root)
(ebox-incremental-prepare-root-commit buffer next-root)))
(source (plist-get commit-input :root))
(callback
(or framework-publish
ebox-incremental--after-declarative-publication))
(participant
(ebox-surface--make-framework-participant
:publish callback :rollback framework-rollback
:state 'unpublished :diagnostics nil))
(_surface
(if-let ((scope-node-ids
(plist-get commit-input :scope-node-ids)))
(ebox-surface-update-buffer-scoped
buffer source scope-node-ids
(plist-get commit-input :report-base)
(plist-get commit-input :state-overrides)
callback nil nil
(plist-get commit-input :projection-kind)
t participant)
(ebox-surface-mount-buffer
buffer source
(plist-get commit-input :report-base)
callback
(plist-get commit-input :preserve-identities-p)
(plist-get commit-input :state-overrides)
participant))))
(ebox-surface--framework-participant-report participant))))))
(if noninteractive
(ebox--with-render-gc
(funcall execute))
(ebox--with-deferred-render-gc
(funcall execute)))))
;;;###autoload
(defun ebox-rerender-buffer-with-context

View File

@ -557,6 +557,20 @@
(let ((face (get-text-property (match-beginning 0) 'face)))
(should (equal (car face) '(:inherit default))))))))
(ert-deftest ebox-default-foreground-reset-preserves-explicit-background ()
"Resolve GUI default foreground without inheriting its background."
(let ((original (symbol-function 'face-attribute)))
(cl-letf (((symbol-function 'face-attribute)
(lambda (face attribute &rest arguments)
(if (and (eq face 'default) (eq attribute :foreground))
"Black"
(apply original face attribute arguments)))))
(should
(equal
(ebox-buffer-paint-text-properties
'(:color ebox/default-foreground :bgcolor "#111827") 'content)
'(face (:foreground "Black" :background "#111827")))))))
(ert-deftest ebox-flex-layout-properties-come-from-ecss-cascade ()
"An ECSS rule should determine a Flex container's actual main axis."
(ebox-test--reset-runtime-state)
@ -3000,6 +3014,44 @@
(should (= gc-cons-threshold (* 512 1024 1024))))
(ebox--deferred-render-gc-restore))))
(ert-deftest ebox-interactive-commit-owns-deferred-render-gc ()
"Keep GC outside an interactive declarative commit and defer restoration."
(ebox-test--reset-runtime-state)
(let ((buffer nil))
(unwind-protect
(progn
(setq buffer
(ebox-render-to-buffer
(generate-new-buffer-name " *ebox-test*")
(ebox-create :content "before")))
(let ((noninteractive nil)
(gc-cons-threshold 1000)
(gc-cons-percentage 0.1)
(ebox-deferred-render-gc-cons-threshold 4000)
(ebox-render-gc-cons-percentage 0.8)
(ebox--deferred-render-gc-state nil)
(ebox--deferred-render-gc-timer nil)
(ebox--deferred-render-gc-depth 0)
observed-threshold)
(unwind-protect
(progn
(ebox-commit
buffer (ebox-create :content "after")
(lambda (_report)
(setq observed-threshold gc-cons-threshold)))
(should (= observed-threshold 4000))
(should (= gc-cons-threshold 4000))
(should (= gc-cons-percentage 0.8))
(should ebox--deferred-render-gc-state)
(should (timerp ebox--deferred-render-gc-timer)))
(when (timerp ebox--deferred-render-gc-timer)
(cancel-timer ebox--deferred-render-gc-timer))
(setq ebox--deferred-render-gc-state nil
ebox--deferred-render-gc-timer nil
ebox--deferred-render-gc-depth 0))))
(when (and buffer (buffer-live-p buffer))
(kill-buffer buffer)))))
(ert-deftest ebox-deferred-render-gc-can-raise-an-active-burst-budget ()
"Measured layout pressure should only raise an active burst budget."
(let ((gc-cons-threshold 1000)

View File

@ -60,7 +60,7 @@
((symbol-function 'window-body-height)
(lambda (_window) 31)))
(let ((values (ebox-surface--context-values buffer nil nil)))
(should (= 776 (plist-get values :viewport-width)))
(should (= 775 (plist-get values :viewport-width)))
(should (= 31 (plist-get values :viewport-height)))))
(kill-buffer buffer))))
@ -93,10 +93,24 @@
((symbol-function 'window-body-height)
(lambda (_window) 62)))
(let ((values (ebox-surface--context-values buffer nil nil)))
(should (= 1429 (plist-get values :viewport-width)))
(should (= 1428 (plist-get values :viewport-width)))
(should (= 62 (plist-get values :viewport-height)))))
(kill-buffer buffer))))
(ert-deftest ebox-surface-window-width-reserves-two-display-columns ()
"Reserve both continuation columns from a live GUI content width."
(cl-letf (((symbol-function 'window-body-width)
(lambda (_window pixelwise)
(should pixelwise)
987))
((symbol-function 'window-pixel-width)
(lambda (_window) 987))
((symbol-function 'window-frame)
(lambda (_window) nil))
((symbol-function 'frame-char-width)
(lambda (&optional _frame) 8)))
(should (= (ebox-surface--window-content-width (selected-window)) 971))))
(ert-deftest ebox-surface-context-prefers-selected-target-window ()
"Ignore stale cross-frame lookup when selected window shows BUFFER."
(let* ((buffer (generate-new-buffer " *ebox-selected-viewport-test*"))
@ -119,7 +133,7 @@
(should (eq candidate window))
60)))
(let ((values (ebox-surface--context-values buffer nil nil)))
(should (= 1399 (plist-get values :viewport-width)))
(should (= 1398 (plist-get values :viewport-width)))
(should (= 60 (plist-get values :viewport-height))))))
(when (window-live-p window) (set-window-buffer window old-buffer))
(kill-buffer buffer))))
@ -150,7 +164,7 @@
((symbol-function 'window-body-height)
(lambda (_window) 31)))
(let ((values (ebox-surface--context-values buffer nil nil)))
(should (= 900 (plist-get values :viewport-width)))
(should (= 899 (plist-get values :viewport-width)))
(should (= 31 (plist-get values :viewport-height)))))
(kill-buffer buffer)
(when (get-buffer " *other-window*")