From af0980efef86decf5a349570dce1d816aafb3d21 Mon Sep 17 00:00:00 2001 From: Kinneyzhang Date: Sun, 26 Jul 2026 16:31:36 +0800 Subject: [PATCH] update --- .DS_Store | Bin 0 -> 6148 bytes ...nc-conflict-20260318-160704-P3OUM7A.md.tmp | Bin 0 -> 93790 bytes ...n.sync-conflict-20260318-162447-WX2NETI.md | 212 ++++++++ ...n.sync-conflict-20260318-162431-WX2NETI.md | 507 ++++++++++++++++++ 4 files changed, 719 insertions(+) create mode 100644 .DS_Store create mode 100644 .syncthing.README_CN.sync-conflict-20260318-160704-P3OUM7A.md.tmp create mode 100644 docs/reactive-optimization.sync-conflict-20260318-162447-WX2NETI.md create mode 100644 docs/reactive-text-properties-en.sync-conflict-20260318-162431-WX2NETI.md diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..7cc77d053cf34cc11789d948a01a594969e40d24 GIT binary patch literal 6148 zcmeHKy-EW?5T4O10yZfv_X&i&!5Yq9NG(WmkO(<1_y>Z;J&Ubc<`uND@lEV){AOp= zTRc*V=nU+Bv-7ia_rdMn5D||rs}a$Vhz1yfEJ}}vx!1L2!Qy1uV>us9W=*~7+2psH zMfUthFQ;8!-IV2Y*-T*@EbboRN8yeAVsqXLb2i>bf`s&VV!E4E%2faA%7Qb`*Vf2AlzB;FAG4 z9|DG8G;9^q(Sa$o0Kfv~B+#Xokepx`4O>NcAgrlCO=W8_Skqw-78ea$MNKER=7X*B zNAtp|I`$7KoH#1_>ra)g!tqP`~U;*DEvw5 literal 0 HcmV?d00001 diff --git a/.syncthing.README_CN.sync-conflict-20260318-160704-P3OUM7A.md.tmp b/.syncthing.README_CN.sync-conflict-20260318-160704-P3OUM7A.md.tmp new file mode 100644 index 0000000000000000000000000000000000000000..957cfdbe7a8a5ecaae376dbd60577242ecf75f0f GIT binary patch literal 93790 zcmeIuF#!Mo0K%a4Pi+kkh(KY$fB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK QfB^#r3>YwAz`)1A0B&sn0RR91 literal 0 HcmV?d00001 diff --git a/docs/reactive-optimization.sync-conflict-20260318-162447-WX2NETI.md b/docs/reactive-optimization.sync-conflict-20260318-162447-WX2NETI.md new file mode 100644 index 0000000..6c848cd --- /dev/null +++ b/docs/reactive-optimization.sync-conflict-20260318-162447-WX2NETI.md @@ -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` | 层定义中的转换选项 | diff --git a/docs/reactive-text-properties-en.sync-conflict-20260318-162431-WX2NETI.md b/docs/reactive-text-properties-en.sync-conflict-20260318-162431-WX2NETI.md new file mode 100644 index 0000000..cf599c7 --- /dev/null +++ b/docs/reactive-text-properties-en.sync-conflict-20260318-162431-WX2NETI.md @@ -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