From a3b531177e1f31aaf055fe0bbf654b74d79eccfe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Dec 2025 12:59:28 +0000 Subject: [PATCH] Fix tp-match and tp-regexp APIs to support multiple patterns as list Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com> --- README.md | 47 +++++++++++++---- README_CN.md | 47 +++++++++++++---- tp-tests.el | 60 +++++++++++++++++++-- tp.el | 143 ++++++++++++++++++++++++++++++--------------------- 4 files changed, 216 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index a11fee7..c72fc43 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/README_CN.md b/README_CN.md index 1c4afea..fd960a3 100644 --- a/README_CN.md +++ b/README_CN.md @@ -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) diff --git a/tp-tests.el b/tp-tests.el index fa9f750..0aec3ef 100644 --- a/tp-tests.el +++ b/tp-tests.el @@ -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"))) diff --git a/tp.el b/tp.el index bff0f37..c2cfd07 100644 --- a/tp.el +++ b/tp.el @@ -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 object) + (let ((pos 0)) + (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 object)) + (setq pos (if (= beg end) (1+ beg) end)))) + object)) + ;; Buffer or nil (current buffer) + (t + (let ((buf (or object (current-buffer)))) + (with-current-buffer buf + (save-excursion + (goto-char (point-min)) + (let (regions) + (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)))))))) + +(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 search-object) - (let ((pos 0)) - (while (string-match (regexp-quote search-pattern) search-object pos) - (let ((beg (match-beginning 0)) - (end (match-end 0))) - (when properties - (funcall apply-fn beg end properties search-object)) - (setq pos (if (= beg end) (1+ beg) end)))) - search-object)) + ((stringp object) + (dolist (p patterns) + (tp--match-apply-single p properties apply-fn object)) + object) ;; Buffer or nil (current buffer) (t - (let ((buf (or search-object (current-buffer)))) - (with-current-buffer buf - (save-excursion - (goto-char (point-min)) - (let (regions) - (while (search-forward search-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))))))))) + (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 (pattern properties apply-fn &optional object) - "Internal function to apply APPLY-FN to regexp matches of PATTERN. +(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))