From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Eli Zaretskii Newsgroups: gmane.emacs.devel Subject: Re: region-based face-remapping Date: Tue, 09 Jan 2024 15:03:26 +0200 Message-ID: <83o7dupskh.fsf@gnu.org> References: Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="34592"; mail-complaints-to="usenet@ciao.gmane.io" Cc: emacs-devel@gnu.org To: JD Smith , Stefan Monnier Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Tue Jan 09 14:04:46 2024 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1rNBmg-0008nf-K1 for ged-emacs-devel@m.gmane-mx.org; Tue, 09 Jan 2024 14:04:46 +0100 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1rNBlr-0002h3-8K; Tue, 09 Jan 2024 08:03:55 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rNBln-0002TM-8i for emacs-devel@gnu.org; Tue, 09 Jan 2024 08:03:51 -0500 Original-Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rNBli-0001Vl-Fq; Tue, 09 Jan 2024 08:03:47 -0500 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=MIME-version:References:Subject:In-Reply-To:To:From: Date; bh=HYB4Fogwvjj2x3/JTwX7GMvYeNYZBGLrwXUktjJbKmg=; b=T8C/IcMBzHfHGiRfS9C3 pIYYAYqt95q3/soRSarvNgCqVD5F1BTJMI9FQaHjFSjVLCJWJbYkEpaLbnCPOcoLKOQ3nAFYd4fux a9F/ELskoigu+Rc9B9GnbVIRd31C4Z7zWzqIzfkzFTypzv47CS/Upzg3+vBVy0DSg/U2cNERBa6hE wPe2Rr7UCsz2Eez79r+f8Ehu35bLfPDRr2Uh8ixSTJO/bM/I33cPcVbLK/+bZXPCyJ2q4O6//KF/+ nksr5O2IT/MPwgsNgS8yDM81eEKOVMXPOqLy74eeb8pPwda17JN8BOzW+O3fIUJbqw7xF9utXDyFE fx4uqk2/kLmRug==; In-Reply-To: (message from JD Smith on Mon, 8 Jan 2024 16:49:21 -0500) X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.devel:314778 Archived-At: > From: JD Smith > 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.