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

From: Eli Zaretskii <[email-masked]>
Date: Sat, 30 Mar 2024 09:34:39 +0300

> From: Stefan Monnier <[email-masked]>
> Cc: [email-masked],  [email-masked],  [email-masked],
>   [email-masked],  [email-masked],  [email-masked],
>   [email-masked],  [email-masked],
>   [email-masked],  [email-masked],  [email-masked]
> Date: Fri, 29 Mar 2024 14:53:41 -0400
> 
> > I cannot imagine how applications would use these APIs.  I'm probably
> > missing something, org the above documentation does.  Can you show
> > some real-life examples?
> 
> Haven't written real code for it yes, no.
> The best I can offer is the sample code in the file:
> 
>     (defvar my-foo--change-tracker nil)
>     (define-minor-mode my-foo-mode
>       "Fooing like there's no tomorrow."
>       (if (null my-foo-mode)
>           (when my-foo--change-tracker
>             (track-changes-unregister my-foo--change-tracker)
>             (setq my-foo--change-tracker nil))
>         (unless my-foo--change-tracker
>           (setq my-foo--change-tracker
>                 (track-changes-register
>                  (lambda ()
>                    (track-changes-fetch
>                     my-foo--change-tracker
>                     (lambda (beg end before)
>                       ..DO THE THING..))))))))
> 
> Where "DO THE THING" is run similarly to what would happen in an
> `after-change-functions`, except:
> 
> - BEFORE is a string holding the content of what was in BEG..END
>   instead of being limited to its length.
> - It's run at most once per command, so there's no performance worries.
> - It's run "outside" of the modifications themselves,
>   so `inhibit-modification-hooks` is nil and the code can wait, modify
>   the buffer, or do any kind of crazy things.

Thanks.

I understand the last point, but that still doesn't explain enough for
me to see the light.  (I've also looked at the two real-life uses you
posted, and it didn't help, probably because the important ideas
drowned in the sea of modifications to code I'm not familiar with well
enough to understand what's important and what isn't.)  If the last
point, i.e. the problems caused by limitations on what can be safely
done from modification hooks, is basically the main advantage, then I
think I understand the rationale.  Otherwise, the above looks like
doing all the job in after-change-functions, and it is not clear to me
how is that better, since if track-changes-fetch will fetch a series
of changes, deciding how to handle them could be much harder than
handling them one by one when each one happens.  For example, the
BEGIN..END values no longer reflect the current buffer contents, and
each fetched change refers to a different content of the buffer (so
the same values of BEG..END don't necessarily mean the same places in
the buffer).  I think the need for the
eglot--virtual-pos-to-lsp-position function in one of your examples is
the tip of that iceberg.

Also, all of your examples seem to have the signal function just call
track-changes-fetch and do almost nothing else, so I wonder why we
need a separate function for that, and more specifically what would be
a use case where the registered signal function does NOT call
track-changes-fetch, but does something else, and track-changes-fetch
is then called outside of the signal function.

Finally, the doc string of track-changes-register does not describe
the exact place in the buffer-change sequence where the signal
function will be called, which makes it harder to reason about it.
Will it be called where we now call signal_after_change or somewhere
else?  And how do you guarantee that the signal function will not be
called again until track-changes-fetch is called?