perf: retain surface state and layered paint slots
This commit is contained in:
parent
479ee8f466
commit
34b65b338f
@ -271,6 +271,12 @@ properties。
|
|||||||
result;
|
result;
|
||||||
- tp-surface-result-create-owned 只可对当前 active prepare context 使用,
|
- tp-surface-result-create-owned 只可对当前 active prepare context 使用,
|
||||||
结果只消费一次。
|
结果只消费一次。
|
||||||
|
- tp-surface-retained-content-result-create 用于对象拓扑不变的 content
|
||||||
|
candidate;最后一个可选 `property-contributions` 参数接受按顺序排列的
|
||||||
|
`(:start N :end N :props PLIST)` 相对范围。TP 使用已注册 property merge
|
||||||
|
policy 在 prepare 内组合这些贡献,再通过同一个 content surface 原子 diff、
|
||||||
|
发布和回滚;调用者不需要创建重叠的 properties surface,也不应预先压平
|
||||||
|
Theme/状态/inline face。
|
||||||
|
|
||||||
producer 接收一个 prepare context,并应返回 plan 或 result:
|
producer 接收一个 prepare context,并应返回 plan 或 result:
|
||||||
|
|
||||||
|
|||||||
@ -777,5 +777,22 @@
|
|||||||
(when (buffer-live-p buffer) (kill-buffer buffer))
|
(when (buffer-live-p buffer) (kill-buffer buffer))
|
||||||
(makunbound symbol)))))
|
(makunbound symbol)))))
|
||||||
|
|
||||||
|
(ert-deftest tp-binding-test-precomputed-keeps-explicit-edge-reactive ()
|
||||||
|
"A precomputed binding skips first compute and reacts through its edge."
|
||||||
|
(let* ((signal (tp-signal-create 1))
|
||||||
|
(parent (tp-bind 'parent 'value (lambda () (tp-signal-read signal))))
|
||||||
|
(runs 0)
|
||||||
|
(child
|
||||||
|
(tp-bind-precomputed
|
||||||
|
'child 'value
|
||||||
|
(lambda () (cl-incf runs) (1+ (tp-binding-read parent)))
|
||||||
|
2 (list parent))))
|
||||||
|
(should (= (tp-binding-read child) 2))
|
||||||
|
(should (= runs 0))
|
||||||
|
(should (= (tp-binding-dependency-count child) 1))
|
||||||
|
(tp-signal-set signal 4)
|
||||||
|
(should (= (tp-binding-read child) 5))
|
||||||
|
(should (= runs 1))))
|
||||||
|
|
||||||
(provide 'tp-binding-tests)
|
(provide 'tp-binding-tests)
|
||||||
;;; tp-binding-tests.el ends here
|
;;; tp-binding-tests.el ends here
|
||||||
|
|||||||
@ -252,6 +252,14 @@ tp-builtins restores the shipped layer definitions."
|
|||||||
(tp-builtins-test--with-background-mode 'dark
|
(tp-builtins-test--with-background-mode 'dark
|
||||||
(should (equal (tp-palette-color 'info :fg) "#58a6ff"))))
|
(should (equal (tp-palette-color 'info :fg) "#58a6ff"))))
|
||||||
|
|
||||||
|
(ert-deftest tp-builtins-test-palette-color-resolves-explicit-mode ()
|
||||||
|
"Resolve a palette for an application mode without reading the frame."
|
||||||
|
(should (equal "#0969da"
|
||||||
|
(tp-palette-color-for-mode 'info :fg 'light)))
|
||||||
|
(should (equal "#58a6ff"
|
||||||
|
(tp-palette-color-for-mode 'info :fg 'dark)))
|
||||||
|
(should-error (tp-palette-color-for-mode 'info :fg 'sepia)))
|
||||||
|
|
||||||
(ert-deftest tp-builtins-test-palette-has-p ()
|
(ert-deftest tp-builtins-test-palette-has-p ()
|
||||||
"tp-palette-has-p tests palette registration and per-key presence."
|
"tp-palette-has-p tests palette registration and per-key presence."
|
||||||
(should (tp-palette-has-p 'info))
|
(should (tp-palette-has-p 'info))
|
||||||
|
|||||||
@ -287,6 +287,17 @@
|
|||||||
(should-not (eq copy value))
|
(should-not (eq copy value))
|
||||||
(should (eq (cdr copy) copy)))))
|
(should (eq (cdr copy) copy)))))
|
||||||
|
|
||||||
|
(ert-deftest tp-core-test-public-property-value-copy-is-defensive ()
|
||||||
|
"The public copy boundary preserves opaque values and isolates containers."
|
||||||
|
(let* ((function (lambda () t))
|
||||||
|
(source (list :nested (vector "value") :function function))
|
||||||
|
(copy (tp-property-value-copy source)))
|
||||||
|
(should (equal copy source))
|
||||||
|
(should-not (eq copy source))
|
||||||
|
(should-not (eq (plist-get copy :nested)
|
||||||
|
(plist-get source :nested)))
|
||||||
|
(should (eq (plist-get copy :function) function))))
|
||||||
|
|
||||||
;;; API-COORD-01: ABSOLUTE coordinates in tp-intervals / tp-intervals-map
|
;;; API-COORD-01: ABSOLUTE coordinates in tp-intervals / tp-intervals-map
|
||||||
|
|
||||||
(ert-deftest tp-core-test-intervals-buffer-relative-default ()
|
(ert-deftest tp-core-test-intervals-buffer-relative-default ()
|
||||||
|
|||||||
@ -33,6 +33,33 @@
|
|||||||
:projector (lambda (value)
|
:projector (lambda (value)
|
||||||
(list 'face (list :foreground value)))))
|
(list 'face (list :foreground value)))))
|
||||||
|
|
||||||
|
(ert-deftest tp-style-test-paint-slot-keeps-one-face-address ()
|
||||||
|
"Updating a paint slot changes its face without rewriting text properties."
|
||||||
|
(let* ((slot (tp-paint-slot-create '(:foreground "red")))
|
||||||
|
(face (tp-paint-slot-face slot))
|
||||||
|
(text (propertize "x" 'face face))
|
||||||
|
(buffer (generate-new-buffer " *tp-paint-slot-test*")))
|
||||||
|
(should (facep face))
|
||||||
|
(should (eq face (get-text-property 0 'face text)))
|
||||||
|
(tp-paint-slot-update slot '(:foreground "red"))
|
||||||
|
(should (equal "red" (face-attribute face :foreground nil nil)))
|
||||||
|
(tp-paint-slot-update slot '(:foreground "blue"))
|
||||||
|
(should (eq face (get-text-property 0 'face text)))
|
||||||
|
(should (equal "blue" (face-attribute face :foreground nil nil)))
|
||||||
|
(unwind-protect
|
||||||
|
(let ((journal
|
||||||
|
(tp-paint-slot-apply-updates
|
||||||
|
buffer (list (cons slot '(:foreground "green"))))))
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(should
|
||||||
|
(equal '(:foreground "green")
|
||||||
|
(cadr (assq face face-remapping-alist)))))
|
||||||
|
(tp-paint-slot-rollback-updates journal)
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(should-not (assq face face-remapping-alist)))
|
||||||
|
(should (equal "blue" (face-attribute face :foreground nil nil))))
|
||||||
|
(kill-buffer buffer))))
|
||||||
|
|
||||||
(ert-deftest tp-style-test-policy-registration-is-atomic ()
|
(ert-deftest tp-style-test-policy-registration-is-atomic ()
|
||||||
"Invalid replacement leaves the previous valid policy installed."
|
"Invalid replacement leaves the previous valid policy installed."
|
||||||
(tp-style-test--isolated
|
(tp-style-test--isolated
|
||||||
@ -155,6 +182,12 @@
|
|||||||
(should (= compute-calls 1))
|
(should (= compute-calls 1))
|
||||||
(should (= result-calls 0))))))
|
(should (= result-calls 0))))))
|
||||||
|
|
||||||
|
(ert-deftest tp-style-test-computed-p-distinguishes-literal-functions ()
|
||||||
|
"Only explicit computed wrappers satisfy the public predicate."
|
||||||
|
(let ((function (lambda () "value")))
|
||||||
|
(should-not (tp-computed-p function))
|
||||||
|
(should (tp-computed-p (tp-computed function)))))
|
||||||
|
|
||||||
(ert-deftest tp-style-test-public-resolver-only-executes-computed-sources ()
|
(ert-deftest tp-style-test-public-resolver-only-executes-computed-sources ()
|
||||||
"The public resolver preserves literal functions and evaluates tags."
|
"The public resolver preserves literal functions and evaluates tags."
|
||||||
(let ((literal (lambda () 'literal))
|
(let ((literal (lambda () 'literal))
|
||||||
|
|||||||
@ -57,6 +57,211 @@
|
|||||||
(with-silent-modifications
|
(with-silent-modifications
|
||||||
(put-text-property beg end 'face 'corrupt))))
|
(put-text-property beg end 'face 'corrupt))))
|
||||||
|
|
||||||
|
(ert-deftest tp-surface-test-commit-batch-is-atomic-and-revision-bound ()
|
||||||
|
"A precomputed batch commits text/properties/state once and rolls back."
|
||||||
|
(tp-surface-test--with-buffer
|
||||||
|
(let* ((surface
|
||||||
|
(tp-surface-mount
|
||||||
|
(current-buffer)
|
||||||
|
(tp-surface-test--leaf 'root "abc" '(face bold))
|
||||||
|
'(:capability content)))
|
||||||
|
(replacement (propertize "XY" 'face 'italic))
|
||||||
|
(batch
|
||||||
|
(tp-commit-batch-create
|
||||||
|
:base-revision (tp-surface-revision surface)
|
||||||
|
:target-revision (1+ (tp-surface-revision surface))
|
||||||
|
:base-extent 3 :target-extent 4
|
||||||
|
:patches
|
||||||
|
(list (list :old-start 1 :old-end 2
|
||||||
|
:new-start 1 :new-end 3
|
||||||
|
:replacement replacement))
|
||||||
|
:coordinate-patches
|
||||||
|
(list (list :old-start 1 :old-end 2
|
||||||
|
:new-start 1 :new-end 3))
|
||||||
|
:client-state '(:value next))))
|
||||||
|
(tp-surface-commit-batch surface batch)
|
||||||
|
(should (equal "aXYc" (buffer-string)))
|
||||||
|
(should (eq 'italic (get-text-property 2 'face)))
|
||||||
|
(should (= 2 (tp-surface-revision surface)))
|
||||||
|
(should (equal '(:value next) (tp-surface-client-state surface)))
|
||||||
|
(should (plist-get (tp-surface-report surface) :commit-batch))
|
||||||
|
(let ((failing
|
||||||
|
(tp-commit-batch-create
|
||||||
|
:base-revision 2 :target-revision 3
|
||||||
|
:base-extent 4 :target-extent 4
|
||||||
|
:patches
|
||||||
|
(list (list :old-start 1 :old-end 3
|
||||||
|
:new-start 1 :new-end 3
|
||||||
|
:replacement "ZZ"))
|
||||||
|
:client-state '(:value rejected))))
|
||||||
|
(add-hook 'after-change-functions
|
||||||
|
#'tp-surface-test--corrupt-after-change nil t)
|
||||||
|
(setq-local tp-surface-test--corrupt-next t)
|
||||||
|
(unwind-protect
|
||||||
|
(should-error (tp-surface-commit-batch surface failing))
|
||||||
|
(remove-hook 'after-change-functions
|
||||||
|
#'tp-surface-test--corrupt-after-change t))
|
||||||
|
(should (equal "aXYc" (buffer-string)))
|
||||||
|
(should (= 2 (tp-surface-revision surface)))
|
||||||
|
(should (equal '(:value next)
|
||||||
|
(tp-surface-client-state surface))))
|
||||||
|
(should-error (tp-surface-commit-batch surface batch)))))
|
||||||
|
|
||||||
|
(ert-deftest tp-surface-test-producer-can-return-equal-coordinate-batch ()
|
||||||
|
"A retained producer can publish a strict batch through normal TP phases."
|
||||||
|
(tp-surface-test--with-buffer
|
||||||
|
(let* ((surface
|
||||||
|
(tp-surface-mount
|
||||||
|
(current-buffer) (tp-surface-test--leaf 'root "abc")
|
||||||
|
'(:capability content :coordinate-mounts t)))
|
||||||
|
root)
|
||||||
|
(maphash (lambda (_path object)
|
||||||
|
(unless (tp--surface-object-parent object)
|
||||||
|
(setq root object)))
|
||||||
|
(tp--surface-objects surface))
|
||||||
|
(let ((old-mounts (copy-sequence (tp--surface-mounts surface)))
|
||||||
|
(old-index (tp--surface-mount-index surface))
|
||||||
|
(batch
|
||||||
|
(tp-commit-batch-create
|
||||||
|
:base-revision 1 :target-revision 2
|
||||||
|
:base-extent 3 :target-extent 4
|
||||||
|
:patches
|
||||||
|
(list (list :old-start 1 :old-end 2
|
||||||
|
:new-start 1 :new-end 3
|
||||||
|
:replacement (propertize "XY" 'face 'bold)))
|
||||||
|
:coordinate-patches
|
||||||
|
(list (list :old-start 1 :old-end 2
|
||||||
|
:new-start 1 :new-end 3))
|
||||||
|
:client-state '(:batch committed))))
|
||||||
|
(tp-surface-update
|
||||||
|
surface
|
||||||
|
(lambda (context)
|
||||||
|
(tp-object-reuse-subtree context root)
|
||||||
|
(tp-commit-batch-result-create context batch)))
|
||||||
|
(should (equal "aXYc" (buffer-string)))
|
||||||
|
(should (eq 'bold (get-text-property 2 'face)))
|
||||||
|
(should (= 2 (tp-surface-revision surface)))
|
||||||
|
(should (equal '(:batch committed)
|
||||||
|
(tp-surface-client-state surface)))
|
||||||
|
(should (eq old-index (tp--surface-mount-index surface)))
|
||||||
|
(should (cl-every #'identity
|
||||||
|
(cl-mapcar #'eq old-mounts
|
||||||
|
(tp--surface-mounts surface))))
|
||||||
|
(should
|
||||||
|
(cl-every (lambda (mount)
|
||||||
|
(<= (tp--mount-position (tp--surface-mount-end mount))
|
||||||
|
(point-max)))
|
||||||
|
(tp--surface-mounts surface)))
|
||||||
|
(should (plist-get (tp-surface-report surface) :commit-batch))
|
||||||
|
(let ((before-mounts (tp-object-mounts root))
|
||||||
|
(before-live (copy-sequence (tp--surface-mounts surface)))
|
||||||
|
(failing
|
||||||
|
(tp-commit-batch-create
|
||||||
|
:base-revision 2 :target-revision 3
|
||||||
|
:base-extent 4 :target-extent 5
|
||||||
|
:patches
|
||||||
|
(list (list :old-start 1 :old-end 3
|
||||||
|
:new-start 1 :new-end 4
|
||||||
|
:replacement "XYZ"))
|
||||||
|
:coordinate-patches
|
||||||
|
(list (list :old-start 1 :old-end 3
|
||||||
|
:new-start 1 :new-end 4)))))
|
||||||
|
(let ((tp--surface-publication-step-function
|
||||||
|
(lambda (step _surface)
|
||||||
|
(when (eq step 'client-state)
|
||||||
|
(error "Injected retained mount rollback")))))
|
||||||
|
(should-error
|
||||||
|
(tp-surface-update
|
||||||
|
surface
|
||||||
|
(lambda (context)
|
||||||
|
(tp-object-reuse-subtree context root)
|
||||||
|
(tp-commit-batch-result-create context failing)))))
|
||||||
|
(should (equal "aXYc" (buffer-string)))
|
||||||
|
(should (= 2 (tp-surface-revision surface)))
|
||||||
|
(should (equal before-mounts (tp-object-mounts root)))
|
||||||
|
(should (cl-every #'identity
|
||||||
|
(cl-mapcar #'eq before-live
|
||||||
|
(tp--surface-mounts surface)))))))))
|
||||||
|
|
||||||
|
(ert-deftest tp-surface-test-commit-batch-promotes-exact-target-mounts ()
|
||||||
|
"A shrinking batch publishes producer-supplied target mounts atomically."
|
||||||
|
(tp-surface-test--with-buffer
|
||||||
|
(let ((owned-state (list :batch 'exact-mounts)) root logical)
|
||||||
|
(let* ((producer
|
||||||
|
(lambda (context)
|
||||||
|
(setq root (tp-object-ensure context nil 'root 'text)
|
||||||
|
logical
|
||||||
|
(tp-object-ensure context root 'logical 'item))
|
||||||
|
(tp-object-attach-content-range
|
||||||
|
context logical root 4 6 '(:slot old))
|
||||||
|
(tp-surface-test--leaf 'root "abcdef")))
|
||||||
|
(surface
|
||||||
|
(tp-surface-mount
|
||||||
|
buffer producer
|
||||||
|
'(:capability content :coordinate-mounts t))))
|
||||||
|
(tp-surface-update
|
||||||
|
surface
|
||||||
|
(lambda (context)
|
||||||
|
(tp-object-reuse-subtree context root)
|
||||||
|
(tp-commit-batch-result-create
|
||||||
|
context
|
||||||
|
(tp-commit-batch-create
|
||||||
|
:base-revision 1 :target-revision 2
|
||||||
|
:base-extent 6 :target-extent 2
|
||||||
|
:patches
|
||||||
|
(list (list :old-start 0 :old-end 6
|
||||||
|
:new-start 0 :new-end 2
|
||||||
|
:replacement "XY"))
|
||||||
|
:coordinate-patches
|
||||||
|
(list (list :old-start 0 :old-end 6
|
||||||
|
:new-start 0 :new-end 2)))
|
||||||
|
:mount-specs
|
||||||
|
(list (list :object root :start 0 :end 2 :tags nil)
|
||||||
|
(list :object logical :start 0 :end 2
|
||||||
|
:tags '(:slot new)))
|
||||||
|
:client-state owned-state)))
|
||||||
|
(should (equal (buffer-string) "XY"))
|
||||||
|
(should (= (tp-surface-revision surface) 2))
|
||||||
|
(should (eq (tp-surface-client-state surface) owned-state))
|
||||||
|
(should (plist-get (tp-surface-report surface) :commit-batch))
|
||||||
|
(should (equal (tp-object-mounts logical)
|
||||||
|
'((:start 1 :end 3 :tags (:slot new)))))))))
|
||||||
|
|
||||||
|
(ert-deftest tp-surface-test-commit-batch-reuses-proven-mount-projection ()
|
||||||
|
"A producer proof can retain mounts despite nonidentity text coordinates."
|
||||||
|
(tp-surface-test--with-buffer
|
||||||
|
(let* ((surface
|
||||||
|
(tp-surface-mount
|
||||||
|
buffer (tp-surface-test--leaf 'root "abcd")
|
||||||
|
'(:capability content :coordinate-mounts t)))
|
||||||
|
(root (tp-object-resolve surface '(root)))
|
||||||
|
(mounts (tp--surface-mounts surface))
|
||||||
|
(mount-index (tp--surface-mount-index surface))
|
||||||
|
(batch
|
||||||
|
(tp-commit-batch-create
|
||||||
|
:base-revision 1 :target-revision 2
|
||||||
|
:base-extent 4 :target-extent 4
|
||||||
|
:patches
|
||||||
|
(list (list :old-start 1 :old-end 3
|
||||||
|
:new-start 1 :new-end 3
|
||||||
|
:replacement "XY"))
|
||||||
|
:coordinate-patches
|
||||||
|
(list (list :old-start 1 :old-end 2
|
||||||
|
:new-start 1 :new-end 1)
|
||||||
|
(list :old-start 3 :old-end 3
|
||||||
|
:new-start 2 :new-end 3)))))
|
||||||
|
(tp-surface-update
|
||||||
|
surface
|
||||||
|
(lambda (context)
|
||||||
|
(tp-object-reuse-subtree context root)
|
||||||
|
(tp-commit-batch-result-create
|
||||||
|
context batch :reuse-mount-projection t)))
|
||||||
|
(should (equal (buffer-string) "aXYd"))
|
||||||
|
(should (eq mounts (tp--surface-mounts surface)))
|
||||||
|
(should (eq mount-index (tp--surface-mount-index surface)))
|
||||||
|
(should (plist-get (tp-surface-report surface)
|
||||||
|
:retained-mount-state)))))
|
||||||
|
|
||||||
(ert-deftest tp-surface-test-plan-validates-and-defensively-copies ()
|
(ert-deftest tp-surface-test-plan-validates-and-defensively-copies ()
|
||||||
"Plans reject duplicate keys and own their caller-provided values."
|
"Plans reject duplicate keys and own their caller-provided values."
|
||||||
(let* ((callback (byte-compile
|
(let* ((callback (byte-compile
|
||||||
@ -121,6 +326,110 @@
|
|||||||
(should (eq (get-text-property 0 'face rendered) 'bold)))
|
(should (eq (get-text-property 0 'face rendered) 'bold)))
|
||||||
(should (equal (tp-surface-plan-tags child) '(:role leaf)))))))
|
(should (equal (tp-surface-plan-tags child) '(:role leaf)))))))
|
||||||
|
|
||||||
|
(ert-deftest tp-surface-test-retained-content-composes-property-contributions ()
|
||||||
|
"A content surface should layer property contributions without text work."
|
||||||
|
(tp-surface-test--with-buffer
|
||||||
|
(let* ((plan
|
||||||
|
(tp-surface-plan-create
|
||||||
|
:key 'root :kind 'group :capability 'content
|
||||||
|
:children
|
||||||
|
(list
|
||||||
|
(tp-surface-plan-create
|
||||||
|
:key 'fragments :kind 'group :capability 'content
|
||||||
|
:children (list (tp-surface-test--leaf 'text "abcd"))))))
|
||||||
|
(surface (tp-surface-mount buffer plan '(:capability content)))
|
||||||
|
(producer
|
||||||
|
(lambda (contributions)
|
||||||
|
(lambda (context)
|
||||||
|
(let* ((root (tp-object-ensure context nil 'root 'group))
|
||||||
|
(fragments
|
||||||
|
(tp-object-ensure context root 'fragments 'group))
|
||||||
|
(_text
|
||||||
|
(tp-object-ensure context fragments 'text 'text))
|
||||||
|
(owned-plan
|
||||||
|
(tp-surface-plan-create-owned
|
||||||
|
:key 'root :kind 'group :capability 'content
|
||||||
|
:children
|
||||||
|
(list
|
||||||
|
(tp-surface-plan-create-owned
|
||||||
|
:key 'fragments :kind 'group :capability 'content
|
||||||
|
:children
|
||||||
|
(list
|
||||||
|
(tp-surface-plan-create-owned
|
||||||
|
:key 'text :kind 'text :text "abcd"
|
||||||
|
:capability 'content)))))))
|
||||||
|
(tp-surface-retained-content-result-create
|
||||||
|
context owned-plan "abcd" nil nil t contributions)))))
|
||||||
|
(base
|
||||||
|
(list :start 0 :end 4
|
||||||
|
:props '(face (:foreground "white"
|
||||||
|
:background "black"))))
|
||||||
|
(specific
|
||||||
|
(list :start 1 :end 3
|
||||||
|
:props '(face (:foreground "red")))))
|
||||||
|
(tp-surface-update surface (funcall producer (list base specific)))
|
||||||
|
(should (equal (buffer-string) "abcd"))
|
||||||
|
(let ((outer (get-text-property 1 'face))
|
||||||
|
(inner (get-text-property 2 'face)))
|
||||||
|
(should (equal (plist-get outer :foreground) "white"))
|
||||||
|
(should (equal (plist-get outer :background) "black"))
|
||||||
|
(should (equal (plist-get inner :foreground) "red"))
|
||||||
|
(should (equal (plist-get inner :background) "black")))
|
||||||
|
(let* ((next-base
|
||||||
|
(list :start 0 :end 4
|
||||||
|
:props '(face (:foreground "#EEEEEE"
|
||||||
|
:background "#111111"))))
|
||||||
|
(report
|
||||||
|
(tp-surface-update
|
||||||
|
surface (funcall producer (list next-base specific)))))
|
||||||
|
(should (equal (buffer-string) "abcd"))
|
||||||
|
(should (= 0 (plist-get report :text-operations)))
|
||||||
|
(should (> (plist-get report :property-operations) 0))
|
||||||
|
(let ((outer (get-text-property 1 'face))
|
||||||
|
(inner (get-text-property 2 'face)))
|
||||||
|
(should (equal (plist-get outer :foreground) "#EEEEEE"))
|
||||||
|
(should (equal (plist-get outer :background) "#111111"))
|
||||||
|
(should (equal (plist-get inner :foreground) "red"))
|
||||||
|
(should (equal (plist-get inner :background) "#111111")))))))
|
||||||
|
|
||||||
|
(ert-deftest tp-surface-test-retained-content-rejects-invalid-contribution ()
|
||||||
|
"Invalid contribution ranges must fail before mutating published content."
|
||||||
|
(tp-surface-test--with-buffer
|
||||||
|
(let* ((plan
|
||||||
|
(tp-surface-plan-create
|
||||||
|
:key 'root :kind 'group :capability 'content
|
||||||
|
:children
|
||||||
|
(list
|
||||||
|
(tp-surface-plan-create
|
||||||
|
:key 'fragments :kind 'group :capability 'content
|
||||||
|
:children (list (tp-surface-test--leaf 'text "safe"))))))
|
||||||
|
(surface (tp-surface-mount buffer plan '(:capability content))))
|
||||||
|
(should-error
|
||||||
|
(tp-surface-update
|
||||||
|
surface
|
||||||
|
(lambda (context)
|
||||||
|
(let* ((root (tp-object-ensure context nil 'root 'group))
|
||||||
|
(fragments
|
||||||
|
(tp-object-ensure context root 'fragments 'group))
|
||||||
|
(_text (tp-object-ensure context fragments 'text 'text))
|
||||||
|
(owned-plan
|
||||||
|
(tp-surface-plan-create-owned
|
||||||
|
:key 'root :kind 'group :capability 'content
|
||||||
|
:children
|
||||||
|
(list
|
||||||
|
(tp-surface-plan-create-owned
|
||||||
|
:key 'fragments :kind 'group :capability 'content
|
||||||
|
:children
|
||||||
|
(list
|
||||||
|
(tp-surface-plan-create-owned
|
||||||
|
:key 'text :kind 'text :text "safe"
|
||||||
|
:capability 'content)))))))
|
||||||
|
(tp-surface-retained-content-result-create
|
||||||
|
context owned-plan "safe" nil nil t
|
||||||
|
'((:start 0 :end 9 :props (face bold)))))))
|
||||||
|
:type 'tp-invalid-content-range)
|
||||||
|
(should (equal (buffer-string) "safe")))))
|
||||||
|
|
||||||
(ert-deftest tp-surface-test-owned-plan-result-transfers-candidate-tree ()
|
(ert-deftest tp-surface-test-owned-plan-result-transfers-candidate-tree ()
|
||||||
"Owned plan and result constructors skip a duplicate candidate snapshot."
|
"Owned plan and result constructors skip a duplicate candidate snapshot."
|
||||||
(tp-surface-test--with-buffer
|
(tp-surface-test--with-buffer
|
||||||
@ -1227,6 +1536,60 @@
|
|||||||
(should (tp-binding-live-p binding))
|
(should (tp-binding-live-p binding))
|
||||||
(should (equal (buffer-string) "after")))))))
|
(should (equal (buffer-string) "after")))))))
|
||||||
|
|
||||||
|
(ert-deftest tp-surface-test-reuse-one-preserves-only-proven-object ()
|
||||||
|
"Single-object reuse retains that identity without retaining a removed peer."
|
||||||
|
(tp-surface-test--with-buffer
|
||||||
|
(let ((value "before") kept removed)
|
||||||
|
(cl-labels
|
||||||
|
((producer (context)
|
||||||
|
(let ((root (tp-object-ensure context nil 'root 'group)))
|
||||||
|
(if kept
|
||||||
|
(tp-object-reuse context kept)
|
||||||
|
(setq kept (tp-object-ensure context root 'kept 'item)
|
||||||
|
removed (tp-object-ensure context root 'removed 'item))
|
||||||
|
(tp-object-retain context kept)
|
||||||
|
(tp-object-retain context removed))
|
||||||
|
(tp-object-ensure context root 'text 'text))
|
||||||
|
(tp-surface-plan-create
|
||||||
|
:key 'root :kind 'group :capability 'content
|
||||||
|
:children (list (tp-surface-test--leaf 'text value)))))
|
||||||
|
(let ((surface (tp-surface-mount
|
||||||
|
buffer #'producer '(:capability content))))
|
||||||
|
(setq value "after")
|
||||||
|
(tp-surface-update surface #'producer)
|
||||||
|
(should (eq kept (tp-object-resolve surface '(root kept))))
|
||||||
|
(should-not (tp-object-live-p removed))
|
||||||
|
(should (equal (buffer-string) "after")))))))
|
||||||
|
|
||||||
|
(ert-deftest tp-surface-test-ensure-at-reuses-anonymous-slot-without-prefix-replay ()
|
||||||
|
"Compiled topology can address one anonymous slot directly."
|
||||||
|
(tp-surface-test--with-buffer
|
||||||
|
(let (second)
|
||||||
|
(cl-labels
|
||||||
|
((producer (context)
|
||||||
|
(let ((root (tp-object-ensure context nil 'root 'group)))
|
||||||
|
(if second
|
||||||
|
(progn
|
||||||
|
(tp-object-reuse
|
||||||
|
context
|
||||||
|
(tp-object-resolve
|
||||||
|
(tp--context-surface context)
|
||||||
|
'(root (:position 0 :kind item))))
|
||||||
|
(setq second
|
||||||
|
(tp-object-ensure-at context root nil 'item 1)))
|
||||||
|
(tp-object-retain
|
||||||
|
context (tp-object-ensure context root nil 'item))
|
||||||
|
(setq second (tp-object-ensure context root nil 'item))
|
||||||
|
(tp-object-retain context second))
|
||||||
|
(tp-object-retain context second))
|
||||||
|
(tp-surface-plan-create
|
||||||
|
:key 'root :kind 'group :capability 'content)))
|
||||||
|
(let ((surface (tp-surface-mount
|
||||||
|
buffer #'producer '(:capability content)))
|
||||||
|
(identity second))
|
||||||
|
(tp-surface-update surface #'producer)
|
||||||
|
(should (eq identity second)))))))
|
||||||
|
|
||||||
(ert-deftest tp-surface-test-logical-object-owns-leaf-local-ranges ()
|
(ert-deftest tp-surface-test-logical-object-owns-leaf-local-ranges ()
|
||||||
"One logical object can own multiple ranges in one content leaf."
|
"One logical object can own multiple ranges in one content leaf."
|
||||||
(tp-surface-test--with-buffer
|
(tp-surface-test--with-buffer
|
||||||
|
|||||||
@ -425,6 +425,12 @@ and other opaque objects keep their identity; functions are never executed."
|
|||||||
(tp--copy-property-value (aref copy index) cache)))
|
(tp--copy-property-value (aref copy index) cache)))
|
||||||
copy)))))))
|
copy)))))))
|
||||||
|
|
||||||
|
(defun tp-property-value-copy (value)
|
||||||
|
"Return a defensive copy of mutable text-property VALUE.
|
||||||
|
Functions, records, and other opaque identities are retained; mutable cons,
|
||||||
|
string, and non-record vector graphs are copied with sharing and cycles intact."
|
||||||
|
(tp--copy-property-value value (make-hash-table :test #'eq)))
|
||||||
|
|
||||||
(defun tp--deep-merge-plist (base new)
|
(defun tp--deep-merge-plist (base new)
|
||||||
"Deep merge NEW plist into BASE plist.
|
"Deep merge NEW plist into BASE plist.
|
||||||
For nested plists (starting with keyword), recursively merge.
|
For nested plists (starting with keyword), recursively merge.
|
||||||
|
|||||||
@ -261,45 +261,43 @@ documentation of NAME and PLIST.")
|
|||||||
"Return non-nil when the current frame's background mode is light."
|
"Return non-nil when the current frame's background mode is light."
|
||||||
(eq (frame-parameter nil 'background-mode) 'light))
|
(eq (frame-parameter nil 'background-mode) 'light))
|
||||||
|
|
||||||
(defun tp-parse-color (color)
|
(defun tp-parse-color (color &optional mode)
|
||||||
"Resolve COLOR to a color string for the current theme.
|
"Resolve COLOR to a color string for MODE or the current frame theme.
|
||||||
COLOR may be:
|
COLOR may be:
|
||||||
- a color string, returned as is: \"red\"
|
- a color string, returned as is: \"red\"
|
||||||
- a (LIGHT . DARK) cons: (\"red\" . \"green\"); either side may be
|
- a (LIGHT . DARK) cons: (\"red\" . \"green\"); either side may be
|
||||||
nil, meaning no color for that mode
|
nil, meaning no color for that mode
|
||||||
- a (:light LIGHT :dark DARK) plist: (:light \"red\" :dark \"green\")
|
- a (:light LIGHT :dark DARK) plist: (:light \"red\" :dark \"green\")
|
||||||
Return nil when COLOR is nil, or when the side selected by the
|
MODE may be `light' or `dark'. When MODE is nil, use the current
|
||||||
current theme is nil. When the theme cannot be determined, fall
|
frame's background mode. Return nil when COLOR is nil, or when the
|
||||||
back to the light color."
|
selected side is nil. An unknown frame mode falls back to `light'."
|
||||||
(cond ((stringp color) color)
|
(let ((mode (or mode (if (tp-theme-dark-p) 'dark 'light))))
|
||||||
|
(unless (memq mode '(light dark))
|
||||||
|
(signal 'wrong-type-argument (list '(member light dark) mode)))
|
||||||
|
(cond ((stringp color) color)
|
||||||
((and (consp color)
|
((and (consp color)
|
||||||
(or (stringp (car color)) (null (car color)))
|
(or (stringp (car color)) (null (car color)))
|
||||||
(or (stringp (cdr color)) (null (cdr color))))
|
(or (stringp (cdr color)) (null (cdr color))))
|
||||||
(cond
|
(if (eq mode 'dark) (cdr color) (car color)))
|
||||||
((tp-theme-light-p) (car color))
|
|
||||||
((tp-theme-dark-p) (cdr color))
|
|
||||||
;; Default to light color when background-mode is unknown
|
|
||||||
(t (car color))))
|
|
||||||
((and (tp-palette--plistp color)
|
((and (tp-palette--plistp color)
|
||||||
(or (plist-member color :light)
|
(or (plist-member color :light)
|
||||||
(plist-member color :dark)))
|
(plist-member color :dark)))
|
||||||
(cond
|
(if (eq mode 'dark)
|
||||||
((tp-theme-light-p) (plist-get color :light))
|
(plist-get color :dark)
|
||||||
((tp-theme-dark-p) (plist-get color :dark))
|
(plist-get color :light)))
|
||||||
;; Default to light color when background-mode is unknown
|
|
||||||
(t (plist-get color :light))))
|
|
||||||
((null color) nil)
|
((null color) nil)
|
||||||
(t (error "Invalid format of color %S" color))))
|
(t (error "Invalid format of color %S" color)))))
|
||||||
|
|
||||||
(defun tp-palette--get-color (symbol key)
|
(defun tp-palette--get-color (symbol key &optional mode)
|
||||||
"Get color value for KEY from the palette named SYMBOL.
|
"Get color value for KEY from palette SYMBOL, resolving MODE.
|
||||||
SYMBOL is looked up in `tp-palette-alist'. KEY should be one of
|
SYMBOL is looked up in `tp-palette-alist'. KEY should be one of
|
||||||
:fg, :bg, or :border. Return nil if SYMBOL names no registered
|
:fg, :bg, or :border. Return nil if SYMBOL names no registered
|
||||||
palette or its definition doesn't contain KEY.
|
palette or its definition doesn't contain KEY.
|
||||||
The public entry point delegating here is `tp-palette-color'."
|
The public entry points are `tp-palette-color' and
|
||||||
|
`tp-palette-color-for-mode'."
|
||||||
(let ((plist (alist-get symbol tp-palette-alist)))
|
(let ((plist (alist-get symbol tp-palette-alist)))
|
||||||
(when (tp-palette--plistp plist)
|
(when (tp-palette--plistp plist)
|
||||||
(tp-parse-color (plist-get plist key)))))
|
(tp-parse-color (plist-get plist key) mode))))
|
||||||
|
|
||||||
(defun tp-palette-color (symbol key)
|
(defun tp-palette-color (symbol key)
|
||||||
"Return the KEY color of the palette named SYMBOL, theme-resolved.
|
"Return the KEY color of the palette named SYMBOL, theme-resolved.
|
||||||
@ -317,6 +315,14 @@ conveniences equivalent to calling it with a fixed KEY. See also
|
|||||||
color."
|
color."
|
||||||
(tp-palette--get-color symbol key))
|
(tp-palette--get-color symbol key))
|
||||||
|
|
||||||
|
(defun tp-palette-color-for-mode (symbol key mode)
|
||||||
|
"Return palette SYMBOL KEY resolved explicitly for MODE.
|
||||||
|
MODE must be `light' or `dark'. Unlike `tp-palette-color', this
|
||||||
|
function does not inspect the selected frame, so a higher-level
|
||||||
|
application Theme can switch palettes without changing the Emacs
|
||||||
|
frame's own background mode."
|
||||||
|
(tp-palette--get-color symbol key mode))
|
||||||
|
|
||||||
(defun tp-palette-has-p (symbol &optional kind)
|
(defun tp-palette-has-p (symbol &optional kind)
|
||||||
"Return non-nil when KIND is available in SYMBOL's palette.
|
"Return non-nil when KIND is available in SYMBOL's palette.
|
||||||
With nil KIND, test only that SYMBOL names a palette registered in
|
With nil KIND, test only that SYMBOL names a palette registered in
|
||||||
|
|||||||
@ -490,6 +490,55 @@ or `retain'."
|
|||||||
(lambda () (tp--bind-in-transaction
|
(lambda () (tp--bind-in-transaction
|
||||||
owner key compute equality lifecycle)))))
|
owner key compute equality lifecycle)))))
|
||||||
|
|
||||||
|
(defun tp--bind-precomputed-in-transaction
|
||||||
|
(owner key compute value dependencies equality lifecycle)
|
||||||
|
"Install one new initialized binding with explicit DEPENDENCIES."
|
||||||
|
(let ((table (tp--owner-binding-table owner t)))
|
||||||
|
(when (gethash key table)
|
||||||
|
(signal 'tp-reactive-error (list :precomputed-binding-exists key)))
|
||||||
|
(dolist (dependency dependencies)
|
||||||
|
(cond ((tp-signal-p dependency) (tp--validate-live-signal dependency))
|
||||||
|
((tp-binding-p dependency) (tp--validate-live-binding dependency))
|
||||||
|
(t (signal 'wrong-type-argument
|
||||||
|
(list '(or tp-signal-p tp-binding-p) dependency)))))
|
||||||
|
(let ((binding
|
||||||
|
(tp--make-binding
|
||||||
|
:id (cl-incf tp--binding-id-counter)
|
||||||
|
:owner owner :key (tp--copy-property-value key) :compute compute
|
||||||
|
:equality equality :last-value value :initialized-p t
|
||||||
|
:dependencies (copy-sequence dependencies)
|
||||||
|
:subscribers (make-hash-table :test #'eq)
|
||||||
|
:dirty nil :state 'clean :revision 1 :lifecycle lifecycle)))
|
||||||
|
(tp--register-binding binding)
|
||||||
|
(push binding tp--transaction-created-bindings)
|
||||||
|
(dolist (dependency dependencies)
|
||||||
|
(tp--subscription-add dependency binding))
|
||||||
|
(when tp--binding-touch-function
|
||||||
|
(funcall tp--binding-touch-function binding))
|
||||||
|
binding)))
|
||||||
|
|
||||||
|
(cl-defun tp-bind-precomputed
|
||||||
|
(owner key compute value dependencies
|
||||||
|
&key (equality #'equal) (lifecycle 'delete))
|
||||||
|
"Install a new binding with precomputed VALUE and explicit DEPENDENCIES.
|
||||||
|
COMPUTE remains the authoritative recomputation function after any dependency
|
||||||
|
changes. This entry avoids evaluating COMPUTE merely to rediscover a value
|
||||||
|
and graph edges already produced by a compiler or pure projection pass."
|
||||||
|
(when (null owner)
|
||||||
|
(signal 'tp-reactive-error (list :owner owner)))
|
||||||
|
(when (null key)
|
||||||
|
(signal 'tp-reactive-error (list :binding-key key)))
|
||||||
|
(tp--validate-binding-options compute equality lifecycle)
|
||||||
|
(unless (proper-list-p dependencies)
|
||||||
|
(signal 'wrong-type-argument (list 'proper-list-p dependencies)))
|
||||||
|
(if tp--transaction-active
|
||||||
|
(tp--bind-precomputed-in-transaction
|
||||||
|
owner key compute value dependencies equality lifecycle)
|
||||||
|
(tp--call-with-transaction
|
||||||
|
(lambda ()
|
||||||
|
(tp--bind-precomputed-in-transaction
|
||||||
|
owner key compute value dependencies equality lifecycle)))))
|
||||||
|
|
||||||
(defun tp--detach-binding-dependencies (binding)
|
(defun tp--detach-binding-dependencies (binding)
|
||||||
"Remove BINDING from all dependency subscriber tables."
|
"Remove BINDING from all dependency subscriber tables."
|
||||||
(dolist (dependency (tp-binding-dependencies binding))
|
(dolist (dependency (tp-binding-dependencies binding))
|
||||||
|
|||||||
97
tp-style.el
97
tp-style.el
@ -28,6 +28,13 @@
|
|||||||
(cl-defstruct (tp--computed-source (:constructor tp--make-computed-source))
|
(cl-defstruct (tp--computed-source (:constructor tp--make-computed-source))
|
||||||
function)
|
function)
|
||||||
|
|
||||||
|
(cl-defstruct (tp-paint-slot (:constructor tp--make-paint-slot))
|
||||||
|
"Stable named-face address for one mutable paint contribution."
|
||||||
|
face spec installed-p)
|
||||||
|
|
||||||
|
(defvar tp--paint-slot-counter 0
|
||||||
|
"Monotonic id source for private paint-slot faces.")
|
||||||
|
|
||||||
(defconst tp--property-policy-option-keys
|
(defconst tp--property-policy-option-keys
|
||||||
'(:normalizer :validator :equality :merge :projector)
|
'(:normalizer :validator :equality :merge :projector)
|
||||||
"Accepted property policy option keys.")
|
"Accepted property policy option keys.")
|
||||||
@ -41,6 +48,92 @@
|
|||||||
(defvar tp--named-styles (make-hash-table :test #'eq)
|
(defvar tp--named-styles (make-hash-table :test #'eq)
|
||||||
"Named direct declaration sets.")
|
"Named direct declaration sets.")
|
||||||
|
|
||||||
|
(defun tp--paint-slot-face-spec (spec)
|
||||||
|
"Return validated face SPEC for a paint slot."
|
||||||
|
(unless (and (listp spec) (zerop (% (length spec) 2))
|
||||||
|
(cl-loop for key in spec by #'cddr always (keywordp key)))
|
||||||
|
(signal 'tp-invalid-declaration (list :paint-slot spec)))
|
||||||
|
(copy-tree spec))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun tp-paint-slot-create (spec)
|
||||||
|
"Create a stable paint slot initialized from anonymous face SPEC."
|
||||||
|
(let* ((face (intern (format "tp-paint-slot-%d"
|
||||||
|
(cl-incf tp--paint-slot-counter))))
|
||||||
|
(slot (tp--make-paint-slot
|
||||||
|
:face face :spec (tp--paint-slot-face-spec spec)
|
||||||
|
:installed-p nil)))
|
||||||
|
(make-face face)
|
||||||
|
slot))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun tp-paint-slot-update (slot spec)
|
||||||
|
"Update SLOT to anonymous face SPEC without changing its address."
|
||||||
|
(unless (tp-paint-slot-p slot)
|
||||||
|
(signal 'wrong-type-argument (list 'tp-paint-slot-p slot)))
|
||||||
|
(let* ((face (tp-paint-slot-face slot))
|
||||||
|
(next (tp--paint-slot-face-spec spec))
|
||||||
|
(previous (tp-paint-slot-spec slot)))
|
||||||
|
(condition-case err
|
||||||
|
(progn
|
||||||
|
(face-spec-reset-face face)
|
||||||
|
(when next
|
||||||
|
(face-spec-set face `((t ,next))))
|
||||||
|
(setf (tp-paint-slot-spec slot) next))
|
||||||
|
(error
|
||||||
|
(face-spec-reset-face face)
|
||||||
|
(when previous
|
||||||
|
(face-spec-set face `((t ,previous))))
|
||||||
|
(signal (car err) (cdr err))))
|
||||||
|
slot))
|
||||||
|
|
||||||
|
(defun tp-paint-slot-apply-updates (buffer updates)
|
||||||
|
"Apply `(SLOT . SPEC)' UPDATES to BUFFER in one face-remap swap.
|
||||||
|
Return a rollback journal containing the previous buffer map and slot specs."
|
||||||
|
(unless (buffer-live-p buffer)
|
||||||
|
(signal 'wrong-type-argument (list 'buffer-live-p buffer)))
|
||||||
|
(let ((normalized
|
||||||
|
(mapcar
|
||||||
|
(lambda (entry)
|
||||||
|
(unless (tp-paint-slot-p (car entry))
|
||||||
|
(signal 'wrong-type-argument
|
||||||
|
(list 'tp-paint-slot-p (car entry))))
|
||||||
|
(cons (car entry) (tp--paint-slot-face-spec (cdr entry))))
|
||||||
|
updates))
|
||||||
|
slot-journal next)
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(setq next (copy-tree face-remapping-alist))
|
||||||
|
(dolist (entry normalized)
|
||||||
|
(let* ((slot (car entry))
|
||||||
|
(spec (cdr entry))
|
||||||
|
(face (tp-paint-slot-face slot)))
|
||||||
|
(push (list slot (copy-tree (tp-paint-slot-spec slot))
|
||||||
|
(tp-paint-slot-installed-p slot))
|
||||||
|
slot-journal)
|
||||||
|
(setq next (assq-delete-all face next))
|
||||||
|
(when spec
|
||||||
|
(push (list face spec) next))
|
||||||
|
(setf (tp-paint-slot-spec slot) spec
|
||||||
|
(tp-paint-slot-installed-p slot) t)))
|
||||||
|
(prog1
|
||||||
|
(list :buffer buffer
|
||||||
|
:face-remapping-alist (copy-tree face-remapping-alist)
|
||||||
|
:slot-specs (nreverse slot-journal))
|
||||||
|
(setq-local face-remapping-alist next)))))
|
||||||
|
|
||||||
|
(defun tp-paint-slot-rollback-updates (journal)
|
||||||
|
"Restore paint slots from reverse-safe JOURNAL."
|
||||||
|
(when-let ((buffer (plist-get journal :buffer)))
|
||||||
|
(when (buffer-live-p buffer)
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(setq-local face-remapping-alist
|
||||||
|
(copy-tree
|
||||||
|
(plist-get journal :face-remapping-alist))))))
|
||||||
|
(dolist (entry (reverse (plist-get journal :slot-specs)))
|
||||||
|
(setf (tp-paint-slot-spec (nth 0 entry)) (copy-tree (nth 1 entry))
|
||||||
|
(tp-paint-slot-installed-p (nth 0 entry)) (nth 2 entry)))
|
||||||
|
nil)
|
||||||
|
|
||||||
(defun tp--canonical-property-id-p (id)
|
(defun tp--canonical-property-id-p (id)
|
||||||
"Return non-nil when ID is a namespaced property symbol."
|
"Return non-nil when ID is a namespaced property symbol."
|
||||||
(and (symbolp id)
|
(and (symbolp id)
|
||||||
@ -142,6 +235,10 @@ OPTIONS support :normalizer, :validator, :equality, :merge, and :projector."
|
|||||||
(signal 'wrong-type-argument (list 'functionp function)))
|
(signal 'wrong-type-argument (list 'functionp function)))
|
||||||
(tp--make-computed-source :function function))
|
(tp--make-computed-source :function function))
|
||||||
|
|
||||||
|
(defun tp-computed-p (value)
|
||||||
|
"Return non-nil when VALUE is an explicit computed source."
|
||||||
|
(tp--computed-source-p value))
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defun tp-resolve-value (value &optional _property _subject)
|
(defun tp-resolve-value (value &optional _property _subject)
|
||||||
"Resolve VALUE only when it is an explicit `tp-computed' source.
|
"Resolve VALUE only when it is an explicit `tp-computed' source.
|
||||||
|
|||||||
751
tp-surface.el
751
tp-surface.el
@ -74,10 +74,23 @@ owned result is bound to OWNER-CONTEXT and consumed once during prepare."
|
|||||||
(:constructor tp--make-retained-content-candidate))
|
(:constructor tp--make-retained-content-candidate))
|
||||||
"Candidate for a content-only update on an unchanged TP object tree.
|
"Candidate for a content-only update on an unchanged TP object tree.
|
||||||
PLAN is the candidate's unchanged object topology with a new text leaf,
|
PLAN is the candidate's unchanged object topology with a new text leaf,
|
||||||
RENDERED is the already-propertized candidate text, and RANGES are the
|
RENDERED is the candidate text, RANGES are the candidate-owned content-range
|
||||||
candidate-owned content-range attachments. CONTEXT authenticates the
|
attachments, and PROPERTY-CONTRIBUTIONS are ordered relative range property
|
||||||
one-shot producer result to the active prepare transaction."
|
layers composed by TP during prepare. CONTEXT authenticates the one-shot
|
||||||
plan rendered ranges client-state context full-surface-p consumed-p)
|
producer result to the active prepare transaction."
|
||||||
|
plan rendered ranges property-contributions client-state context
|
||||||
|
full-surface-p consumed-p)
|
||||||
|
|
||||||
|
(cl-defstruct (tp-commit-batch (:constructor tp--make-commit-batch))
|
||||||
|
"One precomputed content commit for a stable retained surface topology."
|
||||||
|
base-revision target-revision base-extent target-extent patches
|
||||||
|
coordinate-patches client-state)
|
||||||
|
|
||||||
|
(cl-defstruct (tp-commit-batch-candidate
|
||||||
|
(:constructor tp--make-commit-batch-candidate))
|
||||||
|
"One prepare-context-authenticated precomputed commit result."
|
||||||
|
batch context mount-specs exact-mount-specs-p client-state
|
||||||
|
exact-client-state-p reuse-mount-projection-p consumed-p)
|
||||||
|
|
||||||
(cl-defstruct (tp-surface
|
(cl-defstruct (tp-surface
|
||||||
(:constructor tp--make-surface)
|
(:constructor tp--make-surface)
|
||||||
@ -122,7 +135,8 @@ one-shot producer result to the active prepare transaction."
|
|||||||
surface context plan rendered mount-specs ledger-specs property-operations
|
surface context plan rendered mount-specs ledger-specs property-operations
|
||||||
objects client-state producer initial created removed moved reconciled
|
objects client-state producer initial created removed moved reconciled
|
||||||
scope-objects scope-patches scope-fallback live-mounts live-mount-index
|
scope-objects scope-patches scope-fallback live-mounts live-mount-index
|
||||||
live-ledger report retained-content-p)
|
live-ledger report retained-content-p commit-batch
|
||||||
|
retained-mount-state-p mount-coordinate-updates mount-coordinate-undo)
|
||||||
|
|
||||||
(cl-defstruct (tp--surface-snapshot (:constructor tp--make-surface-snapshot))
|
(cl-defstruct (tp--surface-snapshot (:constructor tp--make-surface-snapshot))
|
||||||
plan objects mounts index mount-index ledger client-state producer revision
|
plan objects mounts index mount-index ledger client-state producer revision
|
||||||
@ -261,14 +275,17 @@ Use `tp-surface-result-create' for ordinary caller-owned plans."
|
|||||||
(tp--validate-surface-plan-tree plan) client-state t context))
|
(tp--validate-surface-plan-tree plan) client-state t context))
|
||||||
|
|
||||||
(defun tp-surface-retained-content-result-create
|
(defun tp-surface-retained-content-result-create
|
||||||
(context plan rendered ranges &optional client-state full-surface-p)
|
(context plan rendered ranges &optional client-state full-surface-p
|
||||||
|
property-contributions)
|
||||||
"Create a one-shot retained content candidate for CONTEXT.
|
"Create a one-shot retained content candidate for CONTEXT.
|
||||||
PLAN must preserve the committed TP object topology and contain the new text
|
PLAN must preserve the committed TP object topology and contain the new text
|
||||||
leaf. RENDERED is the final propertized text and RANGES are candidate-local
|
leaf. RENDERED is the final propertized text and RANGES are candidate-local
|
||||||
content attachments already associated with PLAN's text leaf. This entry
|
content attachments already associated with PLAN's text leaf.
|
||||||
point is intentionally narrow: callers must prove that object topology is
|
PROPERTY-CONTRIBUTIONS is an ordered list of relative `:start', `:end', and
|
||||||
unchanged; TP still validates every object, range, scope, and publication
|
`:props' plists. TP composes them over RENDERED using registered property
|
||||||
phase before accepting the candidate."
|
merge policy before diff and publication. This entry point is intentionally
|
||||||
|
narrow: callers must prove that object topology is unchanged; TP still
|
||||||
|
validates every object, range, scope, and publication phase."
|
||||||
(tp--validate-prepare-context context)
|
(tp--validate-prepare-context context)
|
||||||
(unless (eq context tp--current-prepare-context)
|
(unless (eq context tp--current-prepare-context)
|
||||||
(signal 'tp-owned-result-error
|
(signal 'tp-owned-result-error
|
||||||
@ -279,8 +296,53 @@ phase before accepting the candidate."
|
|||||||
(signal 'tp-invalid-surface-plan
|
(signal 'tp-invalid-surface-plan
|
||||||
(list :retained-content plan rendered ranges)))
|
(list :retained-content plan rendered ranges)))
|
||||||
(tp--make-retained-content-candidate
|
(tp--make-retained-content-candidate
|
||||||
:plan plan :rendered rendered :ranges ranges :client-state client-state
|
:plan plan :rendered rendered :ranges ranges
|
||||||
:context context :full-surface-p full-surface-p))
|
:property-contributions property-contributions
|
||||||
|
:client-state client-state :context context
|
||||||
|
:full-surface-p full-surface-p))
|
||||||
|
|
||||||
|
(defun tp--compose-relative-property-contributions (rendered contributions)
|
||||||
|
"Compose ordered relative CONTRIBUTIONS over RENDERED.
|
||||||
|
Each contribution contains `:start', `:end', and direct `:props'."
|
||||||
|
(unless (proper-list-p contributions)
|
||||||
|
(signal 'tp-invalid-content-range
|
||||||
|
(list :property-contributions contributions)))
|
||||||
|
(let ((result (copy-sequence rendered))
|
||||||
|
(limit (length rendered)))
|
||||||
|
(dolist (contribution contributions)
|
||||||
|
(let ((start (plist-get contribution :start))
|
||||||
|
(end (plist-get contribution :end))
|
||||||
|
(props (plist-get contribution :props)))
|
||||||
|
(unless (and (integerp start) (integerp end)
|
||||||
|
(<= 0 start) (<= start end) (<= end limit))
|
||||||
|
(signal 'tp-invalid-content-range
|
||||||
|
(list :property-contribution contribution
|
||||||
|
:leaf-length limit)))
|
||||||
|
(unless (tp--plist-shape-p props)
|
||||||
|
(signal 'tp-invalid-surface-plan
|
||||||
|
(list :property-contribution-props props)))
|
||||||
|
(cl-loop for (property value) on props by #'cddr
|
||||||
|
do
|
||||||
|
(let ((position start)
|
||||||
|
(merge
|
||||||
|
(tp-property-policy-merge
|
||||||
|
(tp-register-text-property property))))
|
||||||
|
(while (< position end)
|
||||||
|
(let* ((next
|
||||||
|
(or (next-single-property-change
|
||||||
|
position property result end)
|
||||||
|
end))
|
||||||
|
(cell
|
||||||
|
(plist-member
|
||||||
|
(text-properties-at position result) property))
|
||||||
|
(target
|
||||||
|
(cond
|
||||||
|
((null value) nil)
|
||||||
|
(cell (funcall merge (cadr cell) value))
|
||||||
|
(t (tp--copy-property-value value)))))
|
||||||
|
(put-text-property position next property target result)
|
||||||
|
(setq position next)))))))
|
||||||
|
result))
|
||||||
|
|
||||||
(defun tp--property-value-equal-p (property left right)
|
(defun tp--property-value-equal-p (property left right)
|
||||||
"Return non-nil when PROPERTY values LEFT and RIGHT are policy-equal."
|
"Return non-nil when PROPERTY values LEFT and RIGHT are policy-equal."
|
||||||
@ -399,6 +461,36 @@ phase before accepting the candidate."
|
|||||||
(puthash object t (tp--context-touched context))
|
(puthash object t (tp--context-touched context))
|
||||||
object))
|
object))
|
||||||
|
|
||||||
|
(defun tp-object-ensure-at (context parent key kind position)
|
||||||
|
"Return candidate identity at explicit sibling POSITION below PARENT.
|
||||||
|
KEYED objects retain their normal explicit-key identity; POSITION is used only
|
||||||
|
for anonymous objects. This is the compiled-topology entry point and does not
|
||||||
|
depend on replaying preceding siblings to discover the same slot."
|
||||||
|
(tp--validate-prepare-context context)
|
||||||
|
(tp--validate-context-parent context parent)
|
||||||
|
(unless kind
|
||||||
|
(signal 'tp-invalid-surface-plan (list :kind kind)))
|
||||||
|
(unless (and (integerp position) (>= position 0))
|
||||||
|
(signal 'tp-invalid-surface-plan (list :position position)))
|
||||||
|
(let* ((segment
|
||||||
|
(if key
|
||||||
|
(let ((seen (tp--context-child-table context parent)))
|
||||||
|
(when (gethash key seen)
|
||||||
|
(signal 'tp-duplicate-object-key (list key)))
|
||||||
|
(puthash key t seen)
|
||||||
|
key)
|
||||||
|
(list :position position :kind kind)))
|
||||||
|
(path (append (and parent (tp--surface-object-path parent))
|
||||||
|
(list segment)))
|
||||||
|
(objects (tp--context-objects context))
|
||||||
|
(old (gethash path objects))
|
||||||
|
(object (if (and old (equal kind (tp--surface-object-kind old)))
|
||||||
|
old
|
||||||
|
(tp--new-candidate-object context parent key kind path))))
|
||||||
|
(puthash path object objects)
|
||||||
|
(puthash object t (tp--context-touched context))
|
||||||
|
object))
|
||||||
|
|
||||||
(defun tp-object-live-p (object)
|
(defun tp-object-live-p (object)
|
||||||
"Return non-nil when OBJECT is committed on a live surface."
|
"Return non-nil when OBJECT is committed on a live surface."
|
||||||
(and (tp-object-p object) (tp--surface-object-live object)
|
(and (tp-object-p object) (tp--surface-object-live object)
|
||||||
@ -472,6 +564,27 @@ When EPHEMERAL is non-nil, no identity may be promoted."
|
|||||||
(puthash object t (tp--context-retained context))
|
(puthash object t (tp--context-retained context))
|
||||||
object)
|
object)
|
||||||
|
|
||||||
|
(defun tp-object-reuse (context object)
|
||||||
|
"Reuse one proven unchanged live OBJECT in CONTEXT.
|
||||||
|
The object and its live bindings remain in the candidate without being marked
|
||||||
|
as touched. The caller owns the proof that identity, parent path, output, and
|
||||||
|
lifecycle are unchanged."
|
||||||
|
(tp--validate-prepare-context context)
|
||||||
|
(unless (tp-object-p object)
|
||||||
|
(signal 'wrong-type-argument (list 'tp-object-p object)))
|
||||||
|
(let* ((surface (tp--context-surface context))
|
||||||
|
(path (tp--surface-object-path object))
|
||||||
|
(objects (tp--context-objects context)))
|
||||||
|
(unless (and (tp-object-live-p object)
|
||||||
|
(eq (tp--surface-object-surface object) surface)
|
||||||
|
(eq object (gethash path objects)))
|
||||||
|
(signal 'tp-stale-object (list object)))
|
||||||
|
(puthash object t (tp--context-retained context))
|
||||||
|
(dolist (binding (tp-binding-owner-bindings object))
|
||||||
|
(when (tp-binding-live-p binding)
|
||||||
|
(puthash binding t (tp--context-bindings context)))))
|
||||||
|
object)
|
||||||
|
|
||||||
(defun tp--object-path-prefix-p (prefix path)
|
(defun tp--object-path-prefix-p (prefix path)
|
||||||
"Return non-nil when PREFIX is a path prefix of PATH."
|
"Return non-nil when PREFIX is a path prefix of PATH."
|
||||||
(and (<= (length prefix) (length path))
|
(and (<= (length prefix) (length path))
|
||||||
@ -730,9 +843,12 @@ OWNED-P transfers candidate-local property values through the render pass."
|
|||||||
(let ((expected (make-hash-table :test #'equal))
|
(let ((expected (make-hash-table :test #'equal))
|
||||||
(actual (make-hash-table :test #'equal)))
|
(actual (make-hash-table :test #'equal)))
|
||||||
(dolist (path (tp--plan-paths plan)) (puthash path t expected))
|
(dolist (path (tp--plan-paths plan)) (puthash path t expected))
|
||||||
(maphash (lambda (object _present)
|
(maphash
|
||||||
(puthash (tp--surface-object-path object) object actual))
|
(lambda (_path object)
|
||||||
(tp--context-touched context))
|
(when (or (gethash object (tp--context-touched context))
|
||||||
|
(gethash object (tp--context-retained context)))
|
||||||
|
(puthash (tp--surface-object-path object) object actual)))
|
||||||
|
(tp--context-objects context))
|
||||||
(maphash
|
(maphash
|
||||||
(lambda (path _present)
|
(lambda (path _present)
|
||||||
(unless (gethash path actual)
|
(unless (gethash path actual)
|
||||||
@ -765,6 +881,16 @@ OWNED-P transfers candidate-local property values through the render pass."
|
|||||||
(defun tp--producer-result (value surface options context)
|
(defun tp--producer-result (value surface options context)
|
||||||
"Normalize producer VALUE for SURFACE and active CONTEXT using OPTIONS."
|
"Normalize producer VALUE for SURFACE and active CONTEXT using OPTIONS."
|
||||||
(cond
|
(cond
|
||||||
|
((tp-commit-batch-candidate-p value)
|
||||||
|
(unless (and context
|
||||||
|
(tp--context-active context)
|
||||||
|
(eq context tp--current-prepare-context)
|
||||||
|
(eq context (tp-commit-batch-candidate-context value))
|
||||||
|
(not (tp-commit-batch-candidate-consumed-p value)))
|
||||||
|
(signal 'tp-owned-result-error (list value context)))
|
||||||
|
(setf (tp-commit-batch-candidate-consumed-p value) t
|
||||||
|
(tp-commit-batch-candidate-context value) nil)
|
||||||
|
(list :commit-batch value))
|
||||||
((tp-retained-content-candidate-p value)
|
((tp-retained-content-candidate-p value)
|
||||||
(unless (and context
|
(unless (and context
|
||||||
(tp--context-active context)
|
(tp--context-active context)
|
||||||
@ -1243,7 +1369,10 @@ still validated and published through the ordinary TP transaction phases."
|
|||||||
(unless (eq (tp--surface-capability surface) 'content)
|
(unless (eq (tp--surface-capability surface) 'content)
|
||||||
(signal 'tp-capability-error (list :retained-content 'properties)))
|
(signal 'tp-capability-error (list :retained-content 'properties)))
|
||||||
(let* ((plan (tp-retained-content-candidate-plan candidate))
|
(let* ((plan (tp-retained-content-candidate-plan candidate))
|
||||||
(rendered (tp-retained-content-candidate-rendered candidate))
|
(rendered
|
||||||
|
(tp--compose-relative-property-contributions
|
||||||
|
(tp-retained-content-candidate-rendered candidate)
|
||||||
|
(tp-retained-content-candidate-property-contributions candidate)))
|
||||||
(ranges (tp-retained-content-candidate-ranges candidate))
|
(ranges (tp-retained-content-candidate-ranges candidate))
|
||||||
(objects (tp--context-objects context))
|
(objects (tp--context-objects context))
|
||||||
(root-path (list (tp--plan-segment plan 0)))
|
(root-path (list (tp--plan-segment plan 0)))
|
||||||
@ -1283,7 +1412,7 @@ still validated and published through the ordinary TP transaction phases."
|
|||||||
(unless (<= end (length rendered))
|
(unless (<= end (length rendered))
|
||||||
(signal 'tp-invalid-content-range
|
(signal 'tp-invalid-content-range
|
||||||
(list :start start :end end
|
(list :start start :end end
|
||||||
:leaf-length (length rendered)))))
|
:leaf-length (length rendered))))))
|
||||||
(puthash text-leaf ranges
|
(puthash text-leaf ranges
|
||||||
(tp--context-content-range-attachments context)))
|
(tp--context-content-range-attachments context)))
|
||||||
(let* ((length (length rendered))
|
(let* ((length (length rendered))
|
||||||
@ -1339,7 +1468,163 @@ still validated and published through the ordinary TP transaction phases."
|
|||||||
:reconciled 0 :scope-objects scope-objects
|
:reconciled 0 :scope-objects scope-objects
|
||||||
:scope-patches scope-patches
|
:scope-patches scope-patches
|
||||||
:scope-fallback (plist-get scope-analysis :fallback)
|
:scope-fallback (plist-get scope-analysis :fallback)
|
||||||
:retained-content-p retained-content-p)))))
|
:retained-content-p retained-content-p))))
|
||||||
|
|
||||||
|
(defun tp--commit-batch-rebase-position (patches position end-p)
|
||||||
|
"Map old POSITION through ordered coordinate PATCHES.
|
||||||
|
END-P selects the right boundary when POSITION lies inside a replacement."
|
||||||
|
(let ((delta 0) result)
|
||||||
|
(while (and patches (null result))
|
||||||
|
(let* ((patch (car patches))
|
||||||
|
(old-start (plist-get patch :old-start))
|
||||||
|
(old-end (plist-get patch :old-end))
|
||||||
|
(new-start (plist-get patch :new-start))
|
||||||
|
(new-end (plist-get patch :new-end)))
|
||||||
|
(cond
|
||||||
|
((< position old-start) (setq result (+ position delta)))
|
||||||
|
((= position old-start) (setq result new-start))
|
||||||
|
((< position old-end) (setq result (if end-p new-end new-start)))
|
||||||
|
((= position old-end) (setq result new-end))
|
||||||
|
(t (setq delta (- new-end old-end)
|
||||||
|
patches (cdr patches))))))
|
||||||
|
(or result (+ position delta))))
|
||||||
|
|
||||||
|
(defun tp--commit-batch-mount-specs (surface batch)
|
||||||
|
"Return SURFACE coordinate mounts rebased through BATCH."
|
||||||
|
(let ((base (marker-position (tp--surface-start surface))))
|
||||||
|
(mapcar
|
||||||
|
(lambda (mount)
|
||||||
|
(let ((start (- (tp--mount-position
|
||||||
|
(tp--surface-mount-start mount)) base))
|
||||||
|
(end (- (tp--mount-position
|
||||||
|
(tp--surface-mount-end mount)) base))
|
||||||
|
(patches (tp-commit-batch-coordinate-patches batch)))
|
||||||
|
(list :object (tp--surface-mount-object mount)
|
||||||
|
:start (tp--commit-batch-rebase-position patches start nil)
|
||||||
|
:end (tp--commit-batch-rebase-position patches end t)
|
||||||
|
:tags (copy-tree (tp--surface-mount-tags mount)))))
|
||||||
|
(tp--surface-mounts surface))))
|
||||||
|
|
||||||
|
(defun tp--commit-batch-retained-mount-state
|
||||||
|
(surface mount-specs context target-extent)
|
||||||
|
"Return exact coordinate updates when SURFACE can retain MOUNT-SPECS.
|
||||||
|
The returned cons distinguishes an exact empty update set from a proof miss."
|
||||||
|
(let ((base (marker-position (tp--surface-start surface)))
|
||||||
|
(mounts (tp--surface-mounts surface))
|
||||||
|
(specs mount-specs)
|
||||||
|
(retainable
|
||||||
|
(and (plist-get (tp--surface-options surface) :coordinate-mounts) t))
|
||||||
|
(updates
|
||||||
|
(and (plist-get (tp--surface-options surface) :coordinate-mounts)
|
||||||
|
(make-vector (* 3 (length mount-specs)) nil)))
|
||||||
|
(update-index 0))
|
||||||
|
(while specs
|
||||||
|
(let* ((spec (pop specs))
|
||||||
|
(mount (and mounts (pop mounts)))
|
||||||
|
(object (plist-get spec :object))
|
||||||
|
(start (plist-get spec :start))
|
||||||
|
(end (plist-get spec :end)))
|
||||||
|
(unless (and (tp-object-p object)
|
||||||
|
(or (gethash object (tp--context-touched context))
|
||||||
|
(gethash object (tp--context-retained context)))
|
||||||
|
(integerp start) (integerp end)
|
||||||
|
(<= 0 start end target-extent))
|
||||||
|
(signal 'tp-surface-error
|
||||||
|
(list :commit-mount-range start end target-extent)))
|
||||||
|
(if (and retainable mount
|
||||||
|
(integerp (tp--surface-mount-start mount))
|
||||||
|
(integerp (tp--surface-mount-end mount))
|
||||||
|
(eq (tp--surface-mount-object mount) object)
|
||||||
|
(eq (tp--surface-mount-tags mount)
|
||||||
|
(plist-get spec :tags)))
|
||||||
|
(progn
|
||||||
|
(aset updates update-index mount)
|
||||||
|
(aset updates (1+ update-index) (+ base start))
|
||||||
|
(aset updates (+ update-index 2) (+ base end))
|
||||||
|
(setq update-index (+ update-index 3)))
|
||||||
|
(setq retainable nil
|
||||||
|
updates nil))))
|
||||||
|
(when mounts (setq retainable nil))
|
||||||
|
(and retainable (cons t updates))))
|
||||||
|
|
||||||
|
(defun tp--commit-batch-stable-object-set-p (surface objects)
|
||||||
|
"Return non-nil when OBJECTS are exactly SURFACE's retained identities."
|
||||||
|
(let ((live (tp--surface-objects surface)))
|
||||||
|
(and (hash-table-p live)
|
||||||
|
(= (hash-table-count live) (length objects))
|
||||||
|
(cl-every
|
||||||
|
(lambda (object)
|
||||||
|
(eq (gethash (tp--surface-object-path object) live) object))
|
||||||
|
objects))))
|
||||||
|
|
||||||
|
(defun tp--commit-batch-coordinate-identity-p (batch)
|
||||||
|
"Return non-nil when BATCH cannot move any retained coordinate."
|
||||||
|
(and (= (tp-commit-batch-base-extent batch)
|
||||||
|
(tp-commit-batch-target-extent batch))
|
||||||
|
(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))))
|
||||||
|
(tp-commit-batch-coordinate-patches batch))))
|
||||||
|
|
||||||
|
(defun tp--prepare-commit-batch
|
||||||
|
(surface candidate context scope-objects input initial)
|
||||||
|
"Prepare strict equal-coordinate CANDIDATE for stable SURFACE topology."
|
||||||
|
(when initial
|
||||||
|
(signal 'tp-invalid-surface-plan (list :commit-batch-initial t)))
|
||||||
|
(unless (and (eq (tp--surface-capability surface) 'content)
|
||||||
|
(plist-get (tp--surface-options surface) :coordinate-mounts))
|
||||||
|
(signal 'tp-capability-error (list :commit-batch-coordinate-surface)))
|
||||||
|
(let* ((batch (tp-commit-batch-candidate-batch candidate))
|
||||||
|
(extent (- (marker-position (tp--surface-end surface))
|
||||||
|
(marker-position (tp--surface-start surface))))
|
||||||
|
(objects (tp--candidate-object-list context))
|
||||||
|
(reuse-mount-projection-p
|
||||||
|
(tp-commit-batch-candidate-reuse-mount-projection-p candidate))
|
||||||
|
(coordinate-identity-p
|
||||||
|
(and (not reuse-mount-projection-p)
|
||||||
|
(not (tp-commit-batch-candidate-exact-mount-specs-p candidate))
|
||||||
|
(tp--commit-batch-coordinate-identity-p batch)))
|
||||||
|
(mount-specs
|
||||||
|
(cond
|
||||||
|
((tp-commit-batch-candidate-exact-mount-specs-p candidate)
|
||||||
|
(tp-commit-batch-candidate-mount-specs candidate))
|
||||||
|
(reuse-mount-projection-p nil)
|
||||||
|
(coordinate-identity-p nil)
|
||||||
|
(t (tp--commit-batch-mount-specs surface batch))))
|
||||||
|
(retained-mount-state
|
||||||
|
(if (or reuse-mount-projection-p coordinate-identity-p)
|
||||||
|
(cons t [])
|
||||||
|
(tp--commit-batch-retained-mount-state
|
||||||
|
surface mount-specs context
|
||||||
|
(tp-commit-batch-target-extent batch)))))
|
||||||
|
(unless (and (= (tp-commit-batch-base-revision batch)
|
||||||
|
(tp--surface-revision surface))
|
||||||
|
(= (tp-commit-batch-target-revision batch)
|
||||||
|
(1+ (tp--surface-revision surface)))
|
||||||
|
(= extent (tp-commit-batch-base-extent batch))
|
||||||
|
(or (not reuse-mount-projection-p)
|
||||||
|
(= (tp-commit-batch-base-extent batch)
|
||||||
|
(tp-commit-batch-target-extent batch))))
|
||||||
|
(signal 'tp-surface-error (list :unsupported-commit-batch batch)))
|
||||||
|
(unless (tp--commit-batch-stable-object-set-p surface objects)
|
||||||
|
(signal 'tp-orphan-object (list :commit-batch-object-set)))
|
||||||
|
(tp--make-prepared-surface
|
||||||
|
:surface surface :context context :plan (tp--surface-plan surface)
|
||||||
|
:rendered nil :mount-specs mount-specs
|
||||||
|
:objects objects
|
||||||
|
:client-state
|
||||||
|
(if (tp-commit-batch-candidate-exact-client-state-p candidate)
|
||||||
|
(tp-commit-batch-candidate-client-state candidate)
|
||||||
|
(tp-commit-batch-client-state batch))
|
||||||
|
:producer input :initial nil :created nil :removed nil :moved 0
|
||||||
|
:reconciled 0 :scope-objects scope-objects
|
||||||
|
:scope-patches (tp-commit-batch-patches batch)
|
||||||
|
:scope-fallback nil :commit-batch batch
|
||||||
|
:retained-mount-state-p (and retained-mount-state t)
|
||||||
|
:mount-coordinate-updates (cdr retained-mount-state))))
|
||||||
|
|
||||||
(defun tp--prepare-surface (surface input options initial)
|
(defun tp--prepare-surface (surface input options initial)
|
||||||
"Prepare INPUT for SURFACE without publishing it.
|
"Prepare INPUT for SURFACE without publishing it.
|
||||||
@ -1362,11 +1647,15 @@ OPTIONS configure the mount and INITIAL is non-nil for first publication."
|
|||||||
surface
|
surface
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(tp--prepare-input surface input options context)))))
|
(tp--prepare-input surface input options context)))))
|
||||||
(if (eq (car normalized) :retained-content)
|
(if (memq (car normalized) '(:retained-content :commit-batch))
|
||||||
(setq result
|
(setq result
|
||||||
(tp--prepare-retained-content
|
(if (eq (car normalized) :retained-content)
|
||||||
surface (cadr normalized) context scope-objects
|
(tp--prepare-retained-content
|
||||||
scope-options initial))
|
surface (cadr normalized) context scope-objects
|
||||||
|
scope-options initial)
|
||||||
|
(tp--prepare-commit-batch
|
||||||
|
surface (cadr normalized) context scope-objects
|
||||||
|
input initial)))
|
||||||
(let* ((plan (nth 0 normalized))
|
(let* ((plan (nth 0 normalized))
|
||||||
(client-state (nth 1 normalized))
|
(client-state (nth 1 normalized))
|
||||||
(owned-p (nth 2 normalized))
|
(owned-p (nth 2 normalized))
|
||||||
@ -1665,6 +1954,199 @@ MOUNT-SPECS and SURFACE provide ranges for PROPERTIES."
|
|||||||
(tp--validate-live-surface surface)
|
(tp--validate-live-surface surface)
|
||||||
(tp--surface-client-state surface))
|
(tp--surface-client-state surface))
|
||||||
|
|
||||||
|
(cl-defun tp-commit-batch-create
|
||||||
|
(&key base-revision target-revision base-extent target-extent
|
||||||
|
patches coordinate-patches client-state)
|
||||||
|
"Create a validated precomputed content commit batch.
|
||||||
|
PATCHES are ordered plists containing :old-start, :old-end, :new-start,
|
||||||
|
:new-end, and a propertized :replacement string."
|
||||||
|
(unless (and (integerp base-revision) (>= base-revision 0)
|
||||||
|
(integerp target-revision)
|
||||||
|
(= target-revision (1+ base-revision)))
|
||||||
|
(signal 'tp-surface-error
|
||||||
|
(list :commit-revision base-revision target-revision)))
|
||||||
|
(unless (and (integerp base-extent) (>= base-extent 0)
|
||||||
|
(integerp target-extent) (>= target-extent 0)
|
||||||
|
(proper-list-p patches) (proper-list-p coordinate-patches))
|
||||||
|
(signal 'tp-surface-error
|
||||||
|
(list :commit-extents base-extent target-extent)))
|
||||||
|
(let ((old-cursor 0) (new-cursor 0) copy coordinate-copy)
|
||||||
|
(dolist (patch patches)
|
||||||
|
(let ((old-start (plist-get patch :old-start))
|
||||||
|
(old-end (plist-get patch :old-end))
|
||||||
|
(new-start (plist-get patch :new-start))
|
||||||
|
(new-end (plist-get patch :new-end))
|
||||||
|
(replacement (plist-get patch :replacement)))
|
||||||
|
(unless (and (integerp old-start) (integerp old-end)
|
||||||
|
(integerp new-start) (integerp new-end)
|
||||||
|
(<= old-cursor old-start old-end base-extent)
|
||||||
|
(<= new-cursor new-start new-end target-extent)
|
||||||
|
(= (- old-start old-cursor)
|
||||||
|
(- new-start new-cursor))
|
||||||
|
(stringp replacement)
|
||||||
|
(= (length replacement) (- new-end new-start)))
|
||||||
|
(signal 'tp-surface-error (list :commit-patch patch)))
|
||||||
|
(push (copy-tree patch) copy)
|
||||||
|
(setq old-cursor old-end new-cursor new-end)))
|
||||||
|
(unless (= (- base-extent old-cursor)
|
||||||
|
(- target-extent new-cursor))
|
||||||
|
(signal 'tp-surface-error
|
||||||
|
(list :commit-tail old-cursor new-cursor)))
|
||||||
|
(setq old-cursor 0 new-cursor 0)
|
||||||
|
(dolist (patch coordinate-patches)
|
||||||
|
(let ((old-start (plist-get patch :old-start))
|
||||||
|
(old-end (plist-get patch :old-end))
|
||||||
|
(new-start (plist-get patch :new-start))
|
||||||
|
(new-end (plist-get patch :new-end)))
|
||||||
|
(unless (and (integerp old-start) (integerp old-end)
|
||||||
|
(integerp new-start) (integerp new-end)
|
||||||
|
(<= old-cursor old-start old-end base-extent)
|
||||||
|
(<= new-cursor new-start new-end target-extent)
|
||||||
|
(= (- old-start old-cursor)
|
||||||
|
(- new-start new-cursor)))
|
||||||
|
(signal 'tp-surface-error
|
||||||
|
(list :commit-coordinate-patch patch)))
|
||||||
|
(push (copy-tree patch) coordinate-copy)
|
||||||
|
(setq old-cursor old-end new-cursor new-end)))
|
||||||
|
(unless (= (- base-extent old-cursor)
|
||||||
|
(- target-extent new-cursor))
|
||||||
|
(signal 'tp-surface-error
|
||||||
|
(list :commit-coordinate-tail old-cursor new-cursor)))
|
||||||
|
(tp--make-commit-batch
|
||||||
|
:base-revision base-revision :target-revision target-revision
|
||||||
|
:base-extent base-extent :target-extent target-extent
|
||||||
|
:patches (nreverse copy)
|
||||||
|
:coordinate-patches (nreverse coordinate-copy)
|
||||||
|
:client-state (tp--copy-property-value client-state))))
|
||||||
|
|
||||||
|
(cl-defun tp-commit-batch-result-create
|
||||||
|
(context batch
|
||||||
|
&key (mount-specs nil mount-specs-p)
|
||||||
|
(client-state nil client-state-p)
|
||||||
|
reuse-mount-projection)
|
||||||
|
"Return one authenticated producer result carrying precomputed BATCH.
|
||||||
|
When MOUNT-SPECS is supplied, it is the complete target coordinate projection
|
||||||
|
for the batch. Each spec contains `:object', `:start', `:end', and optional
|
||||||
|
`:tags'. When CLIENT-STATE is supplied, it replaces BATCH's defensive state
|
||||||
|
snapshot. The active prepare context owns and transfers these candidate
|
||||||
|
values without another materialization. REUSE-MOUNT-PROJECTION asserts that
|
||||||
|
the producer proved the complete target mount topology and coordinates equal
|
||||||
|
to the committed projection, as with `tp-object-reuse-subtree'."
|
||||||
|
(unless (and (tp-prepare-context-p context)
|
||||||
|
(tp--context-active context)
|
||||||
|
(eq context tp--current-prepare-context)
|
||||||
|
(tp-commit-batch-p batch))
|
||||||
|
(signal 'tp-owned-result-error (list :commit-batch context batch)))
|
||||||
|
(when (and mount-specs-p reuse-mount-projection)
|
||||||
|
(signal 'tp-surface-error (list :conflicting-commit-mount-projection)))
|
||||||
|
(when mount-specs-p
|
||||||
|
(unless (proper-list-p mount-specs)
|
||||||
|
(signal 'tp-surface-error (list :commit-mount-specs))))
|
||||||
|
(tp--make-commit-batch-candidate
|
||||||
|
:batch batch :context context :mount-specs mount-specs
|
||||||
|
:exact-mount-specs-p mount-specs-p :client-state client-state
|
||||||
|
:exact-client-state-p client-state-p
|
||||||
|
:reuse-mount-projection-p (and reuse-mount-projection t)))
|
||||||
|
|
||||||
|
(defun tp--commit-batch-apply-patches (surface batch)
|
||||||
|
"Apply BATCH's already validated patches to SURFACE."
|
||||||
|
(let ((buffer (tp--surface-buffer surface))
|
||||||
|
(base (marker-position (tp--surface-start surface))))
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(save-restriction
|
||||||
|
(widen)
|
||||||
|
(let ((inhibit-read-only
|
||||||
|
(plist-get (tp--surface-options surface)
|
||||||
|
:inhibit-read-only)))
|
||||||
|
(dolist (patch (reverse (tp-commit-batch-patches batch)))
|
||||||
|
(let ((start (+ base (plist-get patch :old-start)))
|
||||||
|
(end (+ base (plist-get patch :old-end)))
|
||||||
|
(replacement (plist-get patch :replacement)))
|
||||||
|
(delete-region start end)
|
||||||
|
(goto-char start)
|
||||||
|
(insert replacement))))))
|
||||||
|
(set-marker (tp--surface-start surface) base buffer)
|
||||||
|
(set-marker (tp--surface-end surface)
|
||||||
|
(+ base (tp-commit-batch-target-extent batch)) buffer)))
|
||||||
|
|
||||||
|
(defun tp--commit-batch-validate-buffer (surface batch)
|
||||||
|
"Validate BATCH's published extent and changed spans on SURFACE."
|
||||||
|
(let ((buffer (tp--surface-buffer surface))
|
||||||
|
(base (marker-position (tp--surface-start surface))))
|
||||||
|
(let ((actual (- (marker-position (tp--surface-end surface)) base))
|
||||||
|
(expected (tp-commit-batch-target-extent batch)))
|
||||||
|
(unless (= actual expected)
|
||||||
|
(signal 'tp-publication-mismatch
|
||||||
|
(list :commit-extent :actual actual :expected expected))))
|
||||||
|
(dolist (patch (tp-commit-batch-patches batch))
|
||||||
|
(let ((start (+ base (plist-get patch :new-start)))
|
||||||
|
(end (+ base (plist-get patch :new-end)))
|
||||||
|
(replacement (plist-get patch :replacement)))
|
||||||
|
(unless (equal-including-properties
|
||||||
|
(with-current-buffer buffer (buffer-substring start end))
|
||||||
|
replacement)
|
||||||
|
(signal 'tp-publication-mismatch
|
||||||
|
(list :commit-patch patch)))))))
|
||||||
|
|
||||||
|
(defun tp--commit-batch-validate-mounts (surface batch mounts)
|
||||||
|
"Validate target MOUNTS for BATCH inside SURFACE."
|
||||||
|
(let ((base (marker-position (tp--surface-start surface)))
|
||||||
|
(limit (+ (marker-position (tp--surface-start surface))
|
||||||
|
(tp-commit-batch-target-extent batch))))
|
||||||
|
(dolist (mount mounts)
|
||||||
|
(let ((start (tp--mount-position (tp--surface-mount-start mount)))
|
||||||
|
(end (tp--mount-position (tp--surface-mount-end mount))))
|
||||||
|
(unless (and start end (<= base start end limit))
|
||||||
|
(signal 'tp-publication-mismatch
|
||||||
|
(list :commit-mount mount)))))))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun tp-surface-commit-batch (surface batch)
|
||||||
|
"Atomically apply precomputed BATCH to stable content SURFACE."
|
||||||
|
(tp--validate-live-surface surface)
|
||||||
|
(unless (and (tp-commit-batch-p batch)
|
||||||
|
(eq (tp--surface-capability surface) 'content)
|
||||||
|
(= (tp--surface-revision surface)
|
||||||
|
(tp-commit-batch-base-revision batch))
|
||||||
|
(= (- (marker-position (tp--surface-end surface))
|
||||||
|
(marker-position (tp--surface-start surface)))
|
||||||
|
(tp-commit-batch-base-extent batch)))
|
||||||
|
(signal 'tp-surface-error (list :stale-commit-batch surface batch)))
|
||||||
|
(let* ((buffer (tp--surface-buffer surface))
|
||||||
|
(old-revision (tp--surface-revision surface))
|
||||||
|
(old-client-state (tp--surface-client-state surface))
|
||||||
|
(old-report (tp--surface-report surface))
|
||||||
|
(views (tp--capture-view-state (list buffer)))
|
||||||
|
(group (tp--prepare-change-group-for-buffers (list buffer)))
|
||||||
|
success)
|
||||||
|
(unwind-protect
|
||||||
|
(progn
|
||||||
|
(tp--commit-batch-apply-patches surface batch)
|
||||||
|
(tp--commit-batch-validate-buffer surface batch)
|
||||||
|
(tp--commit-batch-validate-mounts
|
||||||
|
surface batch (tp--surface-mounts surface))
|
||||||
|
(setf (tp--surface-client-state surface)
|
||||||
|
(tp-commit-batch-client-state batch)
|
||||||
|
(tp--surface-revision surface)
|
||||||
|
(tp-commit-batch-target-revision batch)
|
||||||
|
(tp--surface-report surface)
|
||||||
|
(list :transaction-id (cl-incf tp--surface-transaction-id)
|
||||||
|
:surface-id (tp--surface-id surface)
|
||||||
|
:old-revision old-revision
|
||||||
|
:new-revision (tp-commit-batch-target-revision batch)
|
||||||
|
:text-operations (length (tp-commit-batch-patches batch))
|
||||||
|
:property-operations 0 :commit-batch t
|
||||||
|
:rolled-back nil :failure nil))
|
||||||
|
(accept-change-group group)
|
||||||
|
(setq success t)
|
||||||
|
(tp-surface-report surface))
|
||||||
|
(unless success
|
||||||
|
(tp--cancel-change-group-safely group)
|
||||||
|
(setf (tp--surface-client-state surface) old-client-state
|
||||||
|
(tp--surface-revision surface) old-revision
|
||||||
|
(tp--surface-report surface) old-report))
|
||||||
|
(tp--restore-view-state views))))
|
||||||
|
|
||||||
(defun tp--validate-surface-buffer (surface initial)
|
(defun tp--validate-surface-buffer (surface initial)
|
||||||
"Validate SURFACE's buffer before prepare; INITIAL permits a candidate."
|
"Validate SURFACE's buffer before prepare; INITIAL permits a candidate."
|
||||||
(let ((buffer (tp--surface-buffer surface)))
|
(let ((buffer (tp--surface-buffer surface)))
|
||||||
@ -2233,6 +2715,48 @@ When SCOPED is non-nil, inspect only RANGES, including an empty set."
|
|||||||
(tp--prepared-surface-live-ledger prepared) ledger)
|
(tp--prepared-surface-live-ledger prepared) ledger)
|
||||||
mounts))
|
mounts))
|
||||||
|
|
||||||
|
(defun tp--retain-candidate-mount-state (prepared)
|
||||||
|
"Apply PREPARED coordinate updates while retaining mount identity and index."
|
||||||
|
(let* ((surface (tp--prepared-surface-surface prepared))
|
||||||
|
(mounts (tp--surface-mounts surface))
|
||||||
|
(updates (tp--prepared-surface-mount-coordinate-updates prepared))
|
||||||
|
(undo (make-vector (* 2 (/ (length updates) 3)) nil))
|
||||||
|
(update-index 0)
|
||||||
|
(undo-index 0))
|
||||||
|
(while (< update-index (length updates))
|
||||||
|
(let ((mount (aref updates update-index)))
|
||||||
|
(aset undo undo-index (tp--surface-mount-start mount))
|
||||||
|
(aset undo (1+ undo-index) (tp--surface-mount-end mount))
|
||||||
|
(setf (tp--surface-mount-start mount)
|
||||||
|
(aref updates (1+ update-index))
|
||||||
|
(tp--surface-mount-end mount)
|
||||||
|
(aref updates (+ update-index 2)))
|
||||||
|
(setq update-index (+ update-index 3)
|
||||||
|
undo-index (+ undo-index 2))))
|
||||||
|
(setf (tp--prepared-surface-mount-coordinate-undo prepared)
|
||||||
|
undo
|
||||||
|
(tp--prepared-surface-live-mounts prepared) mounts
|
||||||
|
(tp--prepared-surface-live-mount-index prepared)
|
||||||
|
(tp--surface-mount-index surface)
|
||||||
|
(tp--prepared-surface-live-ledger prepared)
|
||||||
|
(tp--surface-ledger surface))
|
||||||
|
mounts))
|
||||||
|
|
||||||
|
(defun tp--restore-retained-mount-state (prepared)
|
||||||
|
"Restore PREPARED retained mount coordinates after rollback."
|
||||||
|
(let ((updates (tp--prepared-surface-mount-coordinate-updates prepared))
|
||||||
|
(undo (tp--prepared-surface-mount-coordinate-undo prepared))
|
||||||
|
(update-index 0)
|
||||||
|
(undo-index 0))
|
||||||
|
(when (and (vectorp updates) (vectorp undo))
|
||||||
|
(while (< update-index (length updates))
|
||||||
|
(let ((mount (aref updates update-index)))
|
||||||
|
(setf (tp--surface-mount-start mount) (aref undo undo-index)
|
||||||
|
(tp--surface-mount-end mount) (aref undo (1+ undo-index)))
|
||||||
|
(setq update-index (+ update-index 3)
|
||||||
|
undo-index (+ undo-index 2))))))
|
||||||
|
(setf (tp--prepared-surface-mount-coordinate-undo prepared) nil))
|
||||||
|
|
||||||
(defun tp--surface-snapshot (surface)
|
(defun tp--surface-snapshot (surface)
|
||||||
"Return a rollback snapshot of SURFACE side state."
|
"Return a rollback snapshot of SURFACE side state."
|
||||||
(tp--make-surface-snapshot
|
(tp--make-surface-snapshot
|
||||||
@ -2261,12 +2785,15 @@ When SCOPED is non-nil, inspect only RANGES, including an empty set."
|
|||||||
(fallback (tp--prepared-surface-scope-fallback prepared))
|
(fallback (tp--prepared-surface-scope-fallback prepared))
|
||||||
(patches (tp--prepared-surface-scope-patches prepared))
|
(patches (tp--prepared-surface-scope-patches prepared))
|
||||||
(touched
|
(touched
|
||||||
(if (and scoped (not fallback))
|
(if-let ((batch (tp--prepared-surface-commit-batch prepared)))
|
||||||
|
(cl-loop for patch in (tp-commit-batch-patches batch)
|
||||||
|
sum (length (plist-get patch :replacement)))
|
||||||
|
(if (and scoped (not fallback))
|
||||||
(cl-loop for patch in patches
|
(cl-loop for patch in patches
|
||||||
sum (if (eq (tp--surface-capability surface) 'content)
|
sum (if (eq (tp--surface-capability surface) 'content)
|
||||||
(length (plist-get patch :replacement))
|
(length (plist-get patch :replacement))
|
||||||
(- (cdr patch) (car patch))))
|
(- (cdr patch) (car patch))))
|
||||||
(length (tp--prepared-surface-rendered prepared)))))
|
(length (tp--prepared-surface-rendered prepared))))))
|
||||||
(list :transaction-id tp--surface-transaction-id
|
(list :transaction-id tp--surface-transaction-id
|
||||||
:surface-id (tp--surface-id surface)
|
:surface-id (tp--surface-id surface)
|
||||||
:old-revision old-revision :new-revision new-revision
|
:old-revision old-revision :new-revision new-revision
|
||||||
@ -2287,6 +2814,9 @@ When SCOPED is non-nil, inspect only RANGES, including an empty set."
|
|||||||
:scope-range-count (and scoped (length patches))
|
:scope-range-count (and scoped (length patches))
|
||||||
:scope-fallback fallback
|
:scope-fallback fallback
|
||||||
:property-conflicts nil :rolled-back nil
|
:property-conflicts nil :rolled-back nil
|
||||||
|
:commit-batch (and (tp--prepared-surface-commit-batch prepared) t)
|
||||||
|
:retained-mount-state
|
||||||
|
(and (tp--prepared-surface-retained-mount-state-p prepared) t)
|
||||||
:failure nil :observer-errors nil :timing nil)))
|
:failure nil :observer-errors nil :timing nil)))
|
||||||
|
|
||||||
(defun tp--scoped-content-ranges (patches)
|
(defun tp--scoped-content-ranges (patches)
|
||||||
@ -2363,6 +2893,15 @@ the generic property-operation ledger."
|
|||||||
|
|
||||||
(defun tp--publish-buffer-content (prepared)
|
(defun tp--publish-buffer-content (prepared)
|
||||||
"Publish PREPARED's text and properties, returning operation counts."
|
"Publish PREPARED's text and properties, returning operation counts."
|
||||||
|
(cl-block publish
|
||||||
|
(when-let ((batch (tp--prepared-surface-commit-batch prepared)))
|
||||||
|
(let ((surface (tp--prepared-surface-surface prepared)))
|
||||||
|
(tp--commit-batch-apply-patches surface batch)
|
||||||
|
(tp--publication-step 'text surface)
|
||||||
|
(tp--commit-batch-validate-buffer surface batch)
|
||||||
|
(tp--publication-step 'property surface)
|
||||||
|
(cl-return-from publish
|
||||||
|
(cons (length (tp-commit-batch-patches batch)) 0))))
|
||||||
(let* ((surface (tp--prepared-surface-surface prepared))
|
(let* ((surface (tp--prepared-surface-surface prepared))
|
||||||
(buffer (tp--surface-buffer surface))
|
(buffer (tp--surface-buffer surface))
|
||||||
(rendered (tp--prepared-surface-rendered prepared))
|
(rendered (tp--prepared-surface-rendered prepared))
|
||||||
@ -2399,7 +2938,7 @@ the generic property-operation ledger."
|
|||||||
(when (tp--prepared-surface-retained-content-p prepared)
|
(when (tp--prepared-surface-retained-content-p prepared)
|
||||||
(tp--publication-step 'property surface))))
|
(tp--publication-step 'property surface))))
|
||||||
(goto-char (min point-before (point-max)))))
|
(goto-char (min point-before (point-max)))))
|
||||||
(cons text-operations (length property-operations))))))
|
(cons text-operations (length property-operations)))))))
|
||||||
|
|
||||||
(defun tp--publish-buffer-properties (prepared)
|
(defun tp--publish-buffer-properties (prepared)
|
||||||
"Publish PREPARED's host-range property operations."
|
"Publish PREPARED's host-range property operations."
|
||||||
@ -2430,7 +2969,10 @@ the generic property-operation ledger."
|
|||||||
(tp--promote-prepared-objects prepared)
|
(tp--promote-prepared-objects prepared)
|
||||||
(setf (tp--surface-plan surface) (tp--prepared-surface-plan prepared)
|
(setf (tp--surface-plan surface) (tp--prepared-surface-plan prepared)
|
||||||
(tp--surface-objects surface)
|
(tp--surface-objects surface)
|
||||||
(tp--surface-object-table (tp--prepared-surface-objects prepared))
|
(if (tp--prepared-surface-retained-mount-state-p prepared)
|
||||||
|
(tp--surface-objects surface)
|
||||||
|
(tp--surface-object-table
|
||||||
|
(tp--prepared-surface-objects prepared)))
|
||||||
(tp--surface-mounts surface) mounts
|
(tp--surface-mounts surface) mounts
|
||||||
(tp--surface-index surface) mounts
|
(tp--surface-index surface) mounts
|
||||||
(tp--surface-mount-index surface)
|
(tp--surface-mount-index surface)
|
||||||
@ -2455,7 +2997,14 @@ the generic property-operation ledger."
|
|||||||
(counts (if (eq (tp--surface-capability surface) 'content)
|
(counts (if (eq (tp--surface-capability surface) 'content)
|
||||||
(tp--publish-buffer-content prepared)
|
(tp--publish-buffer-content prepared)
|
||||||
(tp--publish-buffer-properties prepared))))
|
(tp--publish-buffer-properties prepared))))
|
||||||
(tp--create-candidate-mount-state prepared)
|
(if (tp--prepared-surface-retained-mount-state-p prepared)
|
||||||
|
(tp--retain-candidate-mount-state prepared)
|
||||||
|
(tp--create-candidate-mount-state prepared))
|
||||||
|
(when-let ((batch (and (not (tp--prepared-surface-retained-mount-state-p
|
||||||
|
prepared))
|
||||||
|
(tp--prepared-surface-commit-batch prepared))))
|
||||||
|
(tp--commit-batch-validate-mounts
|
||||||
|
surface batch (tp--prepared-surface-live-mounts prepared)))
|
||||||
(tp--publication-step 'marker surface)
|
(tp--publication-step 'marker surface)
|
||||||
(tp--publication-step 'index surface)
|
(tp--publication-step 'index surface)
|
||||||
(tp--swap-surface-state prepared counts)
|
(tp--swap-surface-state prepared counts)
|
||||||
@ -2627,6 +3176,7 @@ When RETAIN-MARKERS is non-nil, keep snapshot markers for rollback."
|
|||||||
(defun tp--restore-surface-snapshot (prepared snapshot)
|
(defun tp--restore-surface-snapshot (prepared snapshot)
|
||||||
"Restore PREPARED's surface from SNAPSHOT after failed publication."
|
"Restore PREPARED's surface from SNAPSHOT after failed publication."
|
||||||
(let ((surface (tp--prepared-surface-surface prepared)))
|
(let ((surface (tp--prepared-surface-surface prepared)))
|
||||||
|
(tp--restore-retained-mount-state prepared)
|
||||||
(tp--dispose-content-mounts
|
(tp--dispose-content-mounts
|
||||||
(tp--prepared-surface-live-mounts prepared))
|
(tp--prepared-surface-live-mounts prepared))
|
||||||
(tp--dispose-ledger (tp--prepared-surface-live-ledger prepared))
|
(tp--dispose-ledger (tp--prepared-surface-live-ledger prepared))
|
||||||
@ -2729,6 +3279,36 @@ When RETAIN-MARKERS is non-nil, keep snapshot markers for rollback."
|
|||||||
(when tp--surface-precommit-step-function
|
(when tp--surface-precommit-step-function
|
||||||
(funcall tp--surface-precommit-step-function step state)))
|
(funcall tp--surface-precommit-step-function step state)))
|
||||||
|
|
||||||
|
(defun tp--validate-retained-batch-precommit (prepared snapshot)
|
||||||
|
"Validate PREPARED retained batch from constant-time committed identities."
|
||||||
|
(let* ((surface (tp--prepared-surface-surface prepared))
|
||||||
|
(batch (tp--prepared-surface-commit-batch prepared))
|
||||||
|
(mounts (tp--surface-mounts surface))
|
||||||
|
(mount-index (tp--surface-mount-index surface)))
|
||||||
|
(tp--validate-live-surface surface)
|
||||||
|
(unless (and batch
|
||||||
|
(eq (tp--surface-plan surface)
|
||||||
|
(tp--prepared-surface-plan prepared))
|
||||||
|
(eq (tp--surface-objects surface)
|
||||||
|
(tp--surface-snapshot-objects snapshot))
|
||||||
|
(eq mounts (tp--surface-snapshot-mounts snapshot))
|
||||||
|
(eq mount-index
|
||||||
|
(tp--surface-snapshot-mount-index snapshot))
|
||||||
|
(eq (tp--surface-index surface) mounts)
|
||||||
|
(eq (tp--prepared-surface-live-mounts prepared) mounts)
|
||||||
|
(eq (tp--prepared-surface-live-mount-index prepared)
|
||||||
|
mount-index)
|
||||||
|
(= (tp--surface-revision surface)
|
||||||
|
(tp-commit-batch-target-revision batch))
|
||||||
|
(not (tp--context-active
|
||||||
|
(tp--prepared-surface-context prepared)))
|
||||||
|
(plist-get (tp--surface-report surface) :commit-batch)
|
||||||
|
(plist-get (tp--surface-report surface)
|
||||||
|
:retained-mount-state))
|
||||||
|
(signal 'tp-surface-error
|
||||||
|
(list :invalid-retained-batch-precommit
|
||||||
|
(tp--surface-id surface))))))
|
||||||
|
|
||||||
(defun tp--validate-surface-precommit (state)
|
(defun tp--validate-surface-precommit (state)
|
||||||
"Validate final published semantics and rollback ownership in STATE."
|
"Validate final published semantics and rollback ownership in STATE."
|
||||||
(unless (and (gethash 'change-group state)
|
(unless (and (gethash 'change-group state)
|
||||||
@ -2746,15 +3326,18 @@ When RETAIN-MARKERS is non-nil, keep snapshot markers for rollback."
|
|||||||
(unless (and (tp--prepared-surface-p (car entry))
|
(unless (and (tp--prepared-surface-p (car entry))
|
||||||
(tp--surface-snapshot-p (cdr entry)))
|
(tp--surface-snapshot-p (cdr entry)))
|
||||||
(signal 'tp-surface-error (list :invalid-surface-cleanup entry)))
|
(signal 'tp-surface-error (list :invalid-surface-cleanup entry)))
|
||||||
(let* ((prepared (car entry))
|
(let ((prepared (car entry))
|
||||||
(surface (tp--prepared-surface-surface prepared))
|
(snapshot (cdr entry)))
|
||||||
(plan (tp--surface-plan surface))
|
(if (tp--prepared-surface-retained-mount-state-p prepared)
|
||||||
(objects (tp--surface-objects surface))
|
(tp--validate-retained-batch-precommit prepared snapshot)
|
||||||
(mounts (tp--surface-mounts surface))
|
(let* ((surface (tp--prepared-surface-surface prepared))
|
||||||
(mount-index (tp--surface-mount-index surface))
|
(plan (tp--surface-plan surface))
|
||||||
(capability (tp--surface-capability surface))
|
(objects (tp--surface-objects surface))
|
||||||
(mount-set (make-hash-table :test #'eq))
|
(mounts (tp--surface-mounts surface))
|
||||||
(indexed-mount-set (make-hash-table :test #'eq)))
|
(mount-index (tp--surface-mount-index surface))
|
||||||
|
(capability (tp--surface-capability surface))
|
||||||
|
(mount-set (make-hash-table :test #'eq))
|
||||||
|
(indexed-mount-set (make-hash-table :test #'eq)))
|
||||||
(tp--validate-live-surface surface)
|
(tp--validate-live-surface surface)
|
||||||
(unless (eq plan (tp--prepared-surface-plan prepared))
|
(unless (eq plan (tp--prepared-surface-plan prepared))
|
||||||
(signal 'tp-surface-error (list :invalid-published-plan surface)))
|
(signal 'tp-surface-error (list :invalid-published-plan surface)))
|
||||||
@ -2823,31 +3406,32 @@ When RETAIN-MARKERS is non-nil, keep snapshot markers for rollback."
|
|||||||
(list :invalid-published-anchor mount))))
|
(list :invalid-published-anchor mount))))
|
||||||
(when (tp--surface-mount-anchor mount)
|
(when (tp--surface-mount-anchor mount)
|
||||||
(signal 'tp-surface-error
|
(signal 'tp-surface-error
|
||||||
(list :invalid-content-anchor mount)))))))
|
(list :invalid-content-anchor mount)))))))))
|
||||||
state))
|
state))
|
||||||
|
|
||||||
(defun tp--surface-prevalidate-cleanup (state)
|
(defun tp--surface-prevalidate-cleanup (state)
|
||||||
"Validate marker-backed cleanup lists retained in surface STATE."
|
"Validate marker-backed cleanup lists retained in surface STATE."
|
||||||
(dolist (entry (gethash 'snapshots state))
|
(dolist (entry (gethash 'snapshots state))
|
||||||
(let* ((prepared (car entry))
|
(let ((prepared (car entry)))
|
||||||
(snapshot (cdr entry))
|
(unless (tp--prepared-surface-retained-mount-state-p prepared)
|
||||||
(surface (tp--prepared-surface-surface prepared))
|
(let* ((snapshot (cdr entry))
|
||||||
(coordinate-p
|
(surface (tp--prepared-surface-surface prepared))
|
||||||
(plist-get (tp--surface-options surface) :coordinate-mounts)))
|
(coordinate-p
|
||||||
(dolist (mount (tp--surface-snapshot-mounts snapshot))
|
(plist-get (tp--surface-options surface) :coordinate-mounts)))
|
||||||
(when (eq (tp--surface-mount-capability mount) 'content)
|
(dolist (mount (tp--surface-snapshot-mounts snapshot))
|
||||||
(unless (or (and (markerp (tp--surface-mount-start mount))
|
(when (eq (tp--surface-mount-capability mount) 'content)
|
||||||
(markerp (tp--surface-mount-end mount)))
|
(unless (or (and (markerp (tp--surface-mount-start mount))
|
||||||
(and coordinate-p
|
(markerp (tp--surface-mount-end mount)))
|
||||||
(integerp (tp--surface-mount-start mount))
|
(and coordinate-p
|
||||||
(integerp (tp--surface-mount-end mount))))
|
(integerp (tp--surface-mount-start mount))
|
||||||
(signal 'tp-surface-error
|
(integerp (tp--surface-mount-end mount))))
|
||||||
(list :invalid-mount-cleanup mount)))))
|
(signal 'tp-surface-error
|
||||||
(dolist (ledger (tp--surface-snapshot-ledger snapshot))
|
(list :invalid-mount-cleanup mount)))))
|
||||||
(unless (and (markerp (tp--property-ledger-start ledger))
|
(dolist (ledger (tp--surface-snapshot-ledger snapshot))
|
||||||
(markerp (tp--property-ledger-end ledger)))
|
(unless (and (markerp (tp--property-ledger-start ledger))
|
||||||
(signal 'tp-surface-error
|
(markerp (tp--property-ledger-end ledger)))
|
||||||
(list :invalid-ledger-cleanup ledger))))))
|
(signal 'tp-surface-error
|
||||||
|
(list :invalid-ledger-cleanup ledger))))))))
|
||||||
state)
|
state)
|
||||||
|
|
||||||
(defun tp--surface-final-accept ()
|
(defun tp--surface-final-accept ()
|
||||||
@ -2873,12 +3457,13 @@ When RETAIN-MARKERS is non-nil, keep snapshot markers for rollback."
|
|||||||
(let* ((prepared (car entry))
|
(let* ((prepared (car entry))
|
||||||
(snapshot (cdr entry))
|
(snapshot (cdr entry))
|
||||||
(surface (tp--prepared-surface-surface prepared)))
|
(surface (tp--prepared-surface-surface prepared)))
|
||||||
(dolist (mounts
|
(unless (tp--prepared-surface-retained-mount-state-p prepared)
|
||||||
(list (tp--surface-snapshot-mounts snapshot)
|
(dolist (mounts
|
||||||
(tp--surface-mounts surface)))
|
(list (tp--surface-snapshot-mounts snapshot)
|
||||||
(dolist (mount mounts)
|
(tp--surface-mounts surface)))
|
||||||
(when-let ((anchor (tp--surface-mount-anchor mount)))
|
(dolist (mount mounts)
|
||||||
(push anchor anchors))))))
|
(when-let ((anchor (tp--surface-mount-anchor mount)))
|
||||||
|
(push anchor anchors)))))))
|
||||||
(puthash 'anchor-undo
|
(puthash 'anchor-undo
|
||||||
(tp--capture-anchor-ownership-undo (nreverse anchors))
|
(tp--capture-anchor-ownership-undo (nreverse anchors))
|
||||||
state))
|
state))
|
||||||
@ -2886,9 +3471,10 @@ When RETAIN-MARKERS is non-nil, keep snapshot markers for rollback."
|
|||||||
(let* ((prepared (car entry))
|
(let* ((prepared (car entry))
|
||||||
(snapshot (cdr entry))
|
(snapshot (cdr entry))
|
||||||
(surface (tp--prepared-surface-surface prepared)))
|
(surface (tp--prepared-surface-surface prepared)))
|
||||||
(tp--apply-anchor-ownership
|
(unless (tp--prepared-surface-retained-mount-state-p prepared)
|
||||||
surface (tp--surface-snapshot-mounts snapshot)
|
(tp--apply-anchor-ownership
|
||||||
(tp--surface-mounts surface)))))
|
surface (tp--surface-snapshot-mounts snapshot)
|
||||||
|
(tp--surface-mounts surface))))))
|
||||||
(tp--surface-precommit-step 'lifecycle state)
|
(tp--surface-precommit-step 'lifecycle state)
|
||||||
(dolist (prepared all-prepared)
|
(dolist (prepared all-prepared)
|
||||||
(tp--finalize-object-lifecycle prepared)
|
(tp--finalize-object-lifecycle prepared)
|
||||||
@ -3097,23 +3683,24 @@ When RETAIN-MARKERS is non-nil, keep snapshot markers for rollback."
|
|||||||
(let* ((prepared (car entry))
|
(let* ((prepared (car entry))
|
||||||
(snapshot (cdr entry))
|
(snapshot (cdr entry))
|
||||||
(surface (tp--prepared-surface-surface prepared)))
|
(surface (tp--prepared-surface-surface prepared)))
|
||||||
(dolist (anchor
|
(unless (tp--prepared-surface-retained-mount-state-p prepared)
|
||||||
(tp--anchors-in-mounts
|
(dolist (anchor
|
||||||
(tp--surface-snapshot-mounts snapshot)))
|
(tp--anchors-in-mounts
|
||||||
(when (null (tp--anchor-surfaces anchor))
|
(tp--surface-snapshot-mounts snapshot)))
|
||||||
(cleanup
|
(when (null (tp--anchor-surfaces anchor))
|
||||||
surface (list 'old-anchor (tp--anchor-id anchor))
|
(cleanup
|
||||||
(lambda () (tp--dispose-anchor anchor)))))
|
surface (list 'old-anchor (tp--anchor-id anchor))
|
||||||
(cleanup
|
(lambda () (tp--dispose-anchor anchor)))))
|
||||||
surface 'old-mounts
|
(cleanup
|
||||||
(lambda ()
|
surface 'old-mounts
|
||||||
(tp--dispose-content-mounts
|
(lambda ()
|
||||||
(tp--surface-snapshot-mounts snapshot))))
|
(tp--dispose-content-mounts
|
||||||
(cleanup
|
(tp--surface-snapshot-mounts snapshot))))
|
||||||
surface 'old-ledger
|
(cleanup
|
||||||
(lambda ()
|
surface 'old-ledger
|
||||||
(tp--dispose-ledger
|
(lambda ()
|
||||||
(tp--surface-snapshot-ledger snapshot))))))
|
(tp--dispose-ledger
|
||||||
|
(tp--surface-snapshot-ledger snapshot)))))))
|
||||||
(when-let ((table (gethash 'prepared state)))
|
(when-let ((table (gethash 'prepared state)))
|
||||||
(dolist (prepared (tp--all-prepared-surfaces table))
|
(dolist (prepared (tp--all-prepared-surfaces table))
|
||||||
(let* ((surface (tp--prepared-surface-surface prepared))
|
(let* ((surface (tp--prepared-surface-surface prepared))
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user