fix: :data variables now trigger computed value updates via setq-local

Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-12-20 11:00:22 +00:00
parent c3ddeede64
commit ac0e163e66
2 changed files with 97 additions and 37 deletions

View File

@ -27,6 +27,7 @@
`(with-temp-buffer
(setq tp-layer-alist nil)
(setq tp-layer-groups nil)
(tp-reactive-reset)
,@body))
;;; ============================================================
@ -2426,17 +2427,11 @@ Returns list of (START END VALUE) intervals."
(tp-test-with-temp-buffer
(unwind-protect
(progn
(tp-define-layer test-dc-layer
:props (face (:foreground $tp-test-dc-color) help-echo $tp-test-dc-full-name)
:data (tp-test-dc-first tp-test-dc-last)
:compute ((tp-test-dc-full-name
(lambda ()
(concat tp-test-dc-first " " tp-test-dc-last)))))
;; Set data values
;; Set data values first
(setq tp-test-dc-color "blue")
(setq tp-test-dc-first "Jane")
(setq tp-test-dc-last "Smith")
;; Re-eval to trigger the compute
;; Define layer with :data and :compute
(tp-define-layer test-dc-layer
:props (face (:foreground $tp-test-dc-color) help-echo $tp-test-dc-full-name)
:data (tp-test-dc-first tp-test-dc-last)
@ -2450,10 +2445,10 @@ Returns list of (START END VALUE) intervals."
;; Check the computed value
(should (equal tp-test-dc-full-name "Jane Smith")))
;; Cleanup
(makunbound 'tp-test-dc-color)
(makunbound 'tp-test-dc-first)
(makunbound 'tp-test-dc-last)
(makunbound 'tp-test-dc-full-name))))
(ignore-errors (makunbound 'tp-test-dc-color))
(ignore-errors (makunbound 'tp-test-dc-first))
(ignore-errors (makunbound 'tp-test-dc-last))
(ignore-errors (makunbound 'tp-test-dc-full-name)))))
(ert-deftest tp-test-define-layer-watch-requires-props ()
"Test that :watch requires :props to be explicitly specified."
@ -2629,5 +2624,35 @@ Returns list of (START END VALUE) intervals."
;; Cleanup
(makunbound 'tp-test-local-color))))
(ert-deftest tp-test-data-setq-local-triggers-compute ()
"Test that setq-local on :data variables triggers computed value updates."
(tp-test-with-temp-buffer
(unwind-protect
(progn
;; Define layer with :data and :compute
(tp-define-layer test-data-compute-layer
:props (help-echo $tp-test-dc-full)
:data (tp-test-dc-first tp-test-dc-last)
:compute ((tp-test-dc-full
(lambda ()
(concat tp-test-dc-first " " tp-test-dc-last)))))
;; Apply layer to text
(insert "Hello World")
(tp-set 1 6 'test-data-compute-layer)
;; Initial computed value should be " " (concat nil nil = " ")
(should (equal (get-text-property 1 'help-echo) " "))
;; Use setq-local to set first name
(setq-local tp-test-dc-first "Kinney")
;; Computed should be "Kinney " now
(should (equal (get-text-property 1 'help-echo) "Kinney "))
;; Use setq-local to set last name
(setq-local tp-test-dc-last "Zhang")
;; Computed should be "Kinney Zhang" now
(should (equal (get-text-property 1 'help-echo) "Kinney Zhang")))
;; Cleanup
(ignore-errors (makunbound 'tp-test-dc-first))
(ignore-errors (makunbound 'tp-test-dc-last))
(ignore-errors (makunbound 'tp-test-dc-full)))))
(provide 'tp-ert-tests)
;;; tp-ert-tests.el ends here

85
tp.el
View File

@ -161,15 +161,15 @@ override the current variable values (used during watcher callbacks)."
(tp--resolve-reactive-symbols (cdr form) override-alist)))
(t form)))
(defun tp--register-reactive-deps (layer-name reactive-symbols template-props)
(defun tp--register-reactive-deps (layer-name reactive-symbols props)
"Register REACTIVE-SYMBOLS as dependencies for LAYER-NAME.
TEMPLATE-PROPS is the original property specification with reactive symbols.
PROPS is the original property specification with reactive symbols.
Only the reactive portions of the properties are stored for each variable."
;; Register each reactive symbol's dependency with only its relevant properties
(dolist (rsym reactive-symbols)
(let* ((var-sym (tp--reactive-var-symbol rsym))
;; Extract only the properties that use this specific reactive variable
(reactive-props (tp--extract-reactive-props template-props rsym))
(reactive-props (tp--extract-reactive-props props rsym))
(existing (assoc var-sym tp-reactive-deps)))
(if existing
;; Update or add this layer to existing dependencies
@ -228,23 +228,23 @@ Only 'set' operations trigger updates because:
(let* ((layer-name (car dep))
;; Get the reactive props stored directly in the dependency
(reactive-props (cdr dep)))
(when reactive-props
;; Call user-defined watch callbacks for this layer
(tp--invoke-layer-watchers layer-name symbol newval oldval)
;; Update computed properties for this layer
(tp--update-layer-computed layer-name override-alist)
;; Resolve the reactive props with the new value override
(let ((resolved-props (tp--resolve-reactive-symbols
reactive-props override-alist)))
;; Update only the reactive properties in the layer definition
(let ((current-props (cdr (assoc layer-name tp-layer-alist))))
(when current-props
;; Merge the resolved reactive props into the current layer props
(cl-loop for (key val) on resolved-props by #'cddr
do (setq current-props (plist-put current-props key val)))
(tp--set-layer-props layer-name current-props)))
;; Update all text regions with this layer
(tp--update-layer-regions layer-name))))))))
;; Call user-defined watch callbacks for this layer
(tp--invoke-layer-watchers layer-name symbol newval oldval)
;; Update computed properties for this layer
(let ((updated-override (tp--update-layer-computed layer-name override-alist)))
(when reactive-props
;; Resolve the reactive props with the new value override
(let ((resolved-props (tp--resolve-reactive-symbols
reactive-props updated-override)))
;; Update only the reactive properties in the layer definition
(let ((current-props (cdr (assoc layer-name tp-layer-alist))))
(when current-props
;; Merge the resolved reactive props into the current layer props
(cl-loop for (key val) on resolved-props by #'cddr
do (setq current-props (plist-put current-props key val)))
(tp--set-layer-props layer-name current-props))))))
;; Update all text regions with this layer
(tp--update-layer-regions layer-name))))))
(defun tp--invoke-layer-watchers (layer-name symbol newval oldval)
"Invoke all registered watcher callbacks for LAYER-NAME watching SYMBOL.
@ -267,9 +267,14 @@ Returns an updated override-alist with the new computed values."
(dolist (comp computed)
(let* ((var-sym (car comp))
(compute-fn (cdr comp))
;; Temporarily bind variables to their new values from override-alist
;; before calling the compute function
(computed-val
(condition-case err
(funcall compute-fn)
(cl-progv
(mapcar #'car override-alist)
(mapcar #'cdr override-alist)
(funcall compute-fn))
(error
(message "tp: compute error for %s.%s: %s"
layer-name var-sym err)
@ -278,7 +283,25 @@ Returns an updated override-alist with the new computed values."
;; Update the global variable
(set var-sym computed-val)
;; Add to override-alist for property resolution
(push (cons var-sym computed-val) override-alist)))))
(push (cons var-sym computed-val) override-alist)
;; Also update the layer properties if the computed var is used in props
(let ((current-props (cdr (assoc layer-name tp-layer-alist))))
(when current-props
;; Collect all reactive props for this layer from tp-reactive-deps
(let ((all-reactive-props nil))
(dolist (dep tp-reactive-deps)
(let ((layer-entry (assoc layer-name (cdr dep))))
(when (and layer-entry (cdr layer-entry))
;; Merge the reactive props
(cl-loop for (key val) on (cdr layer-entry) by #'cddr
do (setq all-reactive-props
(plist-put all-reactive-props key val))))))
(when all-reactive-props
(let ((resolved-props (tp--resolve-reactive-symbols all-reactive-props override-alist)))
(when resolved-props
(cl-loop for (key val) on resolved-props by #'cddr
do (setq current-props (plist-put current-props key val)))
(tp--set-layer-props layer-name current-props)))))))))))
override-alist)
(defun tp--register-layer-watchers (layer-name watchers)
@ -330,11 +353,23 @@ Sets the global variables to their computed values."
(defun tp--register-layer-data (layer-name data-vars)
"Register DATA-VARS for LAYER-NAME.
DATA-VARS is a list of variable symbols defined via :data."
DATA-VARS is a list of variable symbols defined via :data.
Also adds variable watchers so changes to data vars trigger computed updates."
(when data-vars
(if (assoc layer-name tp-layer-data)
(setf (cdr (assoc layer-name tp-layer-data)) data-vars)
(push (cons layer-name data-vars) tp-layer-data))))
(push (cons layer-name data-vars) tp-layer-data))
;; Add watchers for data variables
(dolist (var-sym data-vars)
(let ((existing (assoc var-sym tp-reactive-deps)))
(if existing
;; Add this layer to existing dependencies (with nil props since data vars don't have direct props)
(let ((layer-entry (assoc layer-name (cdr existing))))
(unless layer-entry
(push (cons layer-name nil) (cdr existing))))
;; Create new dependency entry and add watcher
(push (cons var-sym (list (cons layer-name nil))) tp-reactive-deps)
(add-variable-watcher var-sym #'tp--reactive-variable-watcher))))))
(defun tp--unregister-layer-data (layer-name)
"Unregister data variables for LAYER-NAME."
@ -2293,7 +2328,7 @@ For group names, includes `tp-layers' property with the full layer stack."
(resolved-props (tp--resolve-reactive-symbols props)))
;; Register this anonymous layer in tp-layer-alist with resolved props
(tp--set-layer-props layer-name resolved-props)
;; Register reactive dependencies with the original template props
;; Register reactive dependencies with the original props
(tp--register-reactive-deps layer-name reactive-syms props)
;; Return resolved props with tp-name
(append resolved-props (list 'tp-name layer-name)))