为什么你需要 tp.el?
如果你曾在 Emacs 开发中处理过文本属性(text properties),或许体会过原生 API 的种种痛点:
| 痛点 | 描述 |
|---|---|
| API 不一致 | put-text-property、propertize、get-text-property… 字符串和 buffer 用法完全不同 |
| 无法操作嵌套 | 想修改 face 里的 :underline 子属性?不好意思,你得整个 face 重写 |
| 无法叠加属性 | 同一区域的不同属性集会相互覆盖,无法分层管理 |
| 批量操作困难 | 想给所有匹配文本加属性?准备好写循环吧 |
tp.el 不只是原生 API 的简单封装,而是在原生 API 基础上做了大量增强,旨在系统性地解决上述痛点。
五大核心功能详解
核心功能一:统一 API,一套范式掌握所有场景
原生 Emacs 的 API 让人崩溃——put-text-property 用于 buffer,propertize 用于字符串,参数顺序还不一样。
tp.el 统一了这一切:
;; 三种调用方式,同一个函数!
;; 方式 1:当前 buffer
(tp-set 1 10 '(face bold))
;; 方式 2:指定 buffer 或字符串
(tp-set 1 10 '(face bold) my-buffer)
(tp-set 0 5 '(face bold) my-string)
;; 方式 3:整个字符串(扁平属性)
(tp-set "Hello" 'face 'bold 'help-echo "提示信息")
所有核心函数都支持这三种模式:tp-set、tp-get、tp-remove、tp-add…
再也不用记忆不同函数的不同用法!
核心功能二:三种语义操作,精准控制属性行为
原生 API 只有简单的设置和获取。tp.el 提供了三种清晰的操作语义:
| 函数 | 语义 | 行为 | 使用场景 |
|---|---|---|---|
| tp-reset | 完全替换 | 清除所有现有属性,只保留新设置的 | 需要"干净"的状态时 |
| tp-set | 部分替换 | 只替换指定的属性,保留其他属性 | 日常修改属性 |
| tp-add | 深度合并 | 智能合并嵌套属性,而非简单覆盖 | 增量添加样式 |
;; 深度合并示例 —— 这是原生 API 绝对做不到的!
;; 先设置前景色
(tp-set 1 10 '(face (:foreground "red")))
;; 再添加背景色 —— 使用 tp-add 深度合并
(tp-add 1 10 '(face (:background "blue")))
;; ✅ 结果:face = (:foreground "red" :background "blue")
;; ❌ 原生 API:face = (:background "blue") ← 前景色丢失!
;; 更复杂的 underline 的例子
;; 先设置完整的 underline 样式
(tp-set 1 10 '(face (:underline (:color "green" :style wave :position t))))
;; 使用 tp-add 只修改颜色
(tp-add 1 10 '(face (:underline (:color "red"))))
;; ✅ 结果:
(tp-at 1 'face)
;; => (:underline (:color "red" :style wave :position t))
;; : style 和 : position 完好保留!
Face 智能合并:符号类型的 face 自动添加到 face 列表前面,plist 类型的 face 进行深度合并:
(let ((str (tp-set "Hello" 'face 'bold)))
(tp-add str 'face 'shadow)
(tp-at 0 'face str))
;; => (shadow bold) ← 两个 face 都保留!
核心功能三:路径式子属性操作
这是原生 Emacs API 完全不支持的,像操作 JSON/嵌套数据结构一样操作文本属性:
读取深层嵌套属性
;; 假设 face 结构为:
;; (:underline (:color "green" :style wave) :box (:color "blue"))
;; 路径式访问 —— 简洁直观
(tp-at 5 '(face :underline :style)) ; => wave
(tp-at 5 '(face :box :color)) ; => "blue"
;; 获取多个嵌套键
(tp-get str 'face :underline '(:color :style))
;; => ((: color "green" :style wave))
精确删除子属性
;; 只删除 :underline 下的 :style,保留 :color
(tp-remove 1 10 '(face :underline :style))
;; 删除多个嵌套键
(tp-remove 1 10 '(face :underline (:style :position)))
;; :color 保留,:style 和 :position 被删除
精准修改子属性
;; 只修改下划线颜色,其他属性不变
(tp-add 1 10 '(face (:underline (:color "red"))))
核心功能四:创新属性层系统(Property Layer System)
这是 tp.el 最具创新性的功能!原生 Emacs 完全不支持。
什么是属性层?
在传统 Emacs 中,同一文本区域只能有一组属性。当你设置新属性时,旧属性会被覆盖——这在很多场景下非常不便。
属性层系统(Property Layer System) 借鉴了图形软件的图层概念,允许你在同一文本区域堆叠多组属性,随时切换显示、调整顺序、合并图层 …
定义可复用的属性层
;; 定义单个属性层
(tp-define-layer highlight '(face (:background "yellow")))
(tp-define-layer error '(face (:foreground "red" :weight bold)))
(tp-define-layer link '(face (:underline t) mouse-face highlight))
;; 定义属性层组(多个层的组合)
(tp-define-layer my-group
highlight ; 引用已存在的属性层
(face (:background "red") line-prefix ">>") ; 匿名属性层
(face (:background "green" :weight bold))) ; 另一个匿名属性层
丰富的层操作
;; 📥放置层
(tp-push-layer 1 10 'highlight) ; 推入栈顶(可见)
(tp-push-layer 1 10 'error) ; error 现在在最上面
(tp-put-layer 1 10 'link 1) ; 放到指定索引位置
;; 🔄 切换显示
(tp-rotate-layer 1 10) ; 旋转:栈顶移到底部
(tp-pin-layer 1 10 'highlight) ; 将指定层固定到顶部
;; 🔀 调整顺序
(tp-raise-layer 1 10 'highlight 1) ; 向上移动 1 位
(tp-switch-layer 1 10 'error 'link) ; 交换两层位置
;; 🗑️ 删除层
(tp-pop-layer 1 10) ; 弹出顶层
(tp-delete-layer 1 10 'error) ; 删除指定层
;; 🧩 合并层
(tp-merge-layers 1 10 '(error highlight) 'merged) ; 合并为新层
(tp-flatten-layers 1 10) ; 将所有层扁平化为一层
查询层信息
(tp-layer-list 1 10) ; => (error highlight link) 列出所有层
(tp-layer-count 1 10) ; => 3 层数量
(tp-layer-exists-p 1 10 'error) ; => t 是否存在
(tp-layer-top 1 10) ; => error 获取顶层名称
核心功能五:模式匹配批量操作
告别繁琐的手动循环!一行代码批量添加属性:
字符串匹配
;; 高亮所有 TODO
(tp-match-set "TODO" '(face warning))
;; => ((1 . 5) (17 . 21)) 返回所有匹配位置
;; 多模式匹配
(tp-match-set '("TODO" "FIXME" "BUG" "HACK") '(face error))
;; 三种语义都支持
(tp-match-reset "TODO" '(face warning)) ; 完全替换
(tp-match-set "TODO" '(face warning)) ; 部分替换
(tp-match-add "TODO" '(face (:underline t))) ; 深度合并
正则表达式匹配
;; 高亮所有数字
(tp-regexp-set "[0-9]+" '(face font-lock-number-face))
;; 高亮所有 URL
(tp-regexp-set "https?://[^ \t\n]+" '(face link mouse-face highlight))
;; 多正则匹配
(tp-regexp-set '("[0-9]+" "[A-Z]+") '(face bold))
增强搜索与导航
tp.el 还提供了强大的搜索和遍历功能:
;; 搜索所有匹配区间
(tp-search my-string 'marker)
;; => ((0 5 t) (12 17 t)) 返回所有 (起始 结束 值) 列表
;; 向前/向后搜索 N 次
(tp-forward 'marker nil nil 3) ; 向前搜索第 3 个匹配
(tp-backward 'type 'heading) ; 向后搜索 type=heading
;; 搜索并执行函数
(tp-forward-do #'upcase 'marker) ; 找到匹配并大写转换
;; 批量转换
(tp-search-map #'upcase my-string 'marker) ; 所有匹配都大写
实用工具函数
;; 获取区域内的所有属性区间
(tp-intervals 1 100)
;; => ((1 10 (face bold)) (10 20 (face italic)) ...)
;; 对每个区间执行函数
(tp-intervals-map (lambda (start end props) .. .) 1 100)
;; 获取区域内存在的所有属性名
(tp-plist 1 100)
;; => (face help-echo mouse-face)
;; 检查对象是否有属性
(tp-empty-p my-string) ; => t 或 nil





其实还可以更离谱,这个是没用tp.el的版本,但是我没想好做啥东西。。 