unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: "João Távora" <joaotavora@gmail.com>
To: Stefan Monnier <monnier@iro.umontreal.ca>
Cc: Eli Zaretskii <eliz@gnu.org>,
	dalal.chinmay.0101@gmail.com, emacs-devel@gnu.org,
	dimitri@belopopsky.com, luangruo@yahoo.com
Subject: Re: Eglot "inlay hints" landed
Date: Thu, 23 Feb 2023 23:59:42 +0000	[thread overview]
Message-ID: <CALDnm51z5MdaZGZGwRvgq2Q1dNAjaLeEOHov212Zo=g6d3kHjQ@mail.gmail.com> (raw)
In-Reply-To: <jwv356wdobi.fsf-monnier+emacs@gnu.org>

On Thu, Feb 23, 2023 at 10:19 PM Stefan Monnier
<monnier@iro.umontreal.ca> wrote:
>
> > Mostly the fact that it's operating on a separate timer, but one
> > that is directly correlated to jit-lock-context-time.
>
> But that's because you're willing to wait for the context refresh to do
> your own.
>
> > So the bookkeeping and the coalescing of the small+large jit chunks
> > should be provided by the jit infrastructure instead.
>
> So far, you're the first to need such a thing.  In my experience the
> needs for "jit display refresh" can be fairly subtly different, so it's
> not clear how generally useful your approach would be.  Maybe we could
> provide some shared infrastructure to maintain a "coalescing set of
> buffer regions", but if so, I suspect that it wouldn't need to be tied
> to `jit-lock.el`.
>
> Also, I'm not sure it gives exactly the info you need/want:
> I suspect that in some languages you can have:
>
>    foo (x)
>    ...
>    function foo (bar : Int)

Yeah, in that case we're frobbed.  But isn't that a problem already
those "some languages" for regular "contextual" fontification?

> > And then no extra timer or logic would be needed.
>
> You mean you'd integrate it into `jit-lock-context`?
> Maybe that could be done.
> > Can we make jit-lock.el a :core ELPA package?
> I have no opinion on that.

That's the only reasonable way that Eglot can ever be changed
to make use of new jit-lock infrastructure.

Anyway, have a look at this patch:

This implementation is much simpler than the one based on
windows-scroll-functions.  It's also conceptually safer, since
jit-lock is guaranteed to refontify the regions that may need
redisplaying.

It's not trivially simple though, as simply adding
'eglot--update-hints-1' to jit-lock-functions, while possible, is
going to request inlay hints from the LSP server for every small
fraction changed by the user.  So we do some bookkeeping on regions
and coalesce them at a suitable time for a larger request.

To do this, we use a jit-lock implementation detail,
jit-lock-context-unfontify-pos, which tells us that the contextual
fontification has just finished.  Not sure how brittle it is, but it
seems to work reasonably.

* doc/misc/eglot.texi (Eglot Variables): Remove mention
to deleted eglot-lazy-inlay-hints.

* lisp/progmodes/eglot.el (eglot-lazy-inlay-hints)
(eglot--inlay-hints-after-scroll)
(eglot--inlay-hints-fully)
(eglot--inlay-hints-lazily): Remove.
(eglot--update-hints): Add function.
(eglot-inlay-hints-mode): Simplify.
---
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index eea8be6d1aa..9955b04574f 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -3489,32 +3489,38 @@ eglot-type-hint-face
 (defface eglot-parameter-hint-face '((t (:inherit eglot-inlay-hint-face)))
   "Face used for parameter inlay hint overlays.")

