From 96b91ea4683e8cfe4decfbf1325239961ef127c4 Mon Sep 17 00:00:00 2001 From: Kinneyzhang Date: Fri, 11 Sep 2026 15:40:02 +0800 Subject: [PATCH] Keep layer move publication patches disjoint --- ebox-surface.el | 64 ++++++++++++++++----------- tests/ebox-layer-publication-tests.el | 55 +++++++++++++++++++++++ 2 files changed, 92 insertions(+), 27 deletions(-) diff --git a/ebox-surface.el b/ebox-surface.el index 4be7fff..a90daa4 100644 --- a/ebox-surface.el +++ b/ebox-surface.el @@ -2154,8 +2154,17 @@ function composes those retained facts into TP's standard validated commit 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." +This proof step does not register objects in the active prepare context. + +Layer recomposition has a stronger damage proof than ordinary owner-scoped +updates: `:content-coordinate-patches' already contains every row whose text +or paint properties changed. In that mode, keep those intervals discrete; +adding the owner's enclosing mount would turn two distant damaged rows into +one large replacement and needlessly rewrite the rows between them." (let* ((proof (plist-get state :mixed-owner-proof)) + (layer-damage-only-p + (and (plist-get state :layer-recompose-p) + (null property-contributions))) (owner-ids (or (plist-get proof :owner-ids) (mapcar (lambda (owner-proof) @@ -2189,32 +2198,33 @@ This proof step does not register objects in the active prepare context." :new-start (plist-get patch :new-start) :new-end (plist-get patch :new-end))) 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) - (when (gethash (plist-get range :object) owner-set) - (when-let* ((new-start - (ebox-surface--rebase-coordinate - (plist-get range :start) coordinate-patches)) - (new-end - (ebox-surface--rebase-coordinate - (plist-get range :end) coordinate-patches))) - (push (list :old-start (plist-get range :start) - :old-end (plist-get range :end) - :new-start (car new-start) - :new-end (car new-end)) - intervals))))) + (unless layer-damage-only-p + (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) + (when (gethash (plist-get range :object) owner-set) + (when-let* ((new-start + (ebox-surface--rebase-coordinate + (plist-get range :start) coordinate-patches)) + (new-end + (ebox-surface--rebase-coordinate + (plist-get range :end) coordinate-patches))) + (push (list :old-start (plist-get range :start) + :old-end (plist-get range :end) + :new-start (car new-start) + :new-end (car new-end)) + intervals)))))) (setq intervals (sort intervals (lambda (left right) diff --git a/tests/ebox-layer-publication-tests.el b/tests/ebox-layer-publication-tests.el index 69d2c01..f01be47 100644 --- a/tests/ebox-layer-publication-tests.el +++ b/tests/ebox-layer-publication-tests.el @@ -219,6 +219,61 @@ including all text properties." (should-not (eq (nth 1 moved) (nth 1 first))) (should-not (eq (nth 2 moved) (nth 2 first))))) +(ert-deftest ebox-layer-publication-drag-publishes-only-damaged-rows () + "A distant layer move publishes two disjoint row patches. + +The retained compositor already limits recomposition to the old and new rows. +This regression checks the next boundary too: TP must receive two disjoint +content patches, so the untouched rows between them never enter a buffer +delete/insert operation." + (ebox-layer-publication-test--with-buffer + (let ((input + (ebox-build + '(column :id "root" :width (ch 12) :height (lh 8) + (box :id "before" "Before") + (box :id "host" :width (ch 8) :height (lh 5) + (box :id "base0" :width (ch 8) :height (lh 1) "BASE0000") + (box :id "base1" :width (ch 8) :height (lh 1) "BASE1111") + (box :id "base2" :width (ch 8) :height (lh 1) "BASE2222") + (box :id "base3" :width (ch 8) :height (lh 1) "BASE3333") + (box :id "base4" :width (ch 8) :height (lh 1) "BASE4444") + (box :id "upper" :position absolute :left (px 0) + :top (lh 0) :width (ch 8) :height (lh 1) + :z-index 1 "UPPER001")) + (box :id "after" "After"))))) + (ebox-render-to-buffer (current-buffer) input) + (let* ((before (buffer-string)) + (old-start (+ (point-min) (string-match "UPPER001" before))) + (new-start (+ (point-min) (string-match "BASE4444" before))) + (batch-patches nil) + (changes nil) + (apply-patches (symbol-function 'tp--commit-batch-apply-patches)) + (ranges (list (cons old-start (+ old-start 8)) + (cons new-start (+ new-start 8))))) + (add-hook 'before-change-functions + (lambda (beg end) (push (cons beg end) changes)) nil t) + (cl-letf (((symbol-function 'tp--commit-batch-apply-patches) + (lambda (surface batch) + (setq batch-patches (tp-commit-batch-patches batch)) + (funcall apply-patches surface batch)))) + (ebox-region-update "upper" :top '(lh 4))) + (should (equal + '((8 . 16) (48 . 56)) + (mapcar (lambda (patch) + (cons (plist-get patch :old-start) + (plist-get patch :old-end))) + batch-patches))) + (should changes) + (should + (cl-every + (lambda (change) + (cl-some (lambda (range) + (and (<= (car range) (car change)) + (<= (cdr change) (cdr range)))) + ranges)) + changes)) + (ebox-layer-publication-test--assert-fresh-render))))) + (ert-deftest ebox-layer-publication-rejected-hidden-commit-rolls-back () "Rejected hidden updates leave retained input, revision and text unchanged." (ebox-layer-publication-test--with-buffer