Document 0.3.0 across bilingual READMEs, doctests, and ARCHITECTURE.md
Some checks are pending
CI / test (28.1) (push) Waiting to run
CI / test (29.4) (push) Waiting to run
CI / test (30.1) (push) Waiting to run

README.md/README_CN.md gain verified sections for every 0.3.0 feature:
layer visibility (hide/show model, flatten/merge exclusion), lower and
canonical rotate orders, stack introspection (tp-layer-stack-at,
tp-describe-layer), the reactive layer-buffer registry and lifecycle
(track-buffer, anonymous-layer GC, minimal-diff tp-text), search
bounds/SUBEXP/PREDICATE, multi-argument parameterized layers, ABSOLUTE
interval coordinates, palette primaries, prefix-conforming define
aliases, and deprecation boxes for the raw search wrappers. Return
conventions corrected against probed behavior (add-to-layers mutates
in place; tp-clear now explicitly returns nil in code and docs), the
in-place string-mutation warning added for all 16 stack mutators, and
the stale pre-B43 with-range example fixed in both languages.
EN/CN parity is exact (132 headings, 158 fenced blocks each); all 316
blocks executed with 0 failures. tp-doctest.el grows 63 -> 88
assertions, all accessor-based for Emacs-28 print-order safety.
docs/ARCHITECTURE.md rewritten to the post-review truth: two hook
variables, verified per-module dependency table, relocated subsystem
homes, and a new mutable-state inventory.

Suite 578/578, doctests 88/88, shuffled green, compile clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kinneyzhang 2026-07-27 03:44:45 +08:00
parent aa214456df
commit fe2c03d2bd
5 changed files with 2361 additions and 280 deletions

