[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
master 751f170 2/2: Add new functions to replace strings/regexp in a reg
From: |
Lars Ingebrigtsen |
Subject: |
master 751f170 2/2: Add new functions to replace strings/regexp in a region |
Date: |
Mon, 16 Aug 2021 07:20:49 -0400 (EDT) |
branch: master
commit 751f1707f009c714dbfe047ef43443a5c0c3df89
Author: Lars Ingebrigtsen <larsi@gnus.org>
Commit: Lars Ingebrigtsen <larsi@gnus.org>
Add new functions to replace strings/regexp in a region
* doc/lispref/searching.texi (Search and Replace): Document them.
* lisp/subr.el (replace-string-in-region)
(replace-regexp-in-region): New functions.
* lisp/emacs-lisp/shortdoc.el (regexp, buffer): Mention them.
---
doc/lispref/searching.texi | 26 ++++++++++++++-----
etc/NEWS | 6 +++++
lisp/emacs-lisp/shortdoc.el | 6 +++++
lisp/subr.el | 61 +++++++++++++++++++++++++++++++++++++++++++++
test/lisp/subr-tests.el | 46 ++++++++++++++++++++++++++++++++++
5 files changed, 139 insertions(+), 6 deletions(-)
diff --git a/doc/lispref/searching.texi b/doc/lispref/searching.texi
index 1d3e2d9..fe47e7c 100644
--- a/doc/lispref/searching.texi
+++ b/doc/lispref/searching.texi
@@ -2540,9 +2540,9 @@ associated with it still exists.
@cindex replacement after search
@cindex searching and replacing
- If you want to find all matches for a regexp in part of the buffer,
-and replace them, the best way is to write an explicit loop using
-@code{re-search-forward} and @code{replace-match}, like this:
+ If you want to find all matches for a regexp in part of the buffer
+and replace them, the most flexible way is to write an explicit loop
+using @code{re-search-forward} and @code{replace-match}, like this:
@example
(while (re-search-forward "foo[ \t]+bar" nil t)
@@ -2553,9 +2553,23 @@ and replace them, the best way is to write an explicit
loop using
@xref{Replacing Match,, Replacing the Text that Matched}, for a
description of @code{replace-match}.
- However, replacing matches in a string is more complex, especially
-if you want to do it efficiently. So Emacs provides two functions to do
-this.
+@findex replace-regexp-in-region
+ If it's more convenient, you can also use the
+@code{replace-regexp-in-region}, which does something similar to the
+loop above, but is optionally delimited to a specific region (and
+doesn't change point). Furthermore, it does the searches
+case-sensitively, and performs the replacements without changing case
+in the replacement.
+
+@example
+(replace-regexp-in-region "foo[ \t]+bar" "foobar")
+@end example
+
+@findex replace-string-in-region
+ There's also @code{replace-string-in-region}, which works along the
+same lines, but searches for literal strings instead.
+
+ Emacs also has special functions for replacing matches in a string.
@defun replace-regexp-in-string regexp rep string &optional fixedcase literal
subexp start
This function copies @var{string} and searches it for matches for
diff --git a/etc/NEWS b/etc/NEWS
index c2b53e4..54168e8 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2443,6 +2443,12 @@ images are marked.
** Miscellaneous
++++
+*** New function 'replace-regexp-in-region'.
+
++++
+*** New function 'replace-string-in-region'.
+
---
*** New function 'mail-header-parse-addresses-lax'.
This takes a comma-separated string and returns a list of mail/name
diff --git a/lisp/emacs-lisp/shortdoc.el b/lisp/emacs-lisp/shortdoc.el
index 1b0fbfd..7d4a69f 100644
--- a/lisp/emacs-lisp/shortdoc.el
+++ b/lisp/emacs-lisp/shortdoc.el
@@ -700,6 +700,8 @@ There can be any number of :example/:result elements."
(match-substitute-replacement
:no-eval (match-substitute-replacement "new")
:eg-result "new")
+ (replace-regexp-in-region
+ :no-value (replace-regexp-in-region "[0-9]+" "Num \\&"))
"Utilities"
(regexp-quote
:eval (regexp-quote "foo.*bar"))
@@ -894,6 +896,10 @@ There can be any number of :example/:result elements."
:no-value (erase-buffer))
(insert
:no-value (insert "This string will be inserted in the buffer\n"))
+ (subst-char-in-region
+ :no-eval "(subst-char-in-region (point-min) (point-max) ?+ ?-)")
+ (replace-string-in-region
+ :no-value (replace-string-in-region "foo" "bar"))
"Locking"
(lock-buffer
:no-value (lock-buffer "/tmp/foo"))
diff --git a/lisp/subr.el b/lisp/subr.el
index 1cae3ee..0a31ef2 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -3859,6 +3859,67 @@ Point in BUFFER will be placed after the inserted text."
(with-current-buffer buffer
(insert-buffer-substring current start end))))
+(defun replace-string-in-region (string replacement &optional start end)
+ "Replace STRING with REPLACEMENT in the region from START to END.
+The number of replaced occurrences are returned, or nil if STRING
+doesn't exist in the region.
+
+If START is nil, use the current point. If END is nil, use `point-max'.
+
+Comparisons and replacements are done with fixed case."
+ (if start
+ (when (< start (point-min))
+ (error "Start before start of buffer"))
+ (setq start (point)))
+ (if end
+ (when (> end (point-max))
+ (error "End after end of buffer"))
+ (setq end (point-max)))
+ (save-excursion
+ (let ((matches 0)
+ (case-fold-search nil))
+ (goto-char start)
+ (while (search-forward string end t)
+ (delete-region (match-beginning 0) (match-end 0))
+ (insert replacement)
+ (setq matches (1+ matches)))
+ (and (not (zerop matches))
+ matches))))
+
+(defun replace-regexp-in-region (regexp replacement &optional start end)
+ "Replace REGEXP with REPLACEMENT in the region from START to END.
+The number of replaced occurrences are returned, or nil if REGEXP
+doesn't exist in the region.
+
+If START is nil, use the current point. If END is nil, use `point-max'.
+
+Comparisons and replacements are done with fixed case.
+
+REPLACEMENT can use the following special elements:
+
+ `\\&' in NEWTEXT means substitute original matched text.
+ `\\N' means substitute what matched the Nth `\\(...\\)'.
+ If Nth parens didn't match, substitute nothing.
+ `\\\\' means insert one `\\'.
+ `\\?' is treated literally."
+ (if start
+ (when (< start (point-min))
+ (error "Start before start of buffer"))
+ (setq start (point)))
+ (if end
+ (when (> end (point-max))
+ (error "End after end of buffer"))
+ (setq end (point-max)))
+ (save-excursion
+ (let ((matches 0)
+ (case-fold-search nil))
+ (goto-char start)
+ (while (re-search-forward regexp end t)
+ (replace-match replacement t)
+ (setq matches (1+ matches)))
+ (and (not (zerop matches))
+ matches))))
+
(defun yank-handle-font-lock-face-property (face start end)
"If `font-lock-defaults' is nil, apply FACE as a `face' property.
START and END denote the start and end of the text to act on.
diff --git a/test/lisp/subr-tests.el b/test/lisp/subr-tests.el
index b57982a..21b8a27 100644
--- a/test/lisp/subr-tests.el
+++ b/test/lisp/subr-tests.el
@@ -694,5 +694,51 @@ See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=19350."
(should-not (buffer-local-boundp 'test-not-boundp buf))
(should (buffer-local-boundp 'test-global-boundp buf))))
+(ert-deftest test-replace-string-in-region ()
+ (with-temp-buffer
+ (insert "foo bar zot foobar")
+ (should (= (replace-string-in-region "foo" "new" (point-min) (point-max))
+ 2))
+ (should (equal (buffer-string) "new bar zot newbar")))
+
+ (with-temp-buffer
+ (insert "foo bar zot foobar")
+ (should (= (replace-string-in-region "foo" "new" (point-min) 14)
+ 1))
+ (should (equal (buffer-string) "new bar zot foobar")))
+
+ (with-temp-buffer
+ (insert "foo bar zot foobar")
+ (should-error (replace-string-in-region "foo" "new" (point-min) 30)))
+
+ (with-temp-buffer
+ (insert "Foo bar zot foobar")
+ (should (= (replace-string-in-region "Foo" "new" (point-min))
+ 1))
+ (should (equal (buffer-string) "new bar zot foobar"))))
+
+(ert-deftest test-replace-regexp-in-region ()
+ (with-temp-buffer
+ (insert "foo bar zot foobar")
+ (should (= (replace-regexp-in-region "fo+" "new" (point-min) (point-max))
+ 2))
+ (should (equal (buffer-string) "new bar zot newbar")))
+
+ (with-temp-buffer
+ (insert "foo bar zot foobar")
+ (should (= (replace-regexp-in-region "fo+" "new" (point-min) 14)
+ 1))
+ (should (equal (buffer-string) "new bar zot foobar")))
+
+ (with-temp-buffer
+ (insert "foo bar zot foobar")
+ (should-error (replace-regexp-in-region "fo+" "new" (point-min) 30)))
+
+ (with-temp-buffer
+ (insert "Foo bar zot foobar")
+ (should (= (replace-regexp-in-region "Fo+" "new" (point-min))
+ 1))
+ (should (equal (buffer-string) "new bar zot foobar"))))
+
(provide 'subr-tests)
;;; subr-tests.el ends here