diff options
| author | Lars Ingebrigtsen | 2021-08-16 13:20:35 +0200 |
|---|---|---|
| committer | Lars Ingebrigtsen | 2021-08-16 13:20:35 +0200 |
| commit | 751f1707f009c714dbfe047ef43443a5c0c3df89 (patch) | |
| tree | af4841befe93c09b6e28851fa5c20e57be9abbc5 | |
| parent | 42be41657813ae606427aa53d2f0f0b7039d3ef1 (diff) | |
| download | emacs-751f1707f009c714dbfe047ef43443a5c0c3df89.tar.gz emacs-751f1707f009c714dbfe047ef43443a5c0c3df89.zip | |
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.
| -rw-r--r-- | doc/lispref/searching.texi | 26 | ||||
| -rw-r--r-- | etc/NEWS | 6 | ||||
| -rw-r--r-- | lisp/emacs-lisp/shortdoc.el | 6 | ||||
| -rw-r--r-- | lisp/subr.el | 61 | ||||
| -rw-r--r-- | 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 1d3e2d986c5..fe47e7ccf57 100644 --- a/doc/lispref/searching.texi +++ b/doc/lispref/searching.texi | |||
| @@ -2540,9 +2540,9 @@ associated with it still exists. | |||
| 2540 | @cindex replacement after search | 2540 | @cindex replacement after search |
| 2541 | @cindex searching and replacing | 2541 | @cindex searching and replacing |
| 2542 | 2542 | ||
| 2543 | If you want to find all matches for a regexp in part of the buffer, | 2543 | If you want to find all matches for a regexp in part of the buffer |
| 2544 | and replace them, the best way is to write an explicit loop using | 2544 | and replace them, the most flexible way is to write an explicit loop |
| 2545 | @code{re-search-forward} and @code{replace-match}, like this: | 2545 | using @code{re-search-forward} and @code{replace-match}, like this: |
| 2546 | 2546 | ||
| 2547 | @example | 2547 | @example |
| 2548 | (while (re-search-forward "foo[ \t]+bar" nil t) | 2548 | (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 | |||
| 2553 | @xref{Replacing Match,, Replacing the Text that Matched}, for a | 2553 | @xref{Replacing Match,, Replacing the Text that Matched}, for a |
| 2554 | description of @code{replace-match}. | 2554 | description of @code{replace-match}. |
| 2555 | 2555 | ||
| 2556 | However, replacing matches in a string is more complex, especially | 2556 | @findex replace-regexp-in-region |
| 2557 | if you want to do it efficiently. So Emacs provides two functions to do | 2557 | If it's more convenient, you can also use the |
| 2558 | this. | 2558 | @code{replace-regexp-in-region}, which does something similar to the |
| 2559 | loop above, but is optionally delimited to a specific region (and | ||
| 2560 | doesn't change point). Furthermore, it does the searches | ||
| 2561 | case-sensitively, and performs the replacements without changing case | ||
| 2562 | in the replacement. | ||
| 2563 | |||
| 2564 | @example | ||
| 2565 | (replace-regexp-in-region "foo[ \t]+bar" "foobar") | ||
| 2566 | @end example | ||
| 2567 | |||
| 2568 | @findex replace-string-in-region | ||
| 2569 | There's also @code{replace-string-in-region}, which works along the | ||
| 2570 | same lines, but searches for literal strings instead. | ||
| 2571 | |||
| 2572 | Emacs also has special functions for replacing matches in a string. | ||
| 2559 | 2573 | ||
| 2560 | @defun replace-regexp-in-string regexp rep string &optional fixedcase literal subexp start | 2574 | @defun replace-regexp-in-string regexp rep string &optional fixedcase literal subexp start |
| 2561 | This function copies @var{string} and searches it for matches for | 2575 | This function copies @var{string} and searches it for matches for |
| @@ -2443,6 +2443,12 @@ images are marked. | |||
| 2443 | 2443 | ||
| 2444 | ** Miscellaneous | 2444 | ** Miscellaneous |
| 2445 | 2445 | ||
| 2446 | +++ | ||
| 2447 | *** New function 'replace-regexp-in-region'. | ||
| 2448 | |||
| 2449 | +++ | ||
| 2450 | *** New function 'replace-string-in-region'. | ||
| 2451 | |||
| 2446 | --- | 2452 | --- |
| 2447 | *** New function 'mail-header-parse-addresses-lax'. | 2453 | *** New function 'mail-header-parse-addresses-lax'. |
| 2448 | This takes a comma-separated string and returns a list of mail/name | 2454 | 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 1b0fbfdf715..7d4a69f42a9 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." | |||
| 700 | (match-substitute-replacement | 700 | (match-substitute-replacement |
| 701 | :no-eval (match-substitute-replacement "new") | 701 | :no-eval (match-substitute-replacement "new") |
| 702 | :eg-result "new") | 702 | :eg-result "new") |
| 703 | (replace-regexp-in-region | ||
| 704 | :no-value (replace-regexp-in-region "[0-9]+" "Num \\&")) | ||
| 703 | "Utilities" | 705 | "Utilities" |
| 704 | (regexp-quote | 706 | (regexp-quote |
| 705 | :eval (regexp-quote "foo.*bar")) | 707 | :eval (regexp-quote "foo.*bar")) |
| @@ -894,6 +896,10 @@ There can be any number of :example/:result elements." | |||
| 894 | :no-value (erase-buffer)) | 896 | :no-value (erase-buffer)) |
| 895 | (insert | 897 | (insert |
| 896 | :no-value (insert "This string will be inserted in the buffer\n")) | 898 | :no-value (insert "This string will be inserted in the buffer\n")) |
| 899 | (subst-char-in-region | ||
| 900 | :no-eval "(subst-char-in-region (point-min) (point-max) ?+ ?-)") | ||
| 901 | (replace-string-in-region | ||
| 902 | :no-value (replace-string-in-region "foo" "bar")) | ||
| 897 | "Locking" | 903 | "Locking" |
| 898 | (lock-buffer | 904 | (lock-buffer |
| 899 | :no-value (lock-buffer "/tmp/foo")) | 905 | :no-value (lock-buffer "/tmp/foo")) |
diff --git a/lisp/subr.el b/lisp/subr.el index 1cae3ee1a60..0a31ef2b29f 100644 --- a/lisp/subr.el +++ b/lisp/subr.el | |||
| @@ -3859,6 +3859,67 @@ Point in BUFFER will be placed after the inserted text." | |||
| 3859 | (with-current-buffer buffer | 3859 | (with-current-buffer buffer |
| 3860 | (insert-buffer-substring current start end)))) | 3860 | (insert-buffer-substring current start end)))) |
| 3861 | 3861 | ||
| 3862 | (defun replace-string-in-region (string replacement &optional start end) | ||
| 3863 | "Replace STRING with REPLACEMENT in the region from START to END. | ||
| 3864 | The number of replaced occurrences are returned, or nil if STRING | ||
| 3865 | doesn't exist in the region. | ||
| 3866 | |||
| 3867 | If START is nil, use the current point. If END is nil, use `point-max'. | ||
| 3868 | |||
| 3869 | Comparisons and replacements are done with fixed case." | ||
| 3870 | (if start | ||
| 3871 | (when (< start (point-min)) | ||
| 3872 | (error "Start before start of buffer")) | ||
| 3873 | (setq start (point))) | ||
| 3874 | (if end | ||
| 3875 | (when (> end (point-max)) | ||
| 3876 | (error "End after end of buffer")) | ||
| 3877 | (setq end (point-max))) | ||
| 3878 | (save-excursion | ||
| 3879 | (let ((matches 0) | ||
| 3880 | (case-fold-search nil)) | ||
| 3881 | (goto-char start) | ||
| 3882 | (while (search-forward string end t) | ||
| 3883 | (delete-region (match-beginning 0) (match-end 0)) | ||
| 3884 | (insert replacement) | ||
| 3885 | (setq matches (1+ matches))) | ||
| 3886 | (and (not (zerop matches)) | ||
| 3887 | matches)))) | ||
| 3888 | |||
| 3889 | (defun replace-regexp-in-region (regexp replacement &optional start end) | ||
| 3890 | "Replace REGEXP with REPLACEMENT in the region from START to END. | ||
| 3891 | The number of replaced occurrences are returned, or nil if REGEXP | ||
| 3892 | doesn't exist in the region. | ||
| 3893 | |||
| 3894 | If START is nil, use the current point. If END is nil, use `point-max'. | ||
| 3895 | |||
| 3896 | Comparisons and replacements are done with fixed case. | ||
| 3897 | |||
| 3898 | REPLACEMENT can use the following special elements: | ||
| 3899 | |||
| 3900 | `\\&' in NEWTEXT means substitute original matched text. | ||
| 3901 | `\\N' means substitute what matched the Nth `\\(...\\)'. | ||
| 3902 | If Nth parens didn't match, substitute nothing. | ||
| 3903 | `\\\\' means insert one `\\'. | ||
| 3904 | `\\?' is treated literally." | ||
| 3905 | (if start | ||
| 3906 | (when (< start (point-min)) | ||
| 3907 | (error "Start before start of buffer")) | ||
| 3908 | (setq start (point))) | ||
| 3909 | (if end | ||
| 3910 | (when (> end (point-max)) | ||
| 3911 | (error "End after end of buffer")) | ||
| 3912 | (setq end (point-max))) | ||
| 3913 | (save-excursion | ||
| 3914 | (let ((matches 0) | ||
| 3915 | (case-fold-search nil)) | ||
| 3916 | (goto-char start) | ||
| 3917 | (while (re-search-forward regexp end t) | ||
| 3918 | (replace-match replacement t) | ||
| 3919 | (setq matches (1+ matches))) | ||
| 3920 | (and (not (zerop matches)) | ||
| 3921 | matches)))) | ||
| 3922 | |||
| 3862 | (defun yank-handle-font-lock-face-property (face start end) | 3923 | (defun yank-handle-font-lock-face-property (face start end) |
| 3863 | "If `font-lock-defaults' is nil, apply FACE as a `face' property. | 3924 | "If `font-lock-defaults' is nil, apply FACE as a `face' property. |
| 3864 | START and END denote the start and end of the text to act on. | 3925 | 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 b57982a7055..21b8a27858e 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." | |||
| 694 | (should-not (buffer-local-boundp 'test-not-boundp buf)) | 694 | (should-not (buffer-local-boundp 'test-not-boundp buf)) |
| 695 | (should (buffer-local-boundp 'test-global-boundp buf)))) | 695 | (should (buffer-local-boundp 'test-global-boundp buf)))) |
| 696 | 696 | ||
| 697 | (ert-deftest test-replace-string-in-region () | ||
| 698 | (with-temp-buffer | ||
| 699 | (insert "foo bar zot foobar") | ||
| 700 | (should (= (replace-string-in-region "foo" "new" (point-min) (point-max)) | ||
| 701 | 2)) | ||
| 702 | (should (equal (buffer-string) "new bar zot newbar"))) | ||
| 703 | |||
| 704 | (with-temp-buffer | ||
| 705 | (insert "foo bar zot foobar") | ||
| 706 | (should (= (replace-string-in-region "foo" "new" (point-min) 14) | ||
| 707 | 1)) | ||
| 708 | (should (equal (buffer-string) "new bar zot foobar"))) | ||
| 709 | |||
| 710 | (with-temp-buffer | ||
| 711 | (insert "foo bar zot foobar") | ||
| 712 | (should-error (replace-string-in-region "foo" "new" (point-min) 30))) | ||
| 713 | |||
| 714 | (with-temp-buffer | ||
| 715 | (insert "Foo bar zot foobar") | ||
| 716 | (should (= (replace-string-in-region "Foo" "new" (point-min)) | ||
| 717 | 1)) | ||
| 718 | (should (equal (buffer-string) "new bar zot foobar")))) | ||
| 719 | |||
| 720 | (ert-deftest test-replace-regexp-in-region () | ||
| 721 | (with-temp-buffer | ||
| 722 | (insert "foo bar zot foobar") | ||
| 723 | (should (= (replace-regexp-in-region "fo+" "new" (point-min) (point-max)) | ||
| 724 | 2)) | ||
| 725 | (should (equal (buffer-string) "new bar zot newbar"))) | ||
| 726 | |||
| 727 | (with-temp-buffer | ||
| 728 | (insert "foo bar zot foobar") | ||
| 729 | (should (= (replace-regexp-in-region "fo+" "new" (point-min) 14) | ||
| 730 | 1)) | ||
| 731 | (should (equal (buffer-string) "new bar zot foobar"))) | ||
| 732 | |||
| 733 | (with-temp-buffer | ||
| 734 | (insert "foo bar zot foobar") | ||
| 735 | (should-error (replace-regexp-in-region "fo+" "new" (point-min) 30))) | ||
| 736 | |||
| 737 | (with-temp-buffer | ||
| 738 | (insert "Foo bar zot foobar") | ||
| 739 | (should (= (replace-regexp-in-region "Fo+" "new" (point-min)) | ||
| 740 | 1)) | ||
| 741 | (should (equal (buffer-string) "new bar zot foobar")))) | ||
| 742 | |||
| 697 | (provide 'subr-tests) | 743 | (provide 'subr-tests) |
| 698 | ;;; subr-tests.el ends here | 744 | ;;; subr-tests.el ends here |