Eglot: introduce eglot-advertise-cancellation

Setting this variable to true causes Eglot to send special
cancellation notification for certain stale client request.

This may help some LSP servers avoid doing costly but ultimately useless
work on behalf of the client, improving overall performance.

Request cancellation is described in

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

* lisp/jsonrpc.el (jsonrpc-request): Accept function as value for
CANCEL-ON-INPUT.

* lisp/progmodes/eglot.el (eglot--request): Rework.

* doc/misc/eglot.texi (Customizing Eglot): Mention
eglot-advertise-cancellation.
This commit is contained in:
João Távora 2025-01-14 15:58:57 +00:00
parent 9e4b626f7e
commit 7f0ef9655c
4 changed files with 35 additions and 5 deletions

View file

@ -919,6 +919,13 @@ Set this variable to non-nil if you'd like progress notifications coming
from the language server to be handled by Emacs's progress reporting
facilities. If the value is the symbol @code{messages} the message
buffer is used, else the progress is reported in the mode line.
@cindex request cancellation
@item eglot-advertise-cancellation
Setting this variable to true causes Eglot to send special cancellation
notification for certain stale client request. This may help some LSP
servers avoid doing costly but ultimately useless work on behalf of the
client, improving overall performance.
@end vtable
@node Other Variables

View file

@ -20,6 +20,12 @@ https://github.com/joaotavora/eglot/issues/1234.
* Changes in upcoming Eglot
** New 'eglot-advertise-cancellation' variable
Tweaking this variable may help some LSP servers avoid doing costly but
ultimately useless work on behalf of the client, improving overall
performance.
* Changes in Eglot 1.18 (20/1/2025)

View file

@ -399,10 +399,12 @@ error of type `jsonrpc-error'.
DEFERRED and TIMEOUT as in `jsonrpc-async-request', which see.
If CANCEL-ON-INPUT is non-nil and the user inputs something while
the function is waiting, then it exits immediately, returning
CANCEL-ON-INPUT-RETVAL. Any future replies (normal or error) are
ignored."
If CANCEL-ON-INPUT is non-nil and the user inputs something while the
function is waiting, then any future replies to the request by the
remote endpoint (normal or error) are ignored and the function exits
returning CANCEL-ON-INPUT-RETVAL. If CANCEL-ON-INPUT is a function, it
is invoked with one argument, an integer identifying the cancelled
request as specified in the JSONRPC 2.0 spec."
(let* ((tag (cl-gensym "jsonrpc-request-catch-tag")) id-and-timer
canceled
(throw-on-input nil)
@ -435,6 +437,8 @@ ignored."
(unwind-protect
(let ((inhibit-quit t)) (while (sit-for 30)))
(setq canceled t))
(when (functionp cancel-on-input)
(funcall cancel-on-input (car id-and-timer)))
`(canceled ,cancel-on-input-retval))
(t (while t (accept-process-output nil 30)))))
;; In normal operation, continuations for error/success is

View file

@ -571,6 +571,14 @@ under cursor."
(const :tag "Execute custom commands" :executeCommandProvider)
(const :tag "Inlay hints" :inlayHintProvider)))
(defcustom eglot-advertise-cancellation nil
"If non-nil, Eglot attemps to inform server of cancelled requests.
This is done by sending an additional '$/cancelRequest' notification
every time Eglot decides to forget a request. The effect of this
notification is implementation defined, and is only useful for some
servers."
:type 'boolean)
(defvar eglot-withhold-process-id nil
"If non-nil, Eglot will not send the Emacs process id to the language server.
This can be useful when using docker to run a language server.")
@ -1748,7 +1756,12 @@ Unless IMMEDIATE, send pending changes before making request."
(unless immediate (eglot--signal-textDocument/didChange))
(jsonrpc-request server method params
:timeout timeout
:cancel-on-input cancel-on-input
:cancel-on-input
(cond ((and cancel-on-input
eglot-advertise-cancellation)
(lambda (id)
(jsonrpc-notify server '$/cancelRequest `(:id ,id))))
(cancel-on-input))
:cancel-on-input-retval cancel-on-input-retval))