Unify -do shortfall to all-or-nothing on strings and buffers
tp-forward-do/tp-backward-do target the TIMES-th match specifically; when fewer matches exist the string paths applied FUNCTION to the last available match -- the wrong target -- while buffer paths applied nothing. Both now apply nothing on shortfall and return the available count. Updates the two legacy with-range tests that codified the string behavior, adds four shortfall/exact-count regression tests, and documents the contract in both READMEs and the docstrings. Suite 443/443 green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
2b33495898
commit
ecdd952b55
16
CHANGELOG.md
16
CHANGELOG.md
@ -140,6 +140,13 @@ Search and navigation (tp-search):
|
|||||||
were previously skipped).
|
were previously skipped).
|
||||||
- The triplicated ~38-line replacement lambda was extracted into one
|
- The triplicated ~38-line replacement lambda was extracted into one
|
||||||
shared helper.
|
shared helper.
|
||||||
|
- `tp-forward-do` / `tp-backward-do` shortfall is now all-or-nothing on
|
||||||
|
both paths: TIMES targets the TIMES-th match specifically, so when
|
||||||
|
fewer matches exist nothing is applied and the available count is
|
||||||
|
returned. String paths previously acted on the last available match —
|
||||||
|
the wrong target; the two legacy tests codifying that
|
||||||
|
(`tp-test-forward-do-on-string-with-range` and its backward twin)
|
||||||
|
were updated.
|
||||||
|
|
||||||
Reactive rendering (tp-reactive / tp-render):
|
Reactive rendering (tp-reactive / tp-render):
|
||||||
|
|
||||||
@ -172,15 +179,6 @@ Test infrastructure:
|
|||||||
tests); the suite passes in randomized order.
|
tests); the suite passes in randomized order.
|
||||||
- `tp-tests.el` header and `provide` renamed to match its file name.
|
- `tp-tests.el` header and `provide` renamed to match its file name.
|
||||||
|
|
||||||
### Known divergences
|
|
||||||
|
|
||||||
- On shortfall (fewer than TIMES matches in the range),
|
|
||||||
`tp-forward-do` / `tp-backward-do` string paths still apply the
|
|
||||||
function to the last available match while buffer paths apply
|
|
||||||
nothing. Two legacy tests codify the string behavior
|
|
||||||
(`tp-test-forward-do-on-string-with-range` and its backward twin), so
|
|
||||||
it was left unchanged; unifying it is a pending semantics decision.
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- `tp-member`: like `tp-at`, but distinguishes "property present with
|
- `tp-member`: like `tp-at`, but distinguishes "property present with
|
||||||
|
|||||||
@ -1376,7 +1376,7 @@ Search forward/backward for text with PROPERTY and apply FUNCTION **only to the
|
|||||||
- **PROPERTY** is the text property to search for.
|
- **PROPERTY** is the text property to search for.
|
||||||
- **VALUE** is the optional value to match; nil means search for PROPERTY without matching value.
|
- **VALUE** is the optional value to match; nil means search for PROPERTY without matching value.
|
||||||
- **OBJECT** can be a buffer or string; nil defaults to current buffer.
|
- **OBJECT** can be a buffer or string; nil defaults to current buffer.
|
||||||
- **TIMES** is the number of searches, defaulting to 1. The function searches TIMES times but only applies FUNCTION to the last (Nth) match found.
|
- **TIMES** is the number of searches, defaulting to 1. The function searches TIMES times but only applies FUNCTION to the TIMES-th match. All-or-nothing: if fewer than TIMES matches exist, FUNCTION is not applied at all and the number of available matches is returned.
|
||||||
- **START** and **END** define the search range; defaults are object start and end.
|
- **START** and **END** define the search range; defaults are object start and end.
|
||||||
- Returns the number of successful matches.
|
- Returns the number of successful matches.
|
||||||
|
|
||||||
|
|||||||
@ -1363,7 +1363,7 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的
|
|||||||
- **PROPERTY** 是要搜索的文本属性。
|
- **PROPERTY** 是要搜索的文本属性。
|
||||||
- **VALUE** 为 nil 时,表示搜索 PROPERTY 属性,不用匹配值。
|
- **VALUE** 为 nil 时,表示搜索 PROPERTY 属性,不用匹配值。
|
||||||
- **OBJECT** 默认是当前 buffer 或指定的字符串或指定的 buffer。
|
- **OBJECT** 默认是当前 buffer 或指定的字符串或指定的 buffer。
|
||||||
- **TIMES** 表示向前/向后搜索几次,默认搜索一次。该函数会搜索 TIMES 次,但仅对找到的最后(第 N 次)匹配应用 FUNCTION。
|
- **TIMES** 表示向前/向后搜索几次,默认搜索一次。该函数会搜索 TIMES 次,但仅对第 TIMES 个匹配应用 FUNCTION。要么全有要么全无:当匹配数量不足 TIMES 时,完全不应用 FUNCTION,仅返回实际找到的匹配数量。
|
||||||
- **START** 和 **END** 默认为 OBJECT 的起始和结束位置。
|
- **START** 和 **END** 默认为 OBJECT 的起始和结束位置。
|
||||||
- 返回成功匹配的数量。
|
- 返回成功匹配的数量。
|
||||||
|
|
||||||
|
|||||||
@ -170,6 +170,40 @@ Old code silently wrote 10 chars, yielding \"hellohellod\"."
|
|||||||
(should (= (tp-search-map #'upcase 'marker nil str) 2))
|
(should (= (tp-search-map #'upcase 'marker nil str) 2))
|
||||||
(should (equal (substring-no-properties str) "HELLO world HELLO"))))
|
(should (equal (substring-no-properties str) "HELLO world HELLO"))))
|
||||||
|
|
||||||
|
;;; B43: -do shortfall is all-or-nothing on strings and buffers alike
|
||||||
|
|
||||||
|
(ert-deftest tp-search-test-forward-do-shortfall-string ()
|
||||||
|
"Requesting the Nth match when fewer exist applies nothing (string).
|
||||||
|
The count of available matches is still returned."
|
||||||
|
(let ((str (copy-sequence "hello world")))
|
||||||
|
(tp-set 0 5 '(marker t) str)
|
||||||
|
(should (= (tp-forward-do #'upcase 'marker nil str 3) 1))
|
||||||
|
(should (equal (substring-no-properties str) "hello world"))))
|
||||||
|
|
||||||
|
(ert-deftest tp-search-test-forward-do-shortfall-buffer ()
|
||||||
|
"Requesting the Nth match when fewer exist applies nothing (buffer)."
|
||||||
|
(with-temp-buffer
|
||||||
|
(insert "hello world")
|
||||||
|
(put-text-property 1 6 'marker t)
|
||||||
|
(should (= (tp-forward-do #'upcase 'marker t nil 3) 1))
|
||||||
|
(should (equal (buffer-substring-no-properties (point-min) (point-max))
|
||||||
|
"hello world"))))
|
||||||
|
|
||||||
|
(ert-deftest tp-search-test-backward-do-shortfall-string ()
|
||||||
|
"tp-backward-do shortfall applies nothing on strings."
|
||||||
|
(let ((str (copy-sequence "hello world")))
|
||||||
|
(tp-set 6 11 '(marker t) str)
|
||||||
|
(should (= (tp-backward-do #'upcase 'marker nil str 2) 1))
|
||||||
|
(should (equal (substring-no-properties str) "hello world"))))
|
||||||
|
|
||||||
|
(ert-deftest tp-search-test-forward-do-exact-count-applies ()
|
||||||
|
"With exactly TIMES matches, FUNCTION is applied to the TIMES-th."
|
||||||
|
(let ((str (copy-sequence "aaa bbb aaa")))
|
||||||
|
(tp-set 0 3 '(marker t) str)
|
||||||
|
(tp-set 8 11 '(marker t) str)
|
||||||
|
(should (= (tp-forward-do #'upcase 'marker nil str 2) 2))
|
||||||
|
(should (equal (substring-no-properties str) "aaa bbb AAA"))))
|
||||||
|
|
||||||
(ert-deftest tp-search-test-forward-do-buffer-longer-replacement-grows ()
|
(ert-deftest tp-search-test-forward-do-buffer-longer-replacement-grows ()
|
||||||
"Buffers may grow on longer replacements (delete-region + insert).
|
"Buffers may grow on longer replacements (delete-region + insert).
|
||||||
Uses an explicit VALUE: the buffer paths of the -do functions match
|
Uses an explicit VALUE: the buffer paths of the -do functions match
|
||||||
|
|||||||
16
tp-search.el
16
tp-search.el
@ -377,7 +377,9 @@ VALUE is the optional value to match.
|
|||||||
OBJECT can be a buffer or string; nil defaults to current buffer.
|
OBJECT can be a buffer or string; nil defaults to current buffer.
|
||||||
START and END define the search range; defaults are object start and end.
|
START and END define the search range; defaults are object start and end.
|
||||||
|
|
||||||
Returns the number of successful matches."
|
FUNCTION is called only when the TIMES-th match exists; if fewer
|
||||||
|
matches are available, nothing is applied.
|
||||||
|
Returns the number of matches found (at most TIMES)."
|
||||||
(let ((count (or times 1)))
|
(let ((count (or times 1)))
|
||||||
(cond
|
(cond
|
||||||
;; String object
|
;; String object
|
||||||
@ -390,7 +392,10 @@ Returns the number of successful matches."
|
|||||||
(<= (cadr m) end-pos)))
|
(<= (cadr m) end-pos)))
|
||||||
all-matches))
|
all-matches))
|
||||||
(matches (seq-take filtered-matches count)))
|
(matches (seq-take filtered-matches count)))
|
||||||
(when matches
|
;; All-or-nothing, mirroring the buffer path: FUNCTION targets
|
||||||
|
;; the TIMES-th match specifically, so when fewer matches exist
|
||||||
|
;; acting on a different one would hit the wrong target.
|
||||||
|
(when (= (length matches) count)
|
||||||
(funcall function (car (last matches)) object))
|
(funcall function (car (last matches)) object))
|
||||||
(length matches)))
|
(length matches)))
|
||||||
;; Buffer or nil
|
;; Buffer or nil
|
||||||
@ -530,7 +535,9 @@ VALUE is the optional value to match.
|
|||||||
OBJECT can be a buffer or string; nil defaults to current buffer.
|
OBJECT can be a buffer or string; nil defaults to current buffer.
|
||||||
START and END define the search range; defaults are object start and end.
|
START and END define the search range; defaults are object start and end.
|
||||||
|
|
||||||
Returns the number of successful matches."
|
FUNCTION is called only when the TIMES-th match exists; if fewer
|
||||||
|
matches are available, nothing is applied.
|
||||||
|
Returns the number of matches found (at most TIMES)."
|
||||||
(let ((count (or times 1)))
|
(let ((count (or times 1)))
|
||||||
(cond
|
(cond
|
||||||
;; String object - reverse the matches
|
;; String object - reverse the matches
|
||||||
@ -544,7 +551,8 @@ Returns the number of successful matches."
|
|||||||
(<= (cadr m) end-pos)))
|
(<= (cadr m) end-pos)))
|
||||||
all-matches))
|
all-matches))
|
||||||
(matches (seq-take (nreverse filtered-matches) count)))
|
(matches (seq-take (nreverse filtered-matches) count)))
|
||||||
(when matches
|
;; All-or-nothing; see tp--forward-do.
|
||||||
|
(when (= (length matches) count)
|
||||||
(funcall function (car (last matches)) object))
|
(funcall function (car (last matches)) object))
|
||||||
(length matches)))
|
(length matches)))
|
||||||
;; Buffer or nil
|
;; Buffer or nil
|
||||||
|
|||||||
18
tp-tests.el
18
tp-tests.el
@ -718,7 +718,10 @@ leak between tests regardless of how BODY exits."
|
|||||||
(should (equal (substring str 12 17) "HELLO")))))
|
(should (equal (substring str 12 17) "HELLO")))))
|
||||||
|
|
||||||
(ert-deftest tp-test-forward-do-on-string-with-range ()
|
(ert-deftest tp-test-forward-do-on-string-with-range ()
|
||||||
"Test tp-forward-do on string with start/end range."
|
"Test tp-forward-do on string with start/end range.
|
||||||
|
TIMES targets the TIMES-th match specifically; with only one match in
|
||||||
|
range, asking for the 2nd applies nothing (all-or-nothing, matching
|
||||||
|
the buffer path) and returns the available count."
|
||||||
(let ((str (copy-sequence "hello World hello")))
|
(let ((str (copy-sequence "hello World hello")))
|
||||||
(tp-set 0 5 '(marker t) str)
|
(tp-set 0 5 '(marker t) str)
|
||||||
(tp-set 12 17 '(marker t) str)
|
(tp-set 12 17 '(marker t) str)
|
||||||
@ -727,8 +730,8 @@ leak between tests regardless of how BODY exits."
|
|||||||
(should (= count 1)) ; Only one match in range 6-17
|
(should (= count 1)) ; Only one match in range 6-17
|
||||||
;; First match should NOT be upcased
|
;; First match should NOT be upcased
|
||||||
(should (equal (substring str 0 5) "hello"))
|
(should (equal (substring str 0 5) "hello"))
|
||||||
;; Second match should be upcased
|
;; The requested 2nd match does not exist: nothing is applied
|
||||||
(should (equal (substring str 12 17) "HELLO")))))
|
(should (equal (substring str 12 17) "hello")))))
|
||||||
|
|
||||||
(ert-deftest tp-test-forward-do-function-receives-start-end ()
|
(ert-deftest tp-test-forward-do-function-receives-start-end ()
|
||||||
"Test tp-forward-do passes start and end to function."
|
"Test tp-forward-do passes start and end to function."
|
||||||
@ -776,16 +779,17 @@ leak between tests regardless of how BODY exits."
|
|||||||
(should (equal (substring str 12 17) "hello")))))
|
(should (equal (substring str 12 17) "hello")))))
|
||||||
|
|
||||||
(ert-deftest tp-test-backward-do-on-string-with-range ()
|
(ert-deftest tp-test-backward-do-on-string-with-range ()
|
||||||
"Test tp-backward-do on string with start/end range."
|
"Test tp-backward-do on string with start/end range.
|
||||||
|
All-or-nothing: with one match in range, requesting the 2nd applies
|
||||||
|
nothing and returns the available count."
|
||||||
(let ((str (copy-sequence "hello World hello")))
|
(let ((str (copy-sequence "hello World hello")))
|
||||||
(tp-set 0 5 '(marker t) str)
|
(tp-set 0 5 '(marker t) str)
|
||||||
(tp-set 12 17 '(marker t) str)
|
(tp-set 12 17 '(marker t) str)
|
||||||
;; Search only in range 0-10 (before second match)
|
;; Search only in range 0-10 (before second match)
|
||||||
(let ((count (tp-backward-do #'upcase 'marker nil str 2 0 10)))
|
(let ((count (tp-backward-do #'upcase 'marker nil str 2 0 10)))
|
||||||
(should (= count 1)) ; Only one match in range 0-10
|
(should (= count 1)) ; Only one match in range 0-10
|
||||||
;; First match should be upcased
|
;; The requested 2nd match does not exist: nothing is applied
|
||||||
(should (equal (substring str 0 5) "HELLO"))
|
(should (equal (substring str 0 5) "hello"))
|
||||||
;; Second match should NOT be upcased
|
|
||||||
(should (equal (substring str 12 17) "hello")))))
|
(should (equal (substring str 12 17) "hello")))))
|
||||||
|
|
||||||
(ert-deftest tp-test-backward-do-function-receives-start-end ()
|
(ert-deftest tp-test-backward-do-function-receives-start-end ()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user