# tp.el - Emacs 文本属性操作库
一个功能强大的文本属性操作库,具有创新的属性层系统
功能特性 • 安装 • 快速开始 • API 参考 • 属性层系统
--- ## 概述 **tp.el** 是一个全面增强 Emacs 文本属性操作的库。它不仅仅是对原生文本属性 API(如 `put-text-property`、`get-text-property`)的简单封装,更提供了许多**原生函数所不具备的功能拓展**。tp.el 在以下方面进行了创新: ### 核心创新 1. **统一的 API 参数规范**:所有函数支持多种灵活的调用方式,同时适用于字符串和缓冲区 2. **子属性的精细操作**:支持嵌套属性的路径式访问、修改和深度合并 3. **创新的属性层系统**:在同一文本区域上堆叠、管理多组属性,实现属性的分层控制 4. **模式匹配批量操作**:通过字符串或正则表达式批量应用属性 5. **增强的搜索导航**:丰富的属性搜索和遍历功能 ## 功能特性 ### 统一的 API 参数规范 原生 Emacs API 针对字符串和缓冲区有不同的函数和参数顺序,tp.el 统一了这一切: - ✅ **三种调用约定**:所有核心函数(`tp-set`、`tp-get`、`tp-remove` 等)支持三种灵活的调用方式: ```elisp ;; 1. 当前缓冲区 (tp-set START END '(face bold)) ;; 2. 指定缓冲区或字符串 (tp-set START END '(face bold) OBJECT) ;; 3. 整个字符串(平铺属性) (tp-set STRING 'face 'bold 'help-echo "tip") ``` - ✅ **统一对象支持**:同一个函数同时支持字符串和缓冲区,无需记忆不同的 API ### 三种属性操作语义 原生 API 只有简单的设置和获取,tp.el 提供了三种清晰的操作语义: - ✅ **`tp-reset`**:完全替换 - 清除所有现有属性,设置新属性 - ✅ **`tp-set`**:部分替换 - 只替换指定属性,保留其他属性 - ✅ **`tp-add`**:深度合并 - 智能合并嵌套属性,而非简单覆盖 ```elisp ;; 深度合并示例 (tp-set 1 10 '(face (:foreground "red"))) (tp-add 1 10 '(face (:background "blue"))) ;; 结果: face 是 (:foreground "red" :background "blue") ;; 原生 API 会完全覆盖,而 tp-add 会智能合并 ``` ### 子属性的精细操作 **这是原生 API 完全不具备的功能**。tp.el 支持对嵌套属性进行精细的读取、修改和删除: - ✅ **路径式访问**:通过路径语法访问深层嵌套的属性值 ```elisp ;; 获取嵌套属性 (tp-get str 'face :underline :style) ; => wave (tp-at 5 '(face :box :color)) ; => "blue" ;; 获取多个嵌套键 (tp-get str 'face :underline '(:color :style)) ;; => ((:color "green" :style wave)) ``` - ✅ **子属性删除**:精确移除嵌套属性中的特定键 ```elisp ;; 只删除 :underline 中的 :style,保留 :color (tp-remove 1 10 '(face :underline :style)) ``` - ✅ **深度合并**:`tp-add` 递归合并嵌套的 plist 结构 - ✅ **Face 智能合并**:符号 face 自动前置到 face 列表,plist face 深度合并 ### 创新的属性层系统 **这是 tp.el 最具创新性的功能**,原生 Emacs 完全不支持。属性层系统允许在同一文本区域上堆叠多组属性: - ✅ **属性层栈概念**:多个属性层像栈一样堆叠,只有顶层可见,下层被保留 - ✅ **属性层定义与复用**:通过 `tp-define-layer` 定义可复用的属性层和属性层组 - ✅ **丰富的属性层操作**: - 放置:`tp-put-layer`(指定位置)、`tp-push-layer`(顶部) - 删除:`tp-delete-layer`(按名称/索引)、`tp-pop-layer`(顶层) - 移动:`tp-raise-layer`(上下移动)、`tp-rotate-layer`(轮换)、`tp-pin-layer`(置顶)、`tp-switch-layer`(交换) - 合并:`tp-merge-layers`(合并指定层)、`tp-flatten-layers`(扁平化所有层) - ✅ **属性层查询**:`tp-layer-list`、`tp-layer-count`、`tp-layer-exists-p`、`tp-layer-top` ```elisp ;; 属性层使用示例 (tp-define-layer highlight (face (:background "yellow"))) (tp-define-layer error (face (:foreground "red"))) ;; 堆叠多个属性层 (tp-push-layer 1 10 'highlight) (tp-push-layer 1 10 'error) ; error 现在可见 ;; 轮换显示 (tp-rotate-layer 1 10) ; highlight 现在可见 ``` ### 模式匹配与批量操作 原生 API 需要手动搜索和循环,tp.el 提供了便捷的模式匹配功能: - ✅ **字符串匹配**:`tp-match-set`、`tp-match-reset`、`tp-match-add` - ✅ **正则匹配**:`tp-regexp-set`、`tp-regexp-reset`、`tp-regexp-add` - ✅ **三种语义变体**:每种匹配都支持 set/reset/add 三种操作语义 ```elisp ;; 高亮所有 TODO (tp-match-set "TODO" '(face warning)) ;; 正则匹配所有数字 (tp-regexp-set "[0-9]+" '(face font-lock-number-face)) ;; 深度合并方式添加属性 (tp-match-add "TODO" '(face (:underline t))) ``` ### 增强的搜索与导航 - ✅ **范围搜索**:`tp-search` 返回所有匹配区间的列表 - ✅ **N次搜索**:`tp-forward`/`tp-backward` 支持向前/向后搜索N次 - ✅ **搜索并执行**:`tp-forward-do`/`tp-backward-do` 搜索并对匹配文本执行函数 - ✅ **批量转换**:`tp-search-map` 对所有匹配应用转换函数 ```elisp ;; 搜索所有标记 (tp-search my-string 'marker) ; => ((0 5 t) (12 17 t)) ;; 将所有标记文本转为大写 (tp-search-map #'upcase my-string 'marker) ``` ## 系统要求 - **Emacs 28.1+**(使用 `object-intervals` 函数) - **dash.el**(列表操作工具库) ## 安装 ```elisp ;; 添加到 load-path (add-to-list 'load-path "/path/to/tp") (require 'tp) ``` 或使用 `use-package`: ```elisp (use-package tp :load-path "/path/to/tp") ``` --- ## API 参考 ### API 快速参考 tp.el 所有函数按类别组织的完整概览: #### 核心属性函数 | 函数 | 描述 | |------|------| | [`tp-set`](#tp-set---设置文本属性) | 设置文本属性(仅替换指定属性) | | [`tp-reset`](#tp-reset---替换所有属性) | 替换所有文本属性 | | [`tp-add`](#tp-add---添加合并属性) | 添加/合并属性,支持深度合并 | | [`tp-get`](#tp-get---获取属性值) | 从范围或字符串获取属性值 | | [`tp-at`](#tp-at---获取位置属性) | 获取单个位置的属性值 | | [`tp-remove`](#tp-remove---移除属性) | 移除属性或子属性 | | [`tp-clear`](#tp-clear---清除所有属性) | 清除区域中的所有文本属性 | #### 模式匹配函数 | 函数 | 描述 | |------|------| | [`tp-match-set`](#tp-match-set---匹配字符串) | 在字符串匹配处设置属性 | | [`tp-match-reset`](#tp-match-reset---匹配并重置) | 在字符串匹配处重置所有属性 | | [`tp-match-add`](#tp-match-add---匹配并添加) | 在字符串匹配处添加/合并属性 | | [`tp-regexp-set`](#tp-regexp-set---匹配正则表达式) | 在正则匹配处设置属性 | | [`tp-regexp-reset`](#tp-regexp-reset---正则匹配并重置) | 在正则匹配处重置所有属性 | | [`tp-regexp-add`](#tp-regexp-add---正则匹配并添加) | 在正则匹配处添加/合并属性 | #### 搜索和导航函数 | 函数 | 描述 | |------|------| | [`tp-search-forward`](#tp-search-forward--tp-search-backward) | text-property-search-forward 的原始包装 | | [`tp-search-backward`](#tp-search-forward--tp-search-backward) | text-property-search-backward 的原始包装 | | [`tp-forward`](#tp-forward--tp-backward) | 向前搜索 N 次具有属性的文本(支持缓冲区和字符串) | | [`tp-backward`](#tp-forward--tp-backward) | 向后搜索 N 次具有属性的文本(支持缓冲区和字符串) | | [`tp-forward-do`](#tp-forward-do--tp-backward-do) | 向前搜索并对最后一个匹配应用函数(支持起始和结束范围) | | [`tp-backward-do`](#tp-forward-do--tp-backward-do) | 向后搜索并对最后一个匹配应用函数(支持起始和结束范围) | | [`tp-search`](#tp-search---搜索所有匹配) | 在范围或字符串中搜索所有匹配的属性 | | [`tp-search-map`](#tp-search-map---对匹配文本应用函数) | 对所有匹配的文本应用函数(支持起始和结束范围) | #### 属性层定义函数 | 函数 | 描述 | |------|------| | [`tp-define-layer`](#tp-define-layer---定义单个属性层) | 定义单个属性层 | | [`tp-define-layer-group`](#tp-define-layer-group---定义属性层组) | 定义属性层组 | | [`tp-layer-props`](#tp-layer-props--tp-group-props) | 获取属性层的属性 | | [`tp-group-props`](#tp-layer-props--tp-group-props) | 获取属性层组中所有属性层的属性 | | [`tp-undefine-layer`](#tp-undefine-layer--tp-undefine-group) | 移除属性层定义 | | [`tp-undefine-group`](#tp-undefine-layer--tp-undefine-group) | 移除属性层组定义 | | [`tp-layer-reset`](#tp-layer-reset) | 清除所有属性层/属性层组定义 | #### 属性层放置函数 | 函数 | 描述 | |------|------| | [`tp-put-layer`](#tp-put-layer---在指定位置设置属性层) | 在指定索引位置设置属性层 | | [`tp-push-layer`](#tp-push-layer---推送属性层到顶部) | 将属性层推到堆栈顶部 | #### 属性层删除函数 | 函数 | 描述 | |------|------| | [`tp-delete-layer`](#tp-delete-layer---按名称索引删除属性层) | 按名称或索引删除属性层 | | [`tp-pop-layer`](#tp-pop-layer---弹出顶层) | 移除顶层属性层 | #### 属性层移动函数 | 函数 | 描述 | |------|------| | [`tp-move-layer`](#tp-move-layer---移动属性层到指定位置) | 将属性层从一个位置移动到另一个位置 | | [`tp-raise-layer`](#tp-raise-layer---上移下移属性层) | 将属性层上移/下移 N 个位置 | | [`tp-rotate-layer`](#tp-rotate-layer---轮换属性层) | 轮换属性层(顶层移到底部) | | [`tp-pin-layer`](#tp-pin-layer---将属性层置顶) | 将属性层置顶(使其可见) | | [`tp-switch-layer`](#tp-switch-layer---交换两个属性层) | 交换两个属性层的位置 | #### 属性层合并函数 | 函数 | 描述 | |------|------| | [`tp-merge-layers`](#tp-merge-layers---合并多个属性层) | 将指定属性层合并为新属性层 | | [`tp-flatten-layers`](#tp-flatten-layers---扁平化所有属性层) | 将所有属性层扁平化为单一属性层 | #### 属性层查询函数 | 函数 | 描述 | |------|------| | [`tp-layer-list`](#tp-layer-list---列出所有属性层) | 列出区域中的所有属性层名称 | | [`tp-layer-count`](#tp-layer-count) | 计算区域中的属性层数量 | | [`tp-layer-exists-p`](#tp-layer-exists-p) | 检查区域中是否存在某属性层 | | [`tp-layer-top`](#tp-layer-top) | 获取顶层(可见)属性层的名称 | | [`tp-region-layer-props`](#tp-region-layer-props---获取区域中的层属性) | 获取区域中特定层的属性 | #### 属性层操作函数 | 函数 | 描述 | |------|------| | [`tp-add-to-layers`](#tp-add-to-layers---向特定属性层添加属性) | 通过索引或名称向特定层添加/合并属性 | | [`tp-add-to-all-layers`](#tp-add-to-all-layers---向所有属性层添加属性) | 向所有现有层添加/合并属性 | #### 实用工具函数 | 函数 | 描述 | |------|------| | [`tp-intervals`](#tp-intervals---获取文本属性区间) | 获取区域中的所有文本属性区间 | | [`tp-intervals-map`](#tp-intervals-map---对区间应用函数) | 对区域中的所有区间应用函数 | | [`tp-plist`](#tp-plist---获取区域中的所有属性) | 获取区域中存在的所有属性 | | [`tp-empty-p`](#tp-empty-p---检查对象是否有属性) | 检查对象是否没有文本属性 | --- ### 核心属性函数 #### `tp-set` - 设置文本属性 在字符串或缓冲区区域上设置文本属性。只替换指定的属性,保留其他属性。 ```elisp ;; 当前缓冲区(属性作为列表) (tp-set START END '(PROPERTY VALUE ...)) ;; 特定缓冲区或字符串 (tp-set START END '(PROPERTY VALUE ...) OBJECT) ;; 整个字符串(平铺属性) (tp-set STRING PROPERTY VALUE ...) ``` **示例:** ```elisp ;; 在缓冲区区域设置 face (with-temp-buffer (insert "Hello World") (tp-set 1 10 '(face bold))) ;; => (1 . 10) ;; 设置多个属性 (with-temp-buffer (insert "Hello World") (tp-set 1 10 '(face bold help-echo "Click me"))) ;; => (1 . 10) ;; 在特定缓冲区设置 (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)) ;; => (1 . 10) ;; 在字符串上设置属性(0 索引) (let ((my-string (tp-set 0 5 '(face italic) "Hello World"))) my-string) ;; => #("Hello World" 0 5 (face italic)) ;; 在整个字符串上设置属性 (tp-set "Hello" 'face 'bold 'mouse-face 'highlight) ;; => #("Hello" 0 5 (face bold mouse-face highlight)) ``` --- #### `tp-reset` - 替换所有属性 用指定的属性完全替换所有文本属性。 ```elisp (tp-reset START END '(PROPERTY VALUE ...) &optional OBJECT) (tp-reset STRING PROPERTY VALUE ...) ``` **示例:** ```elisp ;; 替换区域中的所有属性 (with-temp-buffer (insert "Hello World") (tp-set 1 10 '(help-echo "old")) ; 设置已有属性 (tp-reset 1 10 '(face bold)) ; 任何现有属性都会被移除 (tp-at 1)) ;; => (face bold) ; help-echo 被移除了 ;; 在字符串上 (tp-reset "Hello" 'face 'italic) ;; => #("Hello" 0 5 (face italic)) ``` --- #### `tp-add` - 添加/合并属性 添加或更新属性,支持嵌套属性列表的深度合并。 ```elisp (tp-add START END '(PROPERTY VALUE ...) &optional OBJECT) (tp-add STRING PROPERTY VALUE ...) ``` **示例:** ```elisp ;; 添加属性(保留现有,合并嵌套) (with-temp-buffer (insert "Hello World") (tp-set 1 10 '(face bold)) (tp-add 1 10 '(help-echo "tooltip")) (tp-at 1)) ;; => (face bold help-echo "tooltip") ;; 深度合并 face 属性 (with-temp-buffer (insert "Hello World") (tp-set 1 10 '(face (:foreground "red"))) (tp-add 1 10 '(face (:background "blue"))) (tp-at 1 'face)) ;; => (:foreground "red" :background "blue") ;; Face 前置 - 符号 face 会被添加到 face 列表的开头 (let ((str (tp-set "Hello" 'face 'bold))) (tp-add str 'face 'shadow) (tp-at 0 'face str)) ;; => (shadow bold) ``` --- #### `tp-get` - 获取属性值 从范围或字符串获取属性值,支持嵌套子属性访问。 返回 `(START END VALUE)` 区间列表,让你可以查看范围内所有的属性值。 对于单个位置的查询,请使用 `tp-at`。 ```elisp ;; 范围 - 特定属性(返回区间列表) (tp-get START END PROPERTY) (tp-get START END PROPERTY OBJECT) ;; 范围 - 属性路径作为列表 (tp-get START END '(PROPERTY) OBJECT) (tp-get START END '(PROPERTY SUB-KEY ...) OBJECT) ;; 范围 - 深层嵌套属性路径 (tp-get START END '(PROPERTY SUB-KEY SUB-SUB-KEY ...) OBJECT) ;; 范围 - 从嵌套属性中提取多个键 (tp-get START END '(PROPERTY SUB-KEY (KEY1 KEY2 ...)) OBJECT) ;; 范围 - 所有属性(返回区间列表) (tp-get START END) (tp-get START END OBJECT) ;; 整个字符串(返回区间列表) (tp-get STRING) (tp-get STRING PROPERTY) (tp-get STRING PROPERTY SUB-KEY ...) (tp-get STRING PROPERTY SUB-KEY '(KEY1 KEY2 ...)) (tp-get STRING '(PROPERTY SUB-KEY ...)) ``` **示例:** ```elisp ;; 从范围获取 - 返回 (START END VALUE) 区间列表 (with-temp-buffer (insert "Hello World") (tp-set 1 6 '(face bold)) (tp-get 1 10 'face)) ;; => ((1 6 bold)) ;; 获取多个区间 (let ((str (copy-sequence "Hello World Hello"))) (tp-set 0 5 '(face bold) str) (tp-set 12 17 '(face italic) str) (tp-get 0 17 'face str)) ;; => ((0 5 bold) (12 17 italic)) ;; 使用列表形式的属性路径 (let ((my-string (copy-sequence "Hello World Hello World"))) (tp-set 5 20 '(face (:underline (:style wave))) my-string) (tp-get 5 20 '(face :underline :style) my-string)) ;; => ((5 20 wave)) ;; 从整个字符串获取深层嵌套属性 (let ((str (copy-sequence "Hello World"))) (tp-set 0 5 '(face (:underline (:color "green"))) str) (tp-set 6 11 '(face (:underline (:color "yellow"))) str) (tp-get str 'face :underline :color)) ;; => ((0 5 "green") (6 11 "yellow")) ;; 从嵌套属性中获取多个键 (let ((str (copy-sequence "Hello World"))) (tp-set 0 5 '(face (:underline (:color "green" :style wave))) str) (tp-set 6 11 '(face (:underline (:color "yellow" :style line))) str) (tp-get str 'face :underline '(:color :style))) ;; => ((0 5 (:color "green" :style wave)) (6 11 (:color "yellow" :style line))) ;; 获取范围内的所有属性 (with-temp-buffer (insert "Hello World") (tp-set 1 6 '(face bold help-echo "test")) (tp-get 1 10)) ;; => ((1 6 (face bold help-echo "test"))) ;; 从整个字符串获取 - 返回区间列表 (let ((str (copy-sequence "Hello World Hello"))) (tp-set 0 5 '(face bold) str) (tp-set 12 17 '(face italic) str) (list (tp-get str) ; => ((0 5 (face bold)) (12 17 (face italic))) (tp-get str 'face))) ; => ((0 5 bold) (12 17 italic)) ;; => (((0 5 (face bold)) (12 17 (face italic))) ((0 5 bold) (12 17 italic))) ``` --- #### `tp-at` - 获取位置属性 ```elisp ;; 获取位置的所有属性 (tp-at POS) (tp-at POS OBJECT) ;; 获取位置的特定属性 (tp-at POS PROPERTY) (tp-at POS PROPERTY OBJECT) ;; 获取位置的嵌套子属性 (tp-at POS '(PROPERTY SUB-KEY ...)) (tp-at POS '(PROPERTY SUB-KEY ...) OBJECT) ``` 获取 POS 位置的文本属性,可选择按 PROPERTY 过滤。 对于单位置属性查询(以前使用 `tp-get`),现在使用 `tp-at`。 **示例:** ```elisp ;; 获取当前缓冲区位置 5 的所有属性 (with-temp-buffer (insert "Hello World") (tp-set 1 10 '(face bold help-echo "test")) (tp-at 5)) ;; => (face bold help-echo "test") ;; 获取字符串位置 0 的所有属性 (let ((my-string (tp-set "Hello" 'face 'italic 'help-echo "greeting"))) (tp-at 0 my-string)) ;; => (face italic help-echo "greeting") ;; 获取位置的特定属性 (with-temp-buffer (insert "Hello World") (tp-set 1 10 '(face bold)) (tp-at 5 'face)) ;; => bold ;; 获取字符串位置的特定属性 (let ((my-string (tp-set "Hello" 'face 'italic))) (tp-at 0 'face my-string)) ;; => italic ;; 获取位置的嵌套子属性 (with-temp-buffer (insert "Hello World") (tp-set 1 10 '(face (:foreground "red" :box (:color "blue")))) (list (tp-at 5 '(face :foreground)) (tp-at 5 '(face :box :color)))) ;; => ("red" "blue") ;; 从字符串获取嵌套子属性 (let ((str (copy-sequence "Hello"))) (tp-set 0 5 '(face (:foreground "red" :underline t)) str) (tp-at 0 '(face :foreground) str)) ;; => "red" ``` --- #### `tp-remove` - 移除属性 从区域或整个字符串中移除属性或嵌套子属性。 ```elisp ;; 移除整个属性(缓冲区) (tp-remove START END PROPERTY &optional OBJECT) ;; 移除子属性(缓冲区) (tp-remove START END '(PROPERTY SUB-KEY) &optional OBJECT) ;; 移除嵌套子属性(缓冲区) (tp-remove START END '(PROPERTY SUB-KEY (NESTED-KEYS...)) &optional OBJECT) ;; 从整个字符串移除 (tp-remove STRING PROP1 PROP2 ...) (tp-remove STRING PROPERTY SUB-KEY) (tp-remove STRING PROPERTY SUB-KEY '(NESTED-KEYS...)) ``` **示例:** ```elisp ;; 移除整个属性 (with-temp-buffer (insert "Hello World") (tp-set 1 10 '(face bold help-echo "test")) (tp-remove 1 10 'face) (tp-at 1)) ;; => (help-echo "test") ;; 从 face 移除子属性 (with-temp-buffer (insert "Hello World") (tp-set 1 10 '(face (:foreground "red" :underline t))) (tp-remove 1 10 '(face :underline)) (tp-at 1 'face)) ;; => (:foreground "red") ;; 移除特定嵌套键,保留其他 (with-temp-buffer (insert "Hello World") (tp-set 1 10 '(face (:underline (:style wave :position t :color "blue")))) (tp-remove 1 10 '(face :underline (:style :position))) (tp-at 1 '(face :underline))) ;; => (:color "blue") ; :style 和 :position 被移除, :color 保留 ;; 从整个字符串移除 - 移除多个属性 (let ((str (tp-set "Hello World" 'face 'bold 'help-echo "tip"))) (tp-remove str 'face 'help-echo) (tp-at 0 str)) ;; => nil ;; 从字符串移除子属性 (let ((str (copy-sequence "Hello World"))) (tp-set 0 11 '(face (:foreground "red" :underline t)) str) (tp-remove str 'face :underline) (tp-at 0 'face str)) ;; => (:foreground "red") ;; 从字符串移除嵌套键 (let ((str (copy-sequence "Hello World"))) (tp-set 0 11 '(face (:underline (:style wave :color "blue"))) str) (tp-remove str 'face :underline '(:style)) (tp-at 0 '(face :underline) str)) ;; => (:color "blue") ``` --- #### `tp-clear` - 清除所有属性 ```elisp (tp-clear &optional START END OBJECT) ``` 清除区域中的所有文本属性。 **示例:** ```elisp ;; 清除区域 (with-temp-buffer (insert "Hello World") (tp-set 1 10 '(face bold)) (tp-clear 1 10) (tp-at 1)) ;; => nil ;; 清除整个缓冲区 (with-temp-buffer (insert "Hello World") (tp-set 1 12 '(face bold)) (tp-clear) (tp-at 5)) ;; => nil ``` --- ### 模式匹配函数 #### `tp-match-set` - 匹配字符串 ```elisp (tp-match-set PATTERN PLIST &optional OBJECT) ``` 在所有字符串模式匹配处设置属性。 PATTERN 可以是字符串(单个模式)或字符串列表(多个模式)。 PLIST 是属性列表,如 `'(face bold help-echo "tip")`。 OBJECT 是缓冲区或字符串;nil 表示当前缓冲区。 **示例:** ```elisp ;; 在缓冲区中 - 返回 (START . END) 对的列表 (with-temp-buffer (insert "TODO: fix this. TODO: also this.") (tp-match-set "TODO" '(face warning))) ;; => ((1 . 5) (17 . 21)) ;; 在字符串上 - 返回修改后的字符串 (tp-match-set "o" '(face bold) "Hello World") ;; => #("Hello World" 4 5 (face bold) 7 8 (face bold)) ;; 多个模式 - 同时匹配 "world" 和 "Hello" (with-temp-buffer (insert "Hello world, Hello again") (tp-match-set '("world" "Hello") '(face bold))) ;; => ((1 . 6) (7 . 12) (14 . 19)) ; 匹配 "Hello", "world", "Hello" ;; 在字符串上使用多个模式 (tp-match-set '("Hello" "world") '(face bold) "Hello world") ;; => #("Hello world" 0 5 (face bold) 6 11 (face bold)) ``` --- #### `tp-match-reset` - 匹配并重置 重置(完全替换)匹配处的所有属性。 PATTERN 可以是字符串或字符串列表(多个模式)。 PLIST 是属性列表,如 `'(face bold help-echo "tip")`。 OBJECT 是缓冲区或字符串;nil 表示当前缓冲区。 ```elisp (tp-match-reset PATTERN PLIST &optional OBJECT) ``` **示例:** ```elisp ;; 替换匹配文本上的所有属性 (with-temp-buffer (insert "TODO: fix this") (tp-set 1 5 '(help-echo "original")) ; 设置已有属性 (tp-match-reset "TODO" '(face warning)) (tp-at 1)) ;; => (face warning) ; help-echo 被移除 ;; 多个模式 (with-temp-buffer (insert "TODO: fix. FIXME: also fix.") (tp-match-reset '("TODO" "FIXME") '(face warning))) ;; => ((1 . 5) (12 . 17)) ``` --- #### `tp-match-add` - 匹配并添加 在匹配处添加/合并属性,支持深度合并。 PATTERN 可以是字符串或字符串列表(多个模式)。 PLIST 是属性列表,如 `'(face bold help-echo "tip")`。 OBJECT 是缓冲区或字符串;nil 表示当前缓冲区。 ```elisp (tp-match-add PATTERN PLIST &optional OBJECT) ``` **示例:** ```elisp ;; 与现有属性合并 (with-temp-buffer (insert "TODO: fix this") (tp-set 1 5 '(help-echo "important")) (tp-match-add "TODO" '(face (:underline t))) (tp-at 1)) ;; => (face (:underline t) help-echo "important") ;; 多个模式 (with-temp-buffer (insert "TODO: fix. FIXME: also fix.") (tp-match-add '("TODO" "FIXME") '(face (:underline t)))) ;; => ((1 . 5) (12 . 17)) ``` --- #### `tp-regexp-set` - 匹配正则表达式 ```elisp (tp-regexp-set PATTERN PLIST &optional OBJECT) ``` 在所有正则表达式匹配处设置属性。 PATTERN 可以是字符串(单个正则)或字符串列表(多个正则)。 PLIST 是属性列表,如 `'(face bold help-echo "tip")`。 OBJECT 是缓冲区或字符串;nil 表示当前缓冲区。 **示例:** ```elisp ;; 高亮缓冲区中的所有数字 (with-temp-buffer (insert "abc 123 def 456") (tp-regexp-set "[0-9]+" '(face font-lock-number-face)) (list (tp-at 5 'face) (tp-at 13 'face))) ;; => (font-lock-number-face font-lock-number-face) ;; 在字符串上 (tp-regexp-set "[A-Z]+" '(face bold) "Hello WORLD") ;; => #("Hello WORLD" 6 11 (face bold)) ;; 多个正则 - 同时匹配数字和大写字母 (tp-regexp-set '("[0-9]+" "[A-Z]+") '(face bold) "abc 123 XYZ") ;; => #("abc 123 XYZ" 4 7 (face bold) 8 11 (face bold)) ``` --- #### `tp-regexp-reset` - 正则匹配并重置 重置(完全替换)正则匹配处的所有属性。 PATTERN 可以是字符串或字符串列表(多个正则)。 PLIST 是属性列表,如 `'(face bold help-echo "tip")`。 OBJECT 是缓冲区或字符串;nil 表示当前缓冲区。 ```elisp (tp-regexp-reset PATTERN PLIST &optional OBJECT) ``` **示例:** ```elisp ;; 重置正则匹配处的所有属性 (with-temp-buffer (insert "abc 123 def 456") (tp-set 5 8 '(help-echo "original")) (tp-regexp-reset "[0-9]+" '(face bold)) (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) ``` --- #### `tp-regexp-add` - 正则匹配并添加 在正则匹配处添加/合并属性,支持深度合并。 PATTERN 可以是字符串或字符串列表(多个正则)。 PLIST 是属性列表,如 `'(face bold help-echo "tip")`。 OBJECT 是缓冲区或字符串;nil 表示当前缓冲区。 ```elisp (tp-regexp-add PATTERN PLIST &optional OBJECT) ``` **示例:** ```elisp ;; 添加属性到正则匹配处(保留现有) (with-temp-buffer (insert "abc 123 def 456") (tp-set 5 8 '(help-echo "number")) (tp-regexp-add "[0-9]+" '(face bold)) (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") ``` --- ### 搜索和导航函数 #### `tp-search-forward` / `tp-search-backward` ```elisp (tp-search-forward PROPERTY &optional VALUE PREDICATE NOT-CURRENT) (tp-search-backward PROPERTY &optional VALUE PREDICATE NOT-CURRENT) ``` Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的原始包装。 这些是直接使用 prop-match 对象的底层搜索函数。 --- #### `tp-forward` / `tp-backward` ```elisp (tp-forward PROPERTY &optional VALUE OBJECT N) (tp-backward PROPERTY &optional VALUE OBJECT N) ``` 向前/向后搜索 N 次具有 PROPERTY 的文本。 - **N** 是搜索次数,默认为 1。 - **VALUE** 是可选的匹配值。 - **OBJECT** 可以是缓冲区或字符串;nil 默认为当前缓冲区。 - 对于缓冲区,返回最后一次成功搜索的 prop-match 对象。 - 对于字符串,返回所有匹配的 (START END VALUE) 列表。 **示例:** ```elisp ;; 查找下一个具有 'marker 属性的文本 (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 ;; 查找下一个 'type 等于 'heading 的文本 (with-temp-buffer (insert "Hello World") (tp-set 1 6 '(type heading)) (goto-char 1) (let ((match (tp-forward 'type 'heading))) (when match (prop-match-value match)))) ;; => heading ;; 在字符串中搜索 (let ((my-string (copy-sequence "Hello World Hello"))) (tp-set 0 5 '(marker t) my-string) (tp-set 12 17 '(marker t) my-string) (tp-forward 'marker nil my-string 2)) ;; => ((0 5 t) (12 17 t)) ``` --- #### `tp-forward-do` / `tp-backward-do` ```elisp (tp-forward-do FUNCTION PROPERTY &optional VALUE OBJECT TIMES START END) (tp-backward-do FUNCTION PROPERTY &optional VALUE OBJECT TIMES START END) ``` 在 OBJECT 的 START 到 END 范围内,向前/向后搜索匹配 PROPERTY 属性(值为 VALUE)的部分,**仅对最后一次匹配执行 FUNCTION 函数**。 - **FUNCTION** 的参数是 `(TEXT &optional START END)`,其中 TEXT 是此次匹配到的文本,START 和 END 为开始结束的位置。FUNCTION 的返回值将替换字符串或缓冲区中的匹配文本。 - **PROPERTY** 是要搜索的文本属性。 - **VALUE** 为 nil 时,表示搜索 PROPERTY 属性,不用匹配值。 - **OBJECT** 默认是当前 buffer 或指定的字符串或指定的 buffer。 - **TIMES** 表示向前/向后搜索几次,默认搜索一次。该函数会搜索 TIMES 次,但仅对找到的最后(第 N 次)匹配应用 FUNCTION。 - **START** 和 **END** 默认为 OBJECT 的起始和结束位置。 - 返回成功匹配的数量。 **示例:** ```elisp ;; 仅将最后一次(第 2 次)匹配的文本转为大写 (let ((my-string (copy-sequence "hello world hello"))) (tp-set 0 5 '(marker t) my-string) (tp-set 12 17 '(marker t) my-string) (tp-forward-do #'upcase 'marker nil my-string 2) my-string) ;; => "hello world HELLO" ; 仅第 2 次匹配被转为大写 ;; 在指定范围内搜索(仅搜索范围 6-17 内的匹配) (let ((my-string (copy-sequence "hello world hello"))) (tp-set 0 5 '(marker t) my-string) (tp-set 12 17 '(marker t) my-string) (tp-forward-do #'upcase 'marker nil my-string 2 6 17) my-string) ;; => "hello world HELLO" ; 范围 6-17 内仅有 1 个匹配 ;; 使用带有 start 和 end 参数的函数 ;; 函数接收位置信息;使用 upcase 保持相同长度 (let ((my-string (copy-sequence "hello world hello")) (match-info nil)) (tp-set 0 5 '(marker t) my-string) (tp-set 12 17 '(marker t) my-string) (tp-forward-do (lambda (text start end) (setq match-info (list start end)) (upcase text)) 'marker nil my-string 2) (list my-string match-info)) ;; => ("hello world HELLO" (12 17)) ; 仅最后一次匹配被转换 ;; 向后搜索 - 仅将最后一次(第 2 次)匹配的文本转为大写 (let ((my-string (copy-sequence "hello world hello"))) (tp-set 0 5 '(marker t) my-string) (tp-set 12 17 '(marker t) my-string) (tp-backward-do #'upcase 'marker nil my-string 2) my-string) ;; => "HELLO world hello" ; 向后搜索时第一个匹配(即最后找到的)被转为大写 ``` --- #### `tp-search` - 搜索所有匹配 ```elisp ;; 缓冲区/字符串区域 (tp-search START END PROPERTY &optional VALUE OBJECT) ;; 整个字符串 (tp-search STRING PROPERTY &optional VALUE) ``` 在缓冲区/字符串范围或整个字符串中搜索所有具有 PROPERTY 的文本。 返回所有匹配区域的 (START END VALUE) 列表。 **示例:** ```elisp ;; 在缓冲区范围内查找所有 'marker 属性 (with-temp-buffer (insert "Hello World Test Again") (tp-set 1 6 '(marker t)) (tp-set 13 17 '(marker t)) (tp-search 1 22 'marker)) ;; => ((1 6 t) (13 17 t)) ;; 在字符串中查找所有值为 'heading 的 'type 属性 (let ((my-string (copy-sequence "Title Here Body Text"))) (tp-set 0 10 '(type heading) my-string) (tp-search my-string 'type 'heading)) ;; => ((0 10 heading)) ;; 按值过滤 (with-temp-buffer (insert "Heading1 Body Heading2") (tp-set 1 9 '(type heading)) (tp-set 10 14 '(type body)) (tp-set 15 23 '(type heading)) (tp-search 1 23 'type 'heading)) ;; => ((1 9 heading) (15 23 heading)) ``` --- #### `tp-search-map` - 对匹配文本应用函数 ```elisp (tp-search-map FUNCTION PROPERTY &optional VALUE OBJECT START END) ``` 在 OBJECT 的 START 到 END 范围内,匹配到 PROPERTY 属性(值是 VALUE)的部分执行 FUNCTION 函数。 - **FUNCTION** 的参数是 `(TEXT &optional START END IDX)`,其中: - TEXT 是此次匹配到的文本 - START 和 END 为开始结束的位置 - IDX 是遍历中的当前从 0 开始的索引 FUNCTION 的返回值将替换字符串或缓冲区中的匹配文本。 - **PROPERTY** 是要搜索的文本属性。 - **VALUE** 为 nil 时,表示搜索 PROPERTY 属性,不用匹配值。 - **OBJECT** 默认是当前 buffer 或指定的字符串或指定的 buffer。 - **START** 和 **END** 默认为 OBJECT 的起始和结束位置。 - 返回处理的匹配数量。 **示例:** ```elisp ;; 将字符串中所有 marker 文本转为大写 (let ((my-string (copy-sequence "hello world hello"))) (tp-set 0 5 '(marker t) my-string) (tp-set 12 17 '(marker t) my-string) (tp-search-map #'upcase 'marker nil my-string) my-string) ;; => "HELLO world HELLO" ;; 仅在指定范围内搜索 (let ((my-string (copy-sequence "hello world hello"))) (tp-set 0 5 '(marker t) my-string) (tp-set 12 17 '(marker t) my-string) (tp-search-map #'upcase 'marker nil my-string 0 10) my-string) ;; => "HELLO world hello" ; 仅范围 0-10 内的第一个匹配被处理 ;; 使用 start、end 和 idx 参数的自定义转换 ;; 函数接收位置信息;使用 upcase 保持相同长度 (let ((my-string (copy-sequence "aaa bbb ccc")) (positions nil)) (tp-set 0 3 '(marker t) my-string) (tp-set 4 7 '(marker t) my-string) (tp-set 8 11 '(marker t) my-string) (tp-search-map (lambda (text start end idx) (push (list idx start end) positions) (upcase text)) 'marker nil my-string) (list my-string (nreverse positions))) ;; => ("AAA BBB CCC" ((0 0 3) (1 4 7) (2 8 11))) ;; 不使用可选参数的自定义转换 (let ((my-string (copy-sequence "hello world"))) (tp-set 0 5 '(marker t) my-string) (tp-search-map #'upcase 'marker nil my-string) my-string) ;; => "HELLO world" ``` --- ## 属性层系统 属性层系统是 tp.el 的创新功能,允许在同一文本区域堆叠多组属性。只有顶层属性可见,但下层属性会被保留,并可通过轮转或固定操作使其显现。 ### 属性层概念 ┌─────────────────────────────┐ │ 顶层(可见) │ ← idx=0,你看到的 ├─────────────────────────────┤ │ 中间层(隐藏) │ ← idx=1,被保留 ├─────────────────────────────┤ │ 底层(隐藏) │ ← idx=-1,被保留 └─────────────────────────────┘ ### 属性层定义 #### `tp-define-layer` - 定义单个属性层 定义单个文本属性层。支持两种格式: **格式一 - 直接定义文本属性:** ```elisp (tp-define-layer layer-name (face (:background "cyan") line-prefix ">>")) ``` **格式二 - 使用 :props 关键字(为后续扩展预留):** ```elisp (tp-define-layer layer-name :props (face (:background "cyan") line-prefix ">>")) ``` 如果同名的层已存在,新定义将覆盖旧定义。 **示例:** ```elisp ;; 使用格式一(直接 plist)定义单个属性层 (progn (setq tp-layer-alist nil) ; 重置以确保干净的示例 (tp-define-layer highlight (face (:background "yellow" :foreground "black"))) (tp-layer-props 'highlight)) ;; => (face (:background "yellow" :foreground "black") tp-name highlight) ;; 使用格式二(:props 关键字)定义属性层 (progn (tp-define-layer error :props (face (:background "red" :foreground "white") help-echo "错误!")) (tp-layer-props 'error)) ;; => (face (:background "red" :foreground "white") help-echo "错误!" tp-name error) ;; 重新定义已存在的属性层(覆盖旧定义) (progn (tp-define-layer test-layer (face bold)) (tp-define-layer test-layer (face italic)) ; 覆盖 (tp-layer-props 'test-layer)) ;; => (face italic tp-name test-layer) ``` --- #### `tp-define-layer-group` - 定义属性层组 定义包含多个属性层的层组。每个元素支持三种格式: **格式一 - 匿名层(命名为 GROUP-NAME-0, GROUP-NAME-1 等):** ```elisp (tp-define-layer-group tp-test-moons (display "🌑" face (:height 1.0)) (display "🌘" face (:height 1.5)) (display "🌗" face (:height 2.0))) ;; 创建层: tp-test-moons-0, tp-test-moons-1, tp-test-moons-2 ``` **格式二 - 使用 cons-cell 命名层(命名为 GROUP-NAME-suffix):** ```elisp (tp-define-layer-group tp-test-moons ("新月" . (display "🌑" face (:height 1.0))) ("残月" . (display "🌘" face (:height 1.5))) ("下弦月" . (display "🌗" face (:height 2.0)))) ;; 创建层: tp-test-moons-新月, tp-test-moons-残月, tp-test-moons-下弦月 ``` **格式三 - 使用 :props 关键字命名层(命名为 GROUP-NAME-suffix):** ```elisp (tp-define-layer-group tp-test-moons ("新月" :props (display "🌑" face (:height 1.0))) ("残月" :props (display "🌘" face (:height 1.5))) ("下弦月" :props (display "🌗" face (:height 2.0)))) ;; 创建层: tp-test-moons-新月, tp-test-moons-残月, tp-test-moons-下弦月 ``` 你也可以在层组中引用已定义的层: ```elisp (tp-define-layer existing-layer (face bold)) (tp-define-layer-group my-group existing-layer ; 引用已存在的属性层 (face (:background "red") line-prefix ">>") ; 匿名属性层 ("named" . (face italic))) ; 命名属性层 ``` 如果同名的层组已存在,新定义将覆盖旧定义。 定义中的第一个属性层是顶层(默认可见)。 **示例:** ```elisp ;; 先定义状态层,然后将它们组合成层组 (progn (setq tp-layer-alist nil) (setq tp-layer-groups nil) (tp-define-layer highlight (face (:background "yellow" :foreground "black"))) (tp-define-layer error (face (:background "red" :foreground "white"))) (tp-define-layer info (face (:background "blue" :foreground "white"))) (tp-define-layer-group status-colors highlight error info) (length (tp-group-props 'status-colors))) ;; => 3 ;; 使用命名层定义层组 (progn (setq tp-layer-alist nil) (setq tp-layer-groups nil) (tp-define-layer-group moon-phases ("new" . (display "🌑")) ("waxing-crescent" . (display "🌒")) ("first-quarter" . (display "🌓")) ("full" . (display "🌕"))) (tp-layer-props 'moon-phases-full)) ;; => (display "🌕" tp-name moon-phases-full) ``` --- #### `tp-layer-props` / `tp-group-props` ```elisp (tp-layer-props LAYER-NAME) (tp-group-props GROUP-NAME) ``` 获取属性层或属性层组中所有属性层的属性。 **示例:** ```elisp ;; 获取属性层属性 (progn (setq tp-layer-alist nil) (tp-define-layer my-layer (face bold help-echo "tip")) (tp-layer-props 'my-layer)) ;; => (face bold help-echo "tip" tp-name my-layer) ;; 获取属性层组属性 (progn (setq tp-layer-alist nil) (setq tp-layer-groups nil) (tp-define-layer layer1 (face bold)) (tp-define-layer layer2 (face italic)) (tp-define-layer-group my-group layer1 layer2) (length (tp-group-props 'my-group))) ;; => 2 ``` --- #### `tp-undefine-layer` / `tp-undefine-group` ```elisp (tp-undefine-layer NAME) (tp-undefine-group NAME) ``` 移除属性层或属性层组定义。 **示例:** ```elisp ;; 取消定义属性层 (progn (setq tp-layer-alist nil) (tp-define-layer temp-layer (face bold)) (tp-undefine-layer 'temp-layer) (tp-layer-props 'temp-layer)) ;; => nil ;; 取消定义属性层组 (progn (setq tp-layer-alist nil) (setq tp-layer-groups nil) (tp-define-layer l1 (face bold)) (tp-define-layer my-group l1) (tp-undefine-group 'my-group) (assoc 'my-group tp-layer-groups)) ;; => nil ``` --- #### `tp-layer-reset` ```elisp (tp-layer-reset) ``` 清除所有属性层和属性层组定义。 **示例:** ```elisp (progn (tp-define-layer test-layer (face bold)) (tp-layer-reset) (list tp-layer-alist tp-layer-groups)) ;; => (nil nil) ``` --- ### 属性层放置 #### `tp-put-layer` - 在指定位置设置属性层 ```elisp ;; 缓冲区/字符串区域 (tp-put-layer START END LAYER IDX OBJECT) ;; 整个字符串 (tp-put-layer STRING LAYER IDX) ``` 在属性层堆栈的指定索引位置设置属性层。 - `IDX = 0`:顶部(可见属性层) - `IDX = -1`:底部 - 其他值在该位置插入 **示例:** ```elisp ;; 将 base 属性层放在顶部 (progn (tp-layer-reset) (tp-define-layer base (face default)) (tp-define-layer highlight (face (:background "yellow"))) (with-temp-buffer (insert "Hello World") (tp-put-layer 1 10 'base 0) (tp-at 1 'tp-name))) ;; => base ;; 将 highlight 放在索引 1(顶部下面) (progn (tp-layer-reset) (tp-define-layer base (face default)) (tp-define-layer highlight (face (:background "yellow"))) (with-temp-buffer (insert "Hello World") (tp-put-layer 1 10 'base 0) (tp-put-layer 1 10 'highlight 1) (tp-layer-count 1 10))) ;; => 2 ;; 将属性层放在底部 (progn (tp-layer-reset) (tp-define-layer base (face default)) (tp-define-layer info (face (:foreground "blue"))) (with-temp-buffer (insert "Hello World") (tp-put-layer 1 10 'base 0) (tp-put-layer 1 10 'info -1) (tp-layer-top 1 10))) ;; => base ; info 在底部,base 可见 ``` --- #### `tp-push-layer` - 推送属性层到顶部 ```elisp ;; 缓冲区/字符串区域 (tp-push-layer START END LAYER OBJECT) ;; 整个字符串 (tp-push-layer STRING LAYER) ``` 将属性层推到堆栈顶部(相当于 `tp-put-layer ... 0`)。 **示例:** ```elisp ;; 首先推入 base 属性层 (progn (tp-layer-reset) (tp-define-layer base (face default)) (tp-define-layer highlight (face (:background "yellow"))) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'base) (tp-at 1 'tp-name))) ;; => base ;; 将 highlight 推到顶部(现在可见) (progn (tp-layer-reset) (tp-define-layer base (face default)) (tp-define-layer highlight (face (:background "yellow"))) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'base) (tp-push-layer 1 10 'highlight) (tp-at 1 'tp-name))) ;; => highlight ``` --- ### 属性层删除 #### `tp-delete-layer` - 按名称/索引删除属性层 ```elisp ;; 缓冲区/字符串区域 (tp-delete-layer START END LAYER-NAME/IDX OBJECT) ;; 整个字符串 (tp-delete-layer STRING LAYER-NAME/IDX) ``` 通过名称或索引从堆栈任意位置删除属性层。 **示例:** ```elisp ;; 按名称删除 (progn (tp-layer-reset) (tp-define-layer highlight (face (:background "yellow"))) (tp-define-layer base (face default)) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'base) (tp-push-layer 1 10 'highlight) (tp-delete-layer 1 10 'highlight) (tp-at 1 'tp-name))) ;; => base ;; 删除顶层(idx=0) (progn (tp-layer-reset) (tp-define-layer layer1 (face bold)) (tp-define-layer layer2 (face italic)) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'layer1) (tp-push-layer 1 10 'layer2) (tp-delete-layer 1 10 0) (tp-at 1 'tp-name))) ;; => layer1 ;; 删除底层 (progn (tp-layer-reset) (tp-define-layer layer1 (face bold)) (tp-define-layer layer2 (face italic)) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'layer1) (tp-push-layer 1 10 'layer2) (tp-delete-layer 1 10 -1) (tp-layer-count 1 10))) ;; => 1 ``` --- #### `tp-pop-layer` - 弹出顶层 ```elisp ;; 缓冲区/字符串区域 (tp-pop-layer START END OBJECT) ;; 整个字符串 (tp-pop-layer STRING) ``` 删除顶层(相当于 `tp-delete-layer ... 0`)。 **示例:** ```elisp (progn (tp-layer-reset) (tp-define-layer layer1 (face bold)) (tp-define-layer layer2 (face italic)) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'layer1) (tp-push-layer 1 10 'layer2) (tp-pop-layer 1 10) (tp-at 1 'tp-name))) ;; => layer1 ``` --- ### 属性层移动 #### `tp-move-layer` - 移动属性层到指定位置 ```elisp ;; 缓冲区/字符串区域 (tp-move-layer START END FROM-ID TO-IDX OBJECT) ;; 整个字符串 (tp-move-layer STRING FROM-ID TO-IDX) ``` 将属性层从一个位置移动到另一个位置。 - `FROM-ID` 标识要移动的层:可以是整数索引或层名称符号 - `TO-IDX` 是目标位置(整数索引) - 索引 0 表示顶层(可见),-1 表示底层 - 两个索引都是指移动之前的位置 这是通用的属性层移动函数,`tp-raise-layer`、`tp-rotate-layer`、`tp-pin-layer` 和 `tp-switch-layer` 内部都使用它来实现。 **示例:** ```elisp ;; 将索引 2 的层移动到索引 0(顶部) (progn (tp-layer-reset) (tp-define-layer layer1 (face bold)) (tp-define-layer layer2 (face italic)) (tp-define-layer 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) ;; 堆栈: layer3 (0), layer2 (1), layer1 (2) (tp-move-layer 1 10 2 0) (tp-layer-top 1 10))) ;; => layer1 ;; 按名称移动层到底部 (progn (tp-layer-reset) (tp-define-layer layer1 (face bold)) (tp-define-layer layer2 (face italic)) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'layer1) (tp-push-layer 1 10 'layer2) ;; 堆栈: layer2 (顶), layer1 (底) (tp-move-layer 1 10 'layer2 -1) (tp-layer-top 1 10))) ;; => layer1 ;; 在字符串上移动 (let ((str (copy-sequence "Hello"))) (tp-layer-reset) (tp-define-layer layer1 (face bold)) (tp-define-layer layer2 (face italic)) (tp-push-layer str 'layer1) (tp-push-layer str 'layer2) ;; layer2 在顶部 (tp-move-layer str 'layer1 0) (tp-at 0 'tp-name str)) ;; => layer1 ``` --- #### `tp-raise-layer` - 上移/下移属性层 ```elisp ;; 缓冲区/字符串区域 (tp-raise-layer START END IDX/LAYER-NAME N OBJECT) ;; 整个字符串 (tp-raise-layer STRING IDX/LAYER-NAME N) ``` 将属性层上移 N 个位置。正数 N 向顶部移动,负数向底部移动。 **示例:** ```elisp ;; 将 layer1 上移 2 个位置(到顶部) (progn (tp-layer-reset) (tp-define-layer layer1 (face bold)) (tp-define-layer layer2 (face italic)) (tp-define-layer 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) ;; 堆栈: layer3 (顶), layer2, layer1 (底) (tp-raise-layer 1 10 'layer1 2) (tp-layer-top 1 10))) ;; => layer1 ;; 将索引 0 的属性层下移 1 个位置 (progn (tp-layer-reset) (tp-define-layer layer1 (face bold)) (tp-define-layer layer2 (face italic)) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'layer1) (tp-push-layer 1 10 'layer2) ;; 堆栈: layer2 (idx 0), layer1 (idx 1) (tp-raise-layer 1 10 0 -1) (tp-layer-top 1 10))) ;; => layer1 ``` --- #### `tp-rotate-layer` - 轮换属性层 ```elisp ;; 缓冲区/字符串区域 (tp-rotate-layer START END OBJECT) ;; 整个字符串 (tp-rotate-layer STRING) ``` 轮换属性层 - 顶层移到底部,下一层变为可见。 **示例:** ```elisp ;; 堆栈: highlight (顶) -> base (底) (progn (tp-layer-reset) (tp-define-layer base (face default)) (tp-define-layer highlight (face (:background "yellow"))) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'base) (tp-push-layer 1 10 'highlight) ;; 堆栈: highlight (顶) -> base (底) (tp-rotate-layer 1 10) ;; 堆栈: base (顶) -> highlight (底) (tp-layer-top 1 10))) ;; => base ``` --- #### `tp-pin-layer` - 将属性层置顶 ```elisp ;; 缓冲区/字符串区域 (tp-pin-layer START END IDX/LAYER-NAME OBJECT) ;; 整个字符串 (tp-pin-layer STRING IDX/LAYER-NAME) ``` 将特定属性层移到顶部(使其可见)。 **示例:** ```elisp ;; 将 'base 设为顶层 (progn (tp-layer-reset) (tp-define-layer base (face default)) (tp-define-layer highlight (face (:background "yellow"))) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'base) (tp-push-layer 1 10 'highlight) ;; highlight 在顶部 (tp-pin-layer 1 10 'base) (tp-layer-top 1 10))) ;; => base ``` --- #### `tp-switch-layer` - 交换两个属性层 ```elisp ;; 缓冲区/字符串区域 (tp-switch-layer START END IDX1/NAME1 IDX2/NAME2 OBJECT) ;; 整个字符串 (tp-switch-layer STRING IDX1/NAME1 IDX2/NAME2) ``` 交换两个属性层的位置。 **示例:** ```elisp ;; 交换 layer1 和 layer2 (progn (tp-layer-reset) (tp-define-layer layer1 (face bold)) (tp-define-layer layer2 (face italic)) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'layer1) (tp-push-layer 1 10 'layer2) ;; layer2 在顶部 (tp-switch-layer 1 10 'layer1 'layer2) ;; 现在 layer1 在顶部 (tp-layer-top 1 10))) ;; => layer1 ``` --- ### 属性层合并 #### `tp-merge-layers` - 合并多个属性层 ```elisp ;; 缓冲区/字符串区域 (tp-merge-layers START END NEW-LAYER-NAME '(IDX1 LAYER-NAME1 IDX2 ...) OBJECT) ;; 整个字符串 (tp-merge-layers STRING NEW-LAYER-NAME '(IDX1 LAYER-NAME1 IDX2 ...)) ``` 将指定的属性层合并为一个新属性层。列表中靠前的属性层优先级更高。 **示例:** ```elisp ;; 将 layer1 和 layer2 合并为 merged-layer (progn (tp-layer-reset) (tp-define-layer layer1 (face bold)) (tp-define-layer layer2 (help-echo "tip")) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'layer1) (tp-push-layer 1 10 'layer2) (tp-merge-layers 1 10 'merged-layer '(layer1 layer2)) (tp-at 1 'tp-name))) ;; => merged-layer ;; 按索引合并 (progn (tp-layer-reset) (tp-define-layer layer1 (face bold)) (tp-define-layer layer2 (help-echo "tip")) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'layer1) (tp-push-layer 1 10 'layer2) (tp-merge-layers 1 10 'merged '(0 1)) (tp-layer-count 1 10))) ;; => 1 ``` --- #### `tp-flatten-layers` - 扁平化所有属性层 ```elisp ;; 缓冲区/字符串区域 (tp-flatten-layers START END NAME OBJECT) ;; 整个字符串 (tp-flatten-layers STRING NAME) ``` 将所有属性层扁平化为一个具有给定名称的单一属性层。 **示例:** ```elisp ;; 将所有属性层扁平化为 'flat-layer (progn (tp-layer-reset) (tp-define-layer layer1 (face bold)) (tp-define-layer layer2 (help-echo "tip")) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'layer1) (tp-push-layer 1 10 'layer2) (tp-flatten-layers 1 10 'flat-layer) (tp-at 1 'tp-name))) ;; => flat-layer ;; 使用 nil 名称扁平化(无名属性层) (progn (tp-layer-reset) (tp-define-layer layer1 (face bold)) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'layer1) (tp-flatten-layers 1 10 nil) (tp-at 1 'tp-name))) ;; => nil ``` --- ### 属性层查询函数 #### `tp-layer-list` - 列出所有属性层 ```elisp (tp-layer-list START END &optional OBJECT) ``` 获取区域中所有属性层名称的列表。 **示例:** ```elisp (progn (tp-layer-reset) (tp-define-layer highlight (face (:background "yellow"))) (tp-define-layer base (face default)) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'base) (tp-push-layer 1 10 'highlight) (tp-layer-list 1 10))) ;; => (highlight base) ``` --- #### `tp-layer-count` ```elisp (tp-layer-count START END &optional OBJECT) ``` 计算区域中的属性层数量。 **示例:** ```elisp (progn (tp-layer-reset) (tp-define-layer layer1 (face bold)) (tp-define-layer layer2 (face italic)) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'layer1) (tp-push-layer 1 10 'layer2) (tp-layer-count 1 10))) ;; => 2 ``` --- #### `tp-layer-exists-p` ```elisp (tp-layer-exists-p START END NAME &optional OBJECT) ``` 检查区域中是否存在某属性层。 **示例:** ```elisp (progn (tp-layer-reset) (tp-define-layer layer1 (face bold)) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'layer1) (list (tp-layer-exists-p 1 10 'layer1) (tp-layer-exists-p 1 10 'layer2)))) ;; => (t nil) ``` --- #### `tp-layer-top` ```elisp (tp-layer-top START END &optional OBJECT) ``` 获取顶层(可见)属性层的名称。 **示例:** ```elisp (progn (tp-layer-reset) (tp-define-layer layer1 (face bold)) (tp-define-layer layer2 (face italic)) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'layer1) (tp-push-layer 1 10 'layer2) (tp-layer-top 1 10))) ;; => layer2 ``` --- #### `tp-add-to-layers` - 向特定属性层添加属性 ```elisp ;; 缓冲区/字符串区域 (tp-add-to-layers IDX-OR-LAYER-NAME-LIST START END PLIST &optional OBJECT) ;; 整个字符串 (tp-add-to-layers IDX-OR-LAYER-NAME-LIST STRING PROP VAL ...) ``` 向区域或字符串中的特定属性层添加或合并属性。 - **IDX-OR-LAYER-NAME-LIST** 是层索引(整数)或层名称(符号)的列表。对于索引:0 表示顶层,-1 表示底层。 - 属性被深度合并到指定的层中(嵌套的 plist 被合并,而非替换)。 - OBJECT 在区域形式中默认为当前缓冲区。 - 返回修改后的字符串或 nil(对于缓冲区操作)。 **示例:** ```elisp (progn (tp-layer-reset) (tp-define-layer layer1 (face (:foreground "red"))) (tp-define-layer layer2 (face (:foreground "blue"))) (with-temp-buffer (insert "Hello World") (tp-push-layer 1 10 'layer1) (tp-push-layer 1 10 'layer2) ;; 向两个层添加下划线 (tp-add-to-layers '(0 1) 1 10 '(face (:underline t))) (tp-at 5))) ;; 两个层现在都有下划线与其颜色合并 ``` --- #### `tp-add-to-all-layers` - 向所有属性层添加属性 ```elisp ;; 缓冲区/字符串区域 (tp-add-to-all-layers START END PLIST &optional OBJECT) ;; 整个字符串 (tp-add-to-all-layers STRING PROP VAL ...) ``` 向区域或字符串中的所有属性层添加或合并属性。 - 属性被深度合并到所有现有层中。 - OBJECT 在区域形式中默认为当前缓冲区。 - 返回修改后的字符串或 nil(对于缓冲区操作)。 **示例:** ```elisp (let ((str (copy-sequence "Hello World"))) (tp-define-layer layer1 (face bold)) (tp-define-layer layer2 (face italic)) (tp-push-layer 0 5 'layer1 str) (tp-push-layer 0 5 'layer2 str) ;; 向所有层添加下划线 (tp-add-to-all-layers 0 5 '(face (:underline t)) str) str) ``` --- #### `tp-intervals` - 获取文本属性区间 ```elisp (tp-intervals START END &optional OBJECT) ``` 从 OBJECT 中获取 START 到 END 之间的所有文本属性区间。 - 返回每个区间的 (START END PROPERTIES) 列表。 - 使用 `object-intervals`(需要 Emacs 28.1+)。 - OBJECT 可以是缓冲区或字符串;nil 默认为当前缓冲区。 **示例:** ```elisp (with-temp-buffer (insert "Hello World") (tp-set 1 6 '(face bold)) (tp-set 7 12 '(face italic)) (tp-intervals 1 12)) ;; => ((0 5 (face bold)) (6 11 (face italic))) ``` --- #### `tp-intervals-map` - 对区间应用函数 ```elisp (tp-intervals-map FUNCTION START END &optional OBJECT) ``` 对 OBJECT 中 START 到 END 之间的所有区间应用 FUNCTION。 - FUNCTION 接收四个参数:interval-start、interval-end、top-props(可见层属性)和 below-props-lst(隐藏层列表)。 - OBJECT 可以是缓冲区或字符串;nil 默认为当前缓冲区。 - 返回函数结果列表(nil 值被移除)。 **示例:** ```elisp (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) (list start end (plist-get props 'face))) 1 12)) ;; => ((0 5 bold) (6 11 italic)) ``` --- #### `tp-region-layer-props` - 获取区域中的层属性 ```elisp (tp-region-layer-props START END LAYER-NAME &optional OBJECT) ``` 返回区域 START 到 END 中 LAYER-NAME 的层属性。 - 返回匹配区间的 (START END PROPERTIES) 列表。 - OBJECT 默认为当前缓冲区。 **示例:** ```elisp (progn (tp-layer-reset) (tp-define-layer highlight (face (:background "yellow"))) (with-temp-buffer (insert "Hello World Test") (tp-push-layer 1 6 'highlight) (tp-push-layer 12 16 'highlight) (tp-region-layer-props 1 16 'highlight))) ;; => ((1 6 (face (:background "yellow") tp-name highlight)) ;; (12 16 (face (:background "yellow") tp-name highlight))) ``` --- #### `tp-plist` - 获取区域中的所有属性 ```elisp ;; 缓冲区/字符串区域 (tp-plist START END &optional OBJECT) ;; 整个字符串 (tp-plist STRING) ``` 获取区域或字符串中存在的所有属性的属性列表。 - 返回包含范围内找到的所有属性的 plist。 - OBJECT 在区域形式中默认为当前缓冲区。 **示例:** ```elisp (with-temp-buffer (insert "Hello World") (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) ``` --- #### `tp-empty-p` - 检查对象是否有属性 ```elisp (tp-empty-p &optional OBJECT) ``` 如果 OBJECT 没有文本属性,返回 t。 - OBJECT 可以是字符串或缓冲区;nil 默认为当前缓冲区。 - 使用 `object-intervals`(需要 Emacs 28.1+)。 **示例:** ```elisp (tp-empty-p "plain text") ; => t (let ((str (copy-sequence "text"))) (tp-set str 'face 'bold) (tp-empty-p str)) ; => nil ``` --- ## 实用示例 ### 多属性层语法高亮 ```elisp ;; 可以在缓冲区中运行的完整示例 (progn (tp-layer-reset) ;; 为不同高亮目的定义属性层 (tp-define-layer code-base (face font-lock-keyword-face)) (tp-define-layer code-error (face (:underline (:color "red" :style wave)) help-echo "语法错误")) (tp-define-layer code-debug (face (:background "dark blue"))) (with-temp-buffer (insert (make-string 100 ?x)) ; 创建 100 字符缓冲区 ;; 应用基础高亮 (tp-push-layer 1 100 'code-base) ;; 在有问题的代码上添加错误高亮 (tp-push-layer 50 60 'code-error) ;; 检查位置 55 的顶层 (tp-layer-top 50 60))) ;; => code-error ;; 切换函数(用于实际缓冲区) (defun toggle-error-view (start end) "在错误和正常视图之间切换。" (interactive "r") (tp-rotate-layer start end)) ``` ### 状态指示器 ```elisp ;; 包含属性层组的完整示例 (progn (tp-layer-reset) ;; 将状态属性层定义为一个组 (tp-define-layer status-todo (face (:foreground "gray"))) (tp-define-layer status-progress (face (:foreground "yellow"))) (tp-define-layer status-done (face (:foreground "green"))) (tp-define-layer-group task-status status-todo status-progress status-done) ;; 检查组是否已定义 (length (tp-group-props 'task-status))) ;; => 3 ;; 循环切换状态(用于实际缓冲区) (defun cycle-task-status () "循环切换当前行的任务状态属性层。" (interactive) (tp-rotate-layer (line-beginning-position) (line-end-position))) ``` ### 临时高亮 ```elisp ;; 定义临时高亮属性层 (progn (tp-layer-reset) (tp-define-layer temp-highlight (face (:background "yellow"))) (tp-layer-props 'temp-highlight)) ;; => (face (:background "yellow") tp-name temp-highlight) ;; 闪烁函数(用于实际缓冲区) (defun flash-region (start end) "临时闪烁一个区域。" (tp-push-layer start end 'temp-highlight) (run-with-timer 0.5 nil (lambda (s e) (tp-delete-layer s e 'temp-highlight)) start end)) ``` --- ## 许可证 GNU 通用公共许可证 v2 或更高版本。 --- ## 贡献 欢迎贡献!请随时提交 issues 或 pull requests。 ---tp.el - 让文本属性变得强大且易用