Complete refactoring: remove tp-define-layer and tp-define-layer-group, use only define-tp and define-tps

Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-01-04 11:35:30 +00:00
parent 4607f50a13
commit 7934749397
4 changed files with 213 additions and 174 deletions

View File

@ -210,7 +210,7 @@ Native APIs only have simple set and get. tp.el provides three clear operation s
**This is tp.el's most innovative feature**, completely unsupported by native Emacs. The property layer system allows stacking multiple sets of properties on the same text region: **This is tp.el's most innovative feature**, completely unsupported by native Emacs. The property layer system allows stacking multiple sets of properties on the same text region:
- ✅ **Property Layer Stack Concept**: Multiple property layers stack like a stack, only the top layer is visible, lower layers are preserved - ✅ **Property Layer Stack Concept**: Multiple property layers stack like a stack, only the top layer is visible, lower layers are preserved
- ✅ **Property Layer Definition & Reuse**: Define reusable property layers and layer groups via `tp-define-layer` - ✅ **Property Layer Definition & Reuse**: Define reusable property layers and layer groups via `define-tp` and `define-tps`
- ✅ **Rich Property Layer Operations**: - ✅ **Rich Property Layer Operations**:
- Placement: `tp-put-layer` (specific position), `tp-push-layer` (top) - Placement: `tp-put-layer` (specific position), `tp-push-layer` (top)
- Deletion: `tp-delete-layer` (by name/index), `tp-pop-layer` (top layer) - Deletion: `tp-delete-layer` (by name/index), `tp-pop-layer` (top layer)
@ -264,7 +264,7 @@ Native APIs require manual searching and looping. tp.el provides convenient patt
;; Define a layer with reactive properties ;; Define a layer with reactive properties
(defvar my-color "red") ;; Reactive variable (defvar my-color "red") ;; Reactive variable
(tp-define-layer 'my-highlight (define-tp my-highlight ()
:props '(face (:foreground $my-color))) :props '(face (:foreground $my-color)))
;; Apply the layer ;; Apply the layer
@ -274,7 +274,7 @@ Native APIs require manual searching and looping. tp.el provides convenient patt
(setq my-color "blue") ;; All text with my-highlight layer updates to blue! (setq my-color "blue") ;; All text with my-highlight layer updates to blue!
;; Advanced example with :data, :compute, and :watch ;; Advanced example with :data, :compute, and :watch
(tp-define-layer 'full-name-layer (define-tp full-name-layer ()
:props '(help-echo $full-name face (:foreground $name-color)) :props '(help-echo $full-name face (:foreground $name-color))
:data ((first-name . "John") (last-name . "Doe")) ;; With initial values :data ((first-name . "John") (last-name . "Doe")) ;; With initial values
:compute ((full-name (lambda () (concat first-name " " last-name)))) :compute ((full-name (lambda () (concat first-name " " last-name))))
@ -442,7 +442,7 @@ Set text properties on a string or buffer region. Replaces only the specified pr
(tp-set STRING LAYER-NAME) (tp-set STRING LAYER-NAME)
``` ```
LAYER-NAME can be a symbol representing a layer defined by `tp-define-layer` or a group defined by `tp-define-layer-group`. LAYER-NAME can be a symbol representing a layer defined by `define-tp` or a group defined by `define-tps`.
**Examples:** **Examples:**
@ -460,7 +460,7 @@ LAYER-NAME can be a symbol representing a layer defined by `tp-define-layer` or
;; => (1 . 10) ;; => (1 . 10)
;; Use a defined layer name ;; Use a defined layer name
(tp-define-layer 'warning-style (define-tp warning-style ()
'(face (:foreground "orange" :weight bold))) '(face (:foreground "orange" :weight bold)))
(with-temp-buffer (with-temp-buffer
(insert "Hello World") (insert "Hello World")
@ -485,7 +485,7 @@ LAYER-NAME can be a symbol representing a layer defined by `tp-define-layer` or
;; => #("Hello" 0 5 (face bold mouse-face highlight)) ;; => #("Hello" 0 5 (face bold mouse-face highlight))
;; Use a defined layer name on entire string ;; Use a defined layer name on entire string
(tp-define-layer 'my-style (define-tp my-style ()
:props '(face (:foreground $my-color)) :props '(face (:foreground $my-color))
:data ((my-color . "blue"))) :data ((my-color . "blue")))
(tp-set " " 'my-style) (tp-set " " 'my-style)
@ -523,7 +523,7 @@ Completely replace ALL text properties with the specified ones.
(tp-reset STRING PROPERTY VALUE ...) (tp-reset STRING PROPERTY VALUE ...)
``` ```
LAYER-NAME can be a symbol representing a layer defined by `tp-define-layer` or a group defined by `tp-define-layer-group`. LAYER-NAME can be a symbol representing a layer defined by `define-tp` or a group defined by `define-tps`.
**Examples:** **Examples:**
@ -541,7 +541,7 @@ LAYER-NAME can be a symbol representing a layer defined by `tp-define-layer` or
;; => #("Hello" 0 5 (face italic)) ;; => #("Hello" 0 5 (face italic))
;; Use a defined layer name ;; Use a defined layer name
(tp-define-layer 'error-style (define-tp error-style ()
'(face (:foreground "red" :weight bold))) '(face (:foreground "red" :weight bold)))
(with-temp-buffer (with-temp-buffer
(insert "Hello World") (insert "Hello World")
@ -561,7 +561,7 @@ Add or update properties with deep merge support for nested plists.
(tp-add STRING PROPERTY VALUE ...) (tp-add STRING PROPERTY VALUE ...)
``` ```
LAYER-NAME can be a symbol representing a layer defined by `tp-define-layer` or a group defined by `tp-define-layer-group`. LAYER-NAME can be a symbol representing a layer defined by `define-tp` or a group defined by `define-tps`.
**Examples:** **Examples:**
@ -589,7 +589,7 @@ LAYER-NAME can be a symbol representing a layer defined by `tp-define-layer` or
;; => (shadow bold) ;; => (shadow bold)
;; Use a defined layer name ;; Use a defined layer name
(tp-define-layer 'highlight-style (define-tp highlight-style ()
'(face (:background "yellow"))) '(face (:background "yellow")))
(with-temp-buffer (with-temp-buffer
(insert "Hello World") (insert "Hello World")
@ -867,7 +867,7 @@ Clear all text properties from a region.
Set properties on all occurrences of a string pattern. Set properties on all occurrences of a string pattern.
PATTERN can be a string (single pattern) or a list of strings (multiple patterns). PATTERN can be a string (single pattern) or a list of strings (multiple patterns).
PLIST is a property list like `'(face bold help-echo "tip")`. PLIST is a property list like `'(face bold help-echo "tip")`.
LAYER-NAME can be a symbol representing a layer defined by `tp-define-layer` or a group defined by `tp-define-layer-group`. LAYER-NAME can be a symbol representing a layer defined by `define-tp` or a group defined by `define-tps`.
OBJECT is a buffer or string; nil means current buffer. OBJECT is a buffer or string; nil means current buffer.
**Examples:** **Examples:**
@ -894,7 +894,7 @@ OBJECT is a buffer or string; nil means current buffer.
;; => #("Hello world" 0 5 (face bold) 6 11 (face bold)) ;; => #("Hello world" 0 5 (face bold) 6 11 (face bold))
;; Use a defined layer name ;; Use a defined layer name
(tp-define-layer 'todo-style (define-tp todo-style ()
'(face (:foreground "orange" :weight bold))) '(face (:foreground "orange" :weight bold)))
(with-temp-buffer (with-temp-buffer
(insert "TODO: fix this. TODO: also this.") (insert "TODO: fix this. TODO: also this.")
@ -909,7 +909,7 @@ OBJECT is a buffer or string; nil means current buffer.
Reset (completely replace) all properties on matches. Reset (completely replace) all properties on matches.
PATTERN can be a string or list of strings (multiple patterns). PATTERN can be a string or list of strings (multiple patterns).
PLIST is a property list like `'(face bold help-echo "tip")`. PLIST is a property list like `'(face bold help-echo "tip")`.
LAYER-NAME can be a symbol representing a layer defined by `tp-define-layer` or a group defined by `tp-define-layer-group`. LAYER-NAME can be a symbol representing a layer defined by `define-tp` or a group defined by `define-tps`.
OBJECT is a buffer or string; nil means current buffer. OBJECT is a buffer or string; nil means current buffer.
```elisp ```elisp
@ -935,7 +935,7 @@ OBJECT is a buffer or string; nil means current buffer.
;; => ((1 . 5) (12 . 17)) ;; => ((1 . 5) (12 . 17))
;; Use a defined layer name ;; Use a defined layer name
(tp-define-layer 'alert-style (define-tp alert-style ()
'(face (:background "red" :foreground "white"))) '(face (:background "red" :foreground "white")))
(with-temp-buffer (with-temp-buffer
(insert "TODO: fix this") (insert "TODO: fix this")
@ -950,7 +950,7 @@ OBJECT is a buffer or string; nil means current buffer.
Add/merge properties on matches with deep merge support. Add/merge properties on matches with deep merge support.
PATTERN can be a string or list of strings (multiple patterns). PATTERN can be a string or list of strings (multiple patterns).
PLIST is a property list like `'(face bold help-echo "tip")`. PLIST is a property list like `'(face bold help-echo "tip")`.
LAYER-NAME can be a symbol representing a layer defined by `tp-define-layer` or a group defined by `tp-define-layer-group`. LAYER-NAME can be a symbol representing a layer defined by `define-tp` or a group defined by `define-tps`.
OBJECT is a buffer or string; nil means current buffer. OBJECT is a buffer or string; nil means current buffer.
```elisp ```elisp
@ -976,7 +976,7 @@ OBJECT is a buffer or string; nil means current buffer.
;; => ((1 . 5) (12 . 17)) ;; => ((1 . 5) (12 . 17))
;; Use a defined layer name ;; Use a defined layer name
(tp-define-layer 'underline-style (define-tp underline-style ()
'(face (:underline (:color "blue" :style wave)))) '(face (:underline (:color "blue" :style wave))))
(with-temp-buffer (with-temp-buffer
(insert "TODO: fix this") (insert "TODO: fix this")
@ -996,7 +996,7 @@ OBJECT is a buffer or string; nil means current buffer.
Set properties on all matches of a regular expression. Set properties on all matches of a regular expression.
PATTERN can be a string (single regexp) or a list of strings (multiple regexps). PATTERN can be a string (single regexp) or a list of strings (multiple regexps).
PLIST is a property list like `'(face bold help-echo "tip")`. PLIST is a property list like `'(face bold help-echo "tip")`.
LAYER-NAME can be a symbol representing a layer defined by `tp-define-layer` or a group defined by `tp-define-layer-group`. LAYER-NAME can be a symbol representing a layer defined by `define-tp` or a group defined by `define-tps`.
OBJECT is a buffer or string; nil means current buffer. OBJECT is a buffer or string; nil means current buffer.
**Examples:** **Examples:**
@ -1018,7 +1018,7 @@ OBJECT is a buffer or string; nil means current buffer.
;; => #("abc 123 XYZ" 4 7 (face bold) 8 11 (face bold)) ;; => #("abc 123 XYZ" 4 7 (face bold) 8 11 (face bold))
;; Use a defined layer name ;; Use a defined layer name
(tp-define-layer 'number-style (define-tp number-style ()
'(face (:foreground "green"))) '(face (:foreground "green")))
(with-temp-buffer (with-temp-buffer
(insert "abc 123 def 456") (insert "abc 123 def 456")
@ -1033,7 +1033,7 @@ OBJECT is a buffer or string; nil means current buffer.
Reset (completely replace) all properties on regexp matches. Reset (completely replace) all properties on regexp matches.
PATTERN can be a string or list of strings (multiple regexps). PATTERN can be a string or list of strings (multiple regexps).
PLIST is a property list like `'(face bold help-echo "tip")`. PLIST is a property list like `'(face bold help-echo "tip")`.
LAYER-NAME can be a symbol representing a layer defined by `tp-define-layer` or a group defined by `tp-define-layer-group`. LAYER-NAME can be a symbol representing a layer defined by `define-tp` or a group defined by `define-tps`.
OBJECT is a buffer or string; nil means current buffer. OBJECT is a buffer or string; nil means current buffer.
```elisp ```elisp
@ -1060,7 +1060,7 @@ OBJECT is a buffer or string; nil means current buffer.
;; => (face italic) ;; => (face italic)
;; Use a defined layer name ;; Use a defined layer name
(tp-define-layer 'code-number (define-tp code-number ()
'(face (:foreground "cyan"))) '(face (:foreground "cyan")))
(with-temp-buffer (with-temp-buffer
(insert "abc 123 def 456") (insert "abc 123 def 456")
@ -1075,7 +1075,7 @@ OBJECT is a buffer or string; nil means current buffer.
Add/merge properties on regexp matches with deep merge support. Add/merge properties on regexp matches with deep merge support.
PATTERN can be a string or list of strings (multiple regexps). PATTERN can be a string or list of strings (multiple regexps).
PLIST is a property list like `'(face bold help-echo "tip")`. PLIST is a property list like `'(face bold help-echo "tip")`.
LAYER-NAME can be a symbol representing a layer defined by `tp-define-layer` or a group defined by `tp-define-layer-group`. LAYER-NAME can be a symbol representing a layer defined by `define-tp` or a group defined by `define-tps`.
OBJECT is a buffer or string; nil means current buffer. OBJECT is a buffer or string; nil means current buffer.
```elisp ```elisp
@ -1102,7 +1102,7 @@ OBJECT is a buffer or string; nil means current buffer.
;; => (face italic help-echo "number") ;; => (face italic help-echo "number")
;; Use a defined layer name ;; Use a defined layer name
(tp-define-layer 'bold-underline (define-tp bold-underline ()
'(face (:weight bold :underline t))) '(face (:weight bold :underline t)))
(with-temp-buffer (with-temp-buffer
(insert "abc 123 def 456") (insert "abc 123 def 456")
@ -1649,7 +1649,7 @@ This is useful when you want to remove all reactive bindings but keep the layer
;; Define a reactive layer ;; Define a reactive layer
(progn (progn
(defvar my-reactive-color "red") (defvar my-reactive-color "red")
(tp-define-layer 'reactive-layer (define-tp reactive-layer ()
:props '(face (:foreground $my-reactive-color))) :props '(face (:foreground $my-reactive-color)))
;; Clear reactive bindings only ;; Clear reactive bindings only
(tp-reactive-reset) (tp-reactive-reset)
@ -2460,12 +2460,12 @@ Return t if OBJECT has no text properties.
(progn (progn
(tp-layer-reset) (tp-layer-reset)
;; Define layers for different highlighting purposes ;; Define layers for different highlighting purposes
(tp-define-layer 'code-base (define-tp code-base ()
'(face font-lock-keyword-face)) '(face font-lock-keyword-face))
(tp-define-layer 'code-error (define-tp code-error ()
'(face (:underline (:color "red" :style wave)) '(face (:underline (:color "red" :style wave))
help-echo "Syntax error")) help-echo "Syntax error"))
(tp-define-layer 'code-debug (define-tp code-debug ()
'(face (:background "dark blue"))) '(face (:background "dark blue")))
(with-temp-buffer (with-temp-buffer
(insert (make-string 100 ?x)) ; Create 100-char buffer (insert (make-string 100 ?x)) ; Create 100-char buffer
@ -2491,10 +2491,10 @@ Return t if OBJECT has no text properties.
(progn (progn
(tp-layer-reset) (tp-layer-reset)
;; Define status layers as a group ;; Define status layers as a group
(tp-define-layer 'status-todo '(face (:foreground "gray"))) (define-tp status-todo () '(face (:foreground "gray")))
(tp-define-layer 'status-progress '(face (:foreground "yellow"))) (define-tp status-progress () '(face (:foreground "yellow")))
(tp-define-layer 'status-done '(face (:foreground "green"))) (define-tp status-done () '(face (:foreground "green")))
(tp-define-layer-group 'task-status 'status-todo 'status-progress 'status-done) (define-tps task-status 'status-todo 'status-progress 'status-done)
;; Check group is defined ;; Check group is defined
(length (tp-group-props 'task-status))) (length (tp-group-props 'task-status)))
;; => 3 ;; => 3
@ -2512,7 +2512,7 @@ Return t if OBJECT has no text properties.
;; Define temporary highlight layer ;; Define temporary highlight layer
(progn (progn
(tp-layer-reset) (tp-layer-reset)
(tp-define-layer 'temp-highlight (define-tp temp-highlight ()
'(face (:background "yellow"))) '(face (:background "yellow")))
(tp-layer-props 'temp-highlight)) (tp-layer-props 'temp-highlight))
;; => (face (:background "yellow") tp-name temp-highlight) ;; => (face (:background "yellow") tp-name temp-highlight)
@ -2551,7 +2551,7 @@ Traditional text property manipulation requires manually updating all affected t
;; Reactive approach (automatic updates) ;; Reactive approach (automatic updates)
(defvar my-color "red") (defvar my-color "red")
(tp-define-layer 'my-layer (define-tp my-layer ()
:props '(face (:foreground $my-color))) :props '(face (:foreground $my-color)))
(tp-push-layer 1 10 'my-layer) (tp-push-layer 1 10 'my-layer)
;; Just change the variable - all text updates automatically! ;; Just change the variable - all text updates automatically!
@ -2573,7 +2573,7 @@ Traditional text property manipulation requires manually updating all affected t
```elisp ```elisp
(defvar highlight-color "yellow") (defvar highlight-color "yellow")
(tp-define-layer 'my-highlight (define-tp my-highlight ()
:props '(face (:background $highlight-color))) :props '(face (:background $highlight-color)))
(with-temp-buffer (with-temp-buffer
@ -2592,7 +2592,7 @@ Traditional text property manipulation requires manually updating all affected t
(defvar fg-color "white") (defvar fg-color "white")
(defvar bg-color "black") (defvar bg-color "black")
(tp-define-layer 'themed-text (define-tp themed-text ()
:props '(face (:foreground $fg-color :background $bg-color))) :props '(face (:foreground $fg-color :background $bg-color)))
;; Changing either variable updates the text ;; Changing either variable updates the text
@ -2605,7 +2605,7 @@ Traditional text property manipulation requires manually updating all affected t
The `:data` keyword defines additional reactive variables that aren't directly used in `:props` but can trigger computed value updates or be watched: The `:data` keyword defines additional reactive variables that aren't directly used in `:props` but can trigger computed value updates or be watched:
```elisp ```elisp
(tp-define-layer 'user-info (define-tp user-info ()
:props '(help-echo $full-name) :props '(help-echo $full-name)
:data '(first-name last-name) ; Not used directly in props :data '(first-name last-name) ; Not used directly in props
:compute '((full-name (lambda () (concat first-name " " last-name))))) :compute '((full-name (lambda () (concat first-name " " last-name)))))
@ -2616,7 +2616,7 @@ The `:data` keyword defines additional reactive variables that aren't directly u
You can specify initial values using cons cells: You can specify initial values using cons cells:
```elisp ```elisp
(tp-define-layer 'user-info (define-tp user-info ()
:props '(help-echo $full-name) :props '(help-echo $full-name)
:data '((first-name . "John") (last-name . "Doe")) :data '((first-name . "John") (last-name . "Doe"))
:compute '((full-name (lambda () (concat first-name " " last-name))))) :compute '((full-name (lambda () (concat first-name " " last-name)))))
@ -2629,7 +2629,7 @@ You can specify initial values using cons cells:
The `:compute` keyword creates derived values that are automatically recalculated when their dependencies change: The `:compute` keyword creates derived values that are automatically recalculated when their dependencies change:
```elisp ```elisp
(tp-define-layer 'progress-display (define-tp progress-display ()
:props '(display $progress-text face (:foreground $progress-color)) :props '(display $progress-text face (:foreground $progress-color))
:data '((current . 0) (total . 100)) :data '((current . 0) (total . 100))
:compute '((progress-text (lambda () (format "%d%%" (/ (* current 100) total)))) :compute '((progress-text (lambda () (format "%d%%" (/ (* current 100) total))))
@ -2648,7 +2648,7 @@ The `:compute` keyword creates derived values that are automatically recalculate
The `:watch` keyword lets you execute callbacks when reactive variables change: The `:watch` keyword lets you execute callbacks when reactive variables change:
```elisp ```elisp
(tp-define-layer 'monitored-layer (define-tp monitored-layer ()
:props '(face (:foreground $status-color)) :props '(face (:foreground $status-color))
:watch ((status-color :watch ((status-color
(lambda (new-val old-val layer-name) (lambda (new-val old-val layer-name)
@ -2668,7 +2668,7 @@ The `:transform` keyword allows you to register a transformation function that p
```elisp ```elisp
;; Number formatting ;; Number formatting
(tp-define-layer 'price-display (define-tp price-display ()
:props '(tp-text $price) :props '(tp-text $price)
:data '((price . "99.9")) :data '((price . "99.9"))
:transform (lambda (text) :transform (lambda (text)
@ -2676,7 +2676,7 @@ The `:transform` keyword allows you to register a transformation function that p
;; 99.9 displays as $99.00 ;; 99.9 displays as $99.00
;; Date formatting ;; Date formatting
(tp-define-layer 'date-display (define-tp date-display ()
:props '(tp-text $timestamp) :props '(tp-text $timestamp)
:data '((timestamp . "1703865600")) :data '((timestamp . "1703865600"))
:transform (lambda (text) :transform (lambda (text)
@ -2684,7 +2684,7 @@ The `:transform` keyword allows you to register a transformation function that p
(seconds-to-time (string-to-number text))))) (seconds-to-time (string-to-number text)))))
;; Uppercase conversion ;; Uppercase conversion
(tp-define-layer 'uppercase-text (define-tp uppercase-text ()
:props '(tp-text $content) :props '(tp-text $content)
:data '((content . "hello")) :data '((content . "hello"))
:transform #'upcase) :transform #'upcase)
@ -2699,7 +2699,7 @@ The transform function:
### Anonymous Reactive Layers ### Anonymous Reactive Layers
You can use reactive variables even without `tp-define-layer`. When you use `$`-prefixed symbols in an anonymous plist, tp.el automatically generates a unique layer name: You can use reactive variables even without `define-tp`. When you use `$`-prefixed symbols in an anonymous plist, tp.el automatically generates a unique layer name:
```elisp ```elisp
(defvar my-face-color "blue") (defvar my-face-color "blue")
@ -2716,7 +2716,7 @@ You can use reactive variables even without `tp-define-layer`. When you use `$`-
All text property APIs (`tp-set`, `tp-match-set`, `tp-regexp-set`, etc.) now accept layer names directly: All text property APIs (`tp-set`, `tp-match-set`, `tp-regexp-set`, etc.) now accept layer names directly:
```elisp ```elisp
(tp-define-layer 'warning-style (define-tp warning-style ()
:props '(face (:foreground "orange" :weight bold))) :props '(face (:foreground "orange" :weight bold)))
;; Use layer name instead of plist ;; Use layer name instead of plist
@ -2732,7 +2732,7 @@ All text property APIs (`tp-set`, `tp-match-set`, `tp-regexp-set`, etc.) now acc
Layer groups can also use reactive features: Layer groups can also use reactive features:
```elisp ```elisp
(tp-define-layer-group 'status-indicators (define-tps status-indicators
'("success" :props (face (:foreground $success-color)) '("success" :props (face (:foreground $success-color))
:data ((success-color . "green"))) :data ((success-color . "green")))
'("warning" :props (face (:foreground $warning-color)) '("warning" :props (face (:foreground $warning-color))
@ -2746,7 +2746,7 @@ Layer groups can also use reactive features:
When modifying multiple reactive variables simultaneously, each `setq` triggers a separate buffer update. Use `tp-with-batch-updates` to consolidate all changes and apply them once at the end: When modifying multiple reactive variables simultaneously, each `setq` triggers a separate buffer update. Use `tp-with-batch-updates` to consolidate all changes and apply them once at the end:
```elisp ```elisp
(tp-define-layer 'themed-text (define-tp themed-text ()
:props '(face (:foreground $fg-color :background $bg-color)) :props '(face (:foreground $fg-color :background $bg-color))
:data '((fg-color . "white") (bg-color . "black"))) :data '((fg-color . "white") (bg-color . "black")))
@ -2818,13 +2818,13 @@ To clear all reactive dependencies and watchers:
(defvar theme-accent "cyan") (defvar theme-accent "cyan")
;; Define theme-aware layers ;; Define theme-aware layers
(tp-define-layer 'code-keyword (define-tp code-keyword ()
:props '(face (:foreground $theme-accent :weight bold))) :props '(face (:foreground $theme-accent :weight bold)))
(tp-define-layer 'code-comment (define-tp code-comment ()
:props '(face (:foreground "gray" :slant italic))) :props '(face (:foreground "gray" :slant italic)))
(tp-define-layer 'code-string (define-tp code-string ()
:props '(face (:foreground "green"))) :props '(face (:foreground "green")))
;; Apply layers to code ;; Apply layers to code

View File

@ -273,10 +273,10 @@
;; 之后只需改变变量 - 文本自动更新! ;; 之后只需改变变量 - 文本自动更新!
(setq my-color "blue") ;; 所有 my-highlight 层的文本自动变成蓝色! (setq my-color "blue") ;; 所有 my-highlight 层的文本自动变成蓝色!
;; 高级响应式示例(使用内部函数 tp-define-layer ;; 高级响应式示例(使用 define-tp
;; 对于需要 :data、:compute、:watch 等高级特性的场景, ;; 对于需要 :data、:compute、:watch 等高级特性的场景,
;; 可以使用内部函数 tp-define-layer ;; 可以使用 define-tp
(tp-define-layer 'full-name-layer (define-tp full-name-layer ()
:props '(help-echo $full-name face (:foreground $name-color)) :props '(help-echo $full-name face (:foreground $name-color))
:data '((first-name . "John") (last-name . "Doe")) ;; 带初始值 :data '((first-name . "John") (last-name . "Doe")) ;; 带初始值
:compute '((full-name (lambda () (concat first-name " " last-name)))) :compute '((full-name (lambda () (concat first-name " " last-name))))
@ -479,7 +479,7 @@ LAYER-NAME 可以是通过 `define-tp` 定义的自定义文本属性名称或
;; => #("Hello" 0 5 (face bold mouse-face highlight)) ;; => #("Hello" 0 5 (face bold mouse-face highlight))
;; 在整个字符串上使用已定义的层名称 ;; 在整个字符串上使用已定义的层名称
(tp-define-layer 'my-style (define-tp my-style ()
:props '(face (:foreground $my-color)) :props '(face (:foreground $my-color))
:data '((my-color . "blue"))) :data '((my-color . "blue")))
(tp-set " " 'my-style) (tp-set " " 'my-style)
@ -535,7 +535,7 @@ LAYER-NAME 可以是通过 `define-tp` 定义的自定义文本属性名称或
;; => #("Hello" 0 5 (face italic)) ;; => #("Hello" 0 5 (face italic))
;; 使用已定义的层名称 ;; 使用已定义的层名称
(tp-define-layer 'error-style (define-tp error-style ()
'(face (:foreground "red" :weight bold))) '(face (:foreground "red" :weight bold)))
(with-temp-buffer (with-temp-buffer
(insert "Hello World") (insert "Hello World")
@ -583,7 +583,7 @@ LAYER-NAME 可以是通过 `define-tp` 定义的自定义文本属性名称或
;; => (shadow bold) ;; => (shadow bold)
;; 使用已定义的层名称 ;; 使用已定义的层名称
(tp-define-layer 'highlight-style (define-tp highlight-style ()
'(face (:background "yellow"))) '(face (:background "yellow")))
(with-temp-buffer (with-temp-buffer
(insert "Hello World") (insert "Hello World")
@ -888,7 +888,7 @@ OBJECT 是缓冲区或字符串nil 表示当前缓冲区。
;; => #("Hello world" 0 5 (face bold) 6 11 (face bold)) ;; => #("Hello world" 0 5 (face bold) 6 11 (face bold))
;; 使用已定义的层名称 ;; 使用已定义的层名称
(tp-define-layer 'todo-style (define-tp todo-style ()
'(face (:foreground "orange" :weight bold))) '(face (:foreground "orange" :weight bold)))
(with-temp-buffer (with-temp-buffer
(insert "TODO: fix this. TODO: also this.") (insert "TODO: fix this. TODO: also this.")
@ -929,7 +929,7 @@ OBJECT 是缓冲区或字符串nil 表示当前缓冲区。
;; => ((1 . 5) (12 . 17)) ;; => ((1 . 5) (12 . 17))
;; 使用已定义的层名称 ;; 使用已定义的层名称
(tp-define-layer 'alert-style (define-tp alert-style ()
'(face (:background "red" :foreground "white"))) '(face (:background "red" :foreground "white")))
(with-temp-buffer (with-temp-buffer
(insert "TODO: fix this") (insert "TODO: fix this")
@ -970,7 +970,7 @@ OBJECT 是缓冲区或字符串nil 表示当前缓冲区。
;; => ((1 . 5) (12 . 17)) ;; => ((1 . 5) (12 . 17))
;; 使用已定义的层名称 ;; 使用已定义的层名称
(tp-define-layer 'underline-style (define-tp underline-style ()
'(face (:underline (:color "blue" :style wave)))) '(face (:underline (:color "blue" :style wave))))
(with-temp-buffer (with-temp-buffer
(insert "TODO: fix this") (insert "TODO: fix this")
@ -1012,7 +1012,7 @@ OBJECT 是缓冲区或字符串nil 表示当前缓冲区。
;; => #("abc 123 XYZ" 4 7 (face bold) 8 11 (face bold)) ;; => #("abc 123 XYZ" 4 7 (face bold) 8 11 (face bold))
;; 使用已定义的层名称 ;; 使用已定义的层名称
(tp-define-layer 'number-style (define-tp number-style ()
'(face (:foreground "green"))) '(face (:foreground "green")))
(with-temp-buffer (with-temp-buffer
(insert "abc 123 def 456") (insert "abc 123 def 456")
@ -1054,7 +1054,7 @@ OBJECT 是缓冲区或字符串nil 表示当前缓冲区。
;; => (face italic) ;; => (face italic)
;; 使用已定义的层名称 ;; 使用已定义的层名称
(tp-define-layer 'code-number (define-tp code-number ()
'(face (:foreground "cyan"))) '(face (:foreground "cyan")))
(with-temp-buffer (with-temp-buffer
(insert "abc 123 def 456") (insert "abc 123 def 456")
@ -1096,7 +1096,7 @@ OBJECT 是缓冲区或字符串nil 表示当前缓冲区。
;; => (face italic help-echo "number") ;; => (face italic help-echo "number")
;; 使用已定义的层名称 ;; 使用已定义的层名称
(tp-define-layer 'bold-underline (define-tp bold-underline ()
'(face (:weight bold :underline t))) '(face (:weight bold :underline t)))
(with-temp-buffer (with-temp-buffer
(insert "abc 123 def 456") (insert "abc 123 def 456")
@ -1612,8 +1612,8 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
(progn (progn
(setq tp-layer-alist nil) (setq tp-layer-alist nil)
(setq tp-layer-groups nil) (setq tp-layer-groups nil)
(tp-define-layer 'l1 '(face bold)) (define-tp l1 () '(face bold))
(tp-define-layer-group 'my-group 'l1) (define-tps my-group 'l1)
(tp-undefine-group 'my-group) (tp-undefine-group 'my-group)
(assoc 'my-group tp-layer-groups)) (assoc 'my-group tp-layer-groups))
;; => nil ;; => nil
@ -1657,7 +1657,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
;; 定义一个响应式层 ;; 定义一个响应式层
(progn (progn
(defvar my-reactive-color "red") (defvar my-reactive-color "red")
(tp-define-layer 'reactive-layer (define-tp reactive-layer ()
:props '(face (:foreground $my-reactive-color))) :props '(face (:foreground $my-reactive-color)))
;; 仅清除响应式绑定 ;; 仅清除响应式绑定
(tp-reactive-reset) (tp-reactive-reset)
@ -1716,7 +1716,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
(progn (progn
(tp-layer-reset) (tp-layer-reset)
(define-tp base () '(face default)) (define-tp base () '(face default))
(tp-define-layer 'info '(face (:foreground "blue"))) (define-tp info () '(face (:foreground "blue")))
(with-temp-buffer (with-temp-buffer
(insert "Hello World") (insert "Hello World")
(tp-put-layer 1 10 'base 0) (tp-put-layer 1 10 'base 0)
@ -2093,7 +2093,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
(progn (progn
(tp-layer-reset) (tp-layer-reset)
(define-tp layer1 () '(face bold)) (define-tp layer1 () '(face bold))
(tp-define-layer 'layer2 '(help-echo "tip")) (define-tp layer2 () '(help-echo "tip"))
(with-temp-buffer (with-temp-buffer
(insert "Hello World") (insert "Hello World")
(tp-push-layer 1 10 'layer1) (tp-push-layer 1 10 'layer1)
@ -2106,7 +2106,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
(progn (progn
(tp-layer-reset) (tp-layer-reset)
(define-tp layer1 () '(face bold)) (define-tp layer1 () '(face bold))
(tp-define-layer 'layer2 '(help-echo "tip")) (define-tp layer2 () '(help-echo "tip"))
(with-temp-buffer (with-temp-buffer
(insert "Hello World") (insert "Hello World")
(tp-push-layer 1 10 'layer1) (tp-push-layer 1 10 'layer1)
@ -2137,7 +2137,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
(progn (progn
(tp-layer-reset) (tp-layer-reset)
(define-tp layer1 () '(face bold)) (define-tp layer1 () '(face bold))
(tp-define-layer 'layer2 '(help-echo "tip")) (define-tp layer2 () '(help-echo "tip"))
(with-temp-buffer (with-temp-buffer
(insert "Hello World") (insert "Hello World")
(tp-push-layer 1 10 'layer1) (tp-push-layer 1 10 'layer1)
@ -2283,8 +2283,8 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
```elisp ```elisp
(progn (progn
(tp-layer-reset) (tp-layer-reset)
(tp-define-layer 'layer1 '(face (:foreground "red"))) (define-tp layer1 () '(face (:foreground "red")))
(tp-define-layer 'layer2 '(face (:foreground "blue"))) (define-tp layer2 () '(face (:foreground "blue")))
(with-temp-buffer (with-temp-buffer
(insert "Hello World") (insert "Hello World")
(tp-push-layer 1 10 'layer1) (tp-push-layer 1 10 'layer1)
@ -2468,12 +2468,12 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
(progn (progn
(tp-layer-reset) (tp-layer-reset)
;; 为不同高亮目的定义属性层 ;; 为不同高亮目的定义属性层
(tp-define-layer 'code-base (define-tp code-base ()
'(face font-lock-keyword-face)) '(face font-lock-keyword-face))
(tp-define-layer 'code-error (define-tp code-error ()
'(face (:underline (:color "red" :style wave)) '(face (:underline (:color "red" :style wave))
help-echo "语法错误")) help-echo "语法错误"))
(tp-define-layer 'code-debug (define-tp code-debug ()
'(face (:background "dark blue"))) '(face (:background "dark blue")))
(with-temp-buffer (with-temp-buffer
(insert (make-string 100 ?x)) ; 创建 100 字符缓冲区 (insert (make-string 100 ?x)) ; 创建 100 字符缓冲区
@ -2499,10 +2499,10 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
(progn (progn
(tp-layer-reset) (tp-layer-reset)
;; 将状态属性层定义为一个组 ;; 将状态属性层定义为一个组
(tp-define-layer 'status-todo '(face (:foreground "gray"))) (define-tp status-todo () '(face (:foreground "gray")))
(tp-define-layer 'status-progress '(face (:foreground "yellow"))) (define-tp status-progress () '(face (:foreground "yellow")))
(tp-define-layer 'status-done '(face (:foreground "green"))) (define-tp status-done () '(face (:foreground "green")))
(tp-define-layer-group 'task-status 'status-todo 'status-progress 'status-done) (define-tps task-status 'status-todo 'status-progress 'status-done)
;; 检查组是否已定义 ;; 检查组是否已定义
(length (tp-group-props 'task-status))) (length (tp-group-props 'task-status)))
;; => 3 ;; => 3
@ -2520,7 +2520,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
;; 定义临时高亮属性层 ;; 定义临时高亮属性层
(progn (progn
(tp-layer-reset) (tp-layer-reset)
(tp-define-layer 'temp-highlight (define-tp temp-highlight ()
'(face (:background "yellow"))) '(face (:background "yellow")))
(tp-layer-props 'temp-highlight)) (tp-layer-props 'temp-highlight))
;; => (face (:background "yellow") tp-name temp-highlight) ;; => (face (:background "yellow") tp-name temp-highlight)
@ -2559,7 +2559,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
;; 响应式方式(自动更新) ;; 响应式方式(自动更新)
(defvar my-color "red") (defvar my-color "red")
(tp-define-layer 'my-layer (define-tp my-layer ()
:props '(face (:foreground $my-color))) :props '(face (:foreground $my-color)))
(tp-push-layer 1 10 'my-layer) (tp-push-layer 1 10 'my-layer)
;; 只需改变变量 - 所有文本自动更新! ;; 只需改变变量 - 所有文本自动更新!
@ -2581,7 +2581,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
```elisp ```elisp
(defvar highlight-color "yellow") (defvar highlight-color "yellow")
(tp-define-layer 'my-highlight (define-tp my-highlight ()
:props '(face (:background $highlight-color))) :props '(face (:background $highlight-color)))
(with-temp-buffer (with-temp-buffer
@ -2600,7 +2600,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
(defvar fg-color "white") (defvar fg-color "white")
(defvar bg-color "black") (defvar bg-color "black")
(tp-define-layer 'themed-text (define-tp themed-text ()
:props '(face (:foreground $fg-color :background $bg-color))) :props '(face (:foreground $fg-color :background $bg-color)))
;; 改变任一变量都会更新文本 ;; 改变任一变量都会更新文本
@ -2613,7 +2613,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
`:data` 关键字定义不直接用于 `:props` 但可以触发计算值更新或被监听的额外响应式变量: `:data` 关键字定义不直接用于 `:props` 但可以触发计算值更新或被监听的额外响应式变量:
```elisp ```elisp
(tp-define-layer 'user-info (define-tp user-info ()
:props '(help-echo $full-name) :props '(help-echo $full-name)
:data '(first-name last-name) ;; 不直接用于 props :data '(first-name last-name) ;; 不直接用于 props
:compute '((full-name (lambda () (concat first-name " " last-name))))) :compute '((full-name (lambda () (concat first-name " " last-name)))))
@ -2624,7 +2624,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
你可以使用 cons cell 指定初始值: 你可以使用 cons cell 指定初始值:
```elisp ```elisp
(tp-define-layer 'user-info (define-tp user-info ()
:props '(help-echo $full-name) :props '(help-echo $full-name)
:data '((first-name . "张") (last-name . "三")) :data '((first-name . "张") (last-name . "三"))
:compute '((full-name (lambda () (concat first-name last-name))))) :compute '((full-name (lambda () (concat first-name last-name)))))
@ -2637,7 +2637,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
`:compute` 关键字创建派生值,当它们的依赖项改变时会自动重新计算: `:compute` 关键字创建派生值,当它们的依赖项改变时会自动重新计算:
```elisp ```elisp
(tp-define-layer 'progress-display (define-tp progress-display ()
:props '(display $progress-text face (:foreground $progress-color)) :props '(display $progress-text face (:foreground $progress-color))
:data '((current . 0) (total . 100)) :data '((current . 0) (total . 100))
:compute '((progress-text (lambda () (format "%d%%" (/ (* current 100) total)))) :compute '((progress-text (lambda () (format "%d%%" (/ (* current 100) total))))
@ -2656,7 +2656,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
`:watch` 关键字让你在响应式变量改变时执行回调: `:watch` 关键字让你在响应式变量改变时执行回调:
```elisp ```elisp
(tp-define-layer 'monitored-layer (define-tp monitored-layer ()
:props '(face (:foreground $status-color)) :props '(face (:foreground $status-color))
:watch '((status-color :watch '((status-color
(lambda (new-val old-val layer-name) (lambda (new-val old-val layer-name)
@ -2676,7 +2676,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
```elisp ```elisp
;; 数字格式化 ;; 数字格式化
(tp-define-layer 'price-display (define-tp price-display ()
:props '(tp-text $price) :props '(tp-text $price)
:data '((price . "99.9")) :data '((price . "99.9"))
:transform (lambda (text) :transform (lambda (text)
@ -2684,7 +2684,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
;; 99.9 显示为 $99.00 ;; 99.9 显示为 $99.00
;; 日期格式化 ;; 日期格式化
(tp-define-layer 'date-display (define-tp date-display ()
:props '(tp-text $timestamp) :props '(tp-text $timestamp)
:data '((timestamp . "1703865600")) :data '((timestamp . "1703865600"))
:transform (lambda (text) :transform (lambda (text)
@ -2692,7 +2692,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
(seconds-to-time (string-to-number text))))) (seconds-to-time (string-to-number text)))))
;; 大写转换 ;; 大写转换
(tp-define-layer 'uppercase-text (define-tp uppercase-text ()
:props '(tp-text $content) :props '(tp-text $content)
:data '((content . "hello")) :data '((content . "hello"))
:transform #'upcase) :transform #'upcase)
@ -2707,7 +2707,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
### 匿名响应式层 ### 匿名响应式层
即使不使用 `tp-define-layer`,你也可以使用响应式变量。当你在匿名 plist 中使用 `$` 前缀的符号时tp.el 会自动生成唯一的层名: 即使不使用 `define-tp`,你也可以使用响应式变量。当你在匿名 plist 中使用 `$` 前缀的符号时tp.el 会自动生成唯一的层名:
```elisp ```elisp
(defvar my-face-color "blue") (defvar my-face-color "blue")
@ -2724,7 +2724,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
所有文本属性 API`tp-set`、`tp-match-set`、`tp-regexp-set` 等)现在可以直接接受层名: 所有文本属性 API`tp-set`、`tp-match-set`、`tp-regexp-set` 等)现在可以直接接受层名:
```elisp ```elisp
(tp-define-layer 'warning-style (define-tp warning-style ()
:props '(face (:foreground "orange" :weight bold))) :props '(face (:foreground "orange" :weight bold)))
;; 使用层名代替 plist ;; 使用层名代替 plist
@ -2740,7 +2740,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
层组也可以使用响应式特性: 层组也可以使用响应式特性:
```elisp ```elisp
(tp-define-layer-group 'status-indicators (define-tps status-indicators
'("success" :props (face (:foreground $success-color)) '("success" :props (face (:foreground $success-color))
:data ((success-color . "green"))) :data ((success-color . "green")))
'("warning" :props (face (:foreground $warning-color)) '("warning" :props (face (:foreground $warning-color))
@ -2754,7 +2754,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
同时修改多个响应式变量时,每个 `setq` 都会触发一次缓冲区更新。使用 `tp-with-batch-updates` 可以合并所有更改,在结束时一次性应用: 同时修改多个响应式变量时,每个 `setq` 都会触发一次缓冲区更新。使用 `tp-with-batch-updates` 可以合并所有更改,在结束时一次性应用:
```elisp ```elisp
(tp-define-layer 'themed-text (define-tp themed-text ()
:props '(face (:foreground $fg-color :background $bg-color)) :props '(face (:foreground $fg-color :background $bg-color))
:data '((fg-color . "white") (bg-color . "black"))) :data '((fg-color . "white") (bg-color . "black")))
@ -2826,13 +2826,13 @@ tp.el 提供调试模式来帮助理解响应式更新流程:
(defvar theme-accent "cyan") (defvar theme-accent "cyan")
;; 定义主题感知层 ;; 定义主题感知层
(tp-define-layer 'code-keyword (define-tp code-keyword ()
:props '(face (:foreground $theme-accent :weight bold))) :props '(face (:foreground $theme-accent :weight bold)))
(tp-define-layer 'code-comment (define-tp code-comment ()
:props '(face (:foreground "gray" :slant italic))) :props '(face (:foreground "gray" :slant italic)))
(tp-define-layer 'code-string (define-tp code-string ()
:props '(face (:foreground "green"))) :props '(face (:foreground "green")))
;; 将层应用到代码 ;; 将层应用到代码

View File

@ -1816,7 +1816,7 @@ Returns list of (START END VALUE) intervals."
(defvar tp-test-var-color "red" "Test color variable.") (defvar tp-test-var-color "red" "Test color variable.")
(unwind-protect (unwind-protect
(progn (progn
(tp-define-layer 'test-reactive-layer '(face (:foreground $tp-test-var-color))) (define-tp test-reactive-layer () '(face (:foreground $tp-test-var-color)))
;; Check the layer is defined with resolved value ;; Check the layer is defined with resolved value
(let ((props (cdr (assoc 'test-reactive-layer tp-layer-alist)))) (let ((props (cdr (assoc 'test-reactive-layer tp-layer-alist))))
(should (equal (plist-get (plist-get props 'face) :foreground) "red"))) (should (equal (plist-get (plist-get props 'face) :foreground) "red")))
@ -1838,7 +1838,7 @@ Returns list of (START END VALUE) intervals."
(setq tp-test-reactive-color "red") (setq tp-test-reactive-color "red")
(unwind-protect (unwind-protect
(progn (progn
(tp-define-layer 'test-reactive-update '(face (:foreground $tp-test-reactive-color))) (define-tp test-reactive-update () '(face (:foreground $tp-test-reactive-color)))
;; Verify initial value ;; Verify initial value
(let ((props (cdr (assoc 'test-reactive-update tp-layer-alist)))) (let ((props (cdr (assoc 'test-reactive-update tp-layer-alist))))
(should (equal (plist-get (plist-get props 'face) :foreground) "red"))) (should (equal (plist-get (plist-get props 'face) :foreground) "red")))
@ -1857,7 +1857,7 @@ Returns list of (START END VALUE) intervals."
(setq tp-test-region-color "red") (setq tp-test-region-color "red")
(unwind-protect (unwind-protect
(progn (progn
(tp-define-layer 'test-reactive-region '(face (:foreground $tp-test-region-color))) (define-tp test-reactive-region () '(face (:foreground $tp-test-region-color)))
(insert "Hello World") (insert "Hello World")
;; Apply the layer to text ;; Apply the layer to text
(tp-push-layer 1 6 'test-reactive-region) (tp-push-layer 1 6 'test-reactive-region)
@ -1877,7 +1877,7 @@ Returns list of (START END VALUE) intervals."
(setq tp-test-reset-color "red") (setq tp-test-reset-color "red")
(unwind-protect (unwind-protect
(progn (progn
(tp-define-layer 'test-reactive-reset '(face (:foreground $tp-test-reset-color))) (define-tp test-reactive-reset () '(face (:foreground $tp-test-reset-color)))
(should tp-reactive-deps) (should tp-reactive-deps)
(tp-reactive-reset) (tp-reactive-reset)
(should-not tp-reactive-deps)) (should-not tp-reactive-deps))
@ -1891,7 +1891,7 @@ Returns list of (START END VALUE) intervals."
(setq tp-test-reset2-color "red") (setq tp-test-reset2-color "red")
(unwind-protect (unwind-protect
(progn (progn
(tp-define-layer 'test-reactive-reset2 '(face (:foreground $tp-test-reset2-color))) (define-tp test-reactive-reset2 () '(face (:foreground $tp-test-reset2-color)))
(should tp-reactive-deps) (should tp-reactive-deps)
(tp-layer-reset) (tp-layer-reset)
(should-not tp-reactive-deps)) (should-not tp-reactive-deps))
@ -1905,7 +1905,7 @@ Returns list of (START END VALUE) intervals."
(setq tp-test-group-color "red") (setq tp-test-group-color "red")
(unwind-protect (unwind-protect
(progn (progn
(tp-define-layer-group 'test-reactive-group (define-tps test-reactive-group ()
'("first" :props (face (:foreground $tp-test-group-color))) '("first" :props (face (:foreground $tp-test-group-color)))
'("second" :props (face (:foreground "blue")))) '("second" :props (face (:foreground "blue"))))
;; Check the reactive layer is defined with resolved value ;; Check the reactive layer is defined with resolved value
@ -1930,7 +1930,7 @@ Returns list of (START END VALUE) intervals."
(setq tp-test-undef-color "red") (setq tp-test-undef-color "red")
(unwind-protect (unwind-protect
(progn (progn
(tp-define-layer 'test-undef-reactive '(face (:foreground $tp-test-undef-color))) (define-tp test-undef-reactive () '(face (:foreground $tp-test-undef-color)))
;; Check the dependency is registered ;; Check the dependency is registered
(should (assoc 'tp-test-undef-color tp-reactive-deps)) (should (assoc 'tp-test-undef-color tp-reactive-deps))
(let* ((deps (cdr (assoc 'tp-test-undef-color tp-reactive-deps))) (let* ((deps (cdr (assoc 'tp-test-undef-color tp-reactive-deps)))
@ -1977,7 +1977,7 @@ This tests the fix for the bug where (tp-set str 'layer-name) would
incorrectly generate an anonymous tp-name instead of using the layer name." incorrectly generate an anonymous tp-name instead of using the layer name."
(tp-test-with-temp-buffer (tp-test-with-temp-buffer
;; Define a layer with reactive variables ;; Define a layer with reactive variables
(tp-define-layer 'my-entire-string-layer :props '(face (:background $my-entire-string-color)) (define-tp my-entire-string-layer () :props '(face (:background $my-entire-string-color))
:data '((my-entire-string-color . "blue"))) :data '((my-entire-string-color . "blue")))
(let ((str (tp-set " " 'my-entire-string-layer))) (let ((str (tp-set " " 'my-entire-string-layer)))
;; tp-name should be the defined layer name, not an anonymous tp-anon-X ;; tp-name should be the defined layer name, not an anonymous tp-anon-X
@ -2275,7 +2275,7 @@ preserving the native text property behavior."
(setq tp-test-watch-log nil) (setq tp-test-watch-log nil)
(unwind-protect (unwind-protect
(progn (progn
(tp-define-layer 'test-watch-layer :props '(face (:foreground $tp-test-watch-var)) (define-tp test-watch-layer () :props '(face (:foreground $tp-test-watch-var))
:watch '((tp-test-watch-var :watch '((tp-test-watch-var
(lambda (new old layer) (lambda (new old layer)
(push (list new old layer) tp-test-watch-log))))) (push (list new old layer) tp-test-watch-log)))))
@ -2304,7 +2304,7 @@ preserving the native text property behavior."
(tp-test-with-temp-buffer (tp-test-with-temp-buffer
(unwind-protect (unwind-protect
(progn (progn
(tp-define-layer 'test-data-layer :props '(face (:foreground $tp-test-data-color)) (define-tp test-data-layer () :props '(face (:foreground $tp-test-data-color))
:data '(tp-test-data-extra)) :data '(tp-test-data-extra))
;; Check that variables were auto-defined ;; Check that variables were auto-defined
(should (boundp 'tp-test-data-color)) (should (boundp 'tp-test-data-color))
@ -2325,7 +2325,7 @@ preserving the native text property behavior."
;; Set up the source variables ;; Set up the source variables
(setq tp-test-first-name "John") (setq tp-test-first-name "John")
(setq tp-test-last-name "Doe") (setq tp-test-last-name "Doe")
(tp-define-layer 'test-compute-layer (define-tp test-compute-layer ()
:props '(help-echo $tp-test-full-name) :props '(help-echo $tp-test-full-name)
:data '(tp-test-first-name tp-test-last-name) :data '(tp-test-first-name tp-test-last-name)
:compute '((tp-test-full-name :compute '((tp-test-full-name
@ -2355,7 +2355,7 @@ preserving the native text property behavior."
(setq tp-test-dc-first "Jane") (setq tp-test-dc-first "Jane")
(setq tp-test-dc-last "Smith") (setq tp-test-dc-last "Smith")
;; Define layer with :data and :compute ;; Define layer with :data and :compute
(tp-define-layer 'test-dc-layer (define-tp test-dc-layer ()
:props '(face (:foreground $tp-test-dc-color) help-echo $tp-test-dc-full-name) :props '(face (:foreground $tp-test-dc-color) help-echo $tp-test-dc-full-name)
:data '(tp-test-dc-first tp-test-dc-last) :data '(tp-test-dc-first tp-test-dc-last)
:compute '((tp-test-dc-full-name :compute '((tp-test-dc-full-name
@ -2377,21 +2377,21 @@ preserving the native text property behavior."
"Test that :watch requires :props to be explicitly specified." "Test that :watch requires :props to be explicitly specified."
(tp-test-with-temp-buffer (tp-test-with-temp-buffer
(should-error (should-error
(tp-define-layer 'test-invalid (define-tp test-invalid ()
:watch '((some-var (lambda (new old layer) nil))))))) :watch '((some-var (lambda (new old layer) nil)))))))
(ert-deftest tp-test-define-layer-compute-requires-props () (ert-deftest tp-test-define-layer-compute-requires-props ()
"Test that :compute requires :props to be explicitly specified." "Test that :compute requires :props to be explicitly specified."
(tp-test-with-temp-buffer (tp-test-with-temp-buffer
(should-error (should-error
(tp-define-layer 'test-invalid (define-tp test-invalid ()
:compute '((some-var (lambda () "computed"))))))) :compute '((some-var (lambda () "computed")))))))
(ert-deftest tp-test-define-layer-data-requires-props () (ert-deftest tp-test-define-layer-data-requires-props ()
"Test that :data requires :props to be explicitly specified." "Test that :data requires :props to be explicitly specified."
(tp-test-with-temp-buffer (tp-test-with-temp-buffer
(should-error (should-error
(tp-define-layer 'test-invalid (define-tp test-invalid ()
:data '(some-var))))) :data '(some-var)))))
(ert-deftest tp-test-undefine-layer-clears-watch-compute-data () (ert-deftest tp-test-undefine-layer-clears-watch-compute-data ()
@ -2399,7 +2399,7 @@ preserving the native text property behavior."
(tp-test-with-temp-buffer (tp-test-with-temp-buffer
(unwind-protect (unwind-protect
(progn (progn
(tp-define-layer 'test-undef-wcd (define-tp test-undef-wcd ()
:props '(face (:foreground $tp-test-undef-color) help-echo $tp-test-undef-full) :props '(face (:foreground $tp-test-undef-color) help-echo $tp-test-undef-full)
:data '(tp-test-undef-first tp-test-undef-last) :data '(tp-test-undef-first tp-test-undef-last)
:watch '((tp-test-undef-color (lambda (n o l) nil))) :watch '((tp-test-undef-color (lambda (n o l) nil)))
@ -2431,7 +2431,7 @@ preserving the native text property behavior."
(setq tp-test-group-watch-log nil) (setq tp-test-group-watch-log nil)
(unwind-protect (unwind-protect
(progn (progn
(tp-define-layer-group 'test-watch-group (define-tps test-watch-group ()
'("reactive" :props (face (:foreground $tp-test-group-watch-var)) '("reactive" :props (face (:foreground $tp-test-group-watch-var))
:watch ((tp-test-group-watch-var :watch ((tp-test-group-watch-var
(lambda (new old layer) (lambda (new old layer)
@ -2458,7 +2458,7 @@ preserving the native text property behavior."
(progn (progn
(setq tp-test-group-first "Group") (setq tp-test-group-first "Group")
(setq tp-test-group-last "Test") (setq tp-test-group-last "Test")
(tp-define-layer-group 'test-compute-group (define-tps test-compute-group ()
'("computed" :props (help-echo $tp-test-group-full) '("computed" :props (help-echo $tp-test-group-full)
:data (tp-test-group-first tp-test-group-last) :data (tp-test-group-first tp-test-group-last)
:compute '((tp-test-group-full :compute '((tp-test-group-full
@ -2483,7 +2483,7 @@ preserving the native text property behavior."
(tp-test-with-temp-buffer (tp-test-with-temp-buffer
(unwind-protect (unwind-protect
(progn (progn
(tp-define-layer 'test-reset-all (define-tp test-reset-all ()
:props '(face (:foreground $tp-test-reset-color) help-echo $tp-test-reset-full) :props '(face (:foreground $tp-test-reset-color) help-echo $tp-test-reset-full)
:data '(tp-test-reset-first tp-test-reset-last) :data '(tp-test-reset-first tp-test-reset-last)
:watch '((tp-test-reset-color (lambda (n o l) nil))) :watch '((tp-test-reset-color (lambda (n o l) nil)))
@ -2514,7 +2514,7 @@ preserving the native text property behavior."
;; Variables should not exist before ;; Variables should not exist before
(should-not (boundp 'tp-test-auto-var1)) (should-not (boundp 'tp-test-auto-var1))
(should-not (boundp 'tp-test-auto-var2)) (should-not (boundp 'tp-test-auto-var2))
(tp-define-layer 'test-auto-layer :props '(face (:foreground $tp-test-auto-var1)) (define-tp test-auto-layer () :props '(face (:foreground $tp-test-auto-var1))
:data '(tp-test-auto-var2)) :data '(tp-test-auto-var2))
;; Variables should now exist ;; Variables should now exist
(should (boundp 'tp-test-auto-var1)) (should (boundp 'tp-test-auto-var1))
@ -2529,7 +2529,7 @@ preserving the native text property behavior."
(unwind-protect (unwind-protect
(progn (progn
;; Define layer with auto-created variable (nil initial value) ;; Define layer with auto-created variable (nil initial value)
(tp-define-layer 'test-local-layer :props '(face (:foreground $tp-test-local-color))) (define-tp test-local-layer () :props '(face (:foreground $tp-test-local-color)))
;; Apply layer to text ;; Apply layer to text
(insert "Hello World") (insert "Hello World")
(tp-set 1 6 'test-local-layer) (tp-set 1 6 'test-local-layer)
@ -2548,7 +2548,7 @@ preserving the native text property behavior."
(unwind-protect (unwind-protect
(progn (progn
;; Define layer with :data and :compute ;; Define layer with :data and :compute
(tp-define-layer 'test-data-compute-layer (define-tp test-data-compute-layer ()
:props '(help-echo $tp-test-dc-full) :props '(help-echo $tp-test-dc-full)
:data '(tp-test-dc-first tp-test-dc-last) :data '(tp-test-dc-first tp-test-dc-last)
:compute '((tp-test-dc-full :compute '((tp-test-dc-full
@ -2578,7 +2578,7 @@ preserving the native text property behavior."
(unwind-protect (unwind-protect
(progn (progn
;; Define layer with :data having initial values ;; Define layer with :data having initial values
(tp-define-layer 'test-data-init-layer (define-tp test-data-init-layer ()
:props '(face (:foreground $tp-test-init-color) help-echo $tp-test-init-name) :props '(face (:foreground $tp-test-init-color) help-echo $tp-test-init-name)
:data '((tp-test-init-color . "blue") :data '((tp-test-init-color . "blue")
(tp-test-init-name . "Initial Name") (tp-test-init-name . "Initial Name")
@ -2605,7 +2605,7 @@ preserving the native text property behavior."
(unwind-protect (unwind-protect
(progn (progn
;; Define a reactive layer ;; Define a reactive layer
(tp-define-layer 'test-multi-buf-layer :props '(face (:foreground $tp-test-multi-color))) (define-tp test-multi-buf-layer () :props '(face (:foreground $tp-test-multi-color)))
;; Create first buffer with layer applied ;; Create first buffer with layer applied
(setq buf1 (generate-new-buffer " *test-buf1*")) (setq buf1 (generate-new-buffer " *test-buf1*"))
(with-current-buffer buf1 (with-current-buffer buf1
@ -2637,7 +2637,7 @@ preserving the native text property behavior."
(unwind-protect (unwind-protect
(progn (progn
;; Define a reactive layer ;; Define a reactive layer
(tp-define-layer 'test-global-layer :props '(face (:foreground $tp-test-global-color))) (define-tp test-global-layer () :props '(face (:foreground $tp-test-global-color)))
;; Create first buffer with layer applied ;; Create first buffer with layer applied
(setq buf1 (generate-new-buffer " *test-buf1*")) (setq buf1 (generate-new-buffer " *test-buf1*"))
(with-current-buffer buf1 (with-current-buffer buf1
@ -2670,7 +2670,7 @@ preserving the native text property behavior."
(unwind-protect (unwind-protect
(progn (progn
;; First definition with gray color ;; First definition with gray color
(tp-define-layer 'test-redef-layer :props '(face (:background $tp-test-redef-color)) (define-tp test-redef-layer () :props '(face (:background $tp-test-redef-color))
:data '((tp-test-redef-color . "gray"))) :data '((tp-test-redef-color . "gray")))
;; Check initial value ;; Check initial value
(should (equal tp-test-redef-color "gray")) (should (equal tp-test-redef-color "gray"))
@ -2678,7 +2678,7 @@ preserving the native text property behavior."
(let ((props (cdr (assoc 'test-redef-layer tp-layer-alist)))) (let ((props (cdr (assoc 'test-redef-layer tp-layer-alist))))
(should (equal (plist-get (plist-get props 'face) :background) "gray"))) (should (equal (plist-get (plist-get props 'face) :background) "gray")))
;; Re-define with different color ;; Re-define with different color
(tp-define-layer 'test-redef-layer :props '(face (:background $tp-test-redef-color)) (define-tp test-redef-layer () :props '(face (:background $tp-test-redef-color))
:data '((tp-test-redef-color . "blue"))) :data '((tp-test-redef-color . "blue")))
;; Check variable is updated ;; Check variable is updated
(should (equal tp-test-redef-color "blue")) (should (equal tp-test-redef-color "blue"))
@ -2694,12 +2694,12 @@ preserving the native text property behavior."
(unwind-protect (unwind-protect
(progn (progn
;; First definition ;; First definition
(tp-define-layer 'test-redef-props :props '(face (:foreground $tp-test-redef-fg)) (define-tp test-redef-props () :props '(face (:foreground $tp-test-redef-fg))
:data '((tp-test-redef-fg . "red"))) :data '((tp-test-redef-fg . "red")))
(let ((props (cdr (assoc 'test-redef-props tp-layer-alist)))) (let ((props (cdr (assoc 'test-redef-props tp-layer-alist))))
(should (equal (plist-get (plist-get props 'face) :foreground) "red"))) (should (equal (plist-get (plist-get props 'face) :foreground) "red")))
;; Re-define with different props structure ;; Re-define with different props structure
(tp-define-layer 'test-redef-props (define-tp test-redef-props ()
:props '(face (:background $tp-test-redef-bg) help-echo "new") :props '(face (:background $tp-test-redef-bg) help-echo "new")
:data '((tp-test-redef-bg . "yellow"))) :data '((tp-test-redef-bg . "yellow")))
;; Check new props are applied ;; Check new props are applied
@ -2718,14 +2718,14 @@ preserving the native text property behavior."
(unwind-protect (unwind-protect
(progn (progn
;; First definition with $old-var ;; First definition with $old-var
(tp-define-layer 'test-redef-deps :props '(face (:foreground $tp-test-old-var)) (define-tp test-redef-deps () :props '(face (:foreground $tp-test-old-var))
:data '((tp-test-old-var . "red"))) :data '((tp-test-old-var . "red")))
;; Check old var is in dependencies ;; Check old var is in dependencies
(should (assoc 'tp-test-old-var tp-reactive-deps)) (should (assoc 'tp-test-old-var tp-reactive-deps))
(let ((deps (cdr (assoc 'tp-test-old-var tp-reactive-deps)))) (let ((deps (cdr (assoc 'tp-test-old-var tp-reactive-deps))))
(should (assoc 'test-redef-deps deps))) (should (assoc 'test-redef-deps deps)))
;; Re-define with $new-var ;; Re-define with $new-var
(tp-define-layer 'test-redef-deps :props '(face (:foreground $tp-test-new-var)) (define-tp test-redef-deps () :props '(face (:foreground $tp-test-new-var))
:data '((tp-test-new-var . "blue"))) :data '((tp-test-new-var . "blue")))
;; Check old var is no longer in dependencies for this layer ;; Check old var is no longer in dependencies for this layer
(when-let ((deps (cdr (assoc 'tp-test-old-var tp-reactive-deps)))) (when-let ((deps (cdr (assoc 'tp-test-old-var tp-reactive-deps))))
@ -2749,13 +2749,13 @@ preserving the native text property behavior."
(unwind-protect (unwind-protect
(progn (progn
;; First definition with old watcher ;; First definition with old watcher
(tp-define-layer 'test-redef-watch :props '(face (:foreground $tp-test-watch-var)) (define-tp test-redef-watch () :props '(face (:foreground $tp-test-watch-var))
:data '((tp-test-watch-var . "red")) :data '((tp-test-watch-var . "red"))
:watch '((tp-test-watch-var :watch '((tp-test-watch-var
(lambda (new old layer) (lambda (new old layer)
(push (list 'old new) tp-test-watch-log-old))))) (push (list 'old new) tp-test-watch-log-old)))))
;; Re-define with new watcher ;; Re-define with new watcher
(tp-define-layer 'test-redef-watch :props '(face (:foreground $tp-test-watch-var)) (define-tp test-redef-watch () :props '(face (:foreground $tp-test-watch-var))
:data '((tp-test-watch-var . "red")) :data '((tp-test-watch-var . "red"))
:watch '((tp-test-watch-var :watch '((tp-test-watch-var
(lambda (new old layer) (lambda (new old layer)
@ -2779,14 +2779,14 @@ preserving the native text property behavior."
(progn (progn
;; First definition with old compute ;; First definition with old compute
(setq tp-test-compute-src "hello") (setq tp-test-compute-src "hello")
(tp-define-layer 'test-redef-compute (define-tp test-redef-compute ()
:props '(help-echo $tp-test-compute-out) :props '(help-echo $tp-test-compute-out)
:data '(tp-test-compute-src) :data '(tp-test-compute-src)
:compute '((tp-test-compute-out :compute '((tp-test-compute-out
(lambda () (upcase tp-test-compute-src))))) (lambda () (upcase tp-test-compute-src)))))
(should (equal tp-test-compute-out "HELLO")) (should (equal tp-test-compute-out "HELLO"))
;; Re-define with different compute ;; Re-define with different compute
(tp-define-layer 'test-redef-compute (define-tp test-redef-compute ()
:props '(help-echo $tp-test-compute-out) :props '(help-echo $tp-test-compute-out)
:data '(tp-test-compute-src) :data '(tp-test-compute-src)
:compute '((tp-test-compute-out :compute '((tp-test-compute-out
@ -2806,17 +2806,17 @@ preserving the native text property behavior."
(unwind-protect (unwind-protect
(progn (progn
;; First definition with reactive variable ;; First definition with reactive variable
(tp-define-layer 'test-reactive-to-static :props '(face (:foreground $tp-test-r2s-color)) (define-tp test-reactive-to-static () :props '(face (:foreground $tp-test-r2s-color))
:data '((tp-test-r2s-color . "red"))) :data '((tp-test-r2s-color . "red")))
;; Check reactive dependency is registered ;; Check reactive dependency is registered
(should (assoc 'tp-test-r2s-color tp-reactive-deps)) (should (assoc 'tp-test-r2s-color tp-reactive-deps))
;; Re-define as static (non-reactive) ;; Re-define as static (non-reactive)
(tp-define-layer 'test-reactive-to-static '(face bold)) (define-tp test-reactive-to-static () '(face bold))
;; Check reactive dependency is cleared ;; Check reactive dependency is cleared
(when-let ((deps (cdr (assoc 'tp-test-r2s-color tp-reactive-deps)))) (when-let ((deps (cdr (assoc 'tp-test-r2s-color tp-reactive-deps))))
(should-not (assoc 'test-reactive-to-static deps))) (should-not (assoc 'test-reactive-to-static deps)))
;; Check layer has new static props ;; Check layer has new static props
(let ((props (cdr (assoc 'test-reactive-to-static tp-layer-alist)))) (let ((props (tp-layer-props 'test-reactive-to-static)))
(should (eq (plist-get props 'face) 'bold)))) (should (eq (plist-get props 'face) 'bold))))
;; Cleanup ;; Cleanup
(ignore-errors (makunbound 'tp-test-r2s-color))))) (ignore-errors (makunbound 'tp-test-r2s-color)))))
@ -2827,13 +2827,13 @@ preserving the native text property behavior."
(unwind-protect (unwind-protect
(progn (progn
;; First definition ;; First definition
(tp-define-layer-group 'test-redef-group (define-tps test-redef-group ()
'("layer1" :props (face (:background $tp-test-group-color)) '("layer1" :props (face (:background $tp-test-group-color))
:data ((tp-test-group-color . "gray")))) :data ((tp-test-group-color . "gray"))))
;; Check initial value ;; Check initial value
(should (equal tp-test-group-color "gray")) (should (equal tp-test-group-color "gray"))
;; Re-define with different color ;; Re-define with different color
(tp-define-layer-group 'test-redef-group (define-tps test-redef-group ()
'("layer1" :props (face (:background $tp-test-group-color)) '("layer1" :props (face (:background $tp-test-group-color))
:data ((tp-test-group-color . "blue")))) :data ((tp-test-group-color . "blue"))))
;; Check variable is updated ;; Check variable is updated
@ -2850,7 +2850,7 @@ preserving the native text property behavior."
(unwind-protect (unwind-protect
(progn (progn
;; First definition ;; First definition
(tp-define-layer 'test-redef-applied :props '(face (:background $tp-test-applied-color)) (define-tp test-redef-applied () :props '(face (:background $tp-test-applied-color))
:data '((tp-test-applied-color . "gray"))) :data '((tp-test-applied-color . "gray")))
;; Apply to text ;; Apply to text
(insert "Hello World") (insert "Hello World")
@ -2858,10 +2858,10 @@ preserving the native text property behavior."
;; Check initial color ;; Check initial color
(should (equal (plist-get (get-text-property 1 'face) :background) "gray")) (should (equal (plist-get (get-text-property 1 'face) :background) "gray"))
;; Re-define with different color ;; Re-define with different color
(tp-define-layer 'test-redef-applied :props '(face (:background $tp-test-applied-color)) (define-tp test-redef-applied () :props '(face (:background $tp-test-applied-color))
:data '((tp-test-applied-color . "blue"))) :data '((tp-test-applied-color . "blue")))
;; The text should now have the new color ;; The text should now have the new color
;; This happens because tp-define-layer calls tp--update-layer-regions ;; This happens because define-tp calls tp--update-layer-regions
;; at the end to update all text regions with the new properties ;; at the end to update all text regions with the new properties
(should (equal (plist-get (get-text-property 1 'face) :background) "blue"))) (should (equal (plist-get (get-text-property 1 'face) :background) "blue")))
;; Cleanup ;; Cleanup
@ -2948,7 +2948,7 @@ preserving the native text property behavior."
(setq tp-test-reactive-text "Initial") (setq tp-test-reactive-text "Initial")
(unwind-protect (unwind-protect
(progn (progn
(tp-define-layer 'test-reactive-text-layer (define-tp test-reactive-text-layer ()
:props '(face bold tp-text $tp-test-reactive-text)) :props '(face bold tp-text $tp-test-reactive-text))
;; Apply layer to text ;; Apply layer to text
(insert "Hello World") (insert "Hello World")
@ -2976,7 +2976,7 @@ the source text should be used and the reactive variable should be updated."
(unwind-protect (unwind-protect
(progn (progn
;; Define layer with tp-text bound to a reactive variable ;; Define layer with tp-text bound to a reactive variable
(tp-define-layer 'test-init-text-layer (define-tp test-init-text-layer ()
:props '(face bold tp-text $tp-test-text-var)) :props '(face bold tp-text $tp-test-text-var))
;; Apply layer to string - variable is nil, so source text should be used ;; Apply layer to string - variable is nil, so source text should be used
(let ((result (tp-set "2" 'test-init-text-layer))) (let ((result (tp-set "2" 'test-init-text-layer)))
@ -2992,7 +2992,7 @@ the source text should be used and the reactive variable should be updated."
;; This is necessary because the layer definition caches the resolved ;; This is necessary because the layer definition caches the resolved
;; tp-text value, and we want to test the behavior when the variable ;; tp-text value, and we want to test the behavior when the variable
;; already has a non-nil value at layer application time. ;; already has a non-nil value at layer application time.
(tp-define-layer 'test-init-text-layer (define-tp test-init-text-layer ()
:props '(face bold tp-text $tp-test-text-var)) :props '(face bold tp-text $tp-test-text-var))
(let ((result (tp-set "2" 'test-init-text-layer))) (let ((result (tp-set "2" 'test-init-text-layer)))
;; Result should be the variable value "18", not source "2" ;; Result should be the variable value "18", not source "2"
@ -3019,7 +3019,7 @@ the inserted text should be that string, not the source text."
(progn (progn
(setq tp-test-name-part1 "Hello") (setq tp-test-name-part1 "Hello")
(setq tp-test-name-part2 "World") (setq tp-test-name-part2 "World")
(tp-define-layer 'test-computed-text-layer (define-tp test-computed-text-layer ()
:props '(face bold tp-text $tp-test-full-text) :props '(face bold tp-text $tp-test-full-text)
:data '(tp-test-name-part1 tp-test-name-part2) :data '(tp-test-name-part1 tp-test-name-part2)
:compute '((tp-test-full-text :compute '((tp-test-full-text
@ -3346,7 +3346,7 @@ When using tp-set (direct property setting), tp-name is NOT added."
(unwind-protect (unwind-protect
(progn (progn
;; Define a reactive layer ;; Define a reactive layer
(tp-define-layer 'test-batch-layer (define-tp test-batch-layer ()
:props '(face (:foreground $tp-test-batch-color)) :props '(face (:foreground $tp-test-batch-color))
:data '((tp-test-batch-color . "red"))) :data '((tp-test-batch-color . "red")))
(insert "Hello World") (insert "Hello World")
@ -3370,7 +3370,7 @@ When using tp-set (direct property setting), tp-name is NOT added."
(unwind-protect (unwind-protect
(progn (progn
;; Define a reactive layer with multiple vars ;; Define a reactive layer with multiple vars
(tp-define-layer 'test-multi-batch (define-tp test-multi-batch ()
:props '(face (:foreground $tp-test-fg :background $tp-test-bg)) :props '(face (:foreground $tp-test-fg :background $tp-test-bg))
:data '((tp-test-fg . "white") (tp-test-bg . "black"))) :data '((tp-test-fg . "white") (tp-test-bg . "black")))
(insert "Hello World") (insert "Hello World")
@ -3427,7 +3427,7 @@ When using tp-set (direct property setting), tp-name is NOT added."
(unwind-protect (unwind-protect
(progn (progn
;; Define a layer with transform ;; Define a layer with transform
(tp-define-layer 'test-transform-layer (define-tp test-transform-layer ()
:props '(face bold tp-text $tp-test-value) :props '(face bold tp-text $tp-test-value)
:data '((tp-test-value . "hello")) :data '((tp-test-value . "hello"))
:transform #'upcase) :transform #'upcase)
@ -3444,7 +3444,7 @@ When using tp-set (direct property setting), tp-name is NOT added."
(unwind-protect (unwind-protect
(progn (progn
;; Define a layer with transform (format as currency) ;; Define a layer with transform (format as currency)
(tp-define-layer 'test-currency-layer (define-tp test-currency-layer ()
:props '(face bold tp-text $tp-test-amount) :props '(face bold tp-text $tp-test-amount)
:data '((tp-test-amount . "100")) :data '((tp-test-amount . "100"))
:transform (lambda (text) :transform (lambda (text)
@ -3466,14 +3466,14 @@ When using tp-set (direct property setting), tp-name is NOT added."
(unwind-protect (unwind-protect
(progn (progn
;; Define with transform ;; Define with transform
(tp-define-layer 'test-redef-transform (define-tp test-redef-transform ()
:props '(face bold tp-text $tp-test-text) :props '(face bold tp-text $tp-test-text)
:data '((tp-test-text . "hello")) :data '((tp-test-text . "hello"))
:transform #'upcase) :transform #'upcase)
;; Check transform is registered ;; Check transform is registered
(should (assoc 'test-redef-transform tp-layer-transforms)) (should (assoc 'test-redef-transform tp-layer-transforms))
;; Redefine without transform ;; Redefine without transform
(tp-define-layer 'test-redef-transform (define-tp test-redef-transform ()
:props '(face bold tp-text $tp-test-text) :props '(face bold tp-text $tp-test-text)
:data '((tp-test-text . "hello"))) :data '((tp-test-text . "hello")))
;; Transform should be removed ;; Transform should be removed

87
tp.el
View File

@ -2300,7 +2300,7 @@ Example:
;;;============================================================================ ;;;============================================================================
(defun tp--parse-define-layer-args (args) (defun tp--parse-define-layer-args (args)
"Parse ARGS for tp-define-layer function. "Parse ARGS for tp--define-layer-internal function.
Returns plist with keys :props, :data, :watch, :compute, :transform. Returns plist with keys :props, :data, :watch, :compute, :transform.
- Keyword arguments: :props PLIST [:data DATA] [:watch WATCH] [:compute COMPUTE] [:transform FN]" - Keyword arguments: :props PLIST [:data DATA] [:watch WATCH] [:compute COMPUTE] [:transform FN]"
(let (props data watch compute transform has-keywords) (let (props data watch compute transform has-keywords)
@ -2318,7 +2318,7 @@ Returns plist with keys :props, :data, :watch, :compute, :transform.
(:watch (setq watch (cadr rest) rest (cddr rest))) (:watch (setq watch (cadr rest) rest (cddr rest)))
(:compute (setq compute (cadr rest) rest (cddr rest))) (:compute (setq compute (cadr rest) rest (cddr rest)))
(:transform (setq transform (cadr rest) rest (cddr rest))) (:transform (setq transform (cadr rest) rest (cddr rest)))
(_ (error "Unknown keyword in tp-define-layer: %s" (car rest)))))) (_ (error "Unknown keyword in tp--define-layer-internal: %s" (car rest))))))
;; Validate: if :watch, :compute, or :data present, :props must be present ;; Validate: if :watch, :compute, or :data present, :props must be present
(when (and (or watch compute data) (null props)) (when (and (or watch compute data) (null props))
(error "When using :watch, :compute, or :data, :props must be explicitly specified"))) (error "When using :watch, :compute, or :data, :props must be explicitly specified")))
@ -2326,20 +2326,20 @@ Returns plist with keys :props, :data, :watch, :compute, :transform.
((and (= (length args) 1) ((and (= (length args) 1)
(listp (car args))) (listp (car args)))
(setq props (car args))) (setq props (car args)))
(t (error "Invalid tp-define-layer format"))) (t (error "Invalid tp--define-layer-internal format")))
(list :props props :data data :watch watch :compute compute :transform transform))) (list :props props :data data :watch watch :compute compute :transform transform)))
(defun tp-define-layer (name &rest args) (defun tp--define-layer-internal (name &rest args)
"Define a single text property layer named NAME. "Define a single text property layer named NAME.
This function supports two formats: This function supports two formats:
Format 1 - Direct plist (no :watch/:compute/:data/:transform support): Format 1 - Direct plist (no :watch/:compute/:data/:transform support):
(tp-define-layer \\='layer-name (tp--define-layer-internal \\='layer-name
\\='(display \"🌑\" face (:height 1.0))) \\='(display \"🌑\" face (:height 1.0)))
Format 2 - With :props, :data, :watch, :compute, and/or :transform (Vue 3 style reactivity): Format 2 - With :props, :data, :watch, :compute, and/or :transform (Vue 3 style reactivity):
(tp-define-layer \\='layer-name (tp--define-layer-internal \\='layer-name
;; props: $-prefixed symbols are reactive variables; auto-defined if not bound ;; props: $-prefixed symbols are reactive variables; auto-defined if not bound
:props \\='(face (:foreground $my-color) help-echo $full-name) :props \\='(face (:foreground $my-color) help-echo $full-name)
;; data: additional reactive variables not used in props; auto-defined if not bound ;; data: additional reactive variables not used in props; auto-defined if not bound
@ -2444,17 +2444,17 @@ The layer is stored in `tp-layer-alist'."
(defmacro define-tp (name arglist &rest body) (defmacro define-tp (name arglist &rest body)
"Define a text property layer named NAME. "Define a text property layer named NAME.
This macro supports three formats: This macro supports four formats:
Format 1 - Non-parameterized simple (empty arglist, simple body): Format 1 - Non-parameterized simple (empty arglist, simple body):
(define-tp tp-bold () (define-tp tp-bold ()
\\='(face bold)) \\='(face bold))
Format 2 - Parameterized (single argument): Format 2 - Parameterized simple (single argument, simple body):
(define-tp tp-space (pixel) (define-tp tp-space (pixel)
\\=`(display (space :width (,pixel)))) \\=`(display (space :width (,pixel))))
Format 3 - With reactive features (supports :props, :data, :compute, :watch, :transform): Format 3 - Non-parameterized with reactive features:
(define-tp my-layer () (define-tp my-layer ()
:props \\='(face (:foreground $my-color)) :props \\='(face (:foreground $my-color))
:data \\='((my-color . \"red\")) :data \\='((my-color . \"red\"))
@ -2462,6 +2462,12 @@ Format 3 - With reactive features (supports :props, :data, :compute, :watch, :tr
:watch \\='((my-color (lambda (new old layer) (message \"Color changed!\")))) :watch \\='((my-color (lambda (new old layer) (message \"Color changed!\"))))
:transform (lambda (text) (upcase text))) :transform (lambda (text) (upcase text)))
Format 4 - Parameterized with reactive features:
(define-tp my-color-layer (color)
:props \\=`(face (:foreground ,color))
:data \\='((my-var . \"value\"))
:compute ...)
Usage: Usage:
(tp-set \"emacs\" \\='tp-bold t) (tp-set \"emacs\" \\='tp-bold t)
(tp-set 0 5 \\='(tp-bold t) \"emacs\") (tp-set 0 5 \\='(tp-bold t) \"emacs\")
@ -2474,7 +2480,7 @@ ARGLIST must be either:
BODY is either: BODY is either:
- A single property list expression (simple format) - A single property list expression (simple format)
- Keyword arguments starting with :props, :data, :compute, :watch, or :transform - Keyword arguments starting with :props, :data, :compute, :watch, or :transform
(reactive format - only supported for non-parameterized layers) (reactive format)
Note: NAME cannot be a built-in Emacs text property name like `face', Note: NAME cannot be a built-in Emacs text property name like `face',
`display', `invisible', etc. See `tp--builtin-text-properties' for the `display', `invisible', etc. See `tp--builtin-text-properties' for the
@ -2489,10 +2495,15 @@ complete list of reserved names."
(let ((first-elem (car body))) (let ((first-elem (car body)))
(if (and (keywordp first-elem) (if (and (keywordp first-elem)
(memq first-elem '(:props :data :compute :watch :transform))) (memq first-elem '(:props :data :compute :watch :transform)))
;; Reactive format - use tp-define-layer ;; Reactive format
(if arglist (if arglist
(error "define-tp: reactive features (:props, :data, etc.) are only supported for non-parameterized layers") ;; Parameterized reactive: store as function that calls tp--define-layer-internal
`(tp-define-layer ',name ,@body)) (let ((arg (car arglist)))
`(tp--define-layer-parameterized-reactive
',name ',arglist
(lambda (,arg) (list ,@body))))
;; Non-parameterized reactive: use tp--define-layer-internal directly
`(tp--define-layer-internal ',name ,@body))
;; Simple format (original behavior) ;; Simple format (original behavior)
(let ((simple-body (car body))) (let ((simple-body (car body)))
(cond (cond
@ -2506,19 +2517,47 @@ complete list of reserved names."
(t (t
(error "define-tp ARGLIST must be empty or contain exactly one symbol"))))))) (error "define-tp ARGLIST must be empty or contain exactly one symbol")))))))
(defun tp--define-layer-parameterized-reactive (name arglist body-fn)
"Define a parameterized layer NAME with ARGLIST and reactive BODY-FN.
BODY-FN is a function that takes the parameter and returns a keyword plist
like (:props ... :data ... :compute ... :watch ... :transform ...).
The layer is stored and evaluated when tp-layer-props-with-arg is called."
;; Store the parameterized reactive definition
;; Format: (ARGLIST BODY-FN) where BODY-FN returns keyword plist
(let ((entry (list arglist body-fn)))
(if (assoc name tp-layer-alist)
(setf (cdr (assoc name tp-layer-alist)) entry)
(push (cons name entry) tp-layer-alist)))
(assoc name tp-layer-alist))
(defun tp--define-layer-unified (name arglist body) (defun tp--define-layer-unified (name arglist body)
"Define a layer NAME with ARGLIST and BODY using unified structure. "Define a layer NAME with ARGLIST and BODY using unified structure.
For non-parameterized layers, ARGLIST is nil and BODY is the evaluated plist. For non-parameterized layers, ARGLIST is nil and BODY is the evaluated plist.
For parameterized layers, ARGLIST contains one symbol and BODY is the unevaluated form. For parameterized layers, ARGLIST contains one symbol and BODY is the unevaluated form.
Stores the layer in `tp-layer-alist' with format: (LAYER-NAME ARGLIST BODY-FORM)." Stores the layer in `tp-layer-alist' with format: (LAYER-NAME ARGLIST BODY-FORM).
;; Store in tp-layer-alist with unified format: (LAYER-NAME ARGLIST BODY-FORM)
;; For non-parameterized, quote the body so eval returns the plist For non-parameterized layers, if BODY contains reactive symbols ($-prefixed),
(let ((stored-body (if arglist body `',body))) delegates to `tp--define-layer-internal' for proper reactive handling."
(let ((entry (list arglist stored-body))) (if arglist
;; Parameterized - store for later evaluation
(let ((entry (list arglist body)))
(if (assoc name tp-layer-alist) (if (assoc name tp-layer-alist)
(setf (cdr (assoc name tp-layer-alist)) entry) (setf (cdr (assoc name tp-layer-alist)) entry)
(push (cons name entry) tp-layer-alist)))) (push (cons name entry) tp-layer-alist))
(assoc name tp-layer-alist)) (assoc name tp-layer-alist))
;; Non-parameterized - check for reactive symbols
(let ((reactive-syms (tp--collect-reactive-symbols body)))
(if reactive-syms
;; Has reactive symbols - use tp--define-layer-internal for proper handling
(tp--define-layer-internal name body)
;; No reactive symbols - store as static layer
;; Clean up old reactive dependencies if the layer was previously reactive
(tp--unregister-reactive-deps name)
(let ((entry (list nil `',body)))
(if (assoc name tp-layer-alist)
(setf (cdr (assoc name tp-layer-alist)) entry)
(push (cons name entry) tp-layer-alist))
(assoc name tp-layer-alist))))))
(defun tp--layer-group-element-format (element) (defun tp--layer-group-element-format (element)
"Determine the format type of ELEMENT. "Determine the format type of ELEMENT.
@ -2639,7 +2678,7 @@ COMPUTE is the list of computed variable definitions."
(tp--update-layer-regions layer-name))) (tp--update-layer-regions layer-name)))
layer-name)) layer-name))
(defun tp-define-layer-group (name &rest elements) (defun tp--define-layer-internal-group (name &rest elements)
"Define a layer group named NAME containing multiple layers. "Define a layer group named NAME containing multiple layers.
This function accepts a list of layer definitions in ELEMENTS. This function accepts a list of layer definitions in ELEMENTS.
@ -2654,7 +2693,7 @@ Each element in ELEMENTS should be one of:
All property lists should be evaluated (quoted in the call). All property lists should be evaluated (quoted in the call).
Example: Example:
(tp-define-layer-group \\='my-group (tp--define-layer-internal-group \\='my-group
\\='existing-layer \\='existing-layer
\\='(face bold) \\='(face bold)
\\='(\"named\" . (face italic)) \\='(\"named\" . (face italic))
@ -2707,8 +2746,8 @@ ELEMENTS is the list of layer definitions."
(setf (cdr (assoc name tp-layer-groups)) entry) (setf (cdr (assoc name tp-layer-groups)) entry)
(push (cons name entry) tp-layer-groups)) (push (cons name entry) tp-layer-groups))
(assoc name tp-layer-groups)) (assoc name tp-layer-groups))
;; Non-parameterized - define immediately using tp-define-layer-group ;; Non-parameterized - define immediately using tp--define-layer-internal-group
(apply #'tp-define-layer-group name elements))) (apply #'tp--define-layer-internal-group name elements)))
(defun tp--define-layer-group-unified (name arglist body-form) (defun tp--define-layer-group-unified (name arglist body-form)
"Define a parameterized layer group NAME with ARGLIST and BODY-FORM. "Define a parameterized layer group NAME with ARGLIST and BODY-FORM.
@ -3192,7 +3231,7 @@ Returns a list of (START END PROPERTIES) for matching intervals."
Used by layer stack functions that need tp-name for identification. Used by layer stack functions that need tp-name for identification.
LAYER-SPEC can be: LAYER-SPEC can be:
- A symbol (non-parameterized layer name from define-tp or tp-define-layer) - A symbol (non-parameterized layer name from define-tp or tp--define-layer-internal)
- A list (LAYER-NAME ARG) for parameterized layers from define-tp - A list (LAYER-NAME ARG) for parameterized layers from define-tp
- A plist for inline layer definition - A plist for inline layer definition
- A list (NAME &rest PLIST) for named inline layer" - A list (NAME &rest PLIST) for named inline layer"