1001
README.md

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
本文档描述 tp 库的模块分层结构与函数调用层次,从底层基础模块到上层功能模块的分层组织。 本文档描述 tp 库的模块分层结构与函数调用层次,从底层基础模块到上层功能模块的分层组织。
自 0.2.0 起,原来的单文件 tp.el 已拆分为九个分层模块,`tp.el` 只作为总入口(`(require 'tp)` 依次加载全部模块,用户接口不变)。各模块的变更缘由见 [CHANGELOG.md](../CHANGELOG.md)。 自 0.2.0 起,原来的单文件 tp.el 已拆分为九个分层模块,`tp.el` 只作为总入口(`(require 'tp)` 依次加载全部模块,用户接口不变)。0.3.0 进一步收紧了模块边界:`tp-text` 处理链下沉至 tp-ops、批量更新上收至 tp-render、层栈存储编解码与匿名层机制归位 tp-layer钩子变量从四个减少到两个。各变更的缘由见 [CHANGELOG.md](../CHANGELOG.md)。
## 目录 ## 目录
@ -10,14 +10,15 @@
- [模块分层](#模块分层) - [模块分层](#模块分层)
- [tp-core.el基础工具](#tp-coreel基础工具) - [tp-core.el基础工具](#tp-coreel基础工具)
- [tp-reactive.el响应式基础设施](#tp-reactiveel响应式基础设施) - [tp-reactive.el响应式基础设施](#tp-reactiveel响应式基础设施)
- [tp-layer.el层定义与解析](#tp-layerel层定义与解析) - [tp-layer.el层定义、解析与层栈存储](#tp-layerel层定义解析与层栈存储)
- [tp-ops.el核心属性操作](#tp-opsel核心属性操作) - [tp-ops.el核心属性操作与 tp-text 处理链](#tp-opsel核心属性操作与-tp-text-处理链)
- [tp-search.el模式匹配与搜索](#tp-searchel模式匹配与搜索) - [tp-search.el模式匹配与搜索](#tp-searchel模式匹配与搜索)
- [tp-render.el响应式渲染引擎](#tp-renderel响应式渲染引擎) - [tp-render.el响应式渲染引擎](#tp-renderel响应式渲染引擎)
- [tp-stack.el属性层栈操作](#tp-stackel属性层栈操作) - [tp-stack.el属性层栈操作](#tp-stackel属性层栈操作)
- [tp-palette.el调色板数据](#tp-paletteel调色板数据) - [tp-palette.el调色板数据](#tp-paletteel调色板数据)
- [tp-builtins.el内置层与辅助工具](#tp-builtinsel内置层与辅助工具) - [tp-builtins.el内置层与辅助工具](#tp-builtinsel内置层与辅助工具)
- [钩子变量:唯一许可的反向调用](#钩子变量唯一许可的反向调用) - [钩子变量:唯一许可的反向调用](#钩子变量唯一许可的反向调用)
- [可变状态清单](#可变状态清单)
- [函数调用关系图](#函数调用关系图) - [函数调用关系图](#函数调用关系图)
- [设计原则](#设计原则) - [设计原则](#设计原则)
@ -32,6 +33,20 @@ tp-core → tp-reactive → tp-layer → tp-ops → tp-search
→ tp-render → tp-stack → tp-palette → tp-builtins → tp-render → tp-stack → tp-palette → tp-builtins
``` ```
注意加载顺序是依赖顺序的**上界**:并非每个模块都依赖它前面的全部模块。各模块实际 `require` 的 tp- 模块如下(逐一核对自源码头部):
| 模块 | require 的 tp- 模块 |
|------|--------------------|
| tp-core | —(仅 cl-lib、dash、seq |
| tp-reactive | tp-core |
| tp-layer | tp-core、tp-reactive |
| tp-ops | tp-core、tp-reactive、tp-layer |
| tp-search | tp-core、tp-reactive、tp-layer、tp-ops |
| tp-render | tp-core、tp-reactive、tp-layer、tp-ops、tp-search |
| tp-stack | tp-core、tp-reactive、tp-layer**不依赖 tp-ops / tp-search / tp-render** |
| tp-palette | —(不依赖任何 tp- 模块,仅 subr-x |
| tp-builtins | tp-core、tp-layer、tp-ops、tp-palette |
``` ```
┌────────────────────────────────────────────────────────────────┐ ┌────────────────────────────────────────────────────────────────┐
│ tp.el —— 总入口,按序 require 全部模块 │ │ tp.el —— 总入口,按序 require 全部模块 │
@ -41,25 +56,28 @@ tp-core → tp-reactive → tp-layer → tp-ops → tp-search
│ tp-palette-show、显示缓冲辅助宏 │ │ tp-palette-show、显示缓冲辅助宏 │
├────────────────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────────────────┤
│ tp-palette.el 明/暗主题调色板数据、tp-parse-color │ │ tp-palette.el 明/暗主题调色板数据、tp-parse-color │
│ (独立叶模块,不依赖任何 tp- 模块) │
├────────────────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────────────────┤
│ tp-stack.el 层栈操作push/pop/move/merge/flatten …) │ tp-stack.el 层栈操作push/pop/move/hide/show/merge …) │
├────────────────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────────────────┤
│ tp-render.el 响应式重渲染引擎 ──┐ │ │ tp-render.el 响应式重渲染引擎、最小差异 tp-text 编辑、 │
├──────────────────────────────────── │ ─────────────────────────┤ │ 批量更新tp-with-batch-updates + flush──┐ │
│ tp-search.el tp-match-*/tp-regexp-*、tp-search、导航 │ ├─────────────────────────────────────────────────────────── │ ──┤
├──────────────────────────────────── │ ─────────────────────────┤ │ 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-ops.el tp-set/reset/add/get/at/remove/clear、 │ │
├──────────────────────────────────── │ ─────────────────────────┤ │ tp-text 处理链0.3.0 起在此,直接调用) │ │
│ tp-layer.el define-tp/define-tps、层注册表与解析 │ ├─────────────────────────────────────────────────────────── │ ──┤
│ ◁╌╌ tp--layer-refresh-function ╌╌╌╌╌╌┤ │ │ tp-layer.el define-tp/define-tps、层注册表与解析、 │ │
├──────────────────────────────────── │ ─────────────────────────┤ │ 层栈存储编解码、匿名层机制与 GC │ │
│ tp-reactive.el 响应式依赖注册表、变量监听、批量队列 │ │ ◁╌╌ tp--layer-refresh-function ╌╌╌╌╌╌╌╌╌╌┤ │
│ ◁╌╌ tp--reactive-update-function ╌╌╌╌┤ │ ├─────────────────────────────────────────────────────────── │ ──┤
│ ◁╌╌ tp--reactive-flush-function ╌╌╌╌╌┘ │ │ tp-reactive.el 响应式依赖注册表、变量监听、批量队列、 │ │
│ 层→缓冲区注册表 │ │
│ ◁╌╌ tp--reactive-update-function ╌╌╌╌╌╌╌╌╌┘ │
├────────────────────────────────────────────────────────────────┤ ├────────────────────────────────────────────────────────────────┤
│ tp-core.el 区间遍历、plist/face 合并引擎、 │ │ tp-core.el 区间遍历、plist/face 合并引擎、 │
│ 调试日志、$var 符号工具 │ 调试日志、$var 符号工具(无可变状态)
└────────────────────────────────────────────────────────────────┘ └────────────────────────────────────────────────────────────────┘
实线层级上层模块调用下层模块require 依赖)。 实线层级上层模块调用下层模块require 依赖)。
@ -67,7 +85,7 @@ tp-core → tp-reactive → tp-layer → tp-ops → tp-search
由 tp-render.el 在加载时安装实现(见下文)。 由 tp-render.el 在加载时安装实现(见下文)。
``` ```
早期文档把"响应式系统"画在高级 API 之下、却又让它向上调用 `tp-search-map`,与自身的分层原则矛盾。现在这一矛盾已在代码层面消除:需要向上调用的逻辑全部收拢进 `tp-render.el`(位于 `tp-search.el` 之上可以直接调用它下层模块tp-reactive、tp-layer、tp-ops通过**钩子变量**触发渲染,自身不依赖任何上层模块 需要"向上调用"的逻辑全部收拢在 `tp-render.el`(位于 `tp-search.el` 之上可以直接调用它。0.2.0 时这类反向调用靠四个钩子变量实现0.3.0 把其中两个消除在了代码层面——`tp-text` 处理链整体下沉进 tp-ops`tp-set` 等直接调用,不再需要 `tp--tp-text-handler-function`;只加载到 tp-ops 的部分加载也能得到可用的 `tp-text` 文本替换),批量刷新整体上收进 tp-render`tp--flush-batch-updates` 直接调用 `tp--reactive-flush-entry`,不再需要 `tp--reactive-flush-function`。剩下的两个钩子对应真正源自下层的事件变量监听器触发tp-reactive与层重定义触发tp-layer
--- ---
@ -75,14 +93,14 @@ tp-core → tp-reactive → tp-layer → tp-ops → tp-search
### tp-core.el基础工具 ### tp-core.el基础工具
最底层模块,不依赖任何其他 tp 模块,提供区间遍历、合并引擎与调试能力。 最底层模块,不依赖任何其他 tp 模块,提供区间遍历、合并引擎与调试能力。0.3.0 起 tp-core **不再持有任何可变状态**(匿名层计数器已迁至 tp-layer仅剩 `tp-debug-mode` / `tp-debug-echo` 两个 defcustom 用户选项)。
#### 区间操作 #### 区间操作
| 函数 | 描述 | 主要调用者 | | 函数 | 描述 | 主要调用者 |
|------|------|--------| |------|------|--------|
| `tp-intervals` | 获取区域内文本属性区间列表(裁剪到 [START, END) | tp-intervals-map, tp-get | | `tp-intervals` | 获取区域内文本属性区间列表(裁剪到 [START, END);可选 ABSOLUTE 参数返回缓冲区原生坐标,默认仍为相对坐标 | tp-intervals-map, tp-get |
| `tp-intervals-map` | 对区间应用函数 | 多个属性/层操作函数 | | `tp-intervals-map` | 对区间应用函数(同样支持 ABSOLUTE | 多个属性/层操作函数 |
| `tp--map-intervals` | 共享的裁剪式区间遍历引擎 | tp-intervals-map, tp-ops/tp-stack 的区域操作 | | `tp--map-intervals` | 共享的裁剪式区间遍历引擎 | tp-intervals-map, tp-ops/tp-stack 的区域操作, tp-reactive 的缓冲区扫描 |
| `tp-plist` | 获取区域中合并后的所有属性 | 用户 API | | `tp-plist` | 获取区域中合并后的所有属性 | 用户 API |
| `tp-empty-p` | 检查对象是否没有文本属性 | 用户 API | | `tp-empty-p` | 检查对象是否没有文本属性 | 用户 API |
@ -94,6 +112,7 @@ tp-core → tp-reactive → tp-layer → tp-ops → tp-search
| `tp--merge-face-values` | 合并两个 face 值 | 合并引擎内部 | | `tp--merge-face-values` | 合并两个 face 值 | 合并引擎内部 |
| `tp--merge-duplicate-keys` | 合并 plist 中的重复键 | tp--parse-args | | `tp--merge-duplicate-keys` | 合并 plist 中的重复键 | tp--parse-args |
| `tp--parse-face-list` | 解析 face 列表 | 合并引擎内部 | | `tp--parse-face-list` | 解析 face 列表 | 合并引擎内部 |
| `tp--merge-string-props-into-plist` | 将字符串内嵌属性并入 plist | tp-ops 的 tp-text 处理链 |
| `tp--get-nested` | 按路径获取嵌套属性值 | tp-get, tp-at | | `tp--get-nested` | 按路径获取嵌套属性值 | tp-get, tp-at |
`tp-face-properties`(常量,`'(face font-lock-face mouse-face)`)定义参与 face 感知合并的属性家族。 `tp-face-properties`(常量,`'(face font-lock-face mouse-face)`)定义参与 face 感知合并的属性家族。
@ -122,98 +141,139 @@ tp-core → tp-reactive → tp-layer → tp-ops → tp-search
### tp-reactive.el响应式基础设施 ### tp-reactive.el响应式基础设施
只依赖 tp-core。维护响应式依赖注册表、变量监听器与批量更新队列**不包含任何渲染逻辑**,重渲染通过钩子变量委托给 tp-render.el。 只依赖 tp-core。维护响应式依赖注册表、变量监听器、批量更新队列与 0.3.0 新增的**层→缓冲区注册表****不包含任何渲染逻辑**,重渲染通过钩子变量委托给 tp-render.el。批量更新的队列(`tp--batch-update-pending`、`tp--queue-batch-update`)定义在这里,但 `tp-with-batch-updates` 宏与刷新逻辑自 0.3.0 起位于 tp-render.el。
#### 依赖注册与管理 #### 依赖注册与管理
| 函数/变量 | 描述 | | 函数/变量 | 描述 |
|------|------| |------|------|
| `tp-reactive-deps` | 变量 → 依赖它的层及属性 的注册表 | | `tp-reactive-deps` | 变量 → 依赖它的层及属性 的注册表 |
| `tp--register-reactive-deps` | 注册响应式依赖 | | `tp--register-reactive-deps` | 注册响应式依赖 |
| `tp--unregister-reactive-deps` | 取消注册依赖(含 watchers/computed/data | | `tp--unregister-reactive-deps` | 取消注册依赖(含 watchers/computed/data,并移除该层的缓冲区注册表条目 |
| `tp--layer-has-reactive-deps-p` | 层是否有响应式依赖 | | `tp--layer-has-reactive-deps-p` | 层是否有响应式依赖 |
| `tp--register-layer-watchers` / `tp--unregister-layer-watchers` | 注册/清除 `:watch` 回调 | | `tp--register-layer-watchers` / `tp--unregister-layer-watchers` | 注册/清除 `:watch` 回调 |
| `tp--register-layer-computed` / `tp--unregister-layer-computed` | 注册/清除 `:compute` 计算属性 | | `tp--register-layer-computed` / `tp--unregister-layer-computed` | 注册/清除 `:compute` 计算属性 |
| `tp--register-layer-data` / `tp--unregister-layer-data` | 注册/清除 `:data` 变量 | | `tp--register-layer-data` / `tp--unregister-layer-data` | 注册/清除 `:data` 变量 |
| `tp--apply-initial-computed` | 计算 `:compute` 的初始值 | | `tp--apply-initial-computed` | 计算 `:compute` 的初始值 |
| `tp--ensure-reactive-variables` | 确保 `$var` 对应的变量已定义 | | `tp--ensure-reactive-variables` | 确保 `$var` 对应的变量已定义 |
| `tp-reactive-reset` | 重置全部响应式注册表 | | `tp-reactive-reset` | 重置全部响应式注册表(含批量队列与层→缓冲区注册表) |
#### 变量监听与批量更新 #### 层→缓冲区注册表0.3.0
| 函数/宏 | 描述 | 响应式更新不再全量扫描 `(buffer-list)`:每条会写入 `tp-name` 的缓冲区路径tp-set 家族、栈变更函数、match/regexp 应用器)都把目标缓冲区登记到注册表,更新时只访问登记过的缓冲区。
| 函数/变量 | 描述 |
|------|------|
| `tp--layer-buffers` | 哈希表(`:test equal`):层名 → 展示该层的缓冲区列表。键存在但值为空表示"已知:无缓冲区展示该层",与键不存在(`unknown`)严格区分 |
| `tp-reactive--register-layer-buffer` | 幂等登记公开写入口tp-ops/tp-search/tp-stack 各自的注册助手最终都调用它);首次使用时安装 `kill-buffer-hook` 清理器 |
| `tp-reactive-layer-buffers` | 查询某层的已登记存活缓冲区,或返回符号 `unknown`;惰性剔除已死缓冲区 |
| `tp-reactive--buffer-layer-names` | 栈感知的缓冲区扫描:直接 `tp-name``tp-layers` 栈存储内的层(被覆盖或被隐藏)都算在场。`tp-reactive-track-buffer` 与匿名层 GC 的存活检查共用它 |
| `tp-reactive-track-buffer` | 交互命令:扫描缓冲区并登记其中的全部层。用于弥补"插入已带属性的字符串"绕过登记路径的已知缺口 |
| `tp-reactive--prune-killed-buffer` / `tp-reactive--install-kill-buffer-hook` | kill-buffer 时从注册表剔除死缓冲区(条目保留为空列表,即"已知:无" |
`unknown`tp-render 的更新走一次**学习性**全扫描并登记实际找到的缓冲区;一处都没找到的层刻意保持 `unknown`,以便之后经非登记路径(如字符串插入)出现时仍能被下次扫描发现。
#### 变量监听与批量队列
| 函数 | 描述 |
|------|------| |------|------|
| `tp--reactive-variable-watcher` | `add-variable-watcher` 回调;调用 `:watch` 后经 `tp--reactive-update-function` 委托重渲染 | | `tp--reactive-variable-watcher` | `add-variable-watcher` 回调;调用 `:watch` 后经 `tp--reactive-update-function` 委托重渲染 |
| `tp--invoke-layer-watchers` | 调用层的 `:watch` 回调 | | `tp--invoke-layer-watchers` | 调用层的 `:watch` 回调 |
| `tp-with-batch-updates` | 批量更新宏 | | `tp--queue-batch-update` | 将更新加入待处理队列 `tp--batch-update-pending`(刷新在 tp-render |
| `tp--queue-batch-update` | 将更新加入待处理队列 |
| `tp--flush-batch-updates` | 刷新队列,经 `tp--reactive-flush-function` 委托重渲染 |
钩子变量:`tp--reactive-update-function`、`tp--reactive-flush-function`(定义于此,由 tp-render.el 安装)。 钩子变量:`tp--reactive-update-function`(定义于此,由 tp-render.el 安装)。
--- ---
### tp-layer.el层定义与解析 ### tp-layer.el层定义、解析与层栈存储
依赖 tp-core、tp-reactive。提供 `define-tp` / `define-tps` 宏、层注册表、层名解析,以及层栈的数据结构原语。 依赖 tp-core、tp-reactive。提供 `define-tp` / `define-tps` 宏、层注册表、层名解析,以及 0.3.0 归位至此的**层栈存储编解码**与**匿名层完整生命周期**铸造、驻留、注销、GC
#### 层定义 #### 层定义
| 函数/宏 | 描述 | 依赖 | | 函数/宏 | 描述 | 依赖 |
|---------|------|------| |---------|------|------|
| `define-tp` | 定义单个自定义文本属性(层) | tp--define-layer-internal | | `define-tp` | 定义单个自定义文本属性(层);别名 `tp-define-layer` | tp--define-layer-internal |
| `define-tps` | 定义自定义文本属性组(层组);别名 `define-tp-group` | tp--define-layer-group-internal | | `define-tps` | 定义自定义文本属性组(层组);别名 `define-tp-group`、`tp-define-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--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-define-layer-args` | 解析 `:props` / `:data` / `:compute` / `:watch` / `:transform` | - |
| `tp--parse-layer-group-element` | 解析层组元素 | tp--layer-group-element-format | | `tp--parse-layer-group-element` | 解析层组元素 | tp--layer-group-element-format |
| `tp--define-layer-from-parsed` | 从解析结果定义层 | (与 define-tp 类似的依赖) | | `tp--define-layer-from-parsed` | 从解析结果定义层 | (与 define-tp 类似的依赖) |
| `tp--check-layer-cycle` | 检测循环层引用并报错 | tp--layer-expansion-stack | | `tp--check-layer-cycle` | 检测循环层引用并报错 | tp--layer-expansion-stack |
| `tp--anonymous-layer-name-for` | 匿名响应式层的驻留(`equal` 的 props 复用注册项) | - |
0.3.0 起参数化层/层组的 ARGLIST 可以声明**任意个**参数(此前仅限一个);`(LAYER ARG1 ... ARGN)` 与包裹形式 `(LAYER (ARG1 ... ARGN))``tp-set``tp-put-layer` 规格中均可用,实参数量不匹配会报出点名该层与两个数量的清晰错误。
#### 注册表与查询 #### 注册表与查询
| 函数/变量 | 描述 | | 函数/变量 | 描述 |
|------|------| |------|------|
| `tp-layer-alist` / `tp-layer-groups` / `tp-layer-transforms` | 层、层组、转换函数注册表 | | `tp-layer-alist` / `tp-layer-groups` / `tp-layer-transforms` | 层、层组、转换函数注册表 |
| `tp--group-generated-layers` | 层组 → 其定义生成的层 的注册表(组重定义/注销时随之清理) |
| `tp--set-layer-props` / `tp--set-group-layers` | 写入注册表 | | `tp--set-layer-props` / `tp--set-group-layers` | 写入注册表 |
| `tp-layer-props` / `tp-group-props` | 获取层/层组属性(`&optional INCLUDE-TP-NAME`,默认不含 `tp-name`;返回副本) | | `tp-layer-props` / `tp-group-props` | 获取层/层组属性(`&optional INCLUDE-TP-NAME`,默认不含 `tp-name`;返回副本) |
| `tp-layer-props-with-arg` / `tp-group-props-with-arg` | 参数化层/层组的属性求值 | | `tp-layer-props-with-arg` / `tp-group-props-with-arg` | 单参数形式0.3.0 起是 -with-args 的薄封装) |
| `tp-layer-props-with-args` / `tp-group-props-with-args` | 多参数形式ARGS 按位置绑定到层参数 |
| `tp-layer-arglist` | 返回参数化层的形参表副本(非参数化层返回 nil |
| `tp-layer-parameterized-p` / `tp-group-parameterized-p` | 是否参数化 | | `tp-layer-parameterized-p` / `tp-group-parameterized-p` | 是否参数化 |
| `tp-layer-reset` | 重置层系统 | | `tp-describe-layer` | 交互命令:在 help 缓冲区展示层的存储格式、形参表、原始定义体、展开属性、响应式依赖、transform 与所属层组(数据采集在 `tp--describe-layer-data` |
| `tp-undefine-layer` / `tp-undefine-group` | 删除层/层组(含其响应式依赖与转换) | | `tp-layer-reset` | 重置层系统(连带调用 `tp-reactive-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--resolve-props` | 解析属性(展开层名、多参数规格、`$var`、注册依赖、驻留匿名层 | tp-layer-props(-with-args), tp--collect-reactive-symbols, tp--resolve-reactive-symbols, tp--anonymous-layer-name-for, tp--register-reactive-deps |
| `tp--expand-layer-in-plist` | 展开 plist 中的层名键 | tp--is-layer-name-p | | `tp--expand-layer-in-plist` | 展开 plist 中的层名键 | tp--is-layer-name-p |
| `tp--expand-layer-to-props-list` | 层名展开为属性列表 | tp--check-layer-cycle |
#### 层栈数据结构原语 #### 匿名层机制与 GC0.3.0 归位/新增)
| 函数/变量 | 描述 |
|------|------|
| `tp--anonymous-layer-counter` | 匿名层名计数器。**刻意不被任何 reset 清零**:脱离缓冲区的字符串可能仍携带旧的 `tp-anon-N` 属性值,计数器单调递增保证新铸名字永不与之混淆 |
| `tp--generate-anonymous-layer-name` | 生成唯一的 `tp-anon-N` 符号 |
| `tp--anonymous-layer-registry` | 匿名响应式层驻留表:`equal` 的 props 规格复用既有注册项 |
| `tp--anonymous-layer-name-for` | 驻留查询/铸造入口 |
| `tp--buffer-has-layer-region-p` | 栈感知的存活检查:直接 `tp-name``tp-layers` 内(被覆盖/被隐藏)皆算存活 |
| `tp-gc-anonymous-layers` | 交互命令:回收已无任何已登记存活缓冲区展示的匿名层;注册表状态为 `unknown` 的层(可能仅被游离字符串引用)保守保留 |
#### 层栈存储编解码
层栈在原始文本属性上的编码/解码知识集中在这里tp-stack栈操作与 tp-render响应式写穿都向下调用它互不 require。
| 函数 | 描述 | | 函数 | 描述 |
|------|------| |------|------|
| `tp--normalize-layer-spec` | 规范化层规格 | | `tp--normalize-layer-spec` | 规范化层规格(含多参数 `(LAYER ARG1 ... ARGN)` |
| `tp--get-layer-stack` | 获取位置的层栈 | | `tp--build-layer-props` / `tp--layer-stack-to-list` | 旧式编解码原语(无隐藏层语义) |
| `tp--build-layer-props` | 从层列表构建属性 | | `tp--stack-hidden-p` | 层 plist 是否带 `tp-hidden` 标志 |
| `tp--layer-stack-to-list` | 将层栈转换为列表 | | `tp--stack-props-to-list` | 原始属性 → 有序层列表(顶层在前,含隐藏层)。有隐藏层时 `tp-layers` 持有完整栈,直接属性只是最顶可见层的渲染缓存 |
| `tp--stack-build-props` | 有序层列表 → 原始属性。单层栈不携带 `tp-layers`;含隐藏层时切换为"完整栈 + 渲染缓存"存储模式(全部隐藏时不渲染任何层属性) |
| `tp--get-layer-by-idx-or-name` | 通过索引或名称查找层 | | `tp--get-layer-by-idx-or-name` | 通过索引或名称查找层 |
钩子变量:`tp--layer-refresh-function`(定义于此,由 tp-render.el 安装为 `tp--update-layer-regions``tp--layer-refresh` 是它的调用入口,层重定义后经它触发已应用区域的重渲染。 钩子变量:`tp--layer-refresh-function`(定义于此,由 tp-render.el 安装为 `tp--update-layer-regions``tp--layer-refresh` 是它的调用入口,层重定义后经它触发已应用区域的重渲染。
--- ---
### tp-ops.el核心属性操作 ### tp-ops.el核心属性操作与 tp-text 处理链
依赖 tp-core、tp-layer。面向用户的核心属性读写函数直接调用 Emacs 原生文本属性 API。 依赖 tp-core、tp-reactive、tp-layer。面向用户的核心属性读写函数直接调用 Emacs 原生文本属性 API。0.3.0 起 `tp-text` 处理链从 tp-render 下沉至此,`tp-set` 等**同模块直接调用**它(不再经钩子变量)——因此只加载到 tp-ops 的部分加载也能得到可用的 `tp-text` 文本替换。
#### 参数解析 #### 参数解析
| 函数 | 描述 | 调用者 | | 函数 | 描述 | 调用者 |
|------|------|--------| |------|------|--------|
| `tp--parse-args` | 解析灵活的调用格式(整串/区域/层名) | tp-set, tp-reset, tp-add | | `tp--parse-args` | 解析灵活的调用格式(整串/区域/层名/多参数层 | tp-set, tp-reset, tp-add |
| `tp--apply-props-to-string` | 字符串路径的属性应用 | tp-set, tp-reset, tp-add | | `tp--apply-props-to-string` | 字符串路径的属性应用 | tp-set, tp-reset, tp-add |
| `tp--ops-register-layer-buffer` | 应用带 `tp-name` 的属性到缓冲区后,登记到层→缓冲区注册表 | tp-set, tp-reset, tp-add |
#### tp-text 处理链0.3.0 自 tp-render 迁入)
| 函数 | 描述 |
|------|------|
| `tp--handle-tp-text-property` | `tp-text` 属性的总入口:初始化/替换文本、双向同步响应式变量 |
| `tp--tp-text-replace` | 执行文本替换(缓冲区与字符串两条路径) |
| `tp--tp-text-transform` | 应用层的 `:transform`(首次渲染同样生效) |
| `tp--find-tp-text-reactive-var` | 找到层 `tp-text` 绑定的响应式变量 |
| `tp--merge-embedded-props` | 合并 tp-text 字符串内嵌属性与外部属性 |
| `tp--apply-reactive-text-props` | 把结果属性应用到替换文本(值未变的区段跳过写入,保持 buffer-modified 状态) |
| `tp--put-text-property-unless-equal` | 仅在值确实变化时写属性 |
#### 设置属性 #### 设置属性
| 函数 | 描述 | 依赖 | 被依赖 | | 函数 | 描述 | 依赖 | 被依赖 |
|------|------|------|--------| |------|------|------|--------|
| `tp-set` | 设置文本属性(保留其他属性) | tp--parse-args, tp--handle-tp-text | tp-match-set, 层操作 | | `tp-set` | 设置文本属性(保留其他属性) | tp--parse-args, tp--handle-tp-text-property, tp--ops-register-layer-buffer | tp-match-set, 层操作 |
| `tp-reset` | 完全替换所有文本属性 | tp--parse-args, tp--handle-tp-text | tp-match-reset | | `tp-reset` | 完全替换所有文本属性 | 同上 | tp-match-reset |
| `tp-add` | 深度合并属性 | tp--parse-args, tp--deep-merge-plist, tp--prepend-face | tp-match-add | | `tp-add` | 深度合并属性 | 同上 + tp--deep-merge-plist, tp--prepend-face | tp-match-add |
#### 获取属性 #### 获取属性
| 函数 | 描述 | 依赖 | 被依赖 | | 函数 | 描述 | 依赖 | 被依赖 |
@ -226,39 +286,39 @@ tp-core → tp-reactive → tp-layer → tp-ops → tp-search
| 函数 | 描述 | 依赖 | 被依赖 | | 函数 | 描述 | 依赖 | 被依赖 |
|------|------|------|--------| |------|------|------|--------|
| `tp-remove` | 移除属性或子属性 | tp--remove-property, tp--remove-sub, tp--remove-*-from-string | 用户 API | | `tp-remove` | 移除属性或子属性 | tp--remove-property, tp--remove-sub, tp--remove-*-from-string | 用户 API |
| `tp-clear` | 清除所有属性 | - | 用户 API | | `tp-clear` | 清除所有属性(显式返回 nil | - | 用户 API |
钩子变量:`tp--tp-text-handler-function`(定义于此,由 tp-render.el 安装为 `tp--handle-tp-text-property``tp--handle-tp-text` 是它的调用入口,未安装时 `tp-text` 属性按普通属性处理。
--- ---
### tp-search.el模式匹配与搜索 ### tp-search.el模式匹配与搜索
依赖 tp-core、tp-layer、tp-ops。提供模式匹配式属性应用、属性搜索与导航。 依赖 tp-core、tp-reactive、tp-layer、tp-ops0.3.0 新增 tp-reactive 依赖:应用器写入缓冲区后经 `tp--search-register-layer-buffer` 登记层→缓冲区注册表)。提供模式匹配式属性应用、属性搜索与导航。
#### 模式匹配 #### 模式匹配
| 函数 | 描述 | 依赖 | | 函数 | 描述 | 依赖 |
|------|------|------| |------|------|------|
| `tp-match-set` / `tp-match-reset` / `tp-match-add` | 在字符串匹配处设置/重置/合并属性 | tp--match-apply | | `tp-match-set` / `tp-match-reset` / `tp-match-add` | 在字符串匹配处设置/重置/合并属性0.3.0 起接受 START/END 界限(视同只存在该部分;颠倒的界限自动交换) | tp--match-apply |
| `tp-regexp-set` / `tp-regexp-reset` / `tp-regexp-add` | 在正则匹配处设置/重置/合并属性 | tp--regexp-apply | | `tp-regexp-set` / `tp-regexp-reset` / `tp-regexp-add` | 在正则匹配处设置/重置/合并属性0.3.0 起额外接受 SUBEXP属性作用于每个匹配的该捕获组超出组数报清晰错误 | tp--regexp-apply |
| `tp--match-apply` / `tp--regexp-apply` | 字面/正则匹配的入口(含多模式支持) | tp--pattern-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--pattern-apply` / `tp--pattern-apply-single` | 共享的模式匹配引擎(空模式/零宽模式安全;承载 START/END/SUBEXP | tp-set/tp-reset/tp-add 风格的 apply-fn |
| `tp--deep-merge-apply` / `tp--reset-apply` | 传给引擎的合并/重置回调 | tp--deep-merge-plist 等 | | `tp--deep-merge-apply` / `tp--reset-apply` | 传给引擎的合并/重置回调(缓冲区路径顺带登记注册表) | tp--deep-merge-plist, tp--search-register-layer-buffer |
| `tp--search-register-layer-buffer` | 登记助手,转发到 `tp-reactive--register-layer-buffer` | tp-reactive |
#### 搜索和导航 #### 搜索和导航
| 函数 | 描述 | 依赖 | | 函数 | 描述 | 依赖 |
|------|------|------| |------|------|------|
| `tp-search-forward` | 向前搜索属性 | text-property-search-forward | | `tp-forward` | 向前搜索 N 次并移动点0.3.0 起接受 PREDICATE 与 NOT-CURRENT默认保持 0.2.0 的对称 `equal` 匹配契约) | text-property-search-forward |
| `tp-search-backward` | 向后搜索属性 | tp--property-search-backward | | `tp-backward` | 向后搜索 N 次并移动点(与向前语义对称,同样新增 PREDICATE/NOT-CURRENT | tp--property-search-backward |
| `tp--property-search-backward` | 带等值谓词的向后搜索(与向前语义对称) | text-property-search-backward | | `tp--property-search-backward` | 带谓词的向后搜索引擎 | text-property-search-backward |
| `tp-forward` | 向前搜索 N 次并移动点 | tp-search-forward | | `tp--property-match-p` | 谓词归一化nil/t → `equal`;函数 → `(funcall PRED VALUE PROP-VALUE)` | - |
| `tp-backward` | 向后搜索 N 次并移动点 | tp-search-backward | | `tp--string-property-matches` | 字符串路径的按段匹配收集器 | - |
| `tp-search` | 收集所有匹配区间 | tp-intervals 等 | | `tp-search` | 收集所有匹配区间 | tp-intervals 等 |
| `tp-search-forward` / `tp-search-backward` | **已废弃0.3.0make-obsolete**裸封装原语nil-PREDICATE 默认语义与库内 `equal` 匹配相悖;请改用 `tp-forward` / `tp-backward`,或直接用 Emacs 原语 | text-property-search-* |
#### 遍历与替换 #### 遍历与替换
| 函数 | 描述 | 依赖 | | 函数 | 描述 | 依赖 |
|------|------|------| |------|------|------|
| `tp-forward-do` / `tp-backward-do` | 向前/向后搜索并对匹配执行函数 | tp--forward-do / tp--backward-do | | `tp-forward-do` / `tp-backward-do` | 向前/向后搜索并在第 TIMES 个匹配处执行函数(同样透传 PREDICATE/NOT-CURRENT | tp--forward-do / tp--backward-do |
| `tp--forward-do` / `tp--backward-do` | 单方向遍历的内部实现 | tp--replace-match-text | | `tp--forward-do` / `tp--backward-do` | 单方向遍历的内部实现 | tp--replace-match-text |
| `tp-search-map` | 对所有匹配应用函数FUNCTION 接收 TEXT &optional START END IDX | tp--search-do | | `tp-search-map` | 对所有匹配应用函数FUNCTION 接收 TEXT &optional START END IDX | tp--search-do |
| `tp--search-do` | 搜索遍历的内部实现 | tp--replace-match-text | | `tp--search-do` | 搜索遍历的内部实现 | tp--replace-match-text |
@ -268,51 +328,64 @@ tp-core → tp-reactive → tp-layer → tp-ops → tp-search
### tp-render.el响应式渲染引擎 ### tp-render.el响应式渲染引擎
依赖 tp-core、tp-reactive、tp-layer、tp-ops、tp-search。这是唯一"知道"渲染如何进行的模块:它可以直接调用 `tp-search-map`、`tp-add` 等前置模块的函数,并在加载末尾把自己的入口函数**安装**进下层模块预留的钩子变量。 依赖 tp-core、tp-reactive、tp-layer、tp-ops、tp-search。这是唯一"知道"渲染如何进行的模块:它直接调用 `tp-search-map`、`tp--tp-text-transform`、`tp--apply-reactive-text-props`(后两者位于 tp-ops——这条 require 是真实的下行调用,不只是加载顺序),并在加载末尾把自己的入口函数**安装**进下层模块预留的两个钩子变量。0.3.0 起批量更新宏与刷新逻辑也位于此。
#### 缓冲区遍历0.3.0:注册表驱动)
| 函数 | 描述 | 依赖 |
|------|------|------|
| `tp--render-visit-buffer` | 单缓冲区访问接缝(测试可包裹它统计访问次数) | tp-with-current-buffer |
| `tp--map-layer-buffers` | 在可能展示该层的缓冲区中执行更新WHERE 为缓冲区setq-local时只走它否则查注册表只访问已登记缓冲区`unknown` 层回退为一次学习性 `(buffer-list)` 全扫描并登记实际命中的缓冲区 | tp-reactive-layer-buffers, tp--buffer-has-layer-region-p |
#### 重渲染 #### 重渲染
| 函数 | 描述 | 依赖 | | 函数 | 描述 | 依赖 |
|------|------|------| |------|------|------|
| `tp--update-layer-regions` | 重渲染携带某层的所有文本区域(替换该层自己的属性键,保留其他来源属性) | tp--layer-render-props, tp-search-map | | `tp--update-layer-regions` | 重渲染携带某层的所有文本区域(替换该层自己的属性键,保留其他来源属性),并**写穿**到 `tp-layers` 栈存储 | tp--layer-render-props, tp-search-map, tp--write-layer-through-stack-storage |
| `tp--write-layer-through-stack-storage` | 把新属性写进栈存储里该层的条目(被覆盖或被隐藏的副本也保持最新,`tp-show-layer` 后渲染当前值而非陈旧快照;值未变的段不触碰缓冲区) | tp--stack-props-to-list, tp--stack-build-props [tp-layer] |
| `tp--merge-props-into-stack-entry` | 更新栈条目的键,保留其 `tp-hidden` 标志与栈位置 | - |
| `tp--update-layer-computed` | 更新 `:compute` 计算属性nil 值可正常传播) | tp--resolve-reactive-symbols, tp--set-layer-props | | `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--layer-render-props` / `tp--layer-reactive-props` | 求取层的渲染属性 | tp-layer-props |
#### 响应式文本tp-text #### 响应式文本tp-text
| 函数 | 描述 | 依赖 | | 函数 | 描述 | 依赖 |
|------|------|------| |------|------|------|
| `tp--handle-tp-text-property` | 处理 `tp-text` 属性(文本替换) | tp--tp-text-replace | | `tp--update-reactive-text` | 变量变化后更新响应式文本 | tp--replace-reactive-text-in-buffer, tp--map-layer-buffers |
| `tp--update-reactive-text` | 变量变化后更新响应式文本 | tp--replace-reactive-text-in-buffer | | `tp--replace-reactive-text-in-buffer` | 在缓冲区中替换响应式文本0.3.0**最小差异编辑**——修剪公共前后缀只编辑差异区段,且先插入后删除,未变文本内的点位与标记不动;文本相同的更新完全不触碰缓冲区) | tp--edit-region-minimal-diff |
| `tp--replace-reactive-text-in-buffer` | 在缓冲区中替换响应式文本 | - | | `tp--edit-region-minimal-diff` | 最小差异编辑原语 | - |
| `tp--tp-text-transform` | 应用 `:transform` 转换(首次渲染同样生效) | tp-layer-transforms | | `tp--pos-holds-layer-in-storage-only-p` | 某位置的层是否只存在于栈存储(隐藏/被覆盖,跳过可见文本替换) | - |
#### 批量更新0.3.0 自 tp-reactive 迁入)
| 函数/宏 | 描述 |
|------|------|
| `tp-with-batch-updates` | 批量更新宏BODY 内的多次变量修改合并为一次刷新(队列变量仍在 tp-reactive宏向下 let 绑定它们) |
| `tp--flush-batch-updates` | 刷新队列,按层去重后**直接调用** `tp--reactive-flush-entry`(不再经钩子) |
| `tp--reactive-flush-entry` | 单条刷新的工作函数(属性更新或 tp-text 替换) |
#### 引擎入口与钩子安装 #### 引擎入口与钩子安装
| 函数 | 描述 | | 函数 | 描述 |
|------|------| |------|------|
| `tp--reactive-apply-update` | 变量变化的完整处理:更新 computed、合并层定义、重渲染或入批量队列嵌套写入经队列而非递归。安装为 `tp--reactive-update-function` | | `tp--reactive-apply-update` | 变量变化的完整处理:更新 computed、合并层定义、重渲染或入批量队列嵌套写入经队列而非递归。尾部刷新置于 `unwind-protect` 清理段中,重渲染抛错也不会把队列条目困死。安装为 `tp--reactive-update-function` |
| `tp--reactive-flush-entry` | 批量队列刷新时的重渲染入口。安装为 `tp--reactive-flush-function` |
加载末尾执行安装: 加载末尾执行安装(与源码逐字一致)
```elisp ```elisp
(setq tp--reactive-update-function #'tp--reactive-apply-update) (setq tp--reactive-update-function #'tp--reactive-apply-update)
(setq tp--reactive-flush-function #'tp--reactive-flush-entry) (setq tp--layer-refresh-function #'tp--update-layer-regions)
(setq tp--tp-text-handler-function #'tp--handle-tp-text-property)
(setq tp--layer-refresh-function #'tp--update-layer-regions)
``` ```
--- ---
### tp-stack.el属性层栈操作 ### tp-stack.el属性层栈操作
依赖 tp-core、tp-layer、tp-ops。所有栈变更函数建立在共享的裁剪式区域遍历之上,区域操作不会影响 [START, END) 之外的文本。 依赖 tp-core、tp-reactive、tp-layer——**不依赖 tp-ops**0.2.0 的幻影依赖已在 0.3.0 移除,独立字节编译无警告)。所有栈变更函数建立在共享的裁剪式区域遍历之上,区域操作不会影响 [START, END) 之外的文本;栈的存储编解码在 tp-layer向下调用。0.3.0 起所有栈变更函数**返回实际修改的属性段数量**0 表示无匹配;`tp-put-layer`/`tp-push-layer` 例外,仍返回 OBJECT 或 `(START . END)`),每次改写后经 `tp--stack-register-layers` 登记层→缓冲区注册表。字符串形式**原地修改**字符串(与 `tp-set` 的复制语义不同,各函数 docstring 均有警示)
#### 内部助手 #### 内部助手
| 函数 | 描述 | 依赖 | | 函数 | 描述 | 依赖 |
|------|------|------| |------|------|------|
| `tp--parse-layer-args` | 解析层操作的灵活参数 | - | | `tp--parse-layer-args` | 解析层操作的灵活参数 | - |
| `tp--stack-map-region` | 按区间遍历区域内层栈的共享引擎 | tp--map-intervals 风格遍历 | | `tp--plist-remove` | 返回去掉某键的 plist 副本 | - |
| `tp--stack-build-props` | 从层列表构建栈属性(单层栈不携带 `tp-layers` | - | | `tp--stack-map-region` | 按区间遍历区域内层栈的共享引擎(解码经 tp--stack-props-to-list含隐藏层 | tp--map-intervals [tp-core], tp--stack-props-to-list [tp-layer] |
| `tp--put-layer-specs` | 展开层规格(层名/内联 plist/层名列表/参数化/层组) | tp--normalize-layer-spec, tp-group-props(-with-arg) | | `tp--stack-register-layers` | 把新栈中每个带 `tp-name` 的层(含被覆盖与隐藏的)登记到缓冲区注册表 | tp-reactive--register-layer-buffer [tp-reactive] |
| `tp--put-layer-specs` | 展开层规格(层名/内联 plist/层名列表/参数化/层组) | tp--normalize-layer-spec, tp-group-props(-with-arg) [tp-layer] |
| `tp--move-layer-in-stack` | 在栈中移动层 | 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--raise-layer-in-stack` | 在栈中上下移动层 | tp--move-layer-in-stack |
| `tp--switch-layers-in-stack` | 交换两个层的位置 | tp--get-layer-by-idx-or-name | | `tp--switch-layers-in-stack` | 交换两个层的位置 | tp--get-layer-by-idx-or-name |
@ -320,25 +393,29 @@ tp-core → tp-reactive → tp-layer → tp-ops → tp-search
#### 层操作(公开 API #### 层操作(公开 API
| 函数 | 描述 | 依赖 | | 函数 | 描述 | 依赖 |
|------|------|------| |------|------|------|
| `tp-put-layer` | 在指定索引放置层(区域局部) | tp--put-layer-specs, tp--stack-map-region | | `tp-put-layer` | 在指定索引放置层(区域局部0.3.0 新增尾参 NOERROR未定义层名返回 nil 而非报错 | tp--put-layer-specs, tp--stack-map-region |
| `tp-push-layer` | 将层推到顶部 | tp-put-layer | | `tp-push-layer` | 将层推到顶部(同样支持 NOERROR | tp-put-layer |
| `tp-delete-layer` | 删除层 | tp--stack-map-region | | `tp-delete-layer` | 删除层 | tp--stack-map-region |
| `tp-pop-layer` | 弹出顶层 | tp-delete-layer | | `tp-pop-layer` | 弹出顶层 | tp-delete-layer |
| `tp-move-layer` | 移动层到指定位置 | tp--move-layer-in-stack, tp--stack-map-region | | `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-raise-layer` | 上移层 | tp--raise-layer-in-stack, tp--stack-map-region |
| `tp-rotate-layer` | 轮换层 | tp-move-layer | | `tp-lower-layer` | 下移层0.3.0 新增tp-raise-layer 的镜像) | tp--raise-layer-in-stack, tp--stack-map-region |
| `tp-pin-layer` | 将层置顶 | tp-move-layer | | `tp-rotate-layer` | 轮换层0.3.0:规范顺序 `(START END DIRECTION [COUNT] [OBJECT])`,凭 `up`/`down` 符号无歧义分派;旧顺序永久兼容;单趟栈旋转实现) | tp--stack-map-region |
| `tp-pin-layer` | 将层一次性移到栈顶(不阻止后续 push 覆盖) | tp-move-layer |
| `tp-switch-layer` | 交换两个层 | tp--switch-layers-in-stack, tp--stack-map-region | | `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-hide-layer` | 隐藏层0.3.0 新增):层留在栈中、继续接收响应式更新但不渲染;隐藏可见顶层则显露下一可见层;全部隐藏时文本仅剩 `tp-layers` 记账属性 | tp--stack-map-region, tp--stack-build-props [tp-layer] |
| `tp-flatten-layers` | 扁平化所有层 | tp--merge-layer-props, tp--stack-map-region | | `tp-show-layer` | 取消隐藏0.3.0 新增) | 同上 |
| `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-list` | 列出所有层名称(含隐藏层) | tp--stack-map-region |
| `tp-layer-count` | 计算层数量 | tp--stack-map-region | | `tp-layer-count` | 计算层数量(含隐藏层) | tp--stack-map-region |
| `tp-layer-exists-p` | 检查层是否存在 | tp-layer-list | | `tp-layer-exists-p` | 检查层是否存在 | tp-layer-list |
| `tp-layer-top` | 获取顶层名称(覆盖整个请求区域) | tp--stack-map-region | | `tp-layer-top` | 获取顶层名称(覆盖整个请求区域;按栈序报告最顶层,即使它被隐藏) | tp--stack-map-region |
| `tp-layer-stack-at` | 单个位置的完整有序层栈:`(NAME . PROPS)` 列表,顶层在前,隐藏层以 PROPS 中的 `tp-hidden t` 标识0.3.0 新增) | tp--stack-props-to-list [tp-layer] |
| `tp-region-layer-props` | 获取区域中特定层的属性 | tp--stack-map-region | | `tp-region-layer-props` | 获取区域中特定层的属性 | tp--stack-map-region |
#### 层属性操作 #### 层属性操作
@ -351,16 +428,18 @@ tp-core → tp-reactive → tp-layer → tp-ops → tp-search
### tp-palette.el调色板数据 ### tp-palette.el调色板数据
只依赖 tp-core及 subr-x。明/暗主题双值调色板系统,`tp-palette-alist` 是唯一数据源。 **不依赖任何 tp- 模块**(仅 subr-x是独立的叶模块。明/暗主题双值调色板系统,`tp-palette-alist` 是唯一数据源。
| 函数/宏/变量 | 描述 | | 函数/宏/变量 | 描述 |
|------|------| |------|------|
| `define-tp-palette` | 定义调色板(重定义立即生效) | | `define-tp-palette` | 定义调色板(重定义立即生效);别名 `tp-define-palette` |
| `tp-palette-alist` | 调色板注册表(唯一数据源) | | `tp-palette-alist` | 调色板注册表(唯一数据源) |
| `tp-parse-color` | 解析颜色规格(支持 `("light" . "dark")` 及单边 cons | | `tp-parse-color` | 解析颜色规格(支持 `("light" . "dark")` 及单边 cons |
| `tp-theme-dark-p` / `tp-theme-light-p` | 当前主题判断 | | `tp-theme-dark-p` / `tp-theme-light-p` | 当前主题判断 |
| `tp-palette-fg-color` / `tp-palette-bg-color` / `tp-palette-border-color` | 取前景/背景/边框色 | | `tp-palette-color` | 通用的主题解析取色器0.3.0 新增的首选查询入口) |
| `tp-palette-p` / `tp-palette-fg-p` / `tp-palette-bg-p` / `tp-palette-fbg-p` / `tp-palette-border-p` | 调色板谓词 | | `tp-palette-has-p` | 谓词整合入口KIND 取 `:fg`/`:bg`/`:border`/nil0.3.0 新增) |
| `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-palette-pure` | 取纯色值 |
--- ---
@ -374,20 +453,46 @@ tp-core → tp-reactive → tp-layer → tp-ops → tp-search
| 内置层 | `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-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-pop-to-buffer` / `tp-switch-to-buffer` | 显示带属性文本的缓冲辅助宏q 绑定在缓冲区局部 minor-mode keymap 中) |
| `tp-palette-show` | 展示所有调色板 | | `tp-palette-show` | 展示所有调色板 |
| `tp-suffix-symbol` | 符号加后缀助手 | | `tp--suffix-symbol` | 符号加后缀助手0.3.0 起转为私有;`tp-suffix-symbol` 保留为废弃兼容别名) |
--- ---
## 钩子变量:唯一许可的反向调用 ## 钩子变量:唯一许可的反向调用
分层规则的唯一例外是个**钩子变量**:下层模块声明变量并在需要时 `funcall`,实现由 tp-render.el 在加载时安装。这样下层模块不必 `require` 上层模块,依赖图保持严格单向;而在未加载 tp-render 时,下层模块依然可用(钩子为 nil 时优雅降级)。 分层规则的唯一例外是个**钩子变量**:下层模块声明变量并在需要时 `funcall`,实现由 tp-render.el 在加载时安装。这样下层模块不必 `require` 上层模块,依赖图保持严格单向;而在未加载 tp-render 时,下层模块依然可用(钩子为 nil 时优雅降级:层可以定义与应用,只是没有自动重渲染)。
| 钩子变量 | 声明于 | 安装的实现tp-render.el | 用途 | | 钩子变量 | 声明于 | 安装的实现tp-render.el | 用途 |
|----------|--------|---------------------------|------| |----------|--------|---------------------------|------|
| `tp--reactive-update-function` | tp-reactive.el | `tp--reactive-apply-update` | 变量监听器触发的重计算与重渲染 | | `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--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` 属性 |
0.2.0 时钩子有四个0.3.0 删掉了其中两个,代之以真实的模块内/下行调用:
- `tp--tp-text-handler-function`(原声明于 tp-ops整条 `tp-text` 处理链移入 tp-ops`tp-set` 等直接调用 `tp--handle-tp-text-property`。副产品:只加载 tp-ops 的部分加载也能完成 `tp-text` 文本替换。
- `tp--reactive-flush-function`(原声明于 tp-reactive`tp-with-batch-updates` 与 `tp--flush-batch-updates` 移入 tp-render刷新直接调用 `tp--reactive-flush-entry`。副产品:部分加载下批量刷新不再被静默丢弃,而是诚实地报 void-function。
留下的两个钩子对应真正**源自下层的事件**(变量被 set、层被重定义无法在不打破分层的前提下改写为下行调用。
---
## 可变状态清单
各模块持有的可变运行时状态及其清理入口0.3.0 全面核对):
| 模块 | 状态 | 描述 | 清理 |
|------|------|------|------|
| tp-core | —— | **无可变状态**(仅 `tp-debug-mode`/`tp-debug-echo` 两个用户选项;调试日志写入 *tp-debug* 缓冲区,由 `tp-debug-clear` 清除) | - |
| tp-reactive | `tp-reactive-deps` | 变量 → 依赖层 注册表 | `tp-reactive-reset` |
| tp-reactive | `tp-layer-watchers` / `tp-layer-computed` / `tp-layer-data` | `:watch` / `:compute` / `:data` 注册表 | `tp-reactive-reset` |
| tp-reactive | `tp--batch-update-pending` | 批量更新队列0.3.0 起也被 reset 清空,防止残留条目对新定义的层重放) | `tp-reactive-reset` |
| tp-reactive | `tp--layer-buffers` | 层→缓冲区注册表哈希表0.3.0 新增) | `tp-reactive-reset`clrhash单层条目随 `tp-undefine-layer`/层重定义移除;死缓冲区经 kill-buffer-hook 与惰性访问剔除 |
| tp-reactive | `tp--batch-update-active` / `tp--reactive-updating` | 动态标志let 绑定,非持久状态) | 随作用域退出 |
| tp-layer | `tp-layer-alist` / `tp-layer-groups` / `tp-layer-transforms` | 层、层组、转换注册表 | `tp-layer-reset` |
| tp-layer | `tp--group-generated-layers` | 层组生成的层 | `tp-layer-reset` |
| tp-layer | `tp--anonymous-layer-registry` | 匿名层驻留表 | `tp-layer-reset`;单条随 `tp-undefine-layer` / `tp-gc-anonymous-layers` |
| tp-layer | `tp--anonymous-layer-counter` | 匿名层名计数器——**刻意不清零**(任何 reset 都不动它):游离字符串上残留的 `tp-anon-N` 名字永远不能与新铸层重名 | 从不 |
`tp-reactive-reset` 移除全部变量监听器并清空上表 tp-reactive 各行;`tp-layer-reset` 先调用 `tp-reactive-reset`,再清空 tp-layer 各注册表(计数器除外)。
--- ---
@ -401,26 +506,31 @@ tp-set [tp-ops]
├── tp--parse-args [tp-ops] ├── tp--parse-args [tp-ops]
│ ├── tp--merge-duplicate-keys [tp-core] │ ├── tp--merge-duplicate-keys [tp-core]
│ └── tp--resolve-props [tp-layer] │ └── tp--resolve-props [tp-layer]
│ ├── tp-layer-props │ ├── tp-layer-props / tp-layer-props-with-args [tp-layer]
│ ├── tp--collect-reactive-symbols [tp-core] │ ├── tp--collect-reactive-symbols [tp-core]
│ ├── tp--resolve-reactive-symbols [tp-core] │ ├── tp--resolve-reactive-symbols [tp-core]
│ ├── tp--anonymous-layer-name-for [tp-layer]$var 匿名层驻留)
│ └── tp--register-reactive-deps [tp-reactive] │ └── tp--register-reactive-deps [tp-reactive]
├── tp--handle-tp-text [tp-ops] ├── tp--handle-tp-text-property [tp-ops]0.3.0 起同模块直接调用,不再经钩子)
╌╌▷ tp--handle-tp-text-property [tp-render](经钩子) └── tp--tp-text-transform / tp--tp-text-replace [tp-ops]
├── tp--apply-props-to-string [tp-ops](整串形式,返回新字符串) ├── tp--apply-props-to-string [tp-ops](整串形式,返回新字符串)
└── set-text-properties / put-text-propertyEmacs 原生,区域形式) ├── set-text-properties / put-text-propertyEmacs 原生,区域形式)
└── tp--ops-register-layer-buffer [tp-ops](缓冲区目标)
└── tp-reactive--register-layer-buffer [tp-reactive]
``` ```
### tp-add 调用链 ### tp-add 调用链
``` ```
tp-add [tp-ops] tp-add [tp-ops]
├── tp--parse-args [tp-ops] ├── tp--parse-args [tp-ops]
├── tp--handle-tp-text [tp-ops] ╌╌▷ tp--handle-tp-text-property [tp-render] ├── tp--handle-tp-text-property [tp-ops](直接调用)
├── text-properties-atEmacs 原生) ├── text-properties-atEmacs 原生)
├── tp--prepend-face [tp-core]face 家族属性) ├── tp--prepend-face [tp-core]face 家族属性)
│ └── tp--deep-merge-plist [tp-core] │ └── tp--deep-merge-plist [tp-core]
├── tp--deep-merge-plist [tp-core](其他嵌套属性) ├── tp--deep-merge-plist [tp-core](其他嵌套属性)
└── put-text-propertyEmacs 原生) ├── put-text-propertyEmacs 原生)
└── tp--ops-register-layer-buffer [tp-ops]
└── tp-reactive--register-layer-buffer [tp-reactive]
``` ```
### define-tp 调用链 ### define-tp 调用链
@ -429,7 +539,7 @@ define-tp [tp-layer](宏)
└── tp--define-layer-internal [tp-layer] └── tp--define-layer-internal [tp-layer]
├── tp--parse-define-layer-args [tp-layer] ├── tp--parse-define-layer-args [tp-layer]
├── tp--collect-reactive-symbols [tp-core] ├── tp--collect-reactive-symbols [tp-core]
├── tp--unregister-reactive-deps [tp-reactive] ├── tp--unregister-reactive-deps [tp-reactive](连带移除旧的缓冲区注册表条目)
├── tp--ensure-reactive-variables [tp-reactive] ├── tp--ensure-reactive-variables [tp-reactive]
├── tp--register-layer-data [tp-reactive] ├── tp--register-layer-data [tp-reactive]
│ └── add-variable-watcherEmacs 原生) │ └── add-variable-watcherEmacs 原生)
@ -455,8 +565,11 @@ tp-push-layer [tp-stack]
│ │ └── tp-layer-props [tp-layer] │ │ └── tp-layer-props [tp-layer]
│ └── tp-group-props / tp-group-props-with-arg [tp-layer] │ └── tp-group-props / tp-group-props-with-arg [tp-layer]
└── tp--stack-map-region [tp-stack](裁剪到 [START, END) └── tp--stack-map-region [tp-stack](裁剪到 [START, END)
├── tp--stack-build-props [tp-stack] ├── tp--stack-props-to-list [tp-layer](解码既有栈,含隐藏层)
└── set-text-propertiesEmacs 原生) ├── tp--stack-build-props [tp-layer](编码新栈/渲染缓存)
├── set-text-propertiesEmacs 原生)
└── tp--stack-register-layers [tp-stack]
└── tp-reactive--register-layer-buffer [tp-reactive]
``` ```
### 响应式更新调用链 ### 响应式更新调用链
@ -470,15 +583,27 @@ tp-push-layer [tp-stack]
│ └── tp--set-layer-props [tp-layer] │ └── tp--set-layer-props [tp-layer]
├── tp--set-layer-props [tp-layer]深合并回层定义setq-local 不写全局) ├── tp--set-layer-props [tp-layer]深合并回层定义setq-local 不写全局)
├── tp--update-layer-regions [tp-render](属性更新) ├── tp--update-layer-regions [tp-render](属性更新)
│ └── tp-search-map [tp-search] │ └── tp--map-layer-buffers [tp-render]
│ └── put-text-property │ │只访问注册表登记的缓冲区unknown 层回退为
│ │ 一次学习性全扫描并登记命中缓冲区)
│ ├── tp-reactive-layer-buffers [tp-reactive]
│ ├── tp--buffer-has-layer-region-p [tp-layer](回退路径)
│ └── 每缓冲区:
│ ├── tp-search-map [tp-search] → put-text-property
│ └── tp--write-layer-through-stack-storage [tp-render]
│ └── tp--stack-props-to-list /
│ tp--stack-build-props [tp-layer]
│ (隐藏/被覆盖的层副本同步刷新)
└── tp--update-reactive-text [tp-render]tp-text 文本替换) └── tp--update-reactive-text [tp-render]tp-text 文本替换)
└── tp--replace-reactive-text-in-buffer [tp-render] └── tp--replace-reactive-text-in-buffer [tp-render]
└── tp--edit-region-minimal-diff [tp-render]
(最小差异、先插入后删除;文本相同则完全不动缓冲区)
批量模式tp-with-batch-updates/ 更新中的嵌套写入: 批量模式tp-with-batch-updates [tp-render]/ 更新中的嵌套写入:
└── tp--queue-batch-update [tp-reactive](入队,不递归) └── tp--queue-batch-update [tp-reactive](入队,不递归)
└── tp--flush-batch-updates [tp-reactive](退出批量时) └── tp--flush-batch-updates [tp-render](退出批量/最外层更新结束时;
└── ╌╌▷ tp--reactive-flush-entry [tp-render](经钩子) 置于 unwind-protect 清理段,重渲染抛错也会排空队列)
└── tp--reactive-flush-entry [tp-render]0.3.0 起同模块直接调用,不再经钩子)
├── tp--update-layer-regions ├── tp--update-layer-regions
└── tp--update-reactive-text └── tp--update-reactive-text
``` ```
@ -487,9 +612,9 @@ tp-push-layer [tp-stack]
## 设计原则 ## 设计原则
1. **严格分层**:模块只允许 `require` 并调用排在它前面的模块,字节编译器强制检查依赖顺序 1. **严格分层**:模块只允许 `require` 并调用排在它前面的模块,字节编译器强制检查依赖顺序且只声明真实存在的依赖0.3.0 移除了 tp-stack→tp-ops 的幻影依赖tp-palette 不依赖任何 tp- 模块)
2. **钩子反转**:唯一许可的"向上调用"是四个钩子变量(`tp--tp-text-handler-function`、`tp--reactive-update-function`、`tp--reactive-flush-function`、`tp--layer-refresh-function`),由 tp-render.el 统一安装实现 2. **钩子反转**:唯一许可的"向上调用"是两个钩子变量(`tp--reactive-update-function`、`tp--layer-refresh-function`),由 tp-render.el 统一安装实现能改写为下行调用的反转tp-text 链、批量刷新)已在 0.3.0 改写掉
3. **单一职责**:每个模块(和函数)只负责一件事 3. **单一职责**:每个模块(和函数)只负责一件事;一个子系统的完整生命周期住在一个模块里(匿名层的铸造/驻留/注销/GC 全在 tp-layer层栈存储格式知识全在 tp-layer 的编解码器)
4. **复用优先**:共享引擎(`tp--map-intervals`、`tp--stack-map-region`、`tp--pattern-apply`、`tp--replace-match-text`)承载重复逻辑,高层函数复用而非复制 4. **复用优先**:共享引擎(`tp--map-intervals`、`tp--stack-map-region`、`tp--pattern-apply`、`tp--replace-match-text`、`tp-reactive--buffer-layer-names`)承载重复逻辑,高层函数复用而非复制
5. **统一接口**:所有核心属性函数支持相同的调用约定(整串/区域形式、层名、`$var` 5. **统一接口**:所有核心属性函数支持相同的调用约定(整串/区域形式、层名、`$var`、多参数层
6. **响应式解耦**tp-reactive/tp-layer/tp-ops 不依赖渲染引擎;不加载 tp-render 时钩子为 nil各模块优雅降级 6. **响应式解耦**tp-reactive/tp-layer/tp-ops 不依赖渲染引擎;不加载 tp-render 时钩子为 nil各模块优雅降级`tp-text` 替换自 0.3.0 起随 tp-ops 即可用);重渲染只访问层→缓冲区注册表登记的缓冲区,未知层才回退全扫描

View File

@ -564,6 +564,317 @@
(list (tp-forward-do #'upcase 'marker nil str 3) (list (tp-forward-do #'upcase 'marker nil str 3)
(substring-no-properties str)))) (substring-no-properties str))))
;; ---- 0.3.0: search bounds and SUBEXP ----
;; Compared via tp-search / tp-at accessors, not prin1 output, so the
;; property print order difference between Emacs 28 and 29+ cannot bite.
(chk "V3-match-bounds" '((10 . 14))
(with-temp-buffer
(insert "TODO one TODO two")
(tp-match-set "TODO" '(face warning) nil 5 18)))
(chk "V3-subexp" '(((8 10 bold) (13 14 bold)) ((0 3 bold)))
(list (tp-search (tp-regexp-set "\\([0-9]+\\)px" '(face bold)
"margin: 10px 4px" nil nil 1)
'face)
;; group 1 does not participate in the "bar" match
(tp-search (tp-regexp-set "\\(foo\\)\\|bar" '(face bold)
"foo bar" nil nil 1)
'face)))
(chk "V3-subexp-out-of-range"
'(:ERROR (error "Regexp \"[0-9]+\" has no group 2"))
(tp-regexp-set "[0-9]+" '(face bold) "abc 123" nil nil 2))
(chk "V3-regexp-bounds-and-reversed" '(((1 3 bold)) ((1 3 bold)))
(list (tp-search (tp-regexp-set "a+" '(face bold) "aaaa" 1 3) 'face)
(tp-search (tp-regexp-set "a+" '(face bold) "aaaa" 3 1) 'face)))
;; ---- 0.3.0: PREDICATE / NOT-CURRENT ----
(chk "V3-predicate" '((3 6) ((6 11 20)))
(list (with-temp-buffer
(insert "abcdef")
(tp-set 1 3 '(size 10))
(tp-set 3 6 '(size 20))
(goto-char 1)
(let ((match (tp-forward 'size 15 nil 1
(lambda (target v) (and v (> v target))))))
(list (prop-match-beginning match) (prop-match-end match))))
(let ((str (copy-sequence "hello world")))
(tp-set 0 5 '(size 10) str)
(tp-set 6 11 '(size 20) str)
(tp-forward 'size 15 str 2
(lambda (target v) (and v (> v target)))))))
(chk "V3-not-current" '(2 5)
(with-temp-buffer
(insert "one two")
(tp-set 1 4 '(mark t))
(tp-set 5 8 '(mark t))
(let (a b)
(goto-char 2)
(setq a (prop-match-beginning (tp-forward 'mark t)))
(goto-char 2)
(setq b (prop-match-beginning (tp-forward 'mark t nil 1 nil t)))
(list a b))))
;; ---- 0.3.0: multi-argument parameterized layers ----
(chk "V3-multiarg-specs" '((:foreground "red" :background "blue")
((:foreground "red" :background "blue") "tip")
(:foreground "white" :background "black"))
(progn
(tp-layer-reset)
(define-tp tp-colors (fg bg)
`(face (:foreground ,fg :background ,bg)))
(list (tp-at 0 'face (tp-set "hello" 'tp-colors "red" "blue"))
(let ((str (copy-sequence "hello")))
(tp-set 0 5 '(tp-colors ("red" "blue") help-echo "tip") str)
(list (tp-at 0 'face str) (tp-at 0 'help-echo str)))
(with-temp-buffer
(insert "Hello World")
(tp-put-layer 1 10 '(tp-colors "white" "black") 0)
(tp-at 1 'face)))))
(chk "V3-multiarg-arity-error"
'(:ERROR (error "tp layer tp-colors takes 2 argument(s), got 1"))
(tp-set "hello" 'tp-colors "red"))
(chk "V3-args-introspection"
'((face (:foreground "red" :background "blue"))
(fg bg)
((face (:foreground "white" :background "black")) (face bold)))
(progn
(define-tps tp-badge (fg bg)
`(tp-colors ,fg ,bg)
'(face bold))
(list (tp-layer-props-with-args 'tp-colors '("red" "blue"))
(tp-layer-arglist 'tp-colors)
(tp-group-props-with-args 'tp-badge '("white" "black")))))
;; ---- 0.3.0: layer visibility ----
(chk "V3-hide-reveals-below"
'(:visible base :face default :count 2 :layers (highlight base))
(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)
(tp-hide-layer 1 10 'highlight)
(list :visible (tp-at 1 'tp-name)
:face (tp-at 1 'face)
:count (tp-layer-count 1 10)
:layers (tp-layer-list 1 10)))))
(chk "V3-hide-all-bare-and-show" '((:face nil :count 2) (:background "yellow"))
(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)
(tp-hide-layer 1 10 'highlight)
(tp-hide-layer 1 10 'base)
(let ((all-hidden (list :face (tp-at 1 'face)
:count (tp-layer-count 1 10))))
(tp-show-layer 1 10 'highlight)
(list all-hidden (tp-at 1 'face))))))
(chk "V3-hide-run-counts" '(1 0 0)
(progn
(tp-layer-reset)
(define-tp base () '(face default))
(with-temp-buffer
(insert "Hello World")
(tp-push-layer 1 10 'base)
(list (tp-hide-layer 1 10 'base)
(tp-hide-layer 1 10 'base)
(tp-hide-layer 1 10 'nonexistent)))))
(chk "V3-merge-excludes-hidden" '(:face bold :help nil :name merged)
(progn
(tp-layer-reset)
(define-tp layer1 () '(face bold))
(define-tp layer2 () '(help-echo "tip"))
(with-temp-buffer
(insert "Hello World")
(tp-push-layer 1 10 'layer1)
(tp-push-layer 1 10 'layer2)
(tp-hide-layer 1 10 'layer2)
(tp-merge-layers 1 10 'merged '(layer1 layer2))
(list :face (tp-at 1 'face)
:help (tp-at 1 'help-echo)
:name (tp-at 1 'tp-name)))))
(chk "V3-flatten-discards-hidden" '(default flat)
(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)
(tp-hide-layer 1 10 'highlight)
(tp-flatten-layers 1 10 'flat)
(list (tp-at 1 'face) (tp-at 1 'tp-name)))))
;; ---- 0.3.0: movement additions and stack introspection ----
(chk "V3-lower-layer" '(layer2 (layer2 layer3 layer1))
(progn
(tp-layer-reset)
(define-tp layer1 () '(face bold))
(define-tp layer2 () '(face italic))
(define-tp layer3 () '(face underline))
(with-temp-buffer
(insert "Hello World")
(tp-push-layer 1 10 'layer1)
(tp-push-layer 1 10 'layer2)
(tp-push-layer 1 10 'layer3)
(tp-lower-layer 1 10 'layer3 1)
(list (tp-layer-top 1 10) (tp-layer-list 1 10)))))
(chk "V3-rotate-canonical" '((layer1 layer3 layer2) (layer1 layer3 layer2))
(progn
(tp-layer-reset)
(define-tp layer1 () '(face bold))
(define-tp layer2 () '(face italic))
(define-tp layer3 () '(face underline))
(list (with-temp-buffer
(insert "Hello World")
(tp-push-layer 1 10 'layer1)
(tp-push-layer 1 10 'layer2)
(tp-push-layer 1 10 'layer3)
(tp-rotate-layer 1 10 'up)
(tp-layer-list 1 10))
(with-temp-buffer
(insert "Hello World")
(tp-push-layer 1 10 'layer1)
(tp-push-layer 1 10 'layer2)
(tp-push-layer 1 10 'layer3)
(tp-rotate-layer 1 10 'down 2)
(tp-layer-list 1 10)))))
;; Compared via assq/plist-get per layer: the top layer's PROPS come from
;; the direct text properties, whose plist order varies on Emacs 28.
(chk "V3-layer-stack-at" '(((highlight base) (:background "yellow") default nil)
((highlight base) (:background "yellow") default t)
nil)
(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)
(let* ((probe (lambda ()
(let ((stack (tp-layer-stack-at 1)))
(list (mapcar #'car stack)
(plist-get (cdr (assq 'highlight stack)) 'face)
(plist-get (cdr (assq 'base stack)) 'face)
(plist-get (cdr (assq 'highlight stack))
'tp-hidden)))))
(visible (funcall probe)))
(tp-hide-layer 1 10 'highlight)
(list visible
(funcall probe)
(with-temp-buffer (insert "Hello") (tp-layer-stack-at 1)))))))
(chk "V3-put-push-noerror" '(nil nil)
(with-temp-buffer
(insert "Hello World")
(list (tp-put-layer 1 10 'no-such-layer 0 nil t)
(tp-push-layer 1 10 'no-such-layer nil t))))
;; ---- 0.3.0: reactive layer-buffer registry and lifecycle ----
(defvar reg-color "red")
(chk "V3-registry-and-track" '(unknown t (reg-layer))
(progn
(tp-layer-reset)
(define-tp reg-layer ()
:props '(face (:foreground $reg-color)))
(let ((before (tp-reactive-layer-buffers 'reg-layer)))
(with-temp-buffer
(insert "Hello")
(tp-push-layer 1 6 'reg-layer)
(let ((registered (equal (tp-reactive-layer-buffers 'reg-layer)
(list (current-buffer)))))
(list before
registered
(let ((s (tp-set "hello" 'reg-layer)))
(with-temp-buffer
(insert s)
(tp-reactive-track-buffer)))))))))
(defvar tmp-color "green")
(chk "V3-gc-anonymous" '(1 nil nil)
(progn
(tp-reactive-reset)
(tp-layer-reset)
(let ((buf (generate-new-buffer "*gc-demo*")))
(with-current-buffer buf
(insert "Hello")
(tp-set 1 6 '(face (:foreground $tmp-color))))
(kill-buffer buf)
(let ((collected (tp-gc-anonymous-layers)))
(list (length collected)
(tp-layer-props (car collected))
;; string-only layers stay `unknown' and are kept
(let ((s (tp-set "hello" '(face (:foreground $tmp-color)))))
(ignore s)
(tp-gc-anonymous-layers)))))))
;; ---- 0.3.0: minimal-diff tp-text re-rendering ----
(defvar counter-val "0")
(chk "V3-tp-text-minimal-diff" '("count: 9 items" 105 10)
(progn
(tp-layer-reset)
(setq counter-val "0")
(define-tp counter-label ()
:props '(tp-text $counter-val))
(with-temp-buffer
(insert "count: 0 items")
(tp-set 8 9 'counter-label)
(let ((m (copy-marker 10))) ; marker on the "i" of "items"
(setq counter-val "9")
(list (buffer-substring-no-properties 1 (point-max))
(char-after m)
(marker-position m))))))
(chk "V3-tp-text-noop-unmodified" nil
(with-temp-buffer
(insert "count: 9 items")
(tp-set 8 9 'counter-label)
(set-buffer-modified-p nil)
(setq counter-val "9")
(buffer-modified-p)))
;; ---- 0.3.0: ABSOLUTE coordinates and palette primaries ----
(chk "V3-intervals-absolute"
'(((1 6 (face bold)) (6 7 nil) (7 12 (face italic))) "bold text")
(list (with-temp-buffer
(insert "Hello World")
(tp-set 1 6 '(face bold))
(tp-set 7 12 '(face italic))
(tp-intervals 1 12 nil t))
(with-temp-buffer
(insert "Hello World")
(tp-set 1 6 '(face bold))
(dolist (iv (tp-intervals 1 12 nil t))
(when (eq (plist-get (nth 2 iv) 'face) 'bold)
(tp-add (nth 0 iv) (nth 1 iv) '(help-echo "bold text"))))
(tp-at 1 'help-echo))))
(chk "V3-intervals-map-absolute" '((1 6 bold) (6 7 nil) (7 12 italic))
(with-temp-buffer
(insert "Hello World")
(tp-set 1 6 '(face bold))
(tp-set 7 12 '(face italic))
(tp-intervals-map
(lambda (start end props belows)
(ignore belows)
(list start end (plist-get props 'face)))
1 12 nil t)))
;; The resolved color depends on the frame's light/dark mode, like the
;; U-parsecolor2 assertion above.
(chk "V3-palette-primaries" '(t nil (t t t nil))
(list (and (member (tp-palette-color 'info :fg)
'("#0969da" "#58a6ff"))
t)
(tp-palette-color 'no-such-palette :fg)
(list (tp-palette-has-p 'info)
(tp-palette-has-p 'info :fg)
(tp-palette-has-p 'info :border)
(tp-palette-has-p 'no-such-palette))))
(princ (format "\nTOTAL: %d FAILS: %d\n" tp-doctest--total tp-doctest--fails)) (princ (format "\nTOTAL: %d FAILS: %d\n" tp-doctest--total tp-doctest--fails))
(when (> tp-doctest--fails 0) (kill-emacs 1)) (when (> tp-doctest--fails 0) (kill-emacs 1))

View File

@ -1148,7 +1148,8 @@ Returns a new plist (does not modify the original)."
OBJECT is a string or buffer; nil means the current buffer. OBJECT is a string or buffer; nil means the current buffer.
If START and END are not provided, they default to the whole of If START and END are not provided, they default to the whole of
OBJECT: 0/(length OBJECT) for strings, `point-min'/`point-max' of OBJECT: 0/(length OBJECT) for strings, `point-min'/`point-max' of
OBJECT for buffers (the current buffer when OBJECT is nil)." OBJECT for buffers (the current buffer when OBJECT is nil).
Returns nil."
(interactive) (interactive)
(let ((beg (or start (let ((beg (or start
(cond ((stringp object) 0) (cond ((stringp object) 0)
@ -1160,7 +1161,8 @@ OBJECT for buffers (the current buffer when OBJECT is nil)."
((bufferp object) ((bufferp object)
(with-current-buffer object (point-max))) (with-current-buffer object (point-max)))
(t (point-max)))))) (t (point-max))))))
(set-text-properties beg finish nil object))) (set-text-properties beg finish nil object)
nil))
(provide 'tp-ops) (provide 'tp-ops)
;;; tp-ops.el ends here ;;; tp-ops.el ends here