Subject: bug#70077: An easier way to track buffer changes

From: Eli Zaretskii <[email-masked]>
Date: Tue, 09 Apr 2024 06:56:07 +0300

> From: Stefan Monnier <[email-masked]>
> Cc: [email-masked],  [email-masked],  [email-masked]
> Date: Mon, 08 Apr 2024 16:45:39 -0400
> 
> >> Last, but not least: this needs suitable changes in NEWS and ELisp
> >> manual.
> > Working on it.
> 
> Here it is (and aso on `scratch/track-changes`).

Thanks.

> +Using @code{before-change-functions} and @code{after-change-functions}
> +can be difficult in practice because of a number of pitfalls, such as
> +the fact that the two calls are not always properly paired, or some
> +calls may be missing, either because of bugs in the C code or because of
> +inappropriate use of @code{inhibit-modification-hooks}.

I don't think we should talk about bugs in C code in the manual, at
least not so explicitly.  I would rephrase

  the fact that the two calls are not always properly paired, or some
  calls may be missing, either because some Emacs primitives cannot
  properly pair them or because of incorrect use of
  @code{inhibit-modification-hooks}.

> +The start tracking changes, you have to call
   ^^^^^^^^^
"To start"

> +@code{track-changes-register}, passing it a @var{signal} function as
> +argument.  This will return a tracker @var{id} which is used to identify
> +your tracker to the other functions of the library.  The other main
> +function of the library is @code{track-changes-fetch} which lets you
> +fetch the changes you have not yet processed.

The last sentence is redundant, since you are about to describe
track-changes-fetch shortly.

> +When the buffer is modified, the library will call the @var{signal}
> +function to inform you of that change and will immediately start
> +accumulating subsequent changes into a single combined change.
> +The @var{signal} function serves only to warn that a modification
> +occurred but does not receive a description of the change.  Also the
> +library will not call it again until after you processed
> +the change.

The last sentence should IMO say "...until after you retrieved the
change by calling @code{track-changes-fetch}."  The important part
here is to say what "process" means in practice, instead of leaving it
unsaid.

> +To process changes, you need to call @code{track-changes-fetch}, which

That's not really "processing", that's "retrieval", right?  Processing
is what the program does after it retrieves the changes.

> +@defun track-changes-register signal &key nobefore disjoint immediate
> +This function creates a new @emph{tracker}.  Trackers are kept abstract,

I suggest to use "change tracker" instead of just "tracker".  On my
daytime job, "tracker" has a very different meaning, so I stumble each
time I see this used like that.

Also, I suggest to use @dfn for its markup (and add a @cindex for it
for good measure).

> +By default, the call to the @var{signal} function does not happen
> +immediately, but is instead postponed with a 0 seconds timer.
                                                ^^^^^^^^^^^^^^^
A cross-reference to where timers are described is in order there.

> +usually desired to make sure the @var{signal} function is not called too
> +frequently and runs in a permissive context, freeing the client from
> +performance concerns or worries about which operations might be
> +problematic.  If a client wants to have more control, they can provide
> +a non-nil value as the @var{immediate} argument in which case the
         ^^^
@code{nil}

> +If you're not interested in the actual previous content of the buffer,
> +but are using this library only for its ability to combine many small
> +changes into a larger one and to delay the processing to a more
> +convenient time, you can specify a non-nil value for the @var{before}
                                          ^^^
Likewise.

> +While you may like to accumulate many small changes into larger ones,
> +you may not want to do that if the changes are too far apart.  If you
> +specify a non-nil value for the @var{disjoint} argument, the library
                 ^^^
And likewise.

> +modified region, but if you specified a non-nil @var{nobefore} argument
                                               ^^^
And likewise.

> +In case no changes occurred since the last call,
> +@code{track-changes-fetch} simply does not call @var{func} and returns
> +nil.  If changes did occur, it calls @var{func} and returns the value
   ^^^
And likewise.

> +Once @var{func} finishes, @code{track-changes-fetch} re-enables the
> +@var{signal} function so that it will be called the next time a change
> +occurs.  This is the reason why it calls @var{func} instead of returning
> +a description: it makes sure that the @var{signal} function will not be
> +called while you're still processing past changes.

I think there's a subtler issue here that needs to be described
explicitly: if the entire processing of the change is not done inside
FUNC, there's no guarantee that by the time some other function
processes it, the change is still valid and in particular SIGNAL will
not have been called again.  This is not a trivial aspect, since a
program can use FUNC to do just some partial processing, like squirrel
the changes to some buffer for later processing.

>  * New Modes and Packages in Emacs 30.1
>  
> +** New package Track-Changes.

This should be marked with "+++".