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)))))
 
+
+;;; 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)))
+
 
 ;;; Hacks
 ;;;
-- 
2.36.1.windows.1

