diff --git a/README.md b/README.md index e0d3371..974c1d3 100644 --- a/README.md +++ b/README.md @@ -88,12 +88,12 @@ A complete overview of all tp.el functions organized by category: |----------|-------------| | [`tp-search-forward`](#tp-search-forward--tp-search-backward) | Raw wrapper for text-property-search-forward | | [`tp-search-backward`](#tp-search-forward--tp-search-backward) | Raw wrapper for text-property-search-backward | -| [`tp-forward`](#tp-forward--tp-backward) | Search forward N times for text with property | -| [`tp-backward`](#tp-forward--tp-backward) | Search backward N times for text with property | -| [`tp-forward-do`](#tp-forward-do--tp-backward-do) | Apply function to N forward matches | -| [`tp-backward-do`](#tp-forward-do--tp-backward-do) | Apply function to N backward matches | +| [`tp-forward`](#tp-forward--tp-backward) | Search forward N times for text with property (buffers and strings) | +| [`tp-backward`](#tp-forward--tp-backward) | Search backward N times for text with property (buffers and strings) | +| [`tp-forward-do`](#tp-forward-do--tp-backward-do) | Apply function to matched text for N forward matches | +| [`tp-backward-do`](#tp-forward-do--tp-backward-do) | Apply function to matched text for N backward matches | | [`tp-search`](#tp-search---search-all-matches) | Search all matching properties in range or string | -| [`tp-search-do`](#tp-search-do---apply-function-to-all-matches) | Apply function to all matching properties | +| [`tp-search-map`](#tp-search-map---apply-function-to-matched-text) | Apply function to matched text for all matches | #### Query Functions | Function | Description | @@ -573,8 +573,9 @@ Search forward/backward N times for text with PROPERTY. - **N** is the number of searches, defaulting to 1. - **VALUE** is the optional value to match. -- **OBJECT** can be a buffer; nil defaults to current buffer. -- Returns the prop-match object from the last successful search. +- **OBJECT** can be a buffer or string; nil defaults to current buffer. +- For buffers, returns the prop-match object from the last successful search. +- For strings, returns a list of (START END VALUE) for all matches found. **Examples:** @@ -587,6 +588,10 @@ Search forward/backward N times for text with PROPERTY. ;; Search forward 3 times (tp-forward 'marker nil nil 3) + +;; Search in a string +(tp-forward 'marker nil my-string 2) +;; => ((0 5 t) (12 17 t)) ``` --- @@ -598,19 +603,27 @@ Search forward/backward N times for text with PROPERTY. (tp-backward-do FUNCTION PROPERTY &optional VALUE OBJECT N) ``` -Search forward/backward N times for text with PROPERTY and apply FUNCTION to each match. +Search forward/backward N times for text with PROPERTY and apply FUNCTION to matched text. -- **FUNCTION** receives two arguments: the prop-match object and OBJECT. +- **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. +- **OBJECT** can be a buffer or string; nil defaults to current buffer. - Returns the number of successful matches. **Examples:** ```elisp -;; Apply function to next 3 markers +;; Upcase matched text in buffer +(tp-forward-do #'upcase 'marker nil nil 3) + +;; Upcase matched text in string +(tp-forward-do #'upcase 'marker nil my-string 2) + +;; Custom transformation (tp-forward-do - (lambda (match obj) - (message "Found at %d" (prop-match-beginning match))) + (lambda (text) + (concat "[" text "]")) 'marker nil nil 3) ``` @@ -647,36 +660,36 @@ Returns a list of (START END VALUE) for all matching regions. --- -#### `tp-search-do` - Apply Function to All Matches +#### `tp-search-map` - Apply Function to Matched Text ```elisp ;; Buffer/string region -(tp-search-do FUNCTION START END PROPERTY &optional VALUE OBJECT) +(tp-search-map FUNCTION START END PROPERTY &optional VALUE OBJECT) ;; Entire string -(tp-search-do FUNCTION STRING PROPERTY &optional VALUE) +(tp-search-map FUNCTION STRING PROPERTY &optional VALUE) ``` -Execute FUNCTION on all matches of PROPERTY in a buffer/string range or entire string. +Apply FUNCTION to matched text for all matches of PROPERTY. -- **FUNCTION** receives two arguments: the match (list of START END VALUE) and OBJECT. +- **FUNCTION** receives the matched text as its only argument. The return value + of FUNCTION replaces the matched text in the string or buffer. - Returns the number of matches processed. **Examples:** ```elisp -;; Process all markers in buffer range -(tp-search-do - (lambda (match obj) - (message "Found at %d-%d with value %s" - (car match) (cadr match) (caddr match))) - 1 100 'marker) +;; Upcase all markers in string +(tp-search-map #'upcase my-string 'marker) -;; Process all headings in string -(tp-search-do - (lambda (match obj) - (upcase (substring obj (car match) (cadr match)))) - my-string 'type 'heading) +;; Upcase all markers in buffer range +(tp-search-map #'upcase 1 100 'marker) + +;; Custom transformation +(tp-search-map + (lambda (text) + (concat "[" text "]")) + my-string 'marker) ``` --- diff --git a/README_CN.md b/README_CN.md index 3ff4492..8de62d2 100644 --- a/README_CN.md +++ b/README_CN.md @@ -87,12 +87,12 @@ tp.el 所有函数按类别组织的完整概览: |------|------| | [`tp-search-forward`](#tp-search-forward--tp-search-backward) | text-property-search-forward 的原始包装 | | [`tp-search-backward`](#tp-search-forward--tp-search-backward) | text-property-search-backward 的原始包装 | -| [`tp-forward`](#tp-forward--tp-backward) | 向前搜索 N 次具有属性的文本 | -| [`tp-backward`](#tp-forward--tp-backward) | 向后搜索 N 次具有属性的文本 | -| [`tp-forward-do`](#tp-forward-do--tp-backward-do) | 对 N 个向前匹配应用函数 | -| [`tp-backward-do`](#tp-forward-do--tp-backward-do) | 对 N 个向后匹配应用函数 | +| [`tp-forward`](#tp-forward--tp-backward) | 向前搜索 N 次具有属性的文本(支持缓冲区和字符串) | +| [`tp-backward`](#tp-forward--tp-backward) | 向后搜索 N 次具有属性的文本(支持缓冲区和字符串) | +| [`tp-forward-do`](#tp-forward-do--tp-backward-do) | 对 N 个向前匹配的文本应用函数 | +| [`tp-backward-do`](#tp-forward-do--tp-backward-do) | 对 N 个向后匹配的文本应用函数 | | [`tp-search`](#tp-search---搜索所有匹配) | 在范围或字符串中搜索所有匹配的属性 | -| [`tp-search-do`](#tp-search-do---对所有匹配应用函数) | 对所有匹配的属性应用函数 | +| [`tp-search-map`](#tp-search-map---对匹配文本应用函数) | 对所有匹配的文本应用函数 | #### 查询函数 | 函数 | 描述 | @@ -572,8 +572,9 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的 - **N** 是搜索次数,默认为 1。 - **VALUE** 是可选的匹配值。 -- **OBJECT** 可以是缓冲区;nil 默认为当前缓冲区。 -- 返回最后一次成功搜索的 prop-match 对象。 +- **OBJECT** 可以是缓冲区或字符串;nil 默认为当前缓冲区。 +- 对于缓冲区,返回最后一次成功搜索的 prop-match 对象。 +- 对于字符串,返回所有匹配的 (START END VALUE) 列表。 **示例:** @@ -586,6 +587,10 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的 ;; 向前搜索 3 次 (tp-forward 'marker nil nil 3) + +;; 在字符串中搜索 +(tp-forward 'marker nil my-string 2) +;; => ((0 5 t) (12 17 t)) ``` --- @@ -597,19 +602,26 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的 (tp-backward-do FUNCTION PROPERTY &optional VALUE OBJECT N) ``` -向前/向后搜索 N 次具有 PROPERTY 的文本,并对每个匹配应用 FUNCTION。 +向前/向后搜索 N 次具有 PROPERTY 的文本,并对匹配的文本应用 FUNCTION。 -- **FUNCTION** 接收两个参数:prop-match 对象和 OBJECT。 +- **FUNCTION** 接收匹配到的文本作为唯一参数。FUNCTION 的返回值将替换字符串或缓冲区中的匹配文本。 - **N** 是搜索次数,默认为 1。 +- **OBJECT** 可以是缓冲区或字符串;nil 默认为当前缓冲区。 - 返回成功匹配的数量。 **示例:** ```elisp -;; 对下 3 个 marker 应用函数 +;; 将缓冲区中匹配的文本转为大写 +(tp-forward-do #'upcase 'marker nil nil 3) + +;; 将字符串中匹配的文本转为大写 +(tp-forward-do #'upcase 'marker nil my-string 2) + +;; 自定义转换 (tp-forward-do - (lambda (match obj) - (message "在 %d 处找到" (prop-match-beginning match))) + (lambda (text) + (concat "[" text "]")) 'marker nil nil 3) ``` @@ -646,36 +658,35 @@ Emacs 的 `text-property-search-forward` 和 `text-property-search-backward` 的 --- -#### `tp-search-do` - 对所有匹配应用函数 +#### `tp-search-map` - 对匹配文本应用函数 ```elisp ;; 缓冲区/字符串区域 -(tp-search-do FUNCTION START END PROPERTY &optional VALUE OBJECT) +(tp-search-map FUNCTION START END PROPERTY &optional VALUE OBJECT) ;; 整个字符串 -(tp-search-do FUNCTION STRING PROPERTY &optional VALUE) +(tp-search-map FUNCTION STRING PROPERTY &optional VALUE) ``` -在缓冲区/字符串范围或整个字符串中对所有 PROPERTY 匹配执行 FUNCTION。 +对所有 PROPERTY 匹配的文本应用 FUNCTION。 -- **FUNCTION** 接收两个参数:匹配(START END VALUE 列表)和 OBJECT。 +- **FUNCTION** 接收匹配到的文本作为唯一参数。FUNCTION 的返回值将替换字符串或缓冲区中的匹配文本。 - 返回处理的匹配数量。 **示例:** ```elisp -;; 处理缓冲区范围内的所有 marker -(tp-search-do - (lambda (match obj) - (message "在 %d-%d 处找到,值为 %s" - (car match) (cadr match) (caddr match))) - 1 100 'marker) +;; 将字符串中所有 marker 文本转为大写 +(tp-search-map #'upcase my-string 'marker) -;; 处理字符串中的所有标题 -(tp-search-do - (lambda (match obj) - (upcase (substring obj (car match) (cadr match)))) - my-string 'type 'heading) +;; 将缓冲区范围内所有 marker 文本转为大写 +(tp-search-map #'upcase 1 100 'marker) + +;; 自定义转换 +(tp-search-map + (lambda (text) + (concat "[" text "]")) + my-string 'marker) ``` --- diff --git a/tp-tests.el b/tp-tests.el index f11755d..f6db6fc 100644 --- a/tp-tests.el +++ b/tp-tests.el @@ -557,6 +557,16 @@ (should match) (should (= (prop-match-beginning match) 7))))) +(ert-deftest tp-test-forward-on-string () + "Test tp-forward works 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 ((matches (tp-forward 'marker nil str 2))) + (should (= (length matches) 2)) + (should (equal (car matches) '(0 5 t))) + (should (equal (cadr matches) '(12 17 t)))))) + (ert-deftest tp-test-forward-with-n () "Test tp-forward with N parameter." (tp-test-with-temp-buffer @@ -583,8 +593,34 @@ (should match) (should (= (prop-match-beginning match) 1))))) +(ert-deftest tp-test-backward-on-string () + "Test tp-backward works 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 ((matches (tp-backward 'marker nil str 2))) + (should (= (length matches) 2)) + ;; Backward returns matches in reverse order + (should (equal (car matches) '(12 17 t))) + (should (equal (cadr matches) '(0 5 t)))))) + (ert-deftest tp-test-forward-do () - "Test tp-forward-do applies function to matches." + "Test tp-forward-do applies function to matched text." + (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 + (let ((count (tp-forward-do #'upcase 'marker nil nil 2))) + (should (= count 2)) + ;; Check that text was upcased + (should (equal (buffer-substring 1 6) "HELLO")) + (should (equal (buffer-substring 13 17) "TEST"))))) + +(ert-deftest tp-test--forward-do () + "Test tp--forward-do applies function to matches (internal API)." (tp-test-with-temp-buffer (insert "Hello World Test") (tp-set 1 6 '(marker t)) @@ -592,14 +628,29 @@ (goto-char 1) (skip-unless (fboundp 'text-property-search-forward)) (let ((result nil)) - (tp-forward-do + (tp--forward-do (lambda (match obj) (push (prop-match-beginning match) result)) 'marker nil nil 2) (should (= (length result) 2))))) (ert-deftest tp-test-backward-do () - "Test tp-backward-do applies function to matches." + "Test tp-backward-do applies function to matched text." + (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 + (let ((count (tp-backward-do #'upcase 'marker nil nil 2))) + (should (= count 2)) + ;; Check that text was upcased + (should (equal (buffer-substring 1 6) "HELLO")) + (should (equal (buffer-substring 13 17) "TEST"))))) + +(ert-deftest tp-test--backward-do () + "Test tp--backward-do applies function to matches (internal API)." (tp-test-with-temp-buffer (insert "Hello World Test") (tp-set 1 6 '(marker t)) @@ -607,12 +658,34 @@ (goto-char 18) (skip-unless (fboundp 'text-property-search-backward)) (let ((result nil)) - (tp-backward-do + (tp--backward-do (lambda (match obj) (push (prop-match-beginning match) result)) 'marker nil nil 2) (should (= (length result) 2))))) +(ert-deftest tp-test-forward-do-on-string () + "Test tp-forward-do works 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 2))) + (should (= count 2)) + ;; Check that text was upcased + (should (equal (substring str 0 5) "HELLO")) + (should (equal (substring str 12 17) "HELLO"))))) + +(ert-deftest tp-test-backward-do-on-string () + "Test tp-backward-do works 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 2))) + (should (= count 2)) + ;; Check that text was upcased + (should (equal (substring str 0 5) "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." (let ((str (copy-sequence "Hello World Hello"))) @@ -645,13 +718,13 @@ (should (equal (car matches) '(1 6 t))) (should (equal (cadr matches) '(13 18 t)))))) -(ert-deftest tp-test-search-do-on-string () - "Test tp-search-do applies function to all matches in a string." +(ert-deftest tp-test--search-do-on-string () + "Test tp--search-do applies function to all matches in a string (internal API)." (let ((str (copy-sequence "Hello World Hello"))) (tp-set 0 5 '(marker t) str) (tp-set 12 17 '(marker t) str) (let ((result nil)) - (tp-search-do + (tp--search-do (lambda (match obj) (push (car match) result)) str 'marker) @@ -659,14 +732,14 @@ (should (member 0 result)) (should (member 12 result))))) -(ert-deftest tp-test-search-do-in-range () - "Test tp-search-do applies function to all matches in a buffer range." +(ert-deftest tp-test--search-do-in-range () + "Test tp--search-do applies function to all matches in a buffer range (internal API)." (tp-test-with-temp-buffer (insert "Hello World Hello") (tp-set 1 6 '(marker t)) (tp-set 13 18 '(marker t)) (let ((result nil)) - (tp-search-do + (tp--search-do (lambda (match obj) (push (car match) result)) 1 18 'marker) @@ -674,6 +747,29 @@ (should (member 1 result)) (should (member 13 result))))) +(ert-deftest tp-test-search-map-on-string () + "Test tp-search-map applies function to matched text in a string." + (let ((str (copy-sequence "hello World hello"))) + (tp-set 0 5 '(marker t) str) + (tp-set 12 17 '(marker t) str) + (let ((count (tp-search-map #'upcase str 'marker))) + (should (= count 2)) + ;; Check that text was upcased + (should (equal (substring str 0 5) "HELLO")) + (should (equal (substring str 12 17) "HELLO"))))) + +(ert-deftest tp-test-search-map-in-range () + "Test tp-search-map applies function to matched text in a buffer range." + (tp-test-with-temp-buffer + (insert "hello World hello") + (tp-set 1 6 '(marker t)) + (tp-set 13 18 '(marker t)) + (let ((count (tp-search-map #'upcase 1 18 'marker))) + (should (= count 2)) + ;; Check that text was upcased + (should (equal (buffer-substring 1 6) "HELLO")) + (should (equal (buffer-substring 13 18) "HELLO"))))) + ;;; ============================================================ ;;; Utility Function Tests ;;; ============================================================ diff --git a/tp.el b/tp.el index 430b5b6..2cc4eb2 100644 --- a/tp.el +++ b/tp.el @@ -1012,76 +1012,181 @@ VALUE, PREDICATE, and NOT-CURRENT work as in `text-property-search-backward'." N is the number of searches, defaulting to 1. VALUE is the optional value to match. -OBJECT can be a buffer; nil defaults to current buffer. +OBJECT can be a buffer or string; nil defaults to current buffer. -Returns the prop-match object from the last successful search, -or nil if not found. +For buffers, returns the prop-match object from the last successful search. +For strings, returns a list of (START END VALUE) for all matches found. -Uses `tp-search-forward' internally." - (let ((count (or n 1)) - (result nil) - (buf (or object (current-buffer)))) - (with-current-buffer buf - (dotimes (_ count) - (setq result (tp-search-forward property value)))) - result)) +Uses `tp-search-forward' for buffers and `tp-search' for strings." + (let ((count (or n 1))) + (cond + ;; String object - use tp-search + ((stringp object) + (let ((matches (tp-search object property value))) + (seq-take matches count))) + ;; Buffer or nil + (t + (let ((result nil) + (buf (or object (current-buffer)))) + (with-current-buffer buf + (dotimes (_ count) + (setq result (tp-search-forward property value)))) + result))))) (defun tp-backward (property &optional value object n) "Search backward N times for text with PROPERTY. N is the number of searches, defaulting to 1. VALUE is the optional value to match. -OBJECT can be a buffer; nil defaults to current buffer. +OBJECT can be a buffer or string; nil defaults to current buffer. -Returns the prop-match object from the last successful search, -or nil if not found. +For buffers, returns the prop-match object from the last successful search. +For strings, returns a list of (START END VALUE) for the last N matches +in reverse order (from end to start). -Uses `tp-search-backward' internally." - (let ((count (or n 1)) - (result nil) - (buf (or object (current-buffer)))) - (with-current-buffer buf - (dotimes (_ count) - (setq result (tp-search-backward property value)))) - result)) +Uses `tp-search-backward' for buffers and `tp-search' for strings." + (let ((count (or n 1))) + (cond + ;; String object - use tp-search and reverse + ((stringp object) + (let ((matches (nreverse (tp-search object property value)))) + (seq-take matches count))) + ;; Buffer or nil + (t + (let ((result nil) + (buf (or object (current-buffer)))) + (with-current-buffer buf + (dotimes (_ count) + (setq result (tp-search-backward property value)))) + result))))) + +(defun tp--forward-do (function property &optional value object n) + "Internal: Search forward N times for PROPERTY and apply FUNCTION to each match. + +FUNCTION receives two arguments: the prop-match object (or list for strings) +and OBJECT. +N is the number of searches, defaulting to 1. +VALUE is the optional value to match. +OBJECT can be a buffer or string; nil defaults to current buffer. + +Returns the number of successful matches." + (let ((count (or n 1))) + (cond + ;; String object + ((stringp object) + (let ((matches (seq-take (tp-search object property value) count))) + (dolist (match matches) + (funcall function match object)) + (length matches))) + ;; Buffer or nil + (t + (let ((matches 0) + (buf (or object (current-buffer)))) + (with-current-buffer buf + (dotimes (_ count) + (when-let ((match (tp-search-forward property value))) + (funcall function match buf) + (cl-incf matches)))) + matches))))) (defun tp-forward-do (function property &optional value object n) "Search forward N times for text with PROPERTY and apply FUNCTION to each match. -FUNCTION receives two arguments: the prop-match object and OBJECT. +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. VALUE is the optional value to match. -OBJECT can be a buffer; nil defaults to current buffer. +OBJECT can be a buffer or string; nil defaults to current buffer. + +Returns the number of successful matches. + +Example: + ;; Upcase all matched text + (tp-forward-do #\\='upcase \\='marker nil my-string 3)" + (tp--forward-do + (lambda (match obj) + (let* ((start (if (listp match) (car match) (prop-match-beginning match))) + (end (if (listp match) (cadr match) (prop-match-end match))) + (text (if (stringp obj) + (substring obj start end) + (buffer-substring start end))) + (new-text (funcall function text))) + (when (and new-text (not (equal new-text text))) + (if (stringp obj) + ;; For strings, we need to replace in-place + (progn + (store-substring obj start new-text)) + ;; For buffers, delete and insert + (save-excursion + (delete-region start end) + (goto-char start) + (insert new-text)))))) + property value object n)) + +(defun tp--backward-do (function property &optional value object n) + "Internal: Search backward N times for PROPERTY and apply FUNCTION to each match. + +FUNCTION receives two arguments: the prop-match object (or list for strings) +and OBJECT. +N is the number of searches, defaulting to 1. +VALUE is the optional value to match. +OBJECT can be a buffer or string; nil defaults to current buffer. Returns the number of successful matches." - (let ((count (or n 1)) - (matches 0) - (buf (or object (current-buffer)))) - (with-current-buffer buf - (dotimes (_ count) - (when-let ((match (tp-search-forward property value))) - (funcall function match buf) - (cl-incf matches)))) - matches)) + (let ((count (or n 1))) + (cond + ;; String object - reverse the matches + ((stringp object) + (let ((matches (seq-take (nreverse (tp-search object property value)) count))) + (dolist (match matches) + (funcall function match object)) + (length matches))) + ;; Buffer or nil + (t + (let ((matches 0) + (buf (or object (current-buffer)))) + (with-current-buffer buf + (dotimes (_ count) + (when-let ((match (tp-search-backward property value))) + (funcall function match buf) + (cl-incf matches)))) + matches))))) (defun tp-backward-do (function property &optional value object n) "Search backward N times for text with PROPERTY and apply FUNCTION to each match. -FUNCTION receives two arguments: the prop-match object and OBJECT. +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. VALUE is the optional value to match. -OBJECT can be a buffer; nil defaults to current buffer. +OBJECT can be a buffer or string; nil defaults to current buffer. -Returns the number of successful matches." - (let ((count (or n 1)) - (matches 0) - (buf (or object (current-buffer)))) - (with-current-buffer buf - (dotimes (_ count) - (when-let ((match (tp-search-backward property value))) - (funcall function match buf) - (cl-incf matches)))) - matches)) +Returns the number of successful matches. + +Example: + ;; Upcase all matched text + (tp-backward-do #\\='upcase \\='marker nil my-string 3)" + (tp--backward-do + (lambda (match obj) + (let* ((start (if (listp match) (car match) (prop-match-beginning match))) + (end (if (listp match) (cadr match) (prop-match-end match))) + (text (if (stringp obj) + (substring obj start end) + (buffer-substring start end))) + (new-text (funcall function text))) + (when (and new-text (not (equal new-text text))) + (if (stringp obj) + ;; For strings, we need to replace in-place + (progn + (store-substring obj start new-text)) + ;; For buffers, delete and insert + (save-excursion + (delete-region start end) + (goto-char start) + (insert new-text)))))) + property value object n)) (defun tp-search (start-or-string &optional end-or-property property-or-value value object) "Search for all text with PROPERTY in a buffer/string range or entire string. @@ -1157,16 +1262,16 @@ Each element contains the start position, end position, and property value." (nreverse results))) (t (error "Invalid first argument: %S" start-or-string)))) -(defun tp-search-do (function start-or-string &optional end-or-property property-or-value value object) - "Execute FUNCTION on all matches of PROPERTY in a buffer/string range or entire string. +(defun tp--search-do (function start-or-string &optional end-or-property property-or-value value object) + "Internal: Execute FUNCTION on all matches of PROPERTY. This function supports two calling conventions: 1. Buffer/string region: - (tp-search-do FUNCTION START END PROPERTY &optional VALUE OBJECT) + (tp--search-do FUNCTION START END PROPERTY &optional VALUE OBJECT) 2. Entire string: - (tp-search-do FUNCTION STRING PROPERTY &optional VALUE) + (tp--search-do FUNCTION STRING PROPERTY &optional VALUE) FUNCTION receives two arguments: the prop-match (list of START END VALUE) and OBJECT. Returns the number of matches processed." @@ -1187,6 +1292,48 @@ Returns the number of matches processed." (funcall function match obj)) (length matches))) +(defun tp-search-map (function start-or-string &optional end-or-property property-or-value value object) + "Apply FUNCTION to matched text for all matches of PROPERTY. + +This function supports two calling conventions: + +1. Buffer/string region: + (tp-search-map FUNCTION START END PROPERTY &optional VALUE OBJECT) + +2. Entire string: + (tp-search-map FUNCTION STRING PROPERTY &optional VALUE) + +FUNCTION receives the matched text as its only argument. The return value +of FUNCTION replaces the matched text in the string or buffer. + +Returns the number of matches processed. + +Example: + ;; Upcase all matched text + (tp-search-map #\\='upcase my-string \\='marker)" + (let ((obj (cond + ((stringp start-or-string) start-or-string) + ((numberp start-or-string) (or object (current-buffer))) + (t nil)))) + (tp--search-do + (lambda (match obj) + (let* ((start (car match)) + (end (cadr match)) + (text (if (stringp obj) + (substring obj start end) + (buffer-substring start end))) + (new-text (funcall function text))) + (when (and new-text (not (equal new-text text))) + (if (stringp obj) + ;; For strings, replace in-place + (store-substring obj start new-text) + ;; For buffers, delete and insert + (save-excursion + (delete-region start end) + (goto-char start) + (insert new-text)))))) + start-or-string end-or-property property-or-value value object))) + ;;; Query Functions