emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: region-based face-remapping


From: Eli Zaretskii
Subject: Re: region-based face-remapping
Date: Tue, 09 Jan 2024 15:03:26 +0200

> From: JD Smith <jdtsmith@gmail.com>
> Date: Mon, 8 Jan 2024 16:49:21 -0500
> Cc: emacs-devel@gnu.org
> 
>  Why cannot this be done by modifying faces or overlays in the affected
>  region?
> 
> It can, but the region where such face modification is needed could be 
> arbitrarily large (including the
> entire buffer), which makes this more like a font-lock-problem in a PCH or 
> via a timer (with the bonus
> that it could occur either on modification or just point change).  So at 
> worst it would be like
> re-font-locking the entire buffer (many thousands of locations, most just 1 
> character wide) on every
> key press (or rapidly with a timer).

Not sure changing faces will be significantly slower than what you had
in mind.  Maybe simpler (for a Lisp program), but not necessarily
slower.  After all, that's what JIT font-lock does all the time, and
we accept the performance it provides.

> What I’m struggling with is how to do something “like font lock” — i.e. 
> refontify some potentially
> substantial fraction of all the faces in a buffer, not (just) on 
> modifications in an after-change-hook, but
> also on point-dependent “region of interest” changes, with a priority given 
> to the displayed region in
> the window.  IMO, tree-sitter will make this kind of idea more common.

Did you try to use jit-lock-register machinery?

Maybe Stefan (CC'ed) will have better ideas.

> For the highlighting/unhighlighting operation, I think I also mentioned that 
> the faces of interest can and
> will live in display properties, so you’d need to check all ‘display strings 
> within the (un-)highlit region
> too, and rebuild those with the updated faces.

I hope the number of faces you use in those display strings is small,
in which case the feature should still work reasonably well, since you
presumably are familiar with those faces and are managing them in your
code.

> But suppose the change of region was precipitated by the removal or addition 
> of text in the buffer,
> not just point movement?  Now your old region (old-roi, above) is outdated, 
> likely wrong, and possibly
> had holes put in it by the edits.

That's exactly what JIT font-lock solves, right?  Editing a buffer
changes the text, and thus the faces that need to be put on the text
change also.  For example, something that was a comment can become
non-comment, or vice versa.  Isn't what you describe similar (read:
identical), except that the faces themselves are different, i.e. not
font-lock faces?

> OK, getting there, but what about invisible text?  What if the window 
> includes a huge range within a
> large buffer, most of it hidden, and your ts-roi is large (like the whole 
> buffer)?  There’s no point doing
> the (un-)highlighting operation on invisible text.  But you can’t just skip 
> over invisible text, it better have
> its marker property removed, and the act of hiding/unhiding anything is now 
> cause for recalculating
> everything.

Again, JIT font-lock and the display engine already solve this
problem.  So you shouldn't need to reinvent the wheel.

> You can see how tangled it can get, compared to the simplicity (for the elisp 
> programmer) of setting a
> ‘face-remap overlay and moving it around (i.e. similar to updating the class 
> of a div in HTML), and
> letting the display engine sort it out.

Yes, your original idea is much simpler for a Lisp program.  But my
point is that its price in complexity of implementation is
significantly higher, so much so that I doubt we can have it,
certainly soon.  By contrast, if jit-lock-register satisfies your
needs, you should have almost all the tricky machinery in-place
already, and what remains for you to implement is relatively little.

>  It could be a buffer-local variable, which defines the size of the
>  region around point where the faces should change their appearance,
>  and how to change the appearance.  The display engine then could take
>  that into consideration when processing buffer positions around point.
> 
>  Whether this makes sense depends on the applications you have in mind.
> 
>  Since there are many small stretches of text (single character stretches) 
> that would be
>  impacted over
>  a larger region, I’m afraid such a simple approach wouldn’t work.
> 
>  If all you need is change the faces, I think it will work.
> 
> Maybe here you mean something like “within the window region, updating as 
> that changes”, similar to
> what I outlined above?

This was related to my idea of asking the display engine to handle
faces specially in the vicinity of point.  Since the display engine
examines each change in the face property, it could apply special
handling for faces of buffer positions around point.  For a simple
example, suppose that the buffer-local variable I mention above has
the value

   (BEFORE AFTER :background "red")

where BEFORE and AFTER are numbers of positions before and after point
to give the specified background color to all the faces in that
region.  Then the display engine could automatically merge each face
in the region with an anonymous face '(:background "red")', and use
that for the characters in the region.

> [1]  Short aside on timers: do you think an idle timer that repeatedly runs 
> every 75ms of idle time and
> asks “did point change?” then, if so, “did TS region of interest change?” 
> would be preferable to a
> post command hook that kicks a timer to do the same?

I don't think it matters much: a post-command-hook that just calls
run-with-idle-timer is not expensive.

> I already use `timer-set-time' to avoid rapidly
> reallocating a timer.  I’d guess these two approaches are ~equivalent 
> performance-wise, but PCH’s
> can be buffer-local and idle-timers can’t so they are always running.

If you need a timer to be buffer-local, then indeed starting it from a
post-command-hook is more convenient.  Alternatively, the timer
function could test for the buffer and do nothing if it isn't the
buffer where the feature is activated.

> Aside within aside: it would be great if `timer-activate' included an 
> optional no-error argument so you
> don’t have to check if it is on `timer-list’ twice.  I.e. if a timer is 
> already on timer-list and
> `timer-activate’ (with no-error) is called on it, do nothing.

This is usually done by checking whether a timer variable is non-nil,
so it isn't hard to achieve.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]