Add reactive features support (:props, :data, :compute, :watch, :transform) to define-tp macro
Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
This commit is contained in:
parent
5667881765
commit
4607f50a13
28
README.md
28
README.md
@ -1397,9 +1397,9 @@ tp.el unifies the concepts of "custom text properties" and "text property layers
|
||||
|
||||
##### `define-tp` - Define Single Custom Text Property (Layer)
|
||||
|
||||
Define a custom text property. The name does not need to be quoted. Supports two formats:
|
||||
Define a custom text property. The name does not need to be quoted. Supports three formats:
|
||||
|
||||
**Format 1 - Non-parameterized (empty argument list):**
|
||||
**Format 1 - Non-parameterized (empty argument list, simple properties):**
|
||||
|
||||
```elisp
|
||||
(define-tp tp-bold ()
|
||||
@ -1421,6 +1421,30 @@ Define a custom text property. The name does not need to be quoted. Supports two
|
||||
(tp-set 0 5 '(tp-space 2) "emacs")
|
||||
```
|
||||
|
||||
**Format 3 - With reactive features (:props, :data, :compute, :watch, :transform):**
|
||||
|
||||
```elisp
|
||||
(define-tp my-reactive-layer ()
|
||||
:props '(face (:foreground $my-color))
|
||||
:data '((my-color . "red"))
|
||||
:compute '((full-name (lambda () (concat first-name " " last-name))))
|
||||
:watch '((my-color (lambda (new old layer) (message "Color changed!"))))
|
||||
:transform (lambda (text) (upcase text)))
|
||||
|
||||
;; Usage:
|
||||
(tp-push-layer 1 10 'my-reactive-layer)
|
||||
;; Changing the variable automatically updates the text
|
||||
(setq my-color "blue")
|
||||
```
|
||||
|
||||
**Reactive Keywords:**
|
||||
|
||||
- **:props** - Property list where `$`-prefixed symbols are reactive variables
|
||||
- **:data** - Additional reactive variables (can include initial values)
|
||||
- **:compute** - Computed properties that derive values from other variables
|
||||
- **:watch** - Watchers that execute callbacks when variables change
|
||||
- **:transform** - Transform function to process `tp-text` values before display
|
||||
|
||||
##### `define-tps` - Define Custom Text Property Group (Layer Group)
|
||||
|
||||
Define multiple related custom text properties. The name does not need to be quoted. Properties in the group can be used individually or with the group name to set multiple layers.
|
||||
|
||||
28
README_CN.md
28
README_CN.md
@ -1389,9 +1389,9 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
|
||||
|
||||
##### `define-tp` - 定义单个自定义文本属性(层)
|
||||
|
||||
定义自定义文本属性,名称无需单引号引用。支持两种格式:
|
||||
定义自定义文本属性,名称无需单引号引用。支持三种格式:
|
||||
|
||||
**格式一 - 无参数(空参数列表):**
|
||||
**格式一 - 无参数(空参数列表,简单属性):**
|
||||
|
||||
```elisp
|
||||
(define-tp tp-bold ()
|
||||
@ -1413,6 +1413,30 @@ tp.el 统一了"自定义文本属性"和"文本属性层"两个概念:
|
||||
(tp-set 0 5 '(tp-space 2) "emacs")
|
||||
```
|
||||
|
||||
**格式三 - 响应式特性(支持 :props、:data、:compute、:watch、:transform):**
|
||||
|
||||
```elisp
|
||||
(define-tp my-reactive-layer ()
|
||||
:props '(face (:foreground $my-color))
|
||||
:data '((my-color . "red"))
|
||||
:compute '((full-name (lambda () (concat first-name " " last-name))))
|
||||
:watch '((my-color (lambda (new old layer) (message "Color changed!"))))
|
||||
:transform (lambda (text) (upcase text)))
|
||||
|
||||
;; 用法:
|
||||
(tp-push-layer 1 10 'my-reactive-layer)
|
||||
;; 改变变量会自动更新文本
|
||||
(setq my-color "blue")
|
||||
```
|
||||
|
||||
**响应式关键字说明:**
|
||||
|
||||
- **:props** - 属性列表,`$` 前缀的符号是响应式变量
|
||||
- **:data** - 额外的响应式变量列表(可以包含初始值)
|
||||
- **:compute** - 计算属性列表,从其他变量派生值
|
||||
- **:watch** - 监听器列表,变量改变时执行回调
|
||||
- **:transform** - 转换函数,在显示 `tp-text` 值之前对其进行处理
|
||||
|
||||
##### `define-tps` - 定义自定义文本属性组(层组)
|
||||
|
||||
定义多个相关的自定义文本属性,名称无需单引号引用。属性组中定义的文本属性可以单独使用,也可以使用组名称来设置多层。
|
||||
|
||||
63
tp.el
63
tp.el
@ -2441,35 +2441,40 @@ The layer is stored in `tp-layer-alist'."
|
||||
(tp--update-layer-regions name)
|
||||
(assoc name tp-layer-alist)))))
|
||||
|
||||
(defmacro define-tp (name arglist body)
|
||||
(defmacro define-tp (name arglist &rest body)
|
||||
"Define a text property layer named NAME.
|
||||
|
||||
This macro supports two formats:
|
||||
This macro supports three formats:
|
||||
|
||||
Format 1 - Non-parameterized (empty arglist):
|
||||
Format 1 - Non-parameterized simple (empty arglist, simple body):
|
||||
(define-tp tp-bold ()
|
||||
\\='(face bold))
|
||||
|
||||
Format 2 - Parameterized (single argument):
|
||||
(define-tp tp-space (pixel)
|
||||
\\=`(display (space :width (,pixel))))
|
||||
|
||||
Format 3 - With reactive features (supports :props, :data, :compute, :watch, :transform):
|
||||
(define-tp my-layer ()
|
||||
:props \\='(face (:foreground $my-color))
|
||||
:data \\='((my-color . \"red\"))
|
||||
:compute \\='((full-name (lambda () (concat first-name \" \" last-name))))
|
||||
:watch \\='((my-color (lambda (new old layer) (message \"Color changed!\"))))
|
||||
:transform (lambda (text) (upcase text)))
|
||||
|
||||
Usage:
|
||||
(tp-set \"emacs\" \\='tp-bold t)
|
||||
(tp-set 0 5 \\='(tp-bold t) \"emacs\")
|
||||
;; => #(\"emacs\" 0 5 (tp-name tp-bold face bold))
|
||||
|
||||
Format 2 - Parameterized (single argument):
|
||||
(define-tp tp-space (pixel)
|
||||
\\=`(display (space :width (,pixel))))
|
||||
|
||||
Usage:
|
||||
(tp-set \"emacs\" \\='tp-space 2)
|
||||
(tp-set 0 5 \\='(tp-space 2) \"emacs\")
|
||||
;; => #(\"emacs\" 0 5 (tp-name tp-space display (space :width (2))))
|
||||
|
||||
ARGLIST must be either:
|
||||
- An empty list () for non-parameterized layers
|
||||
- A list containing exactly one symbol for parameterized layers
|
||||
|
||||
BODY is the property list expression. For parameterized layers,
|
||||
it will be evaluated with the argument bound.
|
||||
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)
|
||||
|
||||
Note: NAME cannot be a built-in Emacs text property name like `face',
|
||||
`display', `invisible', etc. See `tp--builtin-text-properties' for the
|
||||
@ -2480,16 +2485,26 @@ complete list of reserved names."
|
||||
;; Check for built-in text property name conflict
|
||||
(when (tp--builtin-text-property-p name)
|
||||
(error "define-tp: '%s' is a built-in Emacs text property name and cannot be used as a layer name" name))
|
||||
(cond
|
||||
;; Non-parameterized: empty arglist - store as (LAYER-NAME nil BODY-FORM)
|
||||
((null arglist)
|
||||
`(tp--define-layer-unified ',name nil ,body))
|
||||
;; Parameterized: single argument - store as (LAYER-NAME ARGLIST BODY-FORM)
|
||||
((and (= (length arglist) 1)
|
||||
(symbolp (car arglist)))
|
||||
`(tp--define-layer-unified ',name ',arglist ',body))
|
||||
(t
|
||||
(error "define-tp ARGLIST must be empty or contain exactly one symbol"))))
|
||||
;; Check if body starts with keyword (reactive format)
|
||||
(let ((first-elem (car body)))
|
||||
(if (and (keywordp first-elem)
|
||||
(memq first-elem '(:props :data :compute :watch :transform)))
|
||||
;; Reactive format - use tp-define-layer
|
||||
(if arglist
|
||||
(error "define-tp: reactive features (:props, :data, etc.) are only supported for non-parameterized layers")
|
||||
`(tp-define-layer ',name ,@body))
|
||||
;; Simple format (original behavior)
|
||||
(let ((simple-body (car body)))
|
||||
(cond
|
||||
;; Non-parameterized: empty arglist - store as (LAYER-NAME nil BODY-FORM)
|
||||
((null arglist)
|
||||
`(tp--define-layer-unified ',name nil ,simple-body))
|
||||
;; Parameterized: single argument - store as (LAYER-NAME ARGLIST BODY-FORM)
|
||||
((and (= (length arglist) 1)
|
||||
(symbolp (car arglist)))
|
||||
`(tp--define-layer-unified ',name ',arglist ',simple-body))
|
||||
(t
|
||||
(error "define-tp ARGLIST must be empty or contain exactly one symbol")))))))
|
||||
|
||||
(defun tp--define-layer-unified (name arglist body)
|
||||
"Define a layer NAME with ARGLIST and BODY using unified structure.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user