all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
* bug#61412: [PATCH] Add inlay hints to eglot
@ 2023-02-11  8:13 Chinmay Dalal
  2023-02-11 11:50 ` Chinmay Dalal
                   ` (8 more replies)
  0 siblings, 9 replies; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-11  8:13 UTC (permalink / raw)
  To: 61412; +Cc: Chinmay Dalal

Hello all, I have improved upon the patch from bug#61066.

From the original email:
> - I can't figure out a way to show the hints on a document without causing lags or timeouts from the lsp server
This might have been because they were sending an asynchronous request, waiting
for the response, collecting the hints and displaying them. I have instead used
`jsonrpc-async-request` with a handler function which should remedy this issue.
I have tried this with rust-analyzer and faced no lags.

The original patch also checked the wrong condition (`cond (eglot--managed-mode`)
in `eglot-inlay-mode` and had a superfluous `(run-hooks 'eglot-managed-mode-hook)`
(presumably copy-pasted from `define-minor-mode eglot--managed-mode`), I have
fixed that.

From a reply to the original email:
> AFAIU, inlay hints provide information of the same kind as ElDoc and
> in similar manner from the display and UX POV.  So I think this
> feature should work via ElDoc, not as a separate from-the-scratch
> implementation.
This can't be done via ElDoc because the purpose of inlay hints is to display
variable types and parameter names without moving the cursor to the location.
One can already see this information if they move their cursor to the variable
or function and wait for the "hover" - inlay hints were added to the spec after
this which (IMHO) clearly means something like overlays should be used.

A couple of issues at present:
1. I have not implemented "complex" hints (whose `label` is an array of
`InlayHintLabelPart` instead of a string) because I don't know what to do when
there are multiple labels at the same location.
rust-analyzer uses these hints to show a clickable link at the end of a function
block, which points to the beginning of the function.

2. I need to save the buffer or disable and re-enable `eglot-inlay-mode` to
get hints for the first time after opening a file, even though I call `eglot--update-hints`
once in `eglot-inlay-mode`.

Regards,
Chinmay Dalal
---
 lisp/progmodes/eglot.el | 70 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 69 insertions(+), 1 deletion(-)

diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 6caf589..0d5e63e 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -404,6 +404,10 @@ This can be useful when using docker to run a language server.")
 (when (assoc 'flex completion-styles-alist)
   (add-to-list 'completion-category-defaults '(eglot (styles flex basic))))
 
+(defcustom eglot-inlay-hints t
+  "If non-nil, enable inlay hints."
+  :type 'boolean)
+
 \f
 ;;; Constants
 ;;;
@@ -1624,7 +1628,8 @@ under cursor."
           (const :tag "Highlight links in document" :documentLinkProvider)
           (const :tag "Decorate color references" :colorProvider)
           (const :tag "Fold regions of buffer" :foldingRangeProvider)
-          (const :tag "Execute custom commands" :executeCommandProvider)))
+          (const :tag "Execute custom commands" :executeCommandProvider)
+          (const :tag "Inlay hints" :inlayHintProvider)))
 
 (defun eglot--server-capable (&rest feats)
   "Determine if current server is capable of FEATS."
@@ -1845,6 +1850,8 @@ If it is activated, also signal textDocument/didOpen."
     (when (and buffer-file-name (eglot-current-server))
       (setq eglot--diagnostics nil)
       (eglot--managed-mode)
+      (unless (not eglot-inlay-hints)
+        (eglot-inlay-mode))
       (eglot--signal-textDocument/didOpen))))
 
 (add-hook 'find-file-hook 'eglot--maybe-activate-editing-mode)
@@ -3456,6 +3463,67 @@ If NOERROR, return predicate, else erroring function."
       (revert-buffer)
       (pop-to-buffer (current-buffer)))))
 
+(defface eglot-inlay-hint
+  '((t (:height 0.8 :inherit shadow)))
+  "Face used for inlay hint overlays.")
+
+(define-minor-mode eglot-inlay-mode
+  "Mode for displaying inlay hints."
+  :lighter " inlay"
+  (if eglot-inlay-mode
+      (progn
+        (add-hook 'after-save-hook 'eglot--update-hints 0 t)
+        (eglot--update-hints))
+      (progn
+        (remove-hook 'after-save-hook 'eglot--update-hints t)
+        (eglot--remove-hints))))
+
+(defun eglot--inlay-handler (buffer hints)
+  "Apply vector of inlay hints HINTS on buffer BUFFER."
+  (seq-doseq (hint hints)
+    (let* ((position (plist-get hint :position))
+           (line (plist-get position :line))
+           (character (plist-get position :character))
+           (label (plist-get hint :label)))
+      (when (stringp label)
+        (with-current-buffer buffer
+          (eglot--widening
+            (goto-char (point-min))
+            (forward-line line)
+            (eglot-move-to-column character)
+            (let ((overlay (make-overlay (point) (point))))
+              (overlay-put overlay 'before-string (propertize
+                                                    (concat (if (plist-get hint :paddingLeft) " " "")
+                                                            label
+                                                            (if (plist-get hint :paddingRight) " " ""))
+                                                    'face 'eglot-inlay-hint))
+              (overlay-put overlay 'is-eglot-inlay-hint t))))))))
+
+(defun eglot--remove-hints ()
+  "Remove inlay hints from the buffer."
+  (remove-overlays nil nil 'is-eglot-inlay-hint t))
+
+(defun eglot--update-hints ()
+  "Request inlay hints for the current buffer and apply them."
+  (unless (eglot--server-capable :inlayHintProvider)
+    (eglot--error "This LSP server isn't an :inlayHintProvider"))
+  ;; Remove existing hints
+  (eglot--remove-hints)
+  (let ((buffer (current-buffer)))
+    (jsonrpc-async-request
+      (eglot--current-server-or-lose)
+      :textDocument/inlayHint
+      (list
+        :textDocument (eglot--TextDocumentIdentifier)
+        :range (list
+                :start (list :line 0 :character 0)
+                :end (list
+                      :line (count-lines (point-min) (point-max))
+                      :character 0)))
+      :success-fn (lambda (hints)
+                    (eglot--inlay-handler buffer hints))
+      :deferred t)))
+
 \f
 ;;; Hacks
 ;;;
-- 
2.39.1






^ permalink raw reply related	[flat|nested] 72+ messages in thread

* bug#61412: [PATCH] Add inlay hints to eglot
  2023-02-11  8:13 bug#61412: [PATCH] Add inlay hints to eglot Chinmay Dalal
@ 2023-02-11 11:50 ` Chinmay Dalal
  2023-02-11 15:23 ` bug#61412: Question about implementation Chinmay Dalal
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-11 11:50 UTC (permalink / raw)
  To: 61412

More about InlayHintPart:
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#inlayHintLabelPart





^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Question about implementation
  2023-02-11  8:13 bug#61412: [PATCH] Add inlay hints to eglot Chinmay Dalal
  2023-02-11 11:50 ` Chinmay Dalal
@ 2023-02-11 15:23 ` Chinmay Dalal
  2023-02-13 15:35 ` bug#61412: Hover github discussion Chinmay Dalal
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-11 15:23 UTC (permalink / raw)
  To: 61412


I currently send the request with the coordinates of the whole buffer on
save (while doing nothing on scroll), I was wondering if it would be
better or worse to send requests with coordinates of just the current
viewport on every scroll.





^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Hover github discussion
  2023-02-11  8:13 bug#61412: [PATCH] Add inlay hints to eglot Chinmay Dalal
  2023-02-11 11:50 ` Chinmay Dalal
  2023-02-11 15:23 ` bug#61412: Question about implementation Chinmay Dalal
@ 2023-02-13 15:35 ` Chinmay Dalal
  2023-02-15 13:58   ` Eli Zaretskii
  2023-02-15 12:56 ` bug#61412: [PATCH v2] Add inlay hints to eglot Chinmay Dalal
                   ` (5 subsequent siblings)
  8 siblings, 1 reply; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-13 15:35 UTC (permalink / raw)
  To: 61412


From the discussion on github
https://github.com/joaotavora/eglot/discussions/963

> The default version would be a hoverable symbol that shows something in the echo area when you mouse hover over it.


> Let's continue in the bug tracker discussion initiated by your bug report.
> As far as I understand, inlay hints will show the type of many visible expressions at once, so it's different from the "hover" scenario.

Yes, so it is impossible to use mouse hover for this as you can only
hover on one thing at once, right?





^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: [PATCH v2] Add inlay hints to eglot
  2023-02-11  8:13 bug#61412: [PATCH] Add inlay hints to eglot Chinmay Dalal
                   ` (2 preceding siblings ...)
  2023-02-13 15:35 ` bug#61412: Hover github discussion Chinmay Dalal
@ 2023-02-15 12:56 ` Chinmay Dalal
  2023-02-15 13:08 ` bug#61412: [PATCH v3] " Chinmay Dalal
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-15 12:56 UTC (permalink / raw)
  To: 61412; +Cc: Chinmay Dalal

This uses different faces based on the "kind" of the hint:
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#inlayHintKind

---
 lisp/progmodes/eglot.el | 85 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 84 insertions(+), 1 deletion(-)

diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 6caf589..101644f 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -404,6 +404,10 @@ This can be useful when using docker to run a language server.")
 (when (assoc 'flex completion-styles-alist)
   (add-to-list 'completion-category-defaults '(eglot (styles flex basic))))
 
+(defcustom eglot-inlay-hints t
+  "If non-nil, enable inlay hints."
+  :type 'boolean)
+
 \f
 ;;; Constants
 ;;;
@@ -1624,7 +1628,8 @@ under cursor."
           (const :tag "Highlight links in document" :documentLinkProvider)
           (const :tag "Decorate color references" :colorProvider)
           (const :tag "Fold regions of buffer" :foldingRangeProvider)
-          (const :tag "Execute custom commands" :executeCommandProvider)))
+          (const :tag "Execute custom commands" :executeCommandProvider)
+          (const :tag "Inlay hints" :inlayHintProvider)))
 
 (defun eglot--server-capable (&rest feats)
   "Determine if current server is capable of FEATS."
@@ -1845,6 +1850,8 @@ If it is activated, also signal textDocument/didOpen."
     (when (and buffer-file-name (eglot-current-server))
       (setq eglot--diagnostics nil)
       (eglot--managed-mode)
+      (unless (not eglot-inlay-hints)
+        (eglot-inlay-mode))
       (eglot--signal-textDocument/didOpen))))
 
 (add-hook 'find-file-hook 'eglot--maybe-activate-editing-mode)
@@ -3456,6 +3463,82 @@ If NOERROR, return predicate, else erroring function."
       (revert-buffer)
       (pop-to-buffer (current-buffer)))))
 
+(defface eglot-inlay-hint-face
+  '((t (:height 0.8 :inherit shadow)))
+  "Face used for inlay hint overlays.")
+
+(defface eglot-type-hint-face
+  '((t (:inherit eglot-inlay-hint)))
+  "Face used for type hints.")
+
+(defface eglot-parameter-hint-face
+  '((t (:inherit eglot-inlay-hint)))
+  "Face used for parameter hints.")
+
+(define-minor-mode eglot-inlay-mode
+  "Mode for displaying inlay hints."
+  :lighter " inlay"
+  (if eglot-inlay-mode
+      (progn
+        (add-hook 'after-save-hook 'eglot--update-hints 0 t)
+        (eglot--update-hints))
+      (progn
+        (remove-hook 'after-save-hook 'eglot--update-hints t)
+        (eglot--remove-hints))))
+
+(defun eglot--inlay-handler (buffer hints)
+  "Apply vector of inlay hints HINTS on buffer BUFFER."
+  (seq-doseq (hint hints)
+    (let* ((position (plist-get hint :position))
+           (line (plist-get position :line))
+           (character (plist-get position :character))
+           (kind (plist-get hint :kind))
+           (face (cond ((eq kind 1)
+                        'eglot-type-hint-face)
+                       ((eq kind 2)
+                        'eglot-parameter-hint-face)
+                       (t
+                        'eglot-inlay-hint-face)))
+           (label (plist-get hint :label)))
+      (when (stringp label)
+        (with-current-buffer buffer
+          (eglot--widening
+            (goto-char (point-min))
+            (forward-line line)
+            (eglot-move-to-column character)
+            (let ((overlay (make-overlay (point) (point))))
+              (overlay-put overlay 'before-string (propertize
+                                                    (concat (if (plist-get hint :paddingLeft) " " "")
+                                                            label
+                                                            (if (plist-get hint :paddingRight) " " ""))
+                                                    'face face))
+              (overlay-put overlay 'is-eglot-inlay-hint t))))))))
+
+(defun eglot--remove-hints ()
+  "Remove inlay hints from the buffer."
+  (remove-overlays nil nil 'is-eglot-inlay-hint t))
+
+(defun eglot--update-hints ()
+  "Request inlay hints for the current buffer and apply them."
+  (unless (eglot--server-capable :inlayHintProvider)
+    (eglot--error "This LSP server isn't an :inlayHintProvider"))
+  ;; Remove existing hints
+  (eglot--remove-hints)
+  (let ((buffer (current-buffer)))
+    (jsonrpc-async-request
+      (eglot--current-server-or-lose)
+      :textDocument/inlayHint
+      (list
+        :textDocument (eglot--TextDocumentIdentifier)
+        :range (list
+                :start (list :line 0 :character 0)
+                :end (list
+                      :line (count-lines (point-min) (point-max))
+                      :character 0)))
+      :success-fn (lambda (hints)
+                    (eglot--inlay-handler buffer hints))
+      :deferred t)))
+
 \f
 ;;; Hacks
 ;;;
-- 
2.39.1






^ permalink raw reply related	[flat|nested] 72+ messages in thread

* bug#61412: [PATCH v3] Add inlay hints to eglot
  2023-02-11  8:13 bug#61412: [PATCH] Add inlay hints to eglot Chinmay Dalal
                   ` (3 preceding siblings ...)
  2023-02-15 12:56 ` bug#61412: [PATCH v2] Add inlay hints to eglot Chinmay Dalal
@ 2023-02-15 13:08 ` Chinmay Dalal
  2023-02-15 16:24 ` bug#61412: Inlay activation Chinmay Dalal
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-15 13:08 UTC (permalink / raw)
  To: 61412

[-- Attachment #1: Type: text/plain, Size: 88 bytes --]


This adds the "-face" suffix to the `:inherit` definitions of the new
kind-based faces

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-inlay-hints-to-eglot.patch --]
[-- Type: text/x-patch, Size: 7136 bytes --]

From 3092a1d3828985857298ead604f44c67737550ef Mon Sep 17 00:00:00 2001
From: Chinmay Dalal <dalal.chinmay.0101@gmail.com>
Date: Sat, 11 Feb 2023 10:00:20 +0530
Subject: [PATCH] Add inlay hints to eglot

Hello all, I have improved upon the patch from bug#61066.

From the original email:
> - I can't figure out a way to show the hints on a document without causing lags or timeouts from the lsp server
This might have been because they were sending an asynchronous request, waiting
for the response, collecting the hints and displaying them. I have instead used
`jsonrpc-async-request` with a handler function which should remedy this issue.
I have tried this with rust-analyzer and faced no lags.

The original patch also checked the wrong condition (`cond (eglot--managed-mode`)
in `eglot-inlay-mode` and had a superfluous `(run-hooks 'eglot-managed-mode-hook)`
(presumably copy-pasted from `define-minor-mode eglot--managed-mode`), I have
fixed that.

From a reply to the original email:
> AFAIU, inlay hints provide information of the same kind as ElDoc and
> in similar manner from the display and UX POV.  So I think this
> feature should work via ElDoc, not as a separate from-the-scratch
> implementation.
This can't be done via ElDoc because the purpose of inlay hints is to display
variable types and parameter names without moving the cursor to the location.
One can already see this information if they move their cursor to the variable
or function and wait for the "hover" - inlay hints were added to the spec after
this which (IMHO) clearly means something like overlays should be used.

A couple of issues at present:
1. I have not implemented "complex" hints (whose `label` is an array of
`InlayHintLabelPart` instead of a string) because I don't know what to do when
there are multiple labels at the same location.
rust-analyzer uses these hints to show a clickable link at the end of a function
block, which points to the beginning of the function.

2. I need to save the buffer or disable and re-enable `eglot-inlay-mode` to
get hints for the first time after opening a file, even though I call `eglot--update-hints`
once in `eglot-inlay-mode`.

Regards,
Chinmay Dalal
---
This uses different faces based on the "kind" of the hint:
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#inlayHintKind
---
This adds the "-face" suffix to the `:inherit` definitions of the new kind-based faces
---
 lisp/progmodes/eglot.el | 85 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 84 insertions(+), 1 deletion(-)

diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 6caf589..d3fb329 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -404,6 +404,10 @@ This can be useful when using docker to run a language server.")
 (when (assoc 'flex completion-styles-alist)
   (add-to-list 'completion-category-defaults '(eglot (styles flex basic))))
 
+(defcustom eglot-inlay-hints t
+  "If non-nil, enable inlay hints."
+  :type 'boolean)
+
 \f
 ;;; Constants
 ;;;
@@ -1624,7 +1628,8 @@ under cursor."
           (const :tag "Highlight links in document" :documentLinkProvider)
           (const :tag "Decorate color references" :colorProvider)
           (const :tag "Fold regions of buffer" :foldingRangeProvider)
-          (const :tag "Execute custom commands" :executeCommandProvider)))
+          (const :tag "Execute custom commands" :executeCommandProvider)
+          (const :tag "Inlay hints" :inlayHintProvider)))
 
 (defun eglot--server-capable (&rest feats)
   "Determine if current server is capable of FEATS."
@@ -1845,6 +1850,8 @@ If it is activated, also signal textDocument/didOpen."
     (when (and buffer-file-name (eglot-current-server))
       (setq eglot--diagnostics nil)
       (eglot--managed-mode)
+      (unless (not eglot-inlay-hints)
+        (eglot-inlay-mode))
       (eglot--signal-textDocument/didOpen))))
 
 (add-hook 'find-file-hook 'eglot--maybe-activate-editing-mode)
@@ -3456,6 +3463,82 @@ If NOERROR, return predicate, else erroring function."
       (revert-buffer)
       (pop-to-buffer (current-buffer)))))
 
+(defface eglot-inlay-hint-face
+  '((t (:height 0.8 :inherit shadow)))
+  "Face used for inlay hint overlays.")
+
+(defface eglot-type-hint-face
+  '((t (:inherit eglot-inlay-hint-face)))
+  "Face used for type hints.")
+
+(defface eglot-parameter-hint-face
+  '((t (:inherit eglot-inlay-hint-face)))
+  "Face used for parameter hints.")
+
+(define-minor-mode eglot-inlay-mode
+  "Mode for displaying inlay hints."
+  :lighter " inlay"
+  (if eglot-inlay-mode
+      (progn
+        (add-hook 'after-save-hook 'eglot--update-hints 0 t)
+        (eglot--update-hints))
+      (progn
+        (remove-hook 'after-save-hook 'eglot--update-hints t)
+        (eglot--remove-hints))))
+
+(defun eglot--inlay-handler (buffer hints)
+  "Apply vector of inlay hints HINTS on buffer BUFFER."
+  (seq-doseq (hint hints)
+    (let* ((position (plist-get hint :position))
+           (line (plist-get position :line))
+           (character (plist-get position :character))
+           (kind (plist-get hint :kind))
+           (face (cond ((eq kind 1)
+                        'eglot-type-hint-face)
+                       ((eq kind 2)
+                        'eglot-parameter-hint-face)
+                       (t
+                        'eglot-inlay-hint-face)))
+           (label (plist-get hint :label)))
+      (when (stringp label)
+        (with-current-buffer buffer
+          (eglot--widening
+            (goto-char (point-min))
+            (forward-line line)
+            (eglot-move-to-column character)
+            (let ((overlay (make-overlay (point) (point))))
+              (overlay-put overlay 'before-string (propertize
+                                                    (concat (if (plist-get hint :paddingLeft) " " "")
+                                                            label
+                                                            (if (plist-get hint :paddingRight) " " ""))
+                                                    'face face))
+              (overlay-put overlay 'is-eglot-inlay-hint t))))))))
+
+(defun eglot--remove-hints ()
+  "Remove inlay hints from the buffer."
+  (remove-overlays nil nil 'is-eglot-inlay-hint t))
+
+(defun eglot--update-hints ()
+  "Request inlay hints for the current buffer and apply them."
+  (unless (eglot--server-capable :inlayHintProvider)
+    (eglot--error "This LSP server isn't an :inlayHintProvider"))
+  ;; Remove existing hints
+  (eglot--remove-hints)
+  (let ((buffer (current-buffer)))
+    (jsonrpc-async-request
+      (eglot--current-server-or-lose)
+      :textDocument/inlayHint
+      (list
+        :textDocument (eglot--TextDocumentIdentifier)
+        :range (list
+                :start (list :line 0 :character 0)
+                :end (list
+                      :line (count-lines (point-min) (point-max))
+                      :character 0)))
+      :success-fn (lambda (hints)
+                    (eglot--inlay-handler buffer hints))
+      :deferred t)))
+
 \f
 ;;; Hacks
 ;;;
-- 
2.39.1


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* bug#61412: Hover github discussion
  2023-02-13 15:35 ` bug#61412: Hover github discussion Chinmay Dalal
@ 2023-02-15 13:58   ` Eli Zaretskii
  2023-02-15 15:05     ` João Távora
  0 siblings, 1 reply; 72+ messages in thread
From: Eli Zaretskii @ 2023-02-15 13:58 UTC (permalink / raw)
  To: Chinmay Dalal, João Távora; +Cc: 61412

> From: Chinmay Dalal <dalal.chinmay.0101@gmail.com>
> Date: Mon, 13 Feb 2023 21:05:12 +0530
> 
> 
> From the discussion on github
> https://github.com/joaotavora/eglot/discussions/963
> 
> > The default version would be a hoverable symbol that shows something in the echo area when you mouse hover over it.
> 
> 
> > Let's continue in the bug tracker discussion initiated by your bug report.
> > As far as I understand, inlay hints will show the type of many visible expressions at once, so it's different from the "hover" scenario.
> 
> Yes, so it is impossible to use mouse hover for this as you can only
> hover on one thing at once, right?

João, could you please look into this?





^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Hover github discussion
  2023-02-15 13:58   ` Eli Zaretskii
@ 2023-02-15 15:05     ` João Távora
  2023-02-15 15:38       ` Eli Zaretskii
  0 siblings, 1 reply; 72+ messages in thread
From: João Távora @ 2023-02-15 15:05 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Chinmay Dalal, 61412

[-- Attachment #1: Type: text/plain, Size: 2033 bytes --]

On Wed, Feb 15, 2023 at 1:58 PM Eli Zaretskii <eliz@gnu.org> wrote:

> > From: Chinmay Dalal <dalal.chinmay.0101@gmail.com>
> > Date: Mon, 13 Feb 2023 21:05:12 +0530
> >
> >
> > From the discussion on github
> > https://github.com/joaotavora/eglot/discussions/963
> >
> > > The default version would be a hoverable symbol that shows something
> in the echo area when you mouse hover over it.
> >
> >
> > > Let's continue in the bug tracker discussion initiated by your bug
> report.
> > > As far as I understand, inlay hints will show the type of many visible
> expressions at once, so it's different from the "hover" scenario.
> >
> > Yes, so it is impossible to use mouse hover for this as you can only
> > hover on one thing at once, right?
>

Yes.  I haven't  looked at your code, but my expectation is for inlay hints
to
activate either on a timer after each change, or by the explicit request of
the user via a keybinding.

It simply doesn't make sense to use mouse hover for this because the
hover mechanic is for a single text and inlay hints are about overlaying
multiple pieces of information in multiple locations of the buffer.

João, could you please look into this?
>

Hi Eli, this is about the "inlay hints" feature.

It's on the radar, but I don't have time in the very near future
to handle this.

I seem to recall that recently you voiced some opinion in another
bug report that this feature could be handled by a separate library
instead of a custom Eglot-only implementation.  This could be
quite reasonable -- or it could  be overkill/overengeering.  Only a look
at the pros and cons of this implementation can tell that, and I haven't
had time, sorry.

Anyway, if there is indeed a separate discussion for this, then the
two discussions should be merged.  Be sure to keep me in the loop
if you can as I am overwhelmed with other activies in the moment
and am not scanning the bug-tracker, or even emacs-devel, for
Eglot-related emails at the moment.

João

[-- Attachment #2: Type: text/html, Size: 3028 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Hover github discussion
  2023-02-15 15:05     ` João Távora
@ 2023-02-15 15:38       ` Eli Zaretskii
  0 siblings, 0 replies; 72+ messages in thread
From: Eli Zaretskii @ 2023-02-15 15:38 UTC (permalink / raw)
  To: João Távora; +Cc: dalal.chinmay.0101, 61412

> From: João Távora <joaotavora@gmail.com>
> Date: Wed, 15 Feb 2023 15:05:13 +0000
> Cc: Chinmay Dalal <dalal.chinmay.0101@gmail.com>, 61412@debbugs.gnu.org
> 
> Yes.  I haven't  looked at your code, but my expectation is for inlay hints to
> activate either on a timer after each change, or by the explicit request of
> the user via a keybinding.
> 
> It simply doesn't make sense to use mouse hover for this because the
> hover mechanic is for a single text and inlay hints are about overlaying
> multiple pieces of information in multiple locations of the buffer.
> 
>  João, could you please look into this?
> 
> Hi Eli, this is about the "inlay hints" feature. 
> 
> It's on the radar, but I don't have time in the very near future
> to handle this.

That's okay, as long as this is not forgotten.

> I seem to recall that recently you voiced some opinion in another
> bug report that this feature could be handled by a separate library
> instead of a custom Eglot-only implementation.  This could be 
> quite reasonable -- or it could  be overkill/overengeering.  Only a look 
> at the pros and cons of this implementation can tell that, and I haven't
> had time, sorry.

AFAIR, you and others considered my proposal (to use this via ElDoc)
as not the best idea, for various technical reasons.  So I no longer
insist on my proposal.





^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Inlay activation
  2023-02-11  8:13 bug#61412: [PATCH] Add inlay hints to eglot Chinmay Dalal
                   ` (4 preceding siblings ...)
  2023-02-15 13:08 ` bug#61412: [PATCH v3] " Chinmay Dalal
@ 2023-02-15 16:24 ` Chinmay Dalal
  2023-02-15 18:09   ` Eli Zaretskii
  2023-02-21 15:33 ` bug#61412: Inlay hints implementation Chinmay Dalal
                   ` (2 subsequent siblings)
  8 siblings, 1 reply; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-15 16:24 UTC (permalink / raw)
  To: 61412; +Cc: joaotavora


> Yes.  I haven't  looked at your code, but my expectation is for inlay hints to
> activate either on a timer after each change, or by the explicit request of
> the user via a keybinding.

My code updates inlay hints on every save (`after-save-hook`).

Regarding keybinding, on the github discussion someone suggested that
vscode shows them while a key combination is held and hides them when
they are released, but I was told emacs can't do this.

Regarding timer, personally I find it annoying when lsp-mode starts
showing overlays when I'm not done typing a line, which is why I chose
after-save-hook. For me it would be best if they were updated every time
I pressed enter, but I don't know how to implement that.





^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Inlay activation
  2023-02-15 16:24 ` bug#61412: Inlay activation Chinmay Dalal
@ 2023-02-15 18:09   ` Eli Zaretskii
  2023-02-15 18:48     ` Chinmay Dalal
  0 siblings, 1 reply; 72+ messages in thread
From: Eli Zaretskii @ 2023-02-15 18:09 UTC (permalink / raw)
  To: Chinmay Dalal; +Cc: joaotavora, 61412

> Cc: joaotavora@gmail.com
> From: Chinmay Dalal <dalal.chinmay.0101@gmail.com>
> Date: Wed, 15 Feb 2023 21:54:57 +0530
> 
> Regarding keybinding, on the github discussion someone suggested that
> vscode shows them while a key combination is held and hides them when
> they are released, but I was told emacs can't do this.

Not only can we not do this, it is also not our style.  If we want to
show hints, we either show tooltips, or display a message in the echo
area.





^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Inlay activation
  2023-02-15 18:09   ` Eli Zaretskii
@ 2023-02-15 18:48     ` Chinmay Dalal
  2023-02-15 19:01       ` João Távora
  2023-02-15 19:41       ` Eli Zaretskii
  0 siblings, 2 replies; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-15 18:48 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: joaotavora, 61412

[-- Attachment #1: Type: text/plain, Size: 774 bytes --]


Eli Zaretskii <eliz@gnu.org> writes:

>> Cc: joaotavora@gmail.com
>> From: Chinmay Dalal <dalal.chinmay.0101@gmail.com>
>> Date: Wed, 15 Feb 2023 21:54:57 +0530
>> 
>> Regarding keybinding, on the github discussion someone suggested that
>> vscode shows them while a key combination is held and hides them when
>> they are released, but I was told emacs can't do this.
>
> Not only can we not do this, it is also not our style.  If we want to
> show hints, we either show tooltips, or display a message in the echo
> area.

Tooltips and echo area can't be used as inlay hints are for showing
types of multiple variables/expressions and names of parameters at once.

See the attached screenshots for an example (this is from my
implementation in this patch)


C++ (clangd):

[-- Attachment #2: clangd_inlay.png --]
[-- Type: image/png, Size: 69546 bytes --]

[-- Attachment #3: Type: text/plain, Size: 23 bytes --]


Rust (rust-analyzer):

[-- Attachment #4: ra_inlay.png --]
[-- Type: image/png, Size: 158265 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Inlay activation
  2023-02-15 18:48     ` Chinmay Dalal
@ 2023-02-15 19:01       ` João Távora
  2023-02-15 19:17         ` Chinmay Dalal
  2023-02-15 19:41       ` Eli Zaretskii
  1 sibling, 1 reply; 72+ messages in thread
From: João Távora @ 2023-02-15 19:01 UTC (permalink / raw)
  To: Chinmay Dalal; +Cc: Eli Zaretskii, 61412

[-- Attachment #1: Type: text/plain, Size: 1432 bytes --]

On Wed, Feb 15, 2023 at 6:56 PM Chinmay Dalal <dalal.chinmay.0101@gmail.com>
wrote:

>
> Eli Zaretskii <eliz@gnu.org> writes:
>
> >> Cc: joaotavora@gmail.com
> >> From: Chinmay Dalal <dalal.chinmay.0101@gmail.com>
> >> Date: Wed, 15 Feb 2023 21:54:57 +0530
> >>
> >> Regarding keybinding, on the github discussion someone suggested that
> >> vscode shows them while a key combination is held and hides them when
> >> they are released, but I was told emacs can't do this.
> >
> > Not only can we not do this, it is also not our style.  If we want to
> > show hints, we either show tooltips, or display a message in the echo
> > area.
>
> Tooltips and echo area can't be used as inlay hints are for showing
> types of multiple variables/expressions and names of parameters at once.
>
> See the attached screenshots for an example (this is from my
> implementation in this patch)
>

Yes, I was going to state the same thing. Hopefully your
screenshots illustrate why ElDoc, at least using its
its display methods available at the moment, is not
the best fit for this.

This is a very useful feature if done correctly, and I'm
leaning towards your approach of doing this completely
in eglot.el, so your code is an excellent start.

But the activation and other details have to be carefully
considered.  I will try to look into this soon.

Do you have a FSF copyright assignment, Chinmay?

João

[-- Attachment #2: Type: text/html, Size: 2224 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Inlay activation
  2023-02-15 19:01       ` João Távora
@ 2023-02-15 19:17         ` Chinmay Dalal
  0 siblings, 0 replies; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-15 19:17 UTC (permalink / raw)
  To: João Távora; +Cc: Eli Zaretskii, 61412


João Távora <joaotavora@gmail.com> writes:

> But the activation and other details have to be carefully 
> considered.  I will try to look into this soon.

I agree, can you also look into
https://mail.gnu.org/archive/html/bug-gnu-emacs/2023-02/msg00782.html

> Do you have a FSF copyright assignment, Chinmay?

I do not have one.


Chinmay





^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Inlay activation
  2023-02-15 18:48     ` Chinmay Dalal
  2023-02-15 19:01       ` João Távora
@ 2023-02-15 19:41       ` Eli Zaretskii
  2023-02-15 20:17         ` Chinmay Dalal
  1 sibling, 1 reply; 72+ messages in thread
From: Eli Zaretskii @ 2023-02-15 19:41 UTC (permalink / raw)
  To: Chinmay Dalal; +Cc: joaotavora, 61412

> From: Chinmay Dalal <dalal.chinmay.0101@gmail.com>
> Cc: 61412@debbugs.gnu.org, joaotavora@gmail.com
> Date: Thu, 16 Feb 2023 00:18:41 +0530
> 
> > Not only can we not do this, it is also not our style.  If we want to
> > show hints, we either show tooltips, or display a message in the echo
> > area.
> 
> Tooltips and echo area can't be used as inlay hints are for showing
> types of multiple variables/expressions and names of parameters at once.
> 
> See the attached screenshots for an example (this is from my
> implementation in this patch)

I'm not surer I understand what you are showing here (so feel free to
say a few more words), but if I should guess, I'd say use overlay
strings.





^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Inlay activation
  2023-02-15 19:41       ` Eli Zaretskii
@ 2023-02-15 20:17         ` Chinmay Dalal
  2023-02-21 15:13           ` bug#61412: Inlay hints implementation João Távora
  0 siblings, 1 reply; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-15 20:17 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: joaotavora, 61412

On 16 February 2023 1:11:23 am IST, Eli Zaretskii <eliz@gnu.org> wrote:
>> From: Chinmay Dalal <dalal.chinmay.0101@gmail.com>
>> Cc: 61412@debbugs.gnu.org, joaotavora@gmail.com
>> Date: Thu, 16 Feb 2023 00:18:41 +0530
>> 
>> > Not only can we not do this, it is also not our style.  If we want to
>> > show hints, we either show tooltips, or display a message in the echo
>> > area.
>> 
>> Tooltips and echo area can't be used as inlay hints are for showing
>> types of multiple variables/expressions and names of parameters at once.
>> 
>> See the attached screenshots for an example (this is from my
>> implementation in this patch)
>
>I'm not surer I understand what you are showing here (so feel free to
>say a few more words), but if I should guess, I'd say use overlay
>strings.

Yes, I am using overlay strings

Apologies if the formatting is messed up, I sent this from my phone





^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Inlay hints implementation
  2023-02-15 20:17         ` Chinmay Dalal
@ 2023-02-21 15:13           ` João Távora
  2023-02-21 15:21             ` Eli Zaretskii
  2023-02-21 18:42             ` Dimitri Belopopsky
  0 siblings, 2 replies; 72+ messages in thread
From: João Távora @ 2023-02-21 15:13 UTC (permalink / raw)
  To: Chinmay Dalal, Dimitri Belopopsky, Po Lu; +Cc: Eli Zaretskii, 61412

[-- Attachment #1: Type: text/plain, Size: 1552 bytes --]

[Eli, I just noticed that this bug should be merged with 61066,
but I don't know how to do that]

Hello,

Attached is my implementation of inlay hints for Eglot.
Two patches are provided, where the first just lays some
basic groundwork to make the actual inlay hint implementation
simpler.

Regarding copyright stuff, I did look at Chinmay's patch,
but I re-started from scratch.  While it was a very good
effort, there were too many idiomatic Elisp and Eglot things
to change.  I did take Chinmay's face definitions, though.
Not sure how to proceed here and if this counts as "derived
work" and if we should wait for Chinmay's copyright assignment.

I gave it some light testing and I kind of like it.  Quite
helpful for C++ with clangd (the only server I tested it with).
You can bind `eglot-inlay-hint-mode` to some keybinding probably.

Documentation in the manual is still missing, but shouldn't
be very hard to do.

Anyway, this is likely not the end of the inlay hint story
because, as the second patch documents, this is likely a
very naive implementation that always requests inlay hints
for the entire buffer even if just a fraction of it is visible.

A better implementation would probably leverage
window-scroll-functions along with the Eglot-specific
idle timer.  That is probably much, much more tricky to get
right, but is also more than likely the way to go.

In the meantime, I'd like your opinion on this patch and
the above topics first.

João

I hope gmail doesn't mess up my attachments...

[-- Attachment #2: 0001-Eglot-simplify-capability-checking-code.patch --]
[-- Type: application/octet-stream, Size: 5051 bytes --]

From cacedd67873bfb094d12c08fc23e017fa4de5df1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= <joaotavora@gmail.com>
Date: Tue, 21 Feb 2023 13:59:04 +0000
Subject: [PATCH 1/2] Eglot: simplify capability-checking code

* lisp/progmodes/eglot.el (eglot--server-capable-or-lose): New helper.
(eglot--signal-textDocument/willSave)
(eglot--signal-textDocument/didSave): Tweak docstring.
(eglot--workspace-symbols, xref-backend-identifier-at-point)
(eglot-format, eglot-completion-at-point, eglot-rename)
(eglot-code-actions): Use new eglot--server-capable-or-lose.
---
 lisp/progmodes/eglot.el | 37 +++++++++++++++++++------------------
 1 file changed, 19 insertions(+), 18 deletions(-)

diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 82401b685ce..45f00daca1f 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -1641,6 +1641,13 @@ eglot--server-capable
              if (not (listp (cadr probe))) do (cl-return (if more nil (cadr probe)))
              finally (cl-return (or (cadr probe) t)))))
 
+(defun eglot--server-capable-or-lose (&rest feats)
+  "Like `eglot--server-capable', but maybe error out."
+  (let ((retval (apply #'eglot--server-capable feats)))
+    (unless retval
+      (eglot--error "This LSP server isn't capable of %s" feats))
+    retval))
+
 (defun eglot--range-region (range &optional markers)
   "Return region (BEG . END) that represents LSP RANGE.
 If optional MARKERS, make markers."
@@ -2482,7 +2489,7 @@ eglot--signal-textDocument/didClose
      :textDocument/didClose `(:textDocument ,(eglot--TextDocumentIdentifier)))))
 
 (defun eglot--signal-textDocument/willSave ()
-  "Send textDocument/willSave to server."
+  "Maybe send textDocument/willSave to server."
   (let ((server (eglot--current-server-or-lose))
         (params `(:reason 1 :textDocument ,(eglot--TextDocumentIdentifier))))
     (when (eglot--server-capable :textDocumentSync :willSave)
@@ -2494,7 +2501,7 @@ eglot--signal-textDocument/willSave
                           :timeout 0.5))))))
 
 (defun eglot--signal-textDocument/didSave ()
-  "Send textDocument/didSave to server."
+  "Maybe send textDocument/didSave to server."
   (eglot--signal-textDocument/didChange)
   (when (eglot--server-capable :textDocumentSync :save)
     (jsonrpc-notify
@@ -2591,8 +2598,7 @@ eglot--workspace-symbols
   "Ask for :workspace/symbol on PAT, return list of formatted strings.
 If BUFFER, switch to it before."
   (with-current-buffer (or buffer (current-buffer))
-    (unless (eglot--server-capable :workspaceSymbolProvider)
-      (eglot--error "This LSP server isn't a :workspaceSymbolProvider"))
+    (eglot--server-capable-or-lose :workspaceSymbolProvider)
     (mapcar
      (lambda (wss)
        (eglot--dbind ((WorkspaceSymbol) name containerName kind) wss
@@ -2654,13 +2660,12 @@ eglot--lsp-xref-refs
 
 (cl-defun eglot--lsp-xrefs-for-method (method &key extra-params capability)
   "Make `xref''s for METHOD, EXTRA-PARAMS, check CAPABILITY."
-  (unless (eglot--server-capable
-           (or capability
-               (intern
-                (format ":%sProvider"
-                        (cadr (split-string (symbol-name method)
-                                            "/"))))))
-    (eglot--error "Sorry, this server doesn't do %s" method))
+  (eglot--server-capable-or-lose
+   (or capability
+       (intern
+        (format ":%sProvider"
+                (cadr (split-string (symbol-name method)
+                                    "/"))))))
   (let ((response
          (jsonrpc-request
           (eglot--current-server-or-lose)
@@ -2757,8 +2762,7 @@ eglot-format
                                   :end (eglot--pos-to-lsp-position end)))))
                 (t
                  '(:textDocument/formatting :documentFormattingProvider nil)))))
-    (unless (eglot--server-capable cap)
-      (eglot--error "Server can't format!"))
+    (eglot--server-capable-or-lose cap)
     (eglot--apply-text-edits
      (jsonrpc-request
       (eglot--current-server-or-lose)
@@ -3202,8 +3206,7 @@ eglot-rename
                                          "unknown symbol"))
           nil nil nil nil
           (symbol-name (symbol-at-point)))))
-  (unless (eglot--server-capable :renameProvider)
-    (eglot--error "Server can't rename!"))
+  (eglot--server-capable-or-lose :renameProvider)
   (eglot--apply-workspace-edit
    (jsonrpc-request (eglot--current-server-or-lose)
                     :textDocument/rename `(,@(eglot--TextDocumentPositionParams)
@@ -3230,9 +3233,7 @@ eglot-code-actions
                             '("quickfix" "refactor.extract" "refactor.inline"
                               "refactor.rewrite" "source.organizeImports")))
      t))
-  (unless (or (not interactive)
-              (eglot--server-capable :codeActionProvider))
-    (eglot--error "Server can't execute code actions!"))
+  (eglot--server-capable-or-lose :codeActionProvider)
   (let* ((server (eglot--current-server-or-lose))
          (actions
           (jsonrpc-request
-- 
2.36.1.windows.1


[-- Attachment #3: 0002-Eglot-implement-inlay-hints-bug-61412.patch --]
[-- Type: application/octet-stream, Size: 6591 bytes --]

From fec508d6a8cab778649623d3bbb2954162b4c625 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= <joaotavora@gmail.com>
Date: Tue, 21 Feb 2023 14:14:05 +0000
Subject: [PATCH 2/2] Eglot: implement inlay hints (bug#61412)

This implementation is probably too slow for complex situations and
big files since it requests inlay hints for the whole buffer as which
can possibly mean a large amount of data.

A better implementation will be smart enough to only request inlay
hints for the visible parts of the buffer.

* lisp/progmodes/eglot.el (eglot--lsp-interface-alist): Define
InlayHint.
(eglot-client-capabilities): Announce 'inlayHint' capability.
(eglot-ignored-server-capabilities): Add :inlayHintProvider.
(eglot--document-changed-hook): New helper hook.
(eglot--after-change): Use it.
(eglot-inlay-hint-face, eglot-type-hint-face)
(eglot-parameter-hint-face): New faces.
(eglot--update-hints, eglot--paint-hint): New helpers.
(eglot-inlay-hints-mode): New minor mode.
---
 lisp/progmodes/eglot.el | 71 +++++++++++++++++++++++++++++++++++++++--
 1 file changed, 68 insertions(+), 3 deletions(-)

diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 45f00daca1f..c0c5dea29be 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -483,7 +483,9 @@ eglot--executable-find
       (VersionedTextDocumentIdentifier (:uri :version) ())
       (WorkDoneProgress (:kind) (:title :message :percentage :cancellable))
       (WorkspaceEdit () (:changes :documentChanges))
-      (WorkspaceSymbol (:name :kind) (:containerName :location :data)))
+      (WorkspaceSymbol (:name :kind) (:containerName :location :data))
+      (InlayHint (:position :label) (:kind :textEdits :tooltip :paddingLeft
+                                           :paddingRight :data)))
     "Alist (INTERFACE-NAME . INTERFACE) of known external LSP interfaces.
 
 INTERFACE-NAME is a symbol designated by the spec as
@@ -803,6 +805,7 @@ eglot-client-capabilities
              :formatting         `(:dynamicRegistration :json-false)
              :rangeFormatting    `(:dynamicRegistration :json-false)
              :rename             `(:dynamicRegistration :json-false)
+             :inlayHint          `(:dynamicRegistration :json-false)
              :publishDiagnostics (list :relatedInformation :json-false
                                        ;; TODO: We can support :codeDescription after
                                        ;; adding an appropriate UI to
@@ -1625,7 +1628,8 @@ eglot-ignored-server-capabilities
           (const :tag "Highlight links in document" :documentLinkProvider)
           (const :tag "Decorate color references" :colorProvider)
           (const :tag "Fold regions of buffer" :foldingRangeProvider)
-          (const :tag "Execute custom commands" :executeCommandProvider)))
+          (const :tag "Execute custom commands" :executeCommandProvider)
+          (const :tag "Inlay hints" :inlayHintProvider)))
 
 (defun eglot--server-capable (&rest feats)
   "Determine if current server is capable of FEATS."
@@ -2291,6 +2295,9 @@ eglot--before-change
             (,end . ,(copy-marker end t)))
           eglot--recent-changes)))
 
+(defvar eglot--document-changed-hook '(eglot--signal-textDocument/didChange)
+  "Internal hook for doing things when the document changes.")
+
 (defun eglot--after-change (beg end pre-change-length)
   "Hook onto `after-change-functions'.
 Records BEG, END and PRE-CHANGE-LENGTH locally."
@@ -2331,7 +2338,7 @@ eglot--after-change
            eglot-send-changes-idle-time
            nil (lambda () (eglot--when-live-buffer buf
                             (when eglot--managed-mode
-                              (eglot--signal-textDocument/didChange)
+                              (run-hooks 'eglot--document-changed-hook)
                               (setq eglot--change-idle-timer nil))))))))
 
 ;; HACK! Launching a deferred sync request with outstanding changes is a
@@ -3459,6 +3466,64 @@ eglot-list-connections
       (revert-buffer)
       (pop-to-buffer (current-buffer)))))
 
+\f
+;;; Inlay hints
+(defface eglot-inlay-hint-face '((t (:height 0.8 :inherit shadow)))
+  "Face used for inlay hint overlays.")
+
+(defface eglot-type-hint-face '((t (:inherit eglot-inlay-hint-face)))
+  "Face used for type inlay hint overlays.")
+
+(defface eglot-parameter-hint-face '((t (:inherit eglot-inlay-hint-face)))
+  "Face used for parameter inlay hint overlays.")
+
+(defun eglot--paint-hint (hint)
+  "Paint inlay hint HINT in current buffer."
+  (eglot--dbind ((InlayHint) position kind label paddingLeft paddingRight) hint
+    (goto-char (eglot--lsp-position-to-point position))
+    (let ((ov (make-overlay (point) (point))))
+      (overlay-put ov 'before-string
+                   (propertize
+                    (concat (and paddingLeft " ")
+                            (if (stringp label) label (plist-get label :value))
+                            (and paddingRight " "))
+                    'face (pcase kind
+                            (1 'eglot-type-hint-face)
+                            (2 'eglot-parameter-hint-face)
+                            (_ 'eglot-inlay-hint-face))))
+      (overlay-put ov 'eglot--inlay-hint t))))
+
+(defun eglot--update-hints ()
+  "Request inlay hints for current buffer and paint them."
+  (interactive)
+  (eglot--server-capable-or-lose :inlayHintProvider)
+  (let ((buffer (current-buffer)))
+    (jsonrpc-async-request
+      (eglot--current-server-or-lose)
+      :textDocument/inlayHint
+      (list :textDocument (eglot--TextDocumentIdentifier)
+            :range (list :start (eglot--pos-to-lsp-position (point-min))
+                         :end (eglot--pos-to-lsp-position (point-max))))
+      :success-fn (lambda (hints)
+                    (with-current-buffer buffer
+                      (eglot--widening
+                       (remove-overlays nil nil 'eglot--inlay-hint t)
+                       (mapc #'eglot--paint-hint hints))))
+      :deferred t)))
+
+(define-minor-mode eglot-inlay-hints-mode
+  "Minor mode annotating buffer with LSP inlay hints."
+  :global nil
+  (if eglot-inlay-hints-mode
+      (if (not eglot--managed-mode)
+          (eglot--error "Can't turn on `eglot-inlay-hints' mode.")
+        (add-hook 'eglot--document-changed-hook
+                  'eglot--update-hints nil t)
+        (eglot--update-hints))
+    (remove-hook 'eglot--document-changed-hook
+                 'eglot--update-hints t)
+    (remove-overlays nil nil 'eglot--inlay-hint t)))
+
 \f
 ;;; Hacks
 ;;;
-- 
2.36.1.windows.1


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* bug#61412: Inlay hints implementation
  2023-02-21 15:13           ` bug#61412: Inlay hints implementation João Távora
@ 2023-02-21 15:21             ` Eli Zaretskii
  2023-02-21 18:42             ` Dimitri Belopopsky
  1 sibling, 0 replies; 72+ messages in thread
From: Eli Zaretskii @ 2023-02-21 15:21 UTC (permalink / raw)
  To: João Távora; +Cc: luangruo, dalal.chinmay.0101, dimitri, 61412

merge 61412 61066
thanks

> From: João Távora <joaotavora@gmail.com>
> Date: Tue, 21 Feb 2023 15:13:03 +0000
> Cc: Eli Zaretskii <eliz@gnu.org>, 61412@debbugs.gnu.org
> 
> 
> [Eli, I just noticed that this bug should be merged with 61066,
> but I don't know how to do that]

Done.





^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Inlay hints implementation
  2023-02-11  8:13 bug#61412: [PATCH] Add inlay hints to eglot Chinmay Dalal
                   ` (5 preceding siblings ...)
  2023-02-15 16:24 ` bug#61412: Inlay activation Chinmay Dalal
@ 2023-02-21 15:33 ` Chinmay Dalal
  2023-02-21 15:57 ` Chinmay Dalal
  2023-02-22 15:26 ` Chinmay Dalal
  8 siblings, 0 replies; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-21 15:33 UTC (permalink / raw)
  To: 61412; +Cc: joaotavora


> Not sure how to proceed here and if this counts as "derived
> work" and if we should wait for Chinmay's copyright assignment.

I myself took the face definitions from the patch in bug 61066





^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Inlay hints implementation
  2023-02-11  8:13 bug#61412: [PATCH] Add inlay hints to eglot Chinmay Dalal
                   ` (6 preceding siblings ...)
  2023-02-21 15:33 ` bug#61412: Inlay hints implementation Chinmay Dalal
@ 2023-02-21 15:57 ` Chinmay Dalal
  2023-02-22 15:26 ` Chinmay Dalal
  8 siblings, 0 replies; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-21 15:57 UTC (permalink / raw)
  To: 61412; +Cc: joaotavora


> I did take Chinmay's face definitions, though.
> Not sure how to proceed here and if this counts as "derived
> work" and if we should wait for Chinmay's copyright assignment.

I myself took `:inherit shadow` and `:height 0.8` from Dmitry's patch in
bug #61066 and only added two new faces for the two kinds which inherit
from it

Chinmay





^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Inlay hints implementation
  2023-02-21 15:13           ` bug#61412: Inlay hints implementation João Távora
  2023-02-21 15:21             ` Eli Zaretskii
@ 2023-02-21 18:42             ` Dimitri Belopopsky
  2023-02-21 21:26               ` João Távora
  2023-02-22 19:42               ` Eglot "inlay hints" landed João Távora
  1 sibling, 2 replies; 72+ messages in thread
From: Dimitri Belopopsky @ 2023-02-21 18:42 UTC (permalink / raw)
  To: João Távora; +Cc: Po Lu, Chinmay Dalal, Eli Zaretskii, 61412


[-- Attachment #1.1: Type: text/plain, Size: 2100 bytes --]

Hello,

I thought the point was not to create overlays directly?
I'm sharing what I currently have, using the flymake diagnostics directly.
Maybe something can be salvaged?

In the meantime I'll try out your patches.

On Tue, 21 Feb 2023 at 16:14, João Távora <joaotavora@gmail.com> wrote:

> [Eli, I just noticed that this bug should be merged with 61066,
> but I don't know how to do that]
>
> Hello,
>
> Attached is my implementation of inlay hints for Eglot.
> Two patches are provided, where the first just lays some
> basic groundwork to make the actual inlay hint implementation
> simpler.
>
> Regarding copyright stuff, I did look at Chinmay's patch,
> but I re-started from scratch.  While it was a very good
> effort, there were too many idiomatic Elisp and Eglot things
> to change.  I did take Chinmay's face definitions, though.
> Not sure how to proceed here and if this counts as "derived
> work" and if we should wait for Chinmay's copyright assignment.
>
> I gave it some light testing and I kind of like it.  Quite
> helpful for C++ with clangd (the only server I tested it with).
> You can bind `eglot-inlay-hint-mode` to some keybinding probably.


It's even more helpful in languages like Rust, that rely on type deduction
a lot more.
Clangd has an extension for designator hints that looks interesting too.


> Documentation in the manual is still missing, but shouldn't
> be very hard to do.
>
> Anyway, this is likely not the end of the inlay hint story
> because, as the second patch documents, this is likely a
> very naive implementation that always requests inlay hints
> for the entire buffer even if just a fraction of it is visible.
>


> A better implementation would probably leverage
> window-scroll-functions along with the Eglot-specific
> idle timer.  That is probably much, much more tricky to get
> right, but is also more than likely the way to go.
>
> In the meantime, I'd like your opinion on this patch and
> the above topics first.
>
> João
>
> I hope gmail doesn't mess up my attachments...
>

[-- Attachment #1.2: Type: text/html, Size: 2937 bytes --]

[-- Attachment #2: 0001-Add-inlay-hint-functionality.patch --]
[-- Type: text/x-patch, Size: 237198 bytes --]

From ecb08911dcf5b2eae1338ef832640603eaf441b3 Mon Sep 17 00:00:00 2001
From: Dimitri Belopopsky <dimitri@belopopsky.com>
Date: Mon, 2 Jan 2023 22:06:07 +0100
Subject: [PATCH] Add inlay hint functionality

---
 eglot.el | 3731 +++++++++++++++++++++++++++++-------------------------
 1 file changed, 1985 insertions(+), 1746 deletions(-)

diff --git a/eglot.el b/eglot.el
index 901bf30..9e5a9dd 100644
--- a/eglot.el
+++ b/eglot.el
@@ -114,13 +114,13 @@
 ;; using the latest version from GNU Elpa when we load eglot.el.  Use an
 ;; heuristic to see if we need to `load' it in Emacs < 28.
 (if (and (< emacs-major-version 28)
-         (not (boundp 'eldoc-documentation-strategy)))
-    (load "eldoc")
+      (not (boundp 'eldoc-documentation-strategy)))
+  (load "eldoc")
   (require 'eldoc))
 
 ;; Similar issue as above for Emacs 26.3 and seq.el.
 (if (< emacs-major-version 27)
-    (load "seq")
+  (load "seq")
   (require 'seq))
 
 ;; forward-declare, but don't require (Emacs 28 doesn't seem to care)
@@ -150,90 +150,90 @@ chosen (interactively or automatically)."
     ;; `eglot--executable-find' may take much longer to execute on
     ;; remote files.
     (let* ((listified (cl-loop for a in alternatives
-                               collect (if (listp a) a (list a))))
-           (err (lambda ()
-                  (error "None of '%s' are valid executables"
-                         (mapconcat #'car listified ", ")))))
+                        collect (if (listp a) a (list a))))
+            (err (lambda ()
+                   (error "None of '%s' are valid executables"
+                     (mapconcat #'car listified ", ")))))
       (cond (interactive
-             (let* ((augmented (mapcar (lambda (a)
-                                         (let ((found (eglot--executable-find
-                                                       (car a) t)))
-                                           (and found
-                                                (cons (car a) (cons found (cdr a))))))
-                                       listified))
-                    (available (remove nil augmented)))
-               (cond ((cdr available)
-                      (cdr (assoc
-                            (completing-read
-                             "[eglot] More than one server executable available:"
-                             (mapcar #'car available)
-                             nil t nil nil (car (car available)))
-                            available #'equal)))
-                     ((cdr (car available)))
-                     (t
-                      ;; Don't error when used interactively, let the
-                      ;; Eglot prompt the user for alternative (github#719)
-                      nil))))
-            (t
-             (cl-loop for (p . args) in listified
-                      for probe = (eglot--executable-find p t)
-                      when probe return (cons probe args)
-                      finally (funcall err)))))))
+              (let* ((augmented (mapcar (lambda (a)
+                                          (let ((found (eglot--executable-find
+                                                         (car a) t)))
+                                            (and found
+                                              (cons (car a) (cons found (cdr a))))))
+                                  listified))
+                      (available (remove nil augmented)))
+                (cond ((cdr available)
+                        (cdr (assoc
+                               (completing-read
+                                 "[eglot] More than one server executable available:"
+                                 (mapcar #'car available)
+                                 nil t nil nil (car (car available)))
+                               available #'equal)))
+                  ((cdr (car available)))
+                  (t
+                    ;; Don't error when used interactively, let the
+                    ;; Eglot prompt the user for alternative (github#719)
+                    nil))))
+        (t
+          (cl-loop for (p . args) in listified
+            for probe = (eglot--executable-find p t)
+            when probe return (cons probe args)
+            finally (funcall err)))))))
 
 (defvar eglot-server-programs `((rust-mode . ,(eglot-alternatives '("rust-analyzer" "rls")))
-                                (cmake-mode . ("cmake-language-server"))
-                                (vimrc-mode . ("vim-language-server" "--stdio"))
-                                (python-mode
-                                 . ,(eglot-alternatives
-                                     '("pylsp" "pyls" ("pyright-langserver" "--stdio") "jedi-language-server")))
-                                ((js-mode typescript-mode)
-                                 . ("typescript-language-server" "--stdio"))
-                                (sh-mode . ("bash-language-server" "start"))
-                                ((php-mode phps-mode)
-                                 . ("php" "vendor/felixfbecker/\
+                                 (cmake-mode . ("cmake-language-server"))
+                                 (vimrc-mode . ("vim-language-server" "--stdio"))
+                                 (python-mode
+                                   . ,(eglot-alternatives
+                                        '("pylsp" "pyls" ("pyright-langserver" "--stdio") "jedi-language-server")))
+                                 ((js-mode typescript-mode)
+                                   . ("typescript-language-server" "--stdio"))
+                                 (sh-mode . ("bash-language-server" "start"))
+                                 ((php-mode phps-mode)
+                                   . ("php" "vendor/felixfbecker/\
 language-server/bin/php-language-server.php"))
-                                ((c++-mode c-mode) . ,(eglot-alternatives
-                                                       '("clangd" "ccls")))
-                                (((caml-mode :language-id "ocaml")
-                                  (tuareg-mode :language-id "ocaml") reason-mode)
-                                 . ("ocamllsp"))
-                                (ruby-mode
-                                 . ("solargraph" "socket" "--port" :autoport))
-                                (haskell-mode
-                                 . ("haskell-language-server-wrapper" "--lsp"))
-                                (elm-mode . ("elm-language-server"))
-                                (mint-mode . ("mint" "ls"))
-                                (kotlin-mode . ("kotlin-language-server"))
-                                (go-mode . ("gopls"))
-                                ((R-mode ess-r-mode) . ("R" "--slave" "-e"
-                                                        "languageserver::run()"))
-                                (java-mode . ("jdtls"))
-                                (dart-mode . ("dart" "language-server"
-                                              "--client-id" "emacs.eglot-dart"))
-                                (elixir-mode . ("language_server.sh"))
-                                (ada-mode . ("ada_language_server"))
-                                (scala-mode . ("metals-emacs"))
-                                (racket-mode . ("racket" "-l" "racket-langserver"))
-                                ((tex-mode context-mode texinfo-mode bibtex-mode)
-                                 . ("digestif"))
-                                (erlang-mode . ("erlang_ls" "--transport" "stdio"))
-                                (yaml-mode . ("yaml-language-server" "--stdio"))
-                                (nix-mode . ("rnix-lsp"))
-                                (gdscript-mode . ("localhost" 6008))
-                                ((fortran-mode f90-mode) . ("fortls"))
-                                (futhark-mode . ("futhark" "lsp"))
-                                (lua-mode . ("lua-lsp"))
-                                (zig-mode . ("zls"))
-                                (css-mode . ,(eglot-alternatives '(("vscode-css-language-server" "--stdio") ("css-languageserver" "--stdio"))))
-                                (html-mode . ,(eglot-alternatives '(("vscode-html-language-server" "--stdio") ("html-languageserver" "--stdio"))))
-                                (json-mode . ,(eglot-alternatives '(("vscode-json-language-server" "--stdio") ("json-languageserver" "--stdio"))))
-                                (dockerfile-mode . ("docker-langserver" "--stdio"))
-                                ((clojure-mode clojurescript-mode clojurec-mode) 
-                                 . ("clojure-lsp"))
-                                (csharp-mode . ("omnisharp" "-lsp"))
-                                (purescript-mode . ("purescript-language-server" "--stdio"))
-                                (perl-mode . ("perl" "-MPerl::LanguageServer" "-e" "Perl::LanguageServer::run"))
-                                (markdown-mode . ("marksman" "server")))
+                                 ((c++-mode c-mode) . ,(eglot-alternatives
+                                                         '("clangd" "ccls")))
+                                 (((caml-mode :language-id "ocaml")
+                                    (tuareg-mode :language-id "ocaml") reason-mode)
+                                   . ("ocamllsp"))
+                                 (ruby-mode
+                                   . ("solargraph" "socket" "--port" :autoport))
+                                 (haskell-mode
+                                   . ("haskell-language-server-wrapper" "--lsp"))
+                                 (elm-mode . ("elm-language-server"))
+                                 (mint-mode . ("mint" "ls"))
+                                 (kotlin-mode . ("kotlin-language-server"))
+                                 (go-mode . ("gopls"))
+                                 ((R-mode ess-r-mode) . ("R" "--slave" "-e"
+                                                          "languageserver::run()"))
+                                 (java-mode . ("jdtls"))
+                                 (dart-mode . ("dart" "language-server"
+                                                "--client-id" "emacs.eglot-dart"))
+                                 (elixir-mode . ("language_server.sh"))
+                                 (ada-mode . ("ada_language_server"))
+                                 (scala-mode . ("metals-emacs"))
+                                 (racket-mode . ("racket" "-l" "racket-langserver"))
+                                 ((tex-mode context-mode texinfo-mode bibtex-mode)
+                                   . ("digestif"))
+                                 (erlang-mode . ("erlang_ls" "--transport" "stdio"))
+                                 (yaml-mode . ("yaml-language-server" "--stdio"))
+                                 (nix-mode . ("rnix-lsp"))
+                                 (gdscript-mode . ("localhost" 6008))
+                                 ((fortran-mode f90-mode) . ("fortls"))
+                                 (futhark-mode . ("futhark" "lsp"))
+                                 (lua-mode . ("lua-lsp"))
+                                 (zig-mode . ("zls"))
+                                 (css-mode . ,(eglot-alternatives '(("vscode-css-language-server" "--stdio") ("css-languageserver" "--stdio"))))
+                                 (html-mode . ,(eglot-alternatives '(("vscode-html-language-server" "--stdio") ("html-languageserver" "--stdio"))))
+                                 (json-mode . ,(eglot-alternatives '(("vscode-json-language-server" "--stdio") ("json-languageserver" "--stdio"))))
+                                 (dockerfile-mode . ("docker-langserver" "--stdio"))
+                                 ((clojure-mode clojurescript-mode clojurec-mode)
+                                   . ("clojure-lsp"))
+                                 (csharp-mode . ("omnisharp" "-lsp"))
+                                 (purescript-mode . ("purescript-language-server" "--stdio"))
+                                 (perl-mode . ("perl" "-MPerl::LanguageServer" "-e" "Perl::LanguageServer::run"))
+                                 (markdown-mode . ("marksman" "server")))
   "How the command `eglot' guesses the server to start.
 An association list of (MAJOR-MODE . CONTACT) pairs.  MAJOR-MODE
 identifies the buffers that are to be managed by a specific
@@ -314,6 +314,10 @@ CONTACT can be:
   '((t . (:inherit shadow :strike-through t)))
   "Face used to render deprecated or obsolete code.")
 
+(defface eglot-flymake-inlay-hint
+  '((t (:inherit shadow :height 0.8)))
+  "Face used for inlay hint overlays in flymake.")
+
 (defcustom eglot-autoreconnect 3
   "Control ability to reconnect automatically to the LSP server.
 If t, always reconnect automatically (not recommended).  If nil,
@@ -322,7 +326,7 @@ crashes or network failures.  A positive integer number says to
 only autoreconnect if the previous successful connection attempt
 lasted more than that many seconds."
   :type '(choice (boolean :tag "Whether to inhibit autoreconnection")
-                 (integer :tag "Number of seconds")))
+           (integer :tag "Number of seconds")))
 
 (defcustom eglot-connect-timeout 30
   "Number of seconds before timing out LSP connection attempts.
@@ -336,7 +340,7 @@ integer number means block for that many seconds, and then wait
 for the connection in the background.  nil has the same meaning
 as 0, i.e. don't block at all."
   :type '(choice (boolean :tag "Whether to inhibit autoreconnection")
-                 (integer :tag "Number of seconds")))
+           (integer :tag "Number of seconds")))
 
 (defcustom eglot-autoshutdown nil
   "If non-nil, shut down server after killing last managed buffer."
@@ -356,12 +360,12 @@ For changes on this variable to take effect on a connection
 already started, you need to restart the connection.  That can be
 done by `eglot-reconnect'."
   :type '(choice (const :tag "No limit" nil)
-                 (integer :tag "Number of characters")))
+           (integer :tag "Number of characters")))
 
 (defcustom eglot-confirm-server-initiated-edits 'confirm
   "Non-nil if server-initiated edits should be confirmed with user."
   :type '(choice (const :tag "Don't show confirmation prompt" nil)
-                 (symbol :tag "Show confirmation prompt" 'confirm)))
+           (symbol :tag "Show confirmation prompt" 'confirm)))
 
 (defcustom eglot-extend-to-xref nil
   "If non-nil, activate Eglot in cross-referenced non-project files."
@@ -384,27 +388,27 @@ This can be useful when using docker to run a language server.")
 ;;;
 (defconst eglot--symbol-kind-names
   `((1 . "File") (2 . "Module")
-    (3 . "Namespace") (4 . "Package") (5 . "Class")
-    (6 . "Method") (7 . "Property") (8 . "Field")
-    (9 . "Constructor") (10 . "Enum") (11 . "Interface")
-    (12 . "Function") (13 . "Variable") (14 . "Constant")
-    (15 . "String") (16 . "Number") (17 . "Boolean")
-    (18 . "Array") (19 . "Object") (20 . "Key")
-    (21 . "Null") (22 . "EnumMember") (23 . "Struct")
-    (24 . "Event") (25 . "Operator") (26 . "TypeParameter")))
+     (3 . "Namespace") (4 . "Package") (5 . "Class")
+     (6 . "Method") (7 . "Property") (8 . "Field")
+     (9 . "Constructor") (10 . "Enum") (11 . "Interface")
+     (12 . "Function") (13 . "Variable") (14 . "Constant")
+     (15 . "String") (16 . "Number") (17 . "Boolean")
+     (18 . "Array") (19 . "Object") (20 . "Key")
+     (21 . "Null") (22 . "EnumMember") (23 . "Struct")
+     (24 . "Event") (25 . "Operator") (26 . "TypeParameter")))
 
 (defconst eglot--kind-names
   `((1 . "Text") (2 . "Method") (3 . "Function") (4 . "Constructor")
-    (5 . "Field") (6 . "Variable") (7 . "Class") (8 . "Interface")
-    (9 . "Module") (10 . "Property") (11 . "Unit") (12 . "Value")
-    (13 . "Enum") (14 . "Keyword") (15 . "Snippet") (16 . "Color")
-    (17 . "File") (18 . "Reference") (19 . "Folder") (20 . "EnumMember")
-    (21 . "Constant") (22 . "Struct") (23 . "Event") (24 . "Operator")
-    (25 . "TypeParameter")))
+     (5 . "Field") (6 . "Variable") (7 . "Class") (8 . "Interface")
+     (9 . "Module") (10 . "Property") (11 . "Unit") (12 . "Value")
+     (13 . "Enum") (14 . "Keyword") (15 . "Snippet") (16 . "Color")
+     (17 . "File") (18 . "Reference") (19 . "Folder") (20 . "EnumMember")
+     (21 . "Constant") (22 . "Struct") (23 . "Event") (24 . "Operator")
+     (25 . "TypeParameter")))
 
 (defconst eglot--tag-faces
   `((1 . eglot-diagnostic-tag-unnecessary-face)
-    (2 . eglot-diagnostic-tag-deprecated-face)))
+     (2 . eglot-diagnostic-tag-deprecated-face)))
 
 (defconst eglot--{} (make-hash-table :size 1) "The empty JSON object.")
 (defvaralias 'eglot-{} 'eglot--{})
@@ -420,43 +424,43 @@ This can be useful when using docker to run a language server.")
 (eval-and-compile
   (defvar eglot--lsp-interface-alist
     `(
-      (CodeAction (:title) (:kind :diagnostics :edit :command :isPreferred))
-      (ConfigurationItem () (:scopeUri :section))
-      (Command ((:title . string) (:command . string)) (:arguments))
-      (CompletionItem (:label)
-                      (:kind :detail :documentation :deprecated :preselect
-                             :sortText :filterText :insertText :insertTextFormat
-                             :textEdit :additionalTextEdits :commitCharacters
-                             :command :data :tags))
-      (Diagnostic (:range :message) (:severity :code :source :relatedInformation :codeDescription :tags))
-      (DocumentHighlight (:range) (:kind))
-      (FileSystemWatcher (:globPattern) (:kind))
-      (Hover (:contents) (:range))
-      (InitializeResult (:capabilities) (:serverInfo))
-      (Location (:uri :range))
-      (LocationLink (:targetUri :targetRange :targetSelectionRange) (:originSelectionRange))
-      (LogMessageParams (:type :message))
-      (MarkupContent (:kind :value))
-      (ParameterInformation (:label) (:documentation))
-      (Position (:line :character))
-      (Range (:start :end))
-      (Registration (:id :method) (:registerOptions))
-      (ResponseError (:code :message) (:data))
-      (ShowMessageParams (:type :message))
-      (ShowMessageRequestParams (:type :message) (:actions))
-      (SignatureHelp (:signatures) (:activeSignature :activeParameter))
-      (SignatureInformation (:label) (:documentation :parameters :activeParameter))
-      (SymbolInformation (:name :kind :location)
-                         (:deprecated :containerName))
-      (DocumentSymbol (:name :range :selectionRange :kind)
-                      ;; `:containerName' isn't really allowed , but
-                      ;; it simplifies the impl of `eglot-imenu'.
-                      (:detail :deprecated :children :containerName))
-      (TextDocumentEdit (:textDocument :edits) ())
-      (TextEdit (:range :newText))
-      (VersionedTextDocumentIdentifier (:uri :version) ())
-      (WorkspaceEdit () (:changes :documentChanges))
-      (WorkspaceSymbol (:name :kind) (:containerName :location :data)))
+       (CodeAction (:title) (:kind :diagnostics :edit :command :isPreferred))
+       (ConfigurationItem () (:scopeUri :section))
+       (Command ((:title . string) (:command . string)) (:arguments))
+       (CompletionItem (:label)
+         (:kind :detail :documentation :deprecated :preselect
+           :sortText :filterText :insertText :insertTextFormat
+           :textEdit :additionalTextEdits :commitCharacters
+           :command :data :tags))
+       (Diagnostic (:range :message) (:severity :code :source :relatedInformation :codeDescription :tags))
+       (DocumentHighlight (:range) (:kind))
+       (FileSystemWatcher (:globPattern) (:kind))
+       (Hover (:contents) (:range))
+       (InitializeResult (:capabilities) (:serverInfo))
+       (Location (:uri :range))
+       (LocationLink (:targetUri :targetRange :targetSelectionRange) (:originSelectionRange))
+       (LogMessageParams (:type :message))
+       (MarkupContent (:kind :value))
+       (ParameterInformation (:label) (:documentation))
+       (Position (:line :character))
+       (Range (:start :end))
+       (Registration (:id :method) (:registerOptions))
+       (ResponseError (:code :message) (:data))
+       (ShowMessageParams (:type :message))
+       (ShowMessageRequestParams (:type :message) (:actions))
+       (SignatureHelp (:signatures) (:activeSignature :activeParameter))
+       (SignatureInformation (:label) (:documentation :parameters :activeParameter))
+       (SymbolInformation (:name :kind :location)
+         (:deprecated :containerName))
+       (DocumentSymbol (:name :range :selectionRange :kind)
+         ;; `:containerName' isn't really allowed , but
+         ;; it simplifies the impl of `eglot-imenu'.
+         (:detail :deprecated :children :containerName))
+       (TextDocumentEdit (:textDocument :edits) ())
+       (TextEdit (:range :newText))
+       (VersionedTextDocumentIdentifier (:uri :version) ())
+       (WorkspaceEdit () (:changes :documentChanges))
+       (WorkspaceSymbol (:name :kind) (:containerName :location :data)))
     "Alist (INTERFACE-NAME . INTERFACE) of known external LSP interfaces.
 
 INTERFACE-NAME is a symbol designated by the spec as
@@ -474,10 +478,10 @@ Here's what an element of this alist might look like:
 (eval-and-compile
   (defvar eglot-strict-mode
     '(;; Uncomment next lines for fun and debugging
-      ;; disallow-non-standard-keys
-      ;; enforce-required-keys
-      ;; enforce-optional-keys
-      )
+       ;; disallow-non-standard-keys
+       ;; enforce-required-keys
+       ;; enforce-optional-keys
+       )
     "How strictly to check LSP interfaces at compile- and run-time.
 
 Value is a list of symbols (if the list is empty, no checks are
@@ -501,31 +505,31 @@ If the symbol `disallow-unknown-methods' is present, Eglot warns
 on unknown notifications and errors on unknown requests."))
 
 (cl-defun eglot--check-object (interface-name
-                               object
-                               &optional
-                               (enforce-required t)
-                               (disallow-non-standard t)
-                               (check-types t))
+                                object
+                                &optional
+                                (enforce-required t)
+                                (disallow-non-standard t)
+                                (check-types t))
   "Check that OBJECT conforms to INTERFACE.  Error otherwise."
   (cl-destructuring-bind
-      (&key types required-keys optional-keys &allow-other-keys)
-      (eglot--interface interface-name)
+    (&key types required-keys optional-keys &allow-other-keys)
+    (eglot--interface interface-name)
     (when-let ((missing (and enforce-required
-                             (cl-set-difference required-keys
-                                                (eglot--plist-keys object)))))
+                          (cl-set-difference required-keys
+                            (eglot--plist-keys object)))))
       (eglot--error "A `%s' must have %s" interface-name missing))
     (when-let ((excess (and disallow-non-standard
-                            (cl-set-difference
-                             (eglot--plist-keys object)
-                             (append required-keys optional-keys)))))
+                         (cl-set-difference
+                           (eglot--plist-keys object)
+                           (append required-keys optional-keys)))))
       (eglot--error "A `%s' mustn't have %s" interface-name excess))
     (when check-types
       (cl-loop
-       for (k v) on object by #'cddr
-       for type = (or (cdr (assoc k types)) t) ;; FIXME: enforce nil type?
-       unless (cl-typep v type)
-       do (eglot--error "A `%s' must have a %s as %s, but has %s"
-                        interface-name )))
+        for (k v) on object by #'cddr
+        for type = (or (cdr (assoc k types)) t) ;; FIXME: enforce nil type?
+        unless (cl-typep v type)
+        do (eglot--error "A `%s' must have a %s as %s, but has %s"
+             interface-name )))
     t))
 
 (eval-and-compile
@@ -536,44 +540,44 @@ on unknown notifications and errors on unknown requests."))
 
   (defun eglot--interface (interface-name)
     (let* ((interface (assoc interface-name eglot--lsp-interface-alist))
-           (required (mapcar #'eglot--ensure-type (car (cdr interface))))
-           (optional (mapcar #'eglot--ensure-type (cadr (cdr interface)))))
+            (required (mapcar #'eglot--ensure-type (car (cdr interface))))
+            (optional (mapcar #'eglot--ensure-type (cadr (cdr interface)))))
       (list :types (append required optional)
-            :required-keys (mapcar #'car required)
-            :optional-keys (mapcar #'car optional))))
+        :required-keys (mapcar #'car required)
+        :optional-keys (mapcar #'car optional))))
 
   (defun eglot--check-dspec (interface-name dspec)
     "Check destructuring spec DSPEC against INTERFACE-NAME."
     (cl-destructuring-bind (&key required-keys optional-keys &allow-other-keys)
-        (eglot--interface interface-name)
+      (eglot--interface interface-name)
       (cond ((or required-keys optional-keys)
-             (let ((too-many
-                    (and
-                     (memq 'disallow-non-standard-keys eglot-strict-mode)
-                     (cl-set-difference
-                      (eglot--keywordize-vars dspec)
-                      (append required-keys optional-keys))))
-                   (ignored-required
-                    (and
-                     (memq 'enforce-required-keys eglot-strict-mode)
-                     (cl-set-difference
-                      required-keys (eglot--keywordize-vars dspec))))
-                   (missing-out
-                    (and
-                     (memq 'enforce-optional-keys eglot-strict-mode)
-                     (cl-set-difference
-                      optional-keys (eglot--keywordize-vars dspec)))))
-               (when too-many (byte-compile-warn
-                               "Destructuring for %s has extraneous %s"
-                               interface-name too-many))
-               (when ignored-required (byte-compile-warn
-                                       "Destructuring for %s ignores required %s"
-                                       interface-name ignored-required))
-               (when missing-out (byte-compile-warn
-                                  "Destructuring for %s is missing out on %s"
-                                  interface-name missing-out))))
-            (t
-             (byte-compile-warn "Unknown LSP interface %s" interface-name))))))
+              (let ((too-many
+                      (and
+                        (memq 'disallow-non-standard-keys eglot-strict-mode)
+                        (cl-set-difference
+                          (eglot--keywordize-vars dspec)
+                          (append required-keys optional-keys))))
+                     (ignored-required
+                       (and
+                         (memq 'enforce-required-keys eglot-strict-mode)
+                         (cl-set-difference
+                           required-keys (eglot--keywordize-vars dspec))))
+                     (missing-out
+                       (and
+                         (memq 'enforce-optional-keys eglot-strict-mode)
+                         (cl-set-difference
+                           optional-keys (eglot--keywordize-vars dspec)))))
+                (when too-many (byte-compile-warn
+                                 "Destructuring for %s has extraneous %s"
+                                 interface-name too-many))
+                (when ignored-required (byte-compile-warn
+                                         "Destructuring for %s ignores required %s"
+                                         interface-name ignored-required))
+                (when missing-out (byte-compile-warn
+                                    "Destructuring for %s is missing out on %s"
+                                    interface-name missing-out))))
+        (t
+          (byte-compile-warn "Unknown LSP interface %s" interface-name))))))
 
 (cl-defmacro eglot--dbind (vars object &body body)
   "Destructure OBJECT, binding VARS in BODY.
@@ -581,26 +585,26 @@ VARS is ([(INTERFACE)] SYMS...)
 Honour `eglot-strict-mode'."
   (declare (indent 2) (debug (sexp sexp &rest form)))
   (let ((interface-name (if (consp (car vars))
-                            (car (pop vars))))
-        (object-once (make-symbol "object-once"))
-        (fn-once (make-symbol "fn-once")))
+                          (car (pop vars))))
+         (object-once (make-symbol "object-once"))
+         (fn-once (make-symbol "fn-once")))
     (cond (interface-name
-           (eglot--check-dspec interface-name vars)
-           `(let ((,object-once ,object))
-              (cl-destructuring-bind (&key ,@vars &allow-other-keys) ,object-once
-                (eglot--check-object ',interface-name ,object-once
-                                     (memq 'enforce-required-keys eglot-strict-mode)
-                                     (memq 'disallow-non-standard-keys eglot-strict-mode)
-                                     (memq 'check-types eglot-strict-mode))
-                ,@body)))
-          (t
-           `(let ((,object-once ,object)
-                  (,fn-once (lambda (,@vars) ,@body)))
-              (if (memq 'disallow-non-standard-keys eglot-strict-mode)
-                  (cl-destructuring-bind (&key ,@vars) ,object-once
-                    (funcall ,fn-once ,@vars))
-                (cl-destructuring-bind (&key ,@vars &allow-other-keys) ,object-once
-                  (funcall ,fn-once ,@vars))))))))
+            (eglot--check-dspec interface-name vars)
+            `(let ((,object-once ,object))
+               (cl-destructuring-bind (&key ,@vars &allow-other-keys) ,object-once
+                 (eglot--check-object ',interface-name ,object-once
+                   (memq 'enforce-required-keys eglot-strict-mode)
+                   (memq 'disallow-non-standard-keys eglot-strict-mode)
+                   (memq 'check-types eglot-strict-mode))
+                 ,@body)))
+      (t
+        `(let ((,object-once ,object)
+                (,fn-once (lambda (,@vars) ,@body)))
+           (if (memq 'disallow-non-standard-keys eglot-strict-mode)
+             (cl-destructuring-bind (&key ,@vars) ,object-once
+               (funcall ,fn-once ,@vars))
+             (cl-destructuring-bind (&key ,@vars &allow-other-keys) ,object-once
+               (funcall ,fn-once ,@vars))))))))
 
 
 (cl-defmacro eglot--lambda (cl-lambda-list &body body)
@@ -618,38 +622,38 @@ treated as in `eglot-dbind'."
   (let ((obj-once (make-symbol "obj-once")))
     `(let ((,obj-once ,obj))
        (cond
-        ,@(cl-loop
-           for (vars . body) in clauses
-           for vars-as-keywords = (eglot--keywordize-vars vars)
-           for interface-name = (if (consp (car vars))
+         ,@(cl-loop
+             for (vars . body) in clauses
+             for vars-as-keywords = (eglot--keywordize-vars vars)
+             for interface-name = (if (consp (car vars))
                                     (car (pop vars)))
-           for condition =
-           (cond (interface-name
-                  (eglot--check-dspec interface-name vars)
-                  ;; In this mode, in runtime, we assume
-                  ;; `eglot-strict-mode' is partially on, otherwise we
-                  ;; can't disambiguate between certain types.
-                  `(ignore-errors
-                     (eglot--check-object
-                      ',interface-name ,obj-once
-                      t
-                      (memq 'disallow-non-standard-keys eglot-strict-mode)
-                      t)))
-                 (t
-                  ;; In this interface-less mode we don't check
-                  ;; `eglot-strict-mode' at all: just check that the object
-                  ;; has all the keys the user wants to destructure.
-                  `(null (cl-set-difference
+             for condition =
+             (cond (interface-name
+                     (eglot--check-dspec interface-name vars)
+                     ;; In this mode, in runtime, we assume
+                     ;; `eglot-strict-mode' is partially on, otherwise we
+                     ;; can't disambiguate between certain types.
+                     `(ignore-errors
+                        (eglot--check-object
+                          ',interface-name ,obj-once
+                          t
+                          (memq 'disallow-non-standard-keys eglot-strict-mode)
+                          t)))
+               (t
+                 ;; In this interface-less mode we don't check
+                 ;; `eglot-strict-mode' at all: just check that the object
+                 ;; has all the keys the user wants to destructure.
+                 `(null (cl-set-difference
                           ',vars-as-keywords
                           (eglot--plist-keys ,obj-once)))))
-           collect `(,condition
-                     (cl-destructuring-bind (&key ,@vars &allow-other-keys)
-                         ,obj-once
-                       ,@body)))
-        (t
-         (eglot--error "%S didn't match any of %S"
-                       ,obj-once
-                       ',(mapcar #'car clauses)))))))
+             collect `(,condition
+                        (cl-destructuring-bind (&key ,@vars &allow-other-keys)
+                          ,obj-once
+                          ,@body)))
+         (t
+           (eglot--error "%S didn't match any of %S"
+             ,obj-once
+             ',(mapcar #'car clauses)))))))
 
 \f
 ;;; API (WORK-IN-PROGRESS!)
@@ -683,154 +687,154 @@ treated as in `eglot-dbind'."
 (cl-defgeneric eglot-initialization-options (server)
   "JSON object to send under `initializationOptions'."
   (:method (s)
-   (let ((probe (plist-get (eglot--saved-initargs s) :initializationOptions)))
-     (cond ((functionp probe) (funcall probe s))
-           (probe)
-           (t eglot--{})))))
+    (let ((probe (plist-get (eglot--saved-initargs s) :initializationOptions)))
+      (cond ((functionp probe) (funcall probe s))
+        (probe)
+        (t eglot--{})))))
 
 (cl-defgeneric eglot-register-capability (server method id &rest params)
   "Ask SERVER to register capability METHOD marked with ID."
   (:method
-   (_s method _id &rest _params)
-   (eglot--warn "Server tried to register unsupported capability `%s'"
-                method)))
+    (_s method _id &rest _params)
+    (eglot--warn "Server tried to register unsupported capability `%s'"
+      method)))
 
 (cl-defgeneric eglot-unregister-capability (server method id &rest params)
   "Ask SERVER to register capability METHOD marked with ID."
   (:method
-   (_s method _id &rest _params)
-   (eglot--warn "Server tried to unregister unsupported capability `%s'"
-                method)))
+    (_s method _id &rest _params)
+    (eglot--warn "Server tried to unregister unsupported capability `%s'"
+      method)))
 
 (cl-defgeneric eglot-client-capabilities (server)
   "What the Eglot LSP client supports for SERVER."
   (:method (s)
-           (list
-            :workspace (list
-                        :applyEdit t
-                        :executeCommand `(:dynamicRegistration :json-false)
-                        :workspaceEdit `(:documentChanges t)
-                        :didChangeWatchedFiles
-                        `(:dynamicRegistration
-                          ,(if (eglot--trampish-p s) :json-false t))
-                        :symbol `(:dynamicRegistration :json-false)
-                        :configuration t
-                        :workspaceFolders t)
-            :textDocument
-            (list
-             :synchronization (list
-                               :dynamicRegistration :json-false
-                               :willSave t :willSaveWaitUntil t :didSave t)
-             :completion      (list :dynamicRegistration :json-false
-                                    :completionItem
-                                    `(:snippetSupport
-                                      ,(if (eglot--snippet-expansion-fn)
-                                           t
-                                         :json-false)
-                                      :deprecatedSupport t
-                                      :tagSupport (:valueSet [1]))
-                                    :contextSupport t)
-             :hover              (list :dynamicRegistration :json-false
-                                       :contentFormat
-                                       (if (fboundp 'gfm-view-mode)
-                                           ["markdown" "plaintext"]
-                                         ["plaintext"]))
-             :signatureHelp      (list :dynamicRegistration :json-false
-                                       :signatureInformation
-                                       `(:parameterInformation
-                                         (:labelOffsetSupport t)
-                                         :activeParameterSupport t))
-             :references         `(:dynamicRegistration :json-false)
-             :definition         (list :dynamicRegistration :json-false
-                                       :linkSupport t)
-             :declaration        (list :dynamicRegistration :json-false
-                                       :linkSupport t)
-             :implementation     (list :dynamicRegistration :json-false
-                                       :linkSupport t)
-             :typeDefinition     (list :dynamicRegistration :json-false
-                                       :linkSupport t)
-             :documentSymbol     (list
-                                  :dynamicRegistration :json-false
-                                  :hierarchicalDocumentSymbolSupport t
-                                  :symbolKind `(:valueSet
-                                                [,@(mapcar
-                                                    #'car eglot--symbol-kind-names)]))
-             :documentHighlight  `(:dynamicRegistration :json-false)
-             :codeAction         (list
-                                  :dynamicRegistration :json-false
-                                  :codeActionLiteralSupport
-                                  '(:codeActionKind
-                                    (:valueSet
-                                     ["quickfix"
-                                      "refactor" "refactor.extract"
-                                      "refactor.inline" "refactor.rewrite"
-                                      "source" "source.organizeImports"]))
-                                  :isPreferredSupport t)
-             :formatting         `(:dynamicRegistration :json-false)
-             :rangeFormatting    `(:dynamicRegistration :json-false)
-             :rename             `(:dynamicRegistration :json-false)
-             :publishDiagnostics (list :relatedInformation :json-false
-                                       ;; TODO: We can support :codeDescription after
-                                       ;; adding an appropriate UI to
-                                       ;; Flymake.
-                                       :codeDescriptionSupport :json-false
-                                       :tagSupport
-                                       `(:valueSet
-                                         [,@(mapcar
-                                             #'car eglot--tag-faces)])))
-            :experimental eglot--{})))
+    (list
+      :workspace (list
+                   :applyEdit t
+                   :executeCommand `(:dynamicRegistration :json-false)
+                   :workspaceEdit `(:documentChanges t)
+                   :didChangeWatchedFiles
+                   `(:dynamicRegistration
+                      ,(if (eglot--trampish-p s) :json-false t))
+                   :symbol `(:dynamicRegistration :json-false)
+                   :configuration t
+                   :workspaceFolders t)
+      :textDocument
+      (list
+        :synchronization (list
+                           :dynamicRegistration :json-false
+                           :willSave t :willSaveWaitUntil t :didSave t)
+        :completion      (list :dynamicRegistration :json-false
+                           :completionItem
+                           `(:snippetSupport
+                              ,(if (eglot--snippet-expansion-fn)
+                                 t
+                                 :json-false)
+                              :deprecatedSupport t
+                              :tagSupport (:valueSet [1]))
+                           :contextSupport t)
+        :hover              (list :dynamicRegistration :json-false
+                              :contentFormat
+                              (if (fboundp 'gfm-view-mode)
+                                ["markdown" "plaintext"]
+                                ["plaintext"]))
+        :signatureHelp      (list :dynamicRegistration :json-false
+                              :signatureInformation
+                              `(:parameterInformation
+                                 (:labelOffsetSupport t)
+                                 :activeParameterSupport t))
+        :references         `(:dynamicRegistration :json-false)
+        :definition         (list :dynamicRegistration :json-false
+                              :linkSupport t)
+        :declaration        (list :dynamicRegistration :json-false
+                              :linkSupport t)
+        :implementation     (list :dynamicRegistration :json-false
+                              :linkSupport t)
+        :typeDefinition     (list :dynamicRegistration :json-false
+                              :linkSupport t)
+        :documentSymbol     (list
+                              :dynamicRegistration :json-false
+                              :hierarchicalDocumentSymbolSupport t
+                              :symbolKind `(:valueSet
+                                             [,@(mapcar
+                                                  #'car eglot--symbol-kind-names)]))
+        :documentHighlight  `(:dynamicRegistration :json-false)
+        :codeAction         (list
+                              :dynamicRegistration :json-false
+                              :codeActionLiteralSupport
+                              '(:codeActionKind
+                                 (:valueSet
+                                   ["quickfix"
+                                     "refactor" "refactor.extract"
+                                     "refactor.inline" "refactor.rewrite"
+                                     "source" "source.organizeImports"]))
+                              :isPreferredSupport t)
+        :formatting         `(:dynamicRegistration :json-false)
+        :rangeFormatting    `(:dynamicRegistration :json-false)
+        :rename             `(:dynamicRegistration :json-false)
+        :publishDiagnostics (list :relatedInformation :json-false
+                              ;; TODO: We can support :codeDescription after
+                              ;; adding an appropriate UI to
+                              ;; Flymake.
+                              :codeDescriptionSupport :json-false
+                              :tagSupport
+                              `(:valueSet
+                                 [,@(mapcar
+                                      #'car eglot--tag-faces)])))
+      :experimental eglot--{})))
 
 (cl-defgeneric eglot-workspace-folders (server)
   "Return workspaceFolders for SERVER."
   (let ((project (eglot--project server)))
     (vconcat
-     (mapcar (lambda (dir)
-               (list :uri (eglot--path-to-uri dir)
-                     :name (abbreviate-file-name dir)))
-             `(,(project-root project) ,@(project-external-roots project))))))
+      (mapcar (lambda (dir)
+                (list :uri (eglot--path-to-uri dir)
+                  :name (abbreviate-file-name dir)))
+        `(,(project-root project) ,@(project-external-roots project))))))
 
 (defclass eglot-lsp-server (jsonrpc-process-connection)
   ((project-nickname
-    :documentation "Short nickname for the associated project."
-    :accessor eglot--project-nickname
-    :reader eglot-project-nickname)
-   (major-modes
-    :documentation "Major modes server is responsible for in a given project."
-    :accessor eglot--major-modes)
-   (language-id
-    :documentation "Language ID string for the mode."
-    :accessor eglot--language-id)
-   (capabilities
-    :documentation "JSON object containing server capabilities."
-    :accessor eglot--capabilities)
-   (server-info
-    :documentation "JSON object containing server info."
-    :accessor eglot--server-info)
-   (shutdown-requested
-    :documentation "Flag set when server is shutting down."
-    :accessor eglot--shutdown-requested)
-   (project
-    :documentation "Project associated with server."
-    :accessor eglot--project)
-   (spinner
-    :documentation "List (ID DOING-WHAT DONE-P) representing server progress."
-    :initform `(nil nil t) :accessor eglot--spinner)
-   (inhibit-autoreconnect
-    :initform t
-    :documentation "Generalized boolean inhibiting auto-reconnection if true."
-    :accessor eglot--inhibit-autoreconnect)
-   (file-watches
-    :documentation "Map ID to list of WATCHES for `didChangeWatchedFiles'."
-    :initform (make-hash-table :test #'equal) :accessor eglot--file-watches)
-   (managed-buffers
-    :documentation "List of buffers managed by server."
-    :accessor eglot--managed-buffers)
-   (saved-initargs
-    :documentation "Saved initargs for reconnection purposes."
-    :accessor eglot--saved-initargs)
-   (inferior-process
-    :documentation "Server subprocess started automatically."
-    :accessor eglot--inferior-process))
+     :documentation "Short nickname for the associated project."
+     :accessor eglot--project-nickname
+     :reader eglot-project-nickname)
+    (major-modes
+      :documentation "Major modes server is responsible for in a given project."
+      :accessor eglot--major-modes)
+    (language-id
+      :documentation "Language ID string for the mode."
+      :accessor eglot--language-id)
+    (capabilities
+      :documentation "JSON object containing server capabilities."
+      :accessor eglot--capabilities)
+    (server-info
+      :documentation "JSON object containing server info."
+      :accessor eglot--server-info)
+    (shutdown-requested
+      :documentation "Flag set when server is shutting down."
+      :accessor eglot--shutdown-requested)
+    (project
+      :documentation "Project associated with server."
+      :accessor eglot--project)
+    (spinner
+      :documentation "List (ID DOING-WHAT DONE-P) representing server progress."
+      :initform `(nil nil t) :accessor eglot--spinner)
+    (inhibit-autoreconnect
+      :initform t
+      :documentation "Generalized boolean inhibiting auto-reconnection if true."
+      :accessor eglot--inhibit-autoreconnect)
+    (file-watches
+      :documentation "Map ID to list of WATCHES for `didChangeWatchedFiles'."
+      :initform (make-hash-table :test #'equal) :accessor eglot--file-watches)
+    (managed-buffers
+      :documentation "List of buffers managed by server."
+      :accessor eglot--managed-buffers)
+    (saved-initargs
+      :documentation "Saved initargs for reconnection purposes."
+      :accessor eglot--saved-initargs)
+    (inferior-process
+      :documentation "Server subprocess started automatically."
+      :accessor eglot--inferior-process))
   :documentation
   "Represents a server. Wraps a process for LSP communication.")
 
@@ -855,14 +859,14 @@ If PRESERVE-BUFFERS is non-nil (interactively, when called with a
 prefix argument), do not kill events and output buffers of
 SERVER."
   (interactive (list (eglot--read-server "Shutdown which server"
-                                         (eglot-current-server))
-                     t nil current-prefix-arg))
+                       (eglot-current-server))
+                 t nil current-prefix-arg))
   (eglot--message "Asking %s politely to terminate" (jsonrpc-name server))
   (unwind-protect
-      (progn
-        (setf (eglot--shutdown-requested server) t)
-        (jsonrpc-request server :shutdown nil :timeout (or timeout 1.5))
-        (jsonrpc-notify server :exit nil))
+    (progn
+      (setf (eglot--shutdown-requested server) t)
+      (jsonrpc-request server :shutdown nil :timeout (or timeout 1.5))
+      (jsonrpc-notify server :exit nil))
     ;; Now ask jsonrpc.el to shut down the server.
     (jsonrpc-shutdown server (not preserve-buffers))
     (unless preserve-buffers (kill-buffer (jsonrpc-events-buffer server)))))
@@ -872,33 +876,33 @@ SERVER."
 PRESERVE-BUFFERS as in `eglot-shutdown', which see."
   (interactive (list current-prefix-arg))
   (cl-loop for ss being the hash-values of eglot--servers-by-project
-           do (cl-loop for s in ss do (eglot-shutdown s nil preserve-buffers))))
+    do (cl-loop for s in ss do (eglot-shutdown s nil preserve-buffers))))
 
 (defun eglot--on-shutdown (server)
   "Called by jsonrpc.el when SERVER is already dead."
   ;; Turn off `eglot--managed-mode' where appropriate.
   (dolist (buffer (eglot--managed-buffers server))
     (let (;; Avoid duplicate shutdowns (github#389)
-          (eglot-autoshutdown nil))
+           (eglot-autoshutdown nil))
       (eglot--when-live-buffer buffer (eglot--managed-mode-off))))
   ;; Kill any expensive watches
   (maphash (lambda (_id watches)
              (mapcar #'file-notify-rm-watch watches))
-           (eglot--file-watches server))
+    (eglot--file-watches server))
   ;; Kill any autostarted inferior processes
   (when-let (proc (eglot--inferior-process server))
     (delete-process proc))
   ;; Sever the project/server relationship for `server'
   (setf (gethash (eglot--project server) eglot--servers-by-project)
-        (delq server
-              (gethash (eglot--project server) eglot--servers-by-project)))
+    (delq server
+      (gethash (eglot--project server) eglot--servers-by-project)))
   (cond ((eglot--shutdown-requested server)
-         t)
-        ((not (eglot--inhibit-autoreconnect server))
-         (eglot--warn "Reconnecting after unexpected server exit.")
-         (eglot-reconnect server))
-        ((timerp (eglot--inhibit-autoreconnect server))
-         (eglot--warn "Not auto-reconnecting, last one didn't last long."))))
+          t)
+    ((not (eglot--inhibit-autoreconnect server))
+      (eglot--warn "Reconnecting after unexpected server exit.")
+      (eglot-reconnect server))
+    ((timerp (eglot--inhibit-autoreconnect server))
+      (eglot--warn "Not auto-reconnecting, last one didn't last long."))))
 
 (defun eglot--all-major-modes ()
   "Return all known major modes."
@@ -925,25 +929,25 @@ LANGUAGE-ID is determined from MODE's name.
 CONTACT-PROXY is the value of the corresponding
 `eglot-server-programs' entry."
   (cl-loop
-   for (modes . contact) in eglot-server-programs
-   for mode-symbols = (cons mode
-                            (delete mode
-                                    (mapcar #'car
-                                            (mapcar #'eglot--ensure-list
-                                                    (eglot--ensure-list modes)))))
-   thereis (cl-some
-            (lambda (spec)
-              (cl-destructuring-bind (probe &key language-id &allow-other-keys)
+    for (modes . contact) in eglot-server-programs
+    for mode-symbols = (cons mode
+                         (delete mode
+                           (mapcar #'car
+                             (mapcar #'eglot--ensure-list
+                               (eglot--ensure-list modes)))))
+    thereis (cl-some
+              (lambda (spec)
+                (cl-destructuring-bind (probe &key language-id &allow-other-keys)
                   (eglot--ensure-list spec)
-                (and (provided-mode-derived-p mode probe)
-                     (list
+                  (and (provided-mode-derived-p mode probe)
+                    (list
                       mode-symbols
                       (or language-id
-                          (or (get mode 'eglot-language-id)
-                              (get spec 'eglot-language-id)
-                              (string-remove-suffix "-mode" (symbol-name mode))))
+                        (or (get mode 'eglot-language-id)
+                          (get spec 'eglot-language-id)
+                          (string-remove-suffix "-mode" (symbol-name mode))))
                       contact))))
-            (if (or (symbolp modes) (keywordp (cadr modes)))
+              (if (or (symbolp modes) (keywordp (cadr modes)))
                 (list modes) modes))))
 
 (defun eglot--guess-contact (&optional interactive)
@@ -952,76 +956,76 @@ Return (MANAGED-MODE PROJECT CLASS CONTACT LANG-ID).  If INTERACTIVE is
 non-nil, maybe prompt user, else error as soon as something can't
 be guessed."
   (let* ((guessed-mode (if buffer-file-name major-mode))
-         (main-mode
-          (cond
-           ((and interactive
+          (main-mode
+            (cond
+              ((and interactive
                  (or (>= (prefix-numeric-value current-prefix-arg) 16)
-                     (not guessed-mode)))
-            (intern
-             (completing-read
-              "[eglot] Start a server to manage buffers of what major mode? "
-              (mapcar #'symbol-name (eglot--all-major-modes)) nil t
-              (symbol-name guessed-mode) nil (symbol-name guessed-mode) nil)))
-           ((not guessed-mode)
-            (eglot--error "Can't guess mode to manage for `%s'" (current-buffer)))
-           (t guessed-mode)))
-         (triplet (eglot--lookup-mode main-mode))
-         (managed-modes (car triplet))
-         (language-id (or (cadr triplet)
-                          (string-remove-suffix "-mode" (symbol-name guessed-mode))))
-         (guess (caddr triplet))
-         (guess (if (functionp guess)
-                    (funcall guess interactive)
-                  guess))
-         (class (or (and (consp guess) (symbolp (car guess))
-                         (prog1 (unless current-prefix-arg (car guess))
-                           (setq guess (cdr guess))))
-                    'eglot-lsp-server))
-         (program (and (listp guess)
-                       (stringp (car guess))
-                       ;; A second element might be the port of a (host, port)
-                       ;; pair, but in that case it is not a string.
-                       (or (null (cdr guess)) (stringp (cadr guess)))
-                       (car guess)))
-         (base-prompt
-          (and interactive
-               "Enter program to execute (or <host>:<port>): "))
-         (full-program-invocation
-          (and program
-               (cl-every #'stringp guess)
-               (combine-and-quote-strings guess)))
-         (prompt
-          (and base-prompt
-               (cond (current-prefix-arg base-prompt)
-                     ((null guess)
-                      (format "[eglot] Sorry, couldn't guess for `%s'!\n%s"
-                              main-mode base-prompt))
-                     ((and program
-                           (not (file-name-absolute-p program))
-                           (not (eglot--executable-find program t)))
-                      (if full-program-invocation
-                          (concat (format "[eglot] I guess you want to run `%s'"
-                                          full-program-invocation)
-                                  (format ", but I can't find `%s' in PATH!"
-                                          program)
-                                  "\n" base-prompt)
-                        (eglot--error
-                         (concat "`%s' not found in PATH, but can't form"
-                                 " an interactive prompt for to fix %s!")
-                         program guess))))))
-         (contact
-          (or (and prompt
-                   (split-string-and-unquote
+                   (not guessed-mode)))
+                (intern
+                  (completing-read
+                    "[eglot] Start a server to manage buffers of what major mode? "
+                    (mapcar #'symbol-name (eglot--all-major-modes)) nil t
+                    (symbol-name guessed-mode) nil (symbol-name guessed-mode) nil)))
+              ((not guessed-mode)
+                (eglot--error "Can't guess mode to manage for `%s'" (current-buffer)))
+              (t guessed-mode)))
+          (triplet (eglot--lookup-mode main-mode))
+          (managed-modes (car triplet))
+          (language-id (or (cadr triplet)
+                         (string-remove-suffix "-mode" (symbol-name guessed-mode))))
+          (guess (caddr triplet))
+          (guess (if (functionp guess)
+                   (funcall guess interactive)
+                   guess))
+          (class (or (and (consp guess) (symbolp (car guess))
+                       (prog1 (unless current-prefix-arg (car guess))
+                         (setq guess (cdr guess))))
+                   'eglot-lsp-server))
+          (program (and (listp guess)
+                     (stringp (car guess))
+                     ;; A second element might be the port of a (host, port)
+                     ;; pair, but in that case it is not a string.
+                     (or (null (cdr guess)) (stringp (cadr guess)))
+                     (car guess)))
+          (base-prompt
+            (and interactive
+              "Enter program to execute (or <host>:<port>): "))
+          (full-program-invocation
+            (and program
+              (cl-every #'stringp guess)
+              (combine-and-quote-strings guess)))
+          (prompt
+            (and base-prompt
+              (cond (current-prefix-arg base-prompt)
+                ((null guess)
+                  (format "[eglot] Sorry, couldn't guess for `%s'!\n%s"
+                    main-mode base-prompt))
+                ((and program
+                   (not (file-name-absolute-p program))
+                   (not (eglot--executable-find program t)))
+                  (if full-program-invocation
+                    (concat (format "[eglot] I guess you want to run `%s'"
+                              full-program-invocation)
+                      (format ", but I can't find `%s' in PATH!"
+                        program)
+                      "\n" base-prompt)
+                    (eglot--error
+                      (concat "`%s' not found in PATH, but can't form"
+                        " an interactive prompt for to fix %s!")
+                      program guess))))))
+          (contact
+            (or (and prompt
+                  (split-string-and-unquote
                     (read-shell-command
-                     prompt
-                     full-program-invocation
-                     'eglot-command-history)))
+                      prompt
+                      full-program-invocation
+                      'eglot-command-history)))
               guess)))
     (list managed-modes (eglot--current-project) class contact language-id)))
 
 (defvar eglot-lsp-context)
 (put 'eglot-lsp-context 'variable-documentation
-     "Dynamically non-nil when searching for projects in LSP context.")
+  "Dynamically non-nil when searching for projects in LSP context.")
 
 (defvar eglot--servers-by-xrefed-file
   (make-hash-table :test 'equal :weakness 'value))
@@ -1038,7 +1042,7 @@ suitable root directory for a given LSP server's purposes."
 
 ;;;###autoload
 (defun eglot (managed-major-mode project class contact language-id
-                                 &optional interactive)
+               &optional interactive)
   "Manage a project with a Language Server Protocol (LSP) server.
 
 The LSP server of CLASS is started (or contacted) via CONTACT.
@@ -1074,11 +1078,11 @@ MANAGED-MAJOR-MODE, which matters to a minority of servers.
 INTERACTIVE is t if called interactively."
   (interactive (append (eglot--guess-contact t) '(t)))
   (let* ((current-server (eglot-current-server))
-         (live-p (and current-server (jsonrpc-running-p current-server))))
+          (live-p (and current-server (jsonrpc-running-p current-server))))
     (if (and live-p
-             interactive
-             (y-or-n-p "[eglot] Live process found, reconnect instead? "))
-        (eglot-reconnect current-server interactive)
+          interactive
+          (y-or-n-p "[eglot] Live process found, reconnect instead? "))
+      (eglot-reconnect current-server interactive)
       (when live-p (ignore-errors (eglot-shutdown current-server)))
       (eglot--connect managed-major-mode project class contact language-id))))
 
@@ -1089,10 +1093,10 @@ INTERACTIVE is t if called interactively."
   (when (jsonrpc-running-p server)
     (ignore-errors (eglot-shutdown server interactive nil 'preserve-buffers)))
   (eglot--connect (eglot--major-modes server)
-                  (eglot--project server)
-                  (eieio-object-class-name server)
-                  (eglot--saved-initargs server)
-                  (eglot--language-id server))
+    (eglot--project server)
+    (eieio-object-class-name server)
+    (eglot--saved-initargs server)
+    (eglot--language-id server))
   (eglot--message "Reconnected!"))
 
 (defvar eglot--managed-mode) ; forward decl
@@ -1102,12 +1106,12 @@ INTERACTIVE is t if called interactively."
   "Start Eglot session for current buffer if there isn't one."
   (let ((buffer (current-buffer)))
     (cl-labels
-        ((maybe-connect
-          ()
-          (remove-hook 'post-command-hook #'maybe-connect nil)
-          (eglot--when-live-buffer buffer
-            (unless eglot--managed-mode
-              (apply #'eglot--connect (eglot--guess-contact))))))
+      ((maybe-connect
+         ()
+         (remove-hook 'post-command-hook #'maybe-connect nil)
+         (eglot--when-live-buffer buffer
+           (unless eglot--managed-mode
+             (apply #'eglot--connect (eglot--guess-contact))))))
       (when buffer-file-name
         (add-hook 'post-command-hook #'maybe-connect 'append nil)))))
 
@@ -1117,8 +1121,8 @@ Use current server's or first available Eglot events buffer."
   (interactive (list (eglot-current-server)))
   (let ((buffer (if server (jsonrpc-events-buffer server)
                   (cl-find "\\*EGLOT.*events\\*"
-                           (buffer-list)
-                           :key #'buffer-name :test #'string-match))))
+                    (buffer-list)
+                    :key #'buffer-name :test #'string-match))))
     (if buffer (display-buffer buffer)
       (eglot--error "Can't find an Eglot events buffer!"))))
 
@@ -1150,16 +1154,16 @@ Each function is passed the server as an argument")
 (defun eglot--cmd (contact)
   "Helper for `eglot--connect'."
   (if (file-remote-p default-directory)
-      ;; TODO: this seems like a bug, although it’s everywhere. For
-      ;; some reason, for remote connections only, over a pipe, we
-      ;; need to turn off line buffering on the tty.
-      ;;
-      ;; Not only does this seem like there should be a better way,
-      ;; but it almost certainly doesn’t work on non-unix systems.
-      (list "sh" "-c"
-            (string-join (cons "stty raw > /dev/null;"
-                               (mapcar #'shell-quote-argument contact))
-             " "))
+    ;; TODO: this seems like a bug, although it’s everywhere. For
+    ;; some reason, for remote connections only, over a pipe, we
+    ;; need to turn off line buffering on the tty.
+    ;;
+    ;; Not only does this seem like there should be a better way,
+    ;; but it almost certainly doesn’t work on non-unix systems.
+    (list "sh" "-c"
+      (string-join (cons "stty raw > /dev/null;"
+                     (mapcar #'shell-quote-argument contact))
+        " "))
     contact))
 
 (defvar-local eglot--cached-server nil
@@ -1169,64 +1173,64 @@ Each function is passed the server as an argument")
   "Connect to MANAGED-MODES, LANGUAGE-ID, PROJECT, CLASS and CONTACT.
 This docstring appeases checkdoc, that's all."
   (let* ((default-directory (project-root project))
-         (nickname (file-name-base (directory-file-name default-directory)))
-         (readable-name (format "EGLOT (%s/%s)" nickname managed-modes))
-         autostart-inferior-process
-         server-info
-         (contact (if (functionp contact) (funcall contact) contact))
-         (initargs
-          (cond ((keywordp (car contact)) contact)
-                ((integerp (cadr contact))
-                 (setq server-info (list (format "%s:%s" (car contact)
-                                                 (cadr contact))))
-                 `(:process ,(lambda ()
-                               (apply #'open-network-stream
-                                      readable-name nil
-                                      (car contact) (cadr contact)
-                                      (cddr contact)))))
-                ((and (stringp (car contact)) (memq :autoport contact))
-                 (setq server-info (list "<inferior process>"))
-                 `(:process ,(lambda ()
-                               (pcase-let ((`(,connection . ,inferior)
+          (nickname (file-name-base (directory-file-name default-directory)))
+          (readable-name (format "EGLOT (%s/%s)" nickname managed-modes))
+          autostart-inferior-process
+          server-info
+          (contact (if (functionp contact) (funcall contact) contact))
+          (initargs
+            (cond ((keywordp (car contact)) contact)
+              ((integerp (cadr contact))
+                (setq server-info (list (format "%s:%s" (car contact)
+                                          (cadr contact))))
+                `(:process ,(lambda ()
+                              (apply #'open-network-stream
+                                readable-name nil
+                                (car contact) (cadr contact)
+                                (cddr contact)))))
+              ((and (stringp (car contact)) (memq :autoport contact))
+                (setq server-info (list "<inferior process>"))
+                `(:process ,(lambda ()
+                              (pcase-let ((`(,connection . ,inferior)
                                             (eglot--inferior-bootstrap
-                                             readable-name
-                                             contact)))
-                                 (setq autostart-inferior-process inferior)
-                                 connection))))
-                ((stringp (car contact))
-                 (let* ((probe (cl-position-if #'keywordp contact))
+                                              readable-name
+                                              contact)))
+                                (setq autostart-inferior-process inferior)
+                                connection))))
+              ((stringp (car contact))
+                (let* ((probe (cl-position-if #'keywordp contact))
                         (more-initargs (and probe (cl-subseq contact probe)))
                         (contact (cl-subseq contact 0 probe)))
-                   `(:process
+                  `(:process
                      ,(lambda ()
                         (let ((default-directory default-directory))
                           (make-process
-                           :name readable-name
-                           :command (setq server-info (eglot--cmd contact))
-                           :connection-type 'pipe
-                           :coding 'utf-8-emacs-unix
-                           :noquery t
-                           :stderr (get-buffer-create
-                                    (format "*%s stderr*" readable-name))
-                           :file-handler t)))
+                            :name readable-name
+                            :command (setq server-info (eglot--cmd contact))
+                            :connection-type 'pipe
+                            :coding 'utf-8-emacs-unix
+                            :noquery t
+                            :stderr (get-buffer-create
+                                      (format "*%s stderr*" readable-name))
+                            :file-handler t)))
                      ,@more-initargs)))))
-         (spread (lambda (fn) (lambda (server method params)
-                                (let ((eglot--cached-server server))
-                                 (apply fn server method (append params nil))))))
-         (server
-          (apply
-           #'make-instance class
-           :name readable-name
-           :events-buffer-scrollback-size eglot-events-buffer-size
-           :notification-dispatcher (funcall spread #'eglot-handle-notification)
-           :request-dispatcher (funcall spread #'eglot-handle-request)
-           :on-shutdown #'eglot--on-shutdown
-           initargs))
-         (cancelled nil)
-         (tag (make-symbol "connected-catch-tag")))
+          (spread (lambda (fn) (lambda (server method params)
+                                 (let ((eglot--cached-server server))
+                                   (apply fn server method (append params nil))))))
+          (server
+            (apply
+              #'make-instance class
+              :name readable-name
+              :events-buffer-scrollback-size eglot-events-buffer-size
+              :notification-dispatcher (funcall spread #'eglot-handle-notification)
+              :request-dispatcher (funcall spread #'eglot-handle-request)
+              :on-shutdown #'eglot--on-shutdown
+              initargs))
+          (cancelled nil)
+          (tag (make-symbol "connected-catch-tag")))
     (when server-info
       (jsonrpc--debug server "Running language server: %s"
-                      (string-join server-info " ")))
+        (string-join server-info " ")))
     (setf (eglot--saved-initargs server) initargs)
     (setf (eglot--project server) project)
     (setf (eglot--project-nickname server) nickname)
@@ -1238,91 +1242,91 @@ This docstring appeases checkdoc, that's all."
     ;; maybe-sync-maybe-async semantics we use `jsonrpc-async-request'
     ;; and mimic most of `jsonrpc-request'.
     (unwind-protect
-        (condition-case _quit
-            (let ((retval
-                   (catch tag
-                     (jsonrpc-async-request
-                      server
-                      :initialize
-                      (list :processId
-                            (unless (or eglot-withhold-process-id
-                                        (file-remote-p default-directory)
-                                        (eq (jsonrpc-process-type server)
-                                            'network))
-                              (emacs-pid))
-                            ;; Maybe turn trampy `/ssh:foo@bar:/path/to/baz.py'
-                            ;; into `/path/to/baz.py', so LSP groks it.
-                            :rootPath (file-local-name
-                                       (expand-file-name default-directory))
-                            :rootUri (eglot--path-to-uri default-directory)
-                            :initializationOptions (eglot-initialization-options
-                                                    server)
-                            :capabilities (eglot-client-capabilities server)
-                            :workspaceFolders (eglot-workspace-folders server))
-                      :success-fn
-                      (eglot--lambda ((InitializeResult) capabilities serverInfo)
-                        (unless cancelled
-                          (push server
-                                (gethash project eglot--servers-by-project))
-                          (setf (eglot--capabilities server) capabilities)
-                          (setf (eglot--server-info server) serverInfo)
-                          (jsonrpc-notify server :initialized eglot--{})
-                          (dolist (buffer (buffer-list))
-                            (with-current-buffer buffer
-                              ;; No need to pass SERVER as an argument: it has
-                              ;; been registered in `eglot--servers-by-project',
-                              ;; so that it can be found (and cached) from
-                              ;; `eglot--maybe-activate-editing-mode' in any
-                              ;; managed buffer.
-                              (eglot--maybe-activate-editing-mode)))
-                          (setf (eglot--inhibit-autoreconnect server)
-                                (cond
-                                 ((booleanp eglot-autoreconnect)
-                                  (not eglot-autoreconnect))
-                                 ((cl-plusp eglot-autoreconnect)
-                                  (run-with-timer
-                                   eglot-autoreconnect nil
-                                   (lambda ()
-                                     (setf (eglot--inhibit-autoreconnect server)
-                                           (null eglot-autoreconnect)))))))
-                          (let ((default-directory (project-root project))
-                                (major-mode (car managed-modes)))
-                            (hack-dir-local-variables-non-file-buffer)
-                            (run-hook-with-args 'eglot-connect-hook server))
-                          (eglot--message
-                           "Connected! Server `%s' now managing `%s' buffers \
+      (condition-case _quit
+        (let ((retval
+                (catch tag
+                  (jsonrpc-async-request
+                    server
+                    :initialize
+                    (list :processId
+                      (unless (or eglot-withhold-process-id
+                                (file-remote-p default-directory)
+                                (eq (jsonrpc-process-type server)
+                                  'network))
+                        (emacs-pid))
+                      ;; Maybe turn trampy `/ssh:foo@bar:/path/to/baz.py'
+                      ;; into `/path/to/baz.py', so LSP groks it.
+                      :rootPath (file-local-name
+                                  (expand-file-name default-directory))
+                      :rootUri (eglot--path-to-uri default-directory)
+                      :initializationOptions (eglot-initialization-options
+                                               server)
+                      :capabilities (eglot-client-capabilities server)
+                      :workspaceFolders (eglot-workspace-folders server))
+                    :success-fn
+                    (eglot--lambda ((InitializeResult) capabilities serverInfo)
+                      (unless cancelled
+                        (push server
+                          (gethash project eglot--servers-by-project))
+                        (setf (eglot--capabilities server) capabilities)
+                        (setf (eglot--server-info server) serverInfo)
+                        (jsonrpc-notify server :initialized eglot--{})
+                        (dolist (buffer (buffer-list))
+                          (with-current-buffer buffer
+                            ;; No need to pass SERVER as an argument: it has
+                            ;; been registered in `eglot--servers-by-project',
+                            ;; so that it can be found (and cached) from
+                            ;; `eglot--maybe-activate-editing-mode' in any
+                            ;; managed buffer.
+                            (eglot--maybe-activate-editing-mode)))
+                        (setf (eglot--inhibit-autoreconnect server)
+                          (cond
+                            ((booleanp eglot-autoreconnect)
+                              (not eglot-autoreconnect))
+                            ((cl-plusp eglot-autoreconnect)
+                              (run-with-timer
+                                eglot-autoreconnect nil
+                                (lambda ()
+                                  (setf (eglot--inhibit-autoreconnect server)
+                                    (null eglot-autoreconnect)))))))
+                        (let ((default-directory (project-root project))
+                               (major-mode (car managed-modes)))
+                          (hack-dir-local-variables-non-file-buffer)
+                          (run-hook-with-args 'eglot-connect-hook server))
+                        (eglot--message
+                          "Connected! Server `%s' now managing `%s' buffers \
 in project `%s'."
-                           (or (plist-get serverInfo :name)
-                               (jsonrpc-name server))
-                           managed-modes
-                           (eglot-project-nickname server))
-                          (when tag (throw tag t))))
-                      :timeout eglot-connect-timeout
-                      :error-fn (eglot--lambda ((ResponseError) code message)
+                          (or (plist-get serverInfo :name)
+                            (jsonrpc-name server))
+                          managed-modes
+                          (eglot-project-nickname server))
+                        (when tag (throw tag t))))
+                    :timeout eglot-connect-timeout
+                    :error-fn (eglot--lambda ((ResponseError) code message)
+                                (unless cancelled
+                                  (jsonrpc-shutdown server)
+                                  (let ((msg (format "%s: %s" code message)))
+                                    (if tag (throw tag `(error . ,msg))
+                                      (eglot--error msg)))))
+                    :timeout-fn (lambda ()
                                   (unless cancelled
                                     (jsonrpc-shutdown server)
-                                    (let ((msg (format "%s: %s" code message)))
+                                    (let ((msg (format "Timed out after %s seconds"
+                                                 eglot-connect-timeout)))
                                       (if tag (throw tag `(error . ,msg))
-                                        (eglot--error msg)))))
-                      :timeout-fn (lambda ()
-                                    (unless cancelled
-                                      (jsonrpc-shutdown server)
-                                      (let ((msg (format "Timed out after %s seconds"
-                                                         eglot-connect-timeout)))
-                                        (if tag (throw tag `(error . ,msg))
-                                          (eglot--error msg))))))
-                     (cond ((numberp eglot-sync-connect)
-                            (accept-process-output nil eglot-sync-connect))
-                           (eglot-sync-connect
-                            (while t (accept-process-output
-                                      nil eglot-connect-timeout)))))))
-              (pcase retval
-                (`(error . ,msg) (eglot--error msg))
-                (`nil (eglot--message "Waiting in background for server `%s'"
-                                      (jsonrpc-name server))
-                      nil)
-                (_ server)))
-          (quit (jsonrpc-shutdown server) (setq cancelled 'quit)))
+                                        (eglot--error msg))))))
+                  (cond ((numberp eglot-sync-connect)
+                          (accept-process-output nil eglot-sync-connect))
+                    (eglot-sync-connect
+                      (while t (accept-process-output
+                                 nil eglot-connect-timeout)))))))
+          (pcase retval
+            (`(error . ,msg) (eglot--error msg))
+            (`nil (eglot--message "Waiting in background for server `%s'"
+                    (jsonrpc-name server))
+              nil)
+            (_ server)))
+        (quit (jsonrpc-shutdown server) (setq cancelled 'quit)))
       (setq tag nil))))
 
 (defun eglot--inferior-bootstrap (name contact &optional connect-args)
@@ -1332,47 +1336,47 @@ Name both based on NAME.
 CONNECT-ARGS are passed as additional arguments to
 `open-network-stream'."
   (let* ((port-probe (make-network-process :name "eglot-port-probe-dummy"
-                                           :server t
-                                           :host "localhost"
-                                           :service 0))
-         (port-number (unwind-protect
-                          (process-contact port-probe :service)
-                        (delete-process port-probe)))
-         inferior connection)
+                       :server t
+                       :host "localhost"
+                       :service 0))
+          (port-number (unwind-protect
+                         (process-contact port-probe :service)
+                         (delete-process port-probe)))
+          inferior connection)
     (unwind-protect
-        (progn
-          (setq inferior
-                (make-process
-                 :name (format "autostart-inferior-%s" name)
-                 :stderr (format "*%s stderr*" name)
-                 :noquery t
-                 :command (cl-subst
-                           (format "%s" port-number) :autoport contact)))
-          (setq connection
-                (cl-loop
-                 repeat 10 for i from 1
-                 do (accept-process-output nil 0.5)
-                 while (process-live-p inferior)
-                 do (eglot--message
-                     "Trying to connect to localhost and port %s (attempt %s)"
-                     port-number i)
-                 thereis (ignore-errors
-                           (apply #'open-network-stream
-                                  (format "autoconnect-%s" name)
-                                  nil
-                                  "localhost" port-number connect-args))))
-          (cons connection inferior))
+      (progn
+        (setq inferior
+          (make-process
+            :name (format "autostart-inferior-%s" name)
+            :stderr (format "*%s stderr*" name)
+            :noquery t
+            :command (cl-subst
+                       (format "%s" port-number) :autoport contact)))
+        (setq connection
+          (cl-loop
+            repeat 10 for i from 1
+            do (accept-process-output nil 0.5)
+            while (process-live-p inferior)
+            do (eglot--message
+                 "Trying to connect to localhost and port %s (attempt %s)"
+                 port-number i)
+            thereis (ignore-errors
+                      (apply #'open-network-stream
+                        (format "autoconnect-%s" name)
+                        nil
+                        "localhost" port-number connect-args))))
+        (cons connection inferior))
       (cond ((and (process-live-p connection)
-                  (process-live-p inferior))
-             (eglot--message "Done, connected to %s!" port-number))
-            (t
-             (when inferior (delete-process inferior))
-             (when connection (delete-process connection))
-             (eglot--error "Could not start and connect to server%s"
-                           (if inferior
-                               (format " started with %s"
-                                       (process-command inferior))
-                             "!")))))))
+               (process-live-p inferior))
+              (eglot--message "Done, connected to %s!" port-number))
+        (t
+          (when inferior (delete-process inferior))
+          (when connection (delete-process connection))
+          (eglot--error "Could not start and connect to server%s"
+            (if inferior
+              (format " started with %s"
+                (process-command inferior))
+              "!")))))))
 
 \f
 ;;; Helpers (move these to API?)
@@ -1407,17 +1411,17 @@ fully LSP-compliant servers, this should be set to
   "Calculate current COLUMN as defined by the LSP spec.
 LBP defaults to `line-beginning-position'."
   (/ (- (length (encode-coding-region (or lbp (line-beginning-position))
-                                      ;; Fix github#860
-                                      (min (point) (point-max)) 'utf-16 t))
-        2)
-     2))
+                  ;; Fix github#860
+                  (min (point) (point-max)) 'utf-16 t))
+       2)
+    2))
 
 (defun eglot--pos-to-lsp-position (&optional pos)
   "Convert point POS to LSP position."
   (eglot--widening
-   (list :line (1- (line-number-at-pos pos t)) ; F!@&#$CKING OFF-BY-ONE
-         :character (progn (when pos (goto-char pos))
-                           (funcall eglot-current-column-function)))))
+    (list :line (1- (line-number-at-pos pos t)) ; F!@&#$CKING OFF-BY-ONE
+      :character (progn (when pos (goto-char pos))
+                   (funcall eglot-current-column-function)))))
 
 (defvar eglot-move-to-column-function #'eglot-move-to-lsp-abiding-column
   "Function to move to a column reported by the LSP server.
@@ -1439,22 +1443,22 @@ be set to `eglot-move-to-lsp-abiding-column' (the default), and
   ;; `whitespace-mode', `prettify-symbols-mode', etc.  (github#296,
   ;; github#297)
   (goto-char (min (+ (line-beginning-position) column)
-                  (line-end-position))))
+               (line-end-position))))
 
 (defun eglot-move-to-lsp-abiding-column (column)
   "Move to COLUMN abiding by the LSP spec."
   (save-restriction
     (cl-loop
-     with lbp = (line-beginning-position)
-     initially
-     (narrow-to-region lbp (line-end-position))
-     (move-to-column column)
-     for diff = (- column
+      with lbp = (line-beginning-position)
+      initially
+      (narrow-to-region lbp (line-end-position))
+      (move-to-column column)
+      for diff = (- column
                    (eglot-lsp-abiding-column lbp))
-     until (zerop diff)
-     do (condition-case eob-err
-            (forward-char (/ (if (> diff 0) (1+ diff) (1- diff)) 2))
-          (end-of-buffer (cl-return eob-err))))))
+      until (zerop diff)
+      do (condition-case eob-err
+           (forward-char (/ (if (> diff 0) (1+ diff) (1- diff)) 2))
+           (end-of-buffer (cl-return eob-err))))))
 
 (defun eglot--lsp-position-to-point (pos-plist &optional marker)
   "Convert LSP position POS-PLIST to Emacs point.
@@ -1464,14 +1468,14 @@ If optional MARKER, return a marker instead"
       (widen)
       (goto-char (point-min))
       (forward-line (min most-positive-fixnum
-                         (plist-get pos-plist :line)))
+                      (plist-get pos-plist :line)))
       (unless (eobp) ;; if line was excessive leave point at eob
         (let ((tab-width 1)
-              (col (plist-get pos-plist :character)))
+               (col (plist-get pos-plist :character)))
           (unless (wholenump col)
             (eglot--warn
-             "Caution: LSP server sent invalid character position %s. Using 0 instead."
-             col)
+              "Caution: LSP server sent invalid character position %s. Using 0 instead."
+              col)
             (setq col 0))
           (funcall eglot-move-to-column-function col)))
       (if marker (copy-marker (point-marker)) (point)))))
@@ -1486,50 +1490,50 @@ If optional MARKER, return a marker instead"
   "URIfy PATH."
   (let ((truepath (file-truename path)))
     (concat "file://"
-            ;; Add a leading "/" for local MS Windows-style paths.
-            (if (and (eq system-type 'windows-nt)
-                     (not (file-remote-p truepath)))
-                "/")
-            (url-hexify-string
-             ;; Again watch out for trampy paths.
-             (directory-file-name (file-local-name truepath))
-             eglot--uri-path-allowed-chars))))
+      ;; Add a leading "/" for local MS Windows-style paths.
+      (if (and (eq system-type 'windows-nt)
+            (not (file-remote-p truepath)))
+        "/")
+      (url-hexify-string
+        ;; Again watch out for trampy paths.
+        (directory-file-name (file-local-name truepath))
+        eglot--uri-path-allowed-chars))))
 
 (defun eglot--uri-to-path (uri)
   "Convert URI to file path, helped by `eglot--current-server'."
   (when (keywordp uri) (setq uri (substring (symbol-name uri) 1)))
   (let* ((server (eglot-current-server))
-         (remote-prefix (and server (eglot--trampish-p server)))
-         (retval (url-unhex-string (url-filename (url-generic-parse-url uri))))
-         ;; Remove the leading "/" for local MS Windows-style paths.
-         (normalized (if (and (not remote-prefix)
-                              (eq system-type 'windows-nt)
-                              (cl-plusp (length retval)))
-                         (substring retval 1)
-                       retval)))
+          (remote-prefix (and server (eglot--trampish-p server)))
+          (retval (url-unhex-string (url-filename (url-generic-parse-url uri))))
+          ;; Remove the leading "/" for local MS Windows-style paths.
+          (normalized (if (and (not remote-prefix)
+                            (eq system-type 'windows-nt)
+                            (cl-plusp (length retval)))
+                        (substring retval 1)
+                        retval)))
     (concat remote-prefix normalized)))
 
 (defun eglot--snippet-expansion-fn ()
   "Compute a function to expand snippets.
 Doubles as an indicator of snippet support."
   (and (boundp 'yas-minor-mode)
-       (symbol-value 'yas-minor-mode)
-       'yas-expand-snippet))
+    (symbol-value 'yas-minor-mode)
+    'yas-expand-snippet))
 
 (defun eglot--format-markup (markup)
   "Format MARKUP according to LSP's spec."
   (pcase-let ((`(,string ,mode)
-               (if (stringp markup) (list markup 'gfm-view-mode)
-                 (list (plist-get markup :value)
-                       (pcase (plist-get markup :kind)
-                         ("markdown" 'gfm-view-mode)
-                         ("plaintext" 'text-mode)
-                         (_ major-mode))))))
+                (if (stringp markup) (list markup 'gfm-view-mode)
+                  (list (plist-get markup :value)
+                    (pcase (plist-get markup :kind)
+                      ("markdown" 'gfm-view-mode)
+                      ("plaintext" 'text-mode)
+                      (_ major-mode))))))
     (with-temp-buffer
       (setq-local markdown-fontify-code-blocks-natively t)
       (insert string)
       (let ((inhibit-message t)
-	    (message-log-max nil))
+	           (message-log-max nil))
         (ignore-errors (delay-mode-hooks (funcall mode))))
       (font-lock-ensure)
       (string-trim (buffer-string)))))
@@ -1543,49 +1547,50 @@ You could add, for instance, the symbol
 `:documentHighlightProvider' to prevent automatic highlighting
 under cursor."
   :type '(set
-          :tag "Tick the ones you're not interested in"
-          (const :tag "Documentation on hover" :hoverProvider)
-          (const :tag "Code completion" :completionProvider)
-          (const :tag "Function signature help" :signatureHelpProvider)
-          (const :tag "Go to definition" :definitionProvider)
-          (const :tag "Go to type definition" :typeDefinitionProvider)
-          (const :tag "Go to implementation" :implementationProvider)
-          (const :tag "Go to declaration" :implementationProvider)
-          (const :tag "Find references" :referencesProvider)
-          (const :tag "Highlight symbols automatically" :documentHighlightProvider)
-          (const :tag "List symbols in buffer" :documentSymbolProvider)
-          (const :tag "List symbols in workspace" :workspaceSymbolProvider)
-          (const :tag "Execute code actions" :codeActionProvider)
-          (const :tag "Code lens" :codeLensProvider)
-          (const :tag "Format buffer" :documentFormattingProvider)
-          (const :tag "Format portion of buffer" :documentRangeFormattingProvider)
-          (const :tag "On-type formatting" :documentOnTypeFormattingProvider)
-          (const :tag "Rename symbol" :renameProvider)
-          (const :tag "Highlight links in document" :documentLinkProvider)
-          (const :tag "Decorate color references" :colorProvider)
-          (const :tag "Fold regions of buffer" :foldingRangeProvider)
-          (const :tag "Execute custom commands" :executeCommandProvider)))
+           :tag "Tick the ones you're not interested in"
+           (const :tag "Documentation on hover" :hoverProvider)
+           (const :tag "Code completion" :completionProvider)
+           (const :tag "Function signature help" :signatureHelpProvider)
+           (const :tag "Go to definition" :definitionProvider)
+           (const :tag "Go to type definition" :typeDefinitionProvider)
+           (const :tag "Go to implementation" :implementationProvider)
+           (const :tag "Go to declaration" :implementationProvider)
+           (const :tag "Find references" :referencesProvider)
+           (const :tag "Highlight symbols automatically" :documentHighlightProvider)
+           (const :tag "List symbols in buffer" :documentSymbolProvider)
+           (const :tag "List symbols in workspace" :workspaceSymbolProvider)
+           (const :tag "Execute code actions" :codeActionProvider)
+           (const :tag "Code lens" :codeLensProvider)
+           (const :tag "Inlay Hint" :inlayHintProvider)
+           (const :tag "Format buffer" :documentFormattingProvider)
+           (const :tag "Format portion of buffer" :documentRangeFormattingProvider)
+           (const :tag "On-type formatting" :documentOnTypeFormattingProvider)
+           (const :tag "Rename symbol" :renameProvider)
+           (const :tag "Highlight links in document" :documentLinkProvider)
+           (const :tag "Decorate color references" :colorProvider)
+           (const :tag "Fold regions of buffer" :foldingRangeProvider)
+           (const :tag "Execute custom commands" :executeCommandProvider)))
 
 (defun eglot--server-capable (&rest feats)
   "Determine if current server is capable of FEATS."
   (unless (cl-some (lambda (feat)
                      (memq feat eglot-ignored-server-capabilities))
-                   feats)
+            feats)
     (cl-loop for caps = (eglot--capabilities (eglot--current-server-or-lose))
-             then (cadr probe)
-             for (feat . more) on feats
-             for probe = (plist-member caps feat)
-             if (not probe) do (cl-return nil)
-             if (eq (cadr probe) :json-false) do (cl-return nil)
-             if (not (listp (cadr probe))) do (cl-return (if more nil (cadr probe)))
-             finally (cl-return (or (cadr probe) t)))))
+      then (cadr probe)
+      for (feat . more) on feats
+      for probe = (plist-member caps feat)
+      if (not probe) do (cl-return nil)
+      if (eq (cadr probe) :json-false) do (cl-return nil)
+      if (not (listp (cadr probe))) do (cl-return (if more nil (cadr probe)))
+      finally (cl-return (or (cadr probe) t)))))
 
 (defun eglot--range-region (range &optional markers)
   "Return region (BEG . END) that represents LSP RANGE.
 If optional MARKERS, make markers."
   (let* ((st (plist-get range :start))
-         (beg (eglot--lsp-position-to-point st markers))
-         (end (eglot--lsp-position-to-point (plist-get range :end) markers)))
+          (beg (eglot--lsp-position-to-point st markers))
+          (end (eglot--lsp-position-to-point (plist-get range :end) markers)))
     (cons beg end)))
 
 (defun eglot--read-server (prompt &optional dont-if-just-the-one)
@@ -1593,26 +1598,26 @@ If optional MARKERS, make markers."
 If DONT-IF-JUST-THE-ONE and there's only one server, don't prompt
 and just return it.  PROMPT shouldn't end with a question mark."
   (let ((servers (cl-loop for servers
-                          being hash-values of eglot--servers-by-project
-                          append servers))
-        (name (lambda (srv)
-                (format "%s %s" (eglot-project-nickname srv)
-                        (eglot--major-modes srv)))))
+                   being hash-values of eglot--servers-by-project
+                   append servers))
+         (name (lambda (srv)
+                 (format "%s %s" (eglot-project-nickname srv)
+                   (eglot--major-modes srv)))))
     (cond ((null servers)
-           (eglot--error "No servers!"))
-          ((or (cdr servers) (not dont-if-just-the-one))
-           (let* ((default (when-let ((current (eglot-current-server)))
-                             (funcall name current)))
-                  (read (completing-read
-                         (if default
-                             (format "%s (default %s)? " prompt default)
-                           (concat prompt "? "))
-                         (mapcar name servers)
-                         nil t
-                         nil nil
-                         default)))
-             (cl-find read servers :key name :test #'equal)))
-          (t (car servers)))))
+            (eglot--error "No servers!"))
+      ((or (cdr servers) (not dont-if-just-the-one))
+        (let* ((default (when-let ((current (eglot-current-server)))
+                          (funcall name current)))
+                (read (completing-read
+                        (if default
+                          (format "%s (default %s)? " prompt default)
+                          (concat prompt "? "))
+                        (mapcar name servers)
+                        nil t
+                        nil nil
+                        default)))
+          (cl-find read servers :key name :test #'equal)))
+      (t (car servers)))))
 
 (defun eglot--trampish-p (server)
   "Tell if SERVER's project root is `file-remote-p'."
@@ -1660,9 +1665,9 @@ For example, to keep your Company customization, add the symbol
 (defun eglot--stay-out-of-p (symbol)
   "Tell if Eglot should stay of of SYMBOL."
   (cl-find (symbol-name symbol) eglot-stay-out-of
-           :test (lambda (s thing)
-                   (let ((re (if (symbolp thing) (symbol-name thing) thing)))
-                     (string-match re s)))))
+    :test (lambda (s thing)
+            (let ((re (if (symbolp thing) (symbol-name thing) thing)))
+              (string-match re s)))))
 
 (defmacro eglot--setq-saving (symbol binding)
   `(unless (or (not (boundp ',symbol)) (eglot--stay-out-of-p ',symbol))
@@ -1681,65 +1686,65 @@ Use `eglot-managed-p' to determine if current buffer is managed.")
   "Mode for source buffers managed by some Eglot project."
   :init-value nil :lighter nil :keymap eglot-mode-map
   (cond
-   (eglot--managed-mode
-    (add-hook 'after-change-functions 'eglot--after-change nil t)
-    (add-hook 'before-change-functions 'eglot--before-change nil t)
-    (add-hook 'kill-buffer-hook #'eglot--managed-mode-off nil t)
-    ;; Prepend "didClose" to the hook after the "nonoff", so it will run first
-    (add-hook 'kill-buffer-hook 'eglot--signal-textDocument/didClose nil t)
-    (add-hook 'before-revert-hook 'eglot--signal-textDocument/didClose nil t)
-    (add-hook 'after-revert-hook 'eglot--after-revert-hook nil t)
-    (add-hook 'before-save-hook 'eglot--signal-textDocument/willSave nil t)
-    (add-hook 'after-save-hook 'eglot--signal-textDocument/didSave nil t)
-    (unless (eglot--stay-out-of-p 'xref)
-      (add-hook 'xref-backend-functions 'eglot-xref-backend nil t))
-    (add-hook 'completion-at-point-functions #'eglot-completion-at-point nil t)
-    (add-hook 'change-major-mode-hook #'eglot--managed-mode-off nil t)
-    (add-hook 'post-self-insert-hook 'eglot--post-self-insert-hook nil t)
-    (add-hook 'pre-command-hook 'eglot--pre-command-hook nil t)
-    (eglot--setq-saving eldoc-documentation-functions
-                        '(eglot-signature-eldoc-function
-                          eglot-hover-eldoc-function))
-    (eglot--setq-saving eldoc-documentation-strategy
-                        #'eldoc-documentation-enthusiast)
-    (eglot--setq-saving xref-prompt-for-identifier nil)
-    (eglot--setq-saving flymake-diagnostic-functions '(eglot-flymake-backend))
-    (eglot--setq-saving company-backends '(company-capf))
-    (eglot--setq-saving company-tooltip-align-annotations t)
-    (unless (eglot--stay-out-of-p 'imenu)
-      (add-function :before-until (local 'imenu-create-index-function)
-                    #'eglot-imenu))
-    (unless (eglot--stay-out-of-p 'flymake) (flymake-mode 1))
-    (unless (eglot--stay-out-of-p 'eldoc) (eldoc-mode 1))
-    (cl-pushnew (current-buffer) (eglot--managed-buffers (eglot-current-server))))
-   (t
-    (remove-hook 'after-change-functions 'eglot--after-change t)
-    (remove-hook 'before-change-functions 'eglot--before-change t)
-    (remove-hook 'kill-buffer-hook #'eglot--managed-mode-off t)
-    (remove-hook 'kill-buffer-hook 'eglot--signal-textDocument/didClose t)
-    (remove-hook 'before-revert-hook 'eglot--signal-textDocument/didClose t)
-    (remove-hook 'after-revert-hook 'eglot--after-revert-hook t)
-    (remove-hook 'before-save-hook 'eglot--signal-textDocument/willSave t)
-    (remove-hook 'after-save-hook 'eglot--signal-textDocument/didSave t)
-    (remove-hook 'xref-backend-functions 'eglot-xref-backend t)
-    (remove-hook 'completion-at-point-functions #'eglot-completion-at-point t)
-    (remove-hook 'change-major-mode-hook #'eglot--managed-mode-off t)
-    (remove-hook 'post-self-insert-hook 'eglot--post-self-insert-hook t)
-    (remove-hook 'pre-command-hook 'eglot--pre-command-hook t)
-    (cl-loop for (var . saved-binding) in eglot--saved-bindings
-             do (set (make-local-variable var) saved-binding))
-    (remove-function (local 'imenu-create-index-function) #'eglot-imenu)
-    (when eglot--current-flymake-report-fn
-      (eglot--report-to-flymake nil)
-      (setq eglot--current-flymake-report-fn nil))
-    (let ((server eglot--cached-server))
-      (setq eglot--cached-server nil)
-      (when server
-        (setf (eglot--managed-buffers server)
-              (delq (current-buffer) (eglot--managed-buffers server)))
-        (when (and eglot-autoshutdown
-                   (null (eglot--managed-buffers server)))
-          (eglot-shutdown server))))))
+    (eglot--managed-mode
+      (add-hook 'after-change-functions 'eglot--after-change nil t)
+      (add-hook 'before-change-functions 'eglot--before-change nil t)
+      (add-hook 'kill-buffer-hook #'eglot--managed-mode-off nil t)
+      ;; Prepend "didClose" to the hook after the "nonoff", so it will run first
+      (add-hook 'kill-buffer-hook 'eglot--signal-textDocument/didClose nil t)
+      (add-hook 'before-revert-hook 'eglot--signal-textDocument/didClose nil t)
+      (add-hook 'after-revert-hook 'eglot--after-revert-hook nil t)
+      (add-hook 'before-save-hook 'eglot--signal-textDocument/willSave nil t)
+      (add-hook 'after-save-hook 'eglot--signal-textDocument/didSave nil t)
+      (unless (eglot--stay-out-of-p 'xref)
+        (add-hook 'xref-backend-functions 'eglot-xref-backend nil t))
+      (add-hook 'completion-at-point-functions #'eglot-completion-at-point nil t)
+      (add-hook 'change-major-mode-hook #'eglot--managed-mode-off nil t)
+      (add-hook 'post-self-insert-hook 'eglot--post-self-insert-hook nil t)
+      (add-hook 'pre-command-hook 'eglot--pre-command-hook nil t)
+      (eglot--setq-saving eldoc-documentation-functions
+        '(eglot-signature-eldoc-function
+           eglot-hover-eldoc-function))
+      (eglot--setq-saving eldoc-documentation-strategy
+        #'eldoc-documentation-enthusiast)
+      (eglot--setq-saving xref-prompt-for-identifier nil)
+      (eglot--setq-saving flymake-diagnostic-functions '(eglot-flymake-backend))
+      (eglot--setq-saving company-backends '(company-capf))
+      (eglot--setq-saving company-tooltip-align-annotations t)
+      (unless (eglot--stay-out-of-p 'imenu)
+        (add-function :before-until (local 'imenu-create-index-function)
+          #'eglot-imenu))
+      (unless (eglot--stay-out-of-p 'flymake) (flymake-mode 1))
+      (unless (eglot--stay-out-of-p 'eldoc) (eldoc-mode 1))
+      (cl-pushnew (current-buffer) (eglot--managed-buffers (eglot-current-server))))
+    (t
+      (remove-hook 'after-change-functions 'eglot--after-change t)
+      (remove-hook 'before-change-functions 'eglot--before-change t)
+      (remove-hook 'kill-buffer-hook #'eglot--managed-mode-off t)
+      (remove-hook 'kill-buffer-hook 'eglot--signal-textDocument/didClose t)
+      (remove-hook 'before-revert-hook 'eglot--signal-textDocument/didClose t)
+      (remove-hook 'after-revert-hook 'eglot--after-revert-hook t)
+      (remove-hook 'before-save-hook 'eglot--signal-textDocument/willSave t)
+      (remove-hook 'after-save-hook 'eglot--signal-textDocument/didSave t)
+      (remove-hook 'xref-backend-functions 'eglot-xref-backend t)
+      (remove-hook 'completion-at-point-functions #'eglot-completion-at-point t)
+      (remove-hook 'change-major-mode-hook #'eglot--managed-mode-off t)
+      (remove-hook 'post-self-insert-hook 'eglot--post-self-insert-hook t)
+      (remove-hook 'pre-command-hook 'eglot--pre-command-hook t)
+      (cl-loop for (var . saved-binding) in eglot--saved-bindings
+        do (set (make-local-variable var) saved-binding))
+      (remove-function (local 'imenu-create-index-function) #'eglot-imenu)
+      (when eglot--current-flymake-report-fn
+        (eglot--report-to-flymake nil)
+        (setq eglot--current-flymake-report-fn nil))
+      (let ((server eglot--cached-server))
+        (setq eglot--cached-server nil)
+        (when server
+          (setf (eglot--managed-buffers server)
+            (delq (current-buffer) (eglot--managed-buffers server)))
+          (when (and eglot-autoshutdown
+                  (null (eglot--managed-buffers server)))
+            (eglot-shutdown server))))))
   ;; Note: the public hook runs before the internal eglot--managed-mode-hook.
   (run-hooks 'eglot-managed-mode-hook))
 
@@ -1750,20 +1755,20 @@ Use `eglot-managed-p' to determine if current buffer is managed.")
 (defun eglot-current-server ()
   "Return logical Eglot server for current buffer, nil if none."
   (setq eglot--cached-server
-        (or eglot--cached-server
-            (cl-find major-mode
-                     (gethash (eglot--current-project) eglot--servers-by-project)
-                     :key #'eglot--major-modes
-                     :test #'memq)
-            (and eglot-extend-to-xref
-                 buffer-file-name
-                 (gethash (expand-file-name buffer-file-name)
-                          eglot--servers-by-xrefed-file)))))
+    (or eglot--cached-server
+      (cl-find major-mode
+        (gethash (eglot--current-project) eglot--servers-by-project)
+        :key #'eglot--major-modes
+        :test #'memq)
+      (and eglot-extend-to-xref
+        buffer-file-name
+        (gethash (expand-file-name buffer-file-name)
+          eglot--servers-by-xrefed-file)))))
 
 (defun eglot--current-server-or-lose ()
   "Return current logical Eglot server connection or error."
   (or (eglot-current-server)
-      (jsonrpc-error "No current JSON-RPC connection")))
+    (jsonrpc-error "No current JSON-RPC connection")))
 
 (defvar-local eglot--diagnostics nil
   "Flymake diagnostics for this buffer.")
@@ -1807,7 +1812,7 @@ If it is activated, also signal textDocument/didOpen."
     (let ((start (event-start event))) (with-selected-window (posn-window start)
                                          (save-excursion
                                            (goto-char (or (posn-point start)
-                                                          (point)))
+                                                        (point)))
                                            (call-interactively what)
                                            (force-mode-line-update t))))))
 
@@ -1816,131 +1821,138 @@ If it is activated, also signal textDocument/didOpen."
 
 (easy-menu-define eglot-menu nil "Eglot"
   `("Eglot"
-    ;; Commands for getting information and customization.
-    ["Read manual" eglot-manual]
-    ["Customize Eglot" (lambda () (interactive) (customize-group "eglot"))]
-    "--"
-    ;; xref like commands.
-    ["Find definitions" xref-find-definitions
-     :help "Find definitions of identifier at point"
-     :active (eglot--server-capable :definitionProvider)]
-    ["Find references" xref-find-references
-     :help "Find references to identifier at point"
-     :active (eglot--server-capable :referencesProvider)]
-    ["Find symbols in workspace (apropos)" xref-find-apropos
-     :help "Find symbols matching a query"
-     :active (eglot--server-capable :workspaceSymbolProvider)]
-    ["Find declaration" eglot-find-declaration
-     :help "Find declaration for identifier at point"
-     :active (eglot--server-capable :declarationProvider)]
-    ["Find implementation" eglot-find-implementation
-     :help "Find implementation for identifier at point"
-     :active (eglot--server-capable :implementationProvider)]
-    ["Find type definition" eglot-find-typeDefinition
-     :help "Find type definition for identifier at point"
-     :active (eglot--server-capable :typeDefinitionProvider)]
-    "--"
-    ;; LSP-related commands (mostly Eglot's own commands).
-    ["Rename symbol" eglot-rename
-     :active (eglot--server-capable :renameProvider)]
-    ["Format buffer" eglot-format-buffer
-     :active (eglot--server-capable :documentFormattingProvider)]
-    ["Format active region" eglot-format
-     :active (and (region-active-p)
-                  (eglot--server-capable :documentRangeFormattingProvider))]
-    ["Show Flymake diagnostics for buffer" flymake-show-buffer-diagnostics]
-    ["Show Flymake diagnostics for project" flymake-show-project-diagnostics]
-    ["Show Eldoc documentation at point" eldoc-doc-buffer]
-    "--"
-    ["All possible code actions" eglot-code-actions
-     :active (eglot--server-capable :codeActionProvider)]
-    ["Organize imports" eglot-code-action-organize-imports
-     :visible (eglot--server-capable :codeActionProvider)]
-    ["Extract" eglot-code-action-extract
-     :visible (eglot--server-capable :codeActionProvider)]
-    ["Inline" eglot-code-action-inline
-     :visible (eglot--server-capable :codeActionProvider)]
-    ["Rewrite" eglot-code-action-rewrite
-     :visible (eglot--server-capable :codeActionProvider)]
-    ["Quickfix" eglot-code-action-quickfix
-     :visible (eglot--server-capable :codeActionProvider)]))
+     ;; Commands for getting information and customization.
+     ["Read manual" eglot-manual]
+     ["Customize Eglot" (lambda () (interactive) (customize-group "eglot"))]
+     "--"
+     ;; xref like commands.
+     ["Find definitions" xref-find-definitions
+       :help "Find definitions of identifier at point"
+       :active (eglot--server-capable :definitionProvider)]
+     ["Find references" xref-find-references
+       :help "Find references to identifier at point"
+       :active (eglot--server-capable :referencesProvider)]
+     ["Find symbols in workspace (apropos)" xref-find-apropos
+       :help "Find symbols matching a query"
+       :active (eglot--server-capable :workspaceSymbolProvider)]
+     ["Find declaration" eglot-find-declaration
+       :help "Find declaration for identifier at point"
+       :active (eglot--server-capable :declarationProvider)]
+     ["Find implementation" eglot-find-implementation
+       :help "Find implementation for identifier at point"
+       :active (eglot--server-capable :implementationProvider)]
+     ["Find type definition" eglot-find-typeDefinition
+       :help "Find type definition for identifier at point"
+       :active (eglot--server-capable :typeDefinitionProvider)]
+     "--"
+     ;; LSP-related commands (mostly Eglot's own commands).
+     ["Rename symbol" eglot-rename
+       :active (eglot--server-capable :renameProvider)]
+     ["Format buffer" eglot-format-buffer
+       :active (eglot--server-capable :documentFormattingProvider)]
+     ["Format active region" eglot-format
+       :active (and (region-active-p)
+                 (eglot--server-capable :documentRangeFormattingProvider))]
+     ["Show Flymake diagnostics for buffer" flymake-show-buffer-diagnostics]
+     ["Show Flymake diagnostics for project" flymake-show-project-diagnostics]
+     ["Show Eldoc documentation at point" eldoc-doc-buffer]
+     "--"
+     ["All possible code actions" eglot-code-actions
+       :active (eglot--server-capable :codeActionProvider)]
+     ["Organize imports" eglot-code-action-organize-imports
+       :visible (eglot--server-capable :codeActionProvider)]
+     ["Extract" eglot-code-action-extract
+       :visible (eglot--server-capable :codeActionProvider)]
+     ["Inline" eglot-code-action-inline
+       :visible (eglot--server-capable :codeActionProvider)]
+     ["Rewrite" eglot-code-action-rewrite
+       :visible (eglot--server-capable :codeActionProvider)]
+     ["Quickfix" eglot-code-action-quickfix
+       :visible (eglot--server-capable :codeActionProvider)]))
 
 (easy-menu-define eglot-server-menu nil "Monitor server communication"
   '("Debugging the server communication"
-    ["Reconnect to server" eglot-reconnect]
-    ["Quit server" eglot-shutdown]
-    "--"
-    ["LSP events buffer" eglot-events-buffer]
-    ["Server stderr buffer" eglot-stderr-buffer]
-    ["Customize event buffer size"
-     (lambda ()
-       (interactive)
-       (customize-variable 'eglot-events-buffer-size))]))
+     ["Reconnect to server" eglot-reconnect]
+     ["Quit server" eglot-shutdown]
+     "--"
+     ["LSP events buffer" eglot-events-buffer]
+     ["Server stderr buffer" eglot-stderr-buffer]
+     ["Customize event buffer size"
+       (lambda ()
+         (interactive)
+         (customize-variable 'eglot-events-buffer-size))]))
 
 (defun eglot--mode-line-props (thing face defs &optional prepend)
   "Helper for function `eglot--mode-line-format'.
 Uses THING, FACE, DEFS and PREPEND."
   (cl-loop with map = (make-sparse-keymap)
-           for (elem . rest) on defs
-           for (key def help) = elem
-           do (define-key map `[mode-line ,key] (eglot--mouse-call def))
-           concat (format "%s: %s" key help) into blurb
-           when rest concat "\n" into blurb
-           finally (return `(:propertize ,thing
-                                         face ,face
-                                         keymap ,map help-echo ,(concat prepend blurb)
-                                         mouse-face mode-line-highlight))))
+    for (elem . rest) on defs
+    for (key def help) = elem
+    do (define-key map `[mode-line ,key] (eglot--mouse-call def))
+    concat (format "%s: %s" key help) into blurb
+    when rest concat "\n" into blurb
+    finally (return `(:propertize ,thing
+                       face ,face
+                       keymap ,map help-echo ,(concat prepend blurb)
+                       mouse-face mode-line-highlight))))
 
 (defun eglot--mode-line-format ()
   "Compose the Eglot's mode-line."
   (pcase-let* ((server (eglot-current-server))
-               (nick (and server (eglot-project-nickname server)))
-               (pending (and server (hash-table-count
-                                     (jsonrpc--request-continuations server))))
-               (`(,_id ,doing ,done-p ,_detail) (and server (eglot--spinner server)))
-               (last-error (and server (jsonrpc-last-error server))))
+                (nick (and server (eglot-project-nickname server)))
+                (pending (and server (hash-table-count
+                                       (jsonrpc--request-continuations server))))
+                (`(,_id ,doing ,done-p ,_detail) (and server (eglot--spinner server)))
+                (last-error (and server (jsonrpc-last-error server))))
     (append
-     `(,(propertize
-         eglot-menu-string
-         'face 'eglot-mode-line
-         'mouse-face 'mode-line-highlight
-         'help-echo "Eglot: Emacs LSP client\nmouse-1: Display minor mode menu"
-         'keymap (let ((map (make-sparse-keymap)))
-                   (define-key map [mode-line down-mouse-1] eglot-menu)
-                   map)))
-     (when nick
-       `(":"
-         ,(propertize
-           nick
+      `(,(propertize
+           eglot-menu-string
            'face 'eglot-mode-line
            'mouse-face 'mode-line-highlight
-           'help-echo (format "Project '%s'\nmouse-1: LSP server control menu" nick)
+           'help-echo "Eglot: Emacs LSP client\nmouse-1: Display minor mode menu"
            'keymap (let ((map (make-sparse-keymap)))
-                     (define-key map [mode-line down-mouse-1] eglot-server-menu)
-                     map))
-       ,@(when last-error
-             `("/" ,(eglot--mode-line-props
-                     "error" 'compilation-mode-line-fail
-                     '((mouse-3 eglot-clear-status  "Clear this status"))
-                     (format "An error occurred: %s\n" (plist-get last-error
-                                                                 :message)))))
-         ,@(when (and doing (not done-p))
-             `("/" ,(eglot--mode-line-props doing
-                                            'compilation-mode-line-run '())))
-         ,@(when (cl-plusp pending)
-             `("/" ,(eglot--mode-line-props
-                     (format "%d" pending) 'warning
-                     '((mouse-3 eglot-forget-pending-continuations
-                                "Forget pending continuations"))
-                     "Number of outgoing, \
+                     (define-key map [mode-line down-mouse-1] eglot-menu)
+                     map)))
+      (when nick
+        `(":"
+           ,(propertize
+              nick
+              'face 'eglot-mode-line
+              'mouse-face 'mode-line-highlight
+              'help-echo (format "Project '%s'\nmouse-1: LSP server control menu" nick)
+              'keymap (let ((map (make-sparse-keymap)))
+                        (define-key map [mode-line down-mouse-1] eglot-server-menu)
+                        map))
+           ,@(when last-error
+               `("/" ,(eglot--mode-line-props
+                        "error" 'compilation-mode-line-fail
+                        '((mouse-3 eglot-clear-status  "Clear this status"))
+                        (format "An error occurred: %s\n" (plist-get last-error
+                                                            :message)))))
+           ,@(when (and doing (not done-p))
+               `("/" ,(eglot--mode-line-props doing
+                        'compilation-mode-line-run '())))
+           ,@(when (cl-plusp pending)
+               `("/" ,(eglot--mode-line-props
+                        (format "%d" pending) 'warning
+                        '((mouse-3 eglot-forget-pending-continuations
+                            "Forget pending continuations"))
+                        "Number of outgoing, \
 still unanswered LSP requests to the server\n"))))))))
 
 (add-to-list 'mode-line-misc-info
-             `(eglot--managed-mode (" [" eglot--mode-line-format "] ")))
+  `(eglot--managed-mode (" [" eglot--mode-line-format "] ")))
 
 \f
 ;;; Flymake customization
 ;;;
+;; TODO: type hint and different signature hint? (and future proof generic hint?)
+;;(put 'eglot-hint 'flymake-bitmap nil)
+;;(put 'eglot-hint 'severity (warning-numeric-level :debug))
+;;(put 'eglot-hint 'flymake-type-name "hint")
+;;(put 'eglot-hint 'face nil)
+(put 'eglot-hint 'flymake-category 'flymake-note)
+
 (put 'eglot-note 'flymake-category 'flymake-note)
 (put 'eglot-warning 'flymake-category 'flymake-warning)
 (put 'eglot-error 'flymake-category 'flymake-error)
@@ -1949,14 +1961,14 @@ still unanswered LSP requests to the server\n"))))))))
 (defalias 'eglot--diag-data 'flymake-diagnostic-data)
 
 (cl-loop for i from 1
-         for type in '(eglot-note eglot-warning eglot-error )
-         do (put type 'flymake-overlay-control
-                 `((mouse-face . highlight)
-                   (priority . ,(+ 50 i))
-                   (keymap . ,(let ((map (make-sparse-keymap)))
-                                (define-key map [mouse-1]
-                                  (eglot--mouse-call 'eglot-code-actions))
-                                map)))))
+  for type in '(eglot-note eglot-warning eglot-error )
+  do (put type 'flymake-overlay-control
+       `((mouse-face . highlight)
+          (priority . ,(+ 50 i))
+          (keymap . ,(let ((map (make-sparse-keymap)))
+                       (define-key map [mouse-1]
+                         (eglot--mouse-call 'eglot-code-actions))
+                       map)))))
 
 \f
 ;;; Protocol implementation (Requests, notifications, etc)
@@ -1965,7 +1977,7 @@ still unanswered LSP requests to the server\n"))))))))
   (_server method &key &allow-other-keys)
   "Handle unknown notification."
   (unless (or (string-prefix-p "$" (format "%s" method))
-              (not (memq 'disallow-unknown-methods eglot-strict-mode)))
+            (not (memq 'disallow-unknown-methods eglot-strict-mode)))
     (eglot--warn "Server sent unknown notification method `%s'" method)))
 
 (cl-defmethod eglot-handle-request
@@ -1979,28 +1991,28 @@ still unanswered LSP requests to the server\n"))))))))
   "Execute COMMAND on SERVER with `:workspace/executeCommand'.
 COMMAND is a symbol naming the command."
   (jsonrpc-request server :workspace/executeCommand
-                   `(:command ,(format "%s" command) :arguments ,arguments)))
+    `(:command ,(format "%s" command) :arguments ,arguments)))
 
 (cl-defmethod eglot-handle-notification
   (_server (_method (eql window/showMessage)) &key type message)
   "Handle notification window/showMessage."
   (eglot--message (propertize "Server reports (type=%s): %s"
-                              'face (if (<= type 1) 'error))
-                  type message))
+                    'face (if (<= type 1) 'error))
+    type message))
 
 (cl-defmethod eglot-handle-request
   (_server (_method (eql window/showMessageRequest)) &key type message actions)
   "Handle server request window/showMessageRequest."
   (let* ((actions (append actions nil)) ;; gh#627
-         (label (completing-read
-                 (concat
-                  (format (propertize "[eglot] Server reports (type=%s): %s"
-                                      'face (if (<= type 1) 'error))
-                          type message)
-                  "\nChoose an option: ")
-                 (or (mapcar (lambda (obj) (plist-get obj :title)) actions)
+          (label (completing-read
+                   (concat
+                     (format (propertize "[eglot] Server reports (type=%s): %s"
+                               'face (if (<= type 1) 'error))
+                       type message)
+                     "\nChoose an option: ")
+                   (or (mapcar (lambda (obj) (plist-get obj :title)) actions)
                      '("OK"))
-                 nil t (plist-get (elt actions 0) :title))))
+                   nil t (plist-get (elt actions 0) :title))))
     (if label `(:title ,label) :null)))
 
 (cl-defmethod eglot-handle-notification
@@ -2013,85 +2025,85 @@ COMMAND is a symbol naming the command."
 
 (cl-defmethod eglot-handle-notification
   (_server (_method (eql textDocument/publishDiagnostics)) &key uri diagnostics
-           &allow-other-keys) ; FIXME: doesn't respect `eglot-strict-mode'
+    &allow-other-keys) ; FIXME: doesn't respect `eglot-strict-mode'
   "Handle notification publishDiagnostics."
   (cl-flet ((eglot--diag-type (sev)
               (cond ((null sev) 'eglot-error)
-                    ((<= sev 1) 'eglot-error)
-                    ((= sev 2)  'eglot-warning)
-                    (t          'eglot-note)))
-            (mess (source code message)
-              (concat source (and code (format " [%s]" code)) ": " message)))
+                ((<= sev 1) 'eglot-error)
+                ((= sev 2)  'eglot-warning)
+                (t          'eglot-note)))
+             (mess (source code message)
+               (concat source (and code (format " [%s]" code)) ": " message)))
     (if-let ((buffer (find-buffer-visiting (eglot--uri-to-path uri))))
-        (with-current-buffer buffer
-          (cl-loop
-           for diag-spec across diagnostics
-           collect (eglot--dbind ((Diagnostic) range code message severity source tags)
-                       diag-spec
-                     (setq message (mess source code message))
-                     (pcase-let
-                         ((`(,beg . ,end) (eglot--range-region range)))
-                       ;; Fallback to `flymake-diag-region' if server
-                       ;; botched the range
-                       (when (= beg end)
-                         (if-let* ((st (plist-get range :start))
+      (with-current-buffer buffer
+        (cl-loop
+          for diag-spec across diagnostics
+          collect (eglot--dbind ((Diagnostic) range code message severity source tags)
+                    diag-spec
+                    (setq message (mess source code message))
+                    (pcase-let
+                      ((`(,beg . ,end) (eglot--range-region range)))
+                      ;; Fallback to `flymake-diag-region' if server
+                      ;; botched the range
+                      (when (= beg end)
+                        (if-let* ((st (plist-get range :start))
                                    (diag-region
-                                    (flymake-diag-region
-                                     (current-buffer) (1+ (plist-get st :line))
-                                     (plist-get st :character))))
-                             (setq beg (car diag-region) end (cdr diag-region))
-                           (eglot--widening
+                                     (flymake-diag-region
+                                       (current-buffer) (1+ (plist-get st :line))
+                                       (plist-get st :character))))
+                          (setq beg (car diag-region) end (cdr diag-region))
+                          (eglot--widening
                             (goto-char (point-min))
                             (setq beg
-                                  (line-beginning-position
-                                   (1+ (plist-get (plist-get range :start) :line))))
+                              (line-beginning-position
+                                (1+ (plist-get (plist-get range :start) :line))))
                             (setq end
-                                  (line-end-position
-                                   (1+ (plist-get (plist-get range :end) :line)))))))
-                       (eglot--make-diag
+                              (line-end-position
+                                (1+ (plist-get (plist-get range :end) :line)))))))
+                      (eglot--make-diag
                         (current-buffer) beg end
                         (eglot--diag-type severity)
                         message `((eglot-lsp-diag . ,diag-spec))
                         (when-let ((faces
-                                    (cl-loop for tag across tags
-                                             when (alist-get tag eglot--tag-faces)
-                                             collect it)))
+                                     (cl-loop for tag across tags
+                                       when (alist-get tag eglot--tag-faces)
+                                       collect it)))
                           `((face . ,faces))))))
-           into diags
-           finally (cond ((and
+          into diags
+          finally (cond ((and
                            ;; only add to current report if Flymake
                            ;; starts on idle-timer (github#958)
                            (not (null flymake-no-changes-timeout))
                            eglot--current-flymake-report-fn)
                           (eglot--report-to-flymake diags))
-                         (t
-                          (setq eglot--diagnostics diags)))))
+                    (t
+                      (setq eglot--diagnostics diags)))))
       (cl-loop
-       with path = (expand-file-name (eglot--uri-to-path uri))
-       for diag-spec across diagnostics
-       collect (eglot--dbind ((Diagnostic) code range message severity source) diag-spec
-                 (setq message (mess source code message))
-                 (let* ((start (plist-get range :start))
-                        (line (1+ (plist-get start :line)))
-                        (char (1+ (plist-get start :character))))
-                   (eglot--make-diag
-                    path (cons line char) nil (eglot--diag-type severity) message)))
-       into diags
-       finally
-       (setq flymake-list-only-diagnostics
-             (assoc-delete-all path flymake-list-only-diagnostics #'string=))
-       (push (cons path diags) flymake-list-only-diagnostics)))))
+        with path = (expand-file-name (eglot--uri-to-path uri))
+        for diag-spec across diagnostics
+        collect (eglot--dbind ((Diagnostic) code range message severity source) diag-spec
+                  (setq message (mess source code message))
+                  (let* ((start (plist-get range :start))
+                          (line (1+ (plist-get start :line)))
+                          (char (1+ (plist-get start :character))))
+                    (eglot--make-diag
+                      path (cons line char) nil (eglot--diag-type severity) message)))
+        into diags
+        finally
+        (setq flymake-list-only-diagnostics
+          (assoc-delete-all path flymake-list-only-diagnostics #'string=))
+        (push (cons path diags) flymake-list-only-diagnostics)))))
 
 (cl-defun eglot--register-unregister (server things how)
   "Helper for `registerCapability'.
 THINGS are either registrations or unregisterations (sic)."
   (cl-loop
-   for thing in (cl-coerce things 'list)
-   do (eglot--dbind ((Registration) id method registerOptions) thing
-        (apply (cl-ecase how
-                 (register 'eglot-register-capability)
-                 (unregister 'eglot-unregister-capability))
-               server (intern method) id registerOptions))))
+    for thing in (cl-coerce things 'list)
+    do (eglot--dbind ((Registration) id method registerOptions) thing
+         (apply (cl-ecase how
+                  (register 'eglot-register-capability)
+                  (unregister 'eglot-unregister-capability))
+           server (intern method) id registerOptions))))
 
 (cl-defmethod eglot-handle-request
   (server (_method (eql client/registerCapability)) &key registrations)
@@ -2100,7 +2112,7 @@ THINGS are either registrations or unregisterations (sic)."
 
 (cl-defmethod eglot-handle-request
   (server (_method (eql client/unregisterCapability))
-          &key unregisterations) ;; XXX: "unregisterations" (sic)
+    &key unregisterations) ;; XXX: "unregisterations" (sic)
   "Handle server request client/unregisterCapability."
   (eglot--register-unregister server unregisterations 'unregister))
 
@@ -2118,31 +2130,31 @@ THINGS are either registrations or unregisterations (sic)."
 (defun eglot--TextDocumentIdentifier ()
   "Compute TextDocumentIdentifier object for current buffer."
   `(:uri ,(eglot--path-to-uri (or buffer-file-name
-                                  (ignore-errors
-                                    (buffer-file-name
-                                     (buffer-base-buffer)))))))
+                                (ignore-errors
+                                  (buffer-file-name
+                                    (buffer-base-buffer)))))))
 
 (defvar-local eglot--versioned-identifier 0)
 
 (defun eglot--VersionedTextDocumentIdentifier ()
   "Compute VersionedTextDocumentIdentifier object for current buffer."
   (append (eglot--TextDocumentIdentifier)
-          `(:version ,eglot--versioned-identifier)))
+    `(:version ,eglot--versioned-identifier)))
 
 (defun eglot--TextDocumentItem ()
   "Compute TextDocumentItem object for current buffer."
   (append
-   (eglot--VersionedTextDocumentIdentifier)
-   (list :languageId
-	 (eglot--language-id (eglot--current-server-or-lose))
-         :text
-         (eglot--widening
-          (buffer-substring-no-properties (point-min) (point-max))))))
+    (eglot--VersionedTextDocumentIdentifier)
+    (list :languageId
+	    (eglot--language-id (eglot--current-server-or-lose))
+      :text
+      (eglot--widening
+        (buffer-substring-no-properties (point-min) (point-max))))))
 
 (defun eglot--TextDocumentPositionParams ()
   "Compute TextDocumentPositionParams."
   (list :textDocument (eglot--TextDocumentIdentifier)
-        :position (eglot--pos-to-lsp-position)))
+    :position (eglot--pos-to-lsp-position)))
 
 (defvar-local eglot--last-inserted-char nil
   "If non-nil, value of the last inserted character in buffer.")
@@ -2152,12 +2164,12 @@ THINGS are either registrations or unregisterations (sic)."
   (setq eglot--last-inserted-char last-input-event)
   (let ((ot-provider (eglot--server-capable :documentOnTypeFormattingProvider)))
     (when (and ot-provider
-               (ignore-errors ; github#906, some LS's send empty strings
-                 (or (eq last-input-event
-                         (seq-first (plist-get ot-provider :firstTriggerCharacter)))
-                     (cl-find last-input-event
-                              (plist-get ot-provider :moreTriggerCharacter)
-                              :key #'seq-first))))
+            (ignore-errors ; github#906, some LS's send empty strings
+              (or (eq last-input-event
+                    (seq-first (plist-get ot-provider :firstTriggerCharacter)))
+                (cl-find last-input-event
+                  (plist-get ot-provider :moreTriggerCharacter)
+                  :key #'seq-first))))
       (eglot-format (point) nil last-input-event))))
 
 (defvar eglot--workspace-symbols-cache (make-hash-table :test #'equal)
@@ -2170,14 +2182,14 @@ THINGS are either registrations or unregisterations (sic)."
 
 (defun eglot--CompletionParams ()
   (append
-   (eglot--TextDocumentPositionParams)
-   `(:context
-     ,(if-let (trigger (and (characterp eglot--last-inserted-char)
-                            (cl-find eglot--last-inserted-char
-                                     (eglot--server-capable :completionProvider
-                                                            :triggerCharacters)
-                                     :key (lambda (str) (aref str 0))
-                                     :test #'char-equal)))
+    (eglot--TextDocumentPositionParams)
+    `(:context
+       ,(if-let (trigger (and (characterp eglot--last-inserted-char)
+                           (cl-find eglot--last-inserted-char
+                             (eglot--server-capable :completionProvider
+                               :triggerCharacters)
+                             :key (lambda (str) (aref str 0))
+                             :test #'char-equal)))
           `(:triggerKind 2 :triggerCharacter ,trigger) `(:triggerKind 1)))))
 
 (defvar-local eglot--recent-changes nil
@@ -2198,63 +2210,63 @@ THINGS are either registrations or unregisterations (sic)."
     ;; deleted/added).  Also record markers of BEG and END
     ;; (github#259)
     (push `(,(eglot--pos-to-lsp-position beg)
-            ,(eglot--pos-to-lsp-position end)
-            (,beg . ,(copy-marker beg nil))
-            (,end . ,(copy-marker end t)))
-          eglot--recent-changes)))
+             ,(eglot--pos-to-lsp-position end)
+             (,beg . ,(copy-marker beg nil))
+             (,end . ,(copy-marker end t)))
+      eglot--recent-changes)))
 
 (defun eglot--after-change (beg end pre-change-length)
   "Hook onto `after-change-functions'.
 Records BEG, END and PRE-CHANGE-LENGTH locally."
   (cl-incf eglot--versioned-identifier)
   (pcase (and (listp eglot--recent-changes)
-              (car eglot--recent-changes))
+           (car eglot--recent-changes))
     (`(,lsp-beg ,lsp-end
-                (,b-beg . ,b-beg-marker)
-                (,b-end . ,b-end-marker))
-     ;; github#259 and github#367: With `capitalize-word' or somesuch,
-     ;; `before-change-functions' always records the whole word's
-     ;; `b-beg' and `b-end'.  Similarly, when coalescing two lines
-     ;; into one, `fill-paragraph' they mark the end of the first line
-     ;; up to the end of the second line.  In both situations, args
-     ;; received here contradict that information: `beg' and `end'
-     ;; will differ by 1 and will likely only encompass the letter
-     ;; that was capitalized or, in the sentence-joining situation,
-     ;; the replacement of the newline with a space.  That's we keep
-     ;; markers _and_ positions so we're able to detect and correct
-     ;; this.  We ignore `beg', `len' and `pre-change-len' and send
-     ;; "fuller" information about the region from the markers.  I've
-     ;; also experimented with doing this unconditionally but it seems
-     ;; to break when newlines are added.
-     (if (and (= b-end b-end-marker) (= b-beg b-beg-marker)
-              (or (/= beg b-beg) (/= end b-end)))
-         (setcar eglot--recent-changes
-                 `(,lsp-beg ,lsp-end ,(- b-end-marker b-beg-marker)
-                            ,(buffer-substring-no-properties b-beg-marker
-                                                             b-end-marker)))
-       (setcar eglot--recent-changes
-               `(,lsp-beg ,lsp-end ,pre-change-length
-                          ,(buffer-substring-no-properties beg end)))))
+        (,b-beg . ,b-beg-marker)
+        (,b-end . ,b-end-marker))
+      ;; github#259 and github#367: With `capitalize-word' or somesuch,
+      ;; `before-change-functions' always records the whole word's
+      ;; `b-beg' and `b-end'.  Similarly, when coalescing two lines
+      ;; into one, `fill-paragraph' they mark the end of the first line
+      ;; up to the end of the second line.  In both situations, args
+      ;; received here contradict that information: `beg' and `end'
+      ;; will differ by 1 and will likely only encompass the letter
+      ;; that was capitalized or, in the sentence-joining situation,
+      ;; the replacement of the newline with a space.  That's we keep
+      ;; markers _and_ positions so we're able to detect and correct
+      ;; this.  We ignore `beg', `len' and `pre-change-len' and send
+      ;; "fuller" information about the region from the markers.  I've
+      ;; also experimented with doing this unconditionally but it seems
+      ;; to break when newlines are added.
+      (if (and (= b-end b-end-marker) (= b-beg b-beg-marker)
+            (or (/= beg b-beg) (/= end b-end)))
+        (setcar eglot--recent-changes
+          `(,lsp-beg ,lsp-end ,(- b-end-marker b-beg-marker)
+             ,(buffer-substring-no-properties b-beg-marker
+                b-end-marker)))
+        (setcar eglot--recent-changes
+          `(,lsp-beg ,lsp-end ,pre-change-length
+             ,(buffer-substring-no-properties beg end)))))
     (_ (setf eglot--recent-changes :emacs-messup)))
   (when eglot--change-idle-timer (cancel-timer eglot--change-idle-timer))
   (let ((buf (current-buffer)))
     (setq eglot--change-idle-timer
-          (run-with-idle-timer
-           eglot-send-changes-idle-time
-           nil (lambda () (eglot--when-live-buffer buf
-                            (when eglot--managed-mode
-                              (eglot--signal-textDocument/didChange)
-                              (setq eglot--change-idle-timer nil))))))))
+      (run-with-idle-timer
+        eglot-send-changes-idle-time
+        nil (lambda () (eglot--when-live-buffer buf
+                         (when eglot--managed-mode
+                           (eglot--signal-textDocument/didChange)
+                           (setq eglot--change-idle-timer nil))))))))
 
 ;; HACK! Launching a deferred sync request with outstanding changes is a
 ;; bad idea, since that might lead to the request never having a
 ;; chance to run, because `jsonrpc-connection-ready-p'.
 (advice-add #'jsonrpc-request :before
-            (cl-function (lambda (_proc _method _params &key
-                                        deferred &allow-other-keys)
-                           (when (and eglot--managed-mode deferred)
-                             (eglot--signal-textDocument/didChange))))
-            '((name . eglot--signal-textDocument/didChange)))
+  (cl-function (lambda (_proc _method _params &key
+                         deferred &allow-other-keys)
+                 (when (and eglot--managed-mode deferred)
+                   (eglot--signal-textDocument/didChange))))
+  '((name . eglot--signal-textDocument/didChange)))
 
 (defvar-local eglot-workspace-configuration ()
   "Configure LSP servers specifically for a given project.
@@ -2291,8 +2303,8 @@ format described above.")
 (defun eglot-show-workspace-configuration (&optional server)
   "Dump `eglot-workspace-configuration' as JSON for debugging."
   (interactive (list (and (eglot-current-server)
-                          (eglot--read-server "Server configuration"
-                                              (eglot-current-server)))))
+                       (eglot--read-server "Server configuration"
+                         (eglot-current-server)))))
   (let ((conf (eglot--workspace-configuration-plist server)))
     (with-current-buffer (get-buffer-create "*EGLOT workspace configuration*")
       (erase-buffer)
@@ -2305,83 +2317,83 @@ format described above.")
 
 (defun eglot--workspace-configuration (server)
   (if (functionp eglot-workspace-configuration)
-      (funcall eglot-workspace-configuration server)
+    (funcall eglot-workspace-configuration server)
     eglot-workspace-configuration))
 
 (defun eglot--workspace-configuration-plist (server)
   "Returns `eglot-workspace-configuration' suitable for serialization."
   (let ((val (eglot--workspace-configuration server)))
     (or (and (consp (car val))
-             (cl-loop for (section . v) in val
-                      collect (if (keywordp section) section
-                                (intern (format ":%s" section)))
-                      collect v))
-        val)))
+          (cl-loop for (section . v) in val
+            collect (if (keywordp section) section
+                      (intern (format ":%s" section)))
+            collect v))
+      val)))
 
 (defun eglot-signal-didChangeConfiguration (server)
   "Send a `:workspace/didChangeConfiguration' signal to SERVER.
 When called interactively, use the currently active server"
   (interactive (list (eglot--current-server-or-lose)))
   (jsonrpc-notify
-   server :workspace/didChangeConfiguration
-   (list
-    :settings
-    (or (eglot--workspace-configuration-plist server)
+    server :workspace/didChangeConfiguration
+    (list
+      :settings
+      (or (eglot--workspace-configuration-plist server)
         eglot--{}))))
 
 (cl-defmethod eglot-handle-request
   (server (_method (eql workspace/configuration)) &key items)
   "Handle server request workspace/configuration."
   (apply #'vector
-         (mapcar
-          (eglot--lambda ((ConfigurationItem) scopeUri section)
-            (with-temp-buffer
-              (let* ((uri-path (eglot--uri-to-path scopeUri))
-                     (default-directory
-                       (if (and (not (string-empty-p uri-path))
-                                (file-directory-p uri-path))
-                           (file-name-as-directory uri-path)
-                         (project-root (eglot--project server)))))
-                (setq-local major-mode (car (eglot--major-modes server)))
-                (hack-dir-local-variables-non-file-buffer)
-                (cl-loop for (wsection o)
-                         on (eglot--workspace-configuration-plist server)
-                         by #'cddr
-                         when (string=
-                               (if (keywordp wsection)
-                                   (substring (symbol-name wsection) 1)
-                                 wsection)
-                               section)
-                         return o))))
-          items)))
+    (mapcar
+      (eglot--lambda ((ConfigurationItem) scopeUri section)
+        (with-temp-buffer
+          (let* ((uri-path (eglot--uri-to-path scopeUri))
+                  (default-directory
+                    (if (and (not (string-empty-p uri-path))
+                          (file-directory-p uri-path))
+                      (file-name-as-directory uri-path)
+                      (project-root (eglot--project server)))))
+            (setq-local major-mode (car (eglot--major-modes server)))
+            (hack-dir-local-variables-non-file-buffer)
+            (cl-loop for (wsection o)
+              on (eglot--workspace-configuration-plist server)
+              by #'cddr
+              when (string=
+                     (if (keywordp wsection)
+                       (substring (symbol-name wsection) 1)
+                       wsection)
+                     section)
+              return o))))
+      items)))
 
 (defun eglot--signal-textDocument/didChange ()
   "Send textDocument/didChange to server."
   (when eglot--recent-changes
     (let* ((server (eglot--current-server-or-lose))
-           (sync-capability (eglot--server-capable :textDocumentSync))
-           (sync-kind (if (numberp sync-capability) sync-capability
-                        (plist-get sync-capability :change)))
-           (full-sync-p (or (eq sync-kind 1)
-                            (eq :emacs-messup eglot--recent-changes))))
+            (sync-capability (eglot--server-capable :textDocumentSync))
+            (sync-kind (if (numberp sync-capability) sync-capability
+                         (plist-get sync-capability :change)))
+            (full-sync-p (or (eq sync-kind 1)
+                           (eq :emacs-messup eglot--recent-changes))))
       (jsonrpc-notify
-       server :textDocument/didChange
-       (list
-        :textDocument (eglot--VersionedTextDocumentIdentifier)
-        :contentChanges
-        (if full-sync-p
+        server :textDocument/didChange
+        (list
+          :textDocument (eglot--VersionedTextDocumentIdentifier)
+          :contentChanges
+          (if full-sync-p
             (vector `(:text ,(eglot--widening
-                              (buffer-substring-no-properties (point-min)
-                                                              (point-max)))))
-          (cl-loop for (beg end len text) in (reverse eglot--recent-changes)
-                   ;; github#259: `capitalize-word' and commands based
-                   ;; on `casify_region' will cause multiple duplicate
-                   ;; empty entries in `eglot--before-change' calls
-                   ;; without an `eglot--after-change' reciprocal.
-                   ;; Weed them out here.
-                   when (numberp len)
-                   vconcat `[,(list :range `(:start ,beg :end ,end)
-                                    :rangeLength len :text text)]))))
+                               (buffer-substring-no-properties (point-min)
+                                 (point-max)))))
+            (cl-loop for (beg end len text) in (reverse eglot--recent-changes)
+              ;; github#259: `capitalize-word' and commands based
+              ;; on `casify_region' will cause multiple duplicate
+              ;; empty entries in `eglot--before-change' calls
+              ;; without an `eglot--after-change' reciprocal.
+              ;; Weed them out here.
+              when (numberp len)
+              vconcat `[,(list :range `(:start ,beg :end ,end)
+                           :rangeLength len :text text)]))))
       (setq eglot--recent-changes nil)
       (setf (eglot--spinner server) (list nil :textDocument/didChange t))
       (jsonrpc--call-deferred server))))
@@ -2390,39 +2402,129 @@ When called interactively, use the currently active server"
   "Send textDocument/didOpen to server."
   (setq eglot--recent-changes nil eglot--versioned-identifier 0)
   (jsonrpc-notify
-   (eglot--current-server-or-lose)
-   :textDocument/didOpen `(:textDocument ,(eglot--TextDocumentItem))))
+    (eglot--current-server-or-lose)
+    :textDocument/didOpen `(:textDocument ,(eglot--TextDocumentItem))))
 
 (defun eglot--signal-textDocument/didClose ()
   "Send textDocument/didClose to server."
   (with-demoted-errors
-      "[eglot] error sending textDocument/didClose: %s"
+    "[eglot] error sending textDocument/didClose: %s"
     (jsonrpc-notify
-     (eglot--current-server-or-lose)
-     :textDocument/didClose `(:textDocument ,(eglot--TextDocumentIdentifier)))))
+      (eglot--current-server-or-lose)
+      :textDocument/didClose `(:textDocument ,(eglot--TextDocumentIdentifier)))))
 
 (defun eglot--signal-textDocument/willSave ()
   "Send textDocument/willSave to server."
   (let ((server (eglot--current-server-or-lose))
-        (params `(:reason 1 :textDocument ,(eglot--TextDocumentIdentifier))))
+         (params `(:reason 1 :textDocument ,(eglot--TextDocumentIdentifier))))
     (when (eglot--server-capable :textDocumentSync :willSave)
       (jsonrpc-notify server :textDocument/willSave params))
     (when (eglot--server-capable :textDocumentSync :willSaveWaitUntil)
       (ignore-errors
         (eglot--apply-text-edits
-         (jsonrpc-request server :textDocument/willSaveWaitUntil params
-                          :timeout 0.5))))))
+          (jsonrpc-request server :textDocument/willSaveWaitUntil params
+            :timeout 0.5))))))
 
 (defun eglot--signal-textDocument/didSave ()
   "Send textDocument/didSave to server."
   (eglot--signal-textDocument/didChange)
   (jsonrpc-notify
-   (eglot--current-server-or-lose)
-   :textDocument/didSave
-   (list
-    ;; TODO: Handle TextDocumentSaveRegistrationOptions to control this.
-    :text (buffer-substring-no-properties (point-min) (point-max))
-    :textDocument (eglot--TextDocumentIdentifier))))
+    (eglot--current-server-or-lose)
+    :textDocument/didSave
+    (list
+      ;; TODO: Handle TextDocumentSaveRegistrationOptions to control this.
+      :text (buffer-substring-no-properties (point-min) (point-max))
+      :textDocument (eglot--TextDocumentIdentifier))))
+
+(defun eglot--inlay-hint-handler (buffer hints report-fn)
+  (print report-fn)
+  ;; Make overlays for them.
+  (let (diags nil)
+    (seq-doseq (value hints)
+      (cl-loop for val being the elements of
+        (if (stringp (plist-get value :label))
+          (list (plist-get value :label))
+          (plist-get value :label))
+        do
+	      (eglot--widening
+	        (let (
+                 (line (plist-get (plist-get value :position) :line))
+                 (col (plist-get (plist-get value :position) :character))
+                 (source (if (stringp (plist-get value :label))
+                           (plist-get (plist-get (plist-get value :data) :text_document) :uri)
+                           (plist-get (plist-get (plist-get (car value) :label) :location) :uri)))
+                 (text
+                   (propertize
+                     (concat
+                       (when (plist-get value :paddingLeft) " ")
+                       (if (stringp val) val (plist-get val :value))
+                       (when (plist-get value :paddingRight) " "))
+                     'face 'eglot-inlay-hint)
+                   )
+                 )
+            (print "source")
+            (print source)
+            (with-current-buffer buffer
+                            (print (plist-get value :position))
+            (print (eglot--lsp-position-to-point (plist-get value :position)))
+            (print (eglot--uri-to-path source))
+              (push (eglot--make-diag
+                      (expand-file-name (eglot--uri-to-path source))
+                    ;; FIXME: wrong coordinates?
+                    ;; TODO: check diag correctly updates
+                    (cons (1+ line) (1+ col))
+                    ;;(eglot--lsp-position-to-point (plist-get value :position))
+                    nil
+                    'eglot-inlay-hint
+                    text
+                    nil
+                    `((before-string . ,text)
+                       ))
+              diags)
+              )
+            )
+          )
+        )
+      )
+    (funcall report-fn diags)
+    )
+  )
+
+(defun eglot-get-inlay-hints (report-fn buffer)
+  ;; (let ((lens-table (make-hash-table)))
+	;;       ;; Get the inlay hint objects.
+	;;       (mapc (lambda (inlayHint)
+	;; 	            (when (and (eglot--server-capable
+	;; 		                       :inlayHintProvider :resolveProvider)
+	;; 		                  (not (plist-member inlayHint :command)))
+	;; 	              (setq inlayHint
+	;; 		              (jsonrpc-request (eglot--current-server-or-lose)
+	;; 				            :inlayHint/resolve inlayHint)))
+	;; 	            (let ((line (thread-first inlayHint
+	;; 				                    (plist-get :position)
+	;; 				                    (plist-get :line))))
+	;; 	              (puthash line
+	;; 		              (append (gethash line lens-table) (list inlayHint))
+	;; 		              lens-table)))
+	;; 	      (jsonrpc-request
+	;; 	        (eglot--current-server-or-lose)
+	;; 	        :textDocument/inlayHint
+	;; 	        (list :textDocument (eglot--TextDocumentIdentifier) :range (list :start (list :line 0 :character 0) :end (list :line (count-lines (point-min) (point-max)) :character 0)))
+	;; 	        :deferred :textDocument/inlayHint))
+  ;;   lens-table
+  ;;   )
+  (print "toto")
+(print (eglot--TextDocumentIdentifier))
+	(jsonrpc-async-request
+		(eglot--current-server-or-lose)
+		:textDocument/inlayHint
+		(list
+      :textDocument (eglot--TextDocumentIdentifier)
+      :range (list :start (list :line 0 :character 0) :end (list :line (count-lines (point-min) (point-max)) :character 0)))
+		:deferred t
+    :success-fn (lambda (hints) (eglot--inlay-hint-handler buffer hints report-fn))
+    )
+  )
 
 (defun eglot-flymake-backend (report-fn &rest _more)
   "A Flymake backend for Eglot.
@@ -2431,22 +2533,76 @@ publishes diagnostics.  Between calls to this function, REPORT-FN
 may be called multiple times (respecting the protocol of
 `flymake-backend-functions')."
   (cond (eglot--managed-mode
-         (setq eglot--current-flymake-report-fn report-fn)
-         (eglot--report-to-flymake eglot--diagnostics))
-        (t
-         (funcall report-fn nil))))
+          (setq eglot--current-flymake-report-fn report-fn)
+          (print "more")
+          (print _more)
+          (print "buffer")
+          (print (current-buffer))
+
+          (when (eglot--server-capable :inlayHintProvider)
+            (print eglot--current-flymake-report-fn)
+            (eglot-get-inlay-hints eglot--current-flymake-report-fn (current-buffer))
+          )
+          ;; (when (eglot--server-capable :inlayHintProvider)
+          ;;   (let ((source (current-buffer))
+          ;;          (hints (eglot-get-inlay-hints)))
+          ;;     ;; Make overlays for them.
+	        ;;     (maphash
+	        ;;       (lambda (line values)
+          ;;         (cl-loop for value in values
+          ;;           do
+          ;;           (print value)
+          ;;           (cl-loop for val being the elements of
+          ;;             (if (stringp (plist-get value :label))
+          ;;               (list (plist-get value :label))
+          ;;               (plist-get value :label))
+          ;;             do
+          ;;             (print val)
+	        ;;             (eglot--widening
+	        ;;               (let ((col (plist-get (plist-get value :position) :character))
+          ;;                      (text
+          ;;                        (propertize
+          ;;                          (concat
+          ;;                            (when (plist-get value :paddingLeft) " ")
+          ;;                            (if (stringp val) val (plist-get val :value))
+          ;;                            (when (plist-get value :paddingRight) " "))
+          ;;                          'face 'eglot-inlay-hint)
+          ;;                        )
+          ;;                      )
+          ;;                 (print text)
+          ;;                   (push (eglot--make-diag
+          ;;                           source
+          ;;                           (cons (+ line 1) (+ col 1))
+          ;;                           nil
+          ;;                           'eglot-inlay-hint
+          ;;                           text
+          ;;                           nil
+          ;;                           `((before-string . ,text)
+          ;;                              ))
+          ;;                     eglot--diagnostics)
+          ;;                 )
+          ;;               )
+          ;;             )
+          ;;           )
+          ;;         )
+	        ;;       hints)
+          ;;     ))
+          (eglot--report-to-flymake eglot--diagnostics))
+    (t
+      (funcall report-fn diags)
+      )))
 
 (defun eglot--report-to-flymake (diags)
   "Internal helper for `eglot-flymake-backend'."
   (save-restriction
     (widen)
     (funcall eglot--current-flymake-report-fn diags
-             ;; If the buffer hasn't changed since last
-             ;; call to the report function, flymake won't
-             ;; delete old diagnostics.  Using :region
-             ;; keyword forces flymake to delete
-             ;; them (github#159).
-             :region (cons (point-min) (point-max))))
+      ;; If the buffer hasn't changed since last
+      ;; call to the report function, flymake won't
+      ;; delete old diagnostics.  Using :region
+      ;; keyword forces flymake to delete
+      ;; them (github#159).
+      :region (cons (point-min) (point-max))))
   (setq eglot--diagnostics diags))
 
 (defun eglot-xref-backend () "Eglot xref backend." 'eglot)
@@ -2462,11 +2618,11 @@ may be called multiple times (respecting the protocol of
   (declare (indent 1) (debug (sexp &rest form)))
   (let ((collected (cl-gensym "collected")))
     `(unwind-protect
-         (let (,collected)
-           (cl-flet ((,collector (xref) (push xref ,collected)))
-             ,@body)
-           (setq ,collected (nreverse ,collected))
-           (sort ,collected eglot-xref-lessp-function))
+       (let (,collected)
+         (cl-flet ((,collector (xref) (push xref ,collected)))
+           ,@body)
+         (setq ,collected (nreverse ,collected))
+         (sort ,collected eglot-xref-lessp-function))
        (maphash (lambda (_uri buf) (kill-buffer buf)) eglot--temp-location-buffers)
        (clrhash eglot--temp-location-buffers))))
 
@@ -2474,36 +2630,36 @@ may be called multiple times (respecting the protocol of
   "Like `xref-make-match' but with LSP's NAME, URI and RANGE.
 Try to visit the target file for a richer summary line."
   (pcase-let*
-      ((file (eglot--uri-to-path uri))
-       (visiting (or (find-buffer-visiting file)
-                     (gethash uri eglot--temp-location-buffers)))
-       (collect (lambda ()
-                  (eglot--widening
+    ((file (eglot--uri-to-path uri))
+      (visiting (or (find-buffer-visiting file)
+                  (gethash uri eglot--temp-location-buffers)))
+      (collect (lambda ()
+                 (eglot--widening
                    (pcase-let* ((`(,beg . ,end) (eglot--range-region range))
-                                (bol (progn (goto-char beg) (line-beginning-position)))
-                                (substring (buffer-substring bol (line-end-position)))
-                                (hi-beg (- beg bol))
-                                (hi-end (- (min (line-end-position) end) bol)))
+                                 (bol (progn (goto-char beg) (line-beginning-position)))
+                                 (substring (buffer-substring bol (line-end-position)))
+                                 (hi-beg (- beg bol))
+                                 (hi-end (- (min (line-end-position) end) bol)))
                      (add-face-text-property hi-beg hi-end 'xref-match
-                                             t substring)
+                       t substring)
                      (list substring (line-number-at-pos (point) t)
-                           (eglot-current-column) (- end beg))))))
-       (`(,summary ,line ,column ,length)
+                       (eglot-current-column) (- end beg))))))
+      (`(,summary ,line ,column ,length)
         (cond
-         (visiting (with-current-buffer visiting (funcall collect)))
-         ((file-readable-p file) (with-current-buffer
-                                     (puthash uri (generate-new-buffer " *temp*")
-                                              eglot--temp-location-buffers)
-                                   (insert-file-contents file)
-                                   (funcall collect)))
-         (t ;; fall back to the "dumb strategy"
-          (let* ((start (cl-getf range :start))
-                 (line (1+ (cl-getf start :line)))
-                 (start-pos (cl-getf start :character))
-                 (end-pos (cl-getf (cl-getf range :end) :character)))
-            (list name line start-pos (- end-pos start-pos)))))))
+          (visiting (with-current-buffer visiting (funcall collect)))
+          ((file-readable-p file) (with-current-buffer
+                                    (puthash uri (generate-new-buffer " *temp*")
+                                      eglot--temp-location-buffers)
+                                    (insert-file-contents file)
+                                    (funcall collect)))
+          (t ;; fall back to the "dumb strategy"
+            (let* ((start (cl-getf range :start))
+                    (line (1+ (cl-getf start :line)))
+                    (start-pos (cl-getf start :character))
+                    (end-pos (cl-getf (cl-getf range :end) :character)))
+              (list name line start-pos (- end-pos start-pos)))))))
     (setf (gethash (expand-file-name file) eglot--servers-by-xrefed-file)
-          (eglot--current-server-or-lose))
+      (eglot--current-server-or-lose))
     (xref-make-match summary (xref-make-file-location file line column) length)))
 
 (defun eglot--workspace-symbols (pat &optional buffer)
@@ -2513,42 +2669,42 @@ If BUFFER, switch to it before."
     (unless (eglot--server-capable :workspaceSymbolProvider)
       (eglot--error "This LSP server isn't a :workspaceSymbolProvider"))
     (mapcar
-     (lambda (wss)
-       (eglot--dbind ((WorkspaceSymbol) name containerName kind) wss
-         (propertize
-          (format "%s%s %s"
-                  (if (zerop (length containerName)) ""
-                    (concat (propertize containerName 'face 'shadow) " "))
-                  name
-                  (propertize (alist-get kind eglot--symbol-kind-names "Unknown")
-                              'face 'shadow))
-          'eglot--lsp-workspaceSymbol wss)))
-     (jsonrpc-request (eglot--current-server-or-lose) :workspace/symbol
-                      `(:query ,pat)))))
+      (lambda (wss)
+        (eglot--dbind ((WorkspaceSymbol) name containerName kind) wss
+          (propertize
+            (format "%s%s %s"
+              (if (zerop (length containerName)) ""
+                (concat (propertize containerName 'face 'shadow) " "))
+              name
+              (propertize (alist-get kind eglot--symbol-kind-names "Unknown")
+                'face 'shadow))
+            'eglot--lsp-workspaceSymbol wss)))
+      (jsonrpc-request (eglot--current-server-or-lose) :workspace/symbol
+        `(:query ,pat)))))
 
 (cl-defmethod xref-backend-identifier-completion-table ((_backend (eql eglot)))
   "Yet another tricky connection between LSP and Elisp completion semantics."
   (let ((buf (current-buffer)) (cache eglot--workspace-symbols-cache))
     (cl-labels ((refresh (pat) (eglot--workspace-symbols pat buf))
-                (lookup-1 (pat) ;; check cache, else refresh
-                  (let ((probe (gethash pat cache :missing)))
-                    (if (eq probe :missing) (puthash pat (refresh pat) cache)
-                      probe)))
-                (lookup (pat)
-                  (let ((res (lookup-1 pat))
-                        (def (and (string= pat "") (gethash :default cache))))
-                    (append def res nil)))
-                (score (c)
-                  (cl-getf (get-text-property
-                            0 'eglot--lsp-workspaceSymbol c)
-                           :score 0)))
+                 (lookup-1 (pat) ;; check cache, else refresh
+                   (let ((probe (gethash pat cache :missing)))
+                     (if (eq probe :missing) (puthash pat (refresh pat) cache)
+                       probe)))
+                 (lookup (pat)
+                   (let ((res (lookup-1 pat))
+                          (def (and (string= pat "") (gethash :default cache))))
+                     (append def res nil)))
+                 (score (c)
+                   (cl-getf (get-text-property
+                              0 'eglot--lsp-workspaceSymbol c)
+                     :score 0)))
       (lambda (string _pred action)
         (pcase action
           (`metadata `(metadata
-                       (cycle-sort-function
-                        . ,(lambda (completions)
-                             (cl-sort completions #'> :key #'score)))
-                       (category . eglot-indirection-joy)))
+                        (cycle-sort-function
+                          . ,(lambda (completions)
+                               (cl-sort completions #'> :key #'score)))
+                        (category . eglot-indirection-joy)))
           (`(eglot--lsp-tryc . ,point) `(eglot--lsp-tryc . (,string . ,point)))
           (`(eglot--lsp-allc . ,_point) `(eglot--lsp-allc . ,(lookup string)))
           (_ nil))))))
@@ -2561,18 +2717,18 @@ If BUFFER, switch to it before."
                  ;; Like mess? Ask minibuffer.el about improper lists.
                  (when (equal (car v) string) (throw 'found (car v)))
                  (setq v (cdr v))))
-             eglot--workspace-symbols-cache)))
+      eglot--workspace-symbols-cache)))
 
 (add-to-list 'completion-category-overrides
-             '(eglot-indirection-joy (styles . (eglot--lsp-backend-style))))
+  '(eglot-indirection-joy (styles . (eglot--lsp-backend-style))))
 
 (cl-defmethod xref-backend-identifier-at-point ((_backend (eql eglot)))
   (let ((attempt
-         (and (xref--prompt-p this-command)
-              (puthash :default
-                       (ignore-errors
-                         (eglot--workspace-symbols (symbol-name (symbol-at-point))))
-                       eglot--workspace-symbols-cache))))
+          (and (xref--prompt-p this-command)
+            (puthash :default
+              (ignore-errors
+                (eglot--workspace-symbols (symbol-name (symbol-at-point))))
+              eglot--workspace-symbols-cache))))
     (if attempt (car attempt) "LSP identifier at point")))
 
 (defvar eglot--lsp-xref-refs nil
@@ -2581,37 +2737,37 @@ If BUFFER, switch to it before."
 (cl-defun eglot--lsp-xrefs-for-method (method &key extra-params capability)
   "Make `xref''s for METHOD, EXTRA-PARAMS, check CAPABILITY."
   (unless (eglot--server-capable
-           (or capability
-               (intern
+            (or capability
+              (intern
                 (format ":%sProvider"
-                        (cadr (split-string (symbol-name method)
-                                            "/"))))))
+                  (cadr (split-string (symbol-name method)
+                          "/"))))))
     (eglot--error "Sorry, this server doesn't do %s" method))
   (let ((response
-         (jsonrpc-request
-          (eglot--current-server-or-lose)
-          method (append (eglot--TextDocumentPositionParams) extra-params))))
+          (jsonrpc-request
+            (eglot--current-server-or-lose)
+            method (append (eglot--TextDocumentPositionParams) extra-params))))
     (eglot--collecting-xrefs (collect)
       (mapc
-       (lambda (loc-or-loc-link)
-         (let ((sym-name (symbol-name (symbol-at-point))))
-           (eglot--dcase loc-or-loc-link
-             (((LocationLink) targetUri targetSelectionRange)
-              (collect (eglot--xref-make-match sym-name
-                                               targetUri targetSelectionRange)))
-             (((Location) uri range)
-              (collect (eglot--xref-make-match sym-name
-                                               uri range))))))
-       (if (vectorp response) response (and response (list response)))))))
+        (lambda (loc-or-loc-link)
+          (let ((sym-name (symbol-name (symbol-at-point))))
+            (eglot--dcase loc-or-loc-link
+              (((LocationLink) targetUri targetSelectionRange)
+                (collect (eglot--xref-make-match sym-name
+                           targetUri targetSelectionRange)))
+              (((Location) uri range)
+                (collect (eglot--xref-make-match sym-name
+                           uri range))))))
+        (if (vectorp response) response (and response (list response)))))))
 
 (cl-defun eglot--lsp-xref-helper (method &key extra-params capability )
   "Helper for `eglot-find-declaration' & friends."
   (let ((eglot--lsp-xref-refs (eglot--lsp-xrefs-for-method
-                               method
-                               :extra-params extra-params
-                               :capability capability)))
+                                method
+                                :extra-params extra-params
+                                :capability capability)))
     (if eglot--lsp-xref-refs
-        (xref-find-references "LSP identifier at point.")
+      (xref-find-references "LSP identifier at point.")
       (eglot--message "%s returned no references" method))))
 
 (defun eglot-find-declaration ()
@@ -2632,28 +2788,28 @@ If BUFFER, switch to it before."
 (cl-defmethod xref-backend-definitions ((_backend (eql eglot)) id)
   (let ((probe (eglot--recover-workspace-symbol-meta id)))
     (if probe
-        (eglot--dbind ((WorkspaceSymbol) name location)
-            (get-text-property 0 'eglot--lsp-workspaceSymbol probe)
-          (eglot--dbind ((Location) uri range) location
-            (list (eglot--xref-make-match name uri range))))
-        (eglot--lsp-xrefs-for-method :textDocument/definition))))
+      (eglot--dbind ((WorkspaceSymbol) name location)
+        (get-text-property 0 'eglot--lsp-workspaceSymbol probe)
+        (eglot--dbind ((Location) uri range) location
+          (list (eglot--xref-make-match name uri range))))
+      (eglot--lsp-xrefs-for-method :textDocument/definition))))
 
 (cl-defmethod xref-backend-references ((_backend (eql eglot)) _identifier)
   (or
-   eglot--lsp-xref-refs
-   (eglot--lsp-xrefs-for-method
-    :textDocument/references :extra-params `(:context (:includeDeclaration t)))))
+    eglot--lsp-xref-refs
+    (eglot--lsp-xrefs-for-method
+      :textDocument/references :extra-params `(:context (:includeDeclaration t)))))
 
 (cl-defmethod xref-backend-apropos ((_backend (eql eglot)) pattern)
   (when (eglot--server-capable :workspaceSymbolProvider)
     (eglot--collecting-xrefs (collect)
       (mapc
-       (eglot--lambda ((SymbolInformation) name location)
-         (eglot--dbind ((Location) uri range) location
-           (collect (eglot--xref-make-match name uri range))))
-       (jsonrpc-request (eglot--current-server-or-lose)
-                        :workspace/symbol
-                        `(:query ,pattern))))))
+        (eglot--lambda ((SymbolInformation) name location)
+          (eglot--dbind ((Location) uri range) location
+            (collect (eglot--xref-make-match name uri range))))
+        (jsonrpc-request (eglot--current-server-or-lose)
+          :workspace/symbol
+          `(:query ,pattern))))))
 
 (defun eglot-format-buffer ()
   "Format contents of current buffer."
@@ -2670,304 +2826,304 @@ If non-nil, ON-TYPE-FORMAT is a character just inserted at BEG
 for which LSP on-type-formatting should be requested."
   (interactive (and (region-active-p) (list (region-beginning) (region-end))))
   (pcase-let ((`(,method ,cap ,args)
-               (cond
-                ((and beg on-type-format)
-                 `(:textDocument/onTypeFormatting
-                   :documentOnTypeFormattingProvider
-                   ,`(:position ,(eglot--pos-to-lsp-position beg)
-                      :ch ,(string on-type-format))))
-                ((and beg end)
-                 `(:textDocument/rangeFormatting
-                   :documentRangeFormattingProvider
-                   (:range ,(list :start (eglot--pos-to-lsp-position beg)
+                (cond
+                  ((and beg on-type-format)
+                    `(:textDocument/onTypeFormatting
+                       :documentOnTypeFormattingProvider
+                       ,`(:position ,(eglot--pos-to-lsp-position beg)
+                           :ch ,(string on-type-format))))
+                  ((and beg end)
+                    `(:textDocument/rangeFormatting
+                       :documentRangeFormattingProvider
+                       (:range ,(list :start (eglot--pos-to-lsp-position beg)
                                   :end (eglot--pos-to-lsp-position end)))))
-                (t
-                 '(:textDocument/formatting :documentFormattingProvider nil)))))
+                  (t
+                    '(:textDocument/formatting :documentFormattingProvider nil)))))
     (unless (eglot--server-capable cap)
       (eglot--error "Server can't format!"))
     (eglot--apply-text-edits
-     (jsonrpc-request
-      (eglot--current-server-or-lose)
-      method
-      (cl-list*
-       :textDocument (eglot--TextDocumentIdentifier)
-       :options (list :tabSize tab-width
-                      :insertSpaces (if indent-tabs-mode :json-false t)
-                      :insertFinalNewline (if require-final-newline t :json-false)
-                      :trimFinalNewlines (if delete-trailing-lines t :json-false))
-       args)
-      :deferred method))))
+      (jsonrpc-request
+        (eglot--current-server-or-lose)
+        method
+        (cl-list*
+          :textDocument (eglot--TextDocumentIdentifier)
+          :options (list :tabSize tab-width
+                     :insertSpaces (if indent-tabs-mode :json-false t)
+                     :insertFinalNewline (if require-final-newline t :json-false)
+                     :trimFinalNewlines (if delete-trailing-lines t :json-false))
+          args)
+        :deferred method))))
 
 (defun eglot-completion-at-point ()
   "Eglot's `completion-at-point' function."
   ;; Commit logs for this function help understand what's going on.
   (when-let (completion-capability (eglot--server-capable :completionProvider))
     (let* ((server (eglot--current-server-or-lose))
-           (sort-completions
-            (lambda (completions)
-              (cl-sort completions
-                       #'string-lessp
-                       :key (lambda (c)
-                              (or (plist-get
-                                   (get-text-property 0 'eglot--lsp-item c)
-                                   :sortText)
-                                  "")))))
-           (metadata `(metadata (category . eglot)
-                                (display-sort-function . ,sort-completions)))
-           resp items (cached-proxies :none)
-           (proxies
-            (lambda ()
-              (if (listp cached-proxies) cached-proxies
-                (setq resp
-                      (jsonrpc-request server
-                                       :textDocument/completion
-                                       (eglot--CompletionParams)
-                                       :deferred :textDocument/completion
-                                       :cancel-on-input t))
-                (setq items (append
-                             (if (vectorp resp) resp (plist-get resp :items))
-                             nil))
-                (setq cached-proxies
-                      (mapcar
-                       (jsonrpc-lambda
-                           (&rest item &key label insertText insertTextFormat
-                                  &allow-other-keys)
-                         (let ((proxy
+            (sort-completions
+              (lambda (completions)
+                (cl-sort completions
+                  #'string-lessp
+                  :key (lambda (c)
+                         (or (plist-get
+                               (get-text-property 0 'eglot--lsp-item c)
+                               :sortText)
+                           "")))))
+            (metadata `(metadata (category . eglot)
+                         (display-sort-function . ,sort-completions)))
+            resp items (cached-proxies :none)
+            (proxies
+              (lambda ()
+                (if (listp cached-proxies) cached-proxies
+                  (setq resp
+                    (jsonrpc-request server
+                      :textDocument/completion
+                      (eglot--CompletionParams)
+                      :deferred :textDocument/completion
+                      :cancel-on-input t))
+                  (setq items (append
+                                (if (vectorp resp) resp (plist-get resp :items))
+                                nil))
+                  (setq cached-proxies
+                    (mapcar
+                      (jsonrpc-lambda
+                        (&rest item &key label insertText insertTextFormat
+                          &allow-other-keys)
+                        (let ((proxy
                                 (cond ((and (eql insertTextFormat 2)
-                                            (eglot--snippet-expansion-fn))
-                                       (string-trim-left label))
-                                      ((and insertText
-                                            (not (string-empty-p insertText)))
-                                       insertText)
-                                      (t
-                                       (string-trim-left label)))))
-                           (unless (zerop (length proxy))
-                             (put-text-property 0 1 'eglot--lsp-item item proxy))
-                           proxy))
-                       items)))))
-           (resolved (make-hash-table))
-           (resolve-maybe
-            ;; Maybe completion/resolve JSON object `lsp-comp' into
-            ;; another JSON object, if at all possible.  Otherwise,
-            ;; just return lsp-comp.
-            (lambda (lsp-comp)
-              (or (gethash lsp-comp resolved)
+                                         (eglot--snippet-expansion-fn))
+                                        (string-trim-left label))
+                                  ((and insertText
+                                     (not (string-empty-p insertText)))
+                                    insertText)
+                                  (t
+                                    (string-trim-left label)))))
+                          (unless (zerop (length proxy))
+                            (put-text-property 0 1 'eglot--lsp-item item proxy))
+                          proxy))
+                      items)))))
+            (resolved (make-hash-table))
+            (resolve-maybe
+              ;; Maybe completion/resolve JSON object `lsp-comp' into
+              ;; another JSON object, if at all possible.  Otherwise,
+              ;; just return lsp-comp.
+              (lambda (lsp-comp)
+                (or (gethash lsp-comp resolved)
                   (setf (gethash lsp-comp resolved)
-                        (if (and (eglot--server-capable :completionProvider
-                                                        :resolveProvider)
-                                 (plist-get lsp-comp :data))
-                            (jsonrpc-request server :completionItem/resolve
-                                             lsp-comp :cancel-on-input t)
-                          lsp-comp)))))
-           (bounds (bounds-of-thing-at-point 'symbol)))
+                    (if (and (eglot--server-capable :completionProvider
+                               :resolveProvider)
+                          (plist-get lsp-comp :data))
+                      (jsonrpc-request server :completionItem/resolve
+                        lsp-comp :cancel-on-input t)
+                      lsp-comp)))))
+            (bounds (bounds-of-thing-at-point 'symbol)))
       (list
-       (or (car bounds) (point))
-       (or (cdr bounds) (point))
-       (lambda (probe pred action)
-         (cond
-          ((eq action 'metadata) metadata)               ; metadata
-          ((eq action 'lambda)                           ; test-completion
-           (test-completion probe (funcall proxies)))
-          ((eq (car-safe action) 'boundaries) nil)       ; boundaries
-          ((null action)                                 ; try-completion
-           (try-completion probe (funcall proxies)))
-          ((eq action t)                                 ; all-completions
-           (all-completions
-            ""
-            (funcall proxies)
-            (lambda (proxy)
-              (let* ((item (get-text-property 0 'eglot--lsp-item proxy))
-                     (filterText (plist-get item :filterText)))
-                (and (or (null pred) (funcall pred proxy))
-                     (string-prefix-p
-                      probe (or filterText proxy) completion-ignore-case))))))))
-       :annotation-function
-       (lambda (proxy)
-         (eglot--dbind ((CompletionItem) detail kind)
-             (get-text-property 0 'eglot--lsp-item proxy)
-           (let* ((detail (and (stringp detail)
-                               (not (string= detail ""))
-                               detail))
-                  (annotation
-                   (or detail
-                       (cdr (assoc kind eglot--kind-names)))))
-             (when annotation
-               (concat " "
-                       (propertize annotation
-                                   'face 'font-lock-function-name-face))))))
-       :company-kind
-       ;; Associate each lsp-item with a lsp-kind symbol.
-       (lambda (proxy)
-         (when-let* ((lsp-item (get-text-property 0 'eglot--lsp-item proxy))
-                     (kind (alist-get (plist-get lsp-item :kind)
-                                      eglot--kind-names)))
-           (intern (downcase kind))))
-       :company-deprecated
-       (lambda (proxy)
-         (when-let ((lsp-item (get-text-property 0 'eglot--lsp-item proxy)))
-           (or (seq-contains-p (plist-get lsp-item :tags)
-                               1)
-               (eq t (plist-get lsp-item :deprecated)))))
-       :company-docsig
-       ;; FIXME: autoImportText is specific to the pyright language server
-       (lambda (proxy)
-         (when-let* ((lsp-comp (get-text-property 0 'eglot--lsp-item proxy))
-                     (data (plist-get (funcall resolve-maybe lsp-comp) :data))
-                     (import-text (plist-get data :autoImportText)))
-           import-text))
-       :company-doc-buffer
-       (lambda (proxy)
-         (let* ((documentation
-                 (let ((lsp-comp (get-text-property 0 'eglot--lsp-item proxy)))
-                   (plist-get (funcall resolve-maybe lsp-comp) :documentation)))
-                (formatted (and documentation
-                                (eglot--format-markup documentation))))
-           (when formatted
-             (with-current-buffer (get-buffer-create " *eglot doc*")
-               (erase-buffer)
-               (insert formatted)
-               (current-buffer)))))
-       :company-require-match 'never
-       :company-prefix-length
-       (save-excursion
-         (when (car bounds) (goto-char (car bounds)))
-         (when (listp completion-capability)
-           (looking-back
-            (regexp-opt
-             (cl-coerce (cl-getf completion-capability :triggerCharacters) 'list))
-            (line-beginning-position))))
-       :exit-function
-       (lambda (proxy status)
-         (when (memq status '(finished exact))
-           ;; To assist in using this whole `completion-at-point'
-           ;; function inside `completion-in-region', ensure the exit
-           ;; function runs in the buffer where the completion was
-           ;; triggered from.  This should probably be in Emacs itself.
-           ;; (github#505)
-           (with-current-buffer (if (minibufferp)
-                                    (window-buffer (minibuffer-selected-window))
-                                  (current-buffer))
-             (eglot--dbind ((CompletionItem) insertTextFormat
-                            insertText textEdit additionalTextEdits label)
-                 (funcall
+        (or (car bounds) (point))
+        (or (cdr bounds) (point))
+        (lambda (probe pred action)
+          (cond
+            ((eq action 'metadata) metadata)               ; metadata
+            ((eq action 'lambda)                           ; test-completion
+              (test-completion probe (funcall proxies)))
+            ((eq (car-safe action) 'boundaries) nil)       ; boundaries
+            ((null action)                                 ; try-completion
+              (try-completion probe (funcall proxies)))
+            ((eq action t)                                 ; all-completions
+              (all-completions
+                ""
+                (funcall proxies)
+                (lambda (proxy)
+                  (let* ((item (get-text-property 0 'eglot--lsp-item proxy))
+                          (filterText (plist-get item :filterText)))
+                    (and (or (null pred) (funcall pred proxy))
+                      (string-prefix-p
+                        probe (or filterText proxy) completion-ignore-case))))))))
+        :annotation-function
+        (lambda (proxy)
+          (eglot--dbind ((CompletionItem) detail kind)
+            (get-text-property 0 'eglot--lsp-item proxy)
+            (let* ((detail (and (stringp detail)
+                             (not (string= detail ""))
+                             detail))
+                    (annotation
+                      (or detail
+                        (cdr (assoc kind eglot--kind-names)))))
+              (when annotation
+                (concat " "
+                  (propertize annotation
+                    'face 'font-lock-function-name-face))))))
+        :company-kind
+        ;; Associate each lsp-item with a lsp-kind symbol.
+        (lambda (proxy)
+          (when-let* ((lsp-item (get-text-property 0 'eglot--lsp-item proxy))
+                       (kind (alist-get (plist-get lsp-item :kind)
+                               eglot--kind-names)))
+            (intern (downcase kind))))
+        :company-deprecated
+        (lambda (proxy)
+          (when-let ((lsp-item (get-text-property 0 'eglot--lsp-item proxy)))
+            (or (seq-contains-p (plist-get lsp-item :tags)
+                  1)
+              (eq t (plist-get lsp-item :deprecated)))))
+        :company-docsig
+        ;; FIXME: autoImportText is specific to the pyright language server
+        (lambda (proxy)
+          (when-let* ((lsp-comp (get-text-property 0 'eglot--lsp-item proxy))
+                       (data (plist-get (funcall resolve-maybe lsp-comp) :data))
+                       (import-text (plist-get data :autoImportText)))
+            import-text))
+        :company-doc-buffer
+        (lambda (proxy)
+          (let* ((documentation
+                   (let ((lsp-comp (get-text-property 0 'eglot--lsp-item proxy)))
+                     (plist-get (funcall resolve-maybe lsp-comp) :documentation)))
+                  (formatted (and documentation
+                               (eglot--format-markup documentation))))
+            (when formatted
+              (with-current-buffer (get-buffer-create " *eglot doc*")
+                (erase-buffer)
+                (insert formatted)
+                (current-buffer)))))
+        :company-require-match 'never
+        :company-prefix-length
+        (save-excursion
+          (when (car bounds) (goto-char (car bounds)))
+          (when (listp completion-capability)
+            (looking-back
+              (regexp-opt
+                (cl-coerce (cl-getf completion-capability :triggerCharacters) 'list))
+              (line-beginning-position))))
+        :exit-function
+        (lambda (proxy status)
+          (when (memq status '(finished exact))
+            ;; To assist in using this whole `completion-at-point'
+            ;; function inside `completion-in-region', ensure the exit
+            ;; function runs in the buffer where the completion was
+            ;; triggered from.  This should probably be in Emacs itself.
+            ;; (github#505)
+            (with-current-buffer (if (minibufferp)
+                                   (window-buffer (minibuffer-selected-window))
+                                   (current-buffer))
+              (eglot--dbind ((CompletionItem) insertTextFormat
+                              insertText textEdit additionalTextEdits label)
+                (funcall
                   resolve-maybe
                   (or (get-text-property 0 'eglot--lsp-item proxy)
-                      ;; When selecting from the *Completions*
-                      ;; buffer, `proxy' won't have any properties.
-                      ;; A lookup should fix that (github#148)
-                      (get-text-property
-                       0 'eglot--lsp-item
-                       (cl-find proxy (funcall proxies) :test #'string=))))
-               (let ((snippet-fn (and (eql insertTextFormat 2)
-                                      (eglot--snippet-expansion-fn))))
-                 (cond (textEdit
-                        ;; Undo (yes, undo) the newly inserted completion.
-                        ;; If before completion the buffer was "foo.b" and
-                        ;; now is "foo.bar", `proxy' will be "bar".  We
-                        ;; want to delete only "ar" (`proxy' minus the
-                        ;; symbol whose bounds we've calculated before)
-                        ;; (github#160).
-                        (delete-region (+ (- (point) (length proxy))
-                                          (if bounds
-                                              (- (cdr bounds) (car bounds))
-                                            0))
-                                       (point))
-                        (eglot--dbind ((TextEdit) range newText) textEdit
-                          (pcase-let ((`(,beg . ,end)
-                                       (eglot--range-region range)))
-                            (delete-region beg end)
-                            (goto-char beg)
-                            (funcall (or snippet-fn #'insert) newText))))
-                       (snippet-fn
-                        ;; A snippet should be inserted, but using plain
-                        ;; `insertText'.  This requires us to delete the
-                        ;; whole completion, since `insertText' is the full
-                        ;; completion's text.
-                        (delete-region (- (point) (length proxy)) (point))
-                        (funcall snippet-fn (or insertText label))))
-                 (when (cl-plusp (length additionalTextEdits))
-                   (eglot--apply-text-edits additionalTextEdits)))
-               (eglot--signal-textDocument/didChange)
-               (eldoc)))))))))
+                    ;; When selecting from the *Completions*
+                    ;; buffer, `proxy' won't have any properties.
+                    ;; A lookup should fix that (github#148)
+                    (get-text-property
+                      0 'eglot--lsp-item
+                      (cl-find proxy (funcall proxies) :test #'string=))))
+                (let ((snippet-fn (and (eql insertTextFormat 2)
+                                    (eglot--snippet-expansion-fn))))
+                  (cond (textEdit
+                          ;; Undo (yes, undo) the newly inserted completion.
+                          ;; If before completion the buffer was "foo.b" and
+                          ;; now is "foo.bar", `proxy' will be "bar".  We
+                          ;; want to delete only "ar" (`proxy' minus the
+                          ;; symbol whose bounds we've calculated before)
+                          ;; (github#160).
+                          (delete-region (+ (- (point) (length proxy))
+                                           (if bounds
+                                             (- (cdr bounds) (car bounds))
+                                             0))
+                            (point))
+                          (eglot--dbind ((TextEdit) range newText) textEdit
+                            (pcase-let ((`(,beg . ,end)
+                                          (eglot--range-region range)))
+                              (delete-region beg end)
+                              (goto-char beg)
+                              (funcall (or snippet-fn #'insert) newText))))
+                    (snippet-fn
+                      ;; A snippet should be inserted, but using plain
+                      ;; `insertText'.  This requires us to delete the
+                      ;; whole completion, since `insertText' is the full
+                      ;; completion's text.
+                      (delete-region (- (point) (length proxy)) (point))
+                      (funcall snippet-fn (or insertText label))))
+                  (when (cl-plusp (length additionalTextEdits))
+                    (eglot--apply-text-edits additionalTextEdits)))
+                (eglot--signal-textDocument/didChange)
+                (eldoc)))))))))
 
 (defun eglot--hover-info (contents &optional _range)
   (mapconcat #'eglot--format-markup
-             (if (vectorp contents) contents (list contents)) "\n"))
- 
+    (if (vectorp contents) contents (list contents)) "\n"))
+
 (defun eglot--sig-info (sigs active-sig sig-help-active-param)
   (cl-loop
-   for (sig . moresigs) on (append sigs nil) for i from 0
-   concat
-   (eglot--dbind ((SignatureInformation) label documentation parameters activeParameter) sig
-     (with-temp-buffer
-       (save-excursion (insert label))
-       (let ((active-param (or activeParameter sig-help-active-param))
-             params-start params-end)
-         ;; Ad-hoc attempt to parse label as <name>(<params>)
-         (when (looking-at "\\([^(]+\\)(\\([^)]+\\))")
-           (setq params-start (match-beginning 2) params-end (match-end 2))
-           (add-face-text-property (match-beginning 1) (match-end 1)
-                                   'font-lock-function-name-face))
-         (when (eql i active-sig)
-           ;; Decide whether to add one-line-summary to signature line
-           (when (and (stringp documentation)
-                      (string-match "[[:space:]]*\\([^.\r\n]+[.]?\\)"
-                                    documentation))
-             (setq documentation (match-string 1 documentation))
-             (unless (string-prefix-p (string-trim documentation) label)
-               (goto-char (point-max))
-               (insert ": " (eglot--format-markup documentation))))
-           ;; Decide what to do with the active parameter...
-           (when (and (eql i active-sig) active-param
-                      (< -1 active-param (length parameters)))
-             (eglot--dbind ((ParameterInformation) label documentation)
-                 (aref parameters active-param)
-               ;; ...perhaps highlight it in the formals list
-               (when params-start
-                 (goto-char params-start)
-                 (pcase-let
-                     ((`(,beg ,end)
+    for (sig . moresigs) on (append sigs nil) for i from 0
+    concat
+    (eglot--dbind ((SignatureInformation) label documentation parameters activeParameter) sig
+      (with-temp-buffer
+        (save-excursion (insert label))
+        (let ((active-param (or activeParameter sig-help-active-param))
+               params-start params-end)
+          ;; Ad-hoc attempt to parse label as <name>(<params>)
+          (when (looking-at "\\([^(]+\\)(\\([^)]+\\))")
+            (setq params-start (match-beginning 2) params-end (match-end 2))
+            (add-face-text-property (match-beginning 1) (match-end 1)
+              'font-lock-function-name-face))
+          (when (eql i active-sig)
+            ;; Decide whether to add one-line-summary to signature line
+            (when (and (stringp documentation)
+                    (string-match "[[:space:]]*\\([^.\r\n]+[.]?\\)"
+                      documentation))
+              (setq documentation (match-string 1 documentation))
+              (unless (string-prefix-p (string-trim documentation) label)
+                (goto-char (point-max))
+                (insert ": " (eglot--format-markup documentation))))
+            ;; Decide what to do with the active parameter...
+            (when (and (eql i active-sig) active-param
+                    (< -1 active-param (length parameters)))
+              (eglot--dbind ((ParameterInformation) label documentation)
+                (aref parameters active-param)
+                ;; ...perhaps highlight it in the formals list
+                (when params-start
+                  (goto-char params-start)
+                  (pcase-let
+                    ((`(,beg ,end)
                        (if (stringp label)
-                           (let ((case-fold-search nil))
-                             (and (re-search-forward
-                                   (concat "\\<" (regexp-quote label) "\\>")
-                                   params-end t)
-                                  (list (match-beginning 0) (match-end 0))))
+                         (let ((case-fold-search nil))
+                           (and (re-search-forward
+                                  (concat "\\<" (regexp-quote label) "\\>")
+                                  params-end t)
+                             (list (match-beginning 0) (match-end 0))))
                          (mapcar #'1+ (append label nil)))))
-                   (if (and beg end)
-                       (add-face-text-property
+                    (if (and beg end)
+                      (add-face-text-property
                         beg end
                         'eldoc-highlight-function-argument))))
-               ;; ...and/or maybe add its doc on a line by its own.
-               (when documentation
-                 (goto-char (point-max))
-                 (insert "\n"
-                         (propertize
-                          (if (stringp label)
-                              label
-                            (apply #'buffer-substring (mapcar #'1+ label)))
-                          'face 'eldoc-highlight-function-argument)
-                         ": " (eglot--format-markup documentation))))))
-         (buffer-string))))
-   when moresigs concat "\n"))
+                ;; ...and/or maybe add its doc on a line by its own.
+                (when documentation
+                  (goto-char (point-max))
+                  (insert "\n"
+                    (propertize
+                      (if (stringp label)
+                        label
+                        (apply #'buffer-substring (mapcar #'1+ label)))
+                      'face 'eldoc-highlight-function-argument)
+                    ": " (eglot--format-markup documentation))))))
+          (buffer-string))))
+    when moresigs concat "\n"))
 
 (defun eglot-signature-eldoc-function (cb)
   "A member of `eldoc-documentation-functions', for signatures."
   (when (eglot--server-capable :signatureHelpProvider)
     (let ((buf (current-buffer)))
       (jsonrpc-async-request
-       (eglot--current-server-or-lose)
-       :textDocument/signatureHelp (eglot--TextDocumentPositionParams)
-       :success-fn
-       (eglot--lambda ((SignatureHelp)
-                       signatures activeSignature activeParameter)
-         (eglot--when-buffer-window buf
-           (funcall cb
-                    (unless (seq-empty-p signatures)
-                      (eglot--sig-info signatures
-                                       activeSignature
-                                       activeParameter)))))
-       :deferred :textDocument/signatureHelp))
+        (eglot--current-server-or-lose)
+        :textDocument/signatureHelp (eglot--TextDocumentPositionParams)
+        :success-fn
+        (eglot--lambda ((SignatureHelp)
+                         signatures activeSignature activeParameter)
+          (eglot--when-buffer-window buf
+            (funcall cb
+              (unless (seq-empty-p signatures)
+                (eglot--sig-info signatures
+                  activeSignature
+                  activeParameter)))))
+        :deferred :textDocument/signatureHelp))
     t))
 
 (defun eglot-hover-eldoc-function (cb)
@@ -2975,14 +3131,14 @@ for which LSP on-type-formatting should be requested."
   (when (eglot--server-capable :hoverProvider)
     (let ((buf (current-buffer)))
       (jsonrpc-async-request
-       (eglot--current-server-or-lose)
-       :textDocument/hover (eglot--TextDocumentPositionParams)
-       :success-fn (eglot--lambda ((Hover) contents range)
-                     (eglot--when-buffer-window buf
-                       (let ((info (unless (seq-empty-p contents)
-                                     (eglot--hover-info contents range))))
-                         (funcall cb info :buffer t))))
-       :deferred :textDocument/hover))
+        (eglot--current-server-or-lose)
+        :textDocument/hover (eglot--TextDocumentPositionParams)
+        :success-fn (eglot--lambda ((Hover) contents range)
+                      (eglot--when-buffer-window buf
+                        (let ((info (unless (seq-empty-p contents)
+                                      (eglot--hover-info contents range))))
+                          (funcall cb info :buffer t))))
+        :deferred :textDocument/hover))
     (eglot--highlight-piggyback cb)
     t))
 
@@ -2995,83 +3151,83 @@ for which LSP on-type-formatting should be requested."
   (let ((buf (current-buffer)))
     (when (eglot--server-capable :documentHighlightProvider)
       (jsonrpc-async-request
-       (eglot--current-server-or-lose)
-       :textDocument/documentHighlight (eglot--TextDocumentPositionParams)
-       :success-fn
-       (lambda (highlights)
-         (mapc #'delete-overlay eglot--highlights)
-         (setq eglot--highlights
-               (eglot--when-buffer-window buf
-                 (mapcar
-                  (eglot--lambda ((DocumentHighlight) range)
-                    (pcase-let ((`(,beg . ,end)
-                                 (eglot--range-region range)))
-                      (let ((ov (make-overlay beg end)))
-                        (overlay-put ov 'face 'eglot-highlight-symbol-face)
-                        (overlay-put ov 'modification-hooks
-                                     `(,(lambda (o &rest _) (delete-overlay o))))
-                        ov)))
-                  highlights))))
-       :deferred :textDocument/documentHighlight)
+        (eglot--current-server-or-lose)
+        :textDocument/documentHighlight (eglot--TextDocumentPositionParams)
+        :success-fn
+        (lambda (highlights)
+          (mapc #'delete-overlay eglot--highlights)
+          (setq eglot--highlights
+            (eglot--when-buffer-window buf
+              (mapcar
+                (eglot--lambda ((DocumentHighlight) range)
+                  (pcase-let ((`(,beg . ,end)
+                                (eglot--range-region range)))
+                    (let ((ov (make-overlay beg end)))
+                      (overlay-put ov 'face 'eglot-highlight-symbol-face)
+                      (overlay-put ov 'modification-hooks
+                        `(,(lambda (o &rest _) (delete-overlay o))))
+                      ov)))
+                highlights))))
+        :deferred :textDocument/documentHighlight)
       nil)))
 
 (defun eglot-imenu ()
   "Eglot's `imenu-create-index-function'.
 Returns a list as described in docstring of `imenu--index-alist'."
   (cl-labels
-      ((unfurl (obj)
-         (eglot--dcase obj
-           (((SymbolInformation)) (list obj))
-           (((DocumentSymbol) name children)
-            (cons obj
-                  (mapcar
-                   (lambda (c)
-                     (plist-put
-                      c :containerName
-                      (let ((existing (plist-get c :containerName)))
-                        (if existing (format "%s::%s" name existing)
-                          name))))
-                   (mapcan #'unfurl children)))))))
+    ((unfurl (obj)
+       (eglot--dcase obj
+         (((SymbolInformation)) (list obj))
+         (((DocumentSymbol) name children)
+           (cons obj
+             (mapcar
+               (lambda (c)
+                 (plist-put
+                   c :containerName
+                   (let ((existing (plist-get c :containerName)))
+                     (if existing (format "%s::%s" name existing)
+                       name))))
+               (mapcan #'unfurl children)))))))
     (mapcar
-     (pcase-lambda (`(,kind . ,objs))
-       (cons
-        (alist-get kind eglot--symbol-kind-names "Unknown")
-        (mapcan (pcase-lambda (`(,container . ,objs))
-                  (let ((elems (mapcar
-                                (lambda (obj)
-                                  (cons (plist-get obj :name)
-                                        (car (eglot--range-region
+      (pcase-lambda (`(,kind . ,objs))
+        (cons
+          (alist-get kind eglot--symbol-kind-names "Unknown")
+          (mapcan (pcase-lambda (`(,container . ,objs))
+                    (let ((elems (mapcar
+                                   (lambda (obj)
+                                     (cons (plist-get obj :name)
+                                       (car (eglot--range-region
                                               (eglot--dcase obj
                                                 (((SymbolInformation) location)
-                                                 (plist-get location :range))
+                                                  (plist-get location :range))
                                                 (((DocumentSymbol) selectionRange)
-                                                 selectionRange))))))
-                                objs)))
-                    (if container (list (cons container elems)) elems)))
-                (seq-group-by
-                 (lambda (e) (plist-get e :containerName)) objs))))
-     (seq-group-by
-      (lambda (obj) (plist-get obj :kind))
-      (mapcan #'unfurl
-              (jsonrpc-request (eglot--current-server-or-lose)
-                               :textDocument/documentSymbol
-                               `(:textDocument
-                                 ,(eglot--TextDocumentIdentifier))
-                               :cancel-on-input non-essential))))))
+                                                  selectionRange))))))
+                                   objs)))
+                      (if container (list (cons container elems)) elems)))
+            (seq-group-by
+              (lambda (e) (plist-get e :containerName)) objs))))
+      (seq-group-by
+        (lambda (obj) (plist-get obj :kind))
+        (mapcan #'unfurl
+          (jsonrpc-request (eglot--current-server-or-lose)
+            :textDocument/documentSymbol
+            `(:textDocument
+               ,(eglot--TextDocumentIdentifier))
+            :cancel-on-input non-essential))))))
 
 (defun eglot--apply-text-edits (edits &optional version)
   "Apply EDITS for current buffer if at VERSION, or if it's nil."
   (unless (or (not version) (equal version eglot--versioned-identifier))
     (jsonrpc-error "Edits on `%s' require version %d, you have %d"
-                   (current-buffer) version eglot--versioned-identifier))
+      (current-buffer) version eglot--versioned-identifier))
   (atomic-change-group
     (let* ((change-group (prepare-change-group))
-           (howmany (length edits))
-           (reporter (make-progress-reporter
-                      (format "[eglot] applying %s edits to `%s'..."
-                              howmany (current-buffer))
-                      0 howmany))
-           (done 0))
+            (howmany (length edits))
+            (reporter (make-progress-reporter
+                        (format "[eglot] applying %s edits to `%s'..."
+                          howmany (current-buffer))
+                        0 howmany))
+            (done 0))
       (mapc (pcase-lambda (`(,newText ,beg . ,end))
               (let ((source (current-buffer)))
                 (with-temp-buffer
@@ -3091,19 +3247,19 @@ Returns a list as described in docstring of `imenu--index-alist'."
                           ;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32237
                           ;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32278
                           (let ((inhibit-modification-hooks t)
-                                (length (- end beg))
-                                (beg (marker-position beg))
-                                (end (marker-position end)))
+                                 (length (- end beg))
+                                 (beg (marker-position beg))
+                                 (end (marker-position end)))
                             (run-hook-with-args 'before-change-functions
-                                                beg end)
+                              beg end)
                             (replace-buffer-contents temp)
                             (run-hook-with-args 'after-change-functions
-                                                beg (+ beg (length newText))
-                                                length))))
+                              beg (+ beg (length newText))
+                              length))))
                       (progress-reporter-update reporter (cl-incf done)))))))
-            (mapcar (eglot--lambda ((TextEdit) range newText)
-                      (cons newText (eglot--range-region range 'markers)))
-                    (reverse edits)))
+        (mapcar (eglot--lambda ((TextEdit) range newText)
+                  (cons newText (eglot--range-region range 'markers)))
+          (reverse edits)))
       (undo-amalgamate-change-group change-group)
       (progress-reporter-done reporter))))
 
@@ -3111,45 +3267,45 @@ Returns a list as described in docstring of `imenu--index-alist'."
   "Apply the workspace edit WEDIT.  If CONFIRM, ask user first."
   (eglot--dbind ((WorkspaceEdit) changes documentChanges) wedit
     (let ((prepared
-           (mapcar (eglot--lambda ((TextDocumentEdit) textDocument edits)
-                     (eglot--dbind ((VersionedTextDocumentIdentifier) uri version)
-                         textDocument
-                       (list (eglot--uri-to-path uri) edits version)))
-                   documentChanges)))
+            (mapcar (eglot--lambda ((TextDocumentEdit) textDocument edits)
+                      (eglot--dbind ((VersionedTextDocumentIdentifier) uri version)
+                        textDocument
+                        (list (eglot--uri-to-path uri) edits version)))
+              documentChanges)))
       (unless (and changes documentChanges)
         ;; We don't want double edits, and some servers send both
         ;; changes and documentChanges.  This unless ensures that we
         ;; prefer documentChanges over changes.
         (cl-loop for (uri edits) on changes by #'cddr
-                 do (push (list (eglot--uri-to-path uri) edits) prepared)))
+          do (push (list (eglot--uri-to-path uri) edits) prepared)))
       (if (or confirm
-              (cl-notevery #'find-buffer-visiting
-                           (mapcar #'car prepared)))
-          (unless (y-or-n-p
-                   (format "[eglot] Server wants to edit:\n  %s\n Proceed? "
-                           (mapconcat #'identity (mapcar #'car prepared) "\n  ")))
-            (jsonrpc-error "User cancelled server edit")))
+            (cl-notevery #'find-buffer-visiting
+              (mapcar #'car prepared)))
+        (unless (y-or-n-p
+                  (format "[eglot] Server wants to edit:\n  %s\n Proceed? "
+                    (mapconcat #'identity (mapcar #'car prepared) "\n  ")))
+          (jsonrpc-error "User cancelled server edit")))
       (cl-loop for edit in prepared
-               for (path edits version) = edit
-               do (with-current-buffer (find-file-noselect path)
-                    (eglot--apply-text-edits edits version))
-               finally (eldoc) (eglot--message "Edit successful!")))))
+        for (path edits version) = edit
+        do (with-current-buffer (find-file-noselect path)
+             (eglot--apply-text-edits edits version))
+        finally (eldoc) (eglot--message "Edit successful!")))))
 
 (defun eglot-rename (newname)
   "Rename the current symbol to NEWNAME."
   (interactive
-   (list (read-from-minibuffer
-          (format "Rename `%s' to: " (or (thing-at-point 'symbol t)
+    (list (read-from-minibuffer
+            (format "Rename `%s' to: " (or (thing-at-point 'symbol t)
                                          "unknown symbol"))
-          nil nil nil nil
-          (symbol-name (symbol-at-point)))))
+            nil nil nil nil
+            (symbol-name (symbol-at-point)))))
   (unless (eglot--server-capable :renameProvider)
     (eglot--error "Server can't rename!"))
   (eglot--apply-workspace-edit
-   (jsonrpc-request (eglot--current-server-or-lose)
-                    :textDocument/rename `(,@(eglot--TextDocumentPositionParams)
-                                           :newName ,newname))
-   current-prefix-arg))
+    (jsonrpc-request (eglot--current-server-or-lose)
+      :textDocument/rename `(,@(eglot--TextDocumentPositionParams)
+                              :newName ,newname))
+    current-prefix-arg))
 
 (defun eglot--region-bounds ()
   "Region bounds if active, else bounds of things at point."
@@ -3165,71 +3321,71 @@ Interactively, default BEG and END to region's bounds else BEG is
 point and END is nil, which results in a request for code actions
 at point.  With prefix argument, prompt for ACTION-KIND."
   (interactive
-   `(,@(eglot--region-bounds)
-     ,(and current-prefix-arg
-           (completing-read "[eglot] Action kind: "
-                            '("quickfix" "refactor.extract" "refactor.inline"
-                              "refactor.rewrite" "source.organizeImports")))
-     t))
+    `(,@(eglot--region-bounds)
+       ,(and current-prefix-arg
+          (completing-read "[eglot] Action kind: "
+            '("quickfix" "refactor.extract" "refactor.inline"
+               "refactor.rewrite" "source.organizeImports")))
+       t))
   (unless (or (not interactive)
-              (eglot--server-capable :codeActionProvider))
+            (eglot--server-capable :codeActionProvider))
     (eglot--error "Server can't execute code actions!"))
   (let* ((server (eglot--current-server-or-lose))
-         (actions
-          (jsonrpc-request
-           server
-           :textDocument/codeAction
-           (list :textDocument (eglot--TextDocumentIdentifier)
-                 :range (list :start (eglot--pos-to-lsp-position beg)
-                              :end (eglot--pos-to-lsp-position end))
-                 :context
-                 `(:diagnostics
+          (actions
+            (jsonrpc-request
+              server
+              :textDocument/codeAction
+              (list :textDocument (eglot--TextDocumentIdentifier)
+                :range (list :start (eglot--pos-to-lsp-position beg)
+                         :end (eglot--pos-to-lsp-position end))
+                :context
+                `(:diagnostics
                    [,@(cl-loop for diag in (flymake-diagnostics beg end)
-                               when (cdr (assoc 'eglot-lsp-diag
-                                                (eglot--diag-data diag)))
-                               collect it)]
+                        when (cdr (assoc 'eglot-lsp-diag
+                                    (eglot--diag-data diag)))
+                        collect it)]
                    ,@(when action-kind `(:only [,action-kind]))))
-           :deferred t))
-         ;; Redo filtering, in case the `:only' didn't go through.
-         (actions (cl-loop for a across actions
-                           when (or (not action-kind)
-                                    (equal action-kind (plist-get a :kind)))
-                           collect a)))
+              :deferred t))
+          ;; Redo filtering, in case the `:only' didn't go through.
+          (actions (cl-loop for a across actions
+                     when (or (not action-kind)
+                            (equal action-kind (plist-get a :kind)))
+                     collect a)))
     (if interactive
-        (eglot--read-execute-code-action actions server action-kind)
+      (eglot--read-execute-code-action actions server action-kind)
       actions)))
 
 (defun eglot--read-execute-code-action (actions server &optional action-kind)
   "Helper for interactive calls to `eglot-code-actions'"
   (let* ((menu-items
-          (or (cl-loop for a in actions
-                       collect (cons (plist-get a :title) a))
-              (apply #'eglot--error
-                     (if action-kind `("No \"%s\" code actions here" ,action-kind)
-                       `("No code actions here")))))
-         (preferred-action (cl-find-if
-                            (lambda (menu-item)
-                              (plist-get (cdr menu-item) :isPreferred))
-                            menu-items))
-         (default-action (car (or preferred-action (car menu-items))))
-         (chosen (if (and action-kind (null (cadr menu-items)))
-                     (cdr (car menu-items))
-                   (if (listp last-nonmenu-event)
-                       (x-popup-menu last-nonmenu-event `("Eglot code actions:"
+           (or (cl-loop for a in actions
+                 collect (cons (plist-get a :title) a))
+             (apply #'eglot--error
+               (if action-kind `("No \"%s\" code actions here" ,action-kind)
+                 `("No code actions here")))))
+          (preferred-action (cl-find-if
+                              (lambda (menu-item)
+                                (plist-get (cdr menu-item) :isPreferred))
+                              menu-items))
+          (default-action (car (or preferred-action (car menu-items))))
+          (chosen (if (and action-kind (null (cadr menu-items)))
+                    (cdr (car menu-items))
+                    (if (listp last-nonmenu-event)
+                      (x-popup-menu last-nonmenu-event `("Eglot code actions:"
                                                           ("dummy" ,@menu-items)))
-                     (cdr (assoc (completing-read
-                                  (format "[eglot] Pick an action (default %s): "
-                                          default-action)
-                                  menu-items nil t nil nil default-action)
-                                 menu-items))))))
+                      (cdr (assoc (completing-read
+                                    (format "[eglot] Pick an action (default %s): "
+                                      default-action)
+                                    menu-items nil t nil nil default-action)
+                             menu-items))))))
     (eglot--dcase chosen
       (((Command) command arguments)
-       (eglot-execute-command server (intern command) arguments))
+        (eglot-execute-command server (intern command) arguments))
       (((CodeAction) edit command)
-       (when edit (eglot--apply-workspace-edit edit))
-       (when command
-         (eglot--dbind ((Command) command arguments) command
-           (eglot-execute-command server (intern command) arguments)))))))
+        (when edit (eglot--apply-workspace-edit edit))
+        (when command
+          (eglot--dbind ((Command) command arguments) command
+            (eglot-execute-command server (intern command) arguments)))))))
 
 (defmacro eglot--code-action (name kind)
   "Define NAME to execute KIND code action."
@@ -3252,40 +3408,40 @@ at point.  With prefix argument, prompt for ACTION-KIND."
   "Handle dynamic registration of workspace/didChangeWatchedFiles."
   (eglot-unregister-capability server method id)
   (let* (success
-         (globs (mapcar
-                 (eglot--lambda ((FileSystemWatcher) globPattern)
-                   (eglot--glob-compile globPattern t t))
-                 watchers))
-         (dirs-to-watch
-          (delete-dups (mapcar #'file-name-directory
-                               (project-files
-                                (eglot--project server))))))
+          (globs (mapcar
+                   (eglot--lambda ((FileSystemWatcher) globPattern)
+                     (eglot--glob-compile globPattern t t))
+                   watchers))
+          (dirs-to-watch
+            (delete-dups (mapcar #'file-name-directory
+                           (project-files
+                             (eglot--project server))))))
     (cl-labels
-        ((handle-event
-          (event)
-          (pcase-let ((`(,desc ,action ,file ,file1) event))
-            (cond
+      ((handle-event
+         (event)
+         (pcase-let ((`(,desc ,action ,file ,file1) event))
+           (cond
              ((and (memq action '(created changed deleted))
-                   (cl-find file globs :test (lambda (f g) (funcall g f))))
-              (jsonrpc-notify
-               server :workspace/didChangeWatchedFiles
-               `(:changes ,(vector `(:uri ,(eglot--path-to-uri file)
-                                          :type ,(cl-case action
-                                                   (created 1)
-                                                   (changed 2)
-                                                   (deleted 3)))))))
+                (cl-find file globs :test (lambda (f g) (funcall g f))))
+               (jsonrpc-notify
+                 server :workspace/didChangeWatchedFiles
+                 `(:changes ,(vector `(:uri ,(eglot--path-to-uri file)
+                                        :type ,(cl-case action
+                                                 (created 1)
+                                                 (changed 2)
+                                                 (deleted 3)))))))
              ((eq action 'renamed)
-              (handle-event `(,desc 'deleted ,file))
-              (handle-event `(,desc 'created ,file1)))))))
+               (handle-event `(,desc 'deleted ,file))
+               (handle-event `(,desc 'created ,file1)))))))
       (unwind-protect
-          (progn
-            (dolist (dir dirs-to-watch)
-              (push (file-notify-add-watch dir '(change) #'handle-event)
-                    (gethash id (eglot--file-watches server))))
-            (setq
-             success
-             `(:message ,(format "OK, watching %s directories in %s watchers"
-                                 (length dirs-to-watch) (length watchers)))))
+        (progn
+          (dolist (dir dirs-to-watch)
+            (push (file-notify-add-watch dir '(change) #'handle-event)
+              (gethash id (eglot--file-watches server))))
+          (setq
+            success
+            `(:message ,(format "OK, watching %s directories in %s watchers"
+                          (length dirs-to-watch) (length watchers)))))
         (unless success
           (eglot-unregister-capability server method id))))))
 
@@ -3304,34 +3460,34 @@ at point.  With prefix argument, prompt for ACTION-KIND."
   (with-temp-buffer
     (save-excursion (insert glob))
     (cl-loop
-     with grammar = '((:**      "\\*\\*/?"              eglot--glob-emit-**)
-                      (:*       "\\*"                   eglot--glob-emit-*)
-                      (:?       "\\?"                   eglot--glob-emit-?)
-                      (:{}      "{[^][*{}]+}"           eglot--glob-emit-{})
-                      (:range   "\\[\\^?[^][/,*{}]+\\]" eglot--glob-emit-range)
-                      (:literal "[^][,*?{}]+"           eglot--glob-emit-self))
-     until (eobp)
-     collect (cl-loop
-              for (_token regexp emitter) in grammar
-              thereis (and (re-search-forward (concat "\\=" regexp) nil t)
-                           (list (cl-gensym "state-") emitter (match-string 0)))
-              finally (error "Glob '%s' invalid at %s" (buffer-string) (point))))))
+      with grammar = '((:**      "\\*\\*/?"              eglot--glob-emit-**)
+                        (:*       "\\*"                   eglot--glob-emit-*)
+                        (:?       "\\?"                   eglot--glob-emit-?)
+                        (:{}      "{[^][*{}]+}"           eglot--glob-emit-{})
+                        (:range   "\\[\\^?[^][/,*{}]+\\]" eglot--glob-emit-range)
+                        (:literal "[^][,*?{}]+"           eglot--glob-emit-self))
+      until (eobp)
+      collect (cl-loop
+                for (_token regexp emitter) in grammar
+                thereis (and (re-search-forward (concat "\\=" regexp) nil t)
+                          (list (cl-gensym "state-") emitter (match-string 0)))
+                finally (error "Glob '%s' invalid at %s" (buffer-string) (point))))))
 
 (defun eglot--glob-compile (glob &optional byte-compile noerror)
   "Convert GLOB into Elisp function.  Maybe BYTE-COMPILE it.
 If NOERROR, return predicate, else erroring function."
   (let* ((states (eglot--glob-parse glob))
-         (body `(with-current-buffer (get-buffer-create " *eglot-glob-matcher*")
-                  (erase-buffer)
-                  (save-excursion (insert string))
-                  (cl-labels ,(cl-loop for (this that) on states
-                                       for (self emit text) = this
-                                       for next = (or (car that) 'eobp)
-                                       collect (funcall emit text self next))
-                    (or (,(caar states))
-                        (error "Glob done but more unmatched text: '%s'"
-                               (buffer-substring (point) (point-max)))))))
-         (form `(lambda (string) ,(if noerror `(ignore-errors ,body) body))))
+          (body `(with-current-buffer (get-buffer-create " *eglot-glob-matcher*")
+                   (erase-buffer)
+                   (save-excursion (insert string))
+                   (cl-labels ,(cl-loop for (this that) on states
+                                 for (self emit text) = this
+                                 for next = (or (car that) 'eobp)
+                                 collect (funcall emit text self next))
+                     (or (,(caar states))
+                       (error "Glob done but more unmatched text: '%s'"
+                         (buffer-substring (point) (point-max)))))))
+          (form `(lambda (string) ,(if noerror `(ignore-errors ,body) body))))
     (if byte-compile (byte-compile form) form)))
 
 (defun eglot--glob-emit-self (text self next)
@@ -3339,11 +3495,11 @@ If NOERROR, return predicate, else erroring function."
 
 (defun eglot--glob-emit-** (_ self next)
   `(,self () (or (ignore-errors (save-excursion (,next)))
-                 (and (re-search-forward "\\=/?[^/]+/?") (,self)))))
+               (and (re-search-forward "\\=/?[^/]+/?") (,self)))))
 
 (defun eglot--glob-emit-* (_ self next)
   `(,self () (re-search-forward "\\=[^/]")
-          (or (ignore-errors (save-excursion (,next))) (,self))))
+     (or (ignore-errors (save-excursion (,next))) (,self))))
 
 (defun eglot--glob-emit-? (_ self next)
   `(,self () (re-search-forward "\\=[^/]") (,next)))
@@ -3351,9 +3507,9 @@ If NOERROR, return predicate, else erroring function."
 (defun eglot--glob-emit-{} (arg self next)
   (let ((alternatives (split-string (substring arg 1 (1- (length arg))) ",")))
     `(,self ()
-            (or (re-search-forward ,(concat "\\=" (regexp-opt alternatives)) nil t)
-                (error "Failed matching any of %s" ',alternatives))
-            (,next))))
+       (or (re-search-forward ,(concat "\\=" (regexp-opt alternatives)) nil t)
+         (error "Failed matching any of %s" ',alternatives))
+       (,next))))
 
 (defun eglot--glob-emit-range (arg self next)
   (when (eq ?! (aref arg 1)) (aset arg 1 ?^))
@@ -3366,32 +3522,115 @@ If NOERROR, return predicate, else erroring function."
   "" "Eglot mode for listing server connections
 \\{eglot-list-connections-mode-map}"
   (setq-local tabulated-list-format
-              `[("Language server" 16) ("Project name" 16) ("Modes handled" 16)])
+    `[("Language server" 16) ("Project name" 16) ("Modes handled" 16)])
   (tabulated-list-init-header))
 
 (defun eglot-list-connections ()
   "List currently active Eglot connections."
   (interactive)
   (with-current-buffer
-      (get-buffer-create "*EGLOT connections*")
+    (get-buffer-create "*EGLOT connections*")
     (let ((inhibit-read-only t))
       (erase-buffer)
       (eglot-list-connections-mode)
       (setq-local tabulated-list-entries
-                  (mapcar
-                   (lambda (server)
-                     (list server
-                           `[,(or (plist-get (eglot--server-info server) :name)
-                                  (jsonrpc-name server))
-                             ,(eglot-project-nickname server)
-                             ,(mapconcat #'symbol-name
-                                         (eglot--major-modes server)
-                                         ", ")]))
-                   (cl-reduce #'append
-                              (hash-table-values eglot--servers-by-project))))
+        (mapcar
+          (lambda (server)
+            (list server
+              `[,(or (plist-get (eglot--server-info server) :name)
+                   (jsonrpc-name server))
+                 ,(eglot-project-nickname server)
+                 ,(mapconcat #'symbol-name
+                    (eglot--major-modes server)
+                    ", ")]))
+          (cl-reduce #'append
+            (hash-table-values eglot--servers-by-project))))
       (revert-buffer)
       (pop-to-buffer (current-buffer)))))
 
+(defface eglot-inlay-hint
+  '((t (:inherit shadow :height 0.8)))
+  "Face used for inlay hint overlays.")
+
+;; (define-minor-mode eglot--inlay-mode
+;;   "Mode for displaying inlay hint."
+;;   :lighter " inlay"
+;;   )
+
+;; (defun eglot--update-hints()
+;;   "Refresh inlay hints from LSP server."
+;;   (mapc #'delete-overlay (overlays-in (point-min) (point-max)))
+;;   (let ((read-only-p buffer-read-only)
+;; 	       overlays)
+;;     (condition-case err
+;; 	    (let ((lens-table (make-hash-table)))
+;; 	      ;; Get the inlay hint objects.
+;; 	      (mapc (lambda (inlayHint)
+;; 		            (when (and (eglot--server-capable
+;; 			                       :inlayHintProvider :resolveProvider)
+;; 			                  (not (plist-member inlayHint :command)))
+;; 		              (setq inlayHint
+;; 			              (jsonrpc-request (eglot--current-server-or-lose)
+;; 					            :inlayHint/resolve inlayHint)))
+;; 		            (let ((line (thread-first inlayHint
+;; 					                    (plist-get :position)
+;; 					                    (plist-get :line))))
+;; 		              (puthash line
+;; 			              (append (gethash line lens-table) (list inlayHint))
+;; 			              lens-table)))
+;; 		      (jsonrpc-request
+;; 		        (eglot--current-server-or-lose)
+;; 		        :textDocument/inlayHint
+;; 		        (list :textDocument (eglot--TextDocumentIdentifier) :range (list :start (list :line 0 :character 0) :end (list :line (count-lines (point-min) (point-max)) :character 0)))
+;; 		        :deferred :textDocument/inlayHint))
+
+;; 	      ;; Make overlays for them.
+;; 	      (maphash
+;; 	        (lambda (line values)
+;;             ;; TODO: manage InlayHintLabelPart[]
+;; 	          (eglot--widening
+;; 	            (let ((c (plist-get (plist-get (car values) :position) :character))
+;;                      (label-text (propertize (if (and (plist-member (car values) :label) (stringp (plist-get (car values) :label))) (plist-get (car values) :label) "")))
+;; 		                 (text label-text 'mouse-face 'highlight)
+;;                      )
+;; 	              (goto-char (point-min))
+;; 	              (forward-line line)
+;; 	              (eglot-move-to-column c)
+;; 	              (let ((ov (make-overlay (point) (point))))
+;; 		              (push ov overlays)
+;; 		              (overlay-put ov 'eglot-inlay-hint values)
+;; 		              (overlay-put ov 'before-string (propertize text 'face 'eglot-inlay-hint))
+;;                   ))))
+;; 	        lens-table)
+;;         )
+;;       (error
+;;        (mapc #'delete-overlay overlays)
+;;        (setq buffer-read-only read-only-p)
+;;        (eglot--inlay-mode -1)
+;;        (signal (car err) (cdr err))))))
+
+;;       (defun eglot-inlay-hint ()
+;;         "Ask the server for inlay hint and show them in the current buffer."
+;;         (interactive)
+;;         (unless (eglot--server-capable :inlayHintProvider)
+;;           (error "Server does not support inlay hint."))
+
+;;         ;; Setup minor mode which will clean them up and provide keybindings.
+;;         (eglot--inlay-mode 1)
+;;         (eglot--update-hints)
+;;         (add-hook 'after-save-hook #'eglot--update-hints nil t)
+
+;;         (cl-labels
+;;           ((cleanup
+;;              ()
+;;              (remove-hook 'eglot--inlay-mode-hook #'cleanup t)
+;;              (remove-hook 'after-save-hook 'eglot--update-hints t)
+;;              (unless eglot--inlay-mode
+;;                (mapc #'delete-overlay overlays)
+;;                (setq buffer-read-only read-only-p))))
+;;           (add-hook 'eglot--inlay-mode-hook #'cleanup nil t))
+;;         )
+
 \f
 ;;; Hacks
 ;;;
@@ -3408,7 +3647,7 @@ If NOERROR, return predicate, else erroring function."
 ;;;
 
 (make-obsolete-variable 'eglot--managed-mode-hook
-                        'eglot-managed-mode-hook "1.6")
+  'eglot-managed-mode-hook "1.6")
 (provide 'eglot)
 
 \f
@@ -3430,10 +3669,10 @@ If NOERROR, return predicate, else erroring function."
 ;; accordingly.  tryc/allc names made akward/recognizable on purpose.
 
 (add-to-list 'completion-styles-alist
-             '(eglot--lsp-backend-style
-               eglot--lsp-backend-style-try-completion
-               eglot--lsp-backend-style-all-completions
-               "Ad-hoc completion style provided by the completion table."))
+  '(eglot--lsp-backend-style
+     eglot--lsp-backend-style-try-completion
+     eglot--lsp-backend-style-all-completions
+     "Ad-hoc completion style provided by the completion table."))
 
 (defun eglot--lsp-backend-style-call (op string table pred point)
   (when (functionp table)
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 72+ messages in thread

* bug#61412: Inlay hints implementation
  2023-02-21 18:42             ` Dimitri Belopopsky
@ 2023-02-21 21:26               ` João Távora
  2023-02-25  0:21                 ` João Távora
  2023-02-22 19:42               ` Eglot "inlay hints" landed João Távora
  1 sibling, 1 reply; 72+ messages in thread
From: João Távora @ 2023-02-21 21:26 UTC (permalink / raw)
  To: Dimitri Belopopsky; +Cc: Po Lu, Chinmay Dalal, Eli Zaretskii, 61412

Ok, thanks.  While I'm sure Flymake can be used for this,
I'm not sure it should.  The reason is that inlay hints are,
in principle, much more numerous than diagnostics.  Collecting
all of them could be wasteful when the user is only looking at
a section of the file.  Flymake doesn't have very good
mechanisms to work with this scenario.

Anyway, your patch has a very large number of unrelated whitespace
changes, making it almost impossible to read.  Please make one
with only the essential changes.

João

On Tue, Feb 21, 2023 at 6:42 PM Dimitri Belopopsky
<dimitri@belopopsky.com> wrote:
>
> Hello,
>
> I thought the point was not to create overlays directly?
> I'm sharing what I currently have, using the flymake diagnostics directly.
> Maybe something can be salvaged?
>
> In the meantime I'll try out your patches.
>
> On Tue, 21 Feb 2023 at 16:14, João Távora <joaotavora@gmail.com> wrote:
>>
>> [Eli, I just noticed that this bug should be merged with 61066,
>> but I don't know how to do that]
>>
>> Hello,
>>
>> Attached is my implementation of inlay hints for Eglot.
>> Two patches are provided, where the first just lays some
>> basic groundwork to make the actual inlay hint implementation
>> simpler.
>>
>> Regarding copyright stuff, I did look at Chinmay's patch,
>> but I re-started from scratch.  While it was a very good
>> effort, there were too many idiomatic Elisp and Eglot things
>> to change.  I did take Chinmay's face definitions, though.
>> Not sure how to proceed here and if this counts as "derived
>> work" and if we should wait for Chinmay's copyright assignment.
>>
>> I gave it some light testing and I kind of like it.  Quite
>> helpful for C++ with clangd (the only server I tested it with).
>> You can bind `eglot-inlay-hint-mode` to some keybinding probably.
>
>
> It's even more helpful in languages like Rust, that rely on type deduction a lot more.
> Clangd has an extension for designator hints that looks interesting too.
>
>>
>> Documentation in the manual is still missing, but shouldn't
>> be very hard to do.
>>
>> Anyway, this is likely not the end of the inlay hint story
>> because, as the second patch documents, this is likely a
>> very naive implementation that always requests inlay hints
>> for the entire buffer even if just a fraction of it is visible.
>
>
>>
>> A better implementation would probably leverage
>> window-scroll-functions along with the Eglot-specific
>> idle timer.  That is probably much, much more tricky to get
>> right, but is also more than likely the way to go.
>>
>> In the meantime, I'd like your opinion on this patch and
>> the above topics first.
>>
>> João
>>
>> I hope gmail doesn't mess up my attachments...



-- 
João Távora





^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Inlay hints implementation
  2023-02-11  8:13 bug#61412: [PATCH] Add inlay hints to eglot Chinmay Dalal
                   ` (7 preceding siblings ...)
  2023-02-21 15:57 ` Chinmay Dalal
@ 2023-02-22 15:26 ` Chinmay Dalal
  2023-02-22 16:51   ` Chinmay Dalal
  2023-02-22 23:17   ` João Távora
  8 siblings, 2 replies; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-22 15:26 UTC (permalink / raw)
  To: joaotavora; +Cc: 61412


When I apply these patches and add a hook

(add-hook 'eglot-managed-mode-hook #'eglot-inlay-hints-mode)

and try to delete a buffer, I get

eglot--error: [eglot] Can't turn on `eglot-inlay-hints' mode.

once, and then I am able to delete the buffer.


Chinmay





^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Inlay hints implementation
  2023-02-22 15:26 ` Chinmay Dalal
@ 2023-02-22 16:51   ` Chinmay Dalal
  2023-02-22 23:17   ` João Távora
  1 sibling, 0 replies; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-22 16:51 UTC (permalink / raw)
  To: joaotavora; +Cc: 61412

[-- Attachment #1: Type: text/plain, Size: 470 bytes --]

By "these patches" I mean João's patches, I can't figure out how to reply
properly

On Wed, 22 Feb 2023 at 20:56, Chinmay Dalal <dalal.chinmay.0101@gmail.com>
wrote:

>
> When I apply these patches and add a hook
>
> (add-hook 'eglot-managed-mode-hook #'eglot-inlay-hints-mode)
>
> and try to delete a buffer, I get
>
> eglot--error: [eglot] Can't turn on `eglot-inlay-hints' mode.
>
> once, and then I am able to delete the buffer.
>
>
> Chinmay
>

[-- Attachment #2: Type: text/html, Size: 816 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Eglot "inlay hints" landed
  2023-02-21 18:42             ` Dimitri Belopopsky
  2023-02-21 21:26               ` João Távora
@ 2023-02-22 19:42               ` João Távora
  2023-02-23  1:45                 ` [SPAM UNSURE] " Stephen Leake
                                   ` (5 more replies)
  1 sibling, 6 replies; 72+ messages in thread
From: João Távora @ 2023-02-22 19:42 UTC (permalink / raw)
  To: emacs-devel; +Cc: Dimitri Belopopsky, Chinmay Dalal, Po Lu, Eli Zaretskii

I've just landed Eglot "inlay hints" on lisp/progmodes/eglot.el in the
emacs-29 branch.  It's a feature that some users (including me) were
looking for for some time.

Inlay hints are small text annotations to specific parts of the whole
buffer, not unlike diagnostics, but designed to help readability instead
of indicating problems.  For example, a C++ LSP server can serve hints
about positional parameter names in function calls and a variable's
automatically deduced type.  Emacs can display these hints in many
little 0-length overlays with an 'before-string property, thus helping
the user remember those types and parameter names.

Since inlay hints are potentially a large amount of data to request from
the LSP server, the implementation strives to be as parsimonious as
possible with these requests.

So, by default, inlay hints are only requested for the visible portions
of the buffer across windows showing this buffer.  This is done by
leveraging the 'window-scroll-functions' variable, making for a
reasonably complex implementation involving per-window timers.  When
scrolling a window, it may take a short amount of time for inlay hints
to "pop in".  The new user variable 'eglot-lazy-inlay-hints' can be used
to exert some control over this.

Specifically, if the variable's value is set to 'nil', then inlay hints
are greedily fetched for the whole buffer every time a change occurs.
This is a much simpler mode of operation which may avoid problems, but
is also likely much slower in large buffers.

Also, because the inlay feature is probably visually suprising to some,
it is turned OFF by default, which is not the usual practice of Eglot
(at least not when the necessary infrastructure is present).  This
decision may be changed soon.  Here's a good one-liner for enabling it
by default in every Eglot-managed buffer:

(add-hook 'eglot-managed-mode-hook #'eglot-inlay-hints-mode)

I haven't tested inlay hints extensively across many LSP servers, so I
would appreciate any testing, both for functional edge cases and
regarding performance.  There are possibly more optimization
oportunities in the "lazy" mode of operation, like more aggressively
deleting buffer overlays that are not in visible parts of the buffer.

Though I ended up writing this one from scratch, I want to thank Dimitry
Bolopopsky <dimitri@belopopsky.com> and Chinmay Dala
<dalal.chinmay.0101@gmail.com> for suggestions and early patches.

João



^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Inlay hints implementation
  2023-02-22 15:26 ` Chinmay Dalal
  2023-02-22 16:51   ` Chinmay Dalal
@ 2023-02-22 23:17   ` João Távora
  1 sibling, 0 replies; 72+ messages in thread
From: João Távora @ 2023-02-22 23:17 UTC (permalink / raw)
  To: Chinmay Dalal; +Cc: 61412

[-- Attachment #1: Type: text/plain, Size: 650 bytes --]

On Wed, Feb 22, 2023, 15:26 Chinmay Dalal <dalal.chinmay.0101@gmail.com>
wrote:

>
> When I apply these patches and add a hook
>
> (add-hook 'eglot-managed-mode-hook #'eglot-inlay-hints-mode)
>
> and try to delete a buffer, I get
>
> eglot--error: [eglot] Can't turn on `eglot-inlay-hints' mode.
>
> once, and then I am able to delete the buffer.
>

This is due to an unrelated bug that I've also fixed
in the emacs-29 branch.

Anyway, the "inlay hints" feature is now also installed.
I hope you can test it and report any bugs.  You sohuld
be able to use that one-liner to enable eglot-inlay-hints-mode
automatically.

João

[-- Attachment #2: Type: text/html, Size: 1162 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [SPAM UNSURE] Eglot "inlay hints" landed
  2023-02-22 19:42               ` Eglot "inlay hints" landed João Távora
@ 2023-02-23  1:45                 ` Stephen Leake
  2023-02-23  5:29                 ` Chinmay Dalal
                                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 72+ messages in thread
From: Stephen Leake @ 2023-02-23  1:45 UTC (permalink / raw)
  To: João Távora
  Cc: emacs-devel, Dimitri Belopopsky, Chinmay Dalal, Po Lu,
	Eli Zaretskii

João Távora <joaotavora@gmail.com> writes:

> So, by default, inlay hints are only requested for the visible portions
> of the buffer across windows showing this buffer.  This is done by
> leveraging the 'window-scroll-functions' variable, making for a
> reasonably complex implementation involving per-window timers.  When
> scrolling a window, it may take a short amount of time for inlay hints
> to "pop in".  The new user variable 'eglot-lazy-inlay-hints' can be used
> to exert some control over this.

Since font-lock has the same requirement to refresh the visible portion
of the display, requesting inlay hints in a function registered with
jit-lock-register would be a simpler design.


-- 
-- Stephe



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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 10:17                 ` Tassilo Horn
                                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-23  5:29 UTC (permalink / raw)
  To: João Távora
  Cc: emacs-devel, Dimitri Belopopsky, Po Lu, Eli Zaretskii


Thanks, this works well.

> Since inlay hints are potentially a large amount of data to request from
> the LSP server, the implementation strives to be as parsimonious as
> possible with these requests.
>
> So, by default, inlay hints are only requested for the visible portions
> of the buffer across windows showing this buffer.  This is done by
> leveraging the 'window-scroll-functions' variable, making for a
> reasonably complex implementation involving per-window timers.  When
> scrolling a window, it may take a short amount of time for inlay hints
> to "pop in".

Can it be instead done in such a way that initially (when loading a new
file) they are requested for the whole buffer, then on subsequent
changes they are only requested for the visible regions.

This has some edge cases like pasting a huge block into a selected
region (here, text will be changed beyond what is currently visible so
it will take some time when someone scrolls to that region) but IMHO it
seems strictly better than the present.


Chinmay




^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-23  5:29                 ` Chinmay Dalal
@ 2023-02-23  6:31                   ` Eli Zaretskii
  2023-02-23  9:55                     ` Chinmay Dalal
  0 siblings, 1 reply; 72+ messages in thread
From: Eli Zaretskii @ 2023-02-23  6:31 UTC (permalink / raw)
  To: Chinmay Dalal; +Cc: joaotavora, emacs-devel, dimitri, luangruo

> From: Chinmay Dalal <dalal.chinmay.0101@gmail.com>
> Cc: emacs-devel@gnu.org, Dimitri Belopopsky <dimitri@belopopsky.com>, Po Lu
>  <luangruo@yahoo.com>, Eli  Zaretskii <eliz@gnu.org>
> Date: Thu, 23 Feb 2023 10:59:17 +0530
> 
> > So, by default, inlay hints are only requested for the visible portions
> > of the buffer across windows showing this buffer.  This is done by
> > leveraging the 'window-scroll-functions' variable, making for a
> > reasonably complex implementation involving per-window timers.  When
> > scrolling a window, it may take a short amount of time for inlay hints
> > to "pop in".
> 
> Can it be instead done in such a way that initially (when loading a new
> file) they are requested for the whole buffer, then on subsequent
> changes they are only requested for the visible regions.

Why would we want that?



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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 11:05                       ` Eli Zaretskii
  0 siblings, 2 replies; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-23  9:55 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: joaotavora, emacs-devel, dimitri, luangruo


Eli Zaretskii <eliz@gnu.org> writes:

>> From: Chinmay Dalal <dalal.chinmay.0101@gmail.com>
>> Cc: emacs-devel@gnu.org, Dimitri Belopopsky <dimitri@belopopsky.com>, Po Lu
>>  <luangruo@yahoo.com>, Eli  Zaretskii <eliz@gnu.org>
>> Date: Thu, 23 Feb 2023 10:59:17 +0530
>> 
>> > So, by default, inlay hints are only requested for the visible portions
>> > of the buffer across windows showing this buffer.  This is done by
>> > leveraging the 'window-scroll-functions' variable, making for a
>> > reasonably complex implementation involving per-window timers.  When
>> > scrolling a window, it may take a short amount of time for inlay hints
>> > to "pop in".
>> 
>> Can it be instead done in such a way that initially (when loading a new
>> file) they are requested for the whole buffer, then on subsequent
>> changes they are only requested for the visible regions.
>
> Why would we want that?

It will solve this problem:

>> > When
>> > scrolling a window, it may take a short amount of time for inlay hints
>> > to "pop in".



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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:05                       ` Eli Zaretskii
  1 sibling, 1 reply; 72+ messages in thread
From: João Távora @ 2023-02-23 10:03 UTC (permalink / raw)
  To: Chinmay Dalal; +Cc: Eli Zaretskii, emacs-devel, dimitri, luangruo

On Thu, Feb 23, 2023 at 9:56 AM Chinmay Dalal
<dalal.chinmay.0101@gmail.com> wrote:

> > Why would we want that?
>
>
> >> > When
> >> > scrolling a window, it may take a short amount of time for inlay hints
> >> > to "pop in".

> It will solve this problem:

Not significantly though. There are many operations that
invalidate the whole buffer's hints, like changing the name
of a function parameter, or doing some git operation
which changes the contents of the whole buffer.  That will
still cause pop-in.  So I don't think it's worth the added
complexity: if you want no scrolling pop-in, set
eglot-lazy-inlay-hints to nil.  Maybe it's not bad for small
buffers and fast servers.

João



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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 10:17                 ` Tassilo Horn
  2023-02-23 12:55                 ` Chinmay Dalal
                                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 72+ messages in thread
From: Tassilo Horn @ 2023-02-23 10:17 UTC (permalink / raw)
  To: emacs-devel

João Távora <joaotavora@gmail.com> writes:

Hi João,

> I've just landed Eglot "inlay hints" on lisp/progmodes/eglot.el in the
> emacs-29 branch.  It's a feature that some users (including me) were
> looking for for some time.

I've just tried it out within a rust project of mine and it's fabulous!
Thanks a lot.

Bye,
Tassilo



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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
  0 siblings, 2 replies; 72+ messages in thread
From: Dimitri Belopopsky @ 2023-02-23 10:55 UTC (permalink / raw)
  To: João Távora; +Cc: Chinmay Dalal, Eli Zaretskii, emacs-devel, luangruo

[-- Attachment #1: Type: text/plain, Size: 1492 bytes --]

Inlay hints are working well on my end!
The only issue I seem to be having is when the server doesn't support inlay
hints, and I have " (add-hook 'eglot-managed-mode-hook
#'eglot-inlay-hints-mode)" in my config.

It keeps erroring in this case and the connection to the lsp server seems
to terminate in some cases. In any case it causes interference with other
things, like completion which can stop working.

You can try it with pylsp, and add "(add-hook 'eglot-managed-mode-hook
#'eglot-inlay-hints-mode)" to your config. Trying to write a simple main
function will show issues.

For example this python code:

def main():
    pass



On Thu, 23 Feb 2023 at 11:04, João Távora <joaotavora@gmail.com> wrote:

> On Thu, Feb 23, 2023 at 9:56 AM Chinmay Dalal
> <dalal.chinmay.0101@gmail.com> wrote:
>
> > > Why would we want that?
> >
> >
> > >> > When
> > >> > scrolling a window, it may take a short amount of time for inlay
> hints
> > >> > to "pop in".
>
> > It will solve this problem:
>
> Not significantly though. There are many operations that
> invalidate the whole buffer's hints, like changing the name
> of a function parameter, or doing some git operation
> which changes the contents of the whole buffer.  That will
> still cause pop-in.  So I don't think it's worth the added
> complexity: if you want no scrolling pop-in, set
> eglot-lazy-inlay-hints to nil.  Maybe it's not bad for small
> buffers and fast servers.
>
> João
>

[-- Attachment #2: Type: text/html, Size: 2145 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-23  9:55                     ` Chinmay Dalal
  2023-02-23 10:03                       ` João Távora
@ 2023-02-23 11:05                       ` Eli Zaretskii
  2023-02-23 11:23                         ` João Távora
  1 sibling, 1 reply; 72+ messages in thread
From: Eli Zaretskii @ 2023-02-23 11:05 UTC (permalink / raw)
  To: Chinmay Dalal; +Cc: joaotavora, emacs-devel, dimitri, luangruo

> From: Chinmay Dalal <dalal.chinmay.0101@gmail.com>
> Cc: joaotavora@gmail.com, emacs-devel@gnu.org, dimitri@belopopsky.com,
>  luangruo@yahoo.com
> Date: Thu, 23 Feb 2023 15:25:21 +0530
> 
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> >> Can it be instead done in such a way that initially (when loading a new
> >> file) they are requested for the whole buffer, then on subsequent
> >> changes they are only requested for the visible regions.
> >
> > Why would we want that?
> 
> It will solve this problem:
> 
> >> > When
> >> > scrolling a window, it may take a short amount of time for inlay hints
> >> > to "pop in".

So would using jit-lock-register, which was proposed here.



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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
  1 sibling, 0 replies; 72+ messages in thread
From: João Távora @ 2023-02-23 11:07 UTC (permalink / raw)
  To: Dimitri Belopopsky; +Cc: Chinmay Dalal, Eli Zaretskii, emacs-devel, luangruo

On Thu, Feb 23, 2023 at 10:55 AM Dimitri Belopopsky
<dimitri@belopopsky.com> wrote:
>
> Inlay hints are working well on my end!
> The only issue I seem to be having is when the server doesn't support inlay hints, and I have " (add-hook 'eglot-managed-mode-hook #'eglot-inlay-hints-mode)" in my config.

That's odd.  I distinctly remembered thinking about this
and making this issue a warning, not an error. Maybe it didn't make
it in the commit I pushed somehow.  I'll check.

João



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-23 11:05                       ` Eli Zaretskii
@ 2023-02-23 11:23                         ` João Távora
  2023-02-23 12:36                           ` Eli Zaretskii
  0 siblings, 1 reply; 72+ messages in thread
From: João Távora @ 2023-02-23 11:23 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Chinmay Dalal, emacs-devel, dimitri, luangruo

On Thu, Feb 23, 2023 at 11:05 AM Eli Zaretskii <eliz@gnu.org> wrote:
>
> > From: Chinmay Dalal <dalal.chinmay.0101@gmail.com>
> > Cc: joaotavora@gmail.com, emacs-devel@gnu.org, dimitri@belopopsky.com,
> >  luangruo@yahoo.com
> > Date: Thu, 23 Feb 2023 15:25:21 +0530
> >
> >
> > Eli Zaretskii <eliz@gnu.org> writes:
> >
> > >> Can it be instead done in such a way that initially (when loading a new
> > >> file) they are requested for the whole buffer, then on subsequent
> > >> changes they are only requested for the visible regions.
> > >
> > > Why would we want that?
> >
> > It will solve this problem:
> >
> > >> > When
> > >> > scrolling a window, it may take a short amount of time for inlay hints
> > >> > to "pop in".
>
> So would using jit-lock-register, which was proposed here.

The "pop-in" delay is just a function of the intentional
bandwidth-conserving timer delay + the normal LSP interprocess
communication delay. Any jit/lazy Emacs-side solution
is going to have to deal at least with the second addend of
that sum.

jit-lock-register was unknown to me.  It seems to rely on some
heuristic to know what regions need to be "refontified".  I wonder
if the heuristic will be accurate for inlay hints, since changing
void foo(int bar){...}  to void foo(int baz){...} in one part of
the buffer doesn't usually change the fontification of the rest
of the buffer.  But it might very well invalidate the inlay hints
everywhere.

In fact the invalidation impact is not just in the same buffer,
but potentially all other buffers (all the ones where a call to
'foo' is found).  Eglot's inlay hints implementation doesn't
handle this edge case.  Though I don't think it would be
extremely hard to, it doesn't seem extremely relevant for what
is usually a "best effort" helper feature from the LSP side.

João



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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
  1 sibling, 1 reply; 72+ messages in thread
From: João Távora @ 2023-02-23 12:03 UTC (permalink / raw)
  To: Dimitri Belopopsky; +Cc: Chinmay Dalal, Eli Zaretskii, emacs-devel, luangruo

On Thu, Feb 23, 2023 at 10:55 AM Dimitri Belopopsky
<dimitri@belopopsky.com> wrote:
>
> Inlay hints are working well on my end!
> The only issue I seem to be having is when the server doesn't support inlay hints, and I have " (add-hook 'eglot-managed-mode-hook #'eglot-inlay-hints-mode)" in my config.
>
> It keeps erroring in this case and the connection to the lsp server seems to terminate in some cases. In any case it causes interference with other things, like completion which can stop working.
>
> You can try it with pylsp, and add "(add-hook 'eglot-managed-mode-hook #'eglot-inlay-hints-mode)" to your config. Trying to write a simple main function will show issues.

I can't reproduce this with this server.  All I see is a warning:

Warning (eglot): No :inlayHintProvider support. Inlay hints will not work.

which is how it's supposed to work.

João



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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
  0 siblings, 1 reply; 72+ messages in thread
From: Eli Zaretskii @ 2023-02-23 12:36 UTC (permalink / raw)
  To: João Távora; +Cc: dalal.chinmay.0101, emacs-devel, dimitri, luangruo

> From: João Távora <joaotavora@gmail.com>
> Date: Thu, 23 Feb 2023 11:23:40 +0000
> Cc: Chinmay Dalal <dalal.chinmay.0101@gmail.com>, emacs-devel@gnu.org, 
> 	dimitri@belopopsky.com, luangruo@yahoo.com
> 
> On Thu, Feb 23, 2023 at 11:05 AM Eli Zaretskii <eliz@gnu.org> wrote:
> >
> > > Eli Zaretskii <eliz@gnu.org> writes:
> > >
> > > >> Can it be instead done in such a way that initially (when loading a new
> > > >> file) they are requested for the whole buffer, then on subsequent
> > > >> changes they are only requested for the visible regions.
> > > >
> > > > Why would we want that?
> > >
> > > It will solve this problem:
> > >
> > > >> > When
> > > >> > scrolling a window, it may take a short amount of time for inlay hints
> > > >> > to "pop in".
> >
> > So would using jit-lock-register, which was proposed here.
> 
> The "pop-in" delay is just a function of the intentional
> bandwidth-conserving timer delay + the normal LSP interprocess
> communication delay. Any jit/lazy Emacs-side solution
> is going to have to deal at least with the second addend of
> that sum.

I don't understand: using jit-lock-register just means your code is
called via jit-lock's fontification-functions instead of
window-scroll-functions that you used.  Any problems with LSP delays
that you deal with in the latter should be possible in the former as
well, no?  Or what am I missing?

The advantages of using jit-lock are that (a) it is more accurate in
telling you which parts of the buffer are about to be displayed, and
(b) it is much more reliable, because window-scroll-functions are not
necessarily called when something changes on display.  For example, we
lately discovered that pixel-scroll-precision-mode doesn't call
window-scroll-functions.

> jit-lock-register was unknown to me.  It seems to rely on some
> heuristic to know what regions need to be "refontified".

It isn't a heuristic.  jit-lock is called from the display engine,
which always has a pretty good idea which parts of the buffer it needs
to show on the screen.

> I wonder if the heuristic will be accurate for inlay hints, since
> changing void foo(int bar){...}  to void foo(int baz){...} in one
> part of the buffer doesn't usually change the fontification of the
> rest of the buffer.

The display engine doesn't know which parts will be affected by the
change, it only knowes what's on display and what isn't.  The function
called via fontification-functions are supposed to know their job, and
look at the parts of the buffer according to their needs; jit-lock
just gives them a hint in the form of the region of the buffer it
wants to display.

> In fact the invalidation impact is not just in the same buffer,
> but potentially all other buffers (all the ones where a call to
> 'foo' is found).  Eglot's inlay hints implementation doesn't
> handle this edge case.  Though I don't think it would be
> extremely hard to, it doesn't seem extremely relevant for what
> is usually a "best effort" helper feature from the LSP side.

We don't need to make the overlays until the buffer is shown in some
window, right?



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-22 19:42               ` Eglot "inlay hints" landed João Távora
                                   ` (2 preceding siblings ...)
  2023-02-23 10:17                 ` Tassilo Horn
@ 2023-02-23 12:55                 ` Chinmay Dalal
  2023-02-23 19:50                 ` Nikola Pajkovsky
  2023-02-27 22:50                 ` Johann Klähn
  5 siblings, 0 replies; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-23 12:55 UTC (permalink / raw)
  To: João Távora
  Cc: emacs-devel, Dimitri Belopopsky, Po Lu, Eli Zaretskii


I've come to realize that the pop-in is not a real problem - if one
is to read the information provided by the hints they have to spend time
doing that anyway

Chinmay



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-23 12:36                           ` Eli Zaretskii
@ 2023-02-23 12:57                             ` João Távora
  2023-02-23 14:48                               ` Eli Zaretskii
  0 siblings, 1 reply; 72+ messages in thread
From: João Távora @ 2023-02-23 12:57 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: dalal.chinmay.0101, emacs-devel, dimitri, luangruo

Eli Zaretskii <eliz@gnu.org> writes:

> The advantages of using jit-lock are that (a) it is more accurate in
> telling you which parts of the buffer are about to be displayed, and
> (b)

Ah, if it works with "parts of the buffer about to be displayed", then it
should be good, yes.  But the docstring of jit-lock-register says "START
and END indicating the region that needs to be (re)fontified".  If that
is guaranteed to always match the "region of the buffer about to be
displayed" in the window, then we're good.

I wonder if it also removes the need for the "smoothing" timers I am
using.

But note though, that while this has promise for a simpler and more
robust implementation, it will _not_ solve the "pop-in" delay.  The
effects that Eglot's jit-lock-register FUN fontification function
produces in the buffer are guaranteed _not_ be finished by the time FUN
returns (unless these FUN is allowed to be slow and blocking, which I
really don't think is the point).  That's what I meant by "any
Emacs-side solution [...] deal with [the normal LSP interprocess
communication] delay".

> it is much more reliable, because window-scroll-functions are not
> necessarily called when something changes on display.  For example, we
> lately discovered that pixel-scroll-precision-mode doesn't call
> window-scroll-functions.

>> In fact the invalidation impact is not just in the same buffer,
>> but potentially all other buffers (all the ones where a call to
>> 'foo' is found).  Eglot's inlay hints implementation doesn't
>> handle this edge case.  Though I don't think it would be
>> extremely hard to, it doesn't seem extremely relevant for what
>> is usually a "best effort" helper feature from the LSP side.
>
> We don't need to make the overlays until the buffer is shown in some
> window, right?

Yes, but two buffers A and B might already be showing in some window.
If you do the change in buffer A and it affects B, then in the current
version, the parts of A being show in windows will be updated, but B the
parts of B being shown in some other windows will not.

João

PS: On an unrelated note, I pushed this to emacs-29.  If you wish me to
revert the inlay hints implementation and do all this work in master,
it's fine by me.  In practice, it won't really make that much of a
difference because Eglot (along with being a new emacs-29 feature) is
also a GNU ELPA :core package and emacs-29 users will have access to the
latest and greatest features and bugfixes relatively easily anyway.



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-23 12:03                           ` João Távora
@ 2023-02-23 13:25                             ` Dimitri Belopopsky
  0 siblings, 0 replies; 72+ messages in thread
From: Dimitri Belopopsky @ 2023-02-23 13:25 UTC (permalink / raw)
  To: João Távora; +Cc: Chinmay Dalal, Eli Zaretskii, emacs-devel, luangruo

[-- Attachment #1: Type: text/plain, Size: 1167 bytes --]

On Thu, 23 Feb 2023 at 13:05, João Távora <joaotavora@gmail.com> wrote:

> On Thu, Feb 23, 2023 at 10:55 AM Dimitri Belopopsky
> <dimitri@belopopsky.com> wrote:
> >
> > Inlay hints are working well on my end!
> > The only issue I seem to be having is when the server doesn't support
> inlay hints, and I have " (add-hook 'eglot-managed-mode-hook
> #'eglot-inlay-hints-mode)" in my config.
> >
> > It keeps erroring in this case and the connection to the lsp server
> seems to terminate in some cases. In any case it causes interference with
> other things, like completion which can stop working.
> >
> > You can try it with pylsp, and add "(add-hook 'eglot-managed-mode-hook
> #'eglot-inlay-hints-mode)" to your config. Trying to write a simple main
> function will show issues.
>
> I can't reproduce this with this server.  All I see is a warning:
>
> Warning (eglot): No :inlayHintProvider support. Inlay hints will not work.
>
> which is how it's supposed to work.
>
> João
>

My bad... I'm using straight.el, and somehow it wasn't picking things
correctly. Forcing it to use the built-in variant makes everything work
correctly.

[-- Attachment #2: Type: text/html, Size: 1656 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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
  0 siblings, 1 reply; 72+ messages in thread
From: Eli Zaretskii @ 2023-02-23 14:48 UTC (permalink / raw)
  To: João Távora; +Cc: dalal.chinmay.0101, emacs-devel, dimitri, luangruo

> From: João Távora <joaotavora@gmail.com>
> Cc: dalal.chinmay.0101@gmail.com,  emacs-devel@gnu.org,
>   dimitri@belopopsky.com,  luangruo@yahoo.com
> Date: Thu, 23 Feb 2023 12:57:26 +0000
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > We don't need to make the overlays until the buffer is shown in some
> > window, right?
> 
> Yes, but two buffers A and B might already be showing in some window.
> If you do the change in buffer A and it affects B, then in the current
> version, the parts of A being show in windows will be updated, but B the
> parts of B being shown in some other windows will not.

If there's a change in A that affects B, jit-lock will call
fontification-functions in both A and B, each one when it's about to
display the corresponding window.

> PS: On an unrelated note, I pushed this to emacs-29.  If you wish me to
> revert the inlay hints implementation and do all this work in master,
> it's fine by me.

emacs-29 is fine, thanks.



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-23 14:48                               ` Eli Zaretskii
@ 2023-02-23 16:09                                 ` João Távora
  2023-02-23 17:17                                   ` Eli Zaretskii
  0 siblings, 1 reply; 72+ messages in thread
From: João Távora @ 2023-02-23 16:09 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: dalal.chinmay.0101, emacs-devel, dimitri, luangruo

On Thu, Feb 23, 2023 at 2:49 PM Eli Zaretskii <eliz@gnu.org> wrote:
>
> > From: João Távora <joaotavora@gmail.com>
> > Cc: dalal.chinmay.0101@gmail.com,  emacs-devel@gnu.org,
> >   dimitri@belopopsky.com,  luangruo@yahoo.com
> > Date: Thu, 23 Feb 2023 12:57:26 +0000
> >
> > Eli Zaretskii <eliz@gnu.org> writes:
> >
> > > We don't need to make the overlays until the buffer is shown in some
> > > window, right?
> >
> > Yes, but two buffers A and B might already be showing in some window.
> > If you do the change in buffer A and it affects B, then in the current
> > version, the parts of A being show in windows will be updated, but B the
> > parts of B being shown in some other windows will not.
>
> If there's a change in A that affects B, jit-lock will call
> fontification-functions in both A and B, each one when it's about to
> display the corresponding window.

Sure, but you're in charge of coding up the "affection" by asking
the LSP server.  In other words, only the LSP server knows that the
change in A affects B. You must assume that it does and ask it "Hey LSP
server, given that I've just changed document A, in your document B from
42 to 420 is are there any new or different inlay hints you'd like to
give me?" jit-lock cannot foresee that upfront, it will only act on B's
display if B's buffer is changed.

Regardless of that separate issue, I started experimenting with
jit-lock-register.  It's promising, but has problems. Here's a
small experiment.  Use this foo.cpp file somewhere and have say,
the clangd server handy.

void foo(int bar){}

int main() {
  foo(42);
  .... repeats about ~250 times ....
  foo(42);
}

If you start eglot and eglot-inlay-hints-mode in this buffer,
you should see

void foo(int bar){}

int main() {
  foo(bar: 42);  // 'bar: ' is the untangible hint overlay
  foo(bar: 42);
  foo(bar: 42);
  ...
  < end of window >

I also traced the current eglot--update-hints-1 file which is a
binary function of two buffer positions, just like the jit
function.

This is the function that contacts the LSP server via JSONRPC
and some time in the future, after it returns, the 0-length
overlays will be created.

Here's how it is called after enabling eglot and scrolling 4
screenfuls forward (C-v) and 4 back (M-v).

======================================================================
1 -> (eglot--update-hints-1 1 476)
1 <- eglot--update-hints-1: nil
======================================================================
1 -> (eglot--update-hints-1 443 927)
1 <- eglot--update-hints-1: nil
======================================================================
1 -> (eglot--update-hints-1 894 1378)
1 <- eglot--update-hints-1: nil
======================================================================
1 -> (eglot--update-hints-1 1345 1829)
1 <- eglot--update-hints-1: nil
======================================================================
1 -> (eglot--update-hints-1 1796 2280)
1 <- eglot--update-hints-1: nil
======================================================================
1 -> (eglot--update-hints-1 1345 1829)
1 <- eglot--update-hints-1: nil
======================================================================
1 -> (eglot--update-hints-1 894 1378)
1 <- eglot--update-hints-1: nil
======================================================================
1 -> (eglot--update-hints-1 443 927)
1 <- eglot--update-hints-1: nil
======================================================================
1 -> (eglot--update-hints-1 1 476)
1 <- eglot--update-hints-1: nil

As you can see, it only requests inlay hints for the regions actually
displayed in the window.  It re-requests some stuff when going
back, since it has no memory of what it already requested or if
things were invalidated.

Now if I put eglot--update-hints-1 in jit-lock-function and
disable my window-scroll-functions, then do the same:

======================================================================
1 -> (eglot--update-hints-1 1 1501)
1 <- eglot--update-hints-1: nil
======================================================================
1 -> (eglot--update-hints-1 1501 2678)
1 <- eglot--update-hints-1: nil

As you can see, it did much larger, heavier requests upfront, even before
it knew I was going to scroll forward.  But in general it worked and you
can argue that doing only two requests for larger chunks of inlay hints
is better than more requests for smaller chunks.

Now, if I change the line void foo(int bar){} to void foo(int baz){}
the point is to get updated hints in the window:

int main() {
  foo(baz: 42);  // 'bar: ' is the untangible hint overlay
  foo(baz: 42);
  foo(baz: 42);
  ...
  < end of window >

The jit-lock implementation will do this:

======================================================================
1 -> (eglot--update-hints-1 1 19)
1 <- eglot--update-hints-1: nil
======================================================================
1 -> (eglot--update-hints-1 1 20)
1 <- eglot--update-hints-1: nil
======================================================================
1 -> (eglot--update-hints-1 20 1520)
1 <- eglot--update-hints-1: nil

The first two calls are useless and so is most of the third one.
For the third one you can argue, as above, that it's a good
thing to predict that the user is going to scroll down.  But
the first two, which also caused two LSP requests, are definitely
useless.

OT1H, I think I can make this work, by finding some means to
coalesce those three calls into one.  Suggestions welcome.
Even after that complexity, it seems it will be a much neater
implementation.

OTOH it sort of negates my original intention of requesting
only as many inlay hints as strictly necessary.  THough that
might have been premature optimization anyway.

> > PS: On an unrelated note, I pushed this to emacs-29.  If you wish me to
> > revert the inlay hints implementation and do all this work in master,
> > it's fine by me.
>
> emacs-29 is fine, thanks.

OK!

João



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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
  0 siblings, 1 reply; 72+ messages in thread
From: Eli Zaretskii @ 2023-02-23 17:17 UTC (permalink / raw)
  To: João Távora; +Cc: dalal.chinmay.0101, emacs-devel, dimitri, luangruo

> From: João Távora <joaotavora@gmail.com>
> Date: Thu, 23 Feb 2023 16:09:24 +0000
> Cc: dalal.chinmay.0101@gmail.com, emacs-devel@gnu.org, dimitri@belopopsky.com, 
> 	luangruo@yahoo.com
> 
> > If there's a change in A that affects B, jit-lock will call
> > fontification-functions in both A and B, each one when it's about to
> > display the corresponding window.
> 
> Sure, but you're in charge of coding up the "affection" by asking
> the LSP server.  In other words, only the LSP server knows that the
> change in A affects B. You must assume that it does and ask it "Hey LSP
> server, given that I've just changed document A, in your document B from
> 42 to 420 is are there any new or different inlay hints you'd like to
> give me?" jit-lock cannot foresee that upfront, it will only act on B's
> display if B's buffer is changed.

That's no for jit-lock to do.  And I don't see how it could be
relevant to the issue we are discussing.  How do you do this now?

> Regardless of that separate issue, I started experimenting with
> jit-lock-register.  It's promising, but has problems.

That might be so, but one problem it does NOT have is missing the
cases when you MUST ask the LSP server, because something is going to
change on display.  window-scroll-functions cannot promise that, since
they are only called "when the window is scrolled", and there's more
to that condition than meets the eye, believe me.



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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:27                                       ` Stefan Monnier
  0 siblings, 2 replies; 72+ messages in thread
From: João Távora @ 2023-02-23 17:46 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: dalal.chinmay.0101, emacs-devel, dimitri, luangruo

[-- Attachment #1: Type: text/plain, Size: 2764 bytes --]

On Thu, Feb 23, 2023 at 5:17 PM Eli Zaretskii <eliz@gnu.org> wrote:
>
> > From: João Távora <joaotavora@gmail.com>
> > Date: Thu, 23 Feb 2023 16:09:24 +0000
> > Cc: dalal.chinmay.0101@gmail.com, emacs-devel@gnu.org, dimitri@belopopsky.com,
> >       luangruo@yahoo.com
> >
> > > If there's a change in A that affects B, jit-lock will call
> > > fontification-functions in both A and B, each one when it's about to
> > > display the corresponding window.
> >
> > Sure, but you're in charge of coding up the "affection" by asking
> > the LSP server.  In other words, only the LSP server knows that the
> > change in A affects B. You must assume that it does and ask it "Hey LSP
> > server, given that I've just changed document A, in your document B from
> > 42 to 420 is are there any new or different inlay hints you'd like to
> > give me?" jit-lock cannot foresee that upfront, it will only act on B's
> > display if B's buffer is changed.
>
> That's no for jit-lock to do.  And I don't see how it could be
> relevant to the issue we are discussing.  How do you do this now?

I don't.  I was just pointing out that jit-lock by itself doesn't
solve this A -> B dependency, which you seemed to suggest it does when
you wrote:

> > If there's a change in A that affects B, jit-lock will call
> > fontification-functions in both A and B, each one when it's about to
> > display the corresponding window.

So I agree with you shouldn't be continuing the discussion of this
topic: it is for later.

> That might be so, but one problem it does NOT have is missing the
> cases when you MUST ask the LSP server, because something is going to
> change on display.

That's true.  But then so does the other more naive implementation
which you get when you set eglot-lazy-inlay-hints to nil, and I'm
not sure which one is more performance.

> window-scroll-functions cannot promise that, since
> they are only called "when the window is scrolled", and there's more
> to that condition than meets the eye, believe me.

I believe you.  But as far as I can tell so far, it's the least
imperfect of the methods, and I haven't seen demonstrations of
problems so far, only your speculation of hypothetical problems.
Which again, I believe in, but I would like to measure the actual
problems quantitatively and qualitatively to make a good decision.

I'd love to switch over to the jit-lock implementation as it has
potential to be much neater.  But I can't seem to get it to not
over-request stuff. I attach the patch I've been trying, and it's
clearly got some thinkos when you test it.  It doesn't help that
`window-start` and `window-end` aren't -- apparently -- reliable
when called from a jit-lock function.

João

[-- Attachment #2: better-inlay-hints-maybe.patch --]
[-- Type: application/octet-stream, Size: 4989 bytes --]

diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 7b4251a1242..76b56fa14e0 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -3507,14 +3507,27 @@ eglot-lazy-inlay-hints
 (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--inlay-hints-outstanding (cons nil nil)
+  "Largest (BEG . END) window subregion not yet updated for inlay hints.")
+
+(defun eglot--update-hints (from to)
+  "Jit-lock function for Eglot inlay hints."
+  (cl-symbol-macrolet ((x eglot--inlay-hints-outstanding))
+    (setq x (cons (min from (or (car x) most-positive-fixnum))
+                  (max to (or (cdr x) 0))))
+    (let ((w (get-buffer-window)))
+      (trace-values "window:      " w)
+      (trace-values "window-region:      " (cons (window-start w) (window-end w)))
+      (trace-values "outstanding: " eglot--inlay-hints-outstanding)
+      ;  FIXME: this is NOT the correct condition to decide when to
+      ;  request stuff from the server.
+      (when (or (< (car x) (window-start w)) (> (cdr x) (window-end w)))
+        (unwind-protect
+            (eglot--update-hints-1 (car x) (cdr x))
+          (setq x (cons nil nil)))))))
 
 (defun eglot--update-hints-1 (from to)
-  "Request LSP inlay hints and annotate current buffer from FROM to TO."
+  "Do actual work for `eglot--update-hints', including LSP request."
   (let* ((buf (current-buffer))
          (paint-hint
           (eglot--lambda ((InlayHint) position kind label paddingLeft paddingRight)
@@ -3545,33 +3558,6 @@ 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))))))))))
-
 (define-minor-mode eglot-inlay-hints-mode
   "Minor mode for annotating buffers with LSP server's inlay hints."
   :global nil
@@ -3581,24 +3567,15 @@ eglot-inlay-hints-mode
            (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)
-           ;; 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))
+           (jit-lock-register #'eglot--update-hints))
           (t
            (add-hook 'eglot--document-changed-hook
                      #'eglot--inlay-hints-fully nil t)
            (eglot--inlay-hints-fully))))
         (t
-         (remove-hook 'eglot--document-changed-hook
-                      #'eglot--inlay-hints-lazily t)
+         (jit-lock-unregister #'eglot--update-hints)
          (remove-hook 'eglot--document-changed-hook
                       #'eglot--inlay-hints-fully t)
-         (remove-hook 'window-scroll-functions
-                      #'eglot--inlay-hints-after-scroll t)
          (remove-overlays nil nil 'eglot--inlay-hint t))))
 
 \f

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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:27                                       ` Stefan Monnier
  1 sibling, 1 reply; 72+ messages in thread
From: Eli Zaretskii @ 2023-02-23 18:01 UTC (permalink / raw)
  To: João Távora, Stefan Monnier
  Cc: dalal.chinmay.0101, emacs-devel, dimitri, luangruo

> From: João Távora <joaotavora@gmail.com>
> Date: Thu, 23 Feb 2023 17:46:08 +0000
> Cc: dalal.chinmay.0101@gmail.com, emacs-devel@gnu.org, dimitri@belopopsky.com, 
> 	luangruo@yahoo.com
> 
> > That's no for jit-lock to do.  And I don't see how it could be
> > relevant to the issue we are discussing.  How do you do this now?
> 
> I don't.  I was just pointing out that jit-lock by itself doesn't
> solve this A -> B dependency, which you seemed to suggest it does when
> you wrote:
> 
> > > If there's a change in A that affects B, jit-lock will call
> > > fontification-functions in both A and B, each one when it's about to
> > > display the corresponding window.

That was written under the assumption that the overlays in B are
already updated.  Then redisplay will know it must redraw B.

> > window-scroll-functions cannot promise that, since
> > they are only called "when the window is scrolled", and there's more
> > to that condition than meets the eye, believe me.
> 
> I believe you.  But as far as I can tell so far, it's the least
> imperfect of the methods, and I haven't seen demonstrations of
> problems so far, only your speculation of hypothetical problems.

I actually gave you a recipe for demonstrating the problems I have in
mind: scroll the window under pixel-scroll-precision-mode.  AFAIK, we
don't call window-scroll-functions in that case.

Another situation where we don't call window-scroll-functions is when
the user types into the buffer.

Yet another situation is when you type "C-x 1" to delete all the other
windows on the frame, leaving the current window that now shows more
stuff than before.

> I'd love to switch over to the jit-lock implementation as it has
> potential to be much neater.  But I can't seem to get it to not
> over-request stuff. I attach the patch I've been trying, and it's
> clearly got some thinkos when you test it.  It doesn't help that
> `window-start` and `window-end` aren't -- apparently -- reliable
> when called from a jit-lock function.

Sorry, I don't have time for that ATM, but maybe Stefan will want to
comment.



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-23 18:01                                       ` Eli Zaretskii
@ 2023-02-23 19:26                                         ` João Távora
  2023-02-23 19:54                                           ` Eli Zaretskii
  0 siblings, 1 reply; 72+ messages in thread
From: João Távora @ 2023-02-23 19:26 UTC (permalink / raw)
  To: Eli Zaretskii
  Cc: Stefan Monnier, dalal.chinmay.0101, emacs-devel, dimitri,
	luangruo

On Thu, Feb 23, 2023 at 6:01 PM Eli Zaretskii <eliz@gnu.org> wrote:
>
> > From: João Távora <joaotavora@gmail.com>
> > Date: Thu, 23 Feb 2023 17:46:08 +0000
> > Cc: dalal.chinmay.0101@gmail.com, emacs-devel@gnu.org, dimitri@belopopsky.com,
> >       luangruo@yahoo.com
> >
> > > That's no for jit-lock to do.  And I don't see how it could be
> > > relevant to the issue we are discussing.  How do you do this now?
> >
> > I don't.  I was just pointing out that jit-lock by itself doesn't
> > solve this A -> B dependency, which you seemed to suggest it does when
> > you wrote:
> >
> > > > If there's a change in A that affects B, jit-lock will call
> > > > fontification-functions in both A and B, each one when it's about to
> > > > display the corresponding window.
>
> That was written under the assumption that the overlays in B are
> already updated.  Then redisplay will know it must redraw B.
>
> > > window-scroll-functions cannot promise that, since
> > > they are only called "when the window is scrolled", and there's more
> > > to that condition than meets the eye, believe me.
> >
> > I believe you.  But as far as I can tell so far, it's the least
> > imperfect of the methods, and I haven't seen demonstrations of
> > problems so far, only your speculation of hypothetical problems.
>
> I actually gave you a recipe for demonstrating the problems I have in
> mind: scroll the window under pixel-scroll-precision-mode.  AFAIK, we
> don't call window-scroll-functions in that case.

I don't have a pixel-scroll-enabled Emacs to test.  Can you scroll
large portions of the window like that and w-s-functions will never
get called?  I'd say that's a bug we should fix.

> Another situation where we don't call window-scroll-functions is when
> the user types into the buffer.

That is handled by Eglot's after-change-functions already so it
isn't a problem.

> Yet another situation is when you type "C-x 1" to delete all the other
> windows on the frame, leaving the current window that now shows more
> stuff than before.

Reproduced.  Easy enough to fix with window-configuration-change-functions.
I pushed a fix.

> > I'd love to switch over to the jit-lock implementation as it has
> > potential to be much neater.  But I can't seem to get it to not
> > over-request stuff. I attach the patch I've been trying, and it's
> > clearly got some thinkos when you test it.  It doesn't help that
> > `window-start` and `window-end` aren't -- apparently -- reliable
> > when called from a jit-lock function.
>
> Sorry, I don't have time for that ATM, but maybe Stefan will want to
> comment.

I'll keep trying. I think what I'd want is for the "contextual" call
with the big chunk about to be jit-refontified remember the smaller
adjacent chunks and make a request for the big chunk + the smaller
chunks.  Here's the latest version of the patch using a timer to
achieve that.  It's pretty gross, but I hope it expresses the idea.

diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index eea8be6d1aa..251f6f11090 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -3507,14 +3507,47 @@ eglot-lazy-inlay-hints
 (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--inlay-hints-outstanding nil
+  "List of regions")
+
+(defvar-local eglot--inlay-hints-outstanding-timer nil
+  "Timer")
+
+(defun eglot--update-hints (from to)
+  "Jit-lock function for Eglot inlay hints."
+  (if eglot--inlay-hints-outstanding-timer
+      (cancel-timer eglot--inlay-hints-outstanding-timer))
+  (push (cons from to) eglot--inlay-hints-outstanding)
+  (setq eglot--inlay-hints-outstanding-timer
+        (run-with-timer
+         ;; FIXME: Gross
+         (+ 0.1 (max eglot-lazy-inlay-hints jit-lock-context-time))
+         nil (lambda ()
+               (trace-values eglot--inlay-hints-outstanding)
+               (cl-loop with region< = (lambda (r1 r2)
+                                         (if (= (car r1) (car r2))
+                                             (< (cdr r1) (cdr r2))
+                                           (< (car r1) (car r2))))
+                        with sorted = (cl-sort
eglot--inlay-hints-outstanding region<)
+                        with 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)
+                        for r in coalesced
+                        do (eglot--update-hints-1 (max (car r) (point-min))
+                                                  (min (cdr r) (point-max)))
+                        finally
+                        (setq eglot--inlay-hints-outstanding nil
+                              eglot--inlay-hints-outstanding-timer nil))))))

 (defun eglot--update-hints-1 (from to)
-  "Request LSP inlay hints and annotate current buffer from FROM to TO."
+  "Do actual work for `eglot--update-hints', including LSP request."
   (let* ((buf (current-buffer))
          (paint-hint
           (eglot--lambda ((InlayHint) position kind label paddingLeft
paddingRight)
@@ -3545,36 +3578,6 @@ 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
@@ -3584,28 +3587,15 @@ eglot-inlay-hints-mode
            (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))
+           (jit-lock-register #'eglot--update-hints))
           (t
            (add-hook 'eglot--document-changed-hook
                      #'eglot--inlay-hints-fully nil t)
            (eglot--inlay-hints-fully))))
         (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)
+         (jit-lock-unregister #'eglot--update-hints)
          (remove-hook 'eglot--document-changed-hook
                       #'eglot--inlay-hints-fully t)
-         (remove-hook 'window-scroll-functions
-                      #'eglot--inlay-hints-after-scroll t)
          (remove-overlays nil nil 'eglot--inlay-hint t))))



^ permalink raw reply related	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-23 17:46                                     ` João Távora
  2023-02-23 18:01                                       ` Eli Zaretskii
@ 2023-02-23 19:27                                       ` Stefan Monnier
  2023-02-23 19:39                                         ` João Távora
  1 sibling, 1 reply; 72+ messages in thread
From: Stefan Monnier @ 2023-02-23 19:27 UTC (permalink / raw)
  To: João Távora
  Cc: Eli Zaretskii, dalal.chinmay.0101, emacs-devel, dimitri, luangruo

> I'd love to switch over to the jit-lock implementation as it has
> potential to be much neater.  But I can't seem to get it to not
> over-request stuff. I attach the patch I've been trying, and it's
> clearly got some thinkos when you test it.  It doesn't help that
> `window-start` and `window-end` aren't -- apparently -- reliable
> when called from a jit-lock function.

Sorry, I have not followed the discussion, so I don't know what are
"inlay hints" nor how to test them.  What problem do you see what you
try your code?

> +(defvar-local eglot--inlay-hints-outstanding (cons nil nil)
> +  "Largest (BEG . END) window subregion not yet updated for inlay hints.")
> +
> +(defun eglot--update-hints (from to)
> +  "Jit-lock function for Eglot inlay hints."
> +  (cl-symbol-macrolet ((x eglot--inlay-hints-outstanding))
> +    (setq x (cons (min from (or (car x) most-positive-fixnum))
> +                  (max to (or (cdr x) 0))))
> +    (let ((w (get-buffer-window)))
> +      (trace-values "window:      " w)
> +      (trace-values "window-region:      " (cons (window-start w) (window-end w)))
> +      (trace-values "outstanding: " eglot--inlay-hints-outstanding)
> +      ;  FIXME: this is NOT the correct condition to decide when to
> +      ;  request stuff from the server.
> +      (when (or (< (car x) (window-start w)) (> (cdr x) (window-end w)))
> +        (unwind-protect
> +            (eglot--update-hints-1 (car x) (cdr x))
> +          (setq x (cons nil nil)))))))

Why do you compare to `window-start/end`?

As you say, they're not reliably available during jit-lock, and that's
for fundamental reasons: jit-lock effects can change `window-start/end`.

But I can't see a good reason why you'd need to do such comparison:
the fact that jit-lock calls you is supposed to say "we need this for
redisplay", so you shouldn't need to double-check against window bounds.

Unless maybe you have `jit-lock-stealth-time` set to a non-nil value, in
which case indeed jit-lock will be called even on non-displayed areas of
the buffer, but it's arguably what the user (you in this case) asked for.

IOW, you should be able to skip `eglot--update-hints` altogether and use
`eglot--update-hints-1` directly (modulo catching errors and such maybe).


        Stefan




^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-23 19:27                                       ` Stefan Monnier
@ 2023-02-23 19:39                                         ` João Távora
  2023-02-23 19:53                                           ` Stefan Monnier
  0 siblings, 1 reply; 72+ messages in thread
From: João Távora @ 2023-02-23 19:39 UTC (permalink / raw)
  To: Stefan Monnier
  Cc: Eli Zaretskii, dalal.chinmay.0101, emacs-devel, dimitri, luangruo

On Thu, Feb 23, 2023 at 7:28 PM Stefan Monnier <monnier@iro.umontreal.ca> wrote:
>
> > I'd love to switch over to the jit-lock implementation as it has
> > potential to be much neater.  But I can't seem to get it to not
> > over-request stuff. I attach the patch I've been trying, and it's
> > clearly got some thinkos when you test it.  It doesn't help that
> > `window-start` and `window-end` aren't -- apparently -- reliable
> > when called from a jit-lock function.
>
> Sorry, I have not followed the discussion, so I don't know what are
> "inlay hints" nor how to test them.  What problem do you see what you
> try your code?

Hi Stefan.  Inlay hints are described summarily in the very first
message to this thread and in the commit implementing them.  They're
very many 0-length overlays, almost like tooltips, annotating
type deductions and positional parameter names.

The goal here is to request them at the correct time and balance
multiple objectives:

1. minimize the number of requests
2. ensure the request covers all visible portions of the buffer
across any windows showing it.
3. don't request for too large a region, else the amount of data can
be prohibitive.

The latest message I sent to Eli has my best shot at it.  If you have
a clangd executable somewhere, you can test this rather easily with
the eglot.el in core and a cpp file such as

void foo(int bar) {}

int main() {
   foo(42);
   foo(42);
  ... very many lines of this ...
   foo(42);
   foo(42);
   foo(42);
}

You should see some intangible text appear in the buffer so that
it will look like:


void foo(int bar) {}

int main() {
   foo( bar: 42);
   foo( bar: 42);
  ... very many lines of this ...
   foo( bar: 42);
   foo( bar: 42);
   foo( bar: 42);
}

Then scroll around in the buffer and check the traces of
eglot--update-hints-1.  Try to change that 'int bar' to
'int quux' and see if the system reacts correctly to the change
updating the ' bar: ' hint to ' quux: '.

See also my last message to Eli, which contains a horrible
patch that nevertheless does more or less achieve those
objectives.

João



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-22 19:42               ` Eglot "inlay hints" landed João Távora
                                   ` (3 preceding siblings ...)
  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-27 22:50                 ` Johann Klähn
  5 siblings, 1 reply; 72+ messages in thread
From: Nikola Pajkovsky @ 2023-02-23 19:50 UTC (permalink / raw)
  To: João Távora
  Cc: emacs-devel, Dimitri Belopopsky, Chinmay Dalal, Po Lu,
	Eli Zaretskii

João Távora <joaotavora@gmail.com> writes:

> I've just landed Eglot "inlay hints" on lisp/progmodes/eglot.el in the
> emacs-29 branch.  It's a feature that some users (including me) were
> looking for for some time.

Nice work João!

I tried rust inlay hints, and it does not work for me. I bet it's because
eglot uses label only if it's string. The same problem was in lsp-mode.

  https://github.com/emacs-lsp/lsp-mode/commit/6b01d49757994c09c90623bf67f072d02f00f8e9

--
Nikola



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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
  0 siblings, 1 reply; 72+ messages in thread
From: Stefan Monnier @ 2023-02-23 19:53 UTC (permalink / raw)
  To: João Távora
  Cc: Eli Zaretskii, dalal.chinmay.0101, emacs-devel, dimitri, luangruo

> See also my last message to Eli, which contains a horrible
> patch that nevertheless does more or less achieve those
> objectives.

Hmm... that patch looks reasonably sane to me (while writing the
previous message I hesitated to suggest to delay the actual work to
after redisplay so you can coalesce several jit chunks).

Which part do you find horrible?


        Stefan




^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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
  0 siblings, 1 reply; 72+ messages in thread
From: Eli Zaretskii @ 2023-02-23 19:54 UTC (permalink / raw)
  To: João Távora
  Cc: monnier, dalal.chinmay.0101, emacs-devel, dimitri, luangruo

> From: João Távora <joaotavora@gmail.com>
> Date: Thu, 23 Feb 2023 19:26:10 +0000
> Cc: Stefan Monnier <monnier@iro.umontreal.ca>, dalal.chinmay.0101@gmail.com, 
> 	emacs-devel@gnu.org, dimitri@belopopsky.com, luangruo@yahoo.com
> 
> > I actually gave you a recipe for demonstrating the problems I have in
> > mind: scroll the window under pixel-scroll-precision-mode.  AFAIK, we
> > don't call window-scroll-functions in that case.
> 
> I don't have a pixel-scroll-enabled Emacs to test.  Can you scroll
> large portions of the window like that and w-s-functions will never
> get called?  I'd say that's a bug we should fix.
> 
> > Another situation where we don't call window-scroll-functions is when
> > the user types into the buffer.
> 
> That is handled by Eglot's after-change-functions already so it
> isn't a problem.
> 
> > Yet another situation is when you type "C-x 1" to delete all the other
> > windows on the frame, leaving the current window that now shows more
> > stuff than before.
> 
> Reproduced.  Easy enough to fix with window-configuration-change-functions.
> I pushed a fix.

My point is that using jit-lock machinery, you will never miss an
update, because redisplay is very good at knowing when something needs
to be redrawn.  It has to.  By contrast, all those hooks are less
reliable, and also make Emacs sluggish, because the hooks are many
times triggered when there's nothing to do wrt display.



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-23 19:54                                           ` Eli Zaretskii
@ 2023-02-23 20:03                                             ` João Távora
  0 siblings, 0 replies; 72+ messages in thread
From: João Távora @ 2023-02-23 20:03 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: monnier, dalal.chinmay.0101, emacs-devel, dimitri, luangruo

On Thu, Feb 23, 2023 at 7:54 PM Eli Zaretskii <eliz@gnu.org> wrote:

> > Reproduced.  Easy enough to fix with window-configuration-change-functions.
> > I pushed a fix.
>
> My point is that using jit-lock machinery, you will never miss an
> update, because redisplay is very good at knowing when something needs
> to be redrawn.  It has to.

Sure, I agree there.

> By contrast, all those hooks are less
> reliable, and also make Emacs sluggish, because the hooks are many
> times triggered when there's nothing to do wrt display.

It remains to be seen whether this is more or less sluggish than the jit-lock
version, because at least the naive jit-lock version is making lots of
requests over to the process.  In TRAMP scenarios or networking
scenarios, this may be slow.  So I'm am focusing on finding the best solution
but it's not immediately obvious that jit is always best, because of the fixed
overhead (in this case LSP comms), which doesn't exist in the use cases
jit-lock was designed for.

João



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-23 19:53                                           ` Stefan Monnier
@ 2023-02-23 20:09                                             ` João Távora
  2023-02-23 22:19                                               ` Stefan Monnier
  0 siblings, 1 reply; 72+ messages in thread
From: João Távora @ 2023-02-23 20:09 UTC (permalink / raw)
  To: Stefan Monnier
  Cc: Eli Zaretskii, dalal.chinmay.0101, emacs-devel, dimitri, luangruo

On Thu, Feb 23, 2023 at 7:53 PM Stefan Monnier <monnier@iro.umontreal.ca> wrote:
>
> > See also my last message to Eli, which contains a horrible
> > patch that nevertheless does more or less achieve those
> > objectives.
>
> Hmm... that patch looks reasonably sane to me (while writing the
> previous message I hesitated to suggest to delay the actual work to
> after redisplay so you can coalesce several jit chunks).
>
> Which part do you find horrible?

Mostly the fact that it's operating on a separate timer, but one
that is directly correlated to jit-lock-context-time.  So
the bookkeeping and the coalescing of the small+large
jit chunks should be provided by the jit infrastructure instead. And
then no extra timer or logic would be needed.  Can we make jit-lock.el
a :core ELPA package?

João



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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
  0 siblings, 2 replies; 72+ messages in thread
From: João Távora @ 2023-02-23 21:35 UTC (permalink / raw)
  To: Nikola Pajkovsky
  Cc: emacs-devel, Dimitri Belopopsky, Chinmay Dalal, Po Lu,
	Eli Zaretskii

On Thu, Feb 23, 2023 at 7:50 PM Nikola Pajkovsky <n.pajkovsky@gmail.com> wrote:
>
> João Távora <joaotavora@gmail.com> writes:
>
> > I've just landed Eglot "inlay hints" on lisp/progmodes/eglot.el in the
> > emacs-29 branch.  It's a feature that some users (including me) were
> > looking for for some time.
>
> Nice work João!
>
> I tried rust inlay hints, and it does not work for me. I bet it's because
> eglot uses label only if it's string. The same problem was in lsp-mode.

Actually, I think I have code for that.  So please show a reproduction
recipe.  I think someone reported here that hints are working
correctly with rust-analyzer, and I've tried it briefly myself with good
results. So it must be something else.

João



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-23 21:35                   ` João Távora
@ 2023-02-23 21:45                     ` Nikola Pajkovsky
  2023-02-24  4:20                     ` Chinmay Dalal
  1 sibling, 0 replies; 72+ messages in thread
From: Nikola Pajkovsky @ 2023-02-23 21:45 UTC (permalink / raw)
  To: João Távora
  Cc: Nikola Pajkovsky, emacs-devel, Dimitri Belopopsky, Chinmay Dalal,
	Po Lu, Eli Zaretskii

João Távora <joaotavora@gmail.com> writes:

> On Thu, Feb 23, 2023 at 7:50 PM Nikola Pajkovsky <n.pajkovsky@gmail.com> wrote:
>>
>> João Távora <joaotavora@gmail.com> writes:
>>
>> > I've just landed Eglot "inlay hints" on lisp/progmodes/eglot.el in the
>> > emacs-29 branch.  It's a feature that some users (including me) were
>> > looking for for some time.
>>
>> Nice work João!
>>
>> I tried rust inlay hints, and it does not work for me. I bet it's because
>> eglot uses label only if it's string. The same problem was in lsp-mode.
>
> Actually, I think I have code for that.  So please show a reproduction
> recipe.  I think someone reported here that hints are working
> correctly with rust-analyzer, and I've tried it briefly myself with good
> results. So it must be something else.

Interesting.

```
-> % rust-analyzer --version
rust-analyzer 0.3.1402-standalone
```

For instance, I don't see the inlay hints for the code like this:

```
struct Foo {
    b: u32,
}

fn main() {
    let s = Foo { b: 10 };
    let f = std::fs::File::create("t");
}
```



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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
  0 siblings, 1 reply; 72+ messages in thread
From: Stefan Monnier @ 2023-02-23 22:19 UTC (permalink / raw)
  To: João Távora
  Cc: Eli Zaretskii, dalal.chinmay.0101, emacs-devel, dimitri, luangruo

> 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)

so that changing the `foo` definition will need to update the inlay on
the call to `foo` that is earlier in the buffer, hence jit-lock-context
refresh won't be sufficient and you'll need to force your own refresh.
[ I think jit-lock would benefit from being able to flush a particular
  backend's without forcing all of the backends at the sane time.  ]

> 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.


        Stefan




^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-23 22:19                                               ` Stefan Monnier
@ 2023-02-23 23:59                                                 ` João Távora
  2023-02-24  1:08                                                   ` Stefan Monnier
  0 siblings, 1 reply; 72+ messages in thread
From: João Távora @ 2023-02-23 23:59 UTC (permalink / raw)
  To: Stefan Monnier
  Cc: Eli Zaretskii, dalal.chinmay.0101, emacs-devel, dimitri, luangruo

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))))



^ permalink raw reply related	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-23 23:59                                                 ` João Távora
@ 2023-02-24  1:08                                                   ` Stefan Monnier
  2023-02-24  2:28                                                     ` João Távora
  2023-02-24  7:35                                                     ` Eli Zaretskii
  0 siblings, 2 replies; 72+ messages in thread
From: Stefan Monnier @ 2023-02-24  1:08 UTC (permalink / raw)
  To: João Távora
  Cc: Eli Zaretskii, dalal.chinmay.0101, emacs-devel, dimitri, luangruo

>> 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?

Yes and no: yes, there are already cases where the jit-lock-context
heuristic of refreshing everything *after* a modification is not
sufficient, but as a general rule programming languages are designed for
"forward-only parsing" so it's unusual for a change at POS to affect the
parsing before POS (and most uses of jit-lock limit themselves to
syntactic information, so there are rather few cases where this is
a problem).  In those few cases we use things like the
`jit-lock-defer-multiline` property which the major mode's font-lock
rules can place manually in an ad-hoc manner.

In your case, I suspect for the affected language servers there's not much
more we can do but to say that the whole buffer's inlays need to be
recomputed after a change.

> 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.

I think it won't work reliably in the case where the "immediate"
jit-lock refresh happens to cover everything until window-end (e.g. we
just inserted chunk of text that ends after window-end).  In that case
the jit-lock-context thingy will just mark the rest of the buffer as
"not fresh" but won't call your fontification function at all.
[ And of course, there are also those few rare modes that don't use
  jit-lock-context at all.  ]

I think using your own timer is probably the better option.  I'd
make it wait for `eglot-lazy-inlay-hints` rather than (+
eglot-lazy-inlay-hints jit-lock-context-time), but I'd give it as
default value a value computed from `jit-lock-context-time`.


        Stefan




^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-24  1:08                                                   ` Stefan Monnier
@ 2023-02-24  2:28                                                     ` João Távora
  2023-02-24  7:35                                                     ` Eli Zaretskii
  1 sibling, 0 replies; 72+ messages in thread
From: João Távora @ 2023-02-24  2:28 UTC (permalink / raw)
  To: Stefan Monnier
  Cc: Eli Zaretskii, dalal.chinmay.0101, emacs-devel, dimitri, luangruo

On Fri, Feb 24, 2023 at 1:08 AM Stefan Monnier <monnier@iro.umontreal.ca> wrote:

> In your case, I suspect for the affected language servers there's not much
> more we can do but to say that the whole buffer's inlays need to be
> recomputed after a change.

I'd really like to avoid that.  If it comes to that, and the problem
really is annoying enough (it might not be, and inlay hints are usually
a best-effort thing by the servers themselves) then I think going back
to the window-scroll-functions might be a better option.  It doesn't have this
problem.

> > 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.
>
> I think it won't work reliably in the case where the "immediate"
> jit-lock refresh happens to cover everything until window-end (e.g. we
> just inserted chunk of text that ends after window-end).  In that case
> the jit-lock-context thingy will just mark the rest of the buffer as
> "not fresh" but won't call your fontification function at all.

Yes, I noticed something like that: many immediate small jit lock calls
where the condition was true.

> [ And of course, there are also those few rare modes that don't use
>   jit-lock-context at all.  ]
>
> I think using your own timer is probably the better option.  I'd
> make it wait for `eglot-lazy-inlay-hints` rather than (+
> eglot-lazy-inlay-hints jit-lock-context-time), but I'd give it as
> default value a value computed from `jit-lock-context-time`.

Using a timer is necessary, but I've found that a 0-second timer is
sufficient to coalesce "well".  But we only fire the timer if
the "unfontify-pos == point-max" condition is met.  Then we don't
need to compete with jit-lock-context-timer, which is really the part I found
very bad.

I've pushed this last version to emacs-29.  Have a look there,
please.  And thanks for the insight!

João



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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
  1 sibling, 2 replies; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-24  4:20 UTC (permalink / raw)
  To: João Távora
  Cc: Nikola Pajkovsky, emacs-devel, Dimitri Belopopsky, Po Lu,
	Eli Zaretskii

[-- Attachment #1: Type: text/plain, Size: 1830 bytes --]


João Távora <joaotavora@gmail.com> writes:

> On Thu, Feb 23, 2023 at 7:50 PM Nikola Pajkovsky <n.pajkovsky@gmail.com> wrote:
>>
>> João Távora <joaotavora@gmail.com> writes:
>>
>> > I've just landed Eglot "inlay hints" on lisp/progmodes/eglot.el in the
>> > emacs-29 branch.  It's a feature that some users (including me) were
>> > looking for for some time.
>>
>> Nice work João!
>>
>> I tried rust inlay hints, and it does not work for me. I bet it's because
>> eglot uses label only if it's string. The same problem was in lsp-mode.
>
> Actually, I think I have code for that.  So please show a reproduction
> recipe.  I think someone reported here that hints are working
> correctly with rust-analyzer, and I've tried it briefly myself with good
> results. So it must be something else.
>
> João


You can look at the end of any long rust function with hl-line-mode on:
there's an empty overlay but it's supposed to have a link to the
beginning of the function. Screenshot attached.

I think the bug is in eglot--update-hints-1:

(if (stringp label) label (plist-get label :value))

It should instead be the value of :value from the _first element_ of
label, as the spec says a label is a string or a _list of_ `InlayHintLabelPart`s:

https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#inlayHint

The response from the server is of this form:

(:position (:line 95 :character 1)
 :label [(:value "fn keyboard_interrupt_handler"
          :location (:uri "file:///home/chinmay/stuff/rust/blog_os/src/interrupts.rs"
                     :range (:start (:line 67 :character 26)
                             :end (:line 67 :character 52))))]
 :tooltip "fn keyboard_interrupt_handler"
 :paddingLeft t
 :paddingRight
 :json-false)



[-- Attachment #2: empty overlay --]
[-- Type: image/png, Size: 1424 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-24  4:20                     ` Chinmay Dalal
@ 2023-02-24  5:04                       ` Chinmay Dalal
  2023-02-24  9:59                       ` João Távora
  1 sibling, 0 replies; 72+ messages in thread
From: Chinmay Dalal @ 2023-02-24  5:04 UTC (permalink / raw)
  To: João Távora
  Cc: Nikola Pajkovsky, emacs-devel, Dimitri Belopopsky, Po Lu,
	Eli Zaretskii


Chinmay Dalal <dalal.chinmay.0101@gmail.com> writes:

> João Távora <joaotavora@gmail.com> writes:
>
>> On Thu, Feb 23, 2023 at 7:50 PM Nikola Pajkovsky <n.pajkovsky@gmail.com> wrote:
>>>
>>> João Távora <joaotavora@gmail.com> writes:
>>>
>>> > I've just landed Eglot "inlay hints" on lisp/progmodes/eglot.el in the
>>> > emacs-29 branch.  It's a feature that some users (including me) were
>>> > looking for for some time.
>>>
>>> Nice work João!
>>>
>>> I tried rust inlay hints, and it does not work for me. I bet it's because
>>> eglot uses label only if it's string. The same problem was in lsp-mode.
>>
>> Actually, I think I have code for that.  So please show a reproduction
>> recipe.  I think someone reported here that hints are working
>> correctly with rust-analyzer, and I've tried it briefly myself with good
>> results. So it must be something else.
>>
>> João
>
>
> You can look at the end of any long rust function with hl-line-mode on:
> there's an empty overlay but it's supposed to have a link to the
> beginning of the function. Screenshot attached.
>
> I think the bug is in eglot--update-hints-1:
>
> (if (stringp label) label (plist-get label :value))
>
> It should instead be the value of :value from the _first element_ of
> label, as the spec says a label is a string or a _list of_ `InlayHintLabelPart`s:
>
> https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#inlayHint
>
> The response from the server is of this form:
>
> (:position (:line 95 :character 1)
>  :label [(:value "fn keyboard_interrupt_handler"
>           :location (:uri "file:///home/chinmay/stuff/rust/blog_os/src/interrupts.rs"
>                      :range (:start (:line 67 :character 26)
>                              :end (:line 67 :character 52))))]
>  :tooltip "fn keyboard_interrupt_handler"
>  :paddingLeft t
>  :paddingRight
>  :json-false)
>
>
> [2. empty overlay --- image/png; swappy-20230224-100015.png]...


Of course, this is not ideal when there are multiple elements in the
label list (but not strictly wrong either). I raised this point when I
opened bug#61412:
https://mail.gnu.org/archive/html/bug-gnu-emacs/2023-02/msg00760.html

Also, can the clickable link be actually implemented using the `keymap`
property of overlays:
https://www.gnu.org/software/emacs/manual/html_node/elisp/Special-Properties.html#index-keymap-of-character
instead of just displaying the text?

Chinmay



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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
  1 sibling, 1 reply; 72+ messages in thread
From: Eli Zaretskii @ 2023-02-24  7:35 UTC (permalink / raw)
  To: Stefan Monnier
  Cc: joaotavora, dalal.chinmay.0101, emacs-devel, dimitri, luangruo

> From: 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
> Date: Thu, 23 Feb 2023 20:08:41 -0500
> 
> Yes and no: yes, there are already cases where the jit-lock-context
> heuristic of refreshing everything *after* a modification is not
> sufficient

If a jit-lock client is smart enough, it should itself install an
after-change function to remove the 'fontified property from the
portions of buffer text affected by a change that are before the first
changed position (or maybe even in other buffers).

> but as a general rule programming languages are designed for
> "forward-only parsing" so it's unusual for a change at POS to affect the
> parsing before POS

Strictly speaking, this is inaccurate: a change in the middle of a
line could very well affect parsing of that entire line, so it in
effect affects everything from the beginning of that line (perhaps
even more, if that line is a continued line, in the language syntax).
But jit-lock is supposed to already account for that, and call the
fontification-functions with START set to the beginning of the line,
unless I'm misremembering.  (You already know all that, but maybe João
doesn't.)

> > 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.
> 
> I think it won't work reliably in the case where the "immediate"
> jit-lock refresh happens to cover everything until window-end (e.g. we
> just inserted chunk of text that ends after window-end).  In that case
> the jit-lock-context thingy will just mark the rest of the buffer as
> "not fresh" but won't call your fontification function at all.
> [ And of course, there are also those few rare modes that don't use
>   jit-lock-context at all.  ]

jit-lock doesn't use window-end.  It can't, really: the results of
fontification could affect where the window ends, which is why
window-end is only updated when redisplay of a window successfully
finishes, which is way after jit-lock did its job.

That is why jit-lock calls fontification-functions with END that is
arbitrarily computed from START; this is controlled by
jit-lock-chunk-size.  And a function called via
fontification-functions could legitimately decide it fontifies more
than just the chunk for which it was called.

(Again, you already know all that.  I'm writing this to avoid
potential misunderstandings and confusion.)



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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
  1 sibling, 1 reply; 72+ messages in thread
From: João Távora @ 2023-02-24  9:59 UTC (permalink / raw)
  To: Chinmay Dalal
  Cc: Nikola Pajkovsky, emacs-devel, Dimitri Belopopsky, Po Lu,
	Eli Zaretskii

On Fri, Feb 24, 2023 at 4:30 AM Chinmay Dalal
<dalal.chinmay.0101@gmail.com> wrote:

> You can look at the end of any long rust function with hl-line-mode on:
> there's an empty overlay but it's supposed to have a link to the
> beginning of the function. Screenshot attached.
>
> I think the bug is in eglot--update-hints-1:
>
> (if (stringp label) label (plist-get label :value))
>
> It should instead be the value of :value from the _first element_ of
> label, as the spec says a label is a string or a _list of_ `InlayHintLabelPart`s:

Nice catch!  Indeed I missed that.

> Of course, this is not ideal when there are multiple elements in the
> label list (but not strictly wrong either).

Yeah, for now let's stick to the first.  I don't know how to interpret
"multiple hints" for the same "language element", in Emacs at least.
What do other editors do?

> Also, can the clickable link be actually implemented using the `keymap`
> property of overlays:
> https://www.gnu.org/software/emacs/manual/html_node/elisp/Special-Properties.html#index-keymap-of-character
> instead of just displaying the text?

Probably yes.

João



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-24  7:35                                                     ` Eli Zaretskii
@ 2023-02-24 10:42                                                       ` João Távora
  2023-02-24 11:33                                                         ` Eli Zaretskii
  0 siblings, 1 reply; 72+ messages in thread
From: João Távora @ 2023-02-24 10:42 UTC (permalink / raw)
  To: Eli Zaretskii
  Cc: Stefan Monnier, dalal.chinmay.0101, emacs-devel, dimitri,
	luangruo

On Fri, Feb 24, 2023 at 7:35 AM Eli Zaretskii <eliz@gnu.org> wrote:
>
> > From: 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
> > Date: Thu, 23 Feb 2023 20:08:41 -0500
> >
> > Yes and no: yes, there are already cases where the jit-lock-context
> > heuristic of refreshing everything *after* a modification is not
> > sufficient
>
> If a jit-lock client is smart enough, it should itself install an
> after-change function to remove the 'fontified property from the
> portions of buffer text affected by a change that are before the first
> changed position (or maybe even in other buffers).

Thanks, I was going to ask about that. I suppose this is how one
marks sections of a buffer "dirty" or invalidatest them so that
jit-lock knows it must run over them again when it sees fit.

If it works with other buffers as well, it might provide an
elegant solution to the "A affects B" scenario we discussed
earlier.

> > but as a general rule programming languages are designed for
> > "forward-only parsing" so it's unusual for a change at POS to affect the
> > parsing before POS
>
> Strictly speaking, this is inaccurate: a change in the middle of a
> line could very well affect parsing of that entire line, so it in
> effect affects everything from the beginning of that line (perhaps
> even more, if that line is a continued line, in the language syntax).
> But jit-lock is supposed to already account for that, and call the
> fontification-functions with START set to the beginning of the line,
> unless I'm misremembering.  (You already know all that, but maybe João
> doesn't.)

Thanks, I think I can confirm this.  I've observed this behaviour.
Jit lock starts in the beginning of the line to end of the line
in a small chunk containing the small changed region, only then
proceeds to larger chunks (after some jit-lock-contextual-time,
apparently).

> > > 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.
> >
> > I think it won't work reliably in the case where the "immediate"
> > jit-lock refresh happens to cover everything until window-end (e.g. we
> > just inserted chunk of text that ends after window-end).  In that case
> > the jit-lock-context thingy will just mark the rest of the buffer as
> > "not fresh" but won't call your fontification function at all.
> > [ And of course, there are also those few rare modes that don't use
> >   jit-lock-context at all.  ]
>
> jit-lock doesn't use window-end.  It can't, really: the results of
> fontification could affect where the window ends, which is why
> window-end is only updated when redisplay of a window successfully
> finishes, which is way after jit-lock did its job.
>
> That is why jit-lock calls fontification-functions with END that is
> arbitrarily computed from START; this is controlled by
> jit-lock-chunk-size.  And a function called via
> fontification-functions could legitimately decide it fontifies more
> than just the chunk for which it was called.

And Eglot's eglot--update-hints is indeed one such function.  The
coalescing of small jit-lock chunks is very "generous" and it will
request inlay hints for things in the middle that jit-lock didn't
strictly think are necessary.  This works out well in my testing
with the latest version of the feature installed in emacs-29.

> (Again, you already know all that.  I'm writing this to avoid
> potential misunderstandings and confusion.)

Thanks for the clarifications.

João



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-24  9:59                       ` João Távora
@ 2023-02-24 11:03                         ` Nikola Pajkovsky
  0 siblings, 0 replies; 72+ messages in thread
From: Nikola Pajkovsky @ 2023-02-24 11:03 UTC (permalink / raw)
  To: João Távora
  Cc: Chinmay Dalal, Nikola Pajkovsky, emacs-devel, Dimitri Belopopsky,
	Po Lu, Eli Zaretskii

João Távora <joaotavora@gmail.com> writes:

> On Fri, Feb 24, 2023 at 4:30 AM Chinmay Dalal
> <dalal.chinmay.0101@gmail.com> wrote:
>
>> You can look at the end of any long rust function with hl-line-mode on:
>> there's an empty overlay but it's supposed to have a link to the
>> beginning of the function. Screenshot attached.
>>
>> I think the bug is in eglot--update-hints-1:
>>
>> (if (stringp label) label (plist-get label :value))
>>
>> It should instead be the value of :value from the _first element_ of
>> label, as the spec says a label is a string or a _list of_ `InlayHintLabelPart`s:
>
> Nice catch!  Indeed I missed that.
>
>> Of course, this is not ideal when there are multiple elements in the
>> label list (but not strictly wrong either).
>
> Yeah, for now let's stick to the first.  I don't know how to interpret
> "multiple hints" for the same "language element", in Emacs at least.
> What do other editors do?

What I have done for lsp-mode is that I have joined all labels from
`InlayHintsLabelPart`, and printed out the result.

That works like a charm but probably not the best solution.

>> Also, can the clickable link be actually implemented using the `keymap`
>> property of overlays:
>> https://www.gnu.org/software/emacs/manual/html_node/elisp/Special-Properties.html#index-keymap-of-character
>> instead of just displaying the text?
>
> Probably yes.
>
> João



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  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
  0 siblings, 1 reply; 72+ messages in thread
From: Eli Zaretskii @ 2023-02-24 11:33 UTC (permalink / raw)
  To: João Távora
  Cc: monnier, dalal.chinmay.0101, emacs-devel, dimitri, luangruo

> From: João Távora <joaotavora@gmail.com>
> Date: Fri, 24 Feb 2023 10:42:26 +0000
> Cc: Stefan Monnier <monnier@iro.umontreal.ca>, dalal.chinmay.0101@gmail.com, 
> 	emacs-devel@gnu.org, dimitri@belopopsky.com, luangruo@yahoo.com
> 
> On Fri, Feb 24, 2023 at 7:35 AM Eli Zaretskii <eliz@gnu.org> wrote:
> >
> > If a jit-lock client is smart enough, it should itself install an
> > after-change function to remove the 'fontified property from the
> > portions of buffer text affected by a change that are before the first
> > changed position (or maybe even in other buffers).
> 
> Thanks, I was going to ask about that. I suppose this is how one
> marks sections of a buffer "dirty" or invalidatest them so that
> jit-lock knows it must run over them again when it sees fit.
> 
> If it works with other buffers as well, it might provide an
> elegant solution to the "A affects B" scenario we discussed
> earlier.

I don't see why it wouldn't work.  jit-lock relies on the 'fontified'
property to know which part(s) of buffer text need (re-)fontification
when they are about to be shown on display.  So any portion of buffer
text where this property is nil will cause jit-lock to call
fontification-functions on that part.



^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-24 11:33                                                         ` Eli Zaretskii
@ 2023-02-24 12:26                                                           ` João Távora
  0 siblings, 0 replies; 72+ messages in thread
From: João Távora @ 2023-02-24 12:26 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: monnier, dalal.chinmay.0101, emacs-devel, dimitri, luangruo

On Fri, Feb 24, 2023 at 11:33 AM Eli Zaretskii <eliz@gnu.org> wrote:
>
> > From: João Távora <joaotavora@gmail.com>
> > Date: Fri, 24 Feb 2023 10:42:26 +0000
> > Cc: Stefan Monnier <monnier@iro.umontreal.ca>, dalal.chinmay.0101@gmail.com,
> >       emacs-devel@gnu.org, dimitri@belopopsky.com, luangruo@yahoo.com
> >
> > On Fri, Feb 24, 2023 at 7:35 AM Eli Zaretskii <eliz@gnu.org> wrote:
> > >
> > > If a jit-lock client is smart enough, it should itself install an
> > > after-change function to remove the 'fontified property from the
> > > portions of buffer text affected by a change that are before the first
> > > changed position (or maybe even in other buffers).
> >
> > Thanks, I was going to ask about that. I suppose this is how one
> > marks sections of a buffer "dirty" or invalidatest them so that
> > jit-lock knows it must run over them again when it sees fit.
> >
> > If it works with other buffers as well, it might provide an
> > elegant solution to the "A affects B" scenario we discussed
> > earlier.
>
> I don't see why it wouldn't work.

Yes.  I never said it wouldn't :-) (and you never said I said,
I'm just clarifying :-).

Anyway, there is also this to this topic.

https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_inlayHint_refresh

an "inlay hint refresh request" from the server.  A separate
capability apparently designed to cover this case.  I don't
know how it fits into the equation but I would think it was
designed for editors with none of our jit-capabilities.  Maybe
it's mostly useless to us now, especially given its poor
interface (it's just a bare request with no parameters).

It does illustrate the typical LSP line of reasoning: "the server
knows best, because the server knows the language".  IOWs the LSP
server knowledge stands for Emacs  major-mode knowledge.  The
difference here is that the interfaces to extract that knowledge
from the server are very limited or poorly designed.

João



^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Inlay hints implementation
  2023-02-21 21:26               ` João Távora
@ 2023-02-25  0:21                 ` João Távora
  2023-02-25  7:59                   ` Eli Zaretskii
  0 siblings, 1 reply; 72+ messages in thread
From: João Távora @ 2023-02-25  0:21 UTC (permalink / raw)
  To: Dimitri Belopopsky, 61412-done; +Cc: Po Lu, Chinmay Dalal, Eli Zaretskii

Inlay hints are not implemented in Eglot as of a few days ago.

I'm going to go ahead and close this bug. Please open new bugs for any
missing features
or problems with the new functionality.

Thanks,
João





^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Inlay hints implementation
  2023-02-25  0:21                 ` João Távora
@ 2023-02-25  7:59                   ` Eli Zaretskii
  2023-02-25 10:19                     ` João Távora
  0 siblings, 1 reply; 72+ messages in thread
From: Eli Zaretskii @ 2023-02-25  7:59 UTC (permalink / raw)
  To: João Távora; +Cc: luangruo, 61412-done, dalal.chinmay.0101, dimitri

> From: João Távora <joaotavora@gmail.com>
> Date: Sat, 25 Feb 2023 00:21:40 +0000
> Cc: Chinmay Dalal <dalal.chinmay.0101@gmail.com>, Po Lu <luangruo@yahoo.com>, 
> 	Eli Zaretskii <eliz@gnu.org>
> 
> Inlay hints are not implemented in Eglot as of a few days ago.
                  ^^^
You meant "now" there, right?





^ permalink raw reply	[flat|nested] 72+ messages in thread

* bug#61412: Inlay hints implementation
  2023-02-25  7:59                   ` Eli Zaretskii
@ 2023-02-25 10:19                     ` João Távora
  0 siblings, 0 replies; 72+ messages in thread
From: João Távora @ 2023-02-25 10:19 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Po Lu, 61412-done, Chinmay Dalal, Dimitri Belopopsky

[-- Attachment #1: Type: text/plain, Size: 459 bytes --]

On Sat, Feb 25, 2023, 07:59 Eli Zaretskii <eliz@gnu.org> wrote:

> > From: João Távora <joaotavora@gmail.com>
> > Date: Sat, 25 Feb 2023 00:21:40 +0000
> > Cc: Chinmay Dalal <dalal.chinmay.0101@gmail.com>, Po Lu <
> luangruo@yahoo.com>,
> >       Eli Zaretskii <eliz@gnu.org>
> >
> > Inlay hints are not implemented in Eglot as of a few days ago.
>                   ^^^
> You meant "now" there, right?
>

Yes, of course, sorry.

João

>

[-- Attachment #2: Type: text/html, Size: 1301 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: Eglot "inlay hints" landed
  2023-02-22 19:42               ` Eglot "inlay hints" landed João Távora
                                   ` (4 preceding siblings ...)
  2023-02-23 19:50                 ` Nikola Pajkovsky
@ 2023-02-27 22:50                 ` Johann Klähn
  5 siblings, 0 replies; 72+ messages in thread
From: Johann Klähn @ 2023-02-27 22:50 UTC (permalink / raw)
  To: emacs-devel


Works great, thanks! AFAICT ‘(and paddingLeft …)’ in
eglot--update-hints-1 should check for :json-false, though.




^ permalink raw reply	[flat|nested] 72+ messages in thread

end of thread, other threads:[~2023-02-27 22:50 UTC | newest]

Thread overview: 72+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-02-11  8:13 bug#61412: [PATCH] Add inlay hints to eglot Chinmay Dalal
2023-02-11 11:50 ` Chinmay Dalal
2023-02-11 15:23 ` bug#61412: Question about implementation Chinmay Dalal
2023-02-13 15:35 ` bug#61412: Hover github discussion Chinmay Dalal
2023-02-15 13:58   ` Eli Zaretskii
2023-02-15 15:05     ` João Távora
2023-02-15 15:38       ` Eli Zaretskii
2023-02-15 12:56 ` bug#61412: [PATCH v2] Add inlay hints to eglot Chinmay Dalal
2023-02-15 13:08 ` bug#61412: [PATCH v3] " Chinmay Dalal
2023-02-15 16:24 ` bug#61412: Inlay activation Chinmay Dalal
2023-02-15 18:09   ` Eli Zaretskii
2023-02-15 18:48     ` Chinmay Dalal
2023-02-15 19:01       ` João Távora
2023-02-15 19:17         ` Chinmay Dalal
2023-02-15 19:41       ` Eli Zaretskii
2023-02-15 20:17         ` Chinmay Dalal
2023-02-21 15:13           ` bug#61412: Inlay hints implementation João Távora
2023-02-21 15:21             ` Eli Zaretskii
2023-02-21 18:42             ` Dimitri Belopopsky
2023-02-21 21:26               ` João Távora
2023-02-25  0:21                 ` João Távora
2023-02-25  7:59                   ` Eli Zaretskii
2023-02-25 10:19                     ` João Távora
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
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
2023-02-21 15:33 ` bug#61412: Inlay hints implementation Chinmay Dalal
2023-02-21 15:57 ` Chinmay Dalal
2023-02-22 15:26 ` Chinmay Dalal
2023-02-22 16:51   ` Chinmay Dalal
2023-02-22 23:17   ` João Távora

Code repositories for project(s) associated with this external index

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

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.