perf: retain mount state across mixed geometry and paint updates
This commit is contained in:
parent
789e0b2f17
commit
71154f5f78
204
ebox-surface.el
204
ebox-surface.el
@ -1805,6 +1805,13 @@ the projection roots a second time."
|
|||||||
(ebox-surface--native-full-frame-p state projection-kind)))
|
(ebox-surface--native-full-frame-p state projection-kind)))
|
||||||
(previous-owned-ranges
|
(previous-owned-ranges
|
||||||
(plist-get state :previous-surface-owned-ranges))
|
(plist-get state :previous-surface-owned-ranges))
|
||||||
|
(retained-owned-ranges
|
||||||
|
(and (eq projection-kind 'mixed-owner-reflow)
|
||||||
|
(eq (plist-get (plist-get state :mixed-owner-proof)
|
||||||
|
:geometry-kind)
|
||||||
|
'span-patch)
|
||||||
|
(not (ebox-surface--mixed-range-splice-p state))
|
||||||
|
(plist-get state :span-patch-retained-owned-ranges)))
|
||||||
(node-objects
|
(node-objects
|
||||||
(or (and scroll-fast-p
|
(or (and scroll-fast-p
|
||||||
(plist-get state :surface-node-object-table))
|
(plist-get state :surface-node-object-table))
|
||||||
@ -1818,6 +1825,7 @@ the projection roots a second time."
|
|||||||
;; Paint contributions belong to this one publication. Keep them out of
|
;; Paint contributions belong to this one publication. Keep them out of
|
||||||
;; the transferred client state so later paint cannot replay old layers.
|
;; the transferred client state so later paint cannot replay old layers.
|
||||||
(cl-remf state :paint-property-contributions)
|
(cl-remf state :paint-property-contributions)
|
||||||
|
(cl-remf state :span-patch-retained-owned-ranges)
|
||||||
(plist-put state :surface-node-object-table node-objects)
|
(plist-put state :surface-node-object-table node-objects)
|
||||||
(plist-put state :region-surface-object-table region-objects)
|
(plist-put state :region-surface-object-table region-objects)
|
||||||
(unless (and scroll-fast-p
|
(unless (and scroll-fast-p
|
||||||
@ -1831,11 +1839,12 @@ the projection roots a second time."
|
|||||||
(or (and native-retained-p
|
(or (and native-retained-p
|
||||||
(ebox-surface--native-patch-result
|
(ebox-surface--native-patch-result
|
||||||
context projection state output node-objects region-objects))
|
context projection state output node-objects region-objects))
|
||||||
(and (null property-contributions)
|
(and (or (null property-contributions) retained-owned-ranges)
|
||||||
(memq projection-kind
|
(memq projection-kind
|
||||||
'(span-patch owner-scoped mixed-owner-reflow))
|
'(span-patch owner-scoped mixed-owner-reflow))
|
||||||
(ebox-surface--span-coordinate-result
|
(ebox-surface--span-coordinate-result
|
||||||
context projection state output previous-owned-ranges))
|
context projection state output previous-owned-ranges
|
||||||
|
property-contributions retained-owned-ranges))
|
||||||
(let* ((range-splice-p
|
(let* ((range-splice-p
|
||||||
(and (eq projection-kind 'mixed-owner-reflow)
|
(and (eq projection-kind 'mixed-owner-reflow)
|
||||||
(ebox-surface--mixed-range-splice-p state)))
|
(ebox-surface--mixed-range-splice-p state)))
|
||||||
@ -1849,9 +1858,15 @@ the projection roots a second time."
|
|||||||
(or (and (null property-contributions)
|
(or (and (null property-contributions)
|
||||||
(memq projection-kind
|
(memq projection-kind
|
||||||
'(span-patch owner-scoped mixed-owner-reflow))
|
'(span-patch owner-scoped mixed-owner-reflow))
|
||||||
(ebox-surface--coordinate-commit
|
(when-let* ((batch
|
||||||
context state rendered owned-ranges (nth 3 prepared)
|
(ebox-surface--coordinate-batch
|
||||||
previous-owned-ranges))
|
state rendered previous-owned-ranges)))
|
||||||
|
(tp-commit-batch-result-create
|
||||||
|
context batch
|
||||||
|
:mount-specs
|
||||||
|
(ebox-surface--commit-mount-specs
|
||||||
|
(nth 3 prepared) owned-ranges)
|
||||||
|
:client-state state)))
|
||||||
(if (and (not range-splice-p)
|
(if (and (not range-splice-p)
|
||||||
(or full-surface-p native-retained-p
|
(or full-surface-p native-retained-p
|
||||||
property-contributions))
|
property-contributions))
|
||||||
@ -1965,13 +1980,68 @@ content projection order; fragment and surface records follow unchanged."
|
|||||||
(ebox-surface--commit-mount-specs mount-records owned-ranges)
|
(ebox-surface--commit-mount-specs mount-records owned-ranges)
|
||||||
:client-state state))))))
|
:client-state state))))))
|
||||||
|
|
||||||
(defun ebox-surface--coordinate-commit
|
(defun ebox-surface--patch-property-contributions
|
||||||
(context state rendered owned-ranges mount-records previous-owned-ranges)
|
(patches contributions extent)
|
||||||
"Return STATE's exact proven-owner coordinate commit, or nil.
|
"Return fresh PATCHES carrying completely covered CONTRIBUTIONS, or nil.
|
||||||
PREVIOUS-OWNED-RANGES identify the committed owner intervals. The mixed
|
CONTRIBUTIONS use target coordinates bounded by EXTENT. Preserve their order
|
||||||
|
inside every patch; a gap rejects the batch rather than dropping paint."
|
||||||
|
(when (proper-list-p contributions)
|
||||||
|
(catch 'uncovered
|
||||||
|
(let* ((targets (vconcat (mapcar #'copy-sequence patches)))
|
||||||
|
(count (length targets))
|
||||||
|
(layers (make-vector count nil)))
|
||||||
|
(dolist (contribution contributions)
|
||||||
|
(let ((start (plist-get contribution :start))
|
||||||
|
(end (plist-get contribution :end))
|
||||||
|
(props (plist-get contribution :props)))
|
||||||
|
;; Validate the original interval before clipping. On a proof
|
||||||
|
;; miss the retained-content path reports invalid inputs through
|
||||||
|
;; TP's ordinary contribution validator.
|
||||||
|
(unless (and (integerp start) (integerp end)
|
||||||
|
(<= 0 start end extent)
|
||||||
|
(ebox-style--valid-plist-p props)
|
||||||
|
(cl-loop for (key _value) on props by #'cddr
|
||||||
|
always (symbolp key)))
|
||||||
|
(throw 'uncovered nil))
|
||||||
|
(when (< start end)
|
||||||
|
(let ((low 0) (high count) (position start))
|
||||||
|
;; Patches are ordered and disjoint. Locate the first one
|
||||||
|
;; once per layer, then visit only its actual intersections.
|
||||||
|
(while (< low high)
|
||||||
|
(let ((middle (/ (+ low high) 2)))
|
||||||
|
(if (<= (plist-get (aref targets middle) :new-end) start)
|
||||||
|
(setq low (1+ middle))
|
||||||
|
(setq high middle))))
|
||||||
|
(while (< position end)
|
||||||
|
(when (>= low count) (throw 'uncovered nil))
|
||||||
|
(let* ((patch (aref targets low))
|
||||||
|
(patch-start (plist-get patch :new-start))
|
||||||
|
(next (min end (plist-get patch :new-end))))
|
||||||
|
(when (> patch-start position) (throw 'uncovered nil))
|
||||||
|
(when (< position next)
|
||||||
|
(push (list :start (- position patch-start)
|
||||||
|
:end (- next patch-start) :props props)
|
||||||
|
(aref layers low)))
|
||||||
|
(setq position next low (1+ low))))))))
|
||||||
|
(dotimes (index count)
|
||||||
|
(when (aref layers index)
|
||||||
|
(aset targets index
|
||||||
|
(plist-put (aref targets index) :property-contributions
|
||||||
|
(nreverse (aref layers index))))))
|
||||||
|
(append targets nil)))))
|
||||||
|
|
||||||
|
(defun ebox-surface--coordinate-batch
|
||||||
|
(state rendered previous-owned-ranges
|
||||||
|
&optional property-contributions retained-origin)
|
||||||
|
"Return STATE's exact proven-owner coordinate batch, or nil.
|
||||||
|
RENDERED is the candidate text. PREVIOUS-OWNED-RANGES identify the committed
|
||||||
|
owner intervals. The mixed
|
||||||
projection has already produced exact coordinate patches and final text; this
|
projection has already produced exact coordinate patches and final text; this
|
||||||
function composes those retained facts into TP's standard validated commit
|
function composes those retained facts into TP's standard validated commit
|
||||||
batch without asking TP to rediscover the same diff from a full plan."
|
batch without asking TP to rediscover the same diff from a full plan.
|
||||||
|
PROPERTY-CONTRIBUTIONS add ordered target paint. RETAINED-ORIGIN is the live
|
||||||
|
surface origin when an exact ownership proof permits indexed owner lookup.
|
||||||
|
This proof step does not register objects in the active prepare context."
|
||||||
(let* ((proof (plist-get state :mixed-owner-proof))
|
(let* ((proof (plist-get state :mixed-owner-proof))
|
||||||
(owner-ids
|
(owner-ids
|
||||||
(or (plist-get proof :owner-ids)
|
(or (plist-get proof :owner-ids)
|
||||||
@ -2006,6 +2076,19 @@ batch without asking TP to rediscover the same diff from a full plan."
|
|||||||
:new-start (plist-get patch :new-start)
|
:new-start (plist-get patch :new-start)
|
||||||
:new-end (plist-get patch :new-end)))
|
:new-end (plist-get patch :new-end)))
|
||||||
coordinate-patches))
|
coordinate-patches))
|
||||||
|
(if retained-origin
|
||||||
|
;; The strict retained-property witness preserves the complete
|
||||||
|
;; mount projection. Read just the changed owners from TP's
|
||||||
|
;; existing index, including baseline-only paint removals.
|
||||||
|
(maphash
|
||||||
|
(lambda (object _present)
|
||||||
|
(dolist (mount (tp-object-mounts object))
|
||||||
|
(let ((start (- (plist-get mount :start) retained-origin))
|
||||||
|
(end (- (plist-get mount :end) retained-origin)))
|
||||||
|
(push (list :old-start start :old-end end
|
||||||
|
:new-start start :new-end end)
|
||||||
|
intervals))))
|
||||||
|
owner-set)
|
||||||
(dolist (range previous-owned-ranges)
|
(dolist (range previous-owned-ranges)
|
||||||
(when (gethash (plist-get range :object) owner-set)
|
(when (gethash (plist-get range :object) owner-set)
|
||||||
(when-let* ((new-start
|
(when-let* ((new-start
|
||||||
@ -2018,7 +2101,7 @@ batch without asking TP to rediscover the same diff from a full plan."
|
|||||||
:old-end (plist-get range :end)
|
:old-end (plist-get range :end)
|
||||||
:new-start (car new-start)
|
:new-start (car new-start)
|
||||||
:new-end (car new-end))
|
:new-end (car new-end))
|
||||||
intervals))))
|
intervals)))))
|
||||||
(setq intervals
|
(setq intervals
|
||||||
(sort intervals
|
(sort intervals
|
||||||
(lambda (left right)
|
(lambda (left right)
|
||||||
@ -2077,27 +2160,66 @@ batch without asking TP to rediscover the same diff from a full plan."
|
|||||||
:patches patches
|
:patches patches
|
||||||
:coordinate-patches intervals)
|
:coordinate-patches intervals)
|
||||||
(tp-surface-error nil)))))
|
(tp-surface-error nil)))))
|
||||||
|
(when (and batch property-contributions)
|
||||||
|
(setq batch
|
||||||
|
(when-let* ((layered
|
||||||
|
(ebox-surface--patch-property-contributions
|
||||||
|
patches property-contributions target-extent)))
|
||||||
|
;; The bare batch above proves geometry. Composition
|
||||||
|
;; runs outside its proof-miss catch: a merger can signal
|
||||||
|
;; any condition, including `tp-surface-error', and must
|
||||||
|
;; preserve ordinary transaction failure and rollback.
|
||||||
|
(tp-commit-batch-create
|
||||||
|
:base-revision base-revision
|
||||||
|
:target-revision (1+ base-revision)
|
||||||
|
:base-extent source-extent :target-extent target-extent
|
||||||
|
:patches layered :coordinate-patches intervals))))
|
||||||
(when batch
|
(when batch
|
||||||
(prog1
|
(cl-remf state :content-base-extent)
|
||||||
(tp-commit-batch-result-create
|
batch))))))
|
||||||
context batch
|
|
||||||
:mount-specs
|
|
||||||
(ebox-surface--commit-mount-specs mount-records owned-ranges)
|
|
||||||
:client-state state)
|
|
||||||
(cl-remf state :content-base-extent))))))))
|
|
||||||
|
|
||||||
(defun ebox-surface--span-coordinate-result
|
(defun ebox-surface--span-coordinate-result
|
||||||
(context projection state rendered previous-owned-ranges)
|
(context projection state rendered previous-owned-ranges
|
||||||
"Return a direct span commit from retained coordinate metadata, or nil."
|
&optional property-contributions retained-owned-ranges)
|
||||||
(when-let* ((fragments
|
"Return a direct span commit from retained coordinate metadata, or nil.
|
||||||
(or (plist-get state :mixed-owner-fragment-data)
|
CONTEXT and PROJECTION supply retained object identities. STATE and RENDERED
|
||||||
(plist-get state :span-patch-fragment-data)))
|
hold the candidate coordinates and text; PREVIOUS-OWNED-RANGES is the committed
|
||||||
(patches (plist-get state :content-coordinate-patches))
|
ownership snapshot. PROPERTY-CONTRIBUTIONS are ordered candidate paint layers.
|
||||||
(rebased
|
RETAINED-OWNED-RANGES witnesses unchanged ownership when it is that snapshot."
|
||||||
(and previous-owned-ranges
|
(let* ((patches (plist-get state :content-coordinate-patches))
|
||||||
(ebox-surface--rebase-owned-ranges
|
|
||||||
previous-owned-ranges patches (length rendered))))
|
|
||||||
(surface-root (plist-get projection :surface-root))
|
(surface-root (plist-get projection :surface-root))
|
||||||
|
(retained-origin
|
||||||
|
(when (and surface-root patches retained-owned-ranges
|
||||||
|
(eq retained-owned-ranges previous-owned-ranges)
|
||||||
|
(equal (plist-get state :content-base-extent)
|
||||||
|
(length rendered))
|
||||||
|
(cl-every
|
||||||
|
(lambda (patch)
|
||||||
|
(and (= (plist-get patch :old-start)
|
||||||
|
(plist-get patch :new-start))
|
||||||
|
(= (plist-get patch :old-end)
|
||||||
|
(plist-get patch :new-end))))
|
||||||
|
patches))
|
||||||
|
(let ((mounts (tp-object-mounts surface-root)))
|
||||||
|
(when (and (= (length mounts) 1)
|
||||||
|
(= (- (plist-get (car mounts) :end)
|
||||||
|
(plist-get (car mounts) :start))
|
||||||
|
(length rendered)))
|
||||||
|
(plist-get (car mounts) :start))))))
|
||||||
|
(when-let* ((fragments
|
||||||
|
(and surface-root patches
|
||||||
|
(or (plist-get state :mixed-owner-fragment-data)
|
||||||
|
(plist-get state :span-patch-fragment-data))))
|
||||||
|
(rebased
|
||||||
|
(if retained-origin
|
||||||
|
(cons t previous-owned-ranges)
|
||||||
|
(and (null property-contributions) previous-owned-ranges
|
||||||
|
(ebox-surface--rebase-owned-ranges
|
||||||
|
previous-owned-ranges patches (length rendered)))))
|
||||||
|
(batch
|
||||||
|
(ebox-surface--coordinate-batch
|
||||||
|
state rendered previous-owned-ranges
|
||||||
|
property-contributions retained-origin))
|
||||||
(fragment-root
|
(fragment-root
|
||||||
(tp-object-ensure context surface-root
|
(tp-object-ensure context surface-root
|
||||||
ebox-surface--fragments-key 'ebox/fragments))
|
ebox-surface--fragments-key 'ebox/fragments))
|
||||||
@ -2107,26 +2229,34 @@ batch without asking TP to rediscover the same diff from a full plan."
|
|||||||
(let* ((owned-ranges (cdr rebased))
|
(let* ((owned-ranges (cdr rebased))
|
||||||
(rendered-length (length rendered))
|
(rendered-length (length rendered))
|
||||||
(mount-records
|
(mount-records
|
||||||
|
(unless retained-origin
|
||||||
(list (list :object text-leaf :start 0 :end rendered-length
|
(list (list :object text-leaf :start 0 :end rendered-length
|
||||||
:props nil :tags nil :leaf t)
|
:props nil :tags nil :leaf t)
|
||||||
(list :object fragment-root :start 0 :end rendered-length
|
(list :object fragment-root :start 0 :end rendered-length
|
||||||
:props nil :tags nil :leaf nil)
|
:props nil :tags nil :leaf nil)
|
||||||
(list :object surface-root :start 0 :end rendered-length
|
(list :object surface-root :start 0 :end rendered-length
|
||||||
:props nil :tags nil :leaf nil)))
|
:props nil :tags nil :leaf nil))))
|
||||||
(result
|
(result
|
||||||
(ebox-surface--coordinate-commit
|
(if retained-origin
|
||||||
context state rendered owned-ranges mount-records
|
(tp-commit-batch-result-create
|
||||||
previous-owned-ranges)))
|
context batch :client-state state :reuse-mount-projection t)
|
||||||
|
(tp-commit-batch-result-create
|
||||||
|
context batch
|
||||||
|
:mount-specs
|
||||||
|
(ebox-surface--commit-mount-specs mount-records owned-ranges)
|
||||||
|
:client-state state))))
|
||||||
(when result
|
(when result
|
||||||
(plist-put state :surface-owned-ranges
|
(plist-put state :surface-owned-ranges
|
||||||
(ebox-surface--snapshot-owned-ranges owned-ranges))
|
(if retained-origin
|
||||||
|
owned-ranges
|
||||||
|
(ebox-surface--snapshot-owned-ranges owned-ranges)))
|
||||||
(plist-put state :surface-fragments fragments)
|
(plist-put state :surface-fragments fragments)
|
||||||
(cl-remf state :previous-surface-owned-ranges)
|
(cl-remf state :previous-surface-owned-ranges)
|
||||||
(cl-remf state :mixed-owner-fragment-data)
|
(cl-remf state :mixed-owner-fragment-data)
|
||||||
(cl-remf state :mixed-owner-content-p)
|
(cl-remf state :mixed-owner-content-p)
|
||||||
(cl-remf state :span-patch-fragment-data)
|
(cl-remf state :span-patch-fragment-data)
|
||||||
(cl-remf state :span-patch-content-p)
|
(cl-remf state :span-patch-content-p)
|
||||||
result))))
|
result)))))
|
||||||
|
|
||||||
(defun ebox-surface--mounted-object-for-node (state node-id)
|
(defun ebox-surface--mounted-object-for-node (state node-id)
|
||||||
"Return NODE-ID's nearest live retained object with mounts in STATE."
|
"Return NODE-ID's nearest live retained object with mounts in STATE."
|
||||||
@ -3926,7 +4056,10 @@ Proof mismatches return nil; unexpected rendering errors propagate."
|
|||||||
output old-fragments
|
output old-fragments
|
||||||
(plist-get state :content-coordinate-patches)))))
|
(plist-get state :content-coordinate-patches)))))
|
||||||
(plist-put state :span-patch-fragment-data fragments)
|
(plist-put state :span-patch-fragment-data fragments)
|
||||||
(plist-put state :span-patch-content-p t))
|
(plist-put state :span-patch-content-p t)
|
||||||
|
(when (and retained-properties-p (not coordinate-shift-p))
|
||||||
|
(plist-put state :span-patch-retained-owned-ranges
|
||||||
|
(plist-get previous-state :surface-owned-ranges))))
|
||||||
output)))))))
|
output)))))))
|
||||||
|
|
||||||
(defun ebox-surface--formatting-context-reflow-lines
|
(defun ebox-surface--formatting-context-reflow-lines
|
||||||
@ -5617,6 +5750,7 @@ but cannot introduce or remove a viewport expression or node identity."
|
|||||||
(ebox--new-buffer-render-state root))
|
(ebox--new-buffer-render-state root))
|
||||||
(copy-sequence state-overrides))))
|
(copy-sequence state-overrides))))
|
||||||
(cl-remf state :paint-property-contributions)
|
(cl-remf state :paint-property-contributions)
|
||||||
|
(cl-remf state :span-patch-retained-owned-ranges)
|
||||||
(plist-put state :root-node root)
|
(plist-put state :root-node root)
|
||||||
(when (plist-get state :native-sync-confirmed-p)
|
(when (plist-get state :native-sync-confirmed-p)
|
||||||
(ebox-native-commit-attach-confirmed-base previous-state state))
|
(ebox-native-commit-attach-confirmed-base previous-state state))
|
||||||
|
|||||||
@ -948,6 +948,23 @@
|
|||||||
(ebox-child-range-test--assert-current-snapshot
|
(ebox-child-range-test--assert-current-snapshot
|
||||||
(current-buffer) ancestor))))))))
|
(current-buffer) ancestor))))))))
|
||||||
|
|
||||||
|
(defun ebox-child-range-test--mixed-followup-candidate (buffer selected &optional title)
|
||||||
|
"Return BUFFER's title and row-paint candidate for SELECTED, using TITLE."
|
||||||
|
(let ((candidate (ebox-candidate-begin buffer)))
|
||||||
|
(ebox-candidate-replace-host-ref
|
||||||
|
candidate 'title
|
||||||
|
(ebox-test-text (propertize (or title (format "title-%d" selected))
|
||||||
|
'face '(:weight bold))
|
||||||
|
:key 'title :source-identity 'title))
|
||||||
|
(dolist (row '(1 2))
|
||||||
|
(let ((ref (intern (format "row-%d" row))))
|
||||||
|
(ebox-candidate-replace-host-ref
|
||||||
|
candidate ref
|
||||||
|
(ebox-test-box :key ref :source-identity ref
|
||||||
|
:color (if (= row selected) "#123456" "#654321")
|
||||||
|
(ebox-test-text (format "row-%d" row))))))
|
||||||
|
candidate))
|
||||||
|
|
||||||
(ert-deftest ebox-child-range-retained-kind-replacement-allows-mixed-followups ()
|
(ert-deftest ebox-child-range-retained-kind-replacement-allows-mixed-followups ()
|
||||||
"Later title and row paint edits remain local beside disjoint scrolling."
|
"Later title and row paint edits remain local beside disjoint scrolling."
|
||||||
(with-temp-buffer
|
(with-temp-buffer
|
||||||
@ -960,6 +977,12 @@
|
|||||||
(current-buffer)
|
(current-buffer)
|
||||||
(ebox-child-range-test--replace-snapshot-detail (current-buffer) t))
|
(ebox-child-range-test--replace-snapshot-detail (current-buffer) t))
|
||||||
(let* ((state (ebox--buffer-render-state (current-buffer)))
|
(let* ((state (ebox--buffer-render-state (current-buffer)))
|
||||||
|
(surface ebox-surface--buffer-surface)
|
||||||
|
(mounts (tp--surface-mounts surface))
|
||||||
|
(mount-ids (mapcar #'tp--surface-mount-id mounts))
|
||||||
|
(mount-index (tp--surface-mount-index surface))
|
||||||
|
(index (tp--surface-index surface))
|
||||||
|
(ranges (plist-get state :surface-owned-ranges))
|
||||||
(scroll-id (car (plist-get state :scroll-region-ids)))
|
(scroll-id (car (plist-get state :scroll-region-ids)))
|
||||||
(scroll (gethash scroll-id ebox--scroll-global-state))
|
(scroll (gethash scroll-id ebox--scroll-global-state))
|
||||||
(raw (plist-get scroll :content-lines))
|
(raw (plist-get scroll :content-lines))
|
||||||
@ -972,22 +995,12 @@
|
|||||||
(should scroll-id)
|
(should scroll-id)
|
||||||
(should (= (length (plist-get state :scroll-region-ids)) 1))
|
(should (= (length (plist-get state :scroll-region-ids)) 1))
|
||||||
(dolist (selected '(2 1 2))
|
(dolist (selected '(2 1 2))
|
||||||
(let* ((candidate (ebox-candidate-begin (current-buffer)))
|
(let* ((candidate
|
||||||
|
(ebox-child-range-test--mixed-followup-candidate
|
||||||
|
(current-buffer) selected))
|
||||||
(root-renders 0)
|
(root-renders 0)
|
||||||
(observer (lambda (&rest _arguments) (cl-incf root-renders)))
|
(observer (lambda (&rest _arguments) (cl-incf root-renders)))
|
||||||
report)
|
report)
|
||||||
(ebox-candidate-replace-host-ref
|
|
||||||
candidate 'title
|
|
||||||
(ebox-test-text (propertize (format "title-%d" selected)
|
|
||||||
'face '(:weight bold))
|
|
||||||
:key 'title :source-identity 'title))
|
|
||||||
(dolist (row '(1 2))
|
|
||||||
(let ((ref (intern (format "row-%d" row))))
|
|
||||||
(ebox-candidate-replace-host-ref
|
|
||||||
candidate ref
|
|
||||||
(ebox-test-box :key ref :source-identity ref
|
|
||||||
:color (if (= row selected) "#123456" "#654321")
|
|
||||||
(ebox-test-text (format "row-%d" row))))))
|
|
||||||
(advice-add 'ebox-surface--render-candidate :before observer)
|
(advice-add 'ebox-surface--render-candidate :before observer)
|
||||||
(unwind-protect
|
(unwind-protect
|
||||||
(setq report (ebox-commit (current-buffer) candidate))
|
(setq report (ebox-commit (current-buffer) candidate))
|
||||||
@ -1001,11 +1014,308 @@
|
|||||||
selected root-renders report))
|
selected root-renders report))
|
||||||
(should (eq (plist-get report :projection-kind) 'mixed-owner-reflow))
|
(should (eq (plist-get report :projection-kind) 'mixed-owner-reflow))
|
||||||
(should (zerop root-renders))
|
(should (zerop root-renders))
|
||||||
(should-not (plist-get report :tp-full-root)))
|
(should-not (plist-get report :tp-full-root))
|
||||||
|
(should (plist-get (tp-surface-report surface) :commit-batch))
|
||||||
|
(should (plist-get (tp-surface-report surface) :retained-mount-state))
|
||||||
|
(should (eq mounts (tp--surface-mounts surface)))
|
||||||
|
(should (equal mount-ids
|
||||||
|
(mapcar #'tp--surface-mount-id
|
||||||
|
(tp--surface-mounts surface))))
|
||||||
|
(should (eq mount-index (tp--surface-mount-index surface)))
|
||||||
|
(should (eq index (tp--surface-index surface)))
|
||||||
|
(should (eq ranges
|
||||||
|
(plist-get (ebox--buffer-render-state (current-buffer))
|
||||||
|
:surface-owned-ranges))))
|
||||||
(let ((next-scroll (gethash scroll-id ebox--scroll-global-state)))
|
(let ((next-scroll (gethash scroll-id ebox--scroll-global-state)))
|
||||||
(should (eq raw (plist-get next-scroll :content-lines)))
|
(should (eq raw (plist-get next-scroll :content-lines)))
|
||||||
(should (eq rendered (plist-get next-scroll :rendered-content-lines)))))))))
|
(should (eq rendered (plist-get next-scroll :rendered-content-lines)))))))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-child-range-mixed-followup-publish-failure-restores-and-retries ()
|
||||||
|
"A failed public mixed commit restores the old generation before retry."
|
||||||
|
(with-temp-buffer
|
||||||
|
(ebox-render-to-buffer
|
||||||
|
(current-buffer) (ebox-child-range-test--snapshot-replacement-root))
|
||||||
|
(ebox--ensure-layout-snapshot-details
|
||||||
|
(current-buffer)
|
||||||
|
(plist-get (ebox--host-ref-node (current-buffer) 'detail) :node-id))
|
||||||
|
(ebox-commit
|
||||||
|
(current-buffer)
|
||||||
|
(ebox-child-range-test--replace-snapshot-detail (current-buffer) t))
|
||||||
|
(let* ((state (ebox--buffer-render-state (current-buffer)))
|
||||||
|
(surface ebox-surface--buffer-surface)
|
||||||
|
(revision (tp-surface-revision surface))
|
||||||
|
(previous-report (tp-surface-report surface))
|
||||||
|
(before (buffer-string))
|
||||||
|
(mounts (tp--surface-mounts surface))
|
||||||
|
(mount-ids (mapcar #'tp--surface-mount-id mounts))
|
||||||
|
(mount-index (tp--surface-mount-index surface))
|
||||||
|
(index (tp--surface-index surface))
|
||||||
|
(ranges (plist-get state :surface-owned-ranges))
|
||||||
|
(scroll-id (car (plist-get state :scroll-region-ids)))
|
||||||
|
(scroll (gethash scroll-id ebox--scroll-global-state))
|
||||||
|
trace)
|
||||||
|
(should-error
|
||||||
|
(ebox-commit
|
||||||
|
(current-buffer)
|
||||||
|
(ebox-child-range-test--mixed-followup-candidate (current-buffer) 2)
|
||||||
|
(lambda (_report) (push 'publish trace) (error "reject mixed publication"))
|
||||||
|
(lambda (_report) (push 'rollback trace))))
|
||||||
|
(should (equal trace '(rollback publish)))
|
||||||
|
(should (eq state (ebox--buffer-render-state (current-buffer))))
|
||||||
|
(should (eq state (tp-surface-client-state surface)))
|
||||||
|
(should (= revision (tp-surface-revision surface)))
|
||||||
|
(should (eq ranges (plist-get state :surface-owned-ranges)))
|
||||||
|
(should (eq mounts (tp--surface-mounts surface)))
|
||||||
|
(should (equal mount-ids (mapcar #'tp--surface-mount-id mounts)))
|
||||||
|
(should (eq mount-index (tp--surface-mount-index surface)))
|
||||||
|
(should (eq index (tp--surface-index surface)))
|
||||||
|
(should (eq scroll (gethash scroll-id ebox--scroll-global-state)))
|
||||||
|
(should (equal-including-properties before (buffer-string)))
|
||||||
|
(should (equal previous-report (tp-surface-report surface)))
|
||||||
|
(let ((report
|
||||||
|
(ebox-commit
|
||||||
|
(current-buffer)
|
||||||
|
(ebox-child-range-test--mixed-followup-candidate
|
||||||
|
(current-buffer) 2))))
|
||||||
|
(ebox-child-range-test--assert-full-render-equivalent (current-buffer))
|
||||||
|
(should (= (1+ revision) (tp-surface-revision surface)))
|
||||||
|
(should (eq (plist-get report :projection-kind) 'mixed-owner-reflow))
|
||||||
|
(should-not (plist-get report :tp-full-root))
|
||||||
|
(should (plist-get (tp-surface-report surface) :commit-batch))
|
||||||
|
(should (plist-get (tp-surface-report surface) :retained-mount-state))
|
||||||
|
(should (eq mounts (tp--surface-mounts surface)))
|
||||||
|
(should (equal mount-ids (mapcar #'tp--surface-mount-id mounts)))
|
||||||
|
(should (eq mount-index (tp--surface-mount-index surface)))
|
||||||
|
(should (eq index (tp--surface-index surface)))
|
||||||
|
(should (eq (plist-get scroll :content-lines)
|
||||||
|
(plist-get (gethash scroll-id ebox--scroll-global-state)
|
||||||
|
:content-lines)))))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-child-range-mixed-followup-removes-face-without-contribution ()
|
||||||
|
"A paint owner with no new face still publishes its baseline-only removal."
|
||||||
|
(with-temp-buffer
|
||||||
|
(ebox-render-to-buffer
|
||||||
|
(current-buffer)
|
||||||
|
(ebox-test-column :width '(200)
|
||||||
|
(ebox-test-column :bgcolor "#DDE7EF"
|
||||||
|
(ebox-test-text (propertize "title-1" 'face '(:weight bold))
|
||||||
|
:key 'title :source-identity 'title))
|
||||||
|
(ebox-test-box :key 'row-1 :source-identity 'row-1 :color "#123456"
|
||||||
|
(ebox-test-text (propertize "row-1" 'face '(:slant italic))))))
|
||||||
|
(let* ((surface ebox-surface--buffer-surface)
|
||||||
|
(mounts (tp--surface-mounts surface))
|
||||||
|
(mount-index (tp--surface-mount-index surface))
|
||||||
|
(candidate (ebox-candidate-begin (current-buffer)))
|
||||||
|
(observed nil) (contributions 'not-observed)
|
||||||
|
(observer
|
||||||
|
(lambda (_context _projection state _output &optional kind)
|
||||||
|
(when (eq kind 'mixed-owner-reflow)
|
||||||
|
(setq observed t
|
||||||
|
contributions (plist-get state :paint-property-contributions)))))
|
||||||
|
report)
|
||||||
|
(ebox-candidate-replace-host-ref
|
||||||
|
candidate 'title
|
||||||
|
(ebox-test-text (propertize "title-2" 'face '(:weight bold))
|
||||||
|
:key 'title :source-identity 'title))
|
||||||
|
(ebox-candidate-replace-host-ref
|
||||||
|
candidate 'row-1
|
||||||
|
(ebox-test-box :key 'row-1 :source-identity 'row-1
|
||||||
|
(ebox-test-text (propertize "row-1" 'face '(:slant italic)))))
|
||||||
|
(advice-add 'ebox-surface--projection-result :before observer)
|
||||||
|
(unwind-protect
|
||||||
|
(setq report (ebox-commit (current-buffer) candidate))
|
||||||
|
(advice-remove 'ebox-surface--projection-result observer))
|
||||||
|
(ebox-child-range-test--assert-full-render-equivalent (current-buffer))
|
||||||
|
(should observed)
|
||||||
|
(should-not contributions)
|
||||||
|
(should (eq (plist-get report :projection-kind) 'mixed-owner-reflow))
|
||||||
|
(should (equal (get-text-property (string-match "row-1" (buffer-string))
|
||||||
|
'face (buffer-string))
|
||||||
|
'(:slant italic)))
|
||||||
|
(should (plist-get (tp-surface-report surface) :commit-batch))
|
||||||
|
(should (plist-get (tp-surface-report surface) :retained-mount-state))
|
||||||
|
(should (eq mounts (tp--surface-mounts surface)))
|
||||||
|
(should (eq mount-index (tp--surface-mount-index surface))))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-child-range-mixed-followup-coordinate-change-keeps-fallback ()
|
||||||
|
"A longer title cannot claim the stable-coordinate mount-reuse proof."
|
||||||
|
(with-temp-buffer
|
||||||
|
(ebox-render-to-buffer
|
||||||
|
(current-buffer) (ebox-child-range-test--snapshot-replacement-root))
|
||||||
|
(ebox-commit
|
||||||
|
(current-buffer)
|
||||||
|
(ebox-child-range-test--replace-snapshot-detail (current-buffer) t))
|
||||||
|
(let ((extent (buffer-size)))
|
||||||
|
(ebox-commit
|
||||||
|
(current-buffer)
|
||||||
|
(ebox-child-range-test--mixed-followup-candidate
|
||||||
|
(current-buffer) 2 "title-2 with a longer extent"))
|
||||||
|
(should-not (= extent (buffer-size))))
|
||||||
|
(ebox-child-range-test--assert-full-render-equivalent (current-buffer))
|
||||||
|
(should-not (plist-get (tp-surface-report ebox-surface--buffer-surface)
|
||||||
|
:retained-mount-state))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-child-range-mixed-followup-patch-layers-require-complete-coverage ()
|
||||||
|
"Ordered paint may cross adjacent patches, but cannot disappear into a gap."
|
||||||
|
(let* ((baseline (propertize "abcdefgh" 'face 'bold 'help-echo "base"))
|
||||||
|
(patches
|
||||||
|
(mapcar (lambda (range)
|
||||||
|
(list :old-start (car range) :old-end (cdr range)
|
||||||
|
:new-start (car range) :new-end (cdr range)
|
||||||
|
:replacement (substring baseline (car range) (cdr range))))
|
||||||
|
'((0 . 2) (2 . 4) (6 . 8))))
|
||||||
|
(before (copy-tree patches))
|
||||||
|
(layers '((:start 0 :end 4 :props (face italic))
|
||||||
|
(:start 1 :end 3 :props (face nil help-echo nil))
|
||||||
|
(:start 6 :end 8 :props (face (:foreground "red")))))
|
||||||
|
(mapped (ebox-surface--patch-property-contributions patches layers 8))
|
||||||
|
(batch (tp-commit-batch-create
|
||||||
|
:base-revision 1 :target-revision 2 :base-extent 8 :target-extent 8
|
||||||
|
:patches mapped))
|
||||||
|
(stored (tp-commit-batch-patches batch))
|
||||||
|
(actual (concat (plist-get (nth 0 stored) :replacement)
|
||||||
|
(plist-get (nth 1 stored) :replacement)
|
||||||
|
(substring baseline 4 6)
|
||||||
|
(plist-get (nth 2 stored) :replacement))))
|
||||||
|
(should (equal-including-properties
|
||||||
|
actual (tp--compose-relative-property-contributions baseline layers)))
|
||||||
|
(should (equal-including-properties before patches))
|
||||||
|
(should-not (eq (car patches) (car mapped)))
|
||||||
|
(should-not
|
||||||
|
(ebox-surface--patch-property-contributions
|
||||||
|
patches '((:start 1 :end 7 :props (face italic))) 8))
|
||||||
|
(dolist (invalid '((:start -1 :end 1 :props (face bold))
|
||||||
|
(:start 0 :end 9 :props (face bold))
|
||||||
|
(:start 0 :end 1 :props (face))))
|
||||||
|
(should-not
|
||||||
|
(ebox-surface--patch-property-contributions patches (list invalid) 8)))
|
||||||
|
(should
|
||||||
|
(ebox-surface--patch-property-contributions
|
||||||
|
patches '((:start 5 :end 5 :props (face bold))) 8))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-child-range-mixed-followup-proof-misses-preserve-fallback ()
|
||||||
|
"Missing ownership or paint coverage returns to exact retained-content work."
|
||||||
|
(dolist (miss '(ownership coverage))
|
||||||
|
(with-temp-buffer
|
||||||
|
(ebox-render-to-buffer
|
||||||
|
(current-buffer) (ebox-child-range-test--snapshot-replacement-root))
|
||||||
|
(ebox-commit
|
||||||
|
(current-buffer)
|
||||||
|
(ebox-child-range-test--replace-snapshot-detail (current-buffer) t))
|
||||||
|
(let* ((function (if (eq miss 'ownership)
|
||||||
|
'ebox-surface--span-patch-output
|
||||||
|
'ebox-surface--patch-property-contributions))
|
||||||
|
observed
|
||||||
|
(observer
|
||||||
|
(lambda (original &rest arguments)
|
||||||
|
(setq observed t)
|
||||||
|
(when (eq miss 'ownership)
|
||||||
|
(prog1 (apply original arguments)
|
||||||
|
(cl-remf (nth 2 arguments) :span-patch-retained-owned-ranges))))))
|
||||||
|
(advice-add function :around observer)
|
||||||
|
(unwind-protect
|
||||||
|
(ebox-commit
|
||||||
|
(current-buffer)
|
||||||
|
(ebox-child-range-test--mixed-followup-candidate (current-buffer) 2))
|
||||||
|
(advice-remove function observer))
|
||||||
|
(should observed)
|
||||||
|
(ebox-child-range-test--assert-full-render-equivalent (current-buffer))
|
||||||
|
(should-not (plist-get (tp-surface-report ebox-surface--buffer-surface)
|
||||||
|
:retained-mount-state))))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-child-range-mixed-followup-merger-errors-escape-proof-fallback ()
|
||||||
|
"Even a constructor-like merger error propagates once and preserves state."
|
||||||
|
(with-temp-buffer
|
||||||
|
(ebox-render-to-buffer
|
||||||
|
(current-buffer) (ebox-child-range-test--snapshot-replacement-root))
|
||||||
|
(ebox-commit
|
||||||
|
(current-buffer)
|
||||||
|
(ebox-child-range-test--replace-snapshot-detail (current-buffer) t))
|
||||||
|
(let* ((surface ebox-surface--buffer-surface)
|
||||||
|
(state (ebox--buffer-render-state (current-buffer)))
|
||||||
|
(revision (tp-surface-revision surface))
|
||||||
|
(mounts (tp--surface-mounts surface))
|
||||||
|
(index (tp--surface-mount-index surface))
|
||||||
|
(before (buffer-string))
|
||||||
|
(merge-calls 0) (contributed-batches 0) contributed-face
|
||||||
|
(observer
|
||||||
|
(lambda (original &rest arguments)
|
||||||
|
(let* ((patches (mapcar #'copy-sequence (plist-get arguments :patches)))
|
||||||
|
(patch (cl-find-if
|
||||||
|
(lambda (entry) (plist-get entry :property-contributions))
|
||||||
|
patches)))
|
||||||
|
(when patch
|
||||||
|
;; Exception-boundary fixture: TP merges only over a present
|
||||||
|
;; baseline property. Keep Ebox's actual generated layers,
|
||||||
|
;; adding a baseline only to a detached constructor argument.
|
||||||
|
;; Inline row faces would change the fixture's geometry proof.
|
||||||
|
(let* ((layer (car (plist-get patch :property-contributions)))
|
||||||
|
(start (plist-get layer :start))
|
||||||
|
(replacement (copy-sequence (plist-get patch :replacement))))
|
||||||
|
(should (< start (plist-get layer :end)))
|
||||||
|
(setq contributed-face (plist-get (plist-get layer :props) 'face))
|
||||||
|
(should contributed-face)
|
||||||
|
(put-text-property start (1+ start) 'face '(:slant italic) replacement)
|
||||||
|
(plist-put patch :replacement replacement)
|
||||||
|
(setq arguments (plist-put (copy-sequence arguments) :patches patches))
|
||||||
|
(cl-incf contributed-batches)))
|
||||||
|
(apply original arguments)))))
|
||||||
|
(let ((tp--property-policies (copy-hash-table tp--property-policies))
|
||||||
|
(tp--property-policy-order (copy-sequence tp--property-policy-order)))
|
||||||
|
(tp-define-property-policy
|
||||||
|
(tp-text-property-id 'face)
|
||||||
|
:projector (tp-property-policy-projector
|
||||||
|
(tp-property-policy (tp-text-property-id 'face)))
|
||||||
|
:merge (lambda (old new)
|
||||||
|
(should (equal old '(:slant italic)))
|
||||||
|
(should (equal new contributed-face))
|
||||||
|
(cl-incf merge-calls)
|
||||||
|
(signal 'tp-surface-error '(:commit-patch merger-failure))))
|
||||||
|
(advice-add 'tp-commit-batch-create :around observer)
|
||||||
|
(unwind-protect
|
||||||
|
(let ((condition
|
||||||
|
(should-error
|
||||||
|
(ebox-commit
|
||||||
|
(current-buffer)
|
||||||
|
(ebox-child-range-test--mixed-followup-candidate
|
||||||
|
(current-buffer) 2))
|
||||||
|
:type 'tp-surface-error)))
|
||||||
|
(should (equal (seq-take condition 3)
|
||||||
|
'(tp-surface-error :commit-patch merger-failure))))
|
||||||
|
(advice-remove 'tp-commit-batch-create observer)))
|
||||||
|
(should (= contributed-batches 1))
|
||||||
|
(should (= merge-calls 1))
|
||||||
|
(should (= revision (tp-surface-revision surface)))
|
||||||
|
(should (eq state (tp-surface-client-state surface)))
|
||||||
|
(should (eq state (ebox--buffer-render-state (current-buffer))))
|
||||||
|
(should (eq mounts (tp--surface-mounts surface)))
|
||||||
|
(should (eq index (tp--surface-mount-index surface)))
|
||||||
|
(should (equal-including-properties before (buffer-string)))
|
||||||
|
(ebox-commit
|
||||||
|
(current-buffer)
|
||||||
|
(ebox-child-range-test--mixed-followup-candidate (current-buffer) 2))
|
||||||
|
(ebox-child-range-test--assert-full-render-equivalent (current-buffer))
|
||||||
|
(should (plist-get (tp-surface-report surface) :retained-mount-state)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-child-range-mixed-followup-role-change-keeps-fallback ()
|
||||||
|
"Replacing text with a new box role cannot reuse the old mount topology."
|
||||||
|
(with-temp-buffer
|
||||||
|
(ebox-render-to-buffer
|
||||||
|
(current-buffer) (ebox-child-range-test--snapshot-replacement-root))
|
||||||
|
(ebox-commit
|
||||||
|
(current-buffer)
|
||||||
|
(ebox-child-range-test--replace-snapshot-detail (current-buffer) t))
|
||||||
|
(let ((candidate
|
||||||
|
(ebox-child-range-test--mixed-followup-candidate (current-buffer) 2)))
|
||||||
|
(ebox-candidate-replace-host-ref
|
||||||
|
candidate 'title
|
||||||
|
(ebox-test-box :key 'title :source-identity 'title
|
||||||
|
(ebox-test-text (propertize "title-2" 'face '(:weight bold)))))
|
||||||
|
(ebox-commit (current-buffer) candidate))
|
||||||
|
(ebox-child-range-test--assert-full-render-equivalent (current-buffer))
|
||||||
|
(should-not (plist-get (tp-surface-report ebox-surface--buffer-surface)
|
||||||
|
:retained-mount-state))))
|
||||||
|
|
||||||
(ert-deftest ebox-child-range-does-not-reuse-disappeared-key-positionally ()
|
(ert-deftest ebox-child-range-does-not-reuse-disappeared-key-positionally ()
|
||||||
"A new keyed item must not inherit a removed peer's runtime identity."
|
"A new keyed item must not inherit a removed peer's runtime identity."
|
||||||
(let ((buffer (generate-new-buffer " *ebox-range-key-reentry*")))
|
(let ((buffer (generate-new-buffer " *ebox-range-key-reentry*")))
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user