Simplify reactive deps: remove tp-layer-templates, store only reactive props

Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-12-20 06:36:01 +00:00
parent 072b1b3d7d
commit c22b596883
2 changed files with 67 additions and 42 deletions

View File

@ -1872,6 +1872,17 @@ Returns list of (START END VALUE) intervals."
'($color $bg)))
(should (null (tp--collect-reactive-symbols '(face bold)))))
(ert-deftest tp-test-extract-reactive-props ()
"Test tp--extract-reactive-props extracts only properties using a reactive var."
;; Single reactive property
(should (equal (tp--extract-reactive-props '(help-echo "test" face (:foreground $color)) '$color)
'(face (:foreground $color))))
;; Multiple properties, only one uses the variable
(should (equal (tp--extract-reactive-props '(help-echo "test" face (:foreground $color :background "green")) '$color)
'(face (:foreground $color :background "green"))))
;; No properties use the variable
(should (null (tp--extract-reactive-props '(help-echo "test" face bold) '$color))))
(ert-deftest tp-test-resolve-reactive-symbols ()
"Test tp--resolve-reactive-symbols replaces $foo with variable values."
;; Use defvar to create dynamically-bound variables
@ -1899,10 +1910,14 @@ Returns list of (START END VALUE) intervals."
;; Check the layer is defined with resolved value
(let ((props (cdr (assoc 'test-reactive-layer tp-layer-alist))))
(should (equal (plist-get (plist-get props 'face) :foreground) "red")))
;; Check the template is stored
(should (assoc 'test-reactive-layer tp-layer-templates))
;; Check the dependency is registered
(should (assoc 'tp-test-var-color tp-reactive-deps)))
;; Check the dependency is registered with only reactive props
(should (assoc 'tp-test-var-color tp-reactive-deps))
;; Check the stored reactive props only contain the face property
(let* ((deps (cdr (assoc 'tp-test-var-color tp-reactive-deps)))
(layer-dep (assoc 'test-reactive-layer deps)))
(should layer-dep)
;; The stored props should be just the reactive portion
(should (plist-get (cdr layer-dep) 'face))))
;; Cleanup
(makunbound 'tp-test-var-color))))
@ -1957,10 +1972,8 @@ Returns list of (START END VALUE) intervals."
(tp-define-layer test-reactive-reset
(face (:foreground $tp-test-reset-color)))
(should tp-reactive-deps)
(should tp-layer-templates)
(tp-reactive-reset)
(should-not tp-reactive-deps)
(should-not tp-layer-templates))
(should-not tp-reactive-deps))
;; Cleanup
(makunbound 'tp-test-reset-color))))
@ -1975,8 +1988,7 @@ Returns list of (START END VALUE) intervals."
(face (:foreground $tp-test-reset2-color)))
(should tp-reactive-deps)
(tp-layer-reset)
(should-not tp-reactive-deps)
(should-not tp-layer-templates))
(should-not tp-reactive-deps))
;; Cleanup
(makunbound 'tp-test-reset2-color))))
@ -1996,10 +2008,12 @@ Returns list of (START END VALUE) intervals."
;; Check the non-reactive layer is defined
(let ((props (cdr (assoc 'test-reactive-group-second tp-layer-alist))))
(should (equal (plist-get (plist-get props 'face) :foreground) "blue")))
;; Check the template is stored for reactive layer
(should (assoc 'test-reactive-group-first tp-layer-templates))
;; Non-reactive layer should not have template
(should-not (assoc 'test-reactive-group-second tp-layer-templates)))
;; Check the reactive layer is registered in tp-reactive-deps
(should (assoc 'tp-test-group-color tp-reactive-deps))
;; The reactive layer should be in the dependencies
(let* ((deps (cdr (assoc 'tp-test-group-color tp-reactive-deps)))
(layer-dep (assoc 'test-reactive-group-first deps)))
(should layer-dep)))
;; Cleanup
(makunbound 'tp-test-group-color))))
@ -2012,10 +2026,12 @@ Returns list of (START END VALUE) intervals."
(progn
(tp-define-layer test-undef-reactive
(face (:foreground $tp-test-undef-color)))
(should (assoc 'test-undef-reactive tp-layer-templates))
;; Check the dependency is registered
(should (assoc 'tp-test-undef-color tp-reactive-deps))
(let* ((deps (cdr (assoc 'tp-test-undef-color tp-reactive-deps)))
(layer-dep (assoc 'test-undef-reactive deps)))
(should layer-dep))
(tp-undefine-layer 'test-undef-reactive)
(should-not (assoc 'test-undef-reactive tp-layer-templates))
;; Dependency should be cleaned up if no other layers use it
(should-not (cdr (assoc 'tp-test-undef-color tp-reactive-deps))))
;; Cleanup

63
tp.el
View File

@ -53,13 +53,10 @@ Stores layer group definitions, where each group contains multiple layer names."
(defvar tp-reactive-deps nil
"Alist mapping reactive variables to their dependent layers.
Each element is (VARIABLE-SYMBOL . ((LAYER-NAME . PROPERTY-SPEC) ...)).
PROPERTY-SPEC is the original property specification containing the variable.")
(defvar tp-layer-templates nil
"Alist mapping layer names to their template property specifications.
Each element is (LAYER-NAME . TEMPLATE-PLIST).
TEMPLATE-PLIST contains symbols starting with $ that need to be resolved.")
Each element is (VARIABLE-SYMBOL . ((LAYER-NAME . REACTIVE-PROPS) ...)).
REACTIVE-PROPS contains only the property key-value pairs that use this variable.
For example, for (define-tp my-layer (help-echo \"test\" face (:foreground $color))),
only (face (:foreground $color)) is stored, not the help-echo.")
(defvar tp-reactive-enabled t
"Non-nil means reactive text property updates are enabled.")
@ -89,6 +86,16 @@ Returns a list of reactive symbols found."
(tp--collect-reactive-symbols (cdr form))))
(t nil)))
(defun tp--extract-reactive-props (plist reactive-var)
"Extract only the properties from PLIST that use REACTIVE-VAR.
Returns a plist containing only the key-value pairs that reference REACTIVE-VAR.
REACTIVE-VAR should be the $-prefixed symbol (e.g., $my-color)."
(let ((result nil))
(cl-loop for (key val) on plist by #'cddr
when (member reactive-var (tp--collect-reactive-symbols val))
do (setq result (plist-put result key val)))
result))
(defun tp--resolve-reactive-symbols (form &optional override-alist)
"Recursively resolve all reactive symbols in FORM to their values.
Reactive symbols ($foo) are replaced with the value of the variable foo.
@ -110,32 +117,29 @@ override the current variable values (used during watcher callbacks)."
(defun tp--register-reactive-deps (layer-name reactive-symbols template-props)
"Register REACTIVE-SYMBOLS as dependencies for LAYER-NAME.
TEMPLATE-PROPS is the original property specification with reactive symbols."
;; Store the template for this layer
(if (assoc layer-name tp-layer-templates)
(setf (cdr (assoc layer-name tp-layer-templates)) template-props)
(push (cons layer-name template-props) tp-layer-templates))
;; Register each reactive symbol's dependency
TEMPLATE-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))
(existing (assoc var-sym tp-reactive-deps)))
(if existing
;; Update or add this layer to existing dependencies
(let ((layer-entry (assoc layer-name (cdr existing))))
(if layer-entry
;; Update existing entry with new template-props
(setf (cdr layer-entry) template-props)
;; Update existing entry with new reactive-props
(setf (cdr layer-entry) reactive-props)
;; Add new layer entry
(push (cons layer-name template-props) (cdr existing))))
(push (cons layer-name reactive-props) (cdr existing))))
;; Create new dependency entry and add watcher
(push (cons var-sym (list (cons layer-name template-props))) tp-reactive-deps)
(push (cons var-sym (list (cons layer-name reactive-props))) tp-reactive-deps)
;; Add variable watcher for this variable
(add-variable-watcher var-sym #'tp--reactive-variable-watcher)))))
(defun tp--unregister-reactive-deps (layer-name)
"Unregister all reactive dependencies for LAYER-NAME."
;; Remove from templates
(setq tp-layer-templates (assq-delete-all layer-name tp-layer-templates))
;; Collect variables that need watcher removal
(let ((vars-to-clean nil))
;; First pass: remove layer from dependencies and collect empty vars
@ -170,12 +174,18 @@ Only 'set' operations trigger updates because:
(override-alist (list (cons symbol newval))))
(dolist (dep deps)
(let* ((layer-name (car dep))
(template (cdr (assoc layer-name tp-layer-templates))))
(when template
;; Resolve the template with the new value override
(let ((resolved-props (tp--resolve-reactive-symbols template override-alist)))
;; Update the layer definition using the helper function
(tp--set-layer-props layer-name resolved-props)
;; Get the reactive props stored directly in the dependency
(reactive-props (cdr dep)))
(when reactive-props
;; 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))))))))
@ -206,8 +216,7 @@ Re-applies the layer properties using tp-search-map and tp-add."
(let ((var-sym (car dep)))
(remove-variable-watcher var-sym #'tp--reactive-variable-watcher)))
;; Clear registries
(setq tp-reactive-deps nil)
(setq tp-layer-templates nil))
(setq tp-reactive-deps nil))
;;; Core Property Functions