Next: Troubleshooting Eglot, Previous: Advanced server configuration, Up: Eglot [Contents][Index]
Sometimes it may be useful to extend existing Eglot functionality using Elisp its public methods. A good example of when this need may arise is adding support for a custom LSP protocol extension only implemented by a specific server.
The best source of documentation for this is probably Eglot source code itself, particularly the section marked “API”.
Most of the functionality is implemented with Common-Lisp style generic functions (see Generics in EIEIO) that can be easily extended or overridden. The Eglot code itself is an example on how to do this.
The following is a relatively simple example that adds support for the
inactiveRegions
experimental feature introduced in version 17
of the clangd
C/C++ language server++.
Summarily, the feature works by first having the server detect the
Eglot’s advertisement of the inactiveRegions
client capability
during startup, whereupon the language server will report a list of
regions of inactive code for each buffer. This is usually code
surrounded by C/C++ #ifdef
macros that the preprocessor removes
based on compile-time information.
The language server reports the regions by periodically sending a
textDocument/inactiveRegions
notification for each managed
buffer (see Buffers, Projects, and Eglot). Normally, unknown server
notifications are ignored by Eglot, but we’re going change that.
Both the announcement of the client capability and the handling of the new notification is done by adding methods to generic functions.
eglot-client-capabilities
using a
simple heuristic to detect if current server is clangd
and
enables the inactiveRegion
capability.
(cl-defmethod eglot-client-capabilities :around (server) (let ((base (cl-call-next-method))) (when (cl-find "clangd" (process-command (jsonrpc--process server)) :test #'string-match) (setf (cl-getf (cl-getf base :textDocument) :inactiveRegionsCapabilities) '(:inactiveRegions t))) base))
Notice we use an internal function of the jsonrpc.el
library,
and a regexp search to detect clangd
. An alternative would
be to define a new EIEIO subclass of eglot-lsp-server
, maybe
called eglot-clangd
, so that the method would be simplified:
(cl-defmethod eglot-client-capabilities :around ((_s eglot-clangd)) (let ((base (cl-call-next-method))) (setf (cl-getf (cl-getf base :textDocument) :inactiveRegionsCapabilities) '(:inactiveRegions t))))
However, this would require that users tweak
eglot-server-program
to tell Eglot instantiate such sub-classes
instead of the generic eglot-lsp-server
(see Setting Up LSP Servers). For the purposes of this particular demonstration, we’re
going to use the more hacky regexp route which doesn’t require that.
Note, however, that detecting server versions before announcing new capabilities is generally not needed, as both server and client are required by LSP to ignore unknown capabilities advertised by their counterparts.
eglot-handle-notification
to
process the server notification for the LSP method
textDocument/inactiveRegions
. For each region received it
creates an overlay applying the shadow
face to the region.
Overlays are recreated every time a new notification of this kind is
received.
To learn about how clangd
’s special JSONRPC notification
message is structured in detail you could consult that server’s
documentation. Another possibility is to evaluate the first
capability-announcing method, reconnect to the server and peek in the
events buffer (see eglot-events-buffer). You
could find something like:
[server-notification] Mon Sep 4 01:10:04 2023: (:jsonrpc "2.0" :method "textDocument/inactiveRegions" :params (:textDocument (:uri "file:///path/to/file.cpp") :regions [(:start (:character 0 :line 18) :end (:character 58 :line 19)) (:start (:character 0 :line 36) :end (:character 1 :line 38))]))
This reveals that the textDocument/inactiveRegions
notification
contains a :textDocument
property to designate the managed
buffer and an array of LSP regions under the :regions
property.
Notice how the message (originally in JSON format), is represented as
Elisp plists (see JSONRPC objects in Elisp).
The Eglot generic function machinery will automatically destructure
the incoming message, so these two properties can simply be added to
the new method’s lambda list as &key
arguments. Also, the
eglot-uri-to-path
and eglot-range-region
may be used to
easily parse the LSP :uri
and :start ... :end ...
objects to obtain Emacs objects for file names and positions.
The remainder of the implementation consists of standard Elisp techniques to loop over arrays, manage buffers and overlays.
(cl-defmethod eglot-handle-notification (_server (_method (eql textDocument/inactiveRegions)) &key regions textDocument &allow-other-keys) (if-let* ((path (expand-file-name (eglot-uri-to-path (cl-getf textDocument :uri)))) (buffer (find-buffer-visiting path))) (with-current-buffer buffer (remove-overlays nil nil 'inactive-code t) (cl-loop for r across regions for (beg . end) = (eglot-range-region r) for ov = (make-overlay beg end) do (overlay-put ov 'face 'shadow) (overlay-put ov 'inactive-code t)))))
After evaluating these two additions and reconnecting to the
clangd
language server (version 17), the result will be that
all the inactive code in the buffer will be nicely grayed out using
the LSP server knowledge about current compile time preprocessor
defines.
Next: Troubleshooting Eglot, Previous: Advanced server configuration, Up: Eglot [Contents][Index]