Align README (EN/CN) and docs/ with modular architecture and 0.2.0 semantics

README.md/README_CN.md: new verified Quick Start (fixes the dead
#quick-start nav link); Installation rewritten for the tp-*.el module
family; every broken or drifted example fixed and executed in batch
Emacs (mandatory () ARGLIST and quoted reactive keywords in all
define-tp/define-tps calls, corrected tp-search-map argument order,
interval-list returns, stacked duplicate-face results, gap intervals,
last-wins tp-plist, non-destructive string forms, per-pattern match
ordering, case-fold regexp outputs, rewired end-to-end theme example);
documents the symmetric tp-backward contract, the length-changing
replacement rules, all four tp-put-layer layer specs, and the
previously-missing tp-member, buffer/display macros, and palette
system; state resets now use tp-layer-reset; license corrected to
GPLv3+. CN mirrors EN exactly (119 headings / 258 fences each; code
blocks identical, comments translated).

docs/ARCHITECTURE.md rewritten around the real nine-module layering
and hook-variable inversions; nonexistent helper names removed.
docs/CODE-ANALYSIS.md marked as pre-split historical analysis with
locations/counts corrected. Reactive docs aligned with the fixed
engine semantics (replace-not-accumulate, buffer-local isolation,
nil computed values, batching union, first-render transform).

287 fenced blocks from both READMEs executed: 0 failures; 56-example
assertion suite passes; combined ERT suite 439/439 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kinneyzhang 2026-07-26 21:01:01 +08:00
parent 9aca18979b
commit 2b33495898
9 changed files with 1204 additions and 497 deletions

View File

@ -189,7 +189,7 @@ Test infrastructure:
- Per-module regression test suites: `tp-core-tests.el`,
`tp-ops-tests.el`, `tp-builtins-tests.el`, `tp-layer-tests.el`,
`tp-stack-tests.el`, `tp-search-tests.el`, `tp-render-tests.el`
the combined suite grew from 280 to 438 tests.
the combined suite grew from 280 to 439 tests.
### Changed

457
README.md
View File

@ -18,6 +18,7 @@
## Table of Contents
- [Quick Start](#quick-start)
- [Overview](#overview)
- [Core Innovations](#core-innovations)
- [Features](#features)
@ -38,6 +39,7 @@
- [tp-add](#tp-add---addmerge-properties)
- [tp-get](#tp-get---get-property-value)
- [tp-at](#tp-at---get-property-at-position)
- [tp-member](#tp-member---property-membership-at-position)
- [tp-remove](#tp-remove---remove-property)
- [tp-clear](#tp-clear---clear-all-properties)
- [Pattern Matching Functions](#pattern-matching-functions)
@ -91,6 +93,8 @@
- [tp-plist](#tp-plist---get-all-properties-in-region)
- [tp-empty-p](#tp-empty-p---check-if-object-has-properties)
- [tp-region-layer-props](#tp-region-layer-props---get-layer-properties-in-region)
- [tp-with-current-buffer / tp-pop-to-buffer / tp-switch-to-buffer](#tp-with-current-buffer--tp-pop-to-buffer--tp-switch-to-buffer)
- [Color Palette System](#color-palette-system)
- [Reactive Text Properties](#reactive-text-properties)
- [Core Concept](#core-concept)
- [How It Works](#how-it-works)
@ -115,10 +119,45 @@
---
## Quick Start
```elisp
;; Install: clone the repository, add it to your load-path, and require
(add-to-list 'load-path "/path/to/tp")
(require 'tp)
;; Set properties with one unified API (returns a new propertized string)
(tp-set "hello" 'face 'bold)
;; => #("hello" 0 5 (face bold))
;; Stack property layers on a buffer region
(define-tp spotlight () '(face (:background "yellow")))
(with-temp-buffer
(insert "Hello World")
(tp-push-layer 1 6 'spotlight)
(tp-layer-top 1 6))
;; => spotlight
;; Reactive: text properties follow a variable
(defvar accent-color "red")
(define-tp accent ()
:props '(face (:foreground $accent-color)))
(with-temp-buffer
(insert "Hello")
(tp-push-layer 1 6 'accent)
(setq accent-color "blue") ; text updates automatically!
(tp-at 1 'face))
;; => (:foreground "blue")
```
---
## Overview
**tp.el** is a library that comprehensively enhances Emacs text property manipulation. It is not just a simple wrapper around native text property APIs (like `put-text-property`, `get-text-property`), but provides many **functional extensions that native functions do not have**. tp.el innovates in the following areas:
Since 0.2.0 the library is organized as a family of layered modules (`tp-core`, `tp-reactive`, `tp-layer`, `tp-ops`, `tp-search`, `tp-render`, `tp-stack`, `tp-palette`, `tp-builtins`) behind the umbrella file `tp.el``(require 'tp)` still loads everything, so nothing changes for users. See [Installation](#installation) for the module map.
### Core Innovations
1. **Unified API Parameter Conventions**: All functions support multiple flexible calling patterns, working seamlessly with both strings and buffers
@ -168,13 +207,13 @@ Native APIs only have simple set and get. tp.el provides three clear operation s
- ✅ **Path-style Access**: Access deeply nested property values through path syntax
```elisp
;; Get nested properties
(tp-get str 'face :underline :style) ; => wave
;; Get nested properties (tp-get returns (START END VALUE) intervals)
(tp-get str 'face :underline :style) ; => ((0 5 wave))
(tp-at 5 '(face :box :color)) ; => "blue"
;; Get multiple nested keys
(tp-get str 'face :underline '(:color :style))
;; => ((:color "green" :style wave))
;; => ((0 5 (:color "green" :style wave)))
```
- ✅ **Sub-property Deletion**: Precisely remove specific keys from nested properties
```elisp
@ -191,7 +230,8 @@ Native APIs only have simple set and get. tp.el provides three clear operation s
'face 'bold
'face '(:background "green")
'face '(:foreground "red"))
;; Result: face is ((:background "green" :foreground "red") bold)
;; Result: face is ((:foreground "red") (:background "green") bold)
;; (entries stack into one face list, most recent first)
;; Later values override earlier ones for the same sub-property
(tp-set "emacs"
@ -275,12 +315,13 @@ 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
;; (note: ARGLIST () is mandatory, and the keyword values are quoted)
(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))))
:watch ((first-name (lambda (new old layer)
(message "Name changed from %s to %s" old new)))))
:data '((first-name . "John") (last-name . "Doe") (name-color . "purple"))
:compute '((full-name (lambda () (concat first-name " " last-name))))
:watch '((first-name (lambda (new old layer)
(message "Name changed from %s to %s" old new)))))
```
### Enhanced Search & Navigation
@ -295,16 +336,20 @@ Native APIs require manual searching and looping. tp.el provides convenient patt
(tp-search my-string 'marker) ; => ((0 5 t) (12 17 t))
;; Upcase all marker text
(tp-search-map #'upcase my-string 'marker)
(tp-search-map #'upcase 'marker nil my-string)
```
## Requirements
- **Emacs 28.1+** (uses `object-intervals` function)
- **dash.el** (list manipulation utilities)
- **dash.el 2.19.1+** (list manipulation utilities)
## Installation
The library is the `tp-*.el` module family plus the umbrella file `tp.el`.
Installing means putting the directory on your `load-path` and requiring the
umbrella, which loads every module:
```elisp
;; Add to your load-path
(add-to-list 'load-path "/path/to/tp")
@ -318,6 +363,23 @@ Or with `use-package`:
:load-path "/path/to/tp")
```
The modules and their roles:
| Module | Responsibility |
|---|---|
| `tp-core.el` | Intervals, plist/face merge engine, debug logging, `$var` utilities |
| `tp-reactive.el` | Reactive dependency registry, variable watchers, batching queue |
| `tp-layer.el` | `define-tp` / `define-tps`, layer registry and resolution |
| `tp-ops.el` | `tp-set` / `tp-reset` / `tp-add` / `tp-get` / `tp-at` / `tp-remove` / `tp-clear` |
| `tp-search.el` | `tp-match-*`, `tp-regexp-*`, `tp-search`, navigation |
| `tp-render.el` | Reactive re-rendering engine |
| `tp-stack.el` | Layer stack operations (push/pop/move/merge/flatten/...) |
| `tp-palette.el` | Light/dark color palette data |
| `tp-builtins.el` | Built-in layers, palette gallery, display-buffer helpers |
A `Makefile` is included: `make test` runs all ERT suites, `make compile`
byte-compiles the modules, and `make clean` removes compiled files.
---
## API Reference
@ -334,6 +396,7 @@ A complete overview of all tp.el functions organized by category:
| [`tp-add`](#tp-add---addmerge-properties) | Add/merge properties with deep merge support |
| [`tp-get`](#tp-get---get-property-value) | Get property value(s) from range or string |
| [`tp-at`](#tp-at---get-property-at-position) | Get property value(s) at a single position |
| [`tp-member`](#tp-member---property-membership-at-position) | Like `tp-at`, but distinguishes present-with-nil from absent |
| [`tp-remove`](#tp-remove---remove-property) | Remove a property or sub-property |
| [`tp-clear`](#tp-clear---clear-all-properties) | Clear all text properties from a region |
@ -420,6 +483,17 @@ A complete overview of all tp.el functions organized by category:
| [`tp-intervals-map`](#tp-intervals-map---apply-function-to-intervals) | Apply function to all intervals in a region |
| [`tp-plist`](#tp-plist---get-all-properties-in-region) | Get all properties present in a region |
| [`tp-empty-p`](#tp-empty-p---check-if-object-has-properties) | Check if object has no text properties |
| [`tp-with-current-buffer`](#tp-with-current-buffer--tp-pop-to-buffer--tp-switch-to-buffer) | Run body in a buffer with `inhibit-read-only` bound |
| [`tp-pop-to-buffer`](#tp-with-current-buffer--tp-pop-to-buffer--tp-switch-to-buffer) | Fill a buffer, make it read-only, display via `pop-to-buffer` |
| [`tp-switch-to-buffer`](#tp-with-current-buffer--tp-pop-to-buffer--tp-switch-to-buffer) | Fill a buffer, make it read-only, display via `switch-to-buffer` |
#### Palette Functions
| Function | Description |
|----------|-------------|
| [`tp-palette-alist`](#color-palette-system) | Registry of named palettes (variable) |
| [`define-tp-palette`](#color-palette-system) | Register or update a named palette |
| [`tp-palette-show`](#color-palette-system) | Show a gallery of all registered palettes |
| [`tp-parse-color`](#color-palette-system) | Resolve a color spec for the current light/dark theme |
---
@ -495,8 +569,8 @@ LAYER-NAME can be a symbol representing a layer defined by `define-tp` or a grou
(let ((my-buffer (generate-new-buffer "*test*")))
(with-current-buffer my-buffer
(insert "Hello World"))
(tp-set 1 10 '(face italic) my-buffer)
(kill-buffer my-buffer))
(prog1 (tp-set 1 10 '(face italic) my-buffer)
(kill-buffer my-buffer)))
;; => (1 . 10)
;; Set properties on a string region (0-indexed) - MODIFIES original string
@ -518,16 +592,17 @@ LAYER-NAME can be a symbol representing a layer defined by `define-tp` or a grou
;; Use a defined layer name on entire string
(define-tp my-style ()
:props '(face (:foreground $my-color))
:data ((my-color . "blue")))
:data '((my-color . "blue")))
(tp-set " " 'my-style)
;; => #(" " 0 1 (tp-name my-style face (:foreground "blue") ...))
;; => #(" " 0 1 (face (:foreground "blue") tp-name my-style))
;; Merge multiple faces in a single call (duplicate properties auto-merged)
(tp-set "emacs"
'face 'bold
'face '(:background "green")
'face '(:foreground "red"))
;; => Three faces merged into one: ((:background "green" :foreground "red") bold)
;; => face is ((:foreground "red") (:background "green") bold)
;; (entries stack into one face list, most recent first)
;; Later values override earlier ones for the same sub-property
(tp-set "emacs"
@ -806,6 +881,37 @@ For single-position property queries (previously done with `tp-get`), use `tp-at
---
#### `tp-member` - Property Membership at Position
```elisp
(tp-member POS PROPERTY &optional OBJECT)
```
Like `tp-at`, but returns a `(PROPERTY VALUE)` list when PROPERTY is present
at POS, or nil when it is absent. This distinguishes a property that is
present with the value nil from a property that is missing entirely
(analogous to `plist-member`).
**Examples:**
```elisp
;; Present with value nil vs. absent
(let ((str (copy-sequence "Hello")))
(tp-set 0 5 '(face nil) str)
(list (tp-member 0 'face str) ; present, value nil
(tp-member 0 'display str))) ; absent
;; => ((face nil) nil)
;; In a buffer
(with-temp-buffer
(insert "Hello")
(tp-set 1 6 '(face bold))
(tp-member 1 'face))
;; => (face bold)
```
---
#### `tp-remove` - Remove Property
Remove a property or nested sub-property from a region or entire string.
@ -874,7 +980,7 @@ Remove a property or nested sub-property from a region or entire string.
;; Remove nested keys from string
(let ((original (propertize "Hello" 'face '(:underline (:style wave :color "blue")))))
(let ((result (tp-remove original 'face :underline '(:style))))
(get-text-property 0 '(face :underline) result)))
(tp-at 0 '(face :underline) result)))
;; => (:color "blue")
```
@ -942,7 +1048,8 @@ OBJECT is a buffer or string; nil means current buffer.
(with-temp-buffer
(insert "Hello world, Hello again")
(tp-match-set '("world" "Hello") '(face bold)))
;; => ((1 . 6) (7 . 12) (14 . 19)) ; Matches "Hello", "world", "Hello"
;; => ((7 . 12) (1 . 6) (14 . 19)) ; regions grouped per pattern:
;; "world" first, then each "Hello", in the order patterns are given
;; Multiple patterns on string
(tp-match-set '("Hello" "world") '(face bold) "Hello world")
@ -1064,13 +1171,15 @@ OBJECT is a buffer or string; nil means current buffer.
(list (tp-at 5 'face) (tp-at 13 'face)))
;; => (font-lock-number-face font-lock-number-face)
;; On string
;; On string (`case-fold-search' applies by default, so "Hello" matches too;
;; let-bind it to nil for case-sensitive matching)
(tp-regexp-set "[A-Z]+" '(face bold) "Hello WORLD")
;; => #("Hello WORLD" 6 11 (face bold))
;; => #("Hello WORLD" 0 5 (face bold) 6 11 (face bold))
;; Multiple regexps - match both numbers and uppercase letters
;; (with case folding, "abc" matches "[A-Z]+" as well)
(tp-regexp-set '("[0-9]+" "[A-Z]+") '(face bold) "abc 123 XYZ")
;; => #("abc 123 XYZ" 4 7 (face bold) 8 11 (face bold))
;; => #("abc 123 XYZ" 0 3 (face bold) 4 7 (face bold) 8 11 (face bold))
;; Use a defined layer name
(define-tp number-style ()
@ -1107,12 +1216,12 @@ OBJECT is a buffer or string; nil means current buffer.
(tp-at 5))
;; => (face bold) ; help-echo is removed
;; On string
;; On string - returns a NEW string; the original is unchanged
(let ((str (copy-sequence "abc 123 def")))
(tp-set 4 7 '(help-echo "original") str)
(tp-regexp-reset "[0-9]+" '(face italic) str)
(tp-at 4 str))
;; => (face italic)
(let ((result (tp-regexp-reset "[0-9]+" '(face italic) str)))
(list (tp-at 4 result) (tp-at 4 str))))
;; => ((face italic) (help-echo "original"))
;; Use a defined layer name
(define-tp code-number ()
@ -1149,12 +1258,12 @@ OBJECT is a buffer or string; nil means current buffer.
(tp-at 5))
;; => (face bold help-echo "number")
;; On string
;; On string - returns a NEW string; the original is unchanged
(let ((str (copy-sequence "abc 123 def")))
(tp-set 4 7 '(help-echo "number") str)
(tp-regexp-add "[0-9]+" '(face italic) str)
(tp-at 4 str))
;; => (face italic help-echo "number")
(let ((result (tp-regexp-add "[0-9]+" '(face italic) str)))
(list (tp-at 4 result) (tp-at 4 str))))
;; => ((face italic help-echo "number") (help-echo "number"))
;; Use a defined layer name
(define-tp bold-underline ()
@ -1191,23 +1300,47 @@ These are low-level search functions that work directly with prop-match objects.
Search forward/backward N times for text with PROPERTY.
- **N** is the number of searches, defaulting to 1.
- **VALUE** is the optional value to match.
- **VALUE** is `equal`-matched against the property's value in buffers.
Passing nil therefore matches the next run where PROPERTY is *absent*
(its value is nil); pass the value explicitly to find a propertied region.
- **`tp-backward` mirrors `tp-forward`**: the same equal-matching semantics,
in the opposite direction.
- **OBJECT** can be a buffer or string; nil defaults to current buffer.
- For buffers, returns the prop-match object from the last successful search.
- For strings, returns a list of (START END VALUE) for all matches found.
- For strings, returns a list of (START END VALUE) for runs where PROPERTY
is present; VALUE nil means any value. `tp-backward` returns them from
end to start.
**Examples:**
```elisp
;; Find next text with 'marker property
;; Find next text where 'marker equals t
(with-temp-buffer
(insert "Hello World Test")
(tp-set 7 12 '(marker t))
(goto-char 1)
(let ((match (tp-forward 'marker t)))
(when match
(prop-match-beginning match))))
;; => 7
;; VALUE nil equal-matches nil - i.e. the run WITHOUT the property
(with-temp-buffer
(insert "Hello World Test")
(tp-set 7 12 '(marker t))
(goto-char 1)
(let ((match (tp-forward 'marker)))
(when match
(prop-match-beginning match))))
;; => 7
(list (prop-match-beginning match) (prop-match-end match))))
;; => (1 7) ; the run where marker is absent
;; Backward mirrors forward: same value matching, opposite direction
(with-temp-buffer
(insert "Hello World Test")
(tp-set 7 12 '(marker t))
(goto-char (point-max))
(let ((match (tp-backward 'marker t)))
(list (prop-match-beginning match) (prop-match-end match))))
;; => (7 12)
;; Find next text where 'type equals 'heading
(with-temp-buffer
@ -1238,7 +1371,8 @@ Search forward/backward N times for text with PROPERTY.
Search forward/backward for text with PROPERTY and apply FUNCTION **only to the last match**.
- **FUNCTION** receives `(TEXT &optional START END)` where TEXT is the matched text, START and END are the positions of the match. The return value of FUNCTION replaces the matched text in the string or buffer.
- **FUNCTION** receives `(TEXT &optional START END IDX)` where TEXT is the matched text, START and END are the positions of the match, and IDX is the 0-based match index. FUNCTION is called with as many of these arguments as it accepts. When FUNCTION returns a string, it replaces the matched text in the string or buffer.
- **Replacements may change length in buffers** (the match is deleted and the replacement inserted). **Strings cannot change length in place**: a replacement of a different length signals an error; same-length replacements are applied in place.
- **PROPERTY** is the text property to search for.
- **VALUE** is the optional value to match; nil means search for PROPERTY without matching value.
- **OBJECT** can be a buffer or string; nil defaults to current buffer.
@ -1345,7 +1479,13 @@ Apply FUNCTION to all matches of PROPERTY in OBJECT.
- TEXT is the matched text
- START and END are the positions of the match
- IDX is the 0-based index of the current match
The return value of FUNCTION replaces the matched text in the string or buffer.
FUNCTION is called with as many of these arguments as it accepts. When
FUNCTION returns a string, it replaces the matched text in the string or
buffer.
- **Replacements may change length in buffers** (the match is deleted and the
replacement inserted). **Strings cannot change length in place**: a
replacement of a different length signals an error; same-length
replacements are applied in place.
- **PROPERTY** is the text property to search for.
- **VALUE** is the optional value to match; nil means search for PROPERTY without matching value.
- **OBJECT** can be a buffer or string; nil defaults to current buffer.
@ -1424,8 +1564,8 @@ Custom text properties is a **general-purpose feature** provided by tp.el. After
'face 'bold
'face '(:background "green")
'face '(:foreground "red"))
;; Result: face is ((:background "green" :foreground "red") bold)
;; Three face properties are intelligently merged
;; Result: face is ((:foreground "red") (:background "green") bold)
;; Three face properties stack into one face list, most recent first
;; Later values override earlier ones for the same sub-property
(tp-set "emacs"
@ -1499,7 +1639,7 @@ Text property layers is a **unique feature** of tp.el that requires specific fun
##### `define-tp` - Define Single Custom Text Property (Layer)
Define a custom text property. The name does not need to be quoted. Supports three formats:
Define a custom text property. The name does not need to be quoted. The ARGLIST is **mandatory in every format**: `()` for non-parameterized layers (including the reactive keyword format), `(ARG)` for parameterized layers. Supports three formats:
**Format 1 - Non-parameterized (empty argument list, simple properties):**
@ -1527,9 +1667,9 @@ Define a custom text property. The name does not need to be quoted. Supports thr
```elisp
(define-tp my-reactive-layer ()
:props '(face (:foreground $my-color))
:data '((my-color . "red"))
:compute '((full-name (lambda () (concat first-name " " last-name))))
:props '(face (:foreground $my-color) help-echo $status-note)
:data '((my-color . "red") (status . "active"))
:compute '((status-note (lambda () (concat "status: " status))))
:watch '((my-color (lambda (new old layer) (message "Color changed!"))))
:transform (lambda (text) (upcase text)))
@ -1547,9 +1687,13 @@ Define a custom text property. The name does not need to be quoted. Supports thr
- **:watch** - Watchers that execute callbacks when variables change
- **:transform** - Transform function to process `tp-text` values before display
Note: the values of `:props`, `:data`, `:compute`, and `:watch` must be
**quoted** (they are evaluated when the layer is defined); `:transform`
takes a function.
##### `define-tps` - Define Custom Text Property Group (Layer Group)
Define multiple related custom text properties. The name does not need to be quoted. Properties in the group can be used individually or with the group name to set multiple layers.
Define multiple related custom text properties. The name does not need to be quoted. As with `define-tp`, the ARGLIST is **mandatory**: `()` for non-parameterized groups, `(ARG)` for parameterized ones. Properties in the group can be used individually or with the group name to set multiple layers.
**Format 1 - Non-parameterized (empty argument list):**
@ -1641,8 +1785,7 @@ The first layer in the definition is the top layer (visible by default).
```elisp
;; Define status layers, then group them
(progn
(setq tp-layer-alist nil)
(setq tp-layer-groups nil)
(tp-layer-reset)
(define-tp highlight ()
'(face (:background "yellow" :foreground "black")))
(define-tp error ()
@ -1656,20 +1799,18 @@ The first layer in the definition is the top layer (visible by default).
;; Define a layer group with named layers
(progn
(setq tp-layer-alist nil)
(setq tp-layer-groups nil)
(tp-layer-reset)
(define-tps moon-phases ()
'("new" . (display "🌑"))
'("waxing-crescent" . (display "🌒"))
'("first-quarter" . (display "🌓"))
'("full" . (display "🌕")))
(tp-layer-props 'moon-phases-full))
;; => (display "🌕" tp-name moon-phases-full)
;; => (display "🌕")
;; Parameterized layer group referencing other defined layers
(progn
(setq tp-layer-alist nil)
(setq tp-layer-groups nil)
(tp-layer-reset)
(define-tp tp-test-l1 (color)
`(face (:foreground ,color)))
(define-tp tp-test-l2 (color)
@ -1691,27 +1832,34 @@ The first layer in the definition is the top layer (visible by default).
#### `tp-layer-props` / `tp-group-props`
```elisp
(tp-layer-props LAYER-NAME)
(tp-group-props GROUP-NAME)
(tp-layer-props LAYER-NAME &optional INCLUDE-TP-NAME)
(tp-group-props GROUP-NAME &optional INCLUDE-TP-NAME)
```
Get properties for a layer or all layers in a group.
By default the result contains only the layer's own properties. When
INCLUDE-TP-NAME is non-nil, a `tp-name LAYER-NAME` entry is appended
(the form used internally by the layer stack). Exception: layers with
registered reactive dependencies always include `tp-name` — the
reactive engine uses it to locate and re-render their regions.
**Examples:**
```elisp
;; Get layer properties
;; Get layer properties (no tp-name by default)
(progn
(setq tp-layer-alist nil)
(tp-layer-reset)
(define-tp my-layer ()
'(face bold help-echo "tip"))
(tp-layer-props 'my-layer))
;; => (face bold help-echo "tip" tp-name my-layer)
(list (tp-layer-props 'my-layer)
(tp-layer-props 'my-layer t)))
;; => ((face bold help-echo "tip")
;; (face bold help-echo "tip" tp-name my-layer))
;; Get group properties
(progn
(setq tp-layer-alist nil)
(setq tp-layer-groups nil)
(tp-layer-reset)
(define-tp layer1 () '(face bold))
(define-tp layer2 () '(face italic))
(define-tps my-group ()
@ -1736,7 +1884,7 @@ Remove layer or group definition.
```elisp
;; Undefine a layer
(progn
(setq tp-layer-alist nil)
(tp-layer-reset)
(define-tp temp-layer () '(face bold))
(tp-undefine-layer 'temp-layer)
(tp-layer-props 'temp-layer))
@ -1744,8 +1892,7 @@ Remove layer or group definition.
;; Undefine a group
(progn
(setq tp-layer-alist nil)
(setq tp-layer-groups nil)
(tp-layer-reset)
(define-tp l1 () '(face bold))
(define-tps my-group ()
'l1)
@ -1798,7 +1945,7 @@ This is useful when you want to remove all reactive bindings but keep the layer
(tp-reactive-reset)
;; Layer still exists, but changing my-reactive-color no longer updates it
(tp-layer-props 'reactive-layer))
;; => (face (:foreground "red") tp-name reactive-layer)
;; => (face (:foreground "red"))
```
---
@ -1821,6 +1968,17 @@ Set layer(s) at a specific index position in the layer stack.
- `IDX = -1`: Bottom
- Other values insert at that position
LAYER accepts several specs:
- a layer name defined with `define-tp`: `'highlight`
- an inline property plist (no `define-tp` needed): `'(face bold help-echo "tip")`
- a list of layer names (the first name ends up on top): `'(layer-a layer-b)`
- a parameterized layer call: `'(tp-color "red")`
**Stack model:** only the top layer's properties are the visible text
properties; lower layers are stored in the `tp-layers` text property until
they are raised, rotated, or flattened.
**Examples:**
```elisp
@ -1858,6 +2016,35 @@ Set layer(s) at a specific index position in the layer stack.
(tp-put-layer 1 10 'info -1)
(tp-layer-top 1 10)))
;; => base ; info is at bottom, base is visible
;; Inline plist - no define-tp needed
(with-temp-buffer
(insert "Hello World")
(tp-put-layer 1 10 '(face bold help-echo "tip") 0)
(list (tp-at 1 'face) (tp-at 1 'help-echo)))
;; => (bold "tip")
;; List of layer names - layer-a ends up on top
(progn
(tp-layer-reset)
(define-tp layer-a () '(face bold))
(define-tp layer-b () '(face italic))
(with-temp-buffer
(insert "Hello World")
(tp-put-layer 1 10 '(layer-a layer-b) 0)
(list (tp-at 1 'face) (tp-layer-list 1 10))))
;; => (bold (layer-a layer-b))
;; Parameterized layer call
(progn
(tp-layer-reset)
(define-tp tp-color (color)
`(face (:foreground ,color)))
(with-temp-buffer
(insert "Hello World")
(tp-put-layer 1 10 '(tp-color "red") 0)
(tp-at 1 'face)))
;; => (:foreground "red")
```
---
@ -1899,6 +2086,21 @@ Push a layer to the top of the stack (equivalent to `tp-put-layer ... 0`).
(tp-push-layer 1 10 'highlight)
(tp-at 1 'tp-name)))
;; => highlight
;; The top layer's props are visible; lower layers wait in `tp-layers'
(progn
(tp-layer-reset)
(define-tp base () '(face default))
(define-tp highlight () '(face (:background "yellow")))
(with-temp-buffer
(insert "Hello World")
(tp-push-layer 1 10 'base)
(tp-push-layer 1 10 'highlight)
(list :face (tp-at 1 'face)
:top (tp-layer-top 1 10)
:layers (tp-layer-list 1 10)
:hidden (length (tp-at 1 'tp-layers)))))
;; => (:face (:background "yellow") :top highlight :layers (highlight base) :hidden 1)
```
---
@ -2471,7 +2673,11 @@ Add or merge properties to all layers in a region or string.
Get all text property intervals from START to END in OBJECT.
- Returns a list of (START END PROPERTIES) for each interval.
- Returns a list of (START END PROPERTIES) for each interval, including
gap intervals with no properties, whose PROPERTIES is nil.
- For buffer input, START and END are 1-based buffer positions but the
returned positions are **0-based offsets relative to START**. For strings,
positions are absolute 0-based indices.
- Uses `object-intervals` (requires Emacs 28.1+).
- OBJECT can be a buffer or string; nil defaults to current buffer.
@ -2483,7 +2689,8 @@ Get all text property intervals from START to END in OBJECT.
(tp-set 1 6 '(face bold))
(tp-set 7 12 '(face italic))
(tp-intervals 1 12))
;; => ((0 5 (face bold)) (6 11 (face italic)))
;; => ((0 5 (face bold)) (5 6 nil) (6 11 (face italic)))
;; positions are offsets from START; (5 6 nil) is the unpropertized gap
```
---
@ -2497,8 +2704,9 @@ Get all text property intervals from START to END in OBJECT.
Apply FUNCTION to all intervals between START and END in OBJECT.
- FUNCTION receives four arguments: interval-start, interval-end, top-props (visible layer properties), and below-props-lst (list of hidden layers).
- Intervals with no properties are visited too, with nil top-props (positions follow the same offset convention as `tp-intervals`).
- OBJECT can be a buffer or string; nil defaults to current buffer.
- Returns list of function results (nil values are removed).
- Returns list of function results (nil results are removed).
**Examples:**
@ -2511,7 +2719,7 @@ Apply FUNCTION to all intervals between START and END in OBJECT.
(lambda (start end props belows)
(list start end (plist-get props 'face)))
1 12))
;; => ((0 5 bold) (6 11 italic))
;; => ((0 5 bold) (5 6 nil) (6 11 italic))
```
---
@ -2556,7 +2764,9 @@ Return layer properties for LAYER-NAME in region from START to END.
Get a property list of all properties present in a region or string.
- Returns a plist containing all properties found in the range.
- Returns a single merged plist of the properties found in the range; when
the same property occurs in several intervals, the value from the later
interval wins.
- OBJECT defaults to current buffer for region form.
**Examples:**
@ -2567,7 +2777,7 @@ Get a property list of all properties present in a region or string.
(tp-set 1 6 '(face bold help-echo "Tip"))
(tp-set 7 12 '(face italic))
(tp-plist 1 12))
;; => (face bold help-echo "Tip" face italic)
;; => (help-echo "Tip" face italic) ; later interval's face wins
```
---
@ -2587,13 +2797,81 @@ Return t if OBJECT has no text properties.
```elisp
(tp-empty-p "plain text") ; => t
(let ((str (copy-sequence "text")))
(tp-set str 'face 'bold)
(tp-empty-p str)) ; => nil
;; Whole-string tp-set is non-destructive: the original stays empty
(let* ((str "text")
(new (tp-set str 'face 'bold)))
(list (tp-empty-p str) (tp-empty-p new)))
;; => (t nil)
```
---
#### `tp-with-current-buffer` / `tp-pop-to-buffer` / `tp-switch-to-buffer`
```elisp
(tp-with-current-buffer BUFFER-OR-NAME BODY...)
(tp-pop-to-buffer BUFFER-OR-NAME BODY...)
(tp-switch-to-buffer BUFFER-OR-NAME BODY...)
```
Convenience macros for operating on and displaying propertized content:
- **`tp-with-current-buffer`** evaluates BODY in BUFFER-OR-NAME with
`inhibit-read-only` bound to t. Useful for modifying read-only display
buffers.
- **`tp-pop-to-buffer`** creates (or reuses) BUFFER-OR-NAME, erases it,
evaluates BODY inside it, then makes it read-only and displays it with
`pop-to-buffer`. Press `q` in the displayed buffer to quit its window.
- **`tp-switch-to-buffer`** is the same but displays the buffer with
`switch-to-buffer`.
**Example:**
```elisp
(tp-pop-to-buffer "*tp-demo*"
(insert (tp-set "Important" 'face '(:foreground "red" :weight bold))
" message\n"))
;; Displays *tp-demo* with the propertized text; `q' quits the window
```
---
### Color Palette System
`tp-palette.el` ships a set of named color palettes with separate light-mode
and dark-mode colors, and `tp-builtins.el` exposes them through the built-in
parameterized `tp-palette` layer (as in `(tp-set "emacs" 'tp-palette 'info)`).
- **`tp-palette-alist`** (variable) — alist of `(NAME . PLIST)` palette
definitions; the single source of truth for palette lookups. Each PLIST
maps `:fg`, `:bg`, and `:border` to colors.
- **`define-tp-palette`** — register (or update) a palette:
```elisp
(define-tp-palette my-brand
:fg ("#0969da" . "#58a6ff") ; ("light" . "dark")
:bg ("#ddf4ff" . "#1f3d5c"))
```
- **`tp-palette-show`** — interactive command that displays a gallery buffer
of every registered palette and its `-fg` / `-bg` / `-fbg` / `-border`
variants (`q` quits).
- **`tp-parse-color`** — resolve a color spec for the current theme. Accepts
a plain color string, a `("light" . "dark")` cons (either side may be nil),
or a `(:light L :dark D)` plist:
```elisp
(tp-parse-color "red") ; => "red"
(tp-parse-color '("white" . "black")) ; => "white" on a light theme,
; "black" on a dark theme
```
Note: `tp-layer-reset` clears every layer definition, including built-in
layers like `tp-palette`.
---
## Practical Examples
### Syntax Highlighting with Multiple Layers
@ -2637,7 +2915,7 @@ Return t if OBJECT has no text properties.
(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)
(define-tps task-status () 'status-todo 'status-progress 'status-done)
;; Check group is defined
(length (tp-group-props 'task-status)))
;; => 3
@ -2658,7 +2936,7 @@ Return t if OBJECT has no text properties.
(define-tp temp-highlight ()
'(face (:background "yellow")))
(tp-layer-props 'temp-highlight))
;; => (face (:background "yellow") tp-name temp-highlight)
;; => (face (:background "yellow"))
;; Flash function (for use in real buffers)
(defun flash-region (start end)
@ -2793,10 +3071,10 @@ The `:watch` keyword lets you execute callbacks when reactive variables change:
```elisp
(define-tp monitored-layer ()
:props '(face (:foreground $status-color))
:watch ((status-color
(lambda (new-val old-val layer-name)
(message "Layer %s: color changed from %s to %s"
layer-name old-val new-val)))))
:watch '((status-color
(lambda (new-val old-val layer-name)
(message "Layer %s: color changed from %s to %s"
layer-name old-val new-val)))))
(setq status-color "red")
;; Message: "Layer monitored-layer: color changed from nil to red"
@ -2875,7 +3153,7 @@ All text property APIs (`tp-set`, `tp-match-set`, `tp-regexp-set`, etc.) now acc
Layer groups can also use reactive features:
```elisp
(define-tps status-indicators
(define-tps status-indicators ()
'("success" :props (face (:foreground $success-color))
:data ((success-color . "green")))
'("warning" :props (face (:foreground $warning-color))
@ -2960,22 +3238,18 @@ To clear all reactive dependencies and watchers:
(defvar theme-bg "black")
(defvar theme-accent "cyan")
;; Define theme-aware layers
;; Define theme-aware layers - each one references a theme variable
(define-tp code-text ()
:props '(face (:foreground $theme-fg :background $theme-bg)))
(define-tp code-keyword ()
:props '(face (:foreground $theme-accent :weight bold)))
(define-tp code-comment ()
:props '(face (:foreground "gray" :slant italic)))
(define-tp code-string ()
:props '(face (:foreground "green")))
;; Apply layers to code
;; Apply layers to code in the current buffer
(tp-set (point-min) (point-max) 'code-text)
(tp-match-set '("defun" "defvar" "let" "if" "when") 'code-keyword)
(tp-regexp-set ";.*$" 'code-comment)
(tp-regexp-set "\"[^\"]*\"" 'code-string)
;; Switch to light theme - just change variables!
;; Switch to light theme - just change the variables!
(defun switch-to-light-theme ()
(interactive)
(setq theme-fg "black")
@ -2988,13 +3262,16 @@ To clear all reactive dependencies and watchers:
(setq theme-fg "white")
(setq theme-bg "black")
(setq theme-accent "cyan"))
;; After `switch-to-light-theme', keywords turn blue and the rest of the
;; code turns black-on-white - every region re-renders automatically
```
---
## License
GNU General Public License v2 or later.
GNU General Public License v3 or later. See the [LICENSE](LICENSE) file.
---

View File

@ -17,6 +17,7 @@
## 目录
- [快速开始](#快速开始)
- [概述](#概述)
- [核心创新](#核心创新)
- [功能特性](#功能特性)
@ -37,6 +38,7 @@
- [tp-add](#tp-add---添加合并属性)
- [tp-get](#tp-get---获取属性值)
- [tp-at](#tp-at---获取位置属性)
- [tp-member](#tp-member---判断位置属性是否存在)
- [tp-remove](#tp-remove---移除属性)
- [tp-clear](#tp-clear---清除所有属性)
- [模式匹配函数](#模式匹配函数)
@ -90,6 +92,8 @@
- [tp-plist](#tp-plist---获取区域中的所有属性)
- [tp-empty-p](#tp-empty-p---检查对象是否有属性)
- [tp-region-layer-props](#tp-region-layer-props---获取区域中的层属性)
- [tp-with-current-buffer / tp-pop-to-buffer / tp-switch-to-buffer](#tp-with-current-buffer--tp-pop-to-buffer--tp-switch-to-buffer)
- [调色板系统](#调色板系统)
- [响应式文本属性](#响应式文本属性)
- [核心概念](#核心概念)
- [工作原理](#工作原理)
@ -114,10 +118,45 @@
---
## 快速开始
```elisp
;; 安装:克隆仓库,将其加入 load-path然后 require
(add-to-list 'load-path "/path/to/tp")
(require 'tp)
;; 用统一的 API 设置属性(返回一个新的带属性字符串)
(tp-set "hello" 'face 'bold)
;; => #("hello" 0 5 (face bold))
;; 在缓冲区区域上堆叠属性层
(define-tp spotlight () '(face (:background "yellow")))
(with-temp-buffer
(insert "Hello World")
(tp-push-layer 1 6 'spotlight)
(tp-layer-top 1 6))
;; => spotlight
;; 响应式:文本属性跟随变量变化
(defvar accent-color "red")
(define-tp accent ()
:props '(face (:foreground $accent-color)))
(with-temp-buffer
(insert "Hello")
(tp-push-layer 1 6 'accent)
(setq accent-color "blue") ; 文本自动更新!
(tp-at 1 'face))
;; => (:foreground "blue")
```
---
## 概述
**tp.el** 是一个全面增强 Emacs 文本属性操作的库。它不仅仅是对原生文本属性 API`put-text-property`、`get-text-property`)的简单封装,更提供了许多**原生函数所不具备的功能拓展**。tp.el 在以下方面进行了创新:
自 0.2.0 起,本库被组织为一组分层模块(`tp-core`、`tp-reactive`、`tp-layer`、`tp-ops`、`tp-search`、`tp-render`、`tp-stack`、`tp-palette`、`tp-builtins`),由伞形文件 `tp.el` 统一加载 — `(require 'tp)` 仍会加载全部模块,对用户没有任何变化。模块一览见[安装](#安装)。
### 核心创新
1. **统一的 API 参数规范**:所有函数支持多种灵活的调用方式,同时适用于字符串和缓冲区
@ -167,13 +206,13 @@
- ✅ **路径式访问**:通过路径语法访问深层嵌套的属性值
```elisp
;; 获取嵌套属性
(tp-get str 'face :underline :style) ; => wave
;; 获取嵌套属性tp-get 返回 (START END VALUE) 区间列表)
(tp-get str 'face :underline :style) ; => ((0 5 wave))
(tp-at 5 '(face :box :color)) ; => "blue"
;; 获取多个嵌套键
(tp-get str 'face :underline '(:color :style))
;; => ((:color "green" :style wave))
;; => ((0 5 (:color "green" :style wave)))
```
- ✅ **子属性删除**:精确移除嵌套属性中的特定键
```elisp
@ -190,7 +229,8 @@
'face 'bold
'face '(:background "green")
'face '(:foreground "red"))
;; 结果: face 是 ((:background "green" :foreground "red") bold)
;; 结果: face 是 ((:foreground "red") (:background "green") bold)
;; (各条目堆叠为一个 face 列表,最新的在前)
;; 同一子属性后面的覆盖前面的
(tp-set "emacs"
@ -274,15 +314,14 @@
;; 之后只需改变变量 - 文本自动更新!
(setq my-color "blue") ;; 所有 my-highlight 层的文本自动变成蓝色!
;; 高级响应式示例(使用 define-tp
;; 对于需要 :data、:compute、:watch 等高级特性的场景,
;; 可以使用 define-tp
;; 使用 :data、:compute、:watch 的高级示例
;; (注意:参数列表 () 是必需的,且各关键字的值必须加引号)
(define-tp full-name-layer ()
:props '(help-echo $full-name face (:foreground $name-color))
:data '((first-name . "John") (last-name . "Doe")) ;; 带初始值
:data '((first-name . "John") (last-name . "Doe") (name-color . "purple"))
:compute '((full-name (lambda () (concat first-name " " last-name))))
:watch '((first-name (lambda (new old layer)
(message "名字从 %s 改为 %s" old new)))))
(message "名字从 %s 改为 %s" old new)))))
```
### 增强的搜索与导航
@ -297,16 +336,19 @@
(tp-search my-string 'marker) ; => ((0 5 t) (12 17 t))
;; 将所有标记文本转为大写
(tp-search-map #'upcase my-string 'marker)
(tp-search-map #'upcase 'marker nil my-string)
```
## 系统要求
- **Emacs 28.1+**(使用 `object-intervals` 函数)
- **dash.el**(列表操作工具库)
- **dash.el 2.19.1+**(列表操作工具库)
## 安装
本库由 `tp-*.el` 模块家族加上伞形文件 `tp.el` 组成。安装即把目录加入
`load-path` 并 require 伞形文件,它会加载全部模块:
```elisp
;; 添加到 load-path
(add-to-list 'load-path "/path/to/tp")
@ -320,6 +362,23 @@
:load-path "/path/to/tp")
```
各模块及其职责:
| 模块 | 职责 |
|---|---|
| `tp-core.el` | 区间、plist/face 合并引擎、调试日志、`$var` 工具 |
| `tp-reactive.el` | 响应式依赖注册表、变量监视器、批量更新队列 |
| `tp-layer.el` | `define-tp` / `define-tps`、属性层注册表与解析 |
| `tp-ops.el` | `tp-set` / `tp-reset` / `tp-add` / `tp-get` / `tp-at` / `tp-remove` / `tp-clear` |
| `tp-search.el` | `tp-match-*`、`tp-regexp-*`、`tp-search`、导航 |
| `tp-render.el` | 响应式重渲染引擎 |
| `tp-stack.el` | 属性层栈操作push/pop/移动/合并/扁平化/... |
| `tp-palette.el` | 亮色/暗色调色板数据 |
| `tp-builtins.el` | 内置属性层、调色板画廊、display-buffer 辅助工具 |
项目附带 `Makefile``make test` 运行所有 ERT 测试套件,`make compile`
字节编译各模块,`make clean` 清除编译产物。
---
## API 参考
@ -336,6 +395,7 @@ tp.el 所有函数按类别组织的完整概览:
| [`tp-add`](#tp-add---添加合并属性) | 添加/合并属性,支持深度合并 |
| [`tp-get`](#tp-get---获取属性值) | 从范围或字符串获取属性值 |
| [`tp-at`](#tp-at---获取位置属性) | 获取单个位置的属性值 |
| [`tp-member`](#tp-member---判断位置属性是否存在) | 类似 `tp-at`,但能区分"存在且值为 nil"与"不存在" |
| [`tp-remove`](#tp-remove---移除属性) | 移除属性或子属性 |
| [`tp-clear`](#tp-clear---清除所有属性) | 清除区域中的所有文本属性 |
@ -422,6 +482,17 @@ tp.el 所有函数按类别组织的完整概览:
| [`tp-intervals-map`](#tp-intervals-map---对区间应用函数) | 对区域中的所有区间应用函数 |
| [`tp-plist`](#tp-plist---获取区域中的所有属性) | 获取区域中存在的所有属性 |
| [`tp-empty-p`](#tp-empty-p---检查对象是否有属性) | 检查对象是否没有文本属性 |
| [`tp-with-current-buffer`](#tp-with-current-buffer--tp-pop-to-buffer--tp-switch-to-buffer) | 在绑定 `inhibit-read-only` 的情况下在缓冲区中执行 body |
| [`tp-pop-to-buffer`](#tp-with-current-buffer--tp-pop-to-buffer--tp-switch-to-buffer) | 填充缓冲区、设为只读并通过 `pop-to-buffer` 显示 |
| [`tp-switch-to-buffer`](#tp-with-current-buffer--tp-pop-to-buffer--tp-switch-to-buffer) | 填充缓冲区、设为只读并通过 `switch-to-buffer` 显示 |
#### 调色板函数
| 函数 | 描述 |
|------|------|
| [`tp-palette-alist`](#调色板系统) | 具名调色板注册表(变量) |
| [`define-tp-palette`](#调色板系统) | 注册或更新一个具名调色板 |
| [`tp-palette-show`](#调色板系统) | 展示所有已注册调色板的画廊 |
| [`tp-parse-color`](#调色板系统) | 按当前亮色/暗色主题解析颜色规格 |
---
@ -489,8 +560,8 @@ LAYER-NAME 可以是通过 `define-tp` 定义的自定义文本属性名称或
(let ((my-buffer (generate-new-buffer "*test*")))
(with-current-buffer my-buffer
(insert "Hello World"))
(tp-set 1 10 '(face italic) my-buffer)
(kill-buffer my-buffer))
(prog1 (tp-set 1 10 '(face italic) my-buffer)
(kill-buffer my-buffer)))
;; => (1 . 10)
;; 在字符串区域设置属性0 索引)- 修改原始字符串
@ -514,14 +585,15 @@ LAYER-NAME 可以是通过 `define-tp` 定义的自定义文本属性名称或
:props '(face (:foreground $my-color))
:data '((my-color . "blue")))
(tp-set " " 'my-style)
;; => #(" " 0 1 (tp-name my-style face (:foreground "blue") ...))
;; => #(" " 0 1 (face (:foreground "blue") tp-name my-style))
;; 单次调用中合并多个 face重复属性自动合并
(tp-set "emacs"
'face 'bold
'face '(:background "green")
'face '(:foreground "red"))
;; => 三个 face 合并为一个: ((:background "green" :foreground "red") bold)
;; => face 是 ((:foreground "red") (:background "green") bold)
;; (各条目堆叠为一个 face 列表,最新的在前)
;; 同一子属性后面的值覆盖前面的
(tp-set "emacs"
@ -800,6 +872,34 @@ LAYER-NAME 可以是通过 `define-tp` 定义的自定义文本属性名称或
---
#### `tp-member` - 判断位置属性是否存在
```elisp
(tp-member POS PROPERTY &optional OBJECT)
```
类似 `tp-at`,但当 PROPERTY 在 POS 处存在时返回 `(PROPERTY VALUE)` 列表,不存在时返回 nil。由此可以区分"属性存在且值为 nil"与"属性完全不存在"(类似 `plist-member`)。
**示例:**
```elisp
;; 存在且值为 nil vs. 不存在
(let ((str (copy-sequence "Hello")))
(tp-set 0 5 '(face nil) str)
(list (tp-member 0 'face str) ; 存在,值为 nil
(tp-member 0 'display str))) ; 不存在
;; => ((face nil) nil)
;; 在缓冲区中
(with-temp-buffer
(insert "Hello")
(tp-set 1 6 '(face bold))
(tp-member 1 'face))
;; => (face bold)
```
---
#### `tp-remove` - 移除属性
从区域或整个字符串中移除属性或嵌套子属性。
@ -868,7 +968,7 @@ LAYER-NAME 可以是通过 `define-tp` 定义的自定义文本属性名称或
;; 从字符串移除嵌套键
(let ((original (propertize "Hello" 'face '(:underline (:style wave :color "blue")))))
(let ((result (tp-remove original 'face :underline '(:style))))
(get-text-property 0 '(face :underline) result)))
(tp-at 0 '(face :underline) result)))
;; => (:color "blue")
```
@ -936,7 +1036,8 @@ OBJECT 是缓冲区或字符串nil 表示当前缓冲区。
(with-temp-buffer
(insert "Hello world, Hello again")
(tp-match-set '("world" "Hello") '(face bold)))
;; => ((1 . 6) (7 . 12) (14 . 19)) ; 匹配 "Hello", "world", "Hello"
;; => ((7 . 12) (1 . 6) (14 . 19)) ; 结果按模式分组:
;; 先是 "world" 的区域,再是每个 "Hello",顺序与模式列表一致
;; 在字符串上使用多个模式
(tp-match-set '("Hello" "world") '(face bold) "Hello world")
@ -1058,13 +1159,15 @@ OBJECT 是缓冲区或字符串nil 表示当前缓冲区。
(list (tp-at 5 'face) (tp-at 13 'face)))
;; => (font-lock-number-face font-lock-number-face)
;; 在字符串上
;; 在字符串上(默认受 `case-fold-search' 影响,"Hello" 也会匹配;
;; 需要区分大小写时请将其 let 绑定为 nil
(tp-regexp-set "[A-Z]+" '(face bold) "Hello WORLD")
;; => #("Hello WORLD" 6 11 (face bold))
;; => #("Hello WORLD" 0 5 (face bold) 6 11 (face bold))
;; 多个正则 - 同时匹配数字和大写字母
;; (忽略大小写时 "abc" 也匹配 "[A-Z]+"
(tp-regexp-set '("[0-9]+" "[A-Z]+") '(face bold) "abc 123 XYZ")
;; => #("abc 123 XYZ" 4 7 (face bold) 8 11 (face bold))
;; => #("abc 123 XYZ" 0 3 (face bold) 4 7 (face bold) 8 11 (face bold))
;; 使用已定义的层名称
(define-tp number-style ()
@ -1101,12 +1204,12 @@ OBJECT 是缓冲区或字符串nil 表示当前缓冲区。
(tp-at 5))
;; => (face bold) ; help-echo 被移除
;; 在字符串上
;; 在字符串上 - 返回新字符串,原字符串保持不变
(let ((str (copy-sequence "abc 123 def")))
(tp-set 4 7 '(help-echo "original") str)
(tp-regexp-reset "[0-9]+" '(face italic) str)
(tp-at 4 str))
;; => (face italic)
(let ((result (tp-regexp-reset "[0-9]+" '(face italic) str)))
(list (tp-at 4 result) (tp-at 4 str))))
;; => ((face italic) (help-echo "original"))
;; 使用已定义的层名称
(define-tp code-number ()
@ -1143,12 +1246,12 @@ OBJECT 是缓冲区或字符串nil 表示当前缓冲区。
(tp-at 5))
;; => (face bold help-echo "number")
;; 在字符串上
;; 在字符串上 - 返回新字符串,原字符串保持不变
(let ((str (copy-sequence "abc 123 def")))
(tp-set 4 7 '(help-echo "number") str)
(tp-regexp-add "[0-9]+" '(face italic) str)
(tp-at 4 str))
;; => (face italic help-echo "number")
(let ((result (tp-regexp-add "[0-9]+" '(face italic) str)))
(list (tp-at 4 result) (tp-at 4 str))))
;; => ((face italic help-echo "number") (help-echo "number"))
;; 使用已定义的层名称
(define-tp bold-underline ()
@ -1185,23 +1288,46 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
向前/向后搜索 N 次具有 PROPERTY 的文本。
- **N** 是搜索次数,默认为 1。
- **VALUE** 是可选的匹配值。
- **VALUE** 在缓冲区中与属性值做 `equal` 匹配。
因此传入 nil 会匹配下一段 PROPERTY *不存在*(值为 nil的区段
要查找带属性的区域,请显式传入属性值。
- **`tp-backward``tp-forward` 对称**:相同的 equal 匹配语义,
方向相反。
- **OBJECT** 可以是缓冲区或字符串nil 默认为当前缓冲区。
- 对于缓冲区,返回最后一次成功搜索的 prop-match 对象。
- 对于字符串,返回所有匹配的 (START END VALUE) 列表。
- 对于字符串,返回 PROPERTY 存在的各区段的 (START END VALUE) 列表;
VALUE 为 nil 表示匹配任意值。`tp-backward` 按从末尾到开头的顺序返回。
**示例:**
```elisp
;; 查找下一个具有 'marker 属性的文本
;; 查找下一个 'marker 等于 t 的文本
(with-temp-buffer
(insert "Hello World Test")
(tp-set 7 12 '(marker t))
(goto-char 1)
(let ((match (tp-forward 'marker t)))
(when match
(prop-match-beginning match))))
;; => 7
;; VALUE 为 nil 时 equal 匹配 nil - 即匹配没有该属性的区段
(with-temp-buffer
(insert "Hello World Test")
(tp-set 7 12 '(marker t))
(goto-char 1)
(let ((match (tp-forward 'marker)))
(when match
(prop-match-beginning match))))
;; => 7
(list (prop-match-beginning match) (prop-match-end match))))
;; => (1 7) ; marker 不存在的区段
;; backward 与 forward 对称:相同的值匹配,方向相反
(with-temp-buffer
(insert "Hello World Test")
(tp-set 7 12 '(marker t))
(goto-char (point-max))
(let ((match (tp-backward 'marker t)))
(list (prop-match-beginning match) (prop-match-end match))))
;; => (7 12)
;; 查找下一个 'type 等于 'heading 的文本
(with-temp-buffer
@ -1232,7 +1358,8 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
在 OBJECT 的 START 到 END 范围内,向前/向后搜索匹配 PROPERTY 属性(值为 VALUE的部分**仅对最后一次匹配执行 FUNCTION 函数**。
- **FUNCTION** 的参数是 `(TEXT &optional START END)`,其中 TEXT 是此次匹配到的文本START 和 END 为开始结束的位置。FUNCTION 的返回值将替换字符串或缓冲区中的匹配文本。
- **FUNCTION** 的参数是 `(TEXT &optional START END IDX)`,其中 TEXT 是此次匹配到的文本START 和 END 为开始结束的位置IDX 是从 0 开始的匹配索引。FUNCTION 会按其实际接受的参数个数被调用。当 FUNCTION 返回字符串时,它将替换字符串或缓冲区中的匹配文本。
- **在缓冲区中替换文本可以改变长度**(先删除匹配文本,再插入替换文本)。**字符串无法就地改变长度**:长度不同的替换会发出错误信号;长度相同的替换会就地应用。
- **PROPERTY** 是要搜索的文本属性。
- **VALUE** 为 nil 时,表示搜索 PROPERTY 属性,不用匹配值。
- **OBJECT** 默认是当前 buffer 或指定的字符串或指定的 buffer。
@ -1339,7 +1466,11 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
- TEXT 是此次匹配到的文本
- START 和 END 为开始结束的位置
- IDX 是遍历中的当前从 0 开始的索引
FUNCTION 的返回值将替换字符串或缓冲区中的匹配文本。
FUNCTION 会按其实际接受的参数个数被调用。当 FUNCTION 返回字符串时,
它将替换字符串或缓冲区中的匹配文本。
- **在缓冲区中替换文本可以改变长度**(先删除匹配文本,再插入替换文本)。
**字符串无法就地改变长度**:长度不同的替换会发出错误信号;
长度相同的替换会就地应用。
- **PROPERTY** 是要搜索的文本属性。
- **VALUE** 为 nil 时,表示搜索 PROPERTY 属性,不用匹配值。
- **OBJECT** 默认是当前 buffer 或指定的字符串或指定的 buffer。
@ -1418,8 +1549,8 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
'face 'bold
'face '(:background "green")
'face '(:foreground "red"))
;; 结果: face 是 ((:background "green" :foreground "red") bold)
;; 三个 face 属性被智能合并
;; 结果: face 是 ((:foreground "red") (:background "green") bold)
;; 三个 face 属性堆叠为一个 face 列表,最新的在前
;; 同一子属性后面的覆盖前面的
(tp-set "emacs"
@ -1477,6 +1608,7 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
### 属性层概念
```
┌─────────────────────────────┐
│ 顶层(可见) │ ← idx=0你看到的
├─────────────────────────────┤
@ -1484,6 +1616,7 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
├─────────────────────────────┤
│ 底层(隐藏) │ ← idx=-1被保留
└─────────────────────────────┘
```
### 属性层定义
@ -1491,7 +1624,7 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
##### `define-tp` - 定义单个自定义文本属性(层)
定义自定义文本属性,名称无需单引号引用。支持三种格式:
定义自定义文本属性,名称无需单引号引用。**所有格式中参数列表都是必需的**:无参数层(包括响应式关键字格式)用 `()`,参数化层用 `(ARG)`支持三种格式:
**格式一 - 无参数(空参数列表,简单属性):**
@ -1519,9 +1652,9 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
```elisp
(define-tp my-reactive-layer ()
:props '(face (:foreground $my-color))
:data '((my-color . "red"))
:compute '((full-name (lambda () (concat first-name " " last-name))))
:props '(face (:foreground $my-color) help-echo $status-note)
:data '((my-color . "red") (status . "active"))
:compute '((status-note (lambda () (concat "status: " status))))
:watch '((my-color (lambda (new old layer) (message "Color changed!"))))
:transform (lambda (text) (upcase text)))
@ -1539,9 +1672,12 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
- **:watch** - 监听器列表,变量改变时执行回调
- **:transform** - 转换函数,在显示 `tp-text` 值之前对其进行处理
注意:`:props`、`:data`、`:compute` 和 `:watch` 的值必须**加引号**
(它们在层定义时会被求值);`:transform` 接受一个函数。
##### `define-tps` - 定义自定义文本属性组(层组)
定义多个相关的自定义文本属性,名称无需单引号引用。属性组中定义的文本属性可以单独使用,也可以使用组名称来设置多层。
定义多个相关的自定义文本属性,名称无需单引号引用。`define-tp` 一样,**参数列表是必需的**:无参数层组用 `()`,参数化层组用 `(ARG)`属性组中定义的文本属性可以单独使用,也可以使用组名称来设置多层。
**格式一 - 无参数(空参数列表):**
@ -1636,8 +1772,7 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
```elisp
;; 先定义状态层,然后将它们组合成层组
(progn
(setq tp-layer-alist nil)
(setq tp-layer-groups nil)
(tp-layer-reset)
(define-tp highlight ()
'(face (:background "yellow" :foreground "black")))
(define-tp error ()
@ -1651,20 +1786,18 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
;; 使用命名层定义层组
(progn
(setq tp-layer-alist nil)
(setq tp-layer-groups nil)
(tp-layer-reset)
(define-tps moon-phases ()
'("new" . (display "🌑"))
'("waxing-crescent" . (display "🌒"))
'("first-quarter" . (display "🌓"))
'("full" . (display "🌕")))
(tp-layer-props 'moon-phases-full))
;; => (display "🌕" tp-name moon-phases-full)
;; => (display "🌕")
;; 参数化层组,引用其他已定义的层
(progn
(setq tp-layer-alist nil)
(setq tp-layer-groups nil)
(tp-layer-reset)
(define-tp tp-test-l1 (color)
`(face (:foreground ,color)))
(define-tp tp-test-l2 (color)
@ -1686,27 +1819,33 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
#### `tp-layer-props` / `tp-group-props`
```elisp
(tp-layer-props LAYER-NAME)
(tp-group-props GROUP-NAME)
(tp-layer-props LAYER-NAME &optional INCLUDE-TP-NAME)
(tp-group-props GROUP-NAME &optional INCLUDE-TP-NAME)
```
获取属性层或属性层组中所有属性层的属性。
默认情况下,结果只包含属性层自身的属性。当 INCLUDE-TP-NAME 非 nil 时,
会在结果末尾追加一个 `tp-name LAYER-NAME` 条目(属性层栈内部使用的形式)。
例外:注册了响应式依赖的属性层总是包含 `tp-name` —— 响应式引擎依靠
它定位并重新渲染这些区域。
**示例:**
```elisp
;; 获取属性层属性
;; 获取属性层属性(默认不含 tp-name
(progn
(setq tp-layer-alist nil)
(tp-layer-reset)
(define-tp my-layer ()
'(face bold help-echo "tip"))
(tp-layer-props 'my-layer))
;; => (face bold help-echo "tip" tp-name my-layer)
(list (tp-layer-props 'my-layer)
(tp-layer-props 'my-layer t)))
;; => ((face bold help-echo "tip")
;; (face bold help-echo "tip" tp-name my-layer))
;; 获取属性层组属性
(progn
(setq tp-layer-alist nil)
(setq tp-layer-groups nil)
(tp-layer-reset)
(define-tp layer1 ()
'(face bold))
(define-tp layer2 ()
@ -1733,7 +1872,7 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
```elisp
;; 取消定义属性层
(progn
(setq tp-layer-alist nil)
(tp-layer-reset)
(define-tp temp-layer ()
'(face bold))
(tp-undefine-layer 'temp-layer)
@ -1742,10 +1881,10 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
;; 取消定义属性层组
(progn
(setq tp-layer-alist nil)
(setq tp-layer-groups nil)
(tp-layer-reset)
(define-tp l1 () '(face bold))
(define-tps my-group 'l1)
(define-tps my-group ()
'l1)
(tp-undefine-group 'my-group)
(assoc 'my-group tp-layer-groups))
;; => nil
@ -1795,7 +1934,7 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
(tp-reactive-reset)
;; 层仍然存在,但改变 my-reactive-color 不再更新它
(tp-layer-props 'reactive-layer))
;; => (face (:foreground "red") tp-name reactive-layer)
;; => (face (:foreground "red"))
```
---
@ -1818,6 +1957,16 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
- `IDX = -1`:底部
- 其他值在该位置插入
LAYER 接受以下几种形式:
- 用 `define-tp` 定义的层名:`'highlight`
- 内联属性 plist无需 `define-tp``'(face bold help-echo "tip")`
- 层名列表(第一个层名位于顶部):`'(layer-a layer-b)`
- 参数化层调用:`'(tp-color "red")`
**栈模型:**只有顶层的属性是可见的文本属性;下层被保存在
`tp-layers` 文本属性中,直到被上移、轮换或扁平化。
**示例:**
```elisp
@ -1855,6 +2004,35 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
(tp-put-layer 1 10 'info -1)
(tp-layer-top 1 10)))
;; => base ; info 在底部base 可见
;; 内联 plist - 无需 define-tp
(with-temp-buffer
(insert "Hello World")
(tp-put-layer 1 10 '(face bold help-echo "tip") 0)
(list (tp-at 1 'face) (tp-at 1 'help-echo)))
;; => (bold "tip")
;; 层名列表 - layer-a 位于顶部
(progn
(tp-layer-reset)
(define-tp layer-a () '(face bold))
(define-tp layer-b () '(face italic))
(with-temp-buffer
(insert "Hello World")
(tp-put-layer 1 10 '(layer-a layer-b) 0)
(list (tp-at 1 'face) (tp-layer-list 1 10))))
;; => (bold (layer-a layer-b))
;; 参数化层调用
(progn
(tp-layer-reset)
(define-tp tp-color (color)
`(face (:foreground ,color)))
(with-temp-buffer
(insert "Hello World")
(tp-put-layer 1 10 '(tp-color "red") 0)
(tp-at 1 'face)))
;; => (:foreground "red")
```
---
@ -1896,6 +2074,21 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
(tp-push-layer 1 10 'highlight)
(tp-at 1 'tp-name)))
;; => highlight
;; 顶层的属性是可见的;下层保存在 `tp-layers' 中等待
(progn
(tp-layer-reset)
(define-tp base () '(face default))
(define-tp highlight () '(face (:background "yellow")))
(with-temp-buffer
(insert "Hello World")
(tp-push-layer 1 10 'base)
(tp-push-layer 1 10 'highlight)
(list :face (tp-at 1 'face)
:top (tp-layer-top 1 10)
:layers (tp-layer-list 1 10)
:hidden (length (tp-at 1 'tp-layers)))))
;; => (:face (:background "yellow") :top highlight :layers (highlight base) :hidden 1)
```
---
@ -2468,7 +2661,10 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
从 OBJECT 中获取 START 到 END 之间的所有文本属性区间。
- 返回每个区间的 (START END PROPERTIES) 列表。
- 返回每个区间的 (START END PROPERTIES) 列表,包括没有属性的
间隙区间,其 PROPERTIES 为 nil。
- 对于缓冲区输入START 和 END 是从 1 开始的缓冲区位置,但返回的位置是
**相对于 START 的 0 基偏移量**。对于字符串,位置是绝对的 0 基索引。
- 使用 `object-intervals`(需要 Emacs 28.1+)。
- OBJECT 可以是缓冲区或字符串nil 默认为当前缓冲区。
@ -2480,7 +2676,8 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
(tp-set 1 6 '(face bold))
(tp-set 7 12 '(face italic))
(tp-intervals 1 12))
;; => ((0 5 (face bold)) (6 11 (face italic)))
;; => ((0 5 (face bold)) (5 6 nil) (6 11 (face italic)))
;; 位置是相对 START 的偏移量;(5 6 nil) 是无属性的间隙
```
---
@ -2494,8 +2691,9 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
对 OBJECT 中 START 到 END 之间的所有区间应用 FUNCTION。
- FUNCTION 接收四个参数interval-start、interval-end、top-props可见层属性和 below-props-lst隐藏层列表
- 没有属性的区间也会被访问,此时 top-props 为 nil位置遵循与 `tp-intervals` 相同的偏移量约定)。
- OBJECT 可以是缓冲区或字符串nil 默认为当前缓冲区。
- 返回函数结果列表nil 被移除)。
- 返回函数结果列表nil 结果被移除)。
**示例:**
@ -2508,7 +2706,7 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
(lambda (start end props belows)
(list start end (plist-get props 'face)))
1 12))
;; => ((0 5 bold) (6 11 italic))
;; => ((0 5 bold) (5 6 nil) (6 11 italic))
```
---
@ -2553,7 +2751,8 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
获取区域或字符串中存在的所有属性的属性列表。
- 返回包含范围内找到的所有属性的 plist。
- 返回将范围内找到的属性合并成的单个 plist当同一属性出现在多个
区间中时,靠后区间的值胜出。
- OBJECT 在区域形式中默认为当前缓冲区。
**示例:**
@ -2564,7 +2763,7 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
(tp-set 1 6 '(face bold help-echo "Tip"))
(tp-set 7 12 '(face italic))
(tp-plist 1 12))
;; => (face bold help-echo "Tip" face italic)
;; => (help-echo "Tip" face italic) ; 靠后区间的 face 胜出
```
---
@ -2584,13 +2783,79 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
```elisp
(tp-empty-p "plain text") ; => t
(let ((str (copy-sequence "text")))
(tp-set str 'face 'bold)
(tp-empty-p str)) ; => nil
;; 整个字符串形式的 tp-set 是非破坏性的:原始字符串保持无属性
(let* ((str "text")
(new (tp-set str 'face 'bold)))
(list (tp-empty-p str) (tp-empty-p new)))
;; => (t nil)
```
---
#### `tp-with-current-buffer` / `tp-pop-to-buffer` / `tp-switch-to-buffer`
```elisp
(tp-with-current-buffer BUFFER-OR-NAME BODY...)
(tp-pop-to-buffer BUFFER-OR-NAME BODY...)
(tp-switch-to-buffer BUFFER-OR-NAME BODY...)
```
用于操作和展示带属性内容的便捷宏:
- **`tp-with-current-buffer`** 在 BUFFER-OR-NAME 中求值 BODY并将
`inhibit-read-only` 绑定为 t。适合修改只读的展示缓冲区。
- **`tp-pop-to-buffer`** 创建或复用BUFFER-OR-NAME清空它在其中
求值 BODY然后将其设为只读并通过 `pop-to-buffer` 显示。在显示的
缓冲区中按 `q` 可退出其窗口。
- **`tp-switch-to-buffer`** 与上者相同,但通过 `switch-to-buffer`
显示缓冲区。
**示例:**
```elisp
(tp-pop-to-buffer "*tp-demo*"
(insert (tp-set "Important" 'face '(:foreground "red" :weight bold))
" message\n"))
;; 显示 *tp-demo* 及其中的带属性文本;按 `q' 退出窗口
```
---
### 调色板系统
`tp-palette.el` 内置了一组具名调色板,每个调色板包含独立的亮色模式和
暗色模式颜色;`tp-builtins.el` 通过内置的参数化 `tp-palette` 层将它们
暴露出来(如 `(tp-set "emacs" 'tp-palette 'info)`)。
- **`tp-palette-alist`**(变量)— `(NAME . PLIST)` 形式的调色板定义
alist调色板查询的唯一数据源。每个 PLIST 将 `:fg`、`:bg` 和
`:border` 映射到颜色。
- **`define-tp-palette`** — 注册(或更新)一个调色板:
```elisp
(define-tp-palette my-brand
:fg ("#0969da" . "#58a6ff") ; ("亮色" . "暗色")
:bg ("#ddf4ff" . "#1f3d5c"))
```
- **`tp-palette-show`** — 交互式命令,显示一个画廊缓冲区,展示每个已
注册调色板及其 `-fg` / `-bg` / `-fbg` / `-border` 变体(按 `q` 退出)。
- **`tp-parse-color`** — 按当前主题解析颜色规格。接受普通颜色字符串、
`("亮色" . "暗色")` cons任意一侧可以为 nil
`(:light L :dark D)` plist
```elisp
(tp-parse-color "red") ; => "red"
(tp-parse-color '("white" . "black")) ; => 亮色主题下为 "white"
; 暗色主题下为 "black"
```
注意:`tp-layer-reset` 会清除所有属性层定义,包括 `tp-palette` 这样的
内置属性层。
---
## 实用示例
### 多属性层语法高亮
@ -2634,7 +2899,7 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
(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)
(define-tps task-status () 'status-todo 'status-progress 'status-done)
;; 检查组是否已定义
(length (tp-group-props 'task-status)))
;; => 3
@ -2655,7 +2920,7 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
(define-tp temp-highlight ()
'(face (:background "yellow")))
(tp-layer-props 'temp-highlight))
;; => (face (:background "yellow") tp-name temp-highlight)
;; => (face (:background "yellow"))
;; 闪烁函数(用于实际缓冲区)
(defun flash-region (start end)
@ -2791,9 +3056,9 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
(define-tp monitored-layer ()
:props '(face (:foreground $status-color))
:watch '((status-color
(lambda (new-val old-val layer-name)
(message "层 %s: 颜色从 %s 改为 %s"
layer-name old-val new-val)))))
(lambda (new-val old-val layer-name)
(message "层 %s: 颜色从 %s 改为 %s"
layer-name old-val new-val)))))
(setq status-color "red")
;; 消息: "层 monitored-layer: 颜色从 nil 改为 red"
@ -2872,7 +3137,7 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
层组也可以使用响应式特性:
```elisp
(define-tps status-indicators
(define-tps status-indicators ()
'("success" :props (face (:foreground $success-color))
:data ((success-color . "green")))
'("warning" :props (face (:foreground $warning-color))
@ -2957,20 +3222,16 @@ tp.el 提供调试模式来帮助理解响应式更新流程:
(defvar theme-bg "black")
(defvar theme-accent "cyan")
;; 定义主题感知层
;; 定义主题感知层 - 每个层都引用主题变量
(define-tp code-text ()
:props '(face (:foreground $theme-fg :background $theme-bg)))
(define-tp code-keyword ()
:props '(face (:foreground $theme-accent :weight bold)))
(define-tp code-comment ()
:props '(face (:foreground "gray" :slant italic)))
(define-tp code-string ()
:props '(face (:foreground "green")))
;; 将层应用到代码
;; 将层应用到当前缓冲区中的代码
(tp-set (point-min) (point-max) 'code-text)
(tp-match-set '("defun" "defvar" "let" "if" "when") 'code-keyword)
(tp-regexp-set ";.*$" 'code-comment)
(tp-regexp-set "\"[^\"]*\"" 'code-string)
;; 切换到浅色主题 - 只需改变变量!
(defun switch-to-light-theme ()
@ -2985,13 +3246,16 @@ tp.el 提供调试模式来帮助理解响应式更新流程:
(setq theme-fg "white")
(setq theme-bg "black")
(setq theme-accent "cyan"))
;; 调用 `switch-to-light-theme' 后,关键字变为蓝色,其余代码变为
;; 白底黑字 - 每个区域都会自动重新渲染
```
---
## 许可证
GNU 通用公共许可证 v2 或更高版本
GNU 通用公共许可证 v3 或更高版本。参见 [LICENSE](LICENSE) 文件
---

View File

@ -1,353 +1,495 @@
# tp.el 代码架构文档
# tp 代码架构文档
本文档描述 tp.el 的函数调用层次结构,从底层基础功能到上层 API 的分层组织。
本文档描述 tp 库的模块分层结构与函数调用层次,从底层基础模块到上层功能模块的分层组织。
自 0.2.0 起,原来的单文件 tp.el 已拆分为九个分层模块,`tp.el` 只作为总入口(`(require 'tp)` 依次加载全部模块,用户接口不变)。各模块的变更缘由见 [CHANGELOG.md](../CHANGELOG.md)。
## 目录
- [架构概述](#架构概述)
- [分层结构](#分层结构)
- [第一层:基础工具函数](#第一层基础工具函数)
- [第二层:核心属性操作](#第二层核心属性操作)
- [第三层:属性层系统](#第三层属性层系统)
- [第四层:响应式系统](#第四层响应式系统)
- [第五层:高级 API](#第五层高级-api)
- [模块分层](#模块分层)
- [tp-core.el基础工具](#tp-coreel基础工具)
- [tp-reactive.el响应式基础设施](#tp-reactiveel响应式基础设施)
- [tp-layer.el层定义与解析](#tp-layerel层定义与解析)
- [tp-ops.el核心属性操作](#tp-opsel核心属性操作)
- [tp-search.el模式匹配与搜索](#tp-searchel模式匹配与搜索)
- [tp-render.el响应式渲染引擎](#tp-renderel响应式渲染引擎)
- [tp-stack.el属性层栈操作](#tp-stackel属性层栈操作)
- [tp-palette.el调色板数据](#tp-paletteel调色板数据)
- [tp-builtins.el内置层与辅助工具](#tp-builtinsel内置层与辅助工具)
- [钩子变量:唯一许可的反向调用](#钩子变量唯一许可的反向调用)
- [函数调用关系图](#函数调用关系图)
- [设计原则](#设计原则)
---
## 架构概述
tp.el 采用分层架构设计,每一层建立在下层功能之上
tp 采用严格的线性分层:**每个模块只允许 `require` 并调用排在它前面的模块**,字节编译器强制检查这一依赖顺序。加载顺序即依赖顺序
```
┌─────────────────────────────────────────────────────────────────┐
│ 第五层:高级 API │
│ tp-match-set, tp-regexp-set, tp-forward-do, tp-search-map │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 第四层:响应式系统 │
│ define-tp, define-tps, tp--reactive-variable-watcher, │
│ tp--update-layer-regions, tp--register-reactive-deps │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 第三层:属性层系统 │
│ tp-push-layer, tp-pop-layer, tp-rotate-layer, │
│ tp-layer-list, tp--build-layer-props │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 第二层:核心属性操作 │
│ tp-set, tp-reset, tp-add, tp-get, tp-at, tp-remove, tp-clear │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 第一层:基础工具函数 │
│ tp--parse-args, tp--deep-merge-plist, tp--get-nested, │
│ tp-intervals, tp-empty-p │
└─────────────────────────────────────────────────────────────────┘
tp-core → tp-reactive → tp-layer → tp-ops → tp-search
→ tp-render → tp-stack → tp-palette → tp-builtins
```
```
┌────────────────────────────────────────────────────────────────┐
│ tp.el —— 总入口,按序 require 全部模块 │
└────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────┐
│ tp-builtins.el 内置层tp-link, tp-space, tp-headline …)、 │
│ tp-palette-show、显示缓冲辅助宏 │
├────────────────────────────────────────────────────────────────┤
│ tp-palette.el 明/暗主题调色板数据、tp-parse-color │
├────────────────────────────────────────────────────────────────┤
│ tp-stack.el 层栈操作push/pop/move/merge/flatten …) │
├────────────────────────────────────────────────────────────────┤
│ tp-render.el 响应式重渲染引擎 ──┐ │
├──────────────────────────────────── │ ─────────────────────────┤
│ tp-search.el tp-match-*/tp-regexp-*、tp-search、导航 │
├──────────────────────────────────── │ ─────────────────────────┤
│ tp-ops.el tp-set/reset/add/get/at/remove/clear │
│ ◁╌╌ tp--tp-text-handler-function ╌╌╌╌┤ │
├──────────────────────────────────── │ ─────────────────────────┤
│ tp-layer.el define-tp/define-tps、层注册表与解析 │
│ ◁╌╌ tp--layer-refresh-function ╌╌╌╌╌╌┤ │
├──────────────────────────────────── │ ─────────────────────────┤
│ tp-reactive.el 响应式依赖注册表、变量监听、批量队列 │
│ ◁╌╌ tp--reactive-update-function ╌╌╌╌┤ │
│ ◁╌╌ tp--reactive-flush-function ╌╌╌╌╌┘ │
├────────────────────────────────────────────────────────────────┤
│ tp-core.el 区间遍历、plist/face 合并引擎、 │
│ 调试日志、$var 符号工具 │
└────────────────────────────────────────────────────────────────┘
实线层级上层模块调用下层模块require 依赖)。
虚线(◁╌╌):钩子变量 —— 下层模块预留的函数变量,
由 tp-render.el 在加载时安装实现(见下文)。
```
早期文档把"响应式系统"画在高级 API 之下、却又让它向上调用 `tp-search-map`,与自身的分层原则矛盾。现在这一矛盾已在代码层面消除:需要向上调用的逻辑全部收拢进 `tp-render.el`(位于 `tp-search.el` 之上可以直接调用它下层模块tp-reactive、tp-layer、tp-ops通过**钩子变量**触发渲染,自身不依赖任何上层模块。
---
## 分层结构
## 模块分层
### 第一层:基础工具函数
### tp-core.el基础工具
这些是最底层的工具函数,不依赖于其他 tp.el 函数,主要提供参数解析、数据结构操作等基础能力。
最底层模块,不依赖任何其他 tp 模块,提供区间遍历、合并引擎与调试能力。
#### 区间操作
| 函数 | 描述 | 主要调用者 |
|------|------|--------|
| `tp-intervals` | 获取区域内文本属性区间列表(裁剪到 [START, END) | tp-intervals-map, tp-get |
| `tp-intervals-map` | 对区间应用函数 | 多个属性/层操作函数 |
| `tp--map-intervals` | 共享的裁剪式区间遍历引擎 | tp-intervals-map, tp-ops/tp-stack 的区域操作 |
| `tp-plist` | 获取区域中合并后的所有属性 | 用户 API |
| `tp-empty-p` | 检查对象是否没有文本属性 | 用户 API |
#### plist / face 合并引擎
| 函数 | 描述 | 主要调用者 |
|------|------|--------|
| `tp--deep-merge-plist` | 深度合并两个 plist | tp-add, tp--prepend-face 等 |
| `tp--prepend-face` | face 家族属性的合并逻辑 | tp-add, tp-match-add |
| `tp--merge-face-values` | 合并两个 face 值 | 合并引擎内部 |
| `tp--merge-duplicate-keys` | 合并 plist 中的重复键 | tp--parse-args |
| `tp--parse-face-list` | 解析 face 列表 | 合并引擎内部 |
| `tp--get-nested` | 按路径获取嵌套属性值 | tp-get, tp-at |
`tp-face-properties`(常量,`'(face font-lock-face mouse-face)`)定义参与 face 感知合并的属性家族。
#### `$var` 符号工具
| 函数 | 描述 |
|------|------|
| `tp--reactive-symbol-p` | 检查是否为 `$var` 响应式符号 |
| `tp--reactive-var-symbol` | `$var` 符号转变量符号 |
| `tp--collect-reactive-symbols` | 收集表达式中所有 `$var` 符号 |
| `tp--resolve-reactive-symbols` | 将 `$var` 解析为当前值(支持覆盖表) |
| `tp--extract-reactive-props` | 提取引用特定变量的属性 |
#### 调试工具
| 变量/函数 | 描述 |
|-----------|------|
| `tp-debug-mode` | 启用/禁用调试模式 |
| `tp-debug-echo` | 是否在 minibuffer 显示调试信息 |
| `tp-debug-log` | 记录调试信息 |
| `tp-debug-show` | 显示 *tp-debug* 缓冲区 |
| `tp-debug-clear` | 清除调试日志 |
另有辅助宏 `tp-with-current-buffer`
---
### tp-reactive.el响应式基础设施
只依赖 tp-core。维护响应式依赖注册表、变量监听器与批量更新队列**不包含任何渲染逻辑**,重渲染通过钩子变量委托给 tp-render.el。
#### 依赖注册与管理
| 函数/变量 | 描述 |
|------|------|
| `tp-reactive-deps` | 变量 → 依赖它的层及属性 的注册表 |
| `tp--register-reactive-deps` | 注册响应式依赖 |
| `tp--unregister-reactive-deps` | 取消注册依赖(含 watchers/computed/data |
| `tp--layer-has-reactive-deps-p` | 层是否有响应式依赖 |
| `tp--register-layer-watchers` / `tp--unregister-layer-watchers` | 注册/清除 `:watch` 回调 |
| `tp--register-layer-computed` / `tp--unregister-layer-computed` | 注册/清除 `:compute` 计算属性 |
| `tp--register-layer-data` / `tp--unregister-layer-data` | 注册/清除 `:data` 变量 |
| `tp--apply-initial-computed` | 计算 `:compute` 的初始值 |
| `tp--ensure-reactive-variables` | 确保 `$var` 对应的变量已定义 |
| `tp-reactive-reset` | 重置全部响应式注册表 |
#### 变量监听与批量更新
| 函数/宏 | 描述 |
|------|------|
| `tp--reactive-variable-watcher` | `add-variable-watcher` 回调;调用 `:watch` 后经 `tp--reactive-update-function` 委托重渲染 |
| `tp--invoke-layer-watchers` | 调用层的 `:watch` 回调 |
| `tp-with-batch-updates` | 批量更新宏 |
| `tp--queue-batch-update` | 将更新加入待处理队列 |
| `tp--flush-batch-updates` | 刷新队列,经 `tp--reactive-flush-function` 委托重渲染 |
钩子变量:`tp--reactive-update-function`、`tp--reactive-flush-function`(定义于此,由 tp-render.el 安装)。
---
### tp-layer.el层定义与解析
依赖 tp-core、tp-reactive。提供 `define-tp` / `define-tps` 宏、层注册表、层名解析,以及层栈的数据结构原语。
#### 层定义
| 函数/宏 | 描述 | 依赖 |
|---------|------|------|
| `define-tp` | 定义单个自定义文本属性(层) | tp--define-layer-internal |
| `define-tps` | 定义自定义文本属性组(层组);别名 `define-tp-group` | tp--define-layer-group-internal |
| `tp--define-layer-internal` | 层定义的运行时实现 | tp--parse-define-layer-args, tp--collect-reactive-symbols, tp--ensure-reactive-variables, tp--register-*, tp--layer-refresh |
| `tp--parse-define-layer-args` | 解析 `:props` / `:data` / `:compute` / `:watch` / `:transform` | - |
| `tp--parse-layer-group-element` | 解析层组元素 | tp--layer-group-element-format |
| `tp--define-layer-from-parsed` | 从解析结果定义层 | (与 define-tp 类似的依赖) |
| `tp--check-layer-cycle` | 检测循环层引用并报错 | tp--layer-expansion-stack |
| `tp--anonymous-layer-name-for` | 匿名响应式层的驻留(`equal` 的 props 复用注册项) | - |
#### 注册表与查询
| 函数/变量 | 描述 |
|------|------|
| `tp-layer-alist` / `tp-layer-groups` / `tp-layer-transforms` | 层、层组、转换函数注册表 |
| `tp--set-layer-props` / `tp--set-group-layers` | 写入注册表 |
| `tp-layer-props` / `tp-group-props` | 获取层/层组属性(`&optional INCLUDE-TP-NAME`,默认不含 `tp-name`;返回副本) |
| `tp-layer-props-with-arg` / `tp-group-props-with-arg` | 参数化层/层组的属性求值 |
| `tp-layer-parameterized-p` / `tp-group-parameterized-p` | 是否参数化 |
| `tp-layer-reset` | 重置层系统 |
| `tp-undefine-layer` / `tp-undefine-group` | 删除层/层组(含其响应式依赖与转换) |
#### 属性解析
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp--resolve-props` | 解析属性(展开层名、`$var`、注册依赖) | tp-layer-props, tp--collect-reactive-symbols, tp--resolve-reactive-symbols, tp--register-reactive-deps |
| `tp--expand-layer-in-plist` | 展开 plist 中的层名键 | tp--is-layer-name-p |
| `tp--expand-layer-to-props-list` | 层名展开为属性列表 | tp--check-layer-cycle |
#### 层栈数据结构原语
| 函数 | 描述 |
|------|------|
| `tp--normalize-layer-spec` | 规范化层规格 |
| `tp--get-layer-stack` | 获取位置的层栈 |
| `tp--build-layer-props` | 从层列表构建属性 |
| `tp--layer-stack-to-list` | 将层栈转换为列表 |
| `tp--get-layer-by-idx-or-name` | 通过索引或名称查找层 |
钩子变量:`tp--layer-refresh-function`(定义于此,由 tp-render.el 安装为 `tp--update-layer-regions``tp--layer-refresh` 是它的调用入口,层重定义后经它触发已应用区域的重渲染。
---
### tp-ops.el核心属性操作
依赖 tp-core、tp-layer。面向用户的核心属性读写函数直接调用 Emacs 原生文本属性 API。
#### 参数解析
| 函数 | 描述 | 调用者 |
|------|------|--------|
| `tp--parse-args` | 解析灵活的函数参数格式 | tp-set, tp-reset, tp-add |
| `tp--parse-layer-args` | 解析属性层操作的参数 | tp-put-layer 及其他层操作函数 |
| `tp--parse-define-layer-args` | 解析 define-tp 的参数 | define-tp |
#### 数据结构操作
| 函数 | 描述 | 调用者 |
|------|------|--------|
| `tp--deep-merge-plist` | 深度合并两个 plist | tp-add, tp--prepend-face |
| `tp--prepend-face` | 处理 face 属性的合并逻辑 | tp-add |
| `tp--get-nested` | 获取嵌套属性值 | tp-get, tp-at |
| `tp--remove-nested-keys` | 从 plist 中移除指定键 | tp--remove-property |
#### 区间操作
| 函数 | 描述 | 调用者 |
|------|------|--------|
| `tp-intervals` | 获取文本属性区间列表 | tp-intervals-map, tp-get |
| `tp-intervals-map` | 对区间应用函数 | 多个层操作函数 |
| `tp-empty-p` | 检查对象是否没有文本属性 | tp-put-layer |
---
### 第二层:核心属性操作
这些是核心的文本属性操作函数,直接调用 Emacs 原生的文本属性 API。
| `tp--parse-args` | 解析灵活的调用格式(整串/区域/层名) | tp-set, tp-reset, tp-add |
| `tp--apply-props-to-string` | 字符串路径的属性应用 | tp-set, tp-reset, tp-add |
#### 设置属性
| 函数 | 描述 | 依赖 | 被依赖 |
|------|------|------|--------|
| `tp-set` | 设置文本属性(保留其他属性) | tp--parse-args, tp--handle-tp-text-property | tp-match-set, 层操作 |
| `tp-reset` | 完全替换所有文本属性 | tp--parse-args, tp--handle-tp-text-property | tp-match-reset |
| `tp-add` | 深度合并属性 | tp--parse-args, tp--deep-merge-plist, tp--prepend-face | tp-match-add, tp--update-layer-regions |
| `tp-set` | 设置文本属性(保留其他属性) | tp--parse-args, tp--handle-tp-text | tp-match-set, 层操作 |
| `tp-reset` | 完全替换所有文本属性 | tp--parse-args, tp--handle-tp-text | tp-match-reset |
| `tp-add` | 深度合并属性 | tp--parse-args, tp--deep-merge-plist, tp--prepend-face | tp-match-add |
#### 获取属性
| 函数 | 描述 | 依赖 | 被依赖 |
|------|------|------|--------|
| `tp-get` | 获取范围内的属性值(返回区间列表) | tp--get-nested | 搜索函数 |
| `tp-at` | 获取单个位置的属性值 | tp--get-nested | 大多数高层函数 |
| `tp-plist` | 获取区域中的所有属性 | tp-intervals | 用户 API |
| `tp-member` | 区分"属性值为 nil"与"属性不存在"plist-member 风格) | - | 用户 API |
#### 删除属性
| 函数 | 描述 | 依赖 | 被依赖 |
|------|------|------|--------|
| `tp-remove` | 移除属性或子属性 | tp--remove-property, tp--remove-sub | 用户 API |
| `tp-remove` | 移除属性或子属性 | tp--remove-property, tp--remove-sub, tp--remove-*-from-string | 用户 API |
| `tp-clear` | 清除所有属性 | - | 用户 API |
---
### 第三层:属性层系统
属性层系统在核心属性操作之上,提供多层属性栈的管理能力。
#### 层栈操作(内部)
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp--get-layer-stack` | 获取位置的层栈 | - |
| `tp--build-layer-props` | 从层列表构建属性 | - |
| `tp--layer-stack-to-list` | 将层栈转换为列表 | - |
| `tp--get-layer-by-idx-or-name` | 通过索引或名称查找层 | - |
| `tp--move-layer-in-stack` | 在栈中移动层 | tp--get-layer-by-idx-or-name |
| `tp--raise-layer-in-stack` | 在栈中上下移动层 | tp--move-layer-in-stack |
| `tp--switch-layers-in-stack` | 交换两个层的位置 | tp--get-layer-by-idx-or-name |
| `tp--normalize-layer-spec` | 规范化层规格 | tp-layer-props |
#### 层操作(公开 API
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp-put-layer` | 在指定索引放置层 | tp--normalize-layer-spec, tp--build-layer-props, tp-intervals-map |
| `tp-push-layer` | 将层推到顶部 | tp-put-layer |
| `tp-delete-layer` | 删除层 | tp--get-layer-by-idx-or-name, tp-intervals-map |
| `tp-pop-layer` | 弹出顶层 | tp-delete-layer |
| `tp-move-layer` | 移动层到指定位置 | tp--move-layer-in-stack, tp-intervals-map |
| `tp-raise-layer` | 上移/下移层 | tp--raise-layer-in-stack, tp-intervals-map |
| `tp-rotate-layer` | 轮换层 | tp-move-layer |
| `tp-pin-layer` | 将层置顶 | tp-move-layer |
| `tp-switch-layer` | 交换两个层 | tp--switch-layers-in-stack, tp-intervals-map |
| `tp-merge-layers` | 合并多个层 | tp--get-layer-by-idx-or-name, tp-intervals-map |
| `tp-flatten-layers` | 扁平化所有层 | tp-intervals-map |
#### 层查询
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp-layer-list` | 列出所有层名称 | tp-intervals-map |
| `tp-layer-count` | 计算层数量 | tp-intervals-map |
| `tp-layer-exists-p` | 检查层是否存在 | tp-region-layer-props |
| `tp-layer-top` | 获取顶层名称 | tp-intervals |
| `tp-region-layer-props` | 获取区域中特定层的属性 | tp-intervals-map |
#### 层属性操作
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp-add-to-layers` | 向特定层添加属性 | tp--deep-merge-plist, tp-intervals-map |
| `tp-add-to-all-layers` | 向所有层添加属性 | tp-add-to-layers, tp-layer-count |
钩子变量:`tp--tp-text-handler-function`(定义于此,由 tp-render.el 安装为 `tp--handle-tp-text-property``tp--handle-tp-text` 是它的调用入口,未安装时 `tp-text` 属性按普通属性处理。
---
### 第四层:响应式系统
### tp-search.el模式匹配与搜索
响应式系统提供当变量值改变时自动更新文本属性的能力。
#### 响应式变量处理
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp--reactive-symbol-p` | 检查是否为响应式符号 | - |
| `tp--reactive-var-symbol` | 转换响应式符号为变量符号 | tp--reactive-symbol-p |
| `tp--collect-reactive-symbols` | 收集所有响应式符号 | tp--reactive-symbol-p |
| `tp--resolve-reactive-symbols` | 解析响应式符号为值 | tp--reactive-symbol-p, tp--reactive-var-symbol |
| `tp--extract-reactive-props` | 提取使用特定变量的属性 | tp--collect-reactive-symbols, tp--extract-reactive-value |
| `tp--ensure-reactive-variables` | 确保变量已定义 | tp--reactive-symbol-p, tp--reactive-var-symbol |
#### 依赖注册与管理
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp--register-reactive-deps` | 注册响应式依赖 | tp--reactive-var-symbol, tp--extract-reactive-props |
| `tp--unregister-reactive-deps` | 取消注册依赖 | tp--unregister-layer-watchers, tp--unregister-layer-computed, tp--unregister-layer-data |
| `tp--register-layer-watchers` | 注册层的监听器 | - |
| `tp--register-layer-computed` | 注册计算属性 | - |
| `tp--register-layer-data` | 注册数据变量 | tp--data-var-symbol |
| `tp--unregister-layer-watchers` | 取消注册监听器 | - |
| `tp--unregister-layer-computed` | 取消注册计算属性 | - |
| `tp--unregister-layer-data` | 取消注册数据变量 | - |
#### 响应式更新
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp--reactive-variable-watcher` | 变量监听器回调 | tp--invoke-layer-watchers, tp--update-layer-computed, tp--update-layer-regions, tp--update-reactive-text |
| `tp--invoke-layer-watchers` | 调用层的监听回调 | - |
| `tp--update-layer-computed` | 更新计算属性 | tp--resolve-reactive-symbols, tp--set-layer-props |
| `tp--update-layer-regions` | 更新使用层的文本区域 | tp-layer-props, tp-search-map, tp-add |
| `tp--update-reactive-text` | 更新响应式文本 | tp-layer-props, tp--replace-reactive-text-in-buffer |
| `tp--replace-reactive-text-in-buffer` | 在缓冲区中替换响应式文本 | - |
#### 层定义
| 函数/宏 | 描述 | 依赖 |
|---------|------|------|
| `define-tp` | 定义单个自定义文本属性(层)| tp--parse-define-layer-args, tp--collect-reactive-symbols, tp--ensure-reactive-variables, tp--register-* |
| `define-tps` | 定义自定义文本属性组(层组)| tp--parse-layer-group-element, tp--define-layer-from-parsed |
| `tp--define-layer-from-parsed` | 从解析结果定义层 | (与 define-tp 类似的依赖) |
| `tp--set-layer-props` | 设置层属性 | - |
| `tp--set-group-layers` | 设置组的层列表 | - |
| `tp-layer-props` | 获取层属性 | - |
| `tp-group-props` | 获取组中所有层的属性 | tp-layer-props |
| `tp--resolve-props` | 解析属性(支持层名称) | tp-layer-props, tp--collect-reactive-symbols, tp--resolve-reactive-symbols, tp--register-reactive-deps |
#### 响应式文本
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp--handle-tp-text-property` | 处理 tp-text 属性 | - |
#### 批量更新
| 函数/宏 | 描述 | 依赖 |
|---------|------|------|
| `tp-with-batch-updates` | 批量更新宏 | tp--flush-batch-updates |
| `tp--flush-batch-updates` | 刷新待处理的批量更新 | tp--update-layer-regions, tp--update-reactive-text |
#### 值转换
| 变量/函数 | 描述 | 依赖 |
|-----------|------|------|
| `tp-layer-transforms` | 存储层转换函数的 alist | - |
| `:transform` 选项 | 在 define-tp 中指定转换函数 | tp-layer-transforms |
#### 调试工具
| 变量/函数 | 描述 | 依赖 |
|-----------|------|------|
| `tp-debug-mode` | 启用/禁用调试模式 | - |
| `tp-debug-echo` | 是否在 minibuffer 显示调试信息 | - |
| `tp-debug-log` | 记录调试信息 | tp-debug-mode, tp-debug-echo |
| `tp-debug-show` | 显示 *tp-debug* 缓冲区 | - |
| `tp-debug-clear` | 清除调试日志 | - |
---
### 第五层:高级 API
这些是面向用户的高级 API构建在前四层之上。
依赖 tp-core、tp-layer、tp-ops。提供模式匹配式属性应用、属性搜索与导航。
#### 模式匹配
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp-match-set` | 在字符串匹配处设置属性 | tp--match-apply |
| `tp-match-reset` | 在匹配处重置所有属性 | tp--match-apply |
| `tp-match-add` | 在匹配处添加/合并属性 | tp--match-apply |
| `tp-regexp-set` | 在正则匹配处设置属性 | tp--regexp-apply |
| `tp-regexp-reset` | 在正则匹配处重置属性 | tp--regexp-apply |
| `tp-regexp-add` | 在正则匹配处添加属性 | tp--regexp-apply |
| `tp--match-apply` | 字符串匹配的内部实现 | tp-set/tp-reset/tp-add |
| `tp--regexp-apply` | 正则匹配的内部实现 | tp-set/tp-reset/tp-add |
| `tp-match-set` / `tp-match-reset` / `tp-match-add` | 在字符串匹配处设置/重置/合并属性 | tp--match-apply |
| `tp-regexp-set` / `tp-regexp-reset` / `tp-regexp-add` | 在正则匹配处设置/重置/合并属性 | tp--regexp-apply |
| `tp--match-apply` / `tp--regexp-apply` | 字面/正则匹配的入口(含多模式支持) | tp--pattern-apply |
| `tp--pattern-apply` / `tp--pattern-apply-single` | 共享的模式匹配引擎(空模式/零宽模式安全) | tp-set/tp-reset/tp-add 风格的 apply-fn |
| `tp--deep-merge-apply` / `tp--reset-apply` | 传给引擎的合并/重置回调 | tp--deep-merge-plist 等 |
#### 搜索和导航
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp-search-forward` | 向前搜索属性 | text-property-search-forward |
| `tp-search-backward` | 向后搜索属性 | text-property-search-backward |
| `tp-forward` | 向前搜索 N 次 | tp--forward-on-string, tp-search-forward |
| `tp-backward` | 向后搜索 N 次 | tp--backward-on-string, tp-search-backward |
| `tp-forward-do` | 向前搜索并对最后匹配执行函数 | tp--forward-do-on-string |
| `tp-backward-do` | 向后搜索并对最后匹配执行函数 | tp--backward-do-on-string |
| `tp-search` | 搜索所有匹配 | tp--search-do |
| `tp-search-map` | 对所有匹配应用函数 | tp--search-do |
| `tp--search-do` | 搜索的内部实现 | - |
| `tp-search-backward` | 向后搜索属性 | tp--property-search-backward |
| `tp--property-search-backward` | 带等值谓词的向后搜索(与向前语义对称) | text-property-search-backward |
| `tp-forward` | 向前搜索 N 次并移动点 | tp-search-forward |
| `tp-backward` | 向后搜索 N 次并移动点 | tp-search-backward |
| `tp-search` | 收集所有匹配区间 | tp-intervals 等 |
#### 遍历与替换
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp-forward-do` / `tp-backward-do` | 向前/向后搜索并对匹配执行函数 | tp--forward-do / tp--backward-do |
| `tp--forward-do` / `tp--backward-do` | 单方向遍历的内部实现 | tp--replace-match-text |
| `tp-search-map` | 对所有匹配应用函数FUNCTION 接收 TEXT &optional START END IDX | tp--search-do |
| `tp--search-do` | 搜索遍历的内部实现 | tp--replace-match-text |
| `tp--replace-match-text` | 共享的匹配文本替换助手(缓冲区支持变长替换;字符串变长时报错) | - |
---
### tp-render.el响应式渲染引擎
依赖 tp-core、tp-reactive、tp-layer、tp-ops、tp-search。这是唯一"知道"渲染如何进行的模块:它可以直接调用 `tp-search-map`、`tp-add` 等前置模块的函数,并在加载末尾把自己的入口函数**安装**进下层模块预留的钩子变量。
#### 重渲染
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp--update-layer-regions` | 重渲染携带某层的所有文本区域(替换该层自己的属性键,保留其他来源属性) | tp--layer-render-props, tp-search-map |
| `tp--update-layer-computed` | 更新 `:compute` 计算属性nil 值可正常传播) | tp--resolve-reactive-symbols, tp--set-layer-props |
| `tp--layer-render-props` / `tp--layer-reactive-props` | 求取层的渲染属性 | tp-layer-props |
#### 响应式文本tp-text
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp--handle-tp-text-property` | 处理 `tp-text` 属性(文本替换) | tp--tp-text-replace |
| `tp--update-reactive-text` | 变量变化后更新响应式文本 | tp--replace-reactive-text-in-buffer |
| `tp--replace-reactive-text-in-buffer` | 在缓冲区中替换响应式文本 | - |
| `tp--tp-text-transform` | 应用 `:transform` 转换(首次渲染同样生效) | tp-layer-transforms |
#### 引擎入口与钩子安装
| 函数 | 描述 |
|------|------|
| `tp--reactive-apply-update` | 变量变化的完整处理:更新 computed、合并层定义、重渲染或入批量队列嵌套写入经队列而非递归。安装为 `tp--reactive-update-function` |
| `tp--reactive-flush-entry` | 批量队列刷新时的重渲染入口。安装为 `tp--reactive-flush-function` |
加载末尾执行安装:
```elisp
(setq tp--reactive-update-function #'tp--reactive-apply-update)
(setq tp--reactive-flush-function #'tp--reactive-flush-entry)
(setq tp--tp-text-handler-function #'tp--handle-tp-text-property)
(setq tp--layer-refresh-function #'tp--update-layer-regions)
```
---
### tp-stack.el属性层栈操作
依赖 tp-core、tp-layer、tp-ops。所有栈变更函数建立在共享的裁剪式区域遍历之上区域操作不会影响 [START, END) 之外的文本。
#### 内部助手
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp--parse-layer-args` | 解析层操作的灵活参数 | - |
| `tp--stack-map-region` | 按区间遍历区域内层栈的共享引擎 | tp--map-intervals 风格遍历 |
| `tp--stack-build-props` | 从层列表构建栈属性(单层栈不携带 `tp-layers` | - |
| `tp--put-layer-specs` | 展开层规格(层名/内联 plist/层名列表/参数化/层组) | tp--normalize-layer-spec, tp-group-props(-with-arg) |
| `tp--move-layer-in-stack` | 在栈中移动层 | tp--get-layer-by-idx-or-name |
| `tp--raise-layer-in-stack` | 在栈中上下移动层 | tp--move-layer-in-stack |
| `tp--switch-layers-in-stack` | 交换两个层的位置 | tp--get-layer-by-idx-or-name |
#### 层操作(公开 API
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp-put-layer` | 在指定索引放置层(区域局部) | tp--put-layer-specs, tp--stack-map-region |
| `tp-push-layer` | 将层推到顶部 | tp-put-layer |
| `tp-delete-layer` | 删除层 | tp--stack-map-region |
| `tp-pop-layer` | 弹出顶层 | tp-delete-layer |
| `tp-move-layer` | 移动层到指定位置 | tp--move-layer-in-stack, tp--stack-map-region |
| `tp-raise-layer` | 上移/下移层 | tp--raise-layer-in-stack, tp--stack-map-region |
| `tp-rotate-layer` | 轮换层 | tp-move-layer |
| `tp-pin-layer` | 将层置顶 | tp-move-layer |
| `tp-switch-layer` | 交换两个层 | tp--switch-layers-in-stack, tp--stack-map-region |
| `tp-merge-layers` | 合并多个层(显式 nil 值保留) | tp--merge-layer-props, tp--stack-map-region |
| `tp-flatten-layers` | 扁平化所有层 | tp--merge-layer-props, tp--stack-map-region |
#### 层查询
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp-layer-list` | 列出所有层名称 | tp--stack-map-region |
| `tp-layer-count` | 计算层数量 | tp--stack-map-region |
| `tp-layer-exists-p` | 检查层是否存在 | tp-layer-list |
| `tp-layer-top` | 获取顶层名称(覆盖整个请求区域) | tp--stack-map-region |
| `tp-region-layer-props` | 获取区域中特定层的属性 | tp--stack-map-region |
#### 层属性操作
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp-add-to-layers` | 向特定层添加属性 | tp--deep-merge-plist, tp--stack-map-region |
| `tp-add-to-all-layers` | 向所有层添加属性 | tp-add-to-layers |
---
### tp-palette.el调色板数据
只依赖 tp-core及 subr-x。明/暗主题双值调色板系统,`tp-palette-alist` 是唯一数据源。
| 函数/宏/变量 | 描述 |
|------|------|
| `define-tp-palette` | 定义调色板(重定义立即生效) |
| `tp-palette-alist` | 调色板注册表(唯一数据源) |
| `tp-parse-color` | 解析颜色规格(支持 `("light" . "dark")` 及单边 cons |
| `tp-theme-dark-p` / `tp-theme-light-p` | 当前主题判断 |
| `tp-palette-fg-color` / `tp-palette-bg-color` / `tp-palette-border-color` | 取前景/背景/边框色 |
| `tp-palette-p` / `tp-palette-fg-p` / `tp-palette-bg-p` / `tp-palette-fbg-p` / `tp-palette-border-p` | 调色板谓词 |
| `tp-palette-pure` | 取纯色值 |
---
### tp-builtins.el内置层与辅助工具
最上层模块,依赖 tp-core、tp-layer、tp-ops、tp-palette。提供开箱即用的内置层与展示/缓冲辅助。
| 定义 | 描述 |
|------|------|
| 内置层 | `tp-palette`、`tp-fg`、`tp-bg`、`tp-button`、`tp-underline`、`tp-delete`、`tp-link`、`tp-space`、`tp-headline`、`tp-action` 等(`define-tp` 定义;`tp-link` 的颜色在应用时解析,主题切换即时生效) |
| `tp-pop-to-buffer` / `tp-switch-to-buffer` | 显示带属性文本的缓冲辅助宏q 绑定在缓冲区局部 minor-mode keymap 中) |
| `tp-palette-show` | 展示所有调色板 |
| `tp-suffix-symbol` | 符号加后缀助手 |
---
## 钩子变量:唯一许可的反向调用
分层规则的唯一例外是四个**钩子变量**:下层模块声明变量并在需要时 `funcall`,实现由 tp-render.el 在加载时安装。这样下层模块不必 `require` 上层模块,依赖图保持严格单向;而在未加载 tp-render 时,下层模块依然可用(钩子为 nil 时优雅降级)。
| 钩子变量 | 声明于 | 安装的实现tp-render.el | 用途 |
|----------|--------|---------------------------|------|
| `tp--reactive-update-function` | tp-reactive.el | `tp--reactive-apply-update` | 变量监听器触发的重计算与重渲染 |
| `tp--reactive-flush-function` | tp-reactive.el | `tp--reactive-flush-entry` | 批量更新队列刷新时的重渲染 |
| `tp--layer-refresh-function` | tp-layer.el | `tp--update-layer-regions` | 层重定义后刷新已应用区域 |
| `tp--tp-text-handler-function` | tp-ops.el | `tp--handle-tp-text-property` | `tp-set` 等操作中处理 `tp-text` 属性 |
---
## 函数调用关系图
(标注 `[模块]` 表示函数所在文件;`╌╌▷` 表示经钩子变量的间接调用。)
### tp-set 调用链
```
tp-set
├── tp--parse-args
│ └── tp--resolve-props
tp-set [tp-ops]
├── tp--parse-args [tp-ops]
│ ├── tp--merge-duplicate-keys [tp-core]
│ └── tp--resolve-props [tp-layer]
│ ├── tp-layer-props
│ ├── tp--collect-reactive-symbols
│ ├── tp--resolve-reactive-symbols
│ ├── tp--register-reactive-deps
│ └── tp--build-layer-props (for groups)
├── tp--handle-tp-text-property
└── put-text-property (Emacs 原生)
│ ├── tp--collect-reactive-symbols [tp-core]
│ ├── tp--resolve-reactive-symbols [tp-core]
│ └── tp--register-reactive-deps [tp-reactive]
├── tp--handle-tp-text [tp-ops]
│ ╌╌▷ tp--handle-tp-text-property [tp-render](经钩子)
├── tp--apply-props-to-string [tp-ops](整串形式,返回新字符串)
└── set-text-properties / put-text-propertyEmacs 原生,区域形式)
```
### tp-add 调用链
```
tp-add
├── tp--parse-args
├── tp--handle-tp-text-property
├── text-properties-at (Emacs 原生)
├── tp--prepend-face
│ └── tp--deep-merge-plist
├── tp--deep-merge-plist
└── put-text-property (Emacs 原生)
tp-add [tp-ops]
├── tp--parse-args [tp-ops]
├── tp--handle-tp-text [tp-ops] ╌╌▷ tp--handle-tp-text-property [tp-render]
├── text-properties-atEmacs 原生)
├── tp--prepend-face [tp-core]face 家族属性)
│ └── tp--deep-merge-plist [tp-core]
├── tp--deep-merge-plist [tp-core](其他嵌套属性)
└── put-text-propertyEmacs 原生)
```
### define-tp 调用链
```
define-tp
├── tp--parse-define-layer-args
├── tp--collect-reactive-symbols
├── tp--unregister-reactive-deps
│ ├── tp--unregister-layer-watchers
│ ├── tp--unregister-layer-computed
│ └── tp--unregister-layer-data
├── tp--ensure-reactive-variables
├── tp--register-layer-data
├── tp--register-layer-computed
├── tp--apply-initial-computed
├── tp--register-reactive-deps
├── tp--register-layer-watchers
├── tp--resolve-reactive-symbols
├── tp--set-layer-props
└── tp--update-layer-regions
├── tp-layer-props
└── tp-search-map
└── tp-add
define-tp [tp-layer](宏)
└── tp--define-layer-internal [tp-layer]
├── tp--parse-define-layer-args [tp-layer]
├── tp--collect-reactive-symbols [tp-core]
├── tp--unregister-reactive-deps [tp-reactive]
├── tp--ensure-reactive-variables [tp-reactive]
├── tp--register-layer-data [tp-reactive]
│ └── add-variable-watcherEmacs 原生)
├── tp--register-layer-computed [tp-reactive]
├── tp--apply-initial-computed [tp-reactive]
├── tp--register-reactive-deps [tp-reactive]
├── tp--register-layer-watchers [tp-reactive]
├── tp--resolve-reactive-symbols [tp-core]
├── tp--set-layer-props [tp-layer]
└── tp--layer-refresh [tp-layer]
╌╌▷ tp--update-layer-regions [tp-render](经钩子)
└── tp-search-map [tp-search]
└── put-text-property
```
### tp-push-layer 调用链
```
tp-push-layer
└── tp-put-layer
├── tp--normalize-layer-spec
│ └── tp-layer-props
├── tp-group-props
│ └── tp-layer-props
├── tp-empty-p
├── set-text-properties (Emacs 原生)
└── tp-intervals-map
└── tp-intervals
tp-push-layer [tp-stack]
├── tp--parse-layer-args [tp-stack]
└── tp-put-layer [tp-stack]
├── tp--put-layer-specs [tp-stack]
│ ├── tp--normalize-layer-spec [tp-layer]
└── tp-layer-props [tp-layer]
│ └── tp-group-props / tp-group-props-with-arg [tp-layer]
└── tp--stack-map-region [tp-stack](裁剪到 [START, END)
├── tp--stack-build-props [tp-stack]
└── set-text-propertiesEmacs 原生)
```
### 响应式更新调用链
```
(setq some-reactive-var new-value)
└── tp--reactive-variable-watcher
├── tp--invoke-layer-watchers
├── tp--update-layer-computed
│ ├── tp--resolve-reactive-symbols
│ └── tp--set-layer-props
├── tp--update-layer-regions (属性更新)
│ └── tp-search-map
│ └── tp-add
└── tp--update-reactive-text (文本替换)
└── tp--replace-reactive-text-in-buffer
└── tp--reactive-variable-watcher [tp-reactive]
├── tp--invoke-layer-watchers [tp-reactive]:watch 回调)
└── ╌╌▷ tp--reactive-apply-update [tp-render](经钩子)
├── tp--update-layer-computed [tp-render]
│ ├── tp--resolve-reactive-symbols [tp-core]
│ └── tp--set-layer-props [tp-layer]
├── tp--set-layer-props [tp-layer]深合并回层定义setq-local 不写全局)
├── tp--update-layer-regions [tp-render](属性更新)
│ └── tp-search-map [tp-search]
│ └── put-text-property
└── tp--update-reactive-text [tp-render]tp-text 文本替换)
└── tp--replace-reactive-text-in-buffer [tp-render]
批量模式tp-with-batch-updates/ 更新中的嵌套写入:
└── tp--queue-batch-update [tp-reactive](入队,不递归)
└── tp--flush-batch-updates [tp-reactive](退出批量时)
└── ╌╌▷ tp--reactive-flush-entry [tp-render](经钩子)
├── tp--update-layer-regions
└── tp--update-reactive-text
```
---
## 设计原则
1. **分层封装**:每层只依赖于下层功能,避免跨层调用
2. **单一职责**:每个函数只做一件事
3. **复用优先**:高层函数应该复用低层函数,避免重复代码
4. **统一接口**:所有核心属性函数支持相同的调用约定
5. **响应式解耦**:响应式系统独立于核心属性操作,可选择性使用
1. **严格分层**:模块只允许 `require` 并调用排在它前面的模块,字节编译器强制检查依赖顺序
2. **钩子反转**:唯一许可的"向上调用"是四个钩子变量(`tp--tp-text-handler-function`、`tp--reactive-update-function`、`tp--reactive-flush-function`、`tp--layer-refresh-function`),由 tp-render.el 统一安装实现
3. **单一职责**:每个模块(和函数)只负责一件事
4. **复用优先**:共享引擎(`tp--map-intervals`、`tp--stack-map-region`、`tp--pattern-apply`、`tp--replace-match-text`)承载重复逻辑,高层函数复用而非复制
5. **统一接口**:所有核心属性函数支持相同的调用约定(整串/区域形式、层名、`$var`
6. **响应式解耦**tp-reactive/tp-layer/tp-ops 不依赖渲染引擎;不加载 tp-render 时钩子为 nil各模块优雅降级

View File

@ -1,5 +1,12 @@
# tp.el 代码分析报告
> **历史文档说明2026-07 更新)**:本报告分析的是拆分前的单文件 tp.el0.1.0)。
> 自 0.2.0 起代码库已模块化为九个分层模块tp-core.el → tp-reactive.el → tp-layer.el →
> tp-ops.el → tp-search.el → tp-render.el → tp-stack.el → tp-palette.el → tp-builtins.el
> tp.el 仅作总入口),并修复了大量已确认的 bug。当前架构请以
> [ARCHITECTURE.md](ARCHITECTURE.md) 为准,本次变更明细见 [CHANGELOG.md](../CHANGELOG.md)。
> 下文的调用堆栈与问题分析保留为历史分析;"文件结构"与"关键代码位置"表已更新为当前模块位置。
本报告旨在帮助想要参与 tp.el 开发的开发者快速了解项目结构、核心功能实现、以及潜在的优化方向。
## 目录
@ -23,7 +30,7 @@
## 项目概述
tp.el 是一个 Emacs Lisp 文本属性操作库,采用 **五层架构设计**
tp.el 是一个 Emacs Lisp 文本属性操作库,拆分前的单文件版本采用概念上的 **五层架构设计**
```
┌─────────────────────────────────────────────────────────────────┐
@ -44,15 +51,30 @@ tp.el 是一个 Emacs Lisp 文本属性操作库,采用 **五层架构设计**
└─────────────────────────────────────────────────────────────────┘
```
0.2.0 的模块拆分大体沿用了这一分层思路,并把"响应式系统向上调用高级 API"的
矛盾收拢为 tp-render.el 安装的钩子变量,详见 [ARCHITECTURE.md](ARCHITECTURE.md)。
---
## 文件结构
当前0.2.0)文件结构:
```
tp/
├── tp.el # 核心代码4863 行)
├── tp-palette.el # 预定义颜色调色板层333 行)
├── tp-tests.el # ERT 测试套件4113 行100+ 测试用例)
├── tp.el # 总入口按序加载全部模块62 行)
├── tp-core.el # 区间遍历、plist/face 合并引擎、调试、$var 工具781 行)
├── tp-reactive.el # 响应式依赖注册表、变量监听、批量队列370 行)
├── tp-layer.el # define-tp / define-tps、层注册表与解析1295 行)
├── tp-ops.el # 核心属性操作 tp-set/get/at/remove/...916 行)
├── tp-search.el # 模式匹配、搜索与导航810 行)
├── tp-render.el # 响应式重渲染引擎501 行)
├── tp-stack.el # 属性层栈操作709 行)
├── tp-palette.el # 明/暗主题调色板数据351 行)
├── tp-builtins.el # 内置层与辅助工具193 行)
├── tp-tests.el # 综合 ERT 测试套件4123 行280 个测试)
├── tp-*-tests.el # 各模块回归测试套件7 个文件159 个测试;全套共 439 个测试)
├── Makefile # test / compile / clean
├── docs/
│ ├── ARCHITECTURE.md # 架构文档
│ ├── CODE-ANALYSIS.md # 代码分析报告(本文档)
@ -95,21 +117,23 @@ tp-set (用户调用入口)
#### 关键代码位置
| 函数 | 文件位置 | 作用 |
0.2.0 模块化后按"函数 → 模块文件"定位;文件内具体行号请用 `M-x xref-find-definitions` 查找。)
| 函数 | 模块文件 | 作用 |
|------|----------|------|
| `tp-set` | tp.el:1350 | 主入口函数 |
| `tp--parse-args` | tp.el:1233 | 解析三种调用格式 |
| `tp--resolve-props` | tp.el:3730 | 展开层名称和响应式变量 |
| `tp--handle-tp-text-property` | tp.el:1124 | 处理 tp-text 文本替换 |
| `tp-add` | tp.el:1522 | 深度合并属性 |
| `tp-push-layer` | tp.el:4156 | 推送层到栈顶 |
| `tp-put-layer` | tp.el:4071 | 在指定位置放置层 |
| `define-tp` | tp.el:3154 | 定义自定义层(宏)|
| `define-tps` | tp.el:3448 | 定义层组(宏)|
| `tp--reactive-variable-watcher` | tp.el:713 | 响应式变量监听器回调 |
| `tp--update-layer-regions` | tp.el:993 | 更新使用层的文本区域 |
| `tp-search-map` | tp.el:2918 | 搜索并应用函数 |
| `tp--match-apply` | tp.el:2269 | 模式匹配内部实现 |
| `tp-set` | tp-ops.el | 主入口函数 |
| `tp--parse-args` | tp-ops.el | 解析多种调用格式 |
| `tp--resolve-props` | tp-layer.el | 展开层名称和响应式变量 |
| `tp--handle-tp-text-property` | tp-render.el | 处理 tp-text 文本替换(经钩子 `tp--tp-text-handler-function` 安装到 tp-ops |
| `tp-add` | tp-ops.el | 深度合并属性 |
| `tp-push-layer` | tp-stack.el | 推送层到栈顶 |
| `tp-put-layer` | tp-stack.el | 在指定位置放置层 |
| `define-tp` | tp-layer.el | 定义自定义层(宏)|
| `define-tps` | tp-layer.el | 定义层组(宏)|
| `tp--reactive-variable-watcher` | tp-reactive.el | 响应式变量监听器回调 |
| `tp--update-layer-regions` | tp-render.el | 更新使用层的文本区域 |
| `tp-search-map` | tp-search.el | 搜索并应用函数 |
| `tp--match-apply` | tp-search.el | 模式匹配内部实现 |
---
@ -683,18 +707,18 @@ tp-layers -> [props1 props2 props3]
(setq tp-debug-mode t)
(setq tp-debug-echo t)
;; 运行测试
;; emacs --batch -l tp.el -l tp-tests.el -f ert-run-tests-batch-and-exit
;; 运行测试(全套 439 个 ERT 测试)
;; make test
```
### 2. 添加新功能的步骤
1. **理解分层架构**
- 确定新功能属于哪一层
- 遵循层间调用规则(只调用下层函数)
- 确定新功能属于哪个模块(见 [ARCHITECTURE.md](ARCHITECTURE.md)
- 遵循模块间调用规则(只调用前置模块的函数)
2. **编写测试用例**
- 在 `tp-tests.el` 中添加测试
- 在对应模块的 `tp-*-tests.el`(或综合套件 `tp-tests.el`中添加测试
- 覆盖正常流程和边界情况
3. **实现功能**
@ -731,23 +755,23 @@ tp-reactive-deps
#### 添加新的核心属性函数
1. 在第二层添加函数
1. 在 `tp-ops.el` 添加函数
2. 使用 `tp--parse-args` 解析参数
3. 调用 Emacs 原生 API
4. 添加测试用例
#### 添加新的层操作函数
#### 添加新的层操作函数
1. 在第三层添加函数
2. 使用 `tp-intervals-map` 遍历区间
1. 在 `tp-stack.el` 添加函数
2. 使用 `tp--stack-map-region` 遍历区域内层栈
3. 使用 `tp--get-layer-stack` 获取层栈
4. 添加测试用例
#### 扩展响应式系统
1. 在第四层添加函数
1. 注册/监听逻辑放在 `tp-reactive.el`,渲染逻辑放在 `tp-render.el`
2. 使用 `add-variable-watcher` 注册监听
3. 在适当位置调用 `tp--update-layer-regions`
3. 在适当位置调用 `tp--update-layer-regions`(下层模块经钩子变量触发)
4. 添加测试用例
---
@ -775,5 +799,5 @@ tp.el 是一个设计精良的文本属性操作库,其核心创新包括:
---
*报告生成时间: 2026-01-10*
*tp.el 版本: 0.1.0*
*报告生成时间: 2026-01-10(分析对象:拆分前的单文件 tp.el 0.1.0*
*文件结构与"关键代码位置"表更新于 2026-07-26tp 0.2.0 模块化后)*

View File

@ -10,7 +10,7 @@ The following evaluates and documents the implementation status of six optimizat
**Suggestion**: Support partial updates within a region - only updating the reactive portion while preserving surrounding text properties.
**Evaluation**: Already implemented. tp.el uses `tp-intervals-map` and interval-based update mechanisms to support fine-grained property updates. Updates only affect regions with specific `tp-name` properties.
**Evaluation**: Already implemented. tp.el uses `tp-search-map` over `tp-name`-tagged regions and interval-based update mechanisms to support fine-grained property updates. Updates only affect regions with specific `tp-name` properties, and only the layer's own property keys are replaced — properties contributed by other sources are left untouched.
### 2. Reactive Symbol Cleanup ✅ Already Implemented
@ -192,9 +192,9 @@ Debug mode helps developers understand the reactive update flow:
These optimizations follow tp.el's layered architecture principles:
1. **Debug Mode** - Basic utility layer functionality
2. **Batch Updates** - Implemented in the reactive system layer
3. **Value Transformation** - Implemented in layer definition and reactive text handling
1. **Debug Mode** - Basic utility layer functionality (`tp-core.el`)
2. **Batch Updates** - Implemented in the reactive system layer (`tp-reactive.el`)
3. **Value Transformation** - Implemented in layer definition and reactive text handling (`tp-layer.el` / `tp-render.el`)
All new features integrate seamlessly with the existing reactive system without breaking existing APIs.

View File

@ -10,7 +10,7 @@
**建议**:支持区域内的部分更新,只更新响应式部分,保留周围文本属性。
**评估**已经实现。tp.el 通过 `tp-intervals-map`基于区间的更新机制,已经支持细粒度的属性更新。更新只影响具有特定 `tp-name` 的区域。
**评估**已经实现。tp.el 通过在带 `tp-name` 标记的区域上使用 `tp-search-map`,以及基于区间的更新机制,已经支持细粒度的属性更新。更新只影响具有特定 `tp-name` 的区域,并且只替换该层自身的属性键——由其他来源贡献的属性保持不变
### 2. 响应式符号清理Reactive Symbol Cleanup✅ 已实现
@ -192,9 +192,9 @@
这些优化遵循 tp.el 的分层架构原则:
1. **调试模式** - 作为基础工具层功能
2. **批量更新** - 在响应式系统层实现
3. **值转换** - 在层定义和响应式文本处理中实现
1. **调试模式** - 作为基础工具层功能`tp-core.el`
2. **批量更新** - 在响应式系统层实现`tp-reactive.el`
3. **值转换** - 在层定义和响应式文本处理中实现`tp-layer.el` / `tp-render.el`
所有新功能都与现有的响应式系统无缝集成,不破坏现有 API。

View File

@ -395,13 +395,13 @@ The real power of `tp-text` comes from combining it with reactive variables:
(tp-set 1 12 'dynamic-content)
;; Text now shows "Loading..."
(message "Initial text: %s" (buffer-string))
;; => "Loading... "
;; => "Loading..."
;; Change the variable
(setq my-dynamic-text "Data loaded successfully!")
;; Text updates automatically!
(message "After update: %s" (buffer-string)))
;; => "Data loaded successfully! "
;; => "Data loaded successfully!"
```
### Using :compute for Dynamic Text
@ -451,7 +451,7 @@ You can also use reactive `tp-text` directly in property lists without defining
### 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.
1. **tp-text on strings returns a new string**: Emacs strings cannot change length in place, so string-object calls return a new string instead of modifying the original. A sub-region `tp-text` replaces only that region and keeps the rest of the string; the whole-string form returns just the replacement text.
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.
@ -466,7 +466,7 @@ The `:transform` keyword allows you to register a transformation function that p
:data '((price . "99.9"))
:transform (lambda (text)
(format "$%.2f" (string-to-number text))))
;; 99.9 displays as $99.00
;; 99.9 displays as $99.90
;; Date formatting
(define-tp date-display ()

View File

@ -395,13 +395,13 @@ tp.el 的响应式系统借鉴了 Vue 的 API提供了三个强大的关键
(tp-set 1 12 'dynamic-content)
;; 文本现在显示 "Loading..."
(message "初始文本: %s" (buffer-string))
;; => "Loading... "
;; => "Loading..."
;; 改变变量
(setq my-dynamic-text "数据加载完成!")
;; 文本自动更新!
(message "更新后: %s" (buffer-string)))
;; => "数据加载完成! "
;; => "数据加载完成!"
```
### 使用 :compute 生成动态文本
@ -451,7 +451,7 @@ tp.el 的响应式系统借鉴了 Vue 的 API提供了三个强大的关键
### 注意事项
1. **tp-text 只影响缓冲区文本**:对于字符串对象,由于 Emacs 字符串长度固定,`tp-text` 不会替换字符串内容
1. **tp-text 作用于字符串时返回新字符串**Emacs 字符串长度无法原地改变,因此字符串形式的调用会返回一个新字符串,而不是修改原字符串。子区域的 `tp-text` 只替换该区域并保留字符串的其余部分;整串形式则只返回替换后的文本
2. **保留现有属性**:使用 `tp-set``tp-add` 设置 `tp-text` 时,现有的文本属性会被保留。
3. **非响应式属性不添加 tp-name**:如果文本属性中没有响应式变量(`$` 前缀),则不会添加 `tp-name` 等响应式专用属性,保持原生文本属性行为。
@ -466,7 +466,7 @@ tp.el 的响应式系统借鉴了 Vue 的 API提供了三个强大的关键
:data '((price . "99.9"))
:transform (lambda (text)
(format "$%.2f" (string-to-number text))))
;; 99.9 显示为 $99.00
;; 99.9 显示为 $99.90
;; 日期格式化
(define-tp date-display ()