unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
From: Dimitri Belopopsky <dimitri@belopopsky.com>
To: 61066@debbugs.gnu.org
Subject: bug#61066: [PATCH] Add inlay hint support to eglot
Date: Wed, 25 Jan 2023 23:34:02 +0100	[thread overview]
Message-ID: <CA+46MXakiruOtNK+x4teHeKgh0JgrrJXALQGC0VW=1TA=AtCsA@mail.gmail.com> (raw)


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

Hello,

I've been working on adding support for inlay hints inside eglot using
overlays.
Here is a working patch, but I'm still missing a few things:

- I can't figure out a way to show the hints on a document without causing
lags or timeouts from the lsp server
- I'm currently updating the hints by sending the whole file each time (to
make sure all hints get updated correctly). I'm not sure on how to make
this more efficient (or if it even necessary).

On the implementation side:
- implemented with overlays as a minor model, enabled by default
- shows all hints supported by the protocol
- there is a customisation to disable the minor mode if the user doesn't
want the feature

I'd love to get a few points to finish this patch, and of course any ideas
and feedbacks are welcome!

Kind regards,

Dimitri Belopopsky

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

[-- Attachment #2: 0001-Add-inlay-hints-to-eglot.patch --]
[-- Type: text/x-patch, Size: 5008 bytes --]

From 848fa16eb6257ce454c1810f81be8ed8ed140164 Mon Sep 17 00:00:00 2001
From: Dimitri Belopopsky <dimitri@belopopsky.com>
Date: Wed, 25 Jan 2023 23:24:13 +0100
Subject: [PATCH] Add inlay hints to eglot

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

diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 8ce1a8b7baf..2d947c377a4 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -404,6 +404,11 @@ eglot-withhold-process-id
 (when (assoc 'flex completion-styles-alist)
   (add-to-list 'completion-category-defaults '(eglot (styles flex basic))))
 
+(defcustom eglot-inlay-hints t
+  "If non-nil, activate inlay hints inside the buffer."
+  :type 'boolean)
+
+
 \f
 ;;; Constants
 ;;;
@@ -1624,7 +1629,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 Hint" :inlayHintProvider)))
 
 (defun eglot--server-capable (&rest feats)
   "Determine if current server is capable of FEATS."
@@ -1845,6 +1851,9 @@ eglot--maybe-activate-editing-mode
     (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)
@@ -3453,6 +3462,83 @@ eglot-list-connections
       (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 hint."
+  :lighter " inlay"
+  (cond
+    (eglot--managed-mode
+      (add-hook 'after-save-hook 'eglot--update-hints nil t))
+    (t
+      (remove-hook 'after-save-hook 'eglot--update-hints t))
+    )
+  ;; Note: the public hook runs before the internal eglot--managed-mode-hook.
+  (run-hooks 'eglot-managed-mode-hook)
+  )
+
+(defun eglot--update-hints()
+  "Refresh inlay hints from LSP server."
+  (unless (eglot--server-capable :inlayHintProvider)
+    (error "Server does not support inlay hint."))
+  (mapc #'delete-overlay (overlays-in (point-min) (point-max)))
+  (let ((read-only-p buffer-read-only)
+	       overlays)
+	    (let ((lens-table (make-hash-table)))
+	      ;; Get the inlay hint objects.
+	      (mapc (lambda (inlayHint)
+		            (when (eglot--server-capable
+			                       :inlayHintProvider :resolveProvider)
+		              (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 t))
+
+	      ;; Make overlays for them.
+	      (maphash
+	        (lambda (line values)
+            (cl-loop for value in values
+              do
+              (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 ((c (plist-get (plist-get value :position) :character))
+		                     (text (propertize
+                                 (concat
+                                   (if (plist-get value :paddingLeft) " " "")
+                                   (if (stringp val) val (plist-get val :value))
+                                   (if (plist-get value :paddingRight) " " ""))
+                           '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)
+        )
+    ))
+
+
 \f
 ;;; Hacks
 ;;;
-- 
2.34.1


             reply	other threads:[~2023-01-25 22:34 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-01-25 22:34 Dimitri Belopopsky [this message]
2023-01-26  6:29 ` bug#61066: [PATCH] Add inlay hint support to eglot Eli Zaretskii
2023-01-26 12:30   ` João Távora
2023-01-27 18:44     ` Dimitri Belopopsky
2023-01-28  1:58   ` Dmitry Gutov

Reply instructions:

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

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

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

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

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

  git send-email \
    --in-reply-to='CA+46MXakiruOtNK+x4teHeKgh0JgrrJXALQGC0VW=1TA=AtCsA@mail.gmail.com' \
    --to=dimitri@belopopsky.com \
    --cc=61066@debbugs.gnu.org \
    /path/to/YOUR_REPLY

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

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

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

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