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:
- ✅ **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**:
- Placement: `tp-put-layer` (specific position), `tp-push-layer` (top)
- 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
(defvar my-color "red") ;; Reactive variable
(tp-define-layer 'my-highlight
(define-tp my-highlight ()
:props '(face (:foreground $my-color)))
;; 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!
;; 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))
:data ((first-name . "John") (last-name . "Doe")) ;; With initial values
: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)
```
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:**
@ -460,7 +460,7 @@ LAYER-NAME can be a symbol representing a layer defined by `tp-define-layer` or
;; => (1 . 10)
;; Use a defined layer name
(tp-define-layer 'warning-style
(define-tp warning-style ()
'(face (:foreground "orange" :weight bold)))
(with-temp-buffer
(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))
;; Use a defined layer name on entire string
(tp-define-layer 'my-style
(define-tp my-style ()
:props '(face (:foreground $my-color))
:data ((my-color . "blue")))
(tp-set " " 'my-style)
@ -523,7 +523,7 @@ Completely replace ALL text properties with the specified ones.
(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:**
@ -541,7 +541,7 @@ LAYER-NAME can be a symbol representing a layer defined by `tp-define-layer` or
;; => #("Hello" 0 5 (face italic))
;; Use a defined layer name
(tp-define-layer 'error-style
(define-tp error-style ()
'(face (:foreground "red" :weight bold)))
(with-temp-buffer
(insert "Hello World")
@ -561,7 +561,7 @@ Add or update properties with deep merge support for nested plists.
(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:**
@ -589,7 +589,7 @@ LAYER-NAME can be a symbol representing a layer defined by `tp-define-layer` or
;; => (shadow bold)
;; Use a defined layer name
(tp-define-layer 'highlight-style
(define-tp highlight-style ()
'(face (:background "yellow")))
(with-temp-buffer
(insert "Hello World")
@ -867,7 +867,7 @@ Clear all text properties from a region.
Set properties on all occurrences of a string pattern.
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")`.
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.
**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))
;; Use a defined layer name
(tp-define-layer 'todo-style
(define-tp todo-style ()
'(face (:foreground "orange" :weight bold)))
(with-temp-buffer
(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.
PATTERN can be a string or list of strings (multiple patterns).
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.
```elisp
@ -935,7 +935,7 @@ OBJECT is a buffer or string; nil means current buffer.
;; => ((1 . 5) (12 . 17))
;; Use a defined layer name
(tp-define-layer 'alert-style
(define-tp alert-style ()
'(face (:background "red" :foreground "white")))
(with-temp-buffer
(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.
PATTERN can be a string or list of strings (multiple patterns).
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.
```elisp
@ -976,7 +976,7 @@ OBJECT is a buffer or string; nil means current buffer.
;; => ((1 . 5) (12 . 17))
;; Use a defined layer name
(tp-define-layer 'underline-style
(define-tp underline-style ()
'(face (:underline (:color "blue" :style wave))))
(with-temp-buffer
(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.
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")`.
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.
**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))
;; Use a defined layer name
(tp-define-layer 'number-style
(define-tp number-style ()
'(face (:foreground "green")))
(with-temp-buffer
(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.
PATTERN can be a string or list of strings (multiple regexps).
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.
```elisp
@ -1060,7 +1060,7 @@ OBJECT is a buffer or string; nil means current buffer.
;; => (face italic)
;; Use a defined layer name
(tp-define-layer 'code-number
(define-tp code-number ()
'(face (:foreground "cyan")))
(with-temp-buffer
(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.
PATTERN can be a string or list of strings (multiple regexps).
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.
```elisp
@ -1102,7 +1102,7 @@ OBJECT is a buffer or string; nil means current buffer.
;; => (face italic help-echo "number")
;; Use a defined layer name
(tp-define-layer 'bold-underline
(define-tp bold-underline ()
'(face (:weight bold :underline t)))
(with-temp-buffer
(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
(progn
(defvar my-reactive-color "red")
(tp-define-layer 'reactive-layer
(define-tp reactive-layer ()
:props '(face (:foreground $my-reactive-color)))
;; Clear reactive bindings only
(tp-reactive-reset)
@ -2460,12 +2460,12 @@ Return t if OBJECT has no text properties.
(progn
(tp-layer-reset)
;; Define layers for different highlighting purposes
(tp-define-layer 'code-base
(define-tp code-base ()
'(face font-lock-keyword-face))
(tp-define-layer 'code-error
(define-tp code-error ()
'(face (:underline (:color "red" :style wave))
help-echo "Syntax error"))
(tp-define-layer 'code-debug
(define-tp code-debug ()
'(face (:background "dark blue")))
(with-temp-buffer
(insert (make-string 100 ?x)) ; Create 100-char buffer
@ -2491,10 +2491,10 @@ Return t if OBJECT has no text properties.
(progn
(tp-layer-reset)
;; Define status layers as a group
(tp-define-layer 'status-todo '(face (:foreground "gray")))
(tp-define-layer 'status-progress '(face (:foreground "yellow")))
(tp-define-layer 'status-done '(face (:foreground "green")))
(tp-define-layer-group 'task-status 'status-todo 'status-progress 'status-done)
(define-tp status-todo () '(face (:foreground "gray")))
(define-tp status-progress () '(face (:foreground "yellow")))
(define-tp status-done () '(face (:foreground "green")))
(define-tps task-status 'status-todo 'status-progress 'status-done)
;; Check group is defined
(length (tp-group-props 'task-status)))
;; => 3
@ -2512,7 +2512,7 @@ Return t if OBJECT has no text properties.
;; Define temporary highlight layer
(progn
(tp-layer-reset)
(tp-define-layer 'temp-highlight
(define-tp temp-highlight ()
'(face (:background "yellow")))
(tp-layer-props '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)
(defvar my-color "red")
(tp-define-layer 'my-layer
(define-tp my-layer ()
:props '(face (:foreground $my-color)))
(tp-push-layer 1 10 'my-layer)
;; Just change the variable - all text updates automatically!
@ -2573,7 +2573,7 @@ Traditional text property manipulation requires manually updating all affected t
```elisp
(defvar highlight-color "yellow")
(tp-define-layer 'my-highlight
(define-tp my-highlight ()
:props '(face (:background $highlight-color)))
(with-temp-buffer
@ -2592,7 +2592,7 @@ Traditional text property manipulation requires manually updating all affected t
(defvar fg-color "white")
(defvar bg-color "black")
(tp-define-layer 'themed-text
(define-tp themed-text ()
:props '(face (:foreground $fg-color :background $bg-color)))
;; 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:
```elisp
(tp-define-layer 'user-info
(define-tp user-info ()
:props '(help-echo $full-name)
:data '(first-name last-name) ; Not used directly in props
: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:
```elisp
(tp-define-layer 'user-info
(define-tp user-info ()
:props '(help-echo $full-name)
:data '((first-name . "John") (last-name . "Doe"))
: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:
```elisp
(tp-define-layer 'progress-display
(define-tp progress-display ()
:props '(display $progress-text face (:foreground $progress-color))
:data '((current . 0) (total . 100))
: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:
```elisp
(tp-define-layer 'monitored-layer
(define-tp monitored-layer ()
:props '(face (:foreground $status-color))
:watch ((status-color
(lambda (new-val old-val layer-name)
@ -2668,7 +2668,7 @@ The `:transform` keyword allows you to register a transformation function that p
```elisp
;; Number formatting
(tp-define-layer 'price-display
(define-tp price-display ()
:props '(tp-text $price)
:data '((price . "99.9"))
: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
;; Date formatting
(tp-define-layer 'date-display
(define-tp date-display ()
:props '(tp-text $timestamp)
:data '((timestamp . "1703865600"))
: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)))))
;; Uppercase conversion
(tp-define-layer 'uppercase-text
(define-tp uppercase-text ()
:props '(tp-text $content)
:data '((content . "hello"))
:transform #'upcase)
@ -2699,7 +2699,7 @@ The transform function:
### 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
(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:
```elisp
(tp-define-layer 'warning-style
(define-tp warning-style ()
:props '(face (:foreground "orange" :weight bold)))
;; 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:
```elisp
(tp-define-layer-group 'status-indicators
(define-tps status-indicators
'("success" :props (face (:foreground $success-color))
:data ((success-color . "green")))
'("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:
```elisp
(tp-define-layer 'themed-text
(define-tp themed-text ()
:props '(face (:foreground $fg-color :background $bg-color))
:data '((fg-color . "white") (bg-color . "black")))
@ -2818,13 +2818,13 @@ To clear all reactive dependencies and watchers:
(defvar theme-accent "cyan")
;; Define theme-aware layers
(tp-define-layer 'code-keyword
(define-tp code-keyword ()
:props '(face (:foreground $theme-accent :weight bold)))
(tp-define-layer 'code-comment
(define-tp code-comment ()
:props '(face (:foreground "gray" :slant italic)))
(tp-define-layer 'code-string
(define-tp code-string ()
:props '(face (:foreground "green")))
;; Apply layers to code

View File

@ -273,10 +273,10 @@
;; 之后只需改变变量 - 文本自动更新!
(setq my-color "blue") ;; 所有 my-highlight 层的文本自动变成蓝色!
;; 高级响应式示例(使用内部函数 tp-define-layer
;; 高级响应式示例(使用 define-tp
;; 对于需要 :data、:compute、:watch 等高级特性的场景,
;; 可以使用内部函数 tp-define-layer
(tp-define-layer 'full-name-layer
;; 可以使用 define-tp
(define-tp full-name-layer ()
:props '(help-echo $full-name face (:foreground $name-color))
:data '((first-name . "John") (last-name . "Doe")) ;; 带初始值
: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))
;; 在整个字符串上使用已定义的层名称
(tp-define-layer 'my-style
(define-tp my-style ()
:props '(face (:foreground $my-color))
:data '((my-color . "blue")))
(tp-set " " 'my-style)
@ -535,7 +535,7 @@ LAYER-NAME 可以是通过 `define-tp` 定义的自定义文本属性名称或
;; => #("Hello" 0 5 (face italic))
;; 使用已定义的层名称
(tp-define-layer 'error-style
(define-tp error-style ()
'(face (:foreground "red" :weight bold)))
(with-temp-buffer
(insert "Hello World")
@ -583,7 +583,7 @@ LAYER-NAME 可以是通过 `define-tp` 定义的自定义文本属性名称或
;; => (shadow bold)
;; 使用已定义的层名称
(tp-define-layer 'highlight-style
(define-tp highlight-style ()
'(face (:background "yellow")))
(with-temp-buffer
(insert "Hello World")
@ -888,7 +888,7 @@ OBJECT 是缓冲区或字符串nil 表示当前缓冲区。
;; => #("Hello world" 0 5 (face bold) 6 11 (face bold))
;; 使用已定义的层名称
(tp-define-layer 'todo-style
(define-tp todo-style ()
'(face (:foreground "orange" :weight bold)))
(with-temp-buffer
(insert "TODO: fix this. TODO: also this.")
@ -929,7 +929,7 @@ OBJECT 是缓冲区或字符串nil 表示当前缓冲区。
;; => ((1 . 5) (12 . 17))
;; 使用已定义的层名称
(tp-define-layer 'alert-style
(define-tp alert-style ()
'(face (:background "red" :foreground "white")))
(with-temp-buffer
(insert "TODO: fix this")
@ -970,7 +970,7 @@ OBJECT 是缓冲区或字符串nil 表示当前缓冲区。
;; => ((1 . 5) (12 . 17))
;; 使用已定义的层名称
(tp-define-layer 'underline-style
(define-tp underline-style ()
'(face (:underline (:color "blue" :style wave))))
(with-temp-buffer
(insert "TODO: fix this")
@ -1012,7 +1012,7 @@ OBJECT 是缓冲区或字符串nil 表示当前缓冲区。
;; => #("abc 123 XYZ" 4 7 (face bold) 8 11 (face bold))
;; 使用已定义的层名称
(tp-define-layer 'number-style
(define-tp number-style ()
'(face (:foreground "green")))
(with-temp-buffer
(insert "abc 123 def 456")
@ -1054,7 +1054,7 @@ OBJECT 是缓冲区或字符串nil 表示当前缓冲区。
;; => (face italic)
;; 使用已定义的层名称
(tp-define-layer 'code-number
(define-tp code-number ()
'(face (:foreground "cyan")))
(with-temp-buffer
(insert "abc 123 def 456")
@ -1096,7 +1096,7 @@ OBJECT 是缓冲区或字符串nil 表示当前缓冲区。
;; => (face italic help-echo "number")
;; 使用已定义的层名称
(tp-define-layer 'bold-underline
(define-tp bold-underline ()
'(face (:weight bold :underline t)))
(with-temp-buffer
(insert "abc 123 def 456")
@ -1612,8 +1612,8 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
(progn
(setq tp-layer-alist nil)
(setq tp-layer-groups nil)
(tp-define-layer 'l1 '(face bold))
(tp-define-layer-group 'my-group 'l1)
(define-tp l1 () '(face bold))
(define-tps my-group 'l1)
(tp-undefine-group 'my-group)
(assoc 'my-group tp-layer-groups))
;; => nil
@ -1657,7 +1657,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
;; 定义一个响应式层
(progn
(defvar my-reactive-color "red")
(tp-define-layer 'reactive-layer
(define-tp reactive-layer ()
:props '(face (:foreground $my-reactive-color)))
;; 仅清除响应式绑定
(tp-reactive-reset)
@ -1716,7 +1716,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
(progn
(tp-layer-reset)
(define-tp base () '(face default))
(tp-define-layer 'info '(face (:foreground "blue")))
(define-tp info () '(face (:foreground "blue")))
(with-temp-buffer
(insert "Hello World")
(tp-put-layer 1 10 'base 0)
@ -2093,7 +2093,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
(progn
(tp-layer-reset)
(define-tp layer1 () '(face bold))
(tp-define-layer 'layer2 '(help-echo "tip"))
(define-tp layer2 () '(help-echo "tip"))
(with-temp-buffer
(insert "Hello World")
(tp-push-layer 1 10 'layer1)
@ -2106,7 +2106,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
(progn
(tp-layer-reset)
(define-tp layer1 () '(face bold))
(tp-define-layer 'layer2 '(help-echo "tip"))
(define-tp layer2 () '(help-echo "tip"))
(with-temp-buffer
(insert "Hello World")
(tp-push-layer 1 10 'layer1)
@ -2137,7 +2137,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
(progn
(tp-layer-reset)
(define-tp layer1 () '(face bold))
(tp-define-layer 'layer2 '(help-echo "tip"))
(define-tp layer2 () '(help-echo "tip"))
(with-temp-buffer
(insert "Hello World")
(tp-push-layer 1 10 'layer1)
@ -2283,8 +2283,8 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
```elisp
(progn
(tp-layer-reset)
(tp-define-layer 'layer1 '(face (:foreground "red")))
(tp-define-layer 'layer2 '(face (:foreground "blue")))
(define-tp layer1 () '(face (:foreground "red")))
(define-tp layer2 () '(face (:foreground "blue")))
(with-temp-buffer
(insert "Hello World")
(tp-push-layer 1 10 'layer1)
@ -2468,12 +2468,12 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
(progn
(tp-layer-reset)
;; 为不同高亮目的定义属性层
(tp-define-layer 'code-base
(define-tp code-base ()
'(face font-lock-keyword-face))
(tp-define-layer 'code-error
(define-tp code-error ()
'(face (:underline (:color "red" :style wave))
help-echo "语法错误"))
(tp-define-layer 'code-debug
(define-tp code-debug ()
'(face (:background "dark blue")))
(with-temp-buffer
(insert (make-string 100 ?x)) ; 创建 100 字符缓冲区
@ -2499,10 +2499,10 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
(progn
(tp-layer-reset)
;; 将状态属性层定义为一个组
(tp-define-layer 'status-todo '(face (:foreground "gray")))
(tp-define-layer 'status-progress '(face (:foreground "yellow")))
(tp-define-layer 'status-done '(face (:foreground "green")))
(tp-define-layer-group 'task-status 'status-todo 'status-progress 'status-done)
(define-tp status-todo () '(face (:foreground "gray")))
(define-tp status-progress () '(face (:foreground "yellow")))
(define-tp status-done () '(face (:foreground "green")))
(define-tps task-status 'status-todo 'status-progress 'status-done)
;; 检查组是否已定义
(length (tp-group-props 'task-status)))
;; => 3
@ -2520,7 +2520,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
;; 定义临时高亮属性层
(progn
(tp-layer-reset)
(tp-define-layer 'temp-highlight
(define-tp temp-highlight ()
'(face (:background "yellow")))
(tp-layer-props 'temp-highlight))
;; => (face (:background "yellow") tp-name temp-highlight)
@ -2559,7 +2559,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
;; 响应式方式(自动更新)
(defvar my-color "red")
(tp-define-layer 'my-layer
(define-tp my-layer ()
:props '(face (:foreground $my-color)))
(tp-push-layer 1 10 'my-layer)
;; 只需改变变量 - 所有文本自动更新!
@ -2581,7 +2581,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
```elisp
(defvar highlight-color "yellow")
(tp-define-layer 'my-highlight
(define-tp my-highlight ()
:props '(face (:background $highlight-color)))
(with-temp-buffer
@ -2600,7 +2600,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
(defvar fg-color "white")
(defvar bg-color "black")
(tp-define-layer 'themed-text
(define-tp themed-text ()
:props '(face (:foreground $fg-color :background $bg-color)))
;; 改变任一变量都会更新文本
@ -2613,7 +2613,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
`:data` 关键字定义不直接用于 `:props` 但可以触发计算值更新或被监听的额外响应式变量:
```elisp
(tp-define-layer 'user-info
(define-tp user-info ()
:props '(help-echo $full-name)
:data '(first-name last-name) ;; 不直接用于 props
:compute '((full-name (lambda () (concat first-name " " last-name)))))
@ -2624,7 +2624,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
你可以使用 cons cell 指定初始值:
```elisp
(tp-define-layer 'user-info
(define-tp user-info ()
:props '(help-echo $full-name)
:data '((first-name . "张") (last-name . "三"))
:compute '((full-name (lambda () (concat first-name last-name)))))
@ -2637,7 +2637,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
`:compute` 关键字创建派生值,当它们的依赖项改变时会自动重新计算:
```elisp
(tp-define-layer 'progress-display
(define-tp progress-display ()
:props '(display $progress-text face (:foreground $progress-color))
:data '((current . 0) (total . 100))
:compute '((progress-text (lambda () (format "%d%%" (/ (* current 100) total))))
@ -2656,7 +2656,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
`:watch` 关键字让你在响应式变量改变时执行回调:
```elisp
(tp-define-layer 'monitored-layer
(define-tp monitored-layer ()
:props '(face (:foreground $status-color))
:watch '((status-color
(lambda (new-val old-val layer-name)
@ -2676,7 +2676,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
```elisp
;; 数字格式化
(tp-define-layer 'price-display
(define-tp price-display ()
:props '(tp-text $price)
:data '((price . "99.9"))
:transform (lambda (text)
@ -2684,7 +2684,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
;; 99.9 显示为 $99.00
;; 日期格式化
(tp-define-layer 'date-display
(define-tp date-display ()
:props '(tp-text $timestamp)
:data '((timestamp . "1703865600"))
:transform (lambda (text)
@ -2692,7 +2692,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
(seconds-to-time (string-to-number text)))))
;; 大写转换
(tp-define-layer 'uppercase-text
(define-tp uppercase-text ()
:props '(tp-text $content)
:data '((content . "hello"))
:transform #'upcase)
@ -2707,7 +2707,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
### 匿名响应式层
即使不使用 `tp-define-layer`,你也可以使用响应式变量。当你在匿名 plist 中使用 `$` 前缀的符号时tp.el 会自动生成唯一的层名:
即使不使用 `define-tp`,你也可以使用响应式变量。当你在匿名 plist 中使用 `$` 前缀的符号时tp.el 会自动生成唯一的层名:
```elisp
(defvar my-face-color "blue")
@ -2724,7 +2724,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
所有文本属性 API`tp-set`、`tp-match-set`、`tp-regexp-set` 等)现在可以直接接受层名:
```elisp
(tp-define-layer 'warning-style
(define-tp warning-style ()
:props '(face (:foreground "orange" :weight bold)))
;; 使用层名代替 plist
@ -2740,7 +2740,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
层组也可以使用响应式特性:
```elisp
(tp-define-layer-group 'status-indicators
(define-tps status-indicators
'("success" :props (face (:foreground $success-color))
:data ((success-color . "green")))
'("warning" :props (face (:foreground $warning-color))
@ -2754,7 +2754,7 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
同时修改多个响应式变量时,每个 `setq` 都会触发一次缓冲区更新。使用 `tp-with-batch-updates` 可以合并所有更改,在结束时一次性应用:
```elisp
(tp-define-layer 'themed-text
(define-tp themed-text ()
:props '(face (:foreground $fg-color :background $bg-color))
:data '((fg-color . "white") (bg-color . "black")))
@ -2826,13 +2826,13 @@ tp.el 提供调试模式来帮助理解响应式更新流程:
(defvar theme-accent "cyan")
;; 定义主题感知层
(tp-define-layer 'code-keyword
(define-tp code-keyword ()
:props '(face (:foreground $theme-accent :weight bold)))
(tp-define-layer 'code-comment
(define-tp code-comment ()
:props '(face (:foreground "gray" :slant italic)))
(tp-define-layer 'code-string
(define-tp code-string ()
: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.")
(unwind-protect
(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
(let ((props (cdr (assoc 'test-reactive-layer tp-layer-alist))))
(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")
(unwind-protect
(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
(let ((props (cdr (assoc 'test-reactive-update tp-layer-alist))))
(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")
(unwind-protect
(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")
;; Apply the layer to text
(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")
(unwind-protect
(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)
(tp-reactive-reset)
(should-not tp-reactive-deps))
@ -1891,7 +1891,7 @@ Returns list of (START END VALUE) intervals."
(setq tp-test-reset2-color "red")
(unwind-protect
(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)
(tp-layer-reset)
(should-not tp-reactive-deps))
@ -1905,7 +1905,7 @@ Returns list of (START END VALUE) intervals."
(setq tp-test-group-color "red")
(unwind-protect
(progn
(tp-define-layer-group 'test-reactive-group
(define-tps test-reactive-group ()
'("first" :props (face (:foreground $tp-test-group-color)))
'("second" :props (face (:foreground "blue"))))
;; 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")
(unwind-protect
(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
(should (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."
(tp-test-with-temp-buffer
;; 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")))
(let ((str (tp-set " " 'my-entire-string-layer)))
;; 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)
(unwind-protect
(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
(lambda (new old layer)
(push (list new old layer) tp-test-watch-log)))))
@ -2304,7 +2304,7 @@ preserving the native text property behavior."
(tp-test-with-temp-buffer
(unwind-protect
(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))
;; Check that variables were auto-defined
(should (boundp 'tp-test-data-color))
@ -2325,7 +2325,7 @@ preserving the native text property behavior."
;; Set up the source variables
(setq tp-test-first-name "John")
(setq tp-test-last-name "Doe")
(tp-define-layer 'test-compute-layer
(define-tp test-compute-layer ()
:props '(help-echo $tp-test-full-name)
:data '(tp-test-first-name tp-test-last-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-last "Smith")
;; 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)
:data '(tp-test-dc-first tp-test-dc-last)
: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."
(tp-test-with-temp-buffer
(should-error
(tp-define-layer 'test-invalid
(define-tp test-invalid ()
:watch '((some-var (lambda (new old layer) nil)))))))
(ert-deftest tp-test-define-layer-compute-requires-props ()
"Test that :compute requires :props to be explicitly specified."
(tp-test-with-temp-buffer
(should-error
(tp-define-layer 'test-invalid
(define-tp test-invalid ()
:compute '((some-var (lambda () "computed")))))))
(ert-deftest tp-test-define-layer-data-requires-props ()
"Test that :data requires :props to be explicitly specified."
(tp-test-with-temp-buffer
(should-error
(tp-define-layer 'test-invalid
(define-tp test-invalid ()
:data '(some-var)))))
(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
(unwind-protect
(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)
:data '(tp-test-undef-first tp-test-undef-last)
: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)
(unwind-protect
(progn
(tp-define-layer-group 'test-watch-group
(define-tps test-watch-group ()
'("reactive" :props (face (:foreground $tp-test-group-watch-var))
:watch ((tp-test-group-watch-var
(lambda (new old layer)
@ -2458,7 +2458,7 @@ preserving the native text property behavior."
(progn
(setq tp-test-group-first "Group")
(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)
:data (tp-test-group-first tp-test-group-last)
:compute '((tp-test-group-full
@ -2483,7 +2483,7 @@ preserving the native text property behavior."
(tp-test-with-temp-buffer
(unwind-protect
(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)
:data '(tp-test-reset-first tp-test-reset-last)
: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
(should-not (boundp 'tp-test-auto-var1))
(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))
;; Variables should now exist
(should (boundp 'tp-test-auto-var1))
@ -2529,7 +2529,7 @@ preserving the native text property behavior."
(unwind-protect
(progn
;; 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
(insert "Hello World")
(tp-set 1 6 'test-local-layer)
@ -2548,7 +2548,7 @@ preserving the native text property behavior."
(unwind-protect
(progn
;; 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)
:data '(tp-test-dc-first tp-test-dc-last)
:compute '((tp-test-dc-full
@ -2578,7 +2578,7 @@ preserving the native text property behavior."
(unwind-protect
(progn
;; 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)
:data '((tp-test-init-color . "blue")
(tp-test-init-name . "Initial Name")
@ -2605,7 +2605,7 @@ preserving the native text property behavior."
(unwind-protect
(progn
;; 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
(setq buf1 (generate-new-buffer " *test-buf1*"))
(with-current-buffer buf1
@ -2637,7 +2637,7 @@ preserving the native text property behavior."
(unwind-protect
(progn
;; 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
(setq buf1 (generate-new-buffer " *test-buf1*"))
(with-current-buffer buf1
@ -2670,7 +2670,7 @@ preserving the native text property behavior."
(unwind-protect
(progn
;; 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")))
;; Check initial value
(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))))
(should (equal (plist-get (plist-get props 'face) :background) "gray")))
;; 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")))
;; Check variable is updated
(should (equal tp-test-redef-color "blue"))
@ -2694,12 +2694,12 @@ preserving the native text property behavior."
(unwind-protect
(progn
;; 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")))
(let ((props (cdr (assoc 'test-redef-props tp-layer-alist))))
(should (equal (plist-get (plist-get props 'face) :foreground) "red")))
;; 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")
:data '((tp-test-redef-bg . "yellow")))
;; Check new props are applied
@ -2718,14 +2718,14 @@ preserving the native text property behavior."
(unwind-protect
(progn
;; 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")))
;; Check old var is in dependencies
(should (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)))
;; 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")))
;; Check old var is no longer in dependencies for this layer
(when-let ((deps (cdr (assoc 'tp-test-old-var tp-reactive-deps))))
@ -2749,13 +2749,13 @@ preserving the native text property behavior."
(unwind-protect
(progn
;; 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"))
:watch '((tp-test-watch-var
(lambda (new old layer)
(push (list 'old new) tp-test-watch-log-old)))))
;; 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"))
:watch '((tp-test-watch-var
(lambda (new old layer)
@ -2779,14 +2779,14 @@ preserving the native text property behavior."
(progn
;; First definition with old compute
(setq tp-test-compute-src "hello")
(tp-define-layer 'test-redef-compute
(define-tp test-redef-compute ()
:props '(help-echo $tp-test-compute-out)
:data '(tp-test-compute-src)
:compute '((tp-test-compute-out
(lambda () (upcase tp-test-compute-src)))))
(should (equal tp-test-compute-out "HELLO"))
;; Re-define with different compute
(tp-define-layer 'test-redef-compute
(define-tp test-redef-compute ()
:props '(help-echo $tp-test-compute-out)
:data '(tp-test-compute-src)
:compute '((tp-test-compute-out
@ -2806,17 +2806,17 @@ preserving the native text property behavior."
(unwind-protect
(progn
;; 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")))
;; Check reactive dependency is registered
(should (assoc 'tp-test-r2s-color tp-reactive-deps))
;; 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
(when-let ((deps (cdr (assoc 'tp-test-r2s-color tp-reactive-deps))))
(should-not (assoc 'test-reactive-to-static deps)))
;; 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))))
;; Cleanup
(ignore-errors (makunbound 'tp-test-r2s-color)))))
@ -2827,13 +2827,13 @@ preserving the native text property behavior."
(unwind-protect
(progn
;; First definition
(tp-define-layer-group 'test-redef-group
(define-tps test-redef-group ()
'("layer1" :props (face (:background $tp-test-group-color))
:data ((tp-test-group-color . "gray"))))
;; Check initial value
(should (equal tp-test-group-color "gray"))
;; 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))
:data ((tp-test-group-color . "blue"))))
;; Check variable is updated
@ -2850,7 +2850,7 @@ preserving the native text property behavior."
(unwind-protect
(progn
;; 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")))
;; Apply to text
(insert "Hello World")
@ -2858,10 +2858,10 @@ preserving the native text property behavior."
;; Check initial color
(should (equal (plist-get (get-text-property 1 'face) :background) "gray"))
;; 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")))
;; 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
(should (equal (plist-get (get-text-property 1 'face) :background) "blue")))
;; Cleanup
@ -2948,7 +2948,7 @@ preserving the native text property behavior."
(setq tp-test-reactive-text "Initial")
(unwind-protect
(progn
(tp-define-layer 'test-reactive-text-layer
(define-tp test-reactive-text-layer ()
:props '(face bold tp-text $tp-test-reactive-text))
;; Apply layer to text
(insert "Hello World")
@ -2976,7 +2976,7 @@ the source text should be used and the reactive variable should be updated."
(unwind-protect
(progn
;; 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))
;; Apply layer to string - variable is nil, so source text should be used
(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
;; tp-text value, and we want to test the behavior when the variable
;; already has a non-nil value at layer application time.
(tp-define-layer 'test-init-text-layer
(define-tp test-init-text-layer ()
:props '(face bold tp-text $tp-test-text-var))
(let ((result (tp-set "2" 'test-init-text-layer)))
;; Result should be the variable value "18", not source "2"
@ -3019,7 +3019,7 @@ the inserted text should be that string, not the source text."
(progn
(setq tp-test-name-part1 "Hello")
(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)
:data '(tp-test-name-part1 tp-test-name-part2)
:compute '((tp-test-full-text
@ -3346,7 +3346,7 @@ When using tp-set (direct property setting), tp-name is NOT added."
(unwind-protect
(progn
;; Define a reactive layer
(tp-define-layer 'test-batch-layer
(define-tp test-batch-layer ()
:props '(face (:foreground $tp-test-batch-color))
:data '((tp-test-batch-color . "red")))
(insert "Hello World")
@ -3370,7 +3370,7 @@ When using tp-set (direct property setting), tp-name is NOT added."
(unwind-protect
(progn
;; 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))
:data '((tp-test-fg . "white") (tp-test-bg . "black")))
(insert "Hello World")
@ -3427,7 +3427,7 @@ When using tp-set (direct property setting), tp-name is NOT added."
(unwind-protect
(progn
;; Define a layer with transform
(tp-define-layer 'test-transform-layer
(define-tp test-transform-layer ()
:props '(face bold tp-text $tp-test-value)
:data '((tp-test-value . "hello"))
:transform #'upcase)
@ -3444,7 +3444,7 @@ When using tp-set (direct property setting), tp-name is NOT added."
(unwind-protect
(progn
;; 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)
:data '((tp-test-amount . "100"))
:transform (lambda (text)
@ -3466,14 +3466,14 @@ When using tp-set (direct property setting), tp-name is NOT added."
(unwind-protect
(progn
;; Define with transform
(tp-define-layer 'test-redef-transform
(define-tp test-redef-transform ()
:props '(face bold tp-text $tp-test-text)
:data '((tp-test-text . "hello"))
:transform #'upcase)
;; Check transform is registered
(should (assoc 'test-redef-transform tp-layer-transforms))
;; Redefine without transform
(tp-define-layer 'test-redef-transform
(define-tp test-redef-transform ()
:props '(face bold tp-text $tp-test-text)
:data '((tp-test-text . "hello")))
;; Transform should be removed

93
tp.el
View File

@ -2300,7 +2300,7 @@ Example:
;;;============================================================================
(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.
- Keyword arguments: :props PLIST [:data DATA] [:watch WATCH] [:compute COMPUTE] [:transform FN]"
(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)))
(:compute (setq compute (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
(when (and (or watch compute data) (null props))
(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)
(listp (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)))
(defun tp-define-layer (name &rest args)
(defun tp--define-layer-internal (name &rest args)
"Define a single text property layer named NAME.
This function supports two formats:
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)))
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 \\='(face (:foreground $my-color) help-echo $full-name)
;; 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)
"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):
(define-tp tp-bold ()
\\='(face bold))
Format 2 - Parameterized (single argument):
Format 2 - Parameterized simple (single argument, simple body):
(define-tp tp-space (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 ()
:props \\='(face (:foreground $my-color))
: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!\"))))
: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:
(tp-set \"emacs\" \\='tp-bold t)
(tp-set 0 5 \\='(tp-bold t) \"emacs\")
@ -2474,7 +2480,7 @@ ARGLIST must be either:
BODY is either:
- A single property list expression (simple format)
- 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',
`display', `invisible', etc. See `tp--builtin-text-properties' for the
@ -2489,10 +2495,15 @@ complete list of reserved names."
(let ((first-elem (car body)))
(if (and (keywordp first-elem)
(memq first-elem '(:props :data :compute :watch :transform)))
;; Reactive format - use tp-define-layer
;; Reactive format
(if arglist
(error "define-tp: reactive features (:props, :data, etc.) are only supported for non-parameterized layers")
`(tp-define-layer ',name ,@body))
;; Parameterized reactive: store as function that calls tp--define-layer-internal
(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)
(let ((simple-body (car body)))
(cond
@ -2506,19 +2517,47 @@ complete list of reserved names."
(t
(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)
"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 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)."
;; Store in tp-layer-alist with unified format: (LAYER-NAME ARGLIST BODY-FORM)
;; For non-parameterized, quote the body so eval returns the plist
(let ((stored-body (if arglist body `',body)))
(let ((entry (list arglist stored-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))
Stores the layer in `tp-layer-alist' with format: (LAYER-NAME ARGLIST BODY-FORM).
For non-parameterized layers, if BODY contains reactive symbols ($-prefixed),
delegates to `tp--define-layer-internal' for proper reactive handling."
(if arglist
;; Parameterized - store for later evaluation
(let ((entry (list arglist 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))
;; 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)
"Determine the format type of ELEMENT.
@ -2639,7 +2678,7 @@ COMPUTE is the list of computed variable definitions."
(tp--update-layer-regions 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.
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).
Example:
(tp-define-layer-group \\='my-group
(tp--define-layer-internal-group \\='my-group
\\='existing-layer
\\='(face bold)
\\='(\"named\" . (face italic))
@ -2707,8 +2746,8 @@ ELEMENTS is the list of layer definitions."
(setf (cdr (assoc name tp-layer-groups)) entry)
(push (cons name entry) tp-layer-groups))
(assoc name tp-layer-groups))
;; Non-parameterized - define immediately using tp-define-layer-group
(apply #'tp-define-layer-group name elements)))
;; Non-parameterized - define immediately using tp--define-layer-internal-group
(apply #'tp--define-layer-internal-group name elements)))
(defun tp--define-layer-group-unified (name arglist 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.
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 plist for inline layer definition
- A list (NAME &rest PLIST) for named inline layer"