|
| From: | JD Smith |
| Subject: | Re: region-based face-remapping |
| Date: | Mon, 8 Jan 2024 16:49:21 -0500 |
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). See below for an attempted “better” approach.
For sure. I already by default use a delay timer via a PCH to avoid updating the current-depth (single bar) highlight too quickly (though not an idle timer: instead I schedule the update say 75ms in the future, and push it forward another 75ms when new commands come in before the timer fires — what I call a “kick the can” timer [1, for aside on timers]). As I briefly mentioned before, face-remapping-alist on my system is actually performant enough to do this as fast as users can fire commands (e.g. ~10ms for smooth scrolling). That speed and my subsequent inference about what the display engine might be able to do in this space is what motivated my notion of a ‘face-remap property. 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. I’ve thought of simply performing the highlight update operation on the intersection between the treesitter region-of-interest and the window-start/end region, maybe with 20% space padding on either side of the window. And then, when new regions are indicated, highlight the (new-old) difference, and unhighlight the (old-new) difference (leaving their union unchanged). Something like this (in pseudo-code):
A bit fiddly, but not terrible. Would be better to see if point has changed, and if not, use a cached ts-roi. 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. 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. So instead of saving old-roi in a variable, reach for a marker text property, call it 'indent-bars-ts-highlighted (rear-nonsticky of course). Then, when the region of interest has changed, find (via `next-single-property-changes’) all changes to this marker property across the entire buffer (hopefully fast enough?). Intersect this (possibly disjoint set of) old region(s) against the new (window-region + ts-roi) intersection, and (de-)highlight as before. 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. 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. So I think it’s possible, but it’s also painful, and I’d hazard to guess most package authors wouldn’t go to such lengths.
Maybe here you mean something like “within the window region, updating as that changes”, similar to what I outlined above?
OK, fair enough. Possibly it’s worth pressing on this to see what I can do with current capabilities then. Please let me know if I’m missing any obvious strategies that would simplify the mess above. Thanks for your thoughts. [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 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. 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. |
| [Prev in Thread] | Current Thread | [Next in Thread] |