-(defcustom eglot-lazy-inlay-hints 0.3
-  "If non-nil, restrict LSP inlay hints to visible portion of the buffer.
-
-Value is a number specifying how many seconds to wait after a
-window has been (re)scrolled before requesting new inlay hints
-for the now-visible portion of the buffer shown in the window.
-
-If nil, then inlay hints are requested for the entire buffer.
-This could be slow.
-
-This value is only meaningful if the minor mode
-`eglot-inlay-hints-mode' is turned on in a buffer."
-  :type 'number
-  :version "29.1")
-
-(defun eglot--inlay-hints-fully ()
-  (eglot--widening (eglot--update-hints-1 (point-min) (point-max))))
-
-(cl-defun eglot--inlay-hints-lazily (&optional (buffer (current-buffer)))
-  (eglot--when-live-buffer buffer
-    (when eglot--managed-mode
-      (dolist (window (get-buffer-window-list nil nil 'visible))
-        (eglot--update-hints-1 (window-start window) (window-end window))))))
+(defvar-local eglot--outstanding-inlay-regions nil
+  "List of regions that weren't yet \"contextually\" fontified.")
+
+(defun eglot--update-hints (from to)
+  "Jit-lock function for Eglot inlay hints."
+  ;; HACK: Comparing `jit-lock-context-unfontify-pos' to `point-max'
+  ;; is our way of telling whether this call to `jit-lock-functions'
+  ;; happens after `jit-lock-context-timer' has just run.  This is the
+  ;; best time to coalesce the regions in
+  ;; `eglot--outstanding-inlay-regions' and contact the LSP server.  Any
+  ;; other call to this function just adds another region to that list.
+  (if (= jit-lock-context-unfontify-pos (point-max))
+      (let* ((sorted (sort (cons (cons from to)
+                                 eglot--outstanding-inlay-regions)
+                           (pcase-lambda (`(,a1 . ,a2) `(,b1 . ,b2))
+                             (if (= a1 b1) (< a2 b2) (< a1 b1)))))
+             (coalesced (cl-reduce (lambda (acc x)
+                                     (cond ((and acc (>= (cdr (car acc))
+                                                         (car x)))
+                                            (setcdr (car acc) (cdr x))
+                                            acc)
+                                           (t (push x acc) acc)))
+                                   sorted
+                                   :initial-value nil)))
+        (dolist (r coalesced)
+          (eglot--update-hints-1 (max (car r) (point-min))
+                                 (min (cdr r) (point-max))))
+        (setq eglot--outstanding-inlay-regions nil))
+    (push (cons from to) eglot--outstanding-inlay-regions)))

 (defun eglot--update-hints-1 (from to)
-  "Request LSP inlay hints and annotate current buffer from FROM to TO."
+  "Do most work for `eglot--update-hints', including LSP request."
   (let* ((buf (current-buffer))
          (paint-hint
           (eglot--lambda ((InlayHint) position kind label paddingLeft
paddingRight)
@@ -3545,67 +3551,16 @@ eglot--update-hints-1
                       (mapc paint-hint hints))))
      :deferred 'eglot--update-hints-1)))

-(defun eglot--inlay-hints-after-scroll (window display-start)
-  (cl-macrolet ((wsetq (sym val) `(set-window-parameter window ',sym ,val))
-                (wgetq (sym) `(window-parameter window ',sym)))
-    (let ((buf (window-buffer window))
-          (timer (wgetq eglot--inlay-hints-timer))
-          (last-display-start (wgetq eglot--last-inlay-hint-display-start)))
-      (when (and eglot-lazy-inlay-hints
-                 ;; FIXME: If `window' is _not_ the selected window,
-                 ;; then for some unknown reason probably related to
-                 ;; the overlays added later to the buffer, the scroll
-                 ;; function will be called indefinitely.  Not sure if
-                 ;; an Emacs bug, but prevent useless duplicate calls
-                 ;; by saving and examining `display-start' fixes it.
-                 (not (eql last-display-start display-start)))
-        (when timer (cancel-timer timer))
-        (wsetq eglot--last-inlay-hint-display-start
-               display-start)
-        (wsetq eglot--inlay-hints-timer
-               (run-at-time
-                eglot-lazy-inlay-hints
-                nil (lambda ()
-                      (eglot--when-live-buffer buf
-                        (when (eq buf (window-buffer window))
-                          (eglot--update-hints-1 (window-start window)
-                                                 (window-end window))
-                          (wsetq eglot--inlay-hints-timer nil))))))))))
-
-(defun eglot--inlay-hints-after-window-config-change ()
-  (eglot--update-hints-1 (window-start) (window-end)))
-
 (define-minor-mode eglot-inlay-hints-mode
   "Minor mode for annotating buffers with LSP server's inlay hints."
   :global nil
   (cond (eglot-inlay-hints-mode
-         (cond
-          ((not (eglot--server-capable :inlayHintProvider))
+         (if (eglot--server-capable :inlayHintProvider)
+             (jit-lock-register #'eglot--update-hints 'contextual)
            (eglot--warn
-            "No :inlayHintProvider support. Inlay hints will not work."))
-          (eglot-lazy-inlay-hints
-           (add-hook 'eglot--document-changed-hook
-                     #'eglot--inlay-hints-lazily t t)
-           (add-hook 'window-scroll-functions
-                     #'eglot--inlay-hints-after-scroll nil t)
-           (add-hook 'window-configuration-change-hook
-                     #'eglot--inlay-hints-after-window-config-change nil t)
-           ;; Maybe there isn't a window yet for current buffer,
-           ;; so `run-at-time' ensures this runs after redisplay.
-           (run-at-time 0 nil #'eglot--inlay-hints-lazily))
-          (t
-           (add-hook 'eglot--document-changed-hook
-                     #'eglot--inlay-hints-fully nil t)
-           (eglot--inlay-hints-fully))))
+            "No :inlayHintProvider support. Inlay hints will not work.")))
         (t
-         (remove-hook 'window-configuration-change-hook
-                      #'eglot--inlay-hints-after-window-config-change)
-         (remove-hook 'eglot--document-changed-hook
-                      #'eglot--inlay-hints-lazily t)
-         (remove-hook 'eglot--document-changed-hook
-                      #'eglot--inlay-hints-fully t)
-         (remove-hook 'window-scroll-functions
-                      #'eglot--inlay-hints-after-scroll t)
+         (jit-lock-unregister #'eglot--update-hints)
          (remove-overlays nil nil 'eglot--inlay-hint t))))



  reply	other threads:[~2023-02-23 23:59 UTC|newest]

Thread overview: 44+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <ypi9cz6ahi5n.fsf@gmail.com>
     [not found] ` <83edqqaf8c.fsf@gnu.org>
     [not found]   ` <2B284D77-97DF-4B3E-89FB-13F0CA93D240@gmail.com>
     [not found]     ` <CALDnm53otfeDQGr0dWWUhxGLTSuiWTstLXJz1HXQgWLiAgsk=A@mail.gmail.com>
     [not found]       ` <CA+46MXbbW60t=JccgKGX39jTkOu+i=GZhzSQsfnqBUPb-mnJWg@mail.gmail.com>
2023-02-22 19:42         ` Eglot "inlay hints" landed João Távora
2023-02-23  1:45           ` [SPAM UNSURE] " Stephen Leake
2023-02-23  5:29           ` Chinmay Dalal
2023-02-23  6:31             ` Eli Zaretskii
2023-02-23  9:55               ` Chinmay Dalal
2023-02-23 10:03                 ` João Távora
2023-02-23 10:55                   ` Dimitri Belopopsky
2023-02-23 11:07                     ` João Távora
2023-02-23 12:03                     ` João Távora
2023-02-23 13:25                       ` Dimitri Belopopsky
2023-02-23 11:05                 ` Eli Zaretskii
2023-02-23 11:23                   ` João Távora
2023-02-23 12:36                     ` Eli Zaretskii
2023-02-23 12:57                       ` João Távora
2023-02-23 14:48                         ` Eli Zaretskii
2023-02-23 16:09                           ` João Távora
2023-02-23 17:17                             ` Eli Zaretskii
2023-02-23 17:46                               ` João Távora
2023-02-23 18:01                                 ` Eli Zaretskii
2023-02-23 19:26                                   ` João Távora
2023-02-23 19:54                                     ` Eli Zaretskii
2023-02-23 20:03                                       ` João Távora
2023-02-23 19:27                                 ` Stefan Monnier
2023-02-23 19:39                                   ` João Távora
2023-02-23 19:53                                     ` Stefan Monnier
2023-02-23 20:09                                       ` João Távora
2023-02-23 22:19                                         ` Stefan Monnier
2023-02-23 23:59                                           ` João Távora [this message]
2023-02-24  1:08                                             ` Stefan Monnier
2023-02-24  2:28                                               ` João Távora
2023-02-24  7:35                                               ` Eli Zaretskii
2023-02-24 10:42                                                 ` João Távora
2023-02-24 11:33                                                   ` Eli Zaretskii
2023-02-24 12:26                                                     ` João Távora
2023-02-23 10:17           ` Tassilo Horn
2023-02-23 12:55           ` Chinmay Dalal
2023-02-23 19:50           ` Nikola Pajkovsky
2023-02-23 21:35             ` João Távora
2023-02-23 21:45               ` Nikola Pajkovsky
2023-02-24  4:20               ` Chinmay Dalal
2023-02-24  5:04                 ` Chinmay Dalal
2023-02-24  9:59                 ` João Távora
2023-02-24 11:03                   ` Nikola Pajkovsky
2023-02-27 22:50           ` Johann Klähn

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='CALDnm51z5MdaZGZGwRvgq2Q1dNAjaLeEOHov212Zo=g6d3kHjQ@mail.gmail.com' \
    --to=joaotavora@gmail.com \
    --cc=dalal.chinmay.0101@gmail.com \
    --cc=dimitri@belopopsky.com \
    --cc=eliz@gnu.org \
    --cc=emacs-devel@gnu.org \
    --cc=luangruo@yahoo.com \
    --cc=monnier@iro.umontreal.ca \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).