Subject: bug#75291: Redisplay not updating fringe when face filter changes

From: Daniel Colascione <[email-masked]>
Date: Thu, 02 Jan 2025 13:36:34 -0500

Eli Zaretskii <[email-masked]> writes:

>> From: Daniel Colascione <[email-masked]>
>> Date: Thu, 02 Jan 2025 12:30:16 -0500
>> 
>> Sometimes we don't redraw when its effective face changes indirectly due
>> to a face filter condition evaluating differently.
>> 
>> An explicit redraw-display works around the problem, but it shouldn't be
>> necessary and is an efficiency hit.
>> 
>> See the code below:
>> 
>> ;; -*- lexical-binding: t -*-
>> 
>> ;; emacs -Q
>> 
>> (setf my-window (selected-window))
>> 
>> (dolist (face '(default fringe))
>>   (face-remap-add-relative
>>    face `(:filtered (:window foo t) (:background "green"))))
>> 
>> (set-window-parameter my-window 'foo t)
>> 
>> ;; During this sit-for, background and fringe should be green
>> (sit-for 1)
>> (set-window-parameter my-window 'foo nil)
>> 
>> ;; Enable to work around bug
>> (when nil
>>   (redraw-display))
>> 
>> ;; During this sit-for, background and fringe should be their original
>> ;; color (probably white), but only the background changes.
>> ;; Without redraw-display, the fringe remains green.
>> (sit-for 1)
>
> I think this is bug#74876 again.  I tried to explain there why the way
> the fringe drawing is implemented doesn't work well with
> face-remapping (or with changes in the fringe face in general).
>
> Maybe I'm missing something, but supporting what this and that bug
> want would need a rewrite of update_window_fringes (and possibly also
> the way we record fringes' attributes in the glyph row).

That's amazing. Seven years ago, I implemented
https://github.com/dcolascione/emacs-window-highlight/blob/master/window-highlight.el.
I worked around the bug we're discussing using redraw-display. I see
that Michal (++) implemented the same feature independently and reported
the same bug just a few weeks before I got around to reporting the same
bug from seven years ago!  Pure serendipity.

(BTW: my code uses pre-redisplay-function, not a command hook. I've
tried it both ways and found the redisplay hook to be more reliable.)

Anyway, given that the feature has been implemented twice now, maybe we
can find some way to make it work efficiently?  I haven't looked into
how exactly we do fringe invalidation, but isn't there a way we can
limit the blast radius of the redraw by providing a lisp-level function
to invalidate extra GUI parts of specific windows rather than forcing a
redraw-frame?  It's not clear to me why we couldn't skip redrawing every
row's content but redraw every row's fringe anyway.

Ideally, a change to a face in which the change couldn't possibly affect
layout (e.g. changing a background color) would be pretty efficient from
a redisplay POV, since we wouldn't have to even try to reflow any text.