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>
This commit is contained in:
copilot-swe-agent[bot] 2025-12-16 04:01:57 +00:00
parent ed8baf3970
commit 47b6505d51
4 changed files with 96 additions and 81 deletions

View File

@ -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
```
---

View File

@ -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" ; 仅最后一次匹配被转换
```
---

View File

@ -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."

24
tp.el
View File

@ -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)