[RFC]: replace-region-contents

classic Classic list List threaded Threaded
35 messages Options
12
Reply | Threaded
Open this post in threaded view
|

[RFC]: replace-region-contents

Tassilo Horn-6
Hi all,

on gnu-emacs-help I've asked why my wrapper command around
`json-pretty-print' doesn't restore point although it uses
`save-excursion'.  The reason is that `json-pretty-print' replaces the
region with copy, delete, and insert.  Robert and Eli pointed me to the
new `replace-buffer-contents' which allowed me to rewrite
`json-pretty-print' in a way where point stays where it has been before
pretty-printing:

--8<---------------cut here---------------start------------->8---
(defun json-pretty-print (begin end)
  "Pretty-print selected region."
  (interactive "r")
  (save-excursion
    (save-restriction
      (narrow-to-region begin end)
      (goto-char (point-min))
      (atomic-change-group
        (let ((json-encoding-pretty-print t)
              ;; Distinguish an empty objects from 'null'
              (json-null :json-null)
              ;; Ensure that ordering is maintained
              (json-object-type 'alist)
              (obj (json-read))
              (orig-buffer (current-buffer)))
          (with-temp-buffer
            (insert (json-encode obj))
            (let ((tmp-buffer (current-buffer)))
              (set-buffer orig-buffer)
              (replace-buffer-contents tmp-buffer))))))))
--8<---------------cut here---------------end--------------->8---

I think that replacing a region by transforming its contents in some
arbitray way is some generally useful feature, so I'd propose to extract
that functionality into some new function `replace-region-contents' I'd
like to add to subr-x.el.  Here it is and how it is applied in the
json-pretty-printing scenario.

--8<---------------cut here---------------start------------->8---
;; in subr-x.el (or wherever you please)
(defun replace-region-contents (beg end extract-fn inject-fn)
  "Replace the region between BEG and END using EXTRACT-FN and INJECT-FN.

The current buffer is narrowed to the region between BEG and END,
then EXTRACT-FN is called in order to extract some value.
Thereafter, INJECT-FN is called with that value in a temporary
buffer which it should populate.

Finally, the region in the source buffer is replaced with the
contents of the temporary buffer prepared by INJECT-FN using
`replace-buffer-contents'."
  (save-excursion
    (save-restriction
      (narrow-to-region beg end)
      (goto-char (point-min))
      (atomic-change-group
        (let ((source-buffer (current-buffer))
              (extracted (funcall extract-fn)))
          (with-temp-buffer
            (funcall inject-fn extracted)
            (let ((tmp-buffer (current-buffer)))
              (set-buffer source-buffer)
              (replace-buffer-contents tmp-buffer))))))))

;; in json.el
(defun json-pretty-print (begin end)
  "Pretty-print selected region."
  (interactive "r")
  (replace-region-contents
   begin end
   (lambda ()
     (let ((json-null :json-null)
           ;; Ensure that ordering is maintained
           (json-object-type 'alist))
       (json-read)))
   (lambda (json-obj)
     (let ((json-encoding-pretty-print t)
           ;; Distinguish an empty objects from 'null'
           (json-null :json-null))
       (insert (json-encode json-obj))))))
--8<---------------cut here---------------end--------------->8---

Does that seem like a good idea, is there anything to improve, or should
I just fix `json-pretty-print' as in the first snippet?

Bye,
Tassilo

Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Marcin Borkowski-3

On 2019-02-01, at 22:20, Tassilo Horn <[hidden email]> wrote:

> Hi all,
>
> on gnu-emacs-help I've asked why my wrapper command around
> `json-pretty-print' doesn't restore point although it uses
> `save-excursion'.  The reason is that `json-pretty-print' replaces the
> region with copy, delete, and insert.  Robert and Eli pointed me to the
> new `replace-buffer-contents' which allowed me to rewrite
> `json-pretty-print' in a way where point stays where it has been before
> pretty-printing:
>
> [...]
>
> --8<---------------cut here---------------start------------->8---
> ;; in subr-x.el (or wherever you please)
> (defun replace-region-contents (beg end extract-fn inject-fn)
>   "Replace the region between BEG and END using EXTRACT-FN and INJECT-FN.
>
> The current buffer is narrowed to the region between BEG and END,
> then EXTRACT-FN is called in order to extract some value.
> Thereafter, INJECT-FN is called with that value in a temporary
> buffer which it should populate.
>
> Finally, the region in the source buffer is replaced with the
> contents of the temporary buffer prepared by INJECT-FN using
> `replace-buffer-contents'."

It looks fairly interesting, and I can see quite a few uses for
a function like this.  The docstring doesn't seem to explain a lot to
me, though.  In particular, what are the arguments and return values of
EXTRACT-FN and INJECT-FN?

(I don't have time now to study the code.)

Best,

--
Marcin Borkowski
http://mbork.pl

Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Tassilo Horn-6
Marcin Borkowski <[hidden email]> writes:

Hi Marcin,

>> --8<---------------cut here---------------start------------->8---
>> ;; in subr-x.el (or wherever you please)
>> (defun replace-region-contents (beg end extract-fn inject-fn)
>>   "Replace the region between BEG and END using EXTRACT-FN and INJECT-FN.
>>
>> The current buffer is narrowed to the region between BEG and END,
>> then EXTRACT-FN is called in order to extract some value.
>> Thereafter, INJECT-FN is called with that value in a temporary
>> buffer which it should populate.
>>
>> Finally, the region in the source buffer is replaced with the
>> contents of the temporary buffer prepared by INJECT-FN using
>> `replace-buffer-contents'."
>
> It looks fairly interesting, and I can see quite a few uses for a
> function like this.  The docstring doesn't seem to explain a lot to
> me, though.  In particular, what are the arguments and return values
> of EXTRACT-FN and INJECT-FN?

EXTRACT-FN has no argument.  It's just run in the source buffer.
Whatever it returns is passed as the argument to INJECT-FN which is run
in a temporary, empty buffer and which it should use to prepare the
temporary buffer whose contents will replace the original region in the
source buffer.

Bye,
Tassilo

Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Stefan Monnier
In reply to this post by Tassilo Horn-6
> ;; in subr-x.el (or wherever you please)
> (defun replace-region-contents (beg end extract-fn inject-fn)
>   "Replace the region between BEG and END using EXTRACT-FN and INJECT-FN.
>
> The current buffer is narrowed to the region between BEG and END,
> then EXTRACT-FN is called in order to extract some value.
> Thereafter, INJECT-FN is called with that value in a temporary
> buffer which it should populate.

Why two functions instead of just one?

Also, I think the docstring should hint at the fact that this is meant
for cases where the before and after text share significant parts (so
there's a chance of meaningfully preserving markers).  It might do that
simply be referring to `replace-buffer-contents`.

>       (atomic-change-group

Why?  AFAICT the buffer is not modified until we get to calling
replace-buffer-contents which already has (or should have) the
atomicity property.


        Stefan


PS: BTW, maybe replace-buffer-contents should be changed so it
interprets a string argument as the text to use as replacement rather
than as a buffer name.


Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Stefan Monnier
In reply to this post by Tassilo Horn-6
>       (atomic-change-group

Why?  AFAICT only replace-buffer-contents modifies the buffer and it
only does it atomically.

> The current buffer is narrowed to the region between BEG and END,
> then EXTRACT-FN is called in order to extract some value.
> Thereafter, INJECT-FN is called with that value in a temporary
> buffer which it should populate.

Why 2 separate functions?


        Stefan


PS: I wish replace-buffer-contents interpreted a string as the text to
use as replacement rather than as a buffer name (which I find almost
always to be a misfeature: better call `get-buffer` explicitly when we
need to convert a string to a buffer).  I think we should start by
deprecating this "can be a buffer name" usage ASAP.

Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Eli Zaretskii
> From: Stefan Monnier <[hidden email]>
> Date: Sat, 02 Feb 2019 11:17:12 -0500
> Cc: [hidden email]
>
> PS: I wish replace-buffer-contents interpreted a string as the text to
> use as replacement rather than as a buffer name

You'd need to insert the string into a buffer anyway, before you could
run the code, so might as well do that in Lisp.

(I find text processing based on strings a bad idea in Emacs, btw.
Buffers are much more convenient and efficient.)

Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Stefan Monnier
>> PS: I wish replace-buffer-contents interpreted a string as the text to
>> use as replacement rather than as a buffer name
> You'd need to insert the string into a buffer anyway, before you could
> run the code, so might as well do that in Lisp.

I was only talking about the API, not the implementation.  If I had to
implement the feature, I'd likely rename replace-buffer-contents to
internal--replace-buffer-contents and do the string->buffer conversion
in an Elisp wrapper, simply as a convenience to the user.

`replace-buffer-contents` is basically a fancy replacement for
delete-region + insert and `insert` accepts a string as argument, so
it's very natural to also accept a string as argument.

> (I find text processing based on strings a bad idea in Emacs, btw.

Agreed.

In the case of replace-buffer-contents, I think we could have an equally
efficient code that accepts a string instead of a buffer.  But it'd
definitely be inconvenient to write code that works both ways.

> Buffers are much more convenient and efficient.)

I find strings standing for buffers to be ugly and a source of bugs
(because buffer names can change), so even if we don't accept strings as
I suggested above, I'd prefer that we signal an error when we receive
a string rather than silently looking for a buffer that has the
same name.


        Stefan

Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Eli Zaretskii
> From: Stefan Monnier <[hidden email]>
> Cc: [hidden email], [hidden email]
> Date: Sun, 03 Feb 2019 12:05:57 -0500
>
> I find strings standing for buffers to be ugly and a source of bugs

We could drop the feature of supporting buffer names, but alas, that
ship has sailed.  So we are stuck with the current API.  If accepting
a string of text is a frequent use case, we could create a wrapper
Lisp function to do that, though.

Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Tassilo Horn-6
In reply to this post by Stefan Monnier
Stefan Monnier <[hidden email]> writes:

>> ;; in subr-x.el (or wherever you please)
>> (defun replace-region-contents (beg end extract-fn inject-fn)
>>   "Replace the region between BEG and END using EXTRACT-FN and INJECT-FN.
>>
>> The current buffer is narrowed to the region between BEG and END,
>> then EXTRACT-FN is called in order to extract some value.
>> Thereafter, INJECT-FN is called with that value in a temporary
>> buffer which it should populate.
>
> Why two functions instead of just one?

You mean by copying the region from the source buffer to the temporary
buffer, and then a single function could act just in there?  Maybe a
drawback could be that an EXTRACT-FN might need the original buffer's
configuration, e.g., syntax table, etc.

> Also, I think the docstring should hint at the fact that this is meant
> for cases where the before and after text share significant parts (so
> there's a chance of meaningfully preserving markers).  It might do
> that simply be referring to `replace-buffer-contents`.

It does refer to `replace-buffer-contents'.

>>       (atomic-change-group
>
> Why?  AFAICT the buffer is not modified until we get to calling
> replace-buffer-contents which already has (or should have) the
> atomicity property.

Correct.  I've extracted that from the original `json-pretty-print' and
it's not required anymore.

Bye,
Tassilo

Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Stefan Monnier
>> Why two functions instead of just one?
> You mean by copying the region from the source buffer to the temporary
> buffer, and then a single function could act just in there?

No, a function which directly returns the text to insert in the form of
a string (or a buffer, I guess).

> It does refer to `replace-buffer-contents'.

Oh, indeed, I see it now, sorry.


        Stefan

Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Tassilo Horn-6
Stefan Monnier <[hidden email]> writes:

>>> Why two functions instead of just one?
>> You mean by copying the region from the source buffer to the temporary
>> buffer, and then a single function could act just in there?
>
> No, a function which directly returns the text to insert in the form
> of a string (or a buffer, I guess).

Like so?

--8<---------------cut here---------------start------------->8---
(defun replace-region-contents (beg end replace-fn)
  (save-excursion
    (save-restriction
      (narrow-to-region beg end)
      (goto-char (point-min))
      (let ((repl (funcall replace-fn)))
        (if (bufferp repl)
            (replace-buffer-contents repl)
          (let ((source-buffer (current-buffer)))
            (with-temp-buffer
              (insert repl)
              (let ((tmp-buffer (current-buffer)))
                (set-buffer source-buffer)
                (replace-buffer-contents tmp-buffer)))))))))
--8<---------------cut here---------------end--------------->8---

In the `json-pretty-print' scenario, that would indeed be even easier
(because `json-encode' returns a string anyway):

--8<---------------cut here---------------start------------->8---
(defun json-pretty-print (begin end)
  "Pretty-print selected region."
  (interactive "r")
  (let ((json-encoding-pretty-print t)
        ;; Distinguish an empty objects from 'null'
        (json-null :json-null)
        ;; Ensure that ordering is maintained
        (json-object-type 'alist))
    (replace-region-contents
     begin end
     (lambda () (json-encode (json-read))))))
--8<---------------cut here---------------end--------------->8---

I don't have a preference.  I guess Eli might argue that this version
encourages passing strings around instead of using buffers.  I'd explain
in the doc string that in the case of a string return value, we're going
thru a temporary buffer anyway, so if your REPLACE-FN ends in
(buffer-substring ...), you're clearly doing something wrong...

Bye,
Tassilo

Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Tassilo Horn-6
Tassilo Horn <[hidden email]> writes:

>> No, a function which directly returns the text to insert in the form
>> of a string (or a buffer, I guess).
>
> Like so?
>
> (defun replace-region-contents (beg end replace-fn)
>   (save-excursion
>     (save-restriction
>       (narrow-to-region beg end)
>       (goto-char (point-min))
>       (let ((repl (funcall replace-fn)))
> (if (bufferp repl)
>    (replace-buffer-contents repl)
>  (let ((source-buffer (current-buffer)))
>    (with-temp-buffer
>      (insert repl)
>      (let ((tmp-buffer (current-buffer)))
> (set-buffer source-buffer)
> (replace-buffer-contents tmp-buffer)))))))))

I just became aware of the fact that we cannot use
`replace-buffer-contents' in its current state if the replacement is too
large.  It becomes unbearable slow.  Therefore we cannot simply change
`json-pretty-print' to use it, since e.g., restclient.el calls it to
format possibly huge json snippets.

I wonder where and how to fix that.  Maybe `replace-buffer-contents'
could fall back to recognize itself if it doesn't make sense to try to
restore point and marks, e.g., trivial cases like point is on
(point-min) or (point-max) and there are no marks, or the replacement
buffer's content exceeds some maximum...

Bye,
Tassilo

Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Stefan Monnier
In reply to this post by Tassilo Horn-6
> --8<---------------cut here---------------start------------->8---
> (defun replace-region-contents (beg end replace-fn)
>   (save-excursion
>     (save-restriction
>       (narrow-to-region beg end)
>       (goto-char (point-min))
>       (let ((repl (funcall replace-fn)))
> (if (bufferp repl)
>    (replace-buffer-contents repl)
>  (let ((source-buffer (current-buffer)))
>    (with-temp-buffer
>      (insert repl)
>      (let ((tmp-buffer (current-buffer)))
> (set-buffer source-buffer)
> (replace-buffer-contents tmp-buffer)))))))))
> --8<---------------cut here---------------end--------------->8---

LGTM

> I guess Eli might argue that this version encourages passing strings
> around instead of using buffers.

Passing strings around is not bad.  I think many applications where
replace-region-contents is desirable will generate strings more
efficiently than buffers rather than the other way around (after all,
if the rest can be in a buffer, there's a good chance that you can do
the modifications in-place on-the-fly rather than go through
replace-region-contents).

> I'd explain in the doc string that in the case of a string return
> value, we're going thru a temporary buffer anyway, so if your
> REPLACE-FN ends in (buffer-substring ...), you're clearly doing
> something wrong...

Sounds good.


        Stefan

Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Eli Zaretskii
In reply to this post by Tassilo Horn-6
> From: Tassilo Horn <[hidden email]>
> Date: Tue, 05 Feb 2019 06:57:52 +0100
> Cc: [hidden email]
>
> I don't have a preference.  I guess Eli might argue that this version
> encourages passing strings around instead of using buffers.

Not just strings, _large_ strings.  Small strings are okay, we even
have a special optimization for them.  Large strings are much less
efficient, in both memory management and access, than buffers.  And in
the context of the function we are talking about, the probability of a
string to be large is IMO very high.

I'm not against a string as an option, though.

> I'd explain in the doc string that in the case of a string return
> value, we're going thru a temporary buffer anyway, so if your
> REPLACE-FN ends in (buffer-substring ...), you're clearly doing
> something wrong...

Right, thanks.

Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Eli Zaretskii
In reply to this post by Tassilo Horn-6
> From: Tassilo Horn <[hidden email]>
> Date: Tue, 05 Feb 2019 14:21:46 +0100
> Cc: [hidden email]
>
> I just became aware of the fact that we cannot use
> `replace-buffer-contents' in its current state if the replacement is too
> large.  It becomes unbearable slow.

In what version of Emacs?  I think it was slow in 26.1, but wa ssped
up significantly since then.

If you are using emacs-26 or the master branch from Git, and it is
still slow, can you show an example where it's slow, and tell how much
time it took?

> I wonder where and how to fix that.  Maybe `replace-buffer-contents'
> could fall back to recognize itself if it doesn't make sense to try to
> restore point and marks, e.g., trivial cases like point is on
> (point-min) or (point-max) and there are no marks, or the replacement
> buffer's content exceeds some maximum...

AFAIR, the speed of replace-buffer-contents doesn't depend at all on
whether markers are preserved or not, so I don't think this will
matter.

Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Tassilo Horn-6
Eli Zaretskii <[hidden email]> writes:

>> I just became aware of the fact that we cannot use
>> `replace-buffer-contents' in its current state if the replacement is
>> too large.  It becomes unbearable slow.
>
> In what version of Emacs?  I think it was slow in 26.1, but wa ssped
> up significantly since then.

Master as of today.

> If you are using emacs-26 or the master branch from Git, and it is
> still slow, can you show an example where it's slow, and tell how much
> time it took?

I prepared a tar.xz with a synthetic, huge, one-line JSON file generated
by https://next.json-generator.com and a lisp file benchmarking the
current against my proposed version of `json-pretty-print'.

https://drive.google.com/open?id=1kKahq3csBVXWL-nN1-eMhgDctGWBN1AR

Bye,
Tassilo

Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Tassilo Horn-6
In reply to this post by Stefan Monnier
Stefan Monnier <[hidden email]> writes:

>> --8<---------------cut here---------------start------------->8---
>> (defun replace-region-contents (beg end replace-fn)
>>   (save-excursion
>>     (save-restriction
>>       (narrow-to-region beg end)
>>       (goto-char (point-min))
>>       (let ((repl (funcall replace-fn)))
>> (if (bufferp repl)
>>    (replace-buffer-contents repl)
>>  (let ((source-buffer (current-buffer)))
>>    (with-temp-buffer
>>      (insert repl)
>>      (let ((tmp-buffer (current-buffer)))
>> (set-buffer source-buffer)
>> (replace-buffer-contents tmp-buffer)))))))))
>> --8<---------------cut here---------------end--------------->8---
>
> LGTM

How would I actually use that version with a replace-fn returning a
buffer and not a string?  It looks to me that I need to do the whole
ceremony of creating a temporary buffer, setting buffers, and ensuring
that the temporary buffer is killed even in the case of an abnormal exit
myself.  That's the hassle my original version tried to eliminate in the
first place...

Bye,
Tassilo

Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Marcin Borkowski-3

On 2019-02-06, at 09:07, Tassilo Horn <[hidden email]> wrote:

> Stefan Monnier <[hidden email]> writes:
>
>>> --8<---------------cut here---------------start------------->8---
>>> (defun replace-region-contents (beg end replace-fn)
>>>   (save-excursion
>>>     (save-restriction
>>>       (narrow-to-region beg end)
>>>       (goto-char (point-min))
>>>       (let ((repl (funcall replace-fn)))
>>> (if (bufferp repl)
>>>    (replace-buffer-contents repl)
>>>  (let ((source-buffer (current-buffer)))
>>>    (with-temp-buffer
>>>      (insert repl)
>>>      (let ((tmp-buffer (current-buffer)))
>>> (set-buffer source-buffer)
>>> (replace-buffer-contents tmp-buffer)))))))))
>>> --8<---------------cut here---------------end--------------->8---
>>
>> LGTM
>
> How would I actually use that version with a replace-fn returning a
> buffer and not a string?  It looks to me that I need to do the whole
> ceremony of creating a temporary buffer, setting buffers, and ensuring
> that the temporary buffer is killed even in the case of an abnormal exit
> myself.  That's the hassle my original version tried to eliminate in the
> first place...

I did not follow the whole thread, but why wouldn't `with-temp-buffer'
be a suitable candidate to conduct exactly the ceremony you mentioned?

Also, at least sometimes, buffers are better than strings to perform
e.g. replacements.  (I have a blog post in the works where I actually
measure the performance of one over the other, and due to immutability
of strings there is a lot of GC overhead when you do string replacements
a lot.)

Best,

--
Marcin Borkowski
http://mbork.pl

Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Tassilo Horn-6
Marcin Borkowski <[hidden email]> writes:

>>>> --8<---------------cut here---------------start------------->8---
>>>> (defun replace-region-contents (beg end replace-fn)
>>>>   (save-excursion
>>>>     (save-restriction
>>>>       (narrow-to-region beg end)
>>>>       (goto-char (point-min))
>>>>       (let ((repl (funcall replace-fn)))
>>>> (if (bufferp repl)
>>>>    (replace-buffer-contents repl)
>>>>  (let ((source-buffer (current-buffer)))
>>>>    (with-temp-buffer
>>>>      (insert repl)
>>>>      (let ((tmp-buffer (current-buffer)))
>>>> (set-buffer source-buffer)
>>>> (replace-buffer-contents tmp-buffer)))))))))
>>>> --8<---------------cut here---------------end--------------->8---
>>>
>>> LGTM
>>
>> How would I actually use that version with a replace-fn returning a
>> buffer and not a string?  It looks to me that I need to do the whole
>> ceremony of creating a temporary buffer, setting buffers, and ensuring
>> that the temporary buffer is killed even in the case of an abnormal exit
>> myself.  That's the hassle my original version tried to eliminate in the
>> first place...
>
> I did not follow the whole thread, but why wouldn't `with-temp-buffer'
> be a suitable candidate to conduct exactly the ceremony you mentioned?

You cannot use `with-temp-buffer' inside replace-fn and then return that
temp buffer, because it'll be killed as soon as control flow escapes it.
So you need to define your replace-fn in the lexical scope of an already
established `with-temp-buffer' form.  But since you will need to access
the source buffer, you need to manually arrange in which buffer to
operate what.

> Also, at least sometimes, buffers are better than strings to perform
> e.g. replacements.

Yes, sure, although replacements could be performed in the source buffer
itself.

Bye,
Tassilo

Reply | Threaded
Open this post in threaded view
|

Re: [RFC]: replace-region-contents

Stefan Monnier
In reply to this post by Tassilo Horn-6
> How would I actually use that version with a replace-fn returning a
> buffer and not a string?  It looks to me that I need to do the whole
> ceremony of creating a temporary buffer, setting buffers, and ensuring
> that the temporary buffer is killed even in the case of an abnormal exit
> myself.

Indeed (yet another reason why we should have anonymous buffers, which
can be GC'd without having to go through kill-buffer), but as mentioned
elsewhere I suspect that this use case will be rare, because when the
output wants to be in a buffer rather than a string you will likely be
able to do the replacement "in-place" without using a second buffer (and
hence without needing the diffing machinery of replace-buffer-contents).


        Stefan

12