feat: add recursive resolution for nested custom layer properties
When a define-tp layer returns a plist containing other custom layer names, those are now recursively expanded to their built-in text properties. This fixes the issue where tp-button using tp-palette internally would leave tp-palette as a text property instead of resolving it to the actual face properties. Changes: - Added tp--plist-has-layer-key-p helper function - Updated tp--expand-layer-in-plist to recursively expand layer props - Updated tp-layer-props to expand nested layers in returned plist - Updated tp-layer-props-with-arg to expand nested layers in returned plist - Added 3 tests for nested layer resolution Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
This commit is contained in:
parent
d824de77ee
commit
3410e28dac
80
tp-tests.el
80
tp-tests.el
@ -3622,5 +3622,85 @@ When using tp-set (direct property setting), tp-name is NOT added."
|
||||
;; Valid parameterized layer should work
|
||||
(should (define-tp tp-test-param-valid (value) `(face (:height ,value))))))
|
||||
|
||||
;;; ============================================================
|
||||
;;; Nested Layer Resolution Tests
|
||||
;;; ============================================================
|
||||
|
||||
(ert-deftest tp-test-nested-layer-resolution ()
|
||||
"Test that nested custom layers are resolved to built-in properties.
|
||||
When a layer's body returns a plist containing other custom layer names,
|
||||
those should be recursively expanded to their built-in properties."
|
||||
(tp-test-with-temp-buffer
|
||||
;; Define a base layer that returns built-in properties
|
||||
(define-tp tp-test-base-layer (color)
|
||||
`(face (:foreground ,color :background "white")))
|
||||
;; Define a wrapper layer that uses the base layer
|
||||
(define-tp tp-test-wrapper-layer (plist)
|
||||
(let ((color (plist-get plist :color)))
|
||||
`(tp-test-base-layer ,color
|
||||
help-echo "wrapper")))
|
||||
;; Use the wrapper layer
|
||||
(let ((result (tp-set "test" 'tp-test-wrapper-layer '(:color "red"))))
|
||||
;; The face property should be resolved from tp-test-base-layer
|
||||
(should (equal (plist-get (get-text-property 0 'face result) :foreground) "red"))
|
||||
(should (equal (plist-get (get-text-property 0 'face result) :background) "white"))
|
||||
;; help-echo should also be present
|
||||
(should (equal (get-text-property 0 'help-echo result) "wrapper"))
|
||||
;; tp-test-base-layer should NOT be present as a property
|
||||
(should (null (get-text-property 0 'tp-test-base-layer result))))))
|
||||
|
||||
(ert-deftest tp-test-nested-layer-resolution-with-tp-palette ()
|
||||
"Test nested layer resolution with tp-palette and tp-button pattern.
|
||||
This tests the exact use case from the issue: tp-button uses tp-palette
|
||||
internally, and the final result should have face properties, not tp-palette."
|
||||
(tp-test-with-temp-buffer
|
||||
;; Re-define tp-palette and tp-button since tp-test-with-temp-buffer resets tp-layer-alist
|
||||
(define-tp tp-palette (symbol)
|
||||
(let ((palette (intern (concat "tp-palette-"
|
||||
(symbol-name symbol)))))
|
||||
`(face ( :foreground ,(tp-palette-fg-color palette)
|
||||
:background ,(tp-palette-bg-color palette)
|
||||
:box (:color ,(tp-palette-border-color palette))))))
|
||||
(define-tp tp-button (plist)
|
||||
(let ((palette (plist-get plist :palette))
|
||||
(action (plist-get plist :action)))
|
||||
`( tp-palette ,palette
|
||||
keymap ,(let ((keymap (make-sparse-keymap)))
|
||||
(define-key keymap (kbd "<RET>") action)
|
||||
(define-key keymap [mouse-1] action)
|
||||
keymap))))
|
||||
;; Test that using tp-button resolves tp-palette to face properties
|
||||
(let ((result (tp-set "emacs" 'tp-button '(:palette org-code))))
|
||||
;; The face property should be resolved from tp-palette
|
||||
(let ((face-prop (get-text-property 0 'face result)))
|
||||
(should face-prop)
|
||||
(should (plist-get face-prop :foreground))
|
||||
(should (plist-get face-prop :background))
|
||||
(should (plist-get face-prop :box)))
|
||||
;; keymap should also be present
|
||||
(should (get-text-property 0 'keymap result))
|
||||
;; tp-palette should NOT be present as a property
|
||||
(should (null (get-text-property 0 'tp-palette result))))))
|
||||
|
||||
(ert-deftest tp-test-deeply-nested-layer-resolution ()
|
||||
"Test that deeply nested layers (3 levels) are fully resolved."
|
||||
(tp-test-with-temp-buffer
|
||||
;; Define 3 levels of nesting
|
||||
(define-tp tp-test-level1 (val)
|
||||
`(face (:foreground ,val)))
|
||||
(define-tp tp-test-level2 (val)
|
||||
`(tp-test-level1 ,val help-echo "level2"))
|
||||
(define-tp tp-test-level3 (val)
|
||||
`(tp-test-level2 ,val display "level3"))
|
||||
;; Use the most deeply nested layer
|
||||
(let ((result (tp-set "test" 'tp-test-level3 "blue")))
|
||||
;; All properties should be resolved
|
||||
(should (equal (plist-get (get-text-property 0 'face result) :foreground) "blue"))
|
||||
(should (equal (get-text-property 0 'help-echo result) "level2"))
|
||||
(should (equal (get-text-property 0 'display result) "level3"))
|
||||
;; None of the custom layer names should be present
|
||||
(should (null (get-text-property 0 'tp-test-level1 result)))
|
||||
(should (null (get-text-property 0 'tp-test-level2 result))))))
|
||||
|
||||
(provide 'tp-ert-tests)
|
||||
;;; tp-ert-tests.el ends here
|
||||
|
||||
31
tp.el
31
tp.el
@ -2634,7 +2634,8 @@ Also includes tp-name automatically if the layer has reactive dependencies regis
|
||||
Handles two storage formats:
|
||||
1. Old format (from tp--set-layer-props): (LAYER-NAME . PLIST) - flat plist
|
||||
2. Unified format (from define-tp): (LAYER-NAME ARGLIST BODY-FORM)
|
||||
For parameterized layers (ARGLIST non-nil), returns nil - use `tp-layer-props-with-arg'."
|
||||
For parameterized layers (ARGLIST non-nil), returns nil - use `tp-layer-props-with-arg'.
|
||||
Recursively expands any nested layer names in the returned plist."
|
||||
(when-let ((entry (cdr (assoc layer-name tp-layer-alist))))
|
||||
;; Auto-include tp-name for layers with reactive deps
|
||||
(let ((needs-tp-name (or include-tp-name
|
||||
@ -2654,14 +2655,21 @@ For parameterized layers (ARGLIST non-nil), returns nil - use `tp-layer-props-wi
|
||||
;; Non-parameterized - evaluate body and return props
|
||||
(let ((plist (eval body)))
|
||||
(when plist
|
||||
;; Recursively expand nested layer names
|
||||
(when (tp--plist-has-layer-key-p plist)
|
||||
(setq plist (tp--expand-layer-in-plist plist)))
|
||||
(if needs-tp-name
|
||||
(append plist (list 'tp-name layer-name))
|
||||
plist))))))
|
||||
;; Old format: entry is just a flat plist
|
||||
(t
|
||||
(if needs-tp-name
|
||||
(append entry (list 'tp-name layer-name))
|
||||
entry))))))
|
||||
(let ((plist entry))
|
||||
;; Recursively expand nested layer names
|
||||
(when (tp--plist-has-layer-key-p plist)
|
||||
(setq plist (tp--expand-layer-in-plist plist)))
|
||||
(if needs-tp-name
|
||||
(append plist (list 'tp-name layer-name))
|
||||
plist)))))))
|
||||
|
||||
(defun tp-layer-parameterized-p (layer-name)
|
||||
"Return non-nil if LAYER-NAME is a parameterized layer.
|
||||
@ -2678,7 +2686,8 @@ where ARGLIST is a non-nil list of argument symbols."
|
||||
(defun tp-layer-props-with-arg (layer-name arg &optional include-tp-name)
|
||||
"Return properties for parameterized layer LAYER-NAME with ARG.
|
||||
Evaluates the body form with the argument bound to the parameter.
|
||||
If INCLUDE-TP-NAME is non-nil, appends 'tp-name property to identify the layer."
|
||||
If INCLUDE-TP-NAME is non-nil, appends 'tp-name property to identify the layer.
|
||||
Recursively expands any nested layer names in the returned plist."
|
||||
(when-let ((entry (cdr (assoc layer-name tp-layer-alist))))
|
||||
;; entry is (ARGLIST BODY-FORM)
|
||||
(let ((arglist (car entry))
|
||||
@ -2688,6 +2697,9 @@ If INCLUDE-TP-NAME is non-nil, appends 'tp-name property to identify the layer."
|
||||
;; Evaluate the body with the argument bound
|
||||
(plist (eval `(let ((,arg-sym ',arg)) ,body))))
|
||||
(when plist
|
||||
;; Recursively expand nested layer names
|
||||
(when (tp--plist-has-layer-key-p plist)
|
||||
(setq plist (tp--expand-layer-in-plist plist)))
|
||||
(if include-tp-name
|
||||
(append plist (list 'tp-name layer-name))
|
||||
plist)))))))
|
||||
@ -2706,10 +2718,16 @@ If INCLUDE-TP-NAME is non-nil, each layer's props will include tp-name."
|
||||
(or (assoc sym tp-layer-alist)
|
||||
(assoc sym tp-layer-groups))))
|
||||
|
||||
(defun tp--plist-has-layer-key-p (plist)
|
||||
"Return non-nil if PLIST contains any layer names as keys."
|
||||
(cl-loop for (key _val) on plist by #'cddr
|
||||
thereis (tp--is-layer-name-p key)))
|
||||
|
||||
(defun tp--expand-layer-in-plist (props)
|
||||
"Expand any layer names found in PROPS plist.
|
||||
Scans through PROPS treating it as a plist (key value pairs).
|
||||
When a key is a layer/group name, expands it with its properties.
|
||||
Recursively expands until no more layer names are found in the result.
|
||||
Does NOT add tp-name - this is for direct property setting (tp-set/add/reset).
|
||||
Returns the expanded plist."
|
||||
(let ((result nil)
|
||||
@ -2736,6 +2754,9 @@ Returns the expanded plist."
|
||||
;; Reverse so first layer's properties are applied last (take precedence)
|
||||
(apply #'append (reverse layer-props-list)))))))
|
||||
(when layer-props
|
||||
;; Recursively expand if the layer props contain more layer names
|
||||
(when (tp--plist-has-layer-key-p layer-props)
|
||||
(setq layer-props (tp--expand-layer-in-plist layer-props)))
|
||||
(setq result (append result layer-props)))))
|
||||
;; Regular property - keep as-is
|
||||
(t
|
||||
|
||||
Loading…
Reference in New Issue
Block a user