From 7934749397e998d325e8ad439b21c73174efcf15 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 4 Jan 2026 11:35:30 +0000 Subject: [PATCH] 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> --- README.md | 96 +++++++++++++++++++++++----------------------- README_CN.md | 92 ++++++++++++++++++++++---------------------- tp-tests.el | 106 +++++++++++++++++++++++++-------------------------- tp.el | 93 +++++++++++++++++++++++++++++++------------- 4 files changed, 213 insertions(+), 174 deletions(-) diff --git a/README.md b/README.md index 3e9d3cc..6d938de 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/README_CN.md b/README_CN.md index 289e552..2592c2f 100644 --- a/README_CN.md +++ b/README_CN.md @@ -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"))) ;; 将层应用到代码 diff --git a/tp-tests.el b/tp-tests.el index 39f14f5..d4c2947 100644 --- a/tp-tests.el +++ b/tp-tests.el @@ -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 diff --git a/tp.el b/tp.el index 8288f4e..106701e 100644 --- a/tp.el +++ b/tp.el @@ -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"