Fix tp-match and tp-regexp APIs to support multiple patterns as list

Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-12-15 12:59:28 +00:00
parent 3123f01b1b
commit a3b531177e
4 changed files with 216 additions and 81 deletions

View File

@ -529,17 +529,20 @@ Clear all text properties from a region.
#### `tp-match-set` - Match String
```elisp
;; Buffer
;; Single pattern - Buffer
(tp-match-set PATTERN '(PROPERTY VALUE ...))
;; String or Buffer object
;; Single pattern - String or Buffer object
(tp-match-set PATTERN OBJECT '(PROPERTY VALUE ...))
;; Pattern as (PATTERN STRING) format
(tp-match-set '(PATTERN STRING) '(PROPERTY VALUE ...))
;; Multiple patterns - apply to all matches of all patterns
(tp-match-set '(PATTERN1 PATTERN2 ...) '(PROPERTY VALUE ...))
(tp-match-set '(PATTERN1 PATTERN2 ...) '(PROPERTY VALUE ...) OBJECT)
```
Set properties on all occurrences of a string pattern.
PATTERN can be a string (single pattern) or a list of strings (multiple patterns).
When multiple patterns are provided, each is matched and has properties applied.
**Examples:**
@ -552,9 +555,13 @@ Set properties on all occurrences of a string pattern.
(tp-match-set "o" "Hello World" '(face bold))
;; => #("Hello World" 4 5 (face bold) 7 8 (face bold))
;; Using (PATTERN STRING) format
(tp-match-set '("world" "Hello world") '(face bold))
;; => #("Hello world" 6 11 (face bold))
;; Multiple patterns - match both "world" and "Hello"
(tp-match-set '("world" "Hello") '(face bold))
;; Matches all occurrences of "world" AND all occurrences of "Hello"
;; Multiple patterns on string
(tp-match-set '("Hello" "world") '(face bold) "Hello world")
;; => #("Hello world" 0 5 (face bold) 6 11 (face bold))
```
---
@ -562,6 +569,7 @@ Set properties on all occurrences of a string pattern.
#### `tp-match-reset` - Match and Reset
Reset (completely replace) all properties on matches.
PATTERN can be a string or list of strings (multiple patterns).
```elisp
(tp-match-reset PATTERN '(PROPERTY VALUE ...) &optional OBJECT)
@ -572,6 +580,10 @@ Reset (completely replace) all properties on matches.
```elisp
(tp-match-reset "TODO" '(face warning))
;; Replaces ALL properties on matched text
;; Multiple patterns
(tp-match-reset '("TODO" "FIXME") '(face warning))
;; Replaces properties on all occurrences of "TODO" and "FIXME"
```
---
@ -579,6 +591,7 @@ Reset (completely replace) all properties on matches.
#### `tp-match-add` - Match and Add
Add/merge properties on matches with deep merge support.
PATTERN can be a string or list of strings (multiple patterns).
```elisp
(tp-match-add PATTERN '(PROPERTY VALUE ...) &optional OBJECT)
@ -589,6 +602,10 @@ Add/merge properties on matches with deep merge support.
```elisp
(tp-match-add "TODO" '(face (:underline t)))
;; Merges with existing properties
;; Multiple patterns
(tp-match-add '("TODO" "FIXME") '(face (:underline t)))
;; Merges properties on all occurrences of "TODO" and "FIXME"
```
---
@ -596,14 +613,20 @@ Add/merge properties on matches with deep merge support.
#### `tp-regexp-set` - Match Regexp
```elisp
;; Buffer
;; Single regexp - Buffer
(tp-regexp-set PATTERN '(PROPERTY VALUE ...))
;; String or Buffer object
;; Single regexp - String or Buffer object
(tp-regexp-set PATTERN OBJECT '(PROPERTY VALUE ...))
;; Multiple regexps - apply to all matches of all regexps
(tp-regexp-set '(REGEXP1 REGEXP2 ...) '(PROPERTY VALUE ...))
(tp-regexp-set '(REGEXP1 REGEXP2 ...) '(PROPERTY VALUE ...) OBJECT)
```
Set properties on all matches of a regular expression.
PATTERN can be a string (single regexp) or a list of strings (multiple regexps).
When multiple patterns are provided, each is matched and has properties applied.
**Examples:**
@ -614,6 +637,10 @@ Set properties on all matches of a regular expression.
;; On string
(tp-regexp-set "[A-Z]+" "Hello WORLD" '(face bold))
;; => #("Hello WORLD" 6 11 (face bold))
;; Multiple regexps - match both numbers and uppercase letters
(tp-regexp-set '("[0-9]+" "[A-Z]+") '(face bold) "abc 123 XYZ")
;; Matches "123" and "XYZ"
```
---
@ -621,6 +648,7 @@ Set properties on all matches of a regular expression.
#### `tp-regexp-reset` - Regexp and Reset
Reset (completely replace) all properties on regexp matches.
PATTERN can be a string or list of strings (multiple regexps).
```elisp
(tp-regexp-reset PATTERN '(PROPERTY VALUE ...) &optional OBJECT)
@ -631,6 +659,7 @@ Reset (completely replace) all properties on regexp matches.
#### `tp-regexp-add` - Regexp and Add
Add/merge properties on regexp matches with deep merge support.
PATTERN can be a string or list of strings (multiple regexps).
```elisp
(tp-regexp-add PATTERN '(PROPERTY VALUE ...) &optional OBJECT)

View File

@ -528,17 +528,20 @@ tp.el 所有函数按类别组织的完整概览:
#### `tp-match-set` - 匹配字符串
```elisp
;; 缓冲区
;; 单个模式 - 缓冲区
(tp-match-set PATTERN '(PROPERTY VALUE ...))
;; 字符串或缓冲区对象
;; 单个模式 - 字符串或缓冲区对象
(tp-match-set PATTERN OBJECT '(PROPERTY VALUE ...))
;; 使用 (PATTERN STRING) 格式
(tp-match-set '(PATTERN STRING) '(PROPERTY VALUE ...))
;; 多个模式 - 对所有模式的所有匹配应用属性
(tp-match-set '(PATTERN1 PATTERN2 ...) '(PROPERTY VALUE ...))
(tp-match-set '(PATTERN1 PATTERN2 ...) '(PROPERTY VALUE ...) OBJECT)
```
在所有字符串模式匹配处设置属性。
PATTERN 可以是字符串(单个模式)或字符串列表(多个模式)。
当提供多个模式时,每个模式都会被匹配并应用属性。
**示例:**
@ -551,9 +554,13 @@ tp.el 所有函数按类别组织的完整概览:
(tp-match-set "o" "Hello World" '(face bold))
;; => #("Hello World" 4 5 (face bold) 7 8 (face bold))
;; 使用 (PATTERN STRING) 格式
(tp-match-set '("world" "Hello world") '(face bold))
;; => #("Hello world" 6 11 (face bold))
;; 多个模式 - 同时匹配 "world" 和 "Hello"
(tp-match-set '("world" "Hello") '(face bold))
;; 匹配所有 "world" 出现的位置和所有 "Hello" 出现的位置
;; 在字符串上使用多个模式
(tp-match-set '("Hello" "world") '(face bold) "Hello world")
;; => #("Hello world" 0 5 (face bold) 6 11 (face bold))
```
---
@ -561,6 +568,7 @@ tp.el 所有函数按类别组织的完整概览:
#### `tp-match-reset` - 匹配并重置
重置(完全替换)匹配处的所有属性。
PATTERN 可以是字符串或字符串列表(多个模式)。
```elisp
(tp-match-reset PATTERN '(PROPERTY VALUE ...) &optional OBJECT)
@ -571,6 +579,10 @@ tp.el 所有函数按类别组织的完整概览:
```elisp
(tp-match-reset "TODO" '(face warning))
;; 替换匹配文本上的所有属性
;; 多个模式
(tp-match-reset '("TODO" "FIXME") '(face warning))
;; 替换所有 "TODO" 和 "FIXME" 出现位置的属性
```
---
@ -578,6 +590,7 @@ tp.el 所有函数按类别组织的完整概览:
#### `tp-match-add` - 匹配并添加
在匹配处添加/合并属性,支持深度合并。
PATTERN 可以是字符串或字符串列表(多个模式)。
```elisp
(tp-match-add PATTERN '(PROPERTY VALUE ...) &optional OBJECT)
@ -588,6 +601,10 @@ tp.el 所有函数按类别组织的完整概览:
```elisp
(tp-match-add "TODO" '(face (:underline t)))
;; 与现有属性合并
;; 多个模式
(tp-match-add '("TODO" "FIXME") '(face (:underline t)))
;; 合并属性到所有 "TODO" 和 "FIXME" 出现的位置
```
---
@ -595,14 +612,20 @@ tp.el 所有函数按类别组织的完整概览:
#### `tp-regexp-set` - 匹配正则表达式
```elisp
;; 缓冲区
;; 单个正则 - 缓冲区
(tp-regexp-set PATTERN '(PROPERTY VALUE ...))
;; 字符串或缓冲区对象
;; 单个正则 - 字符串或缓冲区对象
(tp-regexp-set PATTERN OBJECT '(PROPERTY VALUE ...))
;; 多个正则 - 对所有正则的所有匹配应用属性
(tp-regexp-set '(REGEXP1 REGEXP2 ...) '(PROPERTY VALUE ...))
(tp-regexp-set '(REGEXP1 REGEXP2 ...) '(PROPERTY VALUE ...) OBJECT)
```
在所有正则表达式匹配处设置属性。
PATTERN 可以是字符串(单个正则)或字符串列表(多个正则)。
当提供多个模式时,每个模式都会被匹配并应用属性。
**示例:**
@ -613,6 +636,10 @@ tp.el 所有函数按类别组织的完整概览:
;; 在字符串上
(tp-regexp-set "[A-Z]+" "Hello WORLD" '(face bold))
;; => #("Hello WORLD" 6 11 (face bold))
;; 多个正则 - 同时匹配数字和大写字母
(tp-regexp-set '("[0-9]+" "[A-Z]+") '(face bold) "abc 123 XYZ")
;; 匹配 "123" 和 "XYZ"
```
---
@ -620,6 +647,7 @@ tp.el 所有函数按类别组织的完整概览:
#### `tp-regexp-reset` - 正则匹配并重置
重置(完全替换)正则匹配处的所有属性。
PATTERN 可以是字符串或字符串列表(多个正则)。
```elisp
(tp-regexp-reset PATTERN '(PROPERTY VALUE ...) &optional OBJECT)
@ -630,6 +658,7 @@ tp.el 所有函数按类别组织的完整概览:
#### `tp-regexp-add` - 正则匹配并添加
在正则匹配处添加/合并属性,支持深度合并。
PATTERN 可以是字符串或字符串列表(多个正则)。
```elisp
(tp-regexp-add PATTERN '(PROPERTY VALUE ...) &optional OBJECT)

View File

@ -1124,13 +1124,32 @@ Returns list of (START END VALUE) intervals."
;;; Match Pattern Format Tests
;;; ============================================================
(ert-deftest tp-test-match-set-pattern-string-format ()
"Test tp-match-set with (pattern string) format."
(let* ((str (copy-sequence "Hello world"))
(result (tp-match-set '("world" "Hello world") '(face bold))))
(ert-deftest tp-test-match-set-multiple-patterns ()
"Test tp-match-set with multiple patterns (list of patterns)."
(tp-test-with-temp-buffer
(insert "Hello world, Hello again")
;; Match both "world" and "Hello" - both should get properties applied
(let ((regions (tp-match-set '("world" "Hello") '(face bold))))
;; Should find 3 matches: "Hello", "world", "Hello"
(should (= (length regions) 3))
;; Check that "Hello" at position 1 has face bold
(should (eq (tp-at 1 'face) 'bold))
;; Check that "world" at position 7 has face bold
(should (eq (tp-at 7 'face) 'bold))
;; Check that "Hello" at position 14 has face bold
(should (eq (tp-at 14 'face) 'bold)))))
(ert-deftest tp-test-match-set-multiple-patterns-on-string ()
"Test tp-match-set with multiple patterns on string."
(let* ((str (copy-sequence "Hello world, Hello again"))
(result (tp-match-set '("world" "Hello") '(face bold) str)))
(should (stringp result))
;; Check that "Hello" at position 0 has face bold
(should (eq (get-text-property 0 'face result) 'bold))
;; Check that "world" at position 6 has face bold
(should (eq (get-text-property 6 'face result) 'bold))
(should (null (get-text-property 0 'face result)))))
;; Check that "Hello" at position 13 has face bold
(should (eq (get-text-property 13 'face result) 'bold))))
(ert-deftest tp-test-match-reset ()
"Test tp-match-reset completely replaces properties."
@ -1205,6 +1224,37 @@ Returns list of (START END VALUE) intervals."
(should (eq (get-text-property 12 'face result) 'italic))
(should (null (get-text-property 0 'face result))))))
(ert-deftest tp-test-regexp-set-multiple-patterns ()
"Test tp-regexp-set with multiple patterns (list of regexps)."
(tp-test-with-temp-buffer
(insert "abc 123 def 456 ghi")
;; Match both numbers and "abc" - all should get properties applied
(let ((regions (tp-regexp-set '("[0-9]+" "abc") '(face bold))))
;; Should find 3 matches: "abc", "123", "456"
(should (= (length regions) 3))
;; Check that "abc" at position 1 has face bold
(should (eq (tp-at 1 'face) 'bold))
;; Check that "123" at position 5 has face bold
(should (eq (tp-at 5 'face) 'bold))
;; Check that "456" at position 13 has face bold
(should (eq (tp-at 13 'face) 'bold))
;; Check that "def" does NOT have face bold
(should (null (tp-at 9 'face))))))
(ert-deftest tp-test-regexp-set-multiple-patterns-on-string ()
"Test tp-regexp-set with multiple patterns on string."
(let* ((str (copy-sequence "abc 123 def 456"))
(result (tp-regexp-set '("[0-9]+" "abc") '(face italic) str)))
(should (stringp result))
;; Check that "abc" at position 0 has face italic
(should (eq (get-text-property 0 'face result) 'italic))
;; Check that "123" at position 4 has face italic
(should (eq (get-text-property 4 'face result) 'italic))
;; Check that "456" at position 12 has face italic
(should (eq (get-text-property 12 'face result) 'italic))
;; Check that "def" does NOT have face italic
(should (null (get-text-property 8 'face result)))))
(ert-deftest tp-test-get-range-multiple-intervals ()
"Test tp-get returns all property intervals in a range."
(let ((str (copy-sequence "Hello World Hello")))

115
tp.el
View File

@ -712,45 +712,59 @@ OBJECT defaults to current buffer."
;;; Match and regexp functions
(defun tp--match-apply (pattern properties apply-fn &optional object)
"Internal function to apply APPLY-FN to matches of PATTERN.
PATTERN can be a string or (PATTERN STRING) for substring matching.
(defun tp--match-apply-single (pattern properties apply-fn object)
"Apply APPLY-FN to matches of single PATTERN in OBJECT.
APPLY-FN is called with (START END PROPS OBJECT) for each match.
Returns modified object or list of regions."
(let ((search-pattern pattern)
(search-object object))
;; Handle (PATTERN STRING) format
(when (and (listp pattern) (stringp (car pattern)) (stringp (cadr pattern)))
(setq search-pattern (car pattern)
search-object (cadr pattern)))
(cond
;; String object
((stringp search-object)
((stringp object)
(let ((pos 0))
(while (string-match (regexp-quote search-pattern) search-object pos)
(while (string-match (regexp-quote pattern) object pos)
(let ((beg (match-beginning 0))
(end (match-end 0)))
(when properties
(funcall apply-fn beg end properties search-object))
(funcall apply-fn beg end properties object))
(setq pos (if (= beg end) (1+ beg) end))))
search-object))
object))
;; Buffer or nil (current buffer)
(t
(let ((buf (or search-object (current-buffer))))
(let ((buf (or object (current-buffer))))
(with-current-buffer buf
(save-excursion
(goto-char (point-min))
(let (regions)
(while (search-forward search-pattern nil t)
(while (search-forward pattern nil t)
(let ((beg (match-beginning 0))
(end (match-end 0)))
(when properties
(funcall apply-fn beg end properties buf))
(push (cons beg end) regions)))
(nreverse regions)))))))))
(nreverse regions))))))))
(defun tp--regexp-apply (pattern properties apply-fn &optional object)
"Internal function to apply APPLY-FN to regexp matches of PATTERN.
(defun tp--match-apply (pattern properties apply-fn &optional object)
"Internal function to apply APPLY-FN to matches of PATTERN.
PATTERN can be a string or a list of strings (multiple patterns).
When PATTERN is a list, each element is a pattern to match.
APPLY-FN is called with (START END PROPS OBJECT) for each match.
Returns modified object or list of regions."
(let ((patterns (if (listp pattern) pattern (list pattern))))
(cond
;; String object
((stringp object)
(dolist (p patterns)
(tp--match-apply-single p properties apply-fn object))
object)
;; Buffer or nil (current buffer)
(t
(let ((all-regions nil))
(dolist (p patterns)
(let ((regions (tp--match-apply-single p properties apply-fn object)))
(setq all-regions (append all-regions regions))))
all-regions)))))
(defun tp--regexp-apply-single (pattern properties apply-fn object)
"Apply APPLY-FN to regexp matches of single PATTERN in OBJECT.
APPLY-FN is called with (START END PROPS OBJECT) for each match.
Returns modified object or list of regions."
(cond
@ -779,6 +793,27 @@ Returns modified object or list of regions."
(push (cons beg end) regions)))
(nreverse regions))))))))
(defun tp--regexp-apply (pattern properties apply-fn &optional object)
"Internal function to apply APPLY-FN to regexp matches of PATTERN.
PATTERN can be a string (single regexp) or a list of strings (multiple regexps).
When PATTERN is a list, each element is a regexp to match.
APPLY-FN is called with (START END PROPS OBJECT) for each match.
Returns modified object or list of regions."
(let ((patterns (if (listp pattern) pattern (list pattern))))
(cond
;; String object
((stringp object)
(dolist (p patterns)
(tp--regexp-apply-single p properties apply-fn object))
object)
;; Buffer or nil (current buffer)
(t
(let ((all-regions nil))
(dolist (p patterns)
(let ((regions (tp--regexp-apply-single p properties apply-fn object)))
(setq all-regions (append all-regions regions))))
all-regions)))))
(defun tp--parse-match-args (args)
"Parse match/regexp function ARGS.
Returns (OBJECT . PROPERTIES).
@ -824,16 +859,6 @@ Handles two calling conventions:
(setq properties (car properties)))
(cons object properties)))
(defun tp--parse-pattern-format (pattern object)
"Parse PATTERN for (PATTERN STRING) format.
Returns (PARSED-PATTERN . OBJECT)."
(if (and (listp pattern) (stringp (car pattern)))
(cons (car pattern)
(if (stringp (cadr pattern))
(cadr pattern)
object))
(cons pattern object)))
(defun tp--deep-merge-apply (start end props obj)
"Apply PROPS to OBJ from START to END with deep merge.
Merges nested plists instead of replacing them."
@ -866,32 +891,29 @@ This function supports multiple calling conventions:
(tp-match-set PATTERN PROPERTY VALUE ...)
(tp-match-set PATTERN \\='(PROPERTY VALUE ...))
3. With pattern as (PATTERN STRING) to match within STRING:
(tp-match-set \\='(\"world\" \"Hello world\") \\='(face bold))
3. Multiple patterns (list of patterns to match):
(tp-match-set \\='(\"pattern1\" \"pattern2\" ...) \\='(PROPERTY VALUE ...))
(tp-match-set \\='(\"pattern1\" \"pattern2\" ...) \\='(PROPERTY VALUE ...) OBJECT)
PATTERN is the string to search for.
PATTERN is a string (single pattern) or list of strings (multiple patterns).
Each pattern will be matched and have properties applied.
PROPERTIES is a plist of property-value pairs.
Returns:
- For strings: the modified string
- For buffers: list of (START . END) pairs for all matches."
(let* ((parsed (tp--parse-match-args args))
(object (car parsed))
(properties (cdr parsed))
(parsed-pattern (tp--parse-pattern-format pattern object)))
(setq pattern (car parsed-pattern)
object (cdr parsed-pattern))
(properties (cdr parsed)))
(tp--match-apply pattern properties #'tp-set object)))
(defun tp-match-reset (pattern &rest args)
"Reset (completely replace) properties on all occurrences of PATTERN.
Same calling conventions as `tp-match-set'.
PATTERN can be a string or list of strings (multiple patterns).
Unlike `tp-match-set', this completely replaces all existing properties."
(let* ((parsed (tp--parse-match-args args))
(object (car parsed))
(properties (cdr parsed))
(parsed-pattern (tp--parse-pattern-format pattern object)))
(setq pattern (car parsed-pattern)
object (cdr parsed-pattern))
(properties (cdr parsed)))
(tp--match-apply pattern properties
(lambda (start end props obj)
(set-text-properties start end props obj))
@ -900,13 +922,11 @@ Unlike `tp-match-set', this completely replaces all existing properties."
(defun tp-match-add (pattern &rest args)
"Add/update properties on all occurrences of PATTERN.
Same calling conventions as `tp-match-set'.
PATTERN can be a string or list of strings (multiple patterns).
Unlike `tp-match-set', this deeply merges nested properties."
(let* ((parsed (tp--parse-match-args args))
(object (car parsed))
(properties (cdr parsed))
(parsed-pattern (tp--parse-pattern-format pattern object)))
(setq pattern (car parsed-pattern)
object (cdr parsed-pattern))
(properties (cdr parsed)))
(tp--match-apply pattern properties #'tp--deep-merge-apply object)))
(defun tp-regexp-set (pattern &rest args)
@ -923,7 +943,12 @@ This function supports multiple calling conventions:
(tp-regexp-set PATTERN PROPERTY VALUE ...)
(tp-regexp-set PATTERN \\='(PROPERTY VALUE ...))
PATTERN is the regexp to search for.
3. Multiple patterns (list of regexps to match):
(tp-regexp-set \\='(\"regexp1\" \"regexp2\" ...) \\='(PROPERTY VALUE ...))
(tp-regexp-set \\='(\"regexp1\" \"regexp2\" ...) \\='(PROPERTY VALUE ...) OBJECT)
PATTERN is a string (single regexp) or list of strings (multiple regexps).
Each pattern will be matched and have properties applied.
PROPERTIES is a plist of property-value pairs.
Returns:
- For strings: the modified string
@ -936,6 +961,7 @@ Returns:
(defun tp-regexp-reset (pattern &rest args)
"Reset (completely replace) properties on all regexp matches of PATTERN.
Same calling conventions as `tp-regexp-set'.
PATTERN can be a string or list of strings (multiple regexps).
Unlike `tp-regexp-set', this completely replaces all existing properties."
(let* ((parsed (tp--parse-match-args args))
(object (car parsed))
@ -948,6 +974,7 @@ Unlike `tp-regexp-set', this completely replaces all existing properties."
(defun tp-regexp-add (pattern &rest args)
"Add/update properties on all regexp matches of PATTERN.
Same calling conventions as `tp-regexp-set'.
PATTERN can be a string or list of strings (multiple regexps).
Unlike `tp-regexp-set', this deeply merges nested properties."
(let* ((parsed (tp--parse-match-args args))
(object (car parsed))