update
This commit is contained in:
parent
29b2ee54d3
commit
af0980efef
Binary file not shown.
@ -0,0 +1,212 @@
|
||||
# tp.el 响应式系统优化文档
|
||||
|
||||
本文档基于 [twidget](https://github.com/Kinneyzhang/twidget.git) 项目的实践经验,对 tp.el 的响应式系统进行了优化和增强。
|
||||
|
||||
## 优化建议评估
|
||||
|
||||
以下是针对 tp.el 响应式系统的六项优化建议的评估和实现情况:
|
||||
|
||||
### 1. 细粒度响应式更新(Granular Reactive Updates)
|
||||
|
||||
**建议**:支持区域内的部分更新,只更新响应式部分,保留周围文本属性。
|
||||
|
||||
**评估**:已经实现。tp.el 通过 `tp-intervals-map` 和基于区间的更新机制,已经支持细粒度的属性更新。更新只影响具有特定 `tp-name` 的区域。
|
||||
|
||||
### 2. 响应式符号清理(Reactive Symbol Cleanup)✅ 已实现
|
||||
|
||||
**建议**:当 widget 销毁时,添加注销响应式符号的机制。
|
||||
|
||||
**评估**:已经实现。`tp--unregister-reactive-deps` 函数负责清理:
|
||||
- 当层被重新定义时自动调用
|
||||
- 当层被取消定义(`tp-undefine-layer`)时自动调用
|
||||
- 清理变量监听器、计算属性和数据变量
|
||||
|
||||
**关键函数**:
|
||||
- `tp--unregister-reactive-deps`
|
||||
- `tp--unregister-layer-watchers`
|
||||
- `tp--unregister-layer-computed`
|
||||
- `tp--unregister-layer-data`
|
||||
|
||||
### 3. 作用域响应式(Scoped Reactivity)✅ 已实现
|
||||
|
||||
**建议**:为响应式变量添加实例/上下文作用域。
|
||||
|
||||
**评估**:已经实现。`where` 参数在以下函数中支持缓冲区局部更新:
|
||||
- `tp--update-layer-regions`
|
||||
- `tp--update-reactive-text`
|
||||
|
||||
当使用 `setq-local` 时,更新只影响特定缓冲区。
|
||||
|
||||
### 4. 批量更新(Batched Updates)🆕 新增
|
||||
|
||||
**建议**:当多个响应式值同时变化时,批量处理更新以避免冗余的缓冲区修改。
|
||||
|
||||
**实现**:新增 `tp-with-batch-updates` 宏:
|
||||
|
||||
```elisp
|
||||
;; 使用批量更新
|
||||
(tp-with-batch-updates
|
||||
(setq my-color "red")
|
||||
(setq my-size 14)
|
||||
(setq my-text "Hello"))
|
||||
;; 所有更新在批量结束后一次性应用到缓冲区
|
||||
```
|
||||
|
||||
**关键函数和变量**:
|
||||
- `tp-with-batch-updates` - 批量更新宏
|
||||
- `tp--batch-update-active` - 标记是否在批量更新中
|
||||
- `tp--batch-update-pending` - 待处理的更新列表
|
||||
- `tp--flush-batch-updates` - 应用所有待处理更新
|
||||
|
||||
### 5. 值转换(Value Transformation)🆕 新增
|
||||
|
||||
**建议**:允许注册转换函数,在 tp-text 更新时运行。
|
||||
|
||||
**实现**:新增 `:transform` 选项:
|
||||
|
||||
```elisp
|
||||
;; 定义带转换的层
|
||||
(define-tp currency-display ()
|
||||
:props '(face bold tp-text $amount)
|
||||
:data '((amount . "100"))
|
||||
:transform (lambda (text)
|
||||
(format "$%s.00" text)))
|
||||
|
||||
;; 使用后,100 会显示为 $100.00
|
||||
```
|
||||
|
||||
**关键函数和变量**:
|
||||
- `tp-layer-transforms` - 存储层转换函数
|
||||
- 转换在 `tp--handle-tp-text-property` 和 `tp--update-reactive-text` 中应用
|
||||
|
||||
### 6. 调试模式(Debug Mode)🆕 新增
|
||||
|
||||
**建议**:添加调试模式以追踪响应式更新。
|
||||
|
||||
**实现**:新增调试功能:
|
||||
|
||||
```elisp
|
||||
;; 启用调试模式
|
||||
(setq tp-debug-mode t)
|
||||
|
||||
;; 同时在 minibuffer 显示调试信息
|
||||
(setq tp-debug-echo t)
|
||||
|
||||
;; 查看调试日志
|
||||
(tp-debug-show)
|
||||
|
||||
;; 清除调试日志
|
||||
(tp-debug-clear)
|
||||
```
|
||||
|
||||
**关键函数和变量**:
|
||||
- `tp-debug-mode` - 启用/禁用调试模式
|
||||
- `tp-debug-echo` - 是否在 minibuffer 显示调试信息
|
||||
- `tp-debug-log` - 记录调试信息
|
||||
- `tp-debug-show` - 显示调试缓冲区
|
||||
- `tp-debug-clear` - 清除调试日志
|
||||
|
||||
调试日志包含:
|
||||
- 变量变化通知(旧值 → 新值)
|
||||
- 层更新追踪
|
||||
- 批量更新开始/结束
|
||||
- 转换应用信息
|
||||
|
||||
## 新增功能详解
|
||||
|
||||
### 批量更新 (tp-with-batch-updates)
|
||||
|
||||
当需要同时修改多个响应式变量时,使用批量更新可以避免多次缓冲区更新:
|
||||
|
||||
```elisp
|
||||
(define-tp themed-text ()
|
||||
:props '(face (:foreground $fg-color :background $bg-color))
|
||||
:data '((fg-color . "white") (bg-color . "black")))
|
||||
|
||||
(with-temp-buffer
|
||||
(insert "Hello World")
|
||||
(tp-set 1 12 'themed-text)
|
||||
|
||||
;; 不使用批量更新:每个 setq 都会触发一次缓冲区更新
|
||||
(setq fg-color "yellow") ; 第一次更新
|
||||
(setq bg-color "navy") ; 第二次更新
|
||||
|
||||
;; 使用批量更新:所有变化在结束时一次性应用
|
||||
(tp-with-batch-updates
|
||||
(setq fg-color "red")
|
||||
(setq bg-color "blue"))) ; 只更新一次
|
||||
```
|
||||
|
||||
### 值转换 (:transform)
|
||||
|
||||
转换函数允许在显示前处理 tp-text 的值:
|
||||
|
||||
```elisp
|
||||
;; 数字格式化
|
||||
(define-tp price-display ()
|
||||
:props '(tp-text $price)
|
||||
:data '((price . "99.9"))
|
||||
:transform (lambda (text)
|
||||
(format "$%.2f" (string-to-number text))))
|
||||
|
||||
;; 日期格式化
|
||||
(define-tp date-display ()
|
||||
:props '(tp-text $timestamp)
|
||||
:data '((timestamp . "1703865600"))
|
||||
:transform (lambda (text)
|
||||
(format-time-string "%Y-%m-%d"
|
||||
(seconds-to-time (string-to-number text)))))
|
||||
|
||||
;; 大写转换
|
||||
(define-tp uppercase-text ()
|
||||
:props '(tp-text $content)
|
||||
:data '((content . "hello"))
|
||||
:transform #'upcase)
|
||||
```
|
||||
|
||||
### 调试模式
|
||||
|
||||
调试模式帮助开发者理解响应式更新流程:
|
||||
|
||||
```elisp
|
||||
;; 启用完整调试
|
||||
(setq tp-debug-mode t)
|
||||
(setq tp-debug-echo t)
|
||||
|
||||
;; 定义和使用响应式层
|
||||
(define-tp test-layer ()
|
||||
:props '(face (:foreground $my-color))
|
||||
:data '((my-color . "red")))
|
||||
|
||||
(with-temp-buffer
|
||||
(insert "Test")
|
||||
(tp-set 1 5 'test-layer)
|
||||
(setq my-color "blue"))
|
||||
|
||||
;; 调试输出示例:
|
||||
;; [12:34:56.789] Variable my-color changed: "red" -> "blue" (where: global)
|
||||
;; [12:34:56.790] Updating layer test-layer (tp-text affected: no)
|
||||
```
|
||||
|
||||
## 架构说明
|
||||
|
||||
这些优化遵循 tp.el 的分层架构原则:
|
||||
|
||||
1. **调试模式** - 作为基础工具层功能
|
||||
2. **批量更新** - 在响应式系统层实现
|
||||
3. **值转换** - 在层定义和响应式文本处理中实现
|
||||
|
||||
所有新功能都与现有的响应式系统无缝集成,不破坏现有 API。
|
||||
|
||||
## 相关函数一览
|
||||
|
||||
| 函数/变量 | 描述 |
|
||||
|----------|------|
|
||||
| `tp-debug-mode` | 启用调试模式 |
|
||||
| `tp-debug-echo` | 启用 minibuffer 调试输出 |
|
||||
| `tp-debug-log` | 记录调试信息 |
|
||||
| `tp-debug-show` | 显示调试缓冲区 |
|
||||
| `tp-debug-clear` | 清除调试日志 |
|
||||
| `tp-with-batch-updates` | 批量更新宏 |
|
||||
| `tp-layer-transforms` | 层转换函数存储 |
|
||||
| `:transform` | 层定义中的转换选项 |
|
||||
@ -0,0 +1,507 @@
|
||||
# tp.el Complete Guide to Reactive Text Properties
|
||||
|
||||
> Bringing modern frontend framework reactive programming paradigms to the Emacs text properties world
|
||||
|
||||
## Introduction
|
||||
|
||||
In traditional Emacs development, managing text properties has always been a tedious task. Whenever you want to change a property value, you need to manually find all related text regions and update them one by one. This approach is not only error-prone but also difficult to maintain.
|
||||
|
||||
**Reactive Text Properties** is one of the most innovative features in the tp.el library. It borrows the reactive programming concepts from modern frontend frameworks like Vue.js and React, allowing Emacs text properties to **automatically respond to variable changes**.
|
||||
|
||||
Imagine: you define the relationship between a variable and a property once, and from then on, whenever you change the variable's value, all text regions using that variable will **automatically update**. This is the magic of reactive text properties!
|
||||
|
||||
## From Traditional to Reactive
|
||||
|
||||
### Pain Points of the Traditional Approach
|
||||
|
||||
Let's first look at how the traditional approach handles dynamic text properties:
|
||||
|
||||
```lisp
|
||||
;; Traditional approach: define a color variable
|
||||
(defvar my-color "red")
|
||||
|
||||
(tp-pop-to-buffer "*tp-test*"
|
||||
(insert "Hello World")
|
||||
(tp-set 1 12 `(face (:foreground ,my-color)))
|
||||
|
||||
;; Here comes the problem: when you want to change the color...
|
||||
(setq my-color "blue")
|
||||
;; The text doesn't update automatically! You must manually reapply:
|
||||
(tp-set 1 12 `(face (:foreground ,my-color))))
|
||||
```
|
||||
|
||||
The problems with this approach are obvious:
|
||||
1. **Manual tracking**: You need to remember which text regions use which variables
|
||||
2. **Easy to miss**: In complex applications, it's easy to forget to update some regions
|
||||
3. **Code redundancy**: Update logic is scattered throughout the code
|
||||
|
||||
### The Elegance of Reactive Approach
|
||||
|
||||
Now let's see how the reactive approach solves these problems:
|
||||
|
||||
```lisp
|
||||
;; Reactive approach: define a color variable
|
||||
(defvar my-color "red")
|
||||
|
||||
;; Define a reactive layer using $my-color to reference the variable
|
||||
(define-tp my-highlight ()
|
||||
'(face (:foreground $my-color)))
|
||||
|
||||
;; Apply to text
|
||||
(tp-pop-to-buffer "*tp-test*"
|
||||
(insert "Hello World")
|
||||
(tp-set 1 12 'my-highlight)
|
||||
|
||||
;; Now, just change the variable!
|
||||
(setq my-color "blue")
|
||||
;; Magic happens: the text automatically turns blue!
|
||||
)
|
||||
```
|
||||
|
||||
Isn't that amazing? Let's dive deep into how this powerful feature works.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Reactive Variables
|
||||
|
||||
In tp.el, any symbol starting with `$` is treated as a **reactive variable**. For example:
|
||||
- `$my-color` → references variable `my-color`
|
||||
- `$font-size` → references variable `font-size`
|
||||
- `$theme-background` → references variable `theme-background`
|
||||
|
||||
When you use these `$`-prefixed symbols in property definitions, tp.el will:
|
||||
1. Automatically resolve the variable's current value
|
||||
2. Register a watcher to monitor variable changes
|
||||
3. When the variable changes, automatically update all related text regions
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Your First Reactive Layer
|
||||
|
||||
Let's start with a simple example:
|
||||
|
||||
```lisp
|
||||
;; Define a global variable
|
||||
(defvar highlight-bg "yellow")
|
||||
|
||||
;; Define a reactive layer
|
||||
(define-tp simple-highlight ()
|
||||
'(face (:background $highlight-bg)))
|
||||
|
||||
;; Create a test buffer and apply the layer
|
||||
(tp-pop-to-buffer "*tp-test*"
|
||||
(insert "This is text that needs highlighting")
|
||||
(tp-set 1 (point-max) 'simple-highlight)
|
||||
;; => "Initial background color: yellow"
|
||||
;; Change the variable
|
||||
(setq highlight-bg "cyan")
|
||||
;; => "Updated background color: cyan"
|
||||
)
|
||||
```
|
||||
|
||||
### Multiple Reactive Variables
|
||||
|
||||
A layer can reference multiple reactive variables:
|
||||
|
||||
```lisp
|
||||
;; Define multiple variables
|
||||
(defvar fg-color "white")
|
||||
(defvar bg-color "darkGreen")
|
||||
(defvar underline-color "red")
|
||||
|
||||
;; Define a layer using multiple variables
|
||||
(define-tp multi-var-layer ()
|
||||
'(face ( :foreground $fg-color
|
||||
:background $bg-color
|
||||
:underline (:color $underline-color))))
|
||||
|
||||
;; Test
|
||||
(tp-pop-to-buffer "*tp-test*"
|
||||
(insert "Multi-variable reactive example")
|
||||
(tp-set 1 (point-max) 'multi-var-layer)
|
||||
|
||||
;; Changing any variable triggers an update
|
||||
(setq fg-color "yellow") ; Foreground turns yellow
|
||||
(setq bg-color "navy") ; Background turns navy
|
||||
(setq underline-color "lime") ; Underline turns lime green
|
||||
)
|
||||
```
|
||||
|
||||
## Advanced Features: :data, :compute, and :watch
|
||||
|
||||
tp.el's reactive system borrows from Vue's API, providing three powerful keywords:
|
||||
|
||||
### :data - Define Additional Reactive State
|
||||
|
||||
Sometimes you need reactive variables that aren't directly used in `:props`. This is where `:data` comes in.
|
||||
|
||||
Main uses of `:data`:
|
||||
1. Define auxiliary variables that don't appear directly in properties
|
||||
2. Provide initial values for variables
|
||||
3. Work together with `:compute`
|
||||
|
||||
### :compute - Computed Properties
|
||||
|
||||
`:compute` lets you define **derived values**—their values are computed from other variables:
|
||||
|
||||
```lisp
|
||||
;; Complete computed properties example
|
||||
(define-tp computed-greeting ()
|
||||
:props '(display $full-greeting face (:foreground $status-color))
|
||||
:data '((user-name . "John")
|
||||
(greeting-prefix . "Hello"))
|
||||
:compute '((full-greeting (lambda ()
|
||||
(format "%s, %s! Welcome back."
|
||||
greeting-prefix user-name)))
|
||||
(status-color (lambda ()
|
||||
(if (string= user-name "Admin")
|
||||
"red"
|
||||
"green")))))
|
||||
|
||||
;; Test
|
||||
(tp-pop-to-buffer "*tp-test*"
|
||||
(insert "Test text")
|
||||
(tp-set 1 (point-max) 'computed-greeting)
|
||||
;; Initial state
|
||||
(message "full-greeting = %s" full-greeting)
|
||||
;; => "Hello, John! Welcome back."
|
||||
(message "status-color = %s" status-color)
|
||||
;; => "green"
|
||||
;; Change user-name
|
||||
(setq user-name "Admin")
|
||||
;; Computed properties update automatically!
|
||||
(message "full-greeting = %s" full-greeting)
|
||||
;; => "Hello, Admin! Welcome back."
|
||||
(message "status-color = %s" status-color)
|
||||
;; => "red"
|
||||
;; Change greeting-prefix
|
||||
(setq greeting-prefix "Hi")
|
||||
(message "full-greeting = %s" full-greeting))
|
||||
;; => "Hi, Admin! Welcome back."
|
||||
```
|
||||
|
||||
### :watch - Watch Variable Changes
|
||||
|
||||
`:watch` lets you execute **side effect** operations when variables change:
|
||||
|
||||
```lisp
|
||||
;; Layer with watchers
|
||||
(define-tp watched-layer ()
|
||||
:props '(face (:foreground $status-color))
|
||||
:data '((status-color . "green"))
|
||||
:watch '((status-color
|
||||
(lambda (new-val old-val layer-name)
|
||||
(message "[%s] Color changed from %s to %s"
|
||||
layer-name old-val new-val)))))
|
||||
|
||||
;; Test
|
||||
(tp-pop-to-buffer "*tp-test*"
|
||||
(insert "Test text")
|
||||
(tp-set 1 (point-max) 'watched-layer)
|
||||
|
||||
;; Change color - triggers watcher
|
||||
(setq status-color "yellow")
|
||||
;; Message: "[watched-layer] Color changed from green to yellow"
|
||||
|
||||
(setq status-color "red"))
|
||||
;; Message: "[watched-layer] Color changed from yellow to red"
|
||||
```
|
||||
|
||||
Typical uses for `:watch`:
|
||||
- Logging
|
||||
- Updating external state
|
||||
- Triggering notifications
|
||||
- Performing cleanup operations
|
||||
|
||||
## Complete Practical Examples
|
||||
|
||||
### Example 1: Dynamic Color Status Indicator
|
||||
|
||||
This example shows how to create an indicator that automatically changes color based on status:
|
||||
|
||||
```lisp
|
||||
(tp-layer-reset)
|
||||
|
||||
;; Define status color variables
|
||||
(defvar status-color "gray")
|
||||
(defvar status-text "Not Started")
|
||||
|
||||
;; Define status indicator layer
|
||||
(define-tp status-indicator ()
|
||||
'(face (:background $status-color) display $status-text))
|
||||
|
||||
;; Define status update function
|
||||
(defun set-status (status)
|
||||
"Set status, automatically update color and text"
|
||||
(pcase status
|
||||
('pending (setq status-color "gray" status-text "Pending"))
|
||||
('running (setq status-color "blue" status-text "Running"))
|
||||
('success (setq status-color "green" status-text "Success"))
|
||||
('warning (setq status-color "orange" status-text "Warning"))
|
||||
('error (setq status-color "red" status-text "Error"))))
|
||||
|
||||
;; Test the status indicator
|
||||
(tp-pop-to-buffer "*tp-test*"
|
||||
(insert "Status")
|
||||
(tp-set 1 (point-max) 'status-indicator)
|
||||
|
||||
;; Simulate status changes
|
||||
(set-status 'pending)
|
||||
(message "Status: %s, Color: %s" status-text status-color)
|
||||
;; => "Status: Pending, Color: gray"
|
||||
|
||||
(set-status 'running)
|
||||
(message "Status: %s, Color: %s" status-text status-color)
|
||||
;; => "Status: Running, Color: blue"
|
||||
|
||||
(set-status 'success)
|
||||
(message "Status: %s, Color: %s" status-text status-color))
|
||||
;; => "Status: Success, Color: green"
|
||||
```
|
||||
|
||||
### Example 2: Theme Switching System
|
||||
|
||||
This example shows how to create a switchable theme system:
|
||||
|
||||
```lisp
|
||||
(tp-layer-reset)
|
||||
|
||||
;; Define theme color variables
|
||||
(defvar keyword-color nil)
|
||||
(defvar string-color nil)
|
||||
|
||||
;; Define theme-related reactive layers
|
||||
(define-tp themed-keyword ()
|
||||
'(face (:foreground $keyword-color :weight bold)))
|
||||
|
||||
(define-tp themed-string ()
|
||||
'(face (:foreground $string-color)))
|
||||
|
||||
;; Define theme switching functions
|
||||
(defun switch-to-dark-theme ()
|
||||
"Switch to dark theme"
|
||||
(interactive)
|
||||
(setq keyword-color "light blue"
|
||||
string-color "green")
|
||||
(message "Switched to dark theme"))
|
||||
|
||||
(defun switch-to-light-theme ()
|
||||
"Switch to light theme"
|
||||
(interactive)
|
||||
(setq keyword-color "blue"
|
||||
string-color "dark green")
|
||||
(message "Switched to light theme"))
|
||||
|
||||
;; Test theme switching
|
||||
(tp-pop-to-buffer "*tp-test*"
|
||||
(insert "(defun hello () \"greeting\")")
|
||||
|
||||
;; Apply different theme layers
|
||||
(tp-match-set "defun" 'themed-keyword)
|
||||
(tp-regexp-set "\".+\"" 'themed-string)
|
||||
|
||||
(switch-to-dark-theme)
|
||||
;; Initially using dark theme
|
||||
(message "Keyword color: %s" keyword-color)
|
||||
(message "String color: %s" string-color)
|
||||
|
||||
;; Switch to light theme
|
||||
(switch-to-light-theme)
|
||||
;; Text updates automatically!
|
||||
(message "Keyword color: %s" keyword-color)
|
||||
(message "String color: %s" string-color))
|
||||
```
|
||||
|
||||
## Anonymous Reactive Layers
|
||||
|
||||
Besides using `define-tp` to define named layers, you can also use reactive variables directly in property lists. tp.el will automatically generate unique names for these anonymous layers:
|
||||
|
||||
```lisp
|
||||
(tp-layer-reset)
|
||||
|
||||
(defvar inline-color "purple")
|
||||
|
||||
(tp-pop-to-buffer "*tp-test*"
|
||||
(insert "Anonymous reactive layer example")
|
||||
|
||||
;; Use $inline-color directly, no need to pre-define a layer
|
||||
(tp-set 1 (point-max) '(face (:foreground $inline-color)))
|
||||
|
||||
;; Text is now purple
|
||||
(message "Color: %s" (plist-get (tp-at 1 'face) :foreground))
|
||||
;; => "purple"
|
||||
|
||||
;; Change the variable
|
||||
(setq inline-color "orange")
|
||||
|
||||
;; Text automatically turns orange
|
||||
(message "Color: %s" (plist-get (tp-at 1 'face) :foreground)))
|
||||
;; => "orange"
|
||||
```
|
||||
|
||||
Anonymous reactive layers are suitable for simple scenarios where you don't need to reuse the same layer definition in multiple places.
|
||||
|
||||
## Reactive Text (tp-text)
|
||||
|
||||
Besides reactive text **properties**, tp.el also supports reactive **text content** itself. Through the special `tp-text` property, you can make the text content reactive too—when the bound variable changes, the text content automatically updates.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
The `tp-text` property has two ways to use:
|
||||
|
||||
#### 1. Initialize with Current Text
|
||||
|
||||
When `tp-text` is `nil`, it will be automatically set to the current region's text content:
|
||||
|
||||
```lisp
|
||||
(tp-pop-to-buffer "*tp-test*"
|
||||
(insert "Hello World")
|
||||
;; When tp-text is nil, auto-initialize to current text "Hello"
|
||||
(tp-set 1 6 '(face bold tp-text nil))
|
||||
;; Now tp-text value is "Hello"
|
||||
(message "tp-text = %s" (tp-at 1 'tp-text)))
|
||||
;; => "Hello"
|
||||
```
|
||||
|
||||
#### 2. Replace Text Content
|
||||
|
||||
When `tp-text` is a string, it replaces the text in the region while preserving other text properties:
|
||||
|
||||
```lisp
|
||||
(tp-pop-to-buffer "*tp-test*"
|
||||
(insert "Hello World")
|
||||
;; When tp-text is a string, replace the text content
|
||||
(tp-set 1 6 '(face bold tp-text "Hi"))
|
||||
;; Text becomes "Hi World", and "Hi" still has bold style
|
||||
(message "buffer = %s" (buffer-string)))
|
||||
;; => "Hi World"
|
||||
```
|
||||
|
||||
### Reactive Text Layers
|
||||
|
||||
The real power of `tp-text` comes from combining it with reactive variables:
|
||||
|
||||
```lisp
|
||||
;; Define a reactive variable
|
||||
(defvar my-dynamic-text "Loading...")
|
||||
|
||||
;; Define a layer containing tp-text
|
||||
(define-tp dynamic-content ()
|
||||
:props '(face (:foreground "blue") tp-text $my-dynamic-text))
|
||||
|
||||
;; Apply to text
|
||||
(tp-pop-to-buffer "*tp-test*"
|
||||
(insert "placeholder")
|
||||
(tp-set 1 12 'dynamic-content)
|
||||
;; Text now shows "Loading..."
|
||||
(message "Initial text: %s" (buffer-string))
|
||||
;; => "Loading... "
|
||||
|
||||
;; Change the variable
|
||||
(setq my-dynamic-text "Data loaded successfully!")
|
||||
;; Text updates automatically!
|
||||
(message "After update: %s" (buffer-string)))
|
||||
;; => "Data loaded successfully! "
|
||||
```
|
||||
|
||||
### Using :compute for Dynamic Text
|
||||
|
||||
`tp-text` can be combined with `:compute` to create dynamic text derived from other variables:
|
||||
|
||||
```lisp
|
||||
(define-tp greeting-layer ()
|
||||
:props '(face (:foreground "green") tp-text $full-greeting)
|
||||
:data '((user-name . "Guest")
|
||||
(greeting-prefix . "Welcome"))
|
||||
:compute '((full-greeting
|
||||
(lambda ()
|
||||
(format "%s, %s!" greeting-prefix user-name)))))
|
||||
|
||||
;; Apply to text
|
||||
(tp-pop-to-buffer "*tp-test*"
|
||||
(insert "placeholder")
|
||||
(tp-set 1 12 'greeting-layer)
|
||||
;; Shows "Welcome, Guest!"
|
||||
(message "Initial: %s" (buffer-string))
|
||||
|
||||
;; Change user name
|
||||
(setq user-name "John")
|
||||
;; Text automatically updates to "Welcome, John!"
|
||||
(message "After update: %s" (buffer-string)))
|
||||
```
|
||||
|
||||
### Anonymous Reactive Text
|
||||
|
||||
You can also use reactive `tp-text` directly in property lists without defining a layer:
|
||||
|
||||
```lisp
|
||||
(defvar inline-text "Original content")
|
||||
|
||||
(tp-pop-to-buffer "*tp-test*"
|
||||
(insert "placeholder")
|
||||
;; Directly use reactive tp-text
|
||||
(tp-set 1 12 '(face bold tp-text $inline-text))
|
||||
;; Shows "Original content"
|
||||
|
||||
;; Change the variable
|
||||
(setq inline-text "New content")
|
||||
;; Text automatically updates to "New content"
|
||||
)
|
||||
```
|
||||
|
||||
### Important Notes
|
||||
|
||||
1. **tp-text only affects buffer text**: For string objects, since Emacs string length is fixed, `tp-text` won't replace string content.
|
||||
2. **Preserves existing properties**: When using `tp-set` or `tp-add` to set `tp-text`, existing text properties are preserved.
|
||||
3. **Non-reactive properties don't add tp-name**: If there are no reactive variables (`$` prefix) in the text properties, `tp-name` and other reactive-specific properties won't be added, maintaining native text property behavior.
|
||||
|
||||
## Value Transformation with :transform
|
||||
|
||||
The `:transform` keyword allows you to register a transformation function that processes `tp-text` values before they are displayed. This is useful for formatting numbers, dates, or other values:
|
||||
|
||||
```lisp
|
||||
;; Number formatting
|
||||
(define-tp price-display ()
|
||||
:props '(tp-text $price)
|
||||
:data '((price . "99.9"))
|
||||
:transform (lambda (text)
|
||||
(format "$%.2f" (string-to-number text))))
|
||||
;; 99.9 displays as $99.00
|
||||
|
||||
;; Date formatting
|
||||
(define-tp date-display ()
|
||||
:props '(tp-text $timestamp)
|
||||
:data '((timestamp . "1703865600"))
|
||||
:transform (lambda (text)
|
||||
(format-time-string "%Y-%m-%d"
|
||||
(seconds-to-time (string-to-number text)))))
|
||||
|
||||
;; Uppercase conversion
|
||||
(define-tp uppercase-text ()
|
||||
:props '(tp-text $content)
|
||||
:data '((content . "hello"))
|
||||
:transform #'upcase)
|
||||
;; "hello" displays as "HELLO"
|
||||
```
|
||||
|
||||
The transform function:
|
||||
- Receives the raw `tp-text` string value
|
||||
- Returns the transformed string for display
|
||||
- Is applied both on initial display and reactive updates
|
||||
- Errors in transform functions are caught and logged
|
||||
|
||||
> 📖 **For more optimization features like batched updates and debug mode, see [Reactive System Optimization](reactive-optimization-en.md)**
|
||||
|
||||
## Summary
|
||||
|
||||
tp.el's reactive text properties feature brings a modern reactive programming experience to Emacs development. By using `$`-prefixed reactive variables, `:data` to define state, `:compute` for derived values, `:watch` to monitor changes, and `:transform` for value formatting, you can build a more dynamic and maintainable text property system.
|
||||
|
||||
Key points:
|
||||
1. **Reactive Variables**: Use `$` prefix to reference variables
|
||||
2. **:props**: Define properties containing reactive variables
|
||||
3. **:data**: Define additional reactive state and initial values
|
||||
4. **:compute**: Define computed properties derived from other variables
|
||||
5. **:watch**: Watch variable changes and execute side effects
|
||||
6. **:transform**: Transform tp-text values before display
|
||||
7. **Automatic Updates**: Change variable values, all related text updates automatically
|
||||
8. **Reactive Text (tp-text)**: Make text content itself reactive
|
||||
Loading…
Reference in New Issue
Block a user