diff options
| author | Stefan Monnier | 2025-03-28 00:46:53 -0400 |
|---|---|---|
| committer | Stefan Monnier | 2025-03-29 17:49:05 -0400 |
| commit | 7c82cc8b975175aebbad1c43ec1cd98b3232f482 (patch) | |
| tree | 2f2f5ac19ec7055442da0bd58507314d213b9bdd /src | |
| parent | f60fc1287d499e8c93857b1b96e8bd2467b22c8d (diff) | |
| download | emacs-7c82cc8b975175aebbad1c43ec1cd98b3232f482.tar.gz emacs-7c82cc8b975175aebbad1c43ec1cd98b3232f482.zip | |
(replace-region-contents): Improve and promote (bug#76313)
Swap the role of `replace-region-contents` and `replace-buffer-contents`,
so `replace-region-contents` is the main function, implemented in C,
and `replace-buffer-contents` is a mere wrapper (marked as obsolete).
Also remove the need to rely on narrowing and on describing the
new text as a function.
Finally, allow MAX-SECS==0 to require a cheap replacement, and
add an INHERIT argument.
* src/editfns.c: Include `coding.h`.
(Freplace_region_contents): Rename from `Freplace_buffer_contents`.
Change calling convention to that of `replace-region-contents`.
Add more options for the SOURCE argument. Add INHERIT argument.
Skip the costly algorithm if MAX-SECS is 0.
* src/insdel.c (replace_range): Allow NEW to be a buffer.
* lisp/subr.el (replace-buffer-contents): New implementation.
* lisp/emacs-lisp/subr-x.el (replace-region-contents): Delete.
* doc/lispref/text.texi (Replacing): Document new API for
`replace-region-contents`. Remove documentation of
`replace-buffer-contents`.
* test/src/editfns-tests.el (replace-buffer-contents-1)
(replace-buffer-contents-2, replace-buffer-contents-bug31837):
Use `replace-region-contents`.
(editfns--replace-region): Delete.
(editfns-tests--replace-region): Use `replace-region-contents`.
Adds tests for new types of SOURCE args.
Diffstat (limited to 'src')
| -rw-r--r-- | src/coding.c | 2 | ||||
| -rw-r--r-- | src/editfns.c | 150 | ||||
| -rw-r--r-- | src/insdel.c | 12 |
3 files changed, 113 insertions, 51 deletions
diff --git a/src/coding.c b/src/coding.c index b0bd5d3a9ab..63b0dbeb18b 100644 --- a/src/coding.c +++ b/src/coding.c | |||
| @@ -7898,6 +7898,8 @@ code_conversion_save (bool with_work_buf, bool multibyte) | |||
| 7898 | bset_enable_multibyte_characters (current_buffer, multibyte ? Qt : Qnil); | 7898 | bset_enable_multibyte_characters (current_buffer, multibyte ? Qt : Qnil); |
| 7899 | if (EQ (workbuf, Vcode_conversion_reused_workbuf)) | 7899 | if (EQ (workbuf, Vcode_conversion_reused_workbuf)) |
| 7900 | reused_workbuf_in_use = true; | 7900 | reused_workbuf_in_use = true; |
| 7901 | /* FIXME: Maybe we should stay in the new workbuf, because we often | ||
| 7902 | switch right back to it anyway in order to initialize it further. */ | ||
| 7901 | set_buffer_internal (current); | 7903 | set_buffer_internal (current); |
| 7902 | } | 7904 | } |
| 7903 | 7905 | ||
diff --git a/src/editfns.c b/src/editfns.c index 53d6cce7c82..25625793c42 100644 --- a/src/editfns.c +++ b/src/editfns.c | |||
| @@ -54,6 +54,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ | |||
| 54 | #include "buffer.h" | 54 | #include "buffer.h" |
| 55 | #include "window.h" | 55 | #include "window.h" |
| 56 | #include "blockinput.h" | 56 | #include "blockinput.h" |
| 57 | #include "coding.h" | ||
| 57 | 58 | ||
| 58 | #ifdef WINDOWSNT | 59 | #ifdef WINDOWSNT |
| 59 | # include "w32common.h" | 60 | # include "w32common.h" |
| @@ -1914,11 +1915,14 @@ static bool compareseq_early_abort (struct context *); | |||
| 1914 | #include "minmax.h" | 1915 | #include "minmax.h" |
| 1915 | #include "diffseq.h" | 1916 | #include "diffseq.h" |
| 1916 | 1917 | ||
| 1917 | DEFUN ("replace-buffer-contents", Freplace_buffer_contents, | 1918 | DEFUN ("replace-region-contents", Freplace_region_contents, |
| 1918 | Sreplace_buffer_contents, 1, 3, "bSource buffer: ", | 1919 | Sreplace_region_contents, 3, 6, 0, |
| 1919 | doc: /* Replace accessible portion of current buffer with that of SOURCE. | 1920 | doc: /* Replace the region between BEG and END with that of SOURCE. |
| 1920 | SOURCE can be a buffer or a string that names a buffer. | 1921 | SOURCE can be a buffer, a string, or a vector [SBUF SBEG SEND] |
| 1921 | Interactively, prompt for SOURCE. | 1922 | denoting the subtring SBEG..SEND of buffer SBUF. |
| 1923 | |||
| 1924 | If optional argument INHERIT is non-nil, the inserted text will inherit | ||
| 1925 | properties from adjoining text. | ||
| 1922 | 1926 | ||
| 1923 | As far as possible the replacement is non-destructive, i.e. existing | 1927 | As far as possible the replacement is non-destructive, i.e. existing |
| 1924 | buffer contents, markers, properties, and overlays in the current | 1928 | buffer contents, markers, properties, and overlays in the current |
| @@ -1940,18 +1944,85 @@ computation. If the actual costs exceed this limit, heuristics are | |||
| 1940 | used to provide a faster but suboptimal solution. The default value | 1944 | used to provide a faster but suboptimal solution. The default value |
| 1941 | is 1000000. | 1945 | is 1000000. |
| 1942 | 1946 | ||
| 1947 | Note: If the replacement is a string, it’ll usually be placed internally | ||
| 1948 | in a temporary buffer. Therefore, all else being equal, it is preferable | ||
| 1949 | to pass a buffer rather than a string as SOURCE argument. | ||
| 1950 | |||
| 1943 | This function returns t if a non-destructive replacement could be | 1951 | This function returns t if a non-destructive replacement could be |
| 1944 | performed. Otherwise, i.e., if MAX-SECS was exceeded, it returns | 1952 | performed. Otherwise, i.e., if MAX-SECS was exceeded, it returns |
| 1945 | nil. */) | 1953 | nil. |
| 1946 | (Lisp_Object source, Lisp_Object max_secs, Lisp_Object max_costs) | 1954 | |
| 1955 | SOURCE can also be a function that will be called with no arguments | ||
| 1956 | and with current buffer narrowed to BEG..END, and should return | ||
| 1957 | a buffer or a string. But this is deprecated. */) | ||
| 1958 | (Lisp_Object beg, Lisp_Object end, Lisp_Object source, | ||
| 1959 | Lisp_Object max_secs, Lisp_Object max_costs, Lisp_Object inherit) | ||
| 1947 | { | 1960 | { |
| 1948 | struct buffer *a = current_buffer; | 1961 | validate_region (&beg, &end); |
| 1949 | Lisp_Object source_buffer = Fget_buffer (source); | 1962 | ptrdiff_t min_a = XFIXNUM (beg); |
| 1950 | if (NILP (source_buffer)) | 1963 | ptrdiff_t size_a = XFIXNUM (end) - min_a; |
| 1951 | nsberror (source); | 1964 | eassume (size_a >= 0); |
| 1952 | struct buffer *b = XBUFFER (source_buffer); | 1965 | bool a_empty = size_a == 0; |
| 1953 | if (! BUFFER_LIVE_P (b)) | 1966 | bool inh = !NILP (inherit); |
| 1967 | |||
| 1968 | if (FUNCTIONP (source)) | ||
| 1969 | { | ||
| 1970 | specpdl_ref count = SPECPDL_INDEX (); | ||
| 1971 | record_unwind_protect (save_restriction_restore, | ||
| 1972 | save_restriction_save ()); | ||
| 1973 | Fnarrow_to_region (beg, end); | ||
| 1974 | source = calln (source); | ||
| 1975 | unbind_to (count, Qnil); | ||
| 1976 | } | ||
| 1977 | ptrdiff_t min_b, size_b; | ||
| 1978 | struct buffer *b; | ||
| 1979 | if (STRINGP (source)) | ||
| 1980 | { | ||
| 1981 | min_b = BEG; /* Assuming we'll copy it into a buffer. */ | ||
| 1982 | size_b = SCHARS (source); | ||
| 1983 | b = NULL; | ||
| 1984 | } | ||
| 1985 | else if (BUFFERP (source)) | ||
| 1986 | { | ||
| 1987 | b = XBUFFER (source); | ||
| 1988 | min_b = BUF_BEGV (b); | ||
| 1989 | size_b = BUF_ZV (b) - min_b; | ||
| 1990 | } | ||
| 1991 | else | ||
| 1992 | { | ||
| 1993 | CHECK_TYPE (VECTORP (source), | ||
| 1994 | list (Qor, Qstring, Qbuffer, Qvector), source); | ||
| 1995 | /* Let `Faref' signal an error if it's too small. */ | ||
| 1996 | Lisp_Object send = Faref (source, make_fixnum (2)); | ||
| 1997 | Lisp_Object sbeg = AREF (source, 1); | ||
| 1998 | CHECK_BUFFER (AREF (source, 0)); | ||
| 1999 | b = XBUFFER (AREF (source, 0)); | ||
| 2000 | specpdl_ref count = SPECPDL_INDEX (); | ||
| 2001 | record_unwind_current_buffer (); | ||
| 2002 | set_buffer_internal (b); | ||
| 2003 | validate_region (&sbeg, &send); | ||
| 2004 | unbind_to (count, Qnil); | ||
| 2005 | min_b = XFIXNUM (sbeg); | ||
| 2006 | size_b = XFIXNUM (send) - min_b; | ||
| 2007 | } | ||
| 2008 | bool b_empty = size_b == 0; | ||
| 2009 | if (b && !BUFFER_LIVE_P (b)) | ||
| 1954 | error ("Selecting deleted buffer"); | 2010 | error ("Selecting deleted buffer"); |
| 2011 | |||
| 2012 | /* Handle trivial cases where at least one accessible portion is | ||
| 2013 | empty. */ | ||
| 2014 | |||
| 2015 | if (a_empty && b_empty) | ||
| 2016 | return Qt; | ||
| 2017 | else if (a_empty || b_empty | ||
| 2018 | || EQ (max_secs, make_fixnum (0)) | ||
| 2019 | || EQ (max_costs, make_fixnum (0))) | ||
| 2020 | { | ||
| 2021 | replace_range (min_a, min_a + size_a, source, true, false, inh); | ||
| 2022 | return Qt; | ||
| 2023 | } | ||
| 2024 | |||
| 2025 | struct buffer *a = current_buffer; | ||
| 1955 | if (a == b) | 2026 | if (a == b) |
| 1956 | error ("Cannot replace a buffer with itself"); | 2027 | error ("Cannot replace a buffer with itself"); |
| 1957 | 2028 | ||
| @@ -1977,36 +2048,8 @@ nil. */) | |||
| 1977 | time_limit = tlim; | 2048 | time_limit = tlim; |
| 1978 | } | 2049 | } |
| 1979 | 2050 | ||
| 1980 | ptrdiff_t min_a = BEGV; | ||
| 1981 | ptrdiff_t min_b = BUF_BEGV (b); | ||
| 1982 | ptrdiff_t size_a = ZV - min_a; | ||
| 1983 | ptrdiff_t size_b = BUF_ZV (b) - min_b; | ||
| 1984 | eassume (size_a >= 0); | ||
| 1985 | eassume (size_b >= 0); | ||
| 1986 | bool a_empty = size_a == 0; | ||
| 1987 | bool b_empty = size_b == 0; | ||
| 1988 | |||
| 1989 | /* Handle trivial cases where at least one accessible portion is | ||
| 1990 | empty. */ | ||
| 1991 | |||
| 1992 | if (a_empty && b_empty) | ||
| 1993 | return Qt; | ||
| 1994 | |||
| 1995 | if (a_empty) | ||
| 1996 | { | ||
| 1997 | Finsert_buffer_substring (source, Qnil, Qnil); | ||
| 1998 | return Qt; | ||
| 1999 | } | ||
| 2000 | |||
| 2001 | if (b_empty) | ||
| 2002 | { | ||
| 2003 | del_range_both (BEGV, BEGV_BYTE, ZV, ZV_BYTE, true); | ||
| 2004 | return Qt; | ||
| 2005 | } | ||
| 2006 | |||
| 2007 | specpdl_ref count = SPECPDL_INDEX (); | 2051 | specpdl_ref count = SPECPDL_INDEX (); |
| 2008 | 2052 | ||
| 2009 | |||
| 2010 | ptrdiff_t diags = size_a + size_b + 3; | 2053 | ptrdiff_t diags = size_a + size_b + 3; |
| 2011 | ptrdiff_t del_bytes = size_a / CHAR_BIT + 1; | 2054 | ptrdiff_t del_bytes = size_a / CHAR_BIT + 1; |
| 2012 | ptrdiff_t ins_bytes = size_b / CHAR_BIT + 1; | 2055 | ptrdiff_t ins_bytes = size_b / CHAR_BIT + 1; |
| @@ -2020,6 +2063,18 @@ nil. */) | |||
| 2020 | unsigned char *deletions_insertions = memset (buffer + 2 * diags, 0, | 2063 | unsigned char *deletions_insertions = memset (buffer + 2 * diags, 0, |
| 2021 | del_bytes + ins_bytes); | 2064 | del_bytes + ins_bytes); |
| 2022 | 2065 | ||
| 2066 | /* The rest of the code is not prepared to handle a string SOURCE. */ | ||
| 2067 | if (!b) | ||
| 2068 | { | ||
| 2069 | Lisp_Object workbuf | ||
| 2070 | = code_conversion_save (true, STRING_MULTIBYTE (source)); | ||
| 2071 | b = XBUFFER (workbuf); | ||
| 2072 | set_buffer_internal (b); | ||
| 2073 | CALLN (Finsert, source); | ||
| 2074 | set_buffer_internal (a); | ||
| 2075 | } | ||
| 2076 | Lisp_Object source_buffer = make_lisp_ptr (b, Lisp_Vectorlike); | ||
| 2077 | |||
| 2023 | /* FIXME: It is not documented how to initialize the contents of the | 2078 | /* FIXME: It is not documented how to initialize the contents of the |
| 2024 | context structure. This code cargo-cults from the existing | 2079 | context structure. This code cargo-cults from the existing |
| 2025 | caller in src/analyze.c of GNU Diffutils, which appears to | 2080 | caller in src/analyze.c of GNU Diffutils, which appears to |
| @@ -2053,7 +2108,7 @@ nil. */) | |||
| 2053 | Lisp_Object src = CALLN (Fvector, source_buffer, | 2108 | Lisp_Object src = CALLN (Fvector, source_buffer, |
| 2054 | make_fixnum (BUF_BEGV (b)), | 2109 | make_fixnum (BUF_BEGV (b)), |
| 2055 | make_fixnum (BUF_ZV (b))); | 2110 | make_fixnum (BUF_ZV (b))); |
| 2056 | replace_range (BEGV, ZV, src, true, false, false); | 2111 | replace_range (min_a, min_a + size_a, src, true, false, inh); |
| 2057 | SAFE_FREE_UNBIND_TO (count, Qnil); | 2112 | SAFE_FREE_UNBIND_TO (count, Qnil); |
| 2058 | return Qnil; | 2113 | return Qnil; |
| 2059 | } | 2114 | } |
| @@ -2069,7 +2124,7 @@ nil. */) | |||
| 2069 | modification hooks, because then they don't want that. */ | 2124 | modification hooks, because then they don't want that. */ |
| 2070 | if (!inhibit_modification_hooks) | 2125 | if (!inhibit_modification_hooks) |
| 2071 | { | 2126 | { |
| 2072 | prepare_to_modify_buffer (BEGV, ZV, NULL); | 2127 | prepare_to_modify_buffer (min_a, min_a + size_a, NULL); |
| 2073 | specbind (Qinhibit_modification_hooks, Qt); | 2128 | specbind (Qinhibit_modification_hooks, Qt); |
| 2074 | modification_hooks_inhibited = true; | 2129 | modification_hooks_inhibited = true; |
| 2075 | } | 2130 | } |
| @@ -2102,10 +2157,9 @@ nil. */) | |||
| 2102 | eassert (beg_a <= end_a); | 2157 | eassert (beg_a <= end_a); |
| 2103 | eassert (beg_b <= end_b); | 2158 | eassert (beg_b <= end_b); |
| 2104 | eassert (beg_a < end_a || beg_b < end_b); | 2159 | eassert (beg_a < end_a || beg_b < end_b); |
| 2105 | /* FIXME: Use 'replace_range'! */ | ||
| 2106 | ASET (src, 1, make_fixed_natnum (beg_b)); | 2160 | ASET (src, 1, make_fixed_natnum (beg_b)); |
| 2107 | ASET (src, 2, make_fixed_natnum (end_b)); | 2161 | ASET (src, 2, make_fixed_natnum (end_b)); |
| 2108 | replace_range (beg_a, end_a, src, true, false, false); | 2162 | replace_range (beg_a, end_a, src, true, false, inh); |
| 2109 | } | 2163 | } |
| 2110 | --i; | 2164 | --i; |
| 2111 | --j; | 2165 | --j; |
| @@ -2115,8 +2169,8 @@ nil. */) | |||
| 2115 | 2169 | ||
| 2116 | if (modification_hooks_inhibited) | 2170 | if (modification_hooks_inhibited) |
| 2117 | { | 2171 | { |
| 2118 | signal_after_change (BEGV, size_a, ZV - BEGV); | 2172 | signal_after_change (min_a, size_a, size_b); |
| 2119 | update_compositions (BEGV, ZV, CHECK_INSIDE); | 2173 | update_compositions (min_a, min_a + size_b, CHECK_INSIDE); |
| 2120 | /* We've locked the buffer's file above in | 2174 | /* We've locked the buffer's file above in |
| 2121 | prepare_to_modify_buffer; if the buffer is unchanged at this | 2175 | prepare_to_modify_buffer; if the buffer is unchanged at this |
| 2122 | point, i.e. no insertions or deletions have been made, unlock | 2176 | point, i.e. no insertions or deletions have been made, unlock |
| @@ -4787,7 +4841,7 @@ it to be non-nil. */); | |||
| 4787 | 4841 | ||
| 4788 | defsubr (&Sinsert_buffer_substring); | 4842 | defsubr (&Sinsert_buffer_substring); |
| 4789 | defsubr (&Scompare_buffer_substrings); | 4843 | defsubr (&Scompare_buffer_substrings); |
| 4790 | defsubr (&Sreplace_buffer_contents); | 4844 | defsubr (&Sreplace_region_contents); |
| 4791 | defsubr (&Ssubst_char_in_region); | 4845 | defsubr (&Ssubst_char_in_region); |
| 4792 | defsubr (&Stranslate_region_internal); | 4846 | defsubr (&Stranslate_region_internal); |
| 4793 | defsubr (&Sdelete_region); | 4847 | defsubr (&Sdelete_region); |
diff --git a/src/insdel.c b/src/insdel.c index 9b770725971..20267265ab8 100644 --- a/src/insdel.c +++ b/src/insdel.c | |||
| @@ -1409,9 +1409,9 @@ adjust_after_insert (ptrdiff_t from, ptrdiff_t from_byte, | |||
| 1409 | adjust_after_replace (from, from_byte, Qnil, newlen, len_byte); | 1409 | adjust_after_replace (from, from_byte, Qnil, newlen, len_byte); |
| 1410 | } | 1410 | } |
| 1411 | 1411 | ||
| 1412 | /* Replace the text from character positions FROM to TO with NEW. | 1412 | /* Replace the text from character positions FROM to TO with the |
| 1413 | NEW could either be a string, the replacement text, or a vector | 1413 | replacement text NEW. NEW could either be a string, a buffer, or |
| 1414 | [BUFFER BEG END], where BUFFER is the buffer with the replacement | 1414 | a vector [BUFFER BEG END], where BUFFER is the buffer with the replacement |
| 1415 | text and BEG and END are buffer positions in BUFFER that give the | 1415 | text and BEG and END are buffer positions in BUFFER that give the |
| 1416 | replacement text beginning and end. | 1416 | replacement text beginning and end. |
| 1417 | If PREPARE, call prepare_to_modify_buffer. | 1417 | If PREPARE, call prepare_to_modify_buffer. |
| @@ -1439,6 +1439,12 @@ replace_range (ptrdiff_t from, ptrdiff_t to, Lisp_Object new, | |||
| 1439 | insbeg = 0; | 1439 | insbeg = 0; |
| 1440 | inschars = SCHARS (new); | 1440 | inschars = SCHARS (new); |
| 1441 | } | 1441 | } |
| 1442 | else if (BUFFERP (new)) | ||
| 1443 | { | ||
| 1444 | insbuf = XBUFFER (new); | ||
| 1445 | insbeg = BUF_BEGV (insbuf); | ||
| 1446 | inschars = BUF_ZV (insbuf) - insbeg; | ||
| 1447 | } | ||
| 1442 | else | 1448 | else |
| 1443 | { | 1449 | { |
| 1444 | CHECK_VECTOR (new); | 1450 | CHECK_VECTOR (new); |