From 47b6505d519e5669a994b93a3921ce2453245598 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 04:01:57 +0000 Subject: [PATCH] Update tp-forward-do/tp-backward-do docs and tests for "last match only" behavior Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com> --- README.md | 28 +++++++-------- README_CN.md | 27 ++++++++------- tp-tests.el | 98 +++++++++++++++++++++++++++++----------------------- tp.el | 24 +++++++------ 4 files changed, 96 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 286cedb..3d03265 100644 --- a/README.md +++ b/README.md @@ -885,11 +885,10 @@ Search forward/backward N times for text with PROPERTY. (tp-backward-do FUNCTION PROPERTY &optional VALUE OBJECT POINT N) ``` -Search forward/backward N times for text with PROPERTY and apply FUNCTION to matched text. +Search forward/backward N times for text with PROPERTY and apply FUNCTION **only to the last match**. -- **FUNCTION** receives the matched text as its only argument. The return value - of FUNCTION replaces the matched text in the string or buffer. -- **N** is the number of searches, defaulting to 1. +- **FUNCTION** receives the matched text as its first argument. Optionally, FUNCTION can accept two additional arguments: START and END, representing the start and end positions of the match. The return value of FUNCTION replaces the matched text in the string or buffer. +- **N** is the number of searches, defaulting to 1. The function searches N times but only applies FUNCTION to the last (Nth) match found. - **OBJECT** can be a buffer or string; nil defaults to current buffer. - **POINT** is the starting position for search; for buffers nil means current point, for strings nil means 0 (forward) or end of string (backward). @@ -898,7 +897,7 @@ Search forward/backward N times for text with PROPERTY and apply FUNCTION to mat **Examples:** ```elisp -;; Upcase matched text in buffer (starting from current point) +;; Upcase only the last (2nd) match in buffer (with-temp-buffer (insert "hello world test") (tp-set 1 6 '(marker t)) @@ -906,17 +905,17 @@ Search forward/backward N times for text with PROPERTY and apply FUNCTION to mat (goto-char 1) (tp-forward-do #'upcase 'marker nil nil nil 2) (buffer-string)) -;; => "HELLO world TEST" +;; => "hello world TEST" ; Only the 2nd match is upcased -;; Upcase matched text in string (starting from position 0) +;; Upcase only the last (2nd) match in string (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 nil 2) my-string) -;; => "HELLO world HELLO" +;; => "hello world HELLO" ; Only the 2nd match is upcased -;; Start search from specific position +;; Start search from specific position (only 1 match found and transformed) (let ((my-string (copy-sequence "hello world hello"))) (tp-set 0 5 '(marker t) my-string) (tp-set 12 17 '(marker t) my-string) @@ -924,17 +923,18 @@ Search forward/backward N times for text with PROPERTY and apply FUNCTION to mat my-string) ;; => "hello world HELLO" ; Only matches from position 6 onward -;; Custom transformation +;; Using function with start and end parameters (with-temp-buffer (insert "hello world test") (tp-set 1 6 '(marker t)) + (tp-set 13 17 '(marker t)) (goto-char 1) (tp-forward-do - (lambda (text) - (concat "[" text "]")) - 'marker nil nil nil 1) + (lambda (text start end) + (format "[%d-%d]%s" start end text)) + 'marker nil nil nil 2) (buffer-string)) -;; => "[hello] world test" +;; => "hello world [13-17]test" ; Only the last match is transformed ``` --- diff --git a/README_CN.md b/README_CN.md index 9465ff5..1dffde9 100644 --- a/README_CN.md +++ b/README_CN.md @@ -884,10 +884,10 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的 (tp-backward-do FUNCTION PROPERTY &optional VALUE OBJECT POINT N) ``` -向前/向后搜索 N 次具有 PROPERTY 的文本,并对匹配的文本应用 FUNCTION。 +向前/向后搜索 N 次具有 PROPERTY 的文本,**仅对最后一次匹配应用 FUNCTION**。 -- **FUNCTION** 接收匹配到的文本作为唯一参数。FUNCTION 的返回值将替换字符串或缓冲区中的匹配文本。 -- **N** 是搜索次数,默认为 1。 +- **FUNCTION** 接收匹配到的文本作为第一个参数。可选地,FUNCTION 可以接受两个额外的参数:START 和 END,表示匹配的起始和结束位置。FUNCTION 的返回值将替换字符串或缓冲区中的匹配文本。 +- **N** 是搜索次数,默认为 1。该函数会搜索 N 次,但仅对找到的最后(第 N 次)匹配应用 FUNCTION。 - **OBJECT** 可以是缓冲区或字符串;nil 默认为当前缓冲区。 - **POINT** 是搜索的起始位置;对于缓冲区 nil 表示当前位置,对于字符串 nil 表示 0(向前)或字符串末尾(向后)。 - 返回成功匹配的数量。 @@ -895,7 +895,7 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的 **示例:** ```elisp -;; 将缓冲区中匹配的文本转为大写(从当前位置开始) +;; 仅将最后一次(第 2 次)匹配的文本转为大写 (with-temp-buffer (insert "hello world test") (tp-set 1 6 '(marker t)) @@ -903,17 +903,17 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的 (goto-char 1) (tp-forward-do #'upcase 'marker nil nil nil 2) (buffer-string)) -;; => "HELLO world TEST" +;; => "hello world TEST" ; 仅第 2 次匹配被转为大写 -;; 将字符串中匹配的文本转为大写(从位置 0 开始) +;; 仅将最后一次(第 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 nil 2) my-string) -;; => "HELLO world HELLO" +;; => "hello world HELLO" ; 仅第 2 次匹配被转为大写 -;; 从特定位置开始搜索 +;; 从特定位置开始搜索(仅找到 1 次匹配并转换) (let ((my-string (copy-sequence "hello world hello"))) (tp-set 0 5 '(marker t) my-string) (tp-set 12 17 '(marker t) my-string) @@ -921,17 +921,18 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的 my-string) ;; => "hello world HELLO" ; 只处理位置 6 之后的匹配 -;; 自定义转换 +;; 使用带有 start 和 end 参数的函数 (with-temp-buffer (insert "hello world test") (tp-set 1 6 '(marker t)) + (tp-set 13 17 '(marker t)) (goto-char 1) (tp-forward-do - (lambda (text) - (concat "[" text "]")) - 'marker nil nil nil 1) + (lambda (text start end) + (format "[%d-%d]%s" start end text)) + 'marker nil nil nil 2) (buffer-string)) -;; => "[hello] world test" +;; => "hello world [13-17]test" ; 仅最后一次匹配被转换 ``` --- diff --git a/tp-tests.el b/tp-tests.el index a7843bb..3dc24c8 100644 --- a/tp-tests.el +++ b/tp-tests.el @@ -643,22 +643,23 @@ (should (equal (cadr matches) '(0 5 t)))))) (ert-deftest tp-test-forward-do () - "Test tp-forward-do applies function to matched text." + "Test tp-forward-do applies function only to the last match." (tp-test-with-temp-buffer (insert "hello World test") (tp-set 1 6 '(marker t)) (tp-set 13 17 '(marker t)) (goto-char 1) (skip-unless (fboundp 'text-property-search-forward)) - ;; Test that function receives text and can transform it + ;; Test that function is applied only to the last (2nd) match (let ((count (tp-forward-do #'upcase 'marker nil nil nil 2))) (should (= count 2)) - ;; Check that text was upcased - (should (equal (buffer-substring 1 6) "HELLO")) + ;; First match should NOT be upcased + (should (equal (buffer-substring 1 6) "hello")) + ;; Only the last (2nd) match should be upcased (should (equal (buffer-substring 13 17) "TEST"))))) (ert-deftest tp-test--forward-do () - "Test tp--forward-do applies function to matches (internal API)." + "Test tp--forward-do applies function only to the last match (internal API)." (tp-test-with-temp-buffer (insert "Hello World Test") (tp-set 1 6 '(marker t)) @@ -670,25 +671,28 @@ (lambda (match obj) (push (prop-match-beginning match) result)) 'marker nil nil nil 2) - (should (= (length result) 2))))) + ;; Only the last match should be processed + (should (= (length result) 1)) + (should (= (car result) 13))))) (ert-deftest tp-test-backward-do () - "Test tp-backward-do applies function to matched text." + "Test tp-backward-do applies function only to the last match." (tp-test-with-temp-buffer (insert "hello World test") (tp-set 1 6 '(marker t)) (tp-set 13 17 '(marker t)) (goto-char 18) (skip-unless (fboundp 'text-property-search-backward)) - ;; Test that function receives text and can transform it + ;; Test that function is applied only to the last (2nd) match (let ((count (tp-backward-do #'upcase 'marker nil nil nil 2))) (should (= count 2)) - ;; Check that text was upcased + ;; Only the last (2nd) match should be upcased (should (equal (buffer-substring 1 6) "HELLO")) - (should (equal (buffer-substring 13 17) "TEST"))))) + ;; First match (searched backward) should NOT be upcased + (should (equal (buffer-substring 13 17) "test"))))) (ert-deftest tp-test--backward-do () - "Test tp--backward-do applies function to matches (internal API)." + "Test tp--backward-do applies function only to the last match (internal API)." (tp-test-with-temp-buffer (insert "Hello World Test") (tp-set 1 6 '(marker t)) @@ -700,29 +704,33 @@ (lambda (match obj) (push (prop-match-beginning match) result)) 'marker nil nil nil 2) - (should (= (length result) 2))))) + ;; Only the last match should be processed + (should (= (length result) 1)) + (should (= (car result) 1))))) (ert-deftest tp-test-forward-do-on-string () - "Test tp-forward-do works on string objects." + "Test tp-forward-do applies only to the last match on string objects." (let ((str (copy-sequence "hello World hello"))) (tp-set 0 5 '(marker t) str) (tp-set 12 17 '(marker t) str) (let ((count (tp-forward-do #'upcase 'marker nil str nil 2))) (should (= count 2)) - ;; Check that text was upcased - (should (equal (substring str 0 5) "HELLO")) + ;; First match should NOT be upcased + (should (equal (substring str 0 5) "hello")) + ;; Only the last (2nd) match should be upcased (should (equal (substring str 12 17) "HELLO"))))) (ert-deftest tp-test-backward-do-on-string () - "Test tp-backward-do works on string objects." + "Test tp-backward-do applies only to the last match on string objects." (let ((str (copy-sequence "hello World hello"))) (tp-set 0 5 '(marker t) str) (tp-set 12 17 '(marker t) str) (let ((count (tp-backward-do #'upcase 'marker nil str nil 2))) (should (= count 2)) - ;; Check that text was upcased + ;; Only the last (2nd) match should be upcased (should (equal (substring str 0 5) "HELLO")) - (should (equal (substring str 12 17) "HELLO"))))) + ;; First match (searched backward) should NOT be upcased + (should (equal (substring str 12 17) "hello"))))) (ert-deftest tp-test-forward-do-with-point () "Test tp-forward-do with point parameter." @@ -751,7 +759,7 @@ (should (equal (substring str 12 17) "hello"))))) (ert-deftest tp-test-forward-do-with-start-end () - "Test tp-forward-do passes optional start and end to function." + "Test tp-forward-do passes optional start and end to function for last match only." (let ((str (copy-sequence "hello World hello")) (starts nil) (ends nil)) @@ -764,15 +772,15 @@ (upcase txt)) 'marker nil str nil 2))) (should (= count 2)) - ;; Check positions were passed correctly - (should (equal (sort starts #'<) '(0 12))) - (should (equal (sort ends #'<) '(5 17))) - ;; Check text was upcased - (should (equal (substring str 0 5) "HELLO")) + ;; Check only the last match positions were passed + (should (equal starts '(12))) + (should (equal ends '(17))) + ;; Only the last match should be upcased + (should (equal (substring str 0 5) "hello")) (should (equal (substring str 12 17) "HELLO"))))) (ert-deftest tp-test-forward-do-with-start-only () - "Test tp-forward-do passes start when function accepts 2 args." + "Test tp-forward-do passes start to function for last match only when function accepts 2 args." (let ((str (copy-sequence "hello World hello")) (starts nil)) (tp-set 0 5 '(marker t) str) @@ -783,24 +791,25 @@ (upcase txt)) 'marker nil str nil 2))) (should (= count 2)) - ;; Check start positions were passed correctly - (should (equal (sort starts #'<) '(0 12))) - ;; Check text was upcased - (should (equal (substring str 0 5) "HELLO")) + ;; Check only the last match start position was passed + (should (equal starts '(12))) + ;; Only the last match should be upcased + (should (equal (substring str 0 5) "hello")) (should (equal (substring str 12 17) "HELLO"))))) (ert-deftest tp-test-forward-do-backward-compat () - "Test tp-forward-do works with single-argument functions (backward compat)." + "Test tp-forward-do applies only to last match with single-argument functions." (let ((str (copy-sequence "hello World hello"))) (tp-set 0 5 '(marker t) str) (tp-set 12 17 '(marker t) str) ;; Use #'upcase which only takes one argument (tp-forward-do #'upcase 'marker nil str nil 2) - (should (equal (substring str 0 5) "HELLO")) + ;; Only the last match should be upcased + (should (equal (substring str 0 5) "hello")) (should (equal (substring str 12 17) "HELLO")))) (ert-deftest tp-test-backward-do-with-start-end () - "Test tp-backward-do passes optional start and end to function." + "Test tp-backward-do passes optional start and end to function for last match only." (let ((str (copy-sequence "hello World hello")) (starts nil) (ends nil)) @@ -813,15 +822,15 @@ (upcase txt)) 'marker nil str nil 2))) (should (= count 2)) - ;; Check positions were passed correctly - (should (equal (sort starts #'<) '(0 12))) - (should (equal (sort ends #'<) '(5 17))) - ;; Check text was upcased + ;; Check only the last match positions were passed + (should (equal starts '(0))) + (should (equal ends '(5))) + ;; Only the last match should be upcased (should (equal (substring str 0 5) "HELLO")) - (should (equal (substring str 12 17) "HELLO"))))) + (should (equal (substring str 12 17) "hello"))))) (ert-deftest tp-test-backward-do-with-start-only () - "Test tp-backward-do passes start when function accepts 2 args." + "Test tp-backward-do passes start to function for last match only when function accepts 2 args." (let ((str (copy-sequence "hello World hello")) (starts nil)) (tp-set 0 5 '(marker t) str) @@ -832,21 +841,22 @@ (upcase txt)) 'marker nil str nil 2))) (should (= count 2)) - ;; Check start positions were passed correctly - (should (equal (sort starts #'<) '(0 12))) - ;; Check text was upcased + ;; Check only the last match start position was passed + (should (equal starts '(0))) + ;; Only the last match should be upcased (should (equal (substring str 0 5) "HELLO")) - (should (equal (substring str 12 17) "HELLO"))))) + (should (equal (substring str 12 17) "hello"))))) (ert-deftest tp-test-backward-do-backward-compat () - "Test tp-backward-do works with single-argument functions (backward compat)." + "Test tp-backward-do applies only to last match with single-argument functions." (let ((str (copy-sequence "hello World hello"))) (tp-set 0 5 '(marker t) str) (tp-set 12 17 '(marker t) str) ;; Use #'upcase which only takes one argument (tp-backward-do #'upcase 'marker nil str nil 2) + ;; Only the last match should be upcased (should (equal (substring str 0 5) "HELLO")) - (should (equal (substring str 12 17) "HELLO")))) + (should (equal (substring str 12 17) "hello")))) (ert-deftest tp-test-search-on-string () "Test tp-search finds all matching properties in a string." diff --git a/tp.el b/tp.el index eb8916d..b5380d3 100644 --- a/tp.el +++ b/tp.el @@ -1023,7 +1023,7 @@ Returns the number of successful matches." matches))))) (defun tp-forward-do (function property &optional value object point n) - "Search forward N times for text with PROPERTY and apply FUNCTION to each match. + "Search forward N times for text with PROPERTY and apply FUNCTION only to the last match. FUNCTION receives the matched text as its first argument. Optionally, FUNCTION can accept two additional arguments: START and END, representing @@ -1032,7 +1032,8 @@ it receives (TEXT START). If it accepts 3 or more arguments, it receives (TEXT START END). The return value of FUNCTION replaces the matched text in the string or buffer. -N is the number of searches, defaulting to 1. +N is the number of searches, defaulting to 1. The function searches N times +but only applies FUNCTION to the last (Nth) match found. VALUE is the optional value to match. OBJECT can be a buffer or string; nil defaults to current buffer. POINT is the starting position for search; for buffers nil means current point, @@ -1046,14 +1047,15 @@ If the replacement is shorter, only that portion will be replaced. If the replacement is longer, it will be truncated. Example: - ;; Upcase all matched text + ;; Upcase only the last (2nd) match (setq 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 nil 3) + (tp-forward-do #\\='upcase \\='marker nil my-string nil 2) + ;; => \"hello world HELLO\" - only the 2nd match is upcased ;; Use start and end positions (tp-forward-do (lambda (txt start end) (format \"[%d-%d]%s\" start end txt)) - \\='marker nil my-string nil 3)" + \\='marker nil my-string nil 2)" (let ((arity (func-arity function))) (tp--forward-do (lambda (match obj) @@ -1137,7 +1139,7 @@ Returns the number of successful matches." matches))))) (defun tp-backward-do (function property &optional value object point n) - "Search backward N times for text with PROPERTY and apply FUNCTION to each match. + "Search backward N times for text with PROPERTY and apply FUNCTION only to the last match. FUNCTION receives the matched text as its first argument. Optionally, FUNCTION can accept two additional arguments: START and END, representing @@ -1146,7 +1148,8 @@ it receives (TEXT START). If it accepts 3 or more arguments, it receives (TEXT START END). The return value of FUNCTION replaces the matched text in the string or buffer. -N is the number of searches, defaulting to 1. +N is the number of searches, defaulting to 1. The function searches N times +but only applies FUNCTION to the last (Nth) match found. VALUE is the optional value to match. OBJECT can be a buffer or string; nil defaults to current buffer. POINT is the starting position for search; for buffers nil means current point, @@ -1160,14 +1163,15 @@ If the replacement is shorter, only that portion will be replaced. If the replacement is longer, it will be truncated. Example: - ;; Upcase all matched text + ;; Upcase only the last (2nd) match (setq 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 nil 3) + (tp-backward-do #\\='upcase \\='marker nil my-string nil 2) + ;; => \"hello world HELLO\" - only the 2nd (last) match is upcased ;; Use start and end positions (tp-backward-do (lambda (txt start end) (format \"[%d-%d]%s\" start end txt)) - \\='marker nil my-string nil 3)" + \\='marker nil my-string nil 2)" (let ((arity (func-arity function))) (tp--backward-do (lambda (match obj)