diff --git a/doc/misc/eglot.texi b/doc/misc/eglot.texi index df24cf2a1dc..47649455cec 100644 --- a/doc/misc/eglot.texi +++ b/doc/misc/eglot.texi @@ -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 diff --git a/etc/EGLOT-NEWS b/etc/EGLOT-NEWS index f4621994b67..9deac73d9fc 100644 --- a/etc/EGLOT-NEWS +++ b/etc/EGLOT-NEWS @@ -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) diff --git a/lisp/jsonrpc.el b/lisp/jsonrpc.el index 1d3a983d41e..2431d00dad3 100644 --- a/lisp/jsonrpc.el +++ b/lisp/jsonrpc.el @@ -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 diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 92eb1f2715c..f2fcbc0a4c2 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el @@ -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))