Subject: bug#78221: `inhibit-recording-text-property-changes`

From: Stefan Monnier <[email-masked]>
Date: Mon, 12 May 2025 23:48:16 -0400

>> - Don't notify changes to text-properties.
>>   Currently, things like `put-text-property` run the *-change-functions
>>   like any other buffer modification.  In practice this is of dubious value:
>>   - It's already the case that most calls to `put-text-property` and
>>     friends don't cause any notification because they are wrapped within
>>     a `with-silent-modifications` or otherwise take place from code
>>     which always runs with `inhibit-modification-hooks` set
>>     (e.g. because it's run from a *-change-function).
>>     So *-change-functions can't reliably track changes to text-properties.
>>   - Most *-change-functions are *not* interested in text-property
>>     changes anyway.
>>   The above two points suggest maybe we could just refrain from
>>   running *-change-functions when changing the text-properties (just
>>   like we do for changes to overlay properties).
>>   Then again, this would be a backward-incompatible change, so there's
>>   a chance some packages out there would be negatively affected.
>>   Another option would be to allow packages to choose whether to receive
>>   notifications like now or only for changes to the text.
>>   E.g. just like we have `buffer-modified-tick` and
>>   `buffer-chars-modified-tick`, we could have `*-change-functions`
>>   and `*-chars-change-functions` (with some questions remaining about
>>   `first-change-hook` and the `modification-hooks` property).
>>   Or we could rely on a (symbol) property being set on the functions
>>   added to `*-change-functions` to tell whether they want to know about
>>   changes to text-properties or not.
>>   Or maybe a more crude way would be a buffer-local variable controlling
>>   whether `*-change-functions` are called for text-property changes.
>
> I think it was a mistake in the beginning to have text property
> modifications trigger before/after-c-f.

+1

> But seeing it's been that way for so long, I don't think it would be
> a good idea to change it now.

The more I look at it, the more I see it as a serious mistake, indeed.
I still have not found a single *-change-function that needs to know
about text-property changes.

On the contrary, I see that most code that changes text-properties
wraps those changes in `with-silent-modifications` to avoid the various
undesirable side-effects of treating text-properties as being part of
the text.  Actually, the whole purpose of `with-silent-modifications` is
to work around that historical mistake.

Of all the code that touches text-properties I see that the majority is
wrapped (directly or indirectly) in `with-silent-modifications`, and of
the remaining ones, the majority would actually benefit from being
wrapped as well (the authors just haven't bumped (yet) into the
downsides).

The only case I have found so far where we do want to record
text-property changes (not to run *-change-functions, but to mark the
buffer as modified and to record the changes in the undo-list) is the
`enriched-mode`.

For that single use case the rest of ELisp code has had to pay the
complexity cost of `with-silent-modifications` (which is delicate to use
since its effect is nasty if you do modify the buffer's text) plus the
occasional performance cost of uselessly running the *-change-functions
and wasting memory by recording those changes on the undo-list (not to
mention the occasional breakage of promises of how *-change-functions
are run).

AFAICT, in the long term, ELisp code would benefit from fixing that
historical mistake.

I suggest we introduce a new boolean variable
`inhibit-recording-text-property-changes`: if nil we get the current
behavior, but if non-nil then all text-property modifications are
implicitly wrapped in a `with-silent-modifications`: they don't change
the modified-p flag, they don't change the undo list, they don't run any
modification hooks.

I'd expect it to default to nil (for obvious backward compatibility
reasons), and major modes can set it buffer-locally to t.  Eventually,
I'd hope even `enriched-mode` will set it to t, and will instead record
the text-property changes "by hand".

        Stefan