diff --git a/tp-tests.el b/tp-tests.el index 3448995..26cd768 100644 --- a/tp-tests.el +++ b/tp-tests.el @@ -3044,6 +3044,52 @@ preserving the native text property behavior." ;; Cleanup (makunbound 'tp-test-reactive-text)))) +(ert-deftest tp-test-tp-text-reactive-nil-initializes-variable () + "Test tp-text with nil reactive variable initializes the variable to source text. +When tp-text is bound to a reactive variable and that variable is nil, +the source text should be used and the reactive variable should be updated." + (tp-test-with-temp-buffer + (defvar tp-test-text-var nil "Test variable for tp-text initialization.") + (setq tp-test-text-var nil) + (unwind-protect + (progn + ;; Define layer with tp-text bound to a reactive variable + (tp-define-layer 'test-init-text-layer + :props '(face bold tp-text $tp-test-text-var)) + ;; Apply layer to string - variable is nil, so source text should be used + (let ((result (tp-set "2" 'test-init-text-layer))) + ;; Result should be the source text "2" + (should (equal result "2")) + ;; tp-test-text-var should now be "2" + (should (equal tp-test-text-var "2")) + ;; tp-text property should be "2" + (should (equal (tp-at 0 'tp-text result) "2"))) + ;; Now set the variable to a different value and test again + (setq tp-test-text-var "18") + ;; Redefine layer to reset resolved props to the new variable value. + ;; This is necessary because the layer definition caches the resolved + ;; tp-text value, and we want to test the behavior when the variable + ;; already has a non-nil value at layer application time. + (tp-define-layer 'test-init-text-layer + :props '(face bold tp-text $tp-test-text-var)) + (let ((result (tp-set "2" 'test-init-text-layer))) + ;; Result should be the variable value "18", not source "2" + (should (equal result "18")) + ;; Variable should remain "18" + (should (equal tp-test-text-var "18")))) + ;; Cleanup + (makunbound 'tp-test-text-var)))) + +(ert-deftest tp-test-tp-text-direct-string-uses-specified-text () + "Test tp-text with direct string value uses that string, not source text. +When tp-text is set directly to a string (not a reactive variable), +the inserted text should be that string, not the source text." + (let ((result (tp-set "2" 'tp-text "23"))) + ;; Result should be "23", not "2" + (should (equal result "23")) + ;; tp-text property should be "23" + (should (equal (tp-at 0 'tp-text result) "23")))) + (ert-deftest tp-test-tp-text-reactive-computed () "Test tp-text with computed reactive variable." (tp-test-with-temp-buffer diff --git a/tp.el b/tp.el index 6e74eba..5eebd3e 100644 --- a/tp.el +++ b/tp.el @@ -576,6 +576,26 @@ WHERE specifies which buffers to update: ;;; Layer 4: Reactive Text (tp-text property) ;;;============================================================================ +(defun tp--find-tp-text-reactive-var (layer-name) + "Find the reactive variable symbol used for tp-text in LAYER-NAME. +Returns the variable symbol (e.g., tp-test-counter) if tp-text uses a +reactive variable (e.g., $tp-test-counter), or nil if not found. +Searches through `tp-reactive-deps' to find the original reactive props." + (catch 'found + (dolist (dep tp-reactive-deps) + (let* ((var-sym (car dep)) + (layer-entry (assoc layer-name (cdr dep)))) + (when layer-entry + (let ((reactive-props (cdr layer-entry))) + ;; Check if tp-text in reactive-props uses this variable + (when (plist-member reactive-props 'tp-text) + (let ((tp-text-val (plist-get reactive-props 'tp-text))) + ;; Check if tp-text-val is a reactive symbol for this variable + (when (and (tp--reactive-symbol-p tp-text-val) + (eq (tp--reactive-var-symbol tp-text-val) var-sym)) + (throw 'found var-sym)))))))) + nil)) + (defun tp--update-reactive-text (layer-name &optional where) "Update text regions that have tp-text property with LAYER-NAME applied. This is called when a reactive variable bound to tp-text changes. @@ -644,6 +664,20 @@ NEW-OBJECT is the new string object (only different for strings with tp-text)." (with-current-buffer object (buffer-substring-no-properties start end)) (buffer-substring-no-properties start end))))) + ;; If tp-text uses a reactive variable, update that variable to match + ;; This ensures the reactive variable and buffer text stay in sync + (when-let ((layer-name (plist-get props 'tp-name))) + (when-let ((reactive-var (tp--find-tp-text-reactive-var layer-name))) + ;; Update the reactive variable with the current text + ;; Note: Using global `set` here because the layer definition is global. + ;; When the variable is changed, the reactive watcher will update all + ;; buffers that have this layer applied. + (set reactive-var current-text) + ;; Also update the layer definition so future accesses see the new value + (let ((layer-props (cdr (assoc layer-name tp-layer-alist)))) + (when layer-props + (tp--set-layer-props layer-name + (plist-put layer-props 'tp-text current-text)))))) (list (plist-put props 'tp-text current-text) end object))) ;; tp-text has a string value - replace the text in the region ((stringp tp-text-val)