feat: Support anonymous reactive layers with auto-generated tp-name
- Add tp--generate-anonymous-layer-name to create unique layer names (tp-anon-*) - Update tp--resolve-props to handle anonymous plists: - If plist has reactive variables ($...), generate tp-name and register dependencies - All anonymous plists now get a tp-name for reactive support - Update tp--parse-args to process all props through tp--resolve-props - Update tp--ensure-props to handle plists with reactive variables - Add tests for anonymous reactive layers with tp-set, tp-match-set, tp-regexp-set Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
This commit is contained in:
parent
736cfc59d9
commit
56733e7473
94
tp-tests.el
94
tp-tests.el
@ -2242,5 +2242,99 @@ Returns list of (START END VALUE) intervals."
|
|||||||
(should (equal (plist-get (plist-get face :underline) :style) 'wave)))
|
(should (equal (plist-get (plist-get face :underline) :style) 'wave)))
|
||||||
(should (equal (tp-at 1 'help-echo) "complex"))))
|
(should (equal (tp-at 1 'help-echo) "complex"))))
|
||||||
|
|
||||||
|
;;; ============================================================
|
||||||
|
;;; Anonymous Layer and Reactive Text Property Tests
|
||||||
|
;;; ============================================================
|
||||||
|
|
||||||
|
(ert-deftest tp-test-set-anonymous-layer-gets-tp-name ()
|
||||||
|
"Test that tp-set with anonymous plist gets a tp-name."
|
||||||
|
(tp-test-with-temp-buffer
|
||||||
|
(insert "Hello World")
|
||||||
|
(tp-set 1 6 '(face bold))
|
||||||
|
;; Anonymous layer should have a generated tp-name
|
||||||
|
(should (tp-at 1 'tp-name))
|
||||||
|
;; The tp-name should be a symbol starting with tp-anon-
|
||||||
|
(should (string-prefix-p "tp-anon-" (symbol-name (tp-at 1 'tp-name))))))
|
||||||
|
|
||||||
|
(ert-deftest tp-test-set-anonymous-reactive-layer ()
|
||||||
|
"Test that tp-set with anonymous reactive plist works."
|
||||||
|
(tp-test-with-temp-buffer
|
||||||
|
(defvar tp-test-anon-color nil "Test variable for anonymous reactive layer.")
|
||||||
|
(setq tp-test-anon-color "red")
|
||||||
|
(unwind-protect
|
||||||
|
(progn
|
||||||
|
(insert "Hello World")
|
||||||
|
;; Set with anonymous reactive plist
|
||||||
|
(tp-set 1 6 '(face (:foreground $tp-test-anon-color)))
|
||||||
|
;; Should have resolved the reactive variable
|
||||||
|
(let ((face (tp-at 1 'face)))
|
||||||
|
(should (equal (plist-get face :foreground) "red")))
|
||||||
|
;; Should have a generated tp-name
|
||||||
|
(should (tp-at 1 'tp-name))
|
||||||
|
;; The reactive variable should be registered in dependencies
|
||||||
|
(should (assoc 'tp-test-anon-color tp-reactive-deps))
|
||||||
|
;; Change the variable - should update the text
|
||||||
|
(setq tp-test-anon-color "blue")
|
||||||
|
(let ((face (tp-at 1 'face)))
|
||||||
|
(should (equal (plist-get face :foreground) "blue"))))
|
||||||
|
;; Cleanup
|
||||||
|
(makunbound 'tp-test-anon-color))))
|
||||||
|
|
||||||
|
(ert-deftest tp-test-set-anonymous-layer-preserves-existing-tp-name ()
|
||||||
|
"Test that tp-set with anonymous plist preserves existing tp-name."
|
||||||
|
(tp-test-with-temp-buffer
|
||||||
|
(insert "Hello World")
|
||||||
|
;; First set with a layer name
|
||||||
|
(tp-define-layer my-existing-layer (face bold))
|
||||||
|
(tp-set 1 6 'my-existing-layer)
|
||||||
|
(should (eq (tp-at 1 'tp-name) 'my-existing-layer))
|
||||||
|
;; Now set with anonymous plist that already has tp-name
|
||||||
|
(tp-set 1 6 '(face italic tp-name my-custom-name))
|
||||||
|
(should (eq (tp-at 1 'tp-name) 'my-custom-name))))
|
||||||
|
|
||||||
|
(ert-deftest tp-test-match-set-anonymous-reactive-layer ()
|
||||||
|
"Test that tp-match-set with anonymous reactive plist works."
|
||||||
|
(tp-test-with-temp-buffer
|
||||||
|
(defvar tp-test-match-color nil "Test variable for match reactive layer.")
|
||||||
|
(setq tp-test-match-color "green")
|
||||||
|
(unwind-protect
|
||||||
|
(progn
|
||||||
|
(insert "Hello World Hello")
|
||||||
|
;; Set with anonymous reactive plist
|
||||||
|
(tp-match-set "Hello" '(face (:foreground $tp-test-match-color)))
|
||||||
|
;; Should have resolved the reactive variable
|
||||||
|
(let ((face (tp-at 1 'face)))
|
||||||
|
(should (equal (plist-get face :foreground) "green")))
|
||||||
|
;; Should have a generated tp-name
|
||||||
|
(should (tp-at 1 'tp-name))
|
||||||
|
;; Change the variable - should update the text
|
||||||
|
(setq tp-test-match-color "yellow")
|
||||||
|
(let ((face (tp-at 1 'face)))
|
||||||
|
(should (equal (plist-get face :foreground) "yellow"))))
|
||||||
|
;; Cleanup
|
||||||
|
(makunbound 'tp-test-match-color))))
|
||||||
|
|
||||||
|
(ert-deftest tp-test-regexp-set-anonymous-reactive-layer ()
|
||||||
|
"Test that tp-regexp-set with anonymous reactive plist works."
|
||||||
|
(tp-test-with-temp-buffer
|
||||||
|
(defvar tp-test-regexp-color nil "Test variable for regexp reactive layer.")
|
||||||
|
(setq tp-test-regexp-color "purple")
|
||||||
|
(unwind-protect
|
||||||
|
(progn
|
||||||
|
(insert "abc 123 def 456")
|
||||||
|
;; Set with anonymous reactive plist
|
||||||
|
(tp-regexp-set "[0-9]+" '(face (:foreground $tp-test-regexp-color)))
|
||||||
|
;; Should have resolved the reactive variable
|
||||||
|
(let ((face (tp-at 5 'face)))
|
||||||
|
(should (equal (plist-get face :foreground) "purple")))
|
||||||
|
;; Should have a generated tp-name
|
||||||
|
(should (tp-at 5 'tp-name))
|
||||||
|
;; Change the variable - should update the text
|
||||||
|
(setq tp-test-regexp-color "orange")
|
||||||
|
(let ((face (tp-at 5 'face)))
|
||||||
|
(should (equal (plist-get face :foreground) "orange"))))
|
||||||
|
;; Cleanup
|
||||||
|
(makunbound 'tp-test-regexp-color))))
|
||||||
|
|
||||||
(provide 'tp-ert-tests)
|
(provide 'tp-ert-tests)
|
||||||
;;; tp-ert-tests.el ends here
|
;;; tp-ert-tests.el ends here
|
||||||
|
|||||||
66
tp.el
66
tp.el
@ -61,6 +61,17 @@ only (face (:foreground $color)) is stored, not the help-echo.")
|
|||||||
(defvar tp-reactive-enabled t
|
(defvar tp-reactive-enabled t
|
||||||
"Non-nil means reactive text property updates are enabled.")
|
"Non-nil means reactive text property updates are enabled.")
|
||||||
|
|
||||||
|
(defvar tp--anonymous-layer-counter 0
|
||||||
|
"Counter for generating unique anonymous layer names.")
|
||||||
|
|
||||||
|
(defun tp--generate-anonymous-layer-name ()
|
||||||
|
"Generate a unique symbol for anonymous layers.
|
||||||
|
Uses a combination of timestamp and counter to ensure uniqueness."
|
||||||
|
(setq tp--anonymous-layer-counter (1+ tp--anonymous-layer-counter))
|
||||||
|
(intern (format "tp-anon-%s-%d"
|
||||||
|
(format-time-string "%s%N")
|
||||||
|
tp--anonymous-layer-counter)))
|
||||||
|
|
||||||
|
|
||||||
;;; Reactive Text Properties Functions
|
;;; Reactive Text Properties Functions
|
||||||
|
|
||||||
@ -250,8 +261,12 @@ Supports four calling conventions:
|
|||||||
3. String region: (START END PROPS STRING)
|
3. String region: (START END PROPS STRING)
|
||||||
4. Entire string: (STRING PROP VAL ...)
|
4. Entire string: (STRING PROP VAL ...)
|
||||||
|
|
||||||
PROPS can also be a symbol representing a layer or group name defined
|
PROPS can be:
|
||||||
by `define-tp' or `define-tp-group', which will be resolved to its properties."
|
- A symbol representing a layer or group name defined by `define-tp' or `define-tp-group'
|
||||||
|
- A plist of properties (anonymous layers get a generated tp-name)
|
||||||
|
|
||||||
|
For anonymous plists with reactive variables ($...), a unique tp-name is generated
|
||||||
|
and reactive dependencies are registered automatically."
|
||||||
(let (object start finish props)
|
(let (object start finish props)
|
||||||
(cond
|
(cond
|
||||||
;; First arg is a string - apply to entire string
|
;; First arg is a string - apply to entire string
|
||||||
@ -275,10 +290,6 @@ by `define-tp' or `define-tp-group', which will be resolved to its properties."
|
|||||||
(setq object nil
|
(setq object nil
|
||||||
props props-or-val)))
|
props props-or-val)))
|
||||||
(t (error "Invalid first argument: %S" start-or-string)))
|
(t (error "Invalid first argument: %S" start-or-string)))
|
||||||
;; Resolve layer/group name to properties if props is a symbol.
|
|
||||||
;; This allows passing layer names like 'my-layer instead of property lists.
|
|
||||||
(when (symbolp props)
|
|
||||||
(setq props (or (tp--resolve-props props) props)))
|
|
||||||
;; Unwrap double-wrapped properties: when called as (tp-set 1 6 '(face bold)),
|
;; Unwrap double-wrapped properties: when called as (tp-set 1 6 '(face bold)),
|
||||||
;; props is already the plist. But when called internally or from certain
|
;; props is already the plist. But when called internally or from certain
|
||||||
;; contexts, props might be wrapped in an extra list like '((face bold)).
|
;; contexts, props might be wrapped in an extra list like '((face bold)).
|
||||||
@ -286,6 +297,11 @@ by `define-tp' or `define-tp-group', which will be resolved to its properties."
|
|||||||
;; a list (not just a symbol like 'face).
|
;; a list (not just a symbol like 'face).
|
||||||
(when (and (listp props) (listp (car-safe props)))
|
(when (and (listp props) (listp (car-safe props)))
|
||||||
(setq props (car props)))
|
(setq props (car props)))
|
||||||
|
;; Resolve props: handles layer/group names and anonymous reactive plists.
|
||||||
|
;; For symbols: resolves to layer/group properties with tp-name/tp-layers.
|
||||||
|
;; For plists: adds tp-name and handles reactive variables.
|
||||||
|
(when props
|
||||||
|
(setq props (or (tp--resolve-props props) props)))
|
||||||
(list object start finish props)))
|
(list object start finish props)))
|
||||||
|
|
||||||
(defun tp-set (start-or-string &optional end-or-prop props-or-val &rest rest)
|
(defun tp-set (start-or-string &optional end-or-prop props-or-val &rest rest)
|
||||||
@ -1956,19 +1972,43 @@ Appends 'tp-name property to identify the layer."
|
|||||||
"Resolve PROPS to a property list with layer metadata.
|
"Resolve PROPS to a property list with layer metadata.
|
||||||
PROPS can be:
|
PROPS can be:
|
||||||
- A symbol (layer name from `tp-layer-alist' or group name from `tp-layer-groups')
|
- A symbol (layer name from `tp-layer-alist' or group name from `tp-layer-groups')
|
||||||
- A plist (returned as-is)
|
- A plist (handles anonymous layers with reactive variables)
|
||||||
|
|
||||||
If PROPS is a symbol:
|
If PROPS is a symbol:
|
||||||
- First checks `tp-layer-alist' and returns the layer properties WITH `tp-name'
|
- First checks `tp-layer-alist' and returns the layer properties WITH `tp-name'
|
||||||
- Then checks `tp-layer-groups' and returns properties WITH `tp-layers'
|
- Then checks `tp-layer-groups' and returns properties WITH `tp-layers'
|
||||||
|
|
||||||
|
If PROPS is a plist:
|
||||||
|
- If it contains reactive variables ($...), generates a UUID for `tp-name',
|
||||||
|
registers reactive dependencies, and returns the resolved props with `tp-name'.
|
||||||
|
If the plist already has a `tp-name', uses that instead of generating a new one.
|
||||||
|
- If no reactive variables, adds a `tp-name' for the anonymous layer.
|
||||||
|
|
||||||
Returns nil if PROPS is a symbol but no matching layer/group is found.
|
Returns nil if PROPS is a symbol but no matching layer/group is found.
|
||||||
|
|
||||||
For layer names, includes `tp-name' property for reactive text property support.
|
For layer names, includes `tp-name' property for reactive text property support.
|
||||||
For group names, includes `tp-layers' property with the full layer stack."
|
For group names, includes `tp-layers' property with the full layer stack."
|
||||||
(cond
|
(cond
|
||||||
;; Already a plist - return as-is
|
;; Already a plist - check for reactive variables and add tp-name
|
||||||
((listp props) props)
|
((listp props)
|
||||||
|
(let* ((existing-tp-name (plist-get props 'tp-name))
|
||||||
|
(reactive-syms (tp--collect-reactive-symbols props)))
|
||||||
|
(if reactive-syms
|
||||||
|
;; Has reactive symbols - need to handle as anonymous reactive layer
|
||||||
|
(let* ((layer-name (or existing-tp-name (tp--generate-anonymous-layer-name)))
|
||||||
|
;; Resolve reactive symbols to get current values
|
||||||
|
(resolved-props (tp--resolve-reactive-symbols props)))
|
||||||
|
;; Register this anonymous layer in tp-layer-alist with original template
|
||||||
|
(tp--set-layer-props layer-name (tp--resolve-reactive-symbols props))
|
||||||
|
;; Register reactive dependencies with the original template props
|
||||||
|
(tp--register-reactive-deps layer-name reactive-syms props)
|
||||||
|
;; Return resolved props with tp-name
|
||||||
|
(append resolved-props (list 'tp-name layer-name)))
|
||||||
|
;; No reactive symbols - just add tp-name if not present
|
||||||
|
(if existing-tp-name
|
||||||
|
props
|
||||||
|
(let ((layer-name (tp--generate-anonymous-layer-name)))
|
||||||
|
(append props (list 'tp-name layer-name)))))))
|
||||||
;; Symbol - check if it's a layer or group name
|
;; Symbol - check if it's a layer or group name
|
||||||
((symbolp props)
|
((symbolp props)
|
||||||
(cond
|
(cond
|
||||||
@ -1985,12 +2025,12 @@ For group names, includes `tp-layers' property with the full layer stack."
|
|||||||
(t nil)))
|
(t nil)))
|
||||||
|
|
||||||
(defun tp--ensure-props (plist)
|
(defun tp--ensure-props (plist)
|
||||||
"Ensure PLIST is a property list, resolving layer names if needed.
|
"Ensure PLIST is a property list, resolving layer names and handling reactive vars.
|
||||||
If PLIST is a symbol, resolve it via `tp--resolve-props'.
|
If PLIST is a symbol, resolve it via `tp--resolve-props'.
|
||||||
|
If PLIST is a plist, also process it via `tp--resolve-props' to handle
|
||||||
|
anonymous reactive layers.
|
||||||
If resolution fails, return PLIST unchanged (for backward compatibility)."
|
If resolution fails, return PLIST unchanged (for backward compatibility)."
|
||||||
(if (symbolp plist)
|
(or (tp--resolve-props plist) plist))
|
||||||
(or (tp--resolve-props plist) plist)
|
|
||||||
plist))
|
|
||||||
|
|
||||||
(defun tp-layer-reset ()
|
(defun tp-layer-reset ()
|
||||||
"Reset all layer definitions.
|
"Reset all layer definitions.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user