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) (or (plist-get style :background-color)
(plist-get style :bgcolor)))) (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) (defun ebox-buffer-paint-text-properties (style role)
"Return Emacs text properties for computed STYLE in paint ROLE. "Return Emacs text properties for computed STYLE in paint ROLE.
This backend mapper intentionally accepts computed style facts and emits only 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))) (when-let ((foreground (ebox-buffer--paint-color style role)))
(setq face (setq face
(if (eq foreground 'ebox/default-foreground) (if (eq foreground 'ebox/default-foreground)
(plist-put face :inherit 'default) (append (ebox-buffer--default-foreground-face) face)
(plist-put face :foreground foreground)))) (plist-put face :foreground foreground))))
(when-let ((background (ebox-buffer--paint-background-color style role))) (when-let ((background (ebox-buffer--paint-background-color style role)))
(setq face (plist-put face :background background))) (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! (ebox--add-render-face!
string 0 length string 0 length
(if (eq color 'ebox/default-foreground) (if (eq color 'ebox/default-foreground)
'(:inherit default) (ebox-buffer--default-foreground-face)
`(:foreground ,color)) `(:foreground ,color))
t) t)
(ebox--register-render-owned-face-values source string))) (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! (ebox--add-render-face!
string 0 length string 0 length
(if (eq color 'ebox/default-foreground) (if (eq color 'ebox/default-foreground)
'(:inherit default) (ebox-buffer--default-foreground-face)
`(:foreground ,color)) `(:foreground ,color))
t)) t))
(when bgcolor (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 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 `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 clearly inconsistent with the outer pixel width, prefer the outer width and
keep a one-pixel exclusive boundary. Headless/test windows retain the body reserve two character columns for Emacs continuation/truncation display.
width fallback." Headless/test windows retain the body width fallback."
(let ((body (let ((body
(condition-case nil (condition-case nil
(window-body-width window t) (window-body-width window t)
@ -315,11 +315,16 @@ width fallback."
(condition-case nil (condition-case nil
(window-pixel-width window) (window-pixel-width window)
(error nil)))) (error nil))))
(let* ((char-width
(condition-case nil
(frame-char-width (window-frame window))
(error 1)))
(reserve (max 2 (* 2 (max 1 char-width)))))
(max 0 (max 0
(1- (- (if (and body outer (> outer (* 1.8 body)))
(if (and body outer (> outer (* 1.8 body)))
outer outer
(or body outer 0)))))) (or body outer 0))
reserve)))))
(defun ebox-surface--ensure-signals (buffer values) (defun ebox-surface--ensure-signals (buffer values)
"Return BUFFER's context signals for VALUES and whether they were created." "Return BUFFER's context signals for VALUES and whether they were created."

15
ebox.el
View File

@ -4519,15 +4519,19 @@ FRAMEWORK-ROLLBACK, when supplied, receives the same report if publication or
a later transaction phase fails, and is contained if it violates no-throw. a later transaction phase fails, and is contained if it violates no-throw.
Return the successful publication report stored by `ebox-buffer-update-report'." Return the successful publication report stored by `ebox-buffer-update-report'."
(let ((execute
(lambda ()
(unless (or (null framework-publish) (functionp framework-publish)) (unless (or (null framework-publish) (functionp framework-publish))
(signal 'wrong-type-argument (list 'functionp framework-publish))) (signal 'wrong-type-argument (list 'functionp framework-publish)))
(unless (or (null framework-rollback) (functionp framework-rollback)) (unless (or (null framework-rollback)
(functionp framework-rollback))
(signal 'wrong-type-argument (list 'functionp framework-rollback))) (signal 'wrong-type-argument (list 'functionp framework-rollback)))
(when (and framework-rollback (null framework-publish)) (when (and framework-rollback (null framework-publish))
(error "Ebox framework rollback requires framework publish")) (error "Ebox framework rollback requires framework publish"))
(let ((buffer (get-buffer buffer-or-name))) (let ((buffer (get-buffer buffer-or-name)))
(unless (buffer-live-p buffer) (unless (buffer-live-p buffer)
(error "Ebox declarative commit requires an existing live buffer: %S" (error
"Ebox declarative commit requires an existing live buffer: %S"
buffer-or-name)) buffer-or-name))
(let* ((commit-input (let* ((commit-input
(if (ebox-candidate-p next-root) (if (ebox-candidate-p next-root)
@ -4558,7 +4562,12 @@ Return the successful publication report stored by `ebox-buffer-update-report'."
(plist-get commit-input :preserve-identities-p) (plist-get commit-input :preserve-identities-p)
(plist-get commit-input :state-overrides) (plist-get commit-input :state-overrides)
participant)))) participant))))
(ebox-surface--framework-participant-report participant)))) (ebox-surface--framework-participant-report participant))))))
(if noninteractive
(ebox--with-render-gc
(funcall execute))
(ebox--with-deferred-render-gc
(funcall execute)))))
;;;###autoload ;;;###autoload
(defun ebox-rerender-buffer-with-context (defun ebox-rerender-buffer-with-context

View File

@ -557,6 +557,20 @@
(let ((face (get-text-property (match-beginning 0) 'face))) (let ((face (get-text-property (match-beginning 0) 'face)))
(should (equal (car face) '(:inherit default)))))))) (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 () (ert-deftest ebox-flex-layout-properties-come-from-ecss-cascade ()
"An ECSS rule should determine a Flex container's actual main axis." "An ECSS rule should determine a Flex container's actual main axis."
(ebox-test--reset-runtime-state) (ebox-test--reset-runtime-state)
@ -3000,6 +3014,44 @@
(should (= gc-cons-threshold (* 512 1024 1024)))) (should (= gc-cons-threshold (* 512 1024 1024))))
(ebox--deferred-render-gc-restore)))) (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 () (ert-deftest ebox-deferred-render-gc-can-raise-an-active-burst-budget ()
"Measured layout pressure should only raise an active burst budget." "Measured layout pressure should only raise an active burst budget."
(let ((gc-cons-threshold 1000) (let ((gc-cons-threshold 1000)

View File

@ -60,7 +60,7 @@
((symbol-function 'window-body-height) ((symbol-function 'window-body-height)
(lambda (_window) 31))) (lambda (_window) 31)))
(let ((values (ebox-surface--context-values buffer nil nil))) (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))))) (should (= 31 (plist-get values :viewport-height)))))
(kill-buffer buffer)))) (kill-buffer buffer))))
@ -93,10 +93,24 @@
((symbol-function 'window-body-height) ((symbol-function 'window-body-height)
(lambda (_window) 62))) (lambda (_window) 62)))
(let ((values (ebox-surface--context-values buffer nil nil))) (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))))) (should (= 62 (plist-get values :viewport-height)))))
(kill-buffer buffer)))) (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 () (ert-deftest ebox-surface-context-prefers-selected-target-window ()
"Ignore stale cross-frame lookup when selected window shows BUFFER." "Ignore stale cross-frame lookup when selected window shows BUFFER."
(let* ((buffer (generate-new-buffer " *ebox-selected-viewport-test*")) (let* ((buffer (generate-new-buffer " *ebox-selected-viewport-test*"))
@ -119,7 +133,7 @@
(should (eq candidate window)) (should (eq candidate window))
60))) 60)))
(let ((values (ebox-surface--context-values buffer nil nil))) (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)))))) (should (= 60 (plist-get values :viewport-height))))))
(when (window-live-p window) (set-window-buffer window old-buffer)) (when (window-live-p window) (set-window-buffer window old-buffer))
(kill-buffer buffer)))) (kill-buffer buffer))))
@ -150,7 +164,7 @@
((symbol-function 'window-body-height) ((symbol-function 'window-body-height)
(lambda (_window) 31))) (lambda (_window) 31)))
(let ((values (ebox-surface--context-values buffer nil nil))) (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))))) (should (= 31 (plist-get values :viewport-height)))))
(kill-buffer buffer) (kill-buffer buffer)
(when (get-buffer " *other-window*") (when (get-buffer " *other-window*")