refactor: preserve line data through box decoration
This commit is contained in:
parent
acb5afce82
commit
3fadb19121
7
Makefile
7
Makefile
@ -10,7 +10,7 @@ NATIVE_MANIFEST = native/Cargo.toml
|
||||
NATIVE_TARGET ?= $(shell $(EMACS_BATCH) -l ebox-native-reflow.el --eval '(princ (ebox-native-reflow--rust-target))')
|
||||
NATIVE_RELEASE_DIR = native/target/$(NATIVE_TARGET)/release
|
||||
|
||||
.PHONY: all check ci load compile test checkdoc source-tests font-tests core-tests child-range-tests grid-tests ebox-commit-tests surface-tests visual-check-tests package-tests selector-tests dsl-tests flex-tests viewport-topology-tests vertical-materialization-tests m0a-tests state-contract-tests layout-boundary-tests layout-boundary-performance patch-plan-tests patch-plan-performance style-schema-tests style-schema-performance spi-tests spi-performance c1b-contract-tests docs-contract-tests ci-contract-tests performance-evaluator visual-check native-rust-tests native-build diff-check clean package-lint package-lint-install
|
||||
.PHONY: all check ci load compile test checkdoc source-tests font-tests core-tests child-range-tests grid-tests ebox-commit-tests surface-tests visual-check-tests package-tests selector-tests dsl-tests flex-tests viewport-topology-tests vertical-materialization-tests box-line-flow-tests m0a-tests state-contract-tests layout-boundary-tests layout-boundary-performance patch-plan-tests patch-plan-performance style-schema-tests style-schema-performance spi-tests spi-performance c1b-contract-tests docs-contract-tests ci-contract-tests performance-evaluator visual-check native-rust-tests native-build diff-check clean package-lint package-lint-install
|
||||
|
||||
all: check
|
||||
|
||||
@ -25,7 +25,7 @@ compile:
|
||||
rm -f *.elc tests/*.elc scripts/*.elc
|
||||
$(EMACS_BATCH) --eval '(setq byte-compile-error-on-warn t byte-compile-warnings (quote (not obsolete)))' -l ebox.el --eval '(ebox-byte-compile)'
|
||||
|
||||
test: source-tests font-tests core-tests child-range-tests grid-tests ebox-commit-tests surface-tests visual-check-tests package-tests selector-tests dsl-tests flex-tests viewport-topology-tests vertical-materialization-tests m0a-tests state-contract-tests layout-boundary-tests patch-plan-tests style-schema-tests spi-tests docs-contract-tests ci-contract-tests
|
||||
test: source-tests font-tests core-tests child-range-tests grid-tests ebox-commit-tests surface-tests visual-check-tests package-tests selector-tests dsl-tests flex-tests viewport-topology-tests vertical-materialization-tests box-line-flow-tests m0a-tests state-contract-tests layout-boundary-tests patch-plan-tests style-schema-tests spi-tests docs-contract-tests ci-contract-tests
|
||||
|
||||
source-tests:
|
||||
$(EMACS_BATCH) -l tests/ebox-source-tests.el -f ert-run-tests-batch-and-exit
|
||||
@ -69,6 +69,9 @@ viewport-topology-tests:
|
||||
vertical-materialization-tests:
|
||||
$(EMACS_TEST) -l tests/ebox-vertical-materialization-tests.el -f ert-run-tests-batch-and-exit
|
||||
|
||||
box-line-flow-tests:
|
||||
$(EMACS_TEST) -l tests/ebox-box-line-flow-tests.el -f ert-run-tests-batch-and-exit
|
||||
|
||||
m0a-tests:
|
||||
$(EMACS_TEST) -l tests/ebox-m0a-inventory-fixture.el -l tests/ebox-m0a-inventory-tests.el -l tests/ebox-m0a-characterization-tests.el -f ert-run-tests-batch-and-exit
|
||||
|
||||
|
||||
@ -39,6 +39,10 @@
|
||||
(declare-function ebox--line-content-metadata-uniform-p "ebox" (line))
|
||||
(declare-function ebox--lines-align-vertical
|
||||
"ebox" (string height align))
|
||||
(declare-function ebox--pad-lines-vertical
|
||||
"ebox" (lines height &optional offset padding-string))
|
||||
(declare-function ebox--vertical-alignment-offset
|
||||
"ebox" (line-count height align))
|
||||
(declare-function ebox--lines-concat-horizontal "ebox" (&rest strings))
|
||||
(declare-function ebox--lines-stack-vertical "ebox" (&rest strings))
|
||||
(declare-function ebox--add-content-owner
|
||||
@ -1669,12 +1673,11 @@ Internal implementation of `ebox-render' for box nodes."
|
||||
(t
|
||||
(let ((ebox--propertize-private-content-line-p
|
||||
private-formatted-lines-p))
|
||||
(ebox-lines-join
|
||||
(cl-loop for line in visible-lines
|
||||
for idx from 0
|
||||
collect (ebox--propertize-content-line
|
||||
line region-id idx
|
||||
padding-line-filler))))))))
|
||||
(cl-loop for line in visible-lines
|
||||
for idx from 0
|
||||
collect (ebox--propertize-content-line
|
||||
line region-id idx
|
||||
padding-line-filler)))))))
|
||||
|
||||
;; Rendering is the first point where the complete formatted height is
|
||||
;; known. Keep the host box and published scroll state on the same
|
||||
@ -1691,9 +1694,15 @@ Internal implementation of `ebox-render' for box nodes."
|
||||
;; 1. Vertical Alignment of content within the box height. Reused scroll
|
||||
;; lines already fill a top-aligned, chrome-free viewport.
|
||||
(unless simple-scroll-rendered-lines
|
||||
(setq result (ebox--lines-align-vertical
|
||||
result content-height
|
||||
(ebox-get box :vertical-align))))
|
||||
;; The former join treated no decorated lines as one empty string line.
|
||||
;; Keep that string-adapter convention outside the list pad/clip core.
|
||||
(let ((lines (or result (list ""))))
|
||||
(setq result
|
||||
(ebox--pad-lines-vertical
|
||||
lines content-height
|
||||
(ebox--vertical-alignment-offset
|
||||
(length lines) content-height
|
||||
(ebox-get box :vertical-align))))))
|
||||
|
||||
;; Fill any empty lines generated by alignment with correct width spaces
|
||||
;; 保留原有的 ebox-content/ebox-content-idx 属性
|
||||
@ -1717,7 +1726,9 @@ Internal implementation of `ebox-render' for box nodes."
|
||||
'ebox-content-idx 0
|
||||
'ebox-content-owner region-id)))
|
||||
line))
|
||||
(ebox-string-lines result))))
|
||||
;; Splitting the former empty aligned string yielded one line,
|
||||
;; even when alignment clipped all content at height zero.
|
||||
(or result (list "")))))
|
||||
|
||||
;; Scroll state describes formatted content, independently of chrome.
|
||||
(puthash region-id box ebox--region-box-table)
|
||||
|
||||
56
ebox.el
56
ebox.el
@ -589,6 +589,36 @@ segments that incremental publication still needs."
|
||||
'(ebox-content ebox-content-idx
|
||||
ebox-content-owner ebox-content-owners)))))
|
||||
|
||||
(defun ebox--pad-lines-vertical (lines height &optional offset padding-string)
|
||||
"Pad or clip LINES to HEIGHT without changing their strings or list spine.
|
||||
OFFSET shifts content toward the bottom when positive and toward the top when
|
||||
negative. PADDING-STRING defaults to a blank matching the first line's width.
|
||||
The result may borrow LINES or their strings. Treat it as read-only.
|
||||
Empty input has zero lines before padding. Height zero clips to nil."
|
||||
(let ((count (length lines)))
|
||||
(if (> height count)
|
||||
(let* ((fill (or padding-string
|
||||
(ebox--pixel-blank
|
||||
(ebox--string-pixel-width (car lines)) 1)
|
||||
""))
|
||||
(rest (- height count))
|
||||
(offset (or offset 0))
|
||||
(top (if (>= offset 0) (min offset rest)
|
||||
(max 0 (+ offset rest))))
|
||||
(bottom (- rest top)))
|
||||
(append (make-list top fill)
|
||||
(if (> bottom 0)
|
||||
(append lines (make-list bottom fill))
|
||||
lines)))
|
||||
(if (= height count) lines (seq-take lines height)))))
|
||||
|
||||
(defun ebox--vertical-alignment-offset (line-count height align)
|
||||
"Return the offset for LINE-COUNT lines in HEIGHT using ALIGN."
|
||||
(pcase align
|
||||
('bottom (- height line-count))
|
||||
('center (/ (- height line-count) 2))
|
||||
(_ 0)))
|
||||
|
||||
(defun ebox--lines-pad-vertical (string height &optional offset padding-string)
|
||||
"Pad or clip STRING to HEIGHT lines.
|
||||
OFFSET controls content shift: positive (top), negative (bottom).
|
||||
@ -596,31 +626,15 @@ PADDING-STRING fills added lines; its default matches STRING's first-line width.
|
||||
(if (and (stringp string) (integerp height) (= height 1)
|
||||
(not (string-match-p "\n" string)))
|
||||
(copy-sequence string)
|
||||
(let* ((lines (ebox-string-lines string))
|
||||
(line-count (length lines)))
|
||||
(when (> height line-count)
|
||||
(let* ((padding-string (or padding-string
|
||||
(ebox--pixel-blank
|
||||
(ebox--string-pixel-width string) 1)))
|
||||
(rest (- height line-count))
|
||||
(offset (or offset 0))
|
||||
(top-pad (cond ((>= offset 0) (min offset rest))
|
||||
(t (max 0 (+ offset rest)))))
|
||||
(bottom-pad (- rest top-pad))
|
||||
(pad-lines (make-list top-pad padding-string))
|
||||
(bottom-lines (make-list bottom-pad padding-string)))
|
||||
(setq lines (append pad-lines lines bottom-lines))))
|
||||
(ebox-lines-join (seq-take lines height)))))
|
||||
(ebox-lines-join
|
||||
(ebox--pad-lines-vertical
|
||||
(ebox-string-lines string) height offset padding-string))))
|
||||
|
||||
(defun ebox--lines-align-vertical (string height align)
|
||||
"Align STRING to HEIGHT lines.
|
||||
ALIGN can be `top', `center', or `bottom'."
|
||||
(let* ((line-count (ebox-string-height string))
|
||||
(offset (pcase align
|
||||
('top 0)
|
||||
('bottom (- height line-count))
|
||||
('center (/ (- height line-count) 2))
|
||||
(_ 0))))
|
||||
(let ((offset (ebox--vertical-alignment-offset
|
||||
(ebox-string-height string) height align)))
|
||||
(ebox--lines-pad-vertical string height offset)))
|
||||
|
||||
(defun ebox--string-lines-pad-bottom (string height)
|
||||
|
||||
355
tests/ebox-box-line-flow-tests.el
Normal file
355
tests/ebox-box-line-flow-tests.el
Normal file
@ -0,0 +1,355 @@
|
||||
;;; ebox-box-line-flow-tests.el --- Box line flow contracts -*- lexical-binding: t; -*-
|
||||
|
||||
(require 'cl-lib)
|
||||
(require 'ert)
|
||||
(setq load-prefer-newer t)
|
||||
(load-file (expand-file-name "../ebox.el"
|
||||
(file-name-directory load-file-name)))
|
||||
|
||||
(defun ebox-box-line-test--previous-pad (string height &optional offset padding)
|
||||
"Apply the frozen string padding algorithm to STRING at HEIGHT."
|
||||
(let* ((lines (ebox-string-lines string))
|
||||
(count (length lines)))
|
||||
(when (> height count)
|
||||
(let* ((fill (or padding
|
||||
(ebox--pixel-blank (ebox--string-pixel-width string) 1)))
|
||||
(rest (- height count))
|
||||
(shift (or offset 0))
|
||||
(top (if (>= shift 0) (min shift rest)
|
||||
(max 0 (+ shift rest)))))
|
||||
(setq lines (append (make-list top fill) lines
|
||||
(make-list (- rest top) fill)))))
|
||||
(ebox-lines-join (seq-take lines height))))
|
||||
|
||||
(defun ebox-box-line-test--previous-prepare (box formatted height region-id filler)
|
||||
"Freeze BOX's old join/align/split/blank-normalization front half.
|
||||
FORMATTED is captured before decoration; HEIGHT and REGION-ID are the actual
|
||||
render constraints. This oracle does not call the new line operation."
|
||||
(let* ((text-height (ebox-string-height formatted))
|
||||
(scroll-p (eq (ebox-get box :overflow) 'scroll))
|
||||
(start (if scroll-p
|
||||
(max 0 (min (or (ebox-get box :scroll-offset) 0)
|
||||
(max 0 (- text-height height))))
|
||||
0))
|
||||
(lines (ebox-string-lines formatted))
|
||||
(visible (if scroll-p
|
||||
(seq-subseq lines start (min (+ start height) text-height))
|
||||
(seq-take lines height)))
|
||||
(ebox--propertize-private-content-line-p
|
||||
(not (and scroll-p (> text-height height))))
|
||||
(decorated
|
||||
(ebox-lines-join
|
||||
(cl-loop for line in visible for index from 0
|
||||
collect (ebox--propertize-content-line
|
||||
line region-id index filler))))
|
||||
(count (ebox-string-height decorated))
|
||||
(offset (pcase (ebox-get box :vertical-align)
|
||||
('bottom (- height count))
|
||||
('center (/ (- height count) 2))
|
||||
(_ 0)))
|
||||
(aligned (ebox-box-line-test--previous-pad decorated height offset)))
|
||||
(mapcar
|
||||
(lambda (line)
|
||||
(if (and (string-empty-p (string-trim line))
|
||||
(ebox--line-content-metadata-uniform-p line)
|
||||
(not (ebox--line-has-non-content-properties-p line)))
|
||||
(let ((id (get-text-property 0 'ebox-content line))
|
||||
(index (get-text-property 0 'ebox-content-idx line)))
|
||||
(propertize filler 'ebox-content (or id region-id)
|
||||
'ebox-content-idx (if id index 0)
|
||||
'ebox-content-owner region-id))
|
||||
line))
|
||||
(ebox-string-lines aligned))))
|
||||
|
||||
(defun ebox-box-line-test--reference-render (input &optional actual-p)
|
||||
"Render INPUT with the old front half, or current code when ACTUAL-P.
|
||||
Check opaque identity between the actual formatted source and compositor output;
|
||||
the surrounding ephemeral TP surface has its own snapshot value ownership."
|
||||
(let ((format-function (symbol-function 'ebox--format-content))
|
||||
(compositor (symbol-function 'ebox--window-render-flat-wrapper-chunk-lines))
|
||||
(formatted (make-hash-table :test 'eq)))
|
||||
(cl-letf (((symbol-function 'ebox--format-content)
|
||||
(lambda (box)
|
||||
(let ((result (funcall format-function box)))
|
||||
(puthash box
|
||||
(list (copy-sequence result)
|
||||
(plist-get ebox--scroll-window-render-result
|
||||
:content-height))
|
||||
formatted)
|
||||
result)))
|
||||
((symbol-function 'ebox--window-render-flat-wrapper-chunk-lines)
|
||||
(lambda (&rest args)
|
||||
(when (and (nth 7 args) (not actual-p))
|
||||
(let* ((box (car args))
|
||||
(record (gethash box formatted))
|
||||
(text (car record))
|
||||
(height (or (cadr record)
|
||||
(ebox--content-height
|
||||
box (ebox-string-height text)))))
|
||||
(should record)
|
||||
(setf (nth 1 args)
|
||||
(ebox-box-line-test--previous-prepare
|
||||
box text height (nth 3 args) (nth 4 args)))))
|
||||
(let* ((result (apply compositor args))
|
||||
(source (car (gethash (car args) formatted)))
|
||||
(source-pos (and source (string-match "Open" source)))
|
||||
(line (cl-find-if (lambda (value) (string-match "Open" value))
|
||||
result)))
|
||||
(when (and source-pos line)
|
||||
(let ((pos (string-match "Open" line)))
|
||||
(dolist (property '(keymap help-echo))
|
||||
(should (eq (get-text-property pos property line)
|
||||
(get-text-property source-pos property source))))))
|
||||
result))))
|
||||
(ebox-render input))))
|
||||
|
||||
(defun ebox-box-line-test--materializations (input)
|
||||
"Record actual intermediate newline joins/splits while rendering INPUT's root."
|
||||
(let* ((handle (ebox-node-source-handle (car (ebox-canonical-input-roots input))))
|
||||
(render (symbol-function 'ebox--render-box))
|
||||
(format-function (symbol-function 'ebox--format-content))
|
||||
(join (symbol-function 'mapconcat))
|
||||
(slice (symbol-function 'substring))
|
||||
(joined (make-hash-table :test 'eq))
|
||||
(joins 0) (splits 0) active formatting output)
|
||||
(cl-letf (((symbol-function 'ebox--render-box)
|
||||
(lambda (box)
|
||||
(let ((previous active))
|
||||
(setq active (eq (ebox-node-source-handle box) handle))
|
||||
(unwind-protect (funcall render box)
|
||||
(setq active previous)))))
|
||||
((symbol-function 'ebox--format-content)
|
||||
(lambda (box)
|
||||
(let ((previous formatting))
|
||||
(setq formatting t)
|
||||
(unwind-protect (funcall format-function box)
|
||||
(setq formatting previous)))))
|
||||
((symbol-function 'mapconcat)
|
||||
(lambda (function sequence separator)
|
||||
(let ((result (funcall join function sequence separator)))
|
||||
(when (and active (not formatting) (equal separator "\n")
|
||||
(string-match-p "alpha" result))
|
||||
(cl-incf joins)
|
||||
(puthash result t joined))
|
||||
result)))
|
||||
((symbol-function 'substring)
|
||||
(lambda (string from &optional to)
|
||||
(when (and active (not formatting) (gethash string joined))
|
||||
(cl-incf splits))
|
||||
(funcall slice string from to))))
|
||||
(setq output (ebox-render input)))
|
||||
(list :output output :joins joins :splits splits)))
|
||||
|
||||
(ert-deftest ebox-box-line-flow-avoids-intermediate-join-and-split ()
|
||||
"Ordinary decoration should join only at the existing compositor exit."
|
||||
(let* ((input (ebox-build
|
||||
'(box :width (120) :height 8 :overflow hidden
|
||||
:padding (1 (3)) :border (1 solid "#112233")
|
||||
:bgcolor "#ddeeff" (text "alpha\nbeta"))))
|
||||
(expected (ebox-box-line-test--reference-render input))
|
||||
(observed (ebox-box-line-test--materializations input)))
|
||||
(should (equal-including-properties (plist-get observed :output) expected))
|
||||
(ert-info ((format "Actual content joins=%S, joined-string substrings=%S"
|
||||
(plist-get observed :joins) (plist-get observed :splits)))
|
||||
(should (= (plist-get observed :joins) 1))
|
||||
(should (= (plist-get observed :splits) 0)))))
|
||||
|
||||
(ert-deftest ebox-box-line-flow-preserves-old-front-half-output ()
|
||||
"Composition, chrome, visibility and ownership match the frozen string path."
|
||||
(let* ((map (make-sparse-keymap))
|
||||
(callback (let ((value 'opened)) (lambda () value)))
|
||||
(rich (concat (propertize "Open 中" 'keymap map 'help-echo callback)
|
||||
(propertize "\n" 'help-echo "newline only")
|
||||
(propertize " " 'help-echo "rich blank") "\ntail\n")))
|
||||
(define-key map [mouse-1] #'ignore)
|
||||
(dolist (kind '(box row column flex grid))
|
||||
(dolist (height '(0 4 8))
|
||||
(dolist (align '(top center bottom))
|
||||
(dolist (overflow '(hidden visible scroll))
|
||||
(let* ((child (pcase kind
|
||||
('box `(box (text ,rich)))
|
||||
('grid `(grid :grid-template-columns ((110)) (text ,rich)))
|
||||
(_ `(,kind (text ,rich)))))
|
||||
(input
|
||||
(ebox-build
|
||||
`(box :width (140) :height ,height :overflow ,overflow
|
||||
:padding (1 (3))
|
||||
:border (1 solid "#112233") :margin (1 (2))
|
||||
:color "#123456" :bgcolor "#ddeeff" ,child)))
|
||||
(_alignment
|
||||
(plist-put (car (ebox-canonical-input-roots input))
|
||||
:vertical-align align))
|
||||
(source-before (copy-sequence rich))
|
||||
(actual (ebox-box-line-test--reference-render input t))
|
||||
(expected (ebox-box-line-test--reference-render input)))
|
||||
(ert-info ((format "%S height=%S align=%S overflow=%S"
|
||||
kind height align overflow))
|
||||
(should (equal-including-properties actual expected))
|
||||
(should (equal-including-properties rich source-before))
|
||||
(when-let* ((pos (string-match "Open" actual)))
|
||||
(should (eq (lookup-key (get-text-property pos 'keymap actual)
|
||||
[mouse-1]) #'ignore))
|
||||
(should (eq (funcall (get-text-property pos 'help-echo actual))
|
||||
'opened))
|
||||
(should (> (length (get-text-property
|
||||
pos 'ebox-content-owners actual)) 1)))))))))))
|
||||
|
||||
(ert-deftest ebox-box-line-flow-keeps-string-padding-contract ()
|
||||
"String padding retains nil, empty, first-line width and multiline filler."
|
||||
(dolist (input (list nil "" "A" "A\nlonger" "\n" "A\n"))
|
||||
(dolist (height '(0 1 2 4))
|
||||
(dolist (offset '(nil -2 0 2))
|
||||
(dolist (filler (list nil "" (propertize "L\nR" 'help-echo "fill")))
|
||||
(should (equal-including-properties
|
||||
(ebox--lines-pad-vertical input height offset filler)
|
||||
(ebox-box-line-test--previous-pad input height offset filler)))))))
|
||||
(let* ((input "A\nlonger")
|
||||
(output (ebox-string-lines (ebox--lines-pad-vertical input 3)))
|
||||
(fill (car (last output))))
|
||||
(should (= (ebox--string-pixel-width fill) (ebox--string-pixel-width "A")))))
|
||||
|
||||
(ert-deftest ebox-box-line-flow-keeps-simple-scroll-output ()
|
||||
"Complete simple scroll keeps its single join for overflow and underfill."
|
||||
(dolist (text '("alpha\nbeta" "alpha\n2\n3\n4\n5\n6\n7\n8\n9"))
|
||||
(let* ((input (ebox-build
|
||||
`(box :width (120) :height 8 :overflow scroll
|
||||
:bgcolor "#ddeeff" (text ,text))))
|
||||
(expected (ebox-box-line-test--reference-render input))
|
||||
(observed (ebox-box-line-test--materializations input)))
|
||||
(should (equal-including-properties (plist-get observed :output) expected))
|
||||
(should (= (plist-get observed :joins) 1))
|
||||
(should (= (plist-get observed :splits) 0)))))
|
||||
|
||||
(ert-deftest ebox-box-line-flow-list-core-borrows-without-mutating ()
|
||||
"Line padding preserves the original spine, strings and opaque property values."
|
||||
(let* ((map (make-sparse-keymap))
|
||||
(callback (let ((value 'opened)) (lambda () value)))
|
||||
(rich (propertize "Open 中" 'keymap map 'help-echo callback
|
||||
'ebox-content 17 'ebox-content-idx 2
|
||||
'ebox-content-owner 18 'ebox-content-owners '(18 17)))
|
||||
(blank (propertize " " 'display '(space :width (7))
|
||||
'ebox-content-owner 19)))
|
||||
(dolist (lines (list nil (list "") (list rich) (list rich "" blank)))
|
||||
(dolist (height '(0 1 3 5))
|
||||
(dolist (align '(top center bottom))
|
||||
(let* ((spine (cl-loop for tail on lines
|
||||
collect (list tail (car tail) (cdr tail)
|
||||
(copy-sequence (car tail)))))
|
||||
(count (length lines))
|
||||
(offset (pcase align
|
||||
('bottom (- height count))
|
||||
('center (/ (- height count) 2))
|
||||
(_ 0)))
|
||||
(expected (ebox-box-line-test--previous-pad
|
||||
(and lines (ebox-lines-join lines)) height offset))
|
||||
(actual (ebox--pad-lines-vertical lines height offset))
|
||||
(output (ebox-lines-join actual)))
|
||||
(should (equal-including-properties output expected))
|
||||
(when (= height count) (should (eq actual lines)))
|
||||
(when-let* ((pos (string-match "Open" output)))
|
||||
(should (eq (get-text-property pos 'keymap output) map))
|
||||
(should (eq (get-text-property pos 'help-echo output) callback)))
|
||||
;; Only the string boundary grants independent mutable ownership.
|
||||
(when (> (length output) 0)
|
||||
(put-text-property 0 1 'help-echo "output-only" output)
|
||||
(aset output 0 ?Z))
|
||||
(dolist (record spine)
|
||||
(should (eq (car (nth 0 record)) (nth 1 record)))
|
||||
(should (eq (cdr (nth 0 record)) (nth 2 record)))
|
||||
(should (equal-including-properties (nth 1 record) (nth 3 record)))))))))
|
||||
;; The list primitive does not own either legacy string-adapter bridge.
|
||||
(should-not (ebox--pad-lines-vertical nil 0))
|
||||
(should-not (ebox--pad-lines-vertical (list "A") 0)))
|
||||
|
||||
(ert-deftest ebox-box-line-flow-retains-empty-string-adapter-bridges ()
|
||||
"Empty formatted content and zero used height retain the former blank line."
|
||||
(dolist (formatted '(nil "" "A\nB"))
|
||||
(dolist (height '(0 1 3))
|
||||
(let* ((input (ebox-build
|
||||
'(box :width (40) :height 3 :overflow hidden :padding (1 (2))
|
||||
:border (1 solid "#112233") (text "unused"))))
|
||||
(root (car (ebox-canonical-input-roots input)))
|
||||
(handle (ebox-node-source-handle root))
|
||||
(format-function (symbol-function 'ebox--format-content))
|
||||
(height-function (symbol-function 'ebox--content-height)))
|
||||
;; Isolate the decoration boundary, including a zero used content
|
||||
;; height; the ordinary public height resolver clamps to at least one.
|
||||
(cl-letf (((symbol-function 'ebox--format-content)
|
||||
(lambda (box)
|
||||
(if (eq (ebox-node-source-handle box) handle)
|
||||
formatted
|
||||
(funcall format-function box))))
|
||||
((symbol-function 'ebox--content-height)
|
||||
(lambda (box natural)
|
||||
(if (eq (ebox-node-source-handle box) handle)
|
||||
height
|
||||
(funcall height-function box natural)))))
|
||||
(let ((actual (ebox-box-line-test--reference-render input t))
|
||||
(expected (ebox-box-line-test--reference-render input)))
|
||||
(should (equal-including-properties actual expected))
|
||||
(when (= height 0)
|
||||
(should (= (ebox-string-height actual) 3)))))))))
|
||||
|
||||
(ert-deftest ebox-box-line-flow-zero-width-default-padding-is-a-line ()
|
||||
"Zero-width default blanks remain empty string lines at the list boundary."
|
||||
(dolist (padding '(0 (1 (2))))
|
||||
(let* ((input (ebox-build
|
||||
`(box :width (0) :height 3 :overflow hidden
|
||||
:padding ,padding (text ""))))
|
||||
(actual (ebox-box-line-test--reference-render input t))
|
||||
(expected (ebox-box-line-test--reference-render input)))
|
||||
(should (equal-including-properties actual expected)))))
|
||||
|
||||
(ert-deftest ebox-box-line-flow-scroll-to-end-and-back-preserves-source ()
|
||||
"Real decorated scroll A→B→A keeps stored lines intact and reaches the end."
|
||||
(let* ((buffer (generate-new-buffer " *ebox-box-line-scroll*"))
|
||||
(map (make-sparse-keymap))
|
||||
(_key (define-key map [mouse-1] #'ignore))
|
||||
(text (mapconcat (lambda (index) (format "Open %02d" index))
|
||||
(number-sequence 0 9) "\n"))
|
||||
(input (ebox-build
|
||||
`(box :width (120) :height 4 :overflow scroll
|
||||
:padding (0 (2)) :border (1 solid "#112233")
|
||||
(text ,(propertize text 'keymap map 'help-echo "action")))))
|
||||
(ebox-runtime-idle-prewarm nil)
|
||||
(ebox-runtime-idle-reflow-cache-prewarm nil))
|
||||
(unwind-protect
|
||||
(progn
|
||||
(ebox-render-to-buffer buffer input)
|
||||
(with-current-buffer buffer
|
||||
(let* ((root (plist-get (ebox--buffer-render-state buffer) :root-node))
|
||||
(id (plist-get root :region-id))
|
||||
(state (ebox--scroll-get-state id))
|
||||
(lines (plist-get state :content-lines))
|
||||
(spine (cl-loop for tail on lines
|
||||
collect (list tail (car tail) (cdr tail)
|
||||
(copy-sequence (car tail)))))
|
||||
(initial (buffer-string))
|
||||
(end (max 0 (- (length lines) (plist-get state :content-height)))))
|
||||
(should state)
|
||||
(goto-char (point-min))
|
||||
(ebox-scroll-down end)
|
||||
(let* ((current (ebox--scroll-get-state id))
|
||||
(render-state (ebox--buffer-render-state buffer))
|
||||
(fresh-input
|
||||
(ebox-canonical-input-create
|
||||
(list (plist-get render-state :root-node))
|
||||
(plist-get render-state :source-index))))
|
||||
(should (= end (max 0 (- (length (plist-get current :content-lines))
|
||||
(plist-get current :content-height)))))
|
||||
(should (= (plist-get current :scroll-offset) end))
|
||||
(should (> end 0))
|
||||
(should (string-match-p "Open 09" (buffer-string)))
|
||||
(should (equal-including-properties
|
||||
(buffer-string) (ebox-box-line-test--reference-render fresh-input))))
|
||||
(ebox-scroll-up end)
|
||||
(should (= (plist-get (ebox--scroll-get-state id) :scroll-offset) 0))
|
||||
(should (equal-including-properties (buffer-string) initial))
|
||||
(dolist (record spine)
|
||||
(should (eq (car (nth 0 record)) (nth 1 record)))
|
||||
(should (eq (cdr (nth 0 record)) (nth 2 record)))
|
||||
(should (equal-including-properties (nth 1 record) (nth 3 record)))))))
|
||||
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
||||
|
||||
(provide 'ebox-box-line-flow-tests)
|
||||
;;; ebox-box-line-flow-tests.el ends here
|
||||
@ -4657,11 +4657,11 @@ line compositor's complete property output, including empty lines and chrome."
|
||||
(observed (ebox-test--box-full-height-joins input 6))
|
||||
(joins (plist-get observed :joins)))
|
||||
(should (= (ebox-string-height (plist-get observed :rendered)) 8))
|
||||
;; Alignment joins once; blank normalization retains its line result and
|
||||
;; the shared compositor adds only the final border-box join.
|
||||
;; Alignment and blank normalization retain lines; the shared compositor
|
||||
;; performs the only full-height join at the border-box output boundary.
|
||||
(ert-info ((format "Materialized (height bytes): %S"
|
||||
joins))
|
||||
(should (= (length joins) 2)))))
|
||||
(should (= (length joins) 1)))))
|
||||
|
||||
(ert-deftest ebox-box-chrome-keeps-complete-scroll-materialization ()
|
||||
"Existing fully painted scroll output must not enter decoration again."
|
||||
|
||||
Loading…
Reference in New Issue
Block a user