diff options
| author | João Távora | 2025-02-16 18:27:48 +0000 |
|---|---|---|
| committer | João Távora | 2025-02-24 23:38:15 +0000 |
| commit | e4c911adeaa679a92fab58b196b27c502aaed2f3 (patch) | |
| tree | 34714f0b29c309f3efb8d3b699c0ff5da25d7f19 | |
| parent | 0ac0f355e50639fb2cdacb6cbfca696d5592a89e (diff) | |
| download | emacs-e4c911adeaa679a92fab58b196b27c502aaed2f3.tar.gz emacs-e4c911adeaa679a92fab58b196b27c502aaed2f3.zip | |
Eglot: use eglot-advertise-cancellation in more situations
The async requests frequently issued by ElDoc are a significant
source of request pile-up on the server side (for some servers).
With this change, Eglot will issue additional LSP
$/cancelRequest notifications for in-flight requests of certain
kinds in the pre-command hook.
This required a small change to the 'jsonrpc-async-request'
entrypoint.
This feature is experimental.
* lisp/jsonrpc.el (jsonrpc-async-request): No longer returns nil.
* lisp/progmodes/eglot.el (eglot--inflight-async-requests): New variable.
(eglot--cancel-inflight-async-requests): New function.
(eglot--async-request): New function.
(eglot--pre-command-hook): Call eglot--cancel-inflight-async-requests.
(eglot-signature-eldoc-function, eglot-hover-eldoc-function)
(eglot-highlight-eldoc-function, eglot-code-action-suggestion):
Use eglot--async-request.
| -rw-r--r-- | lisp/jsonrpc.el | 6 | ||||
| -rw-r--r-- | lisp/progmodes/eglot.el | 75 |
2 files changed, 69 insertions, 12 deletions
diff --git a/lisp/jsonrpc.el b/lisp/jsonrpc.el index be9f4917e80..c5a099af8ec 100644 --- a/lisp/jsonrpc.el +++ b/lisp/jsonrpc.el | |||
| @@ -377,9 +377,9 @@ never be sent at all, in case it is overridden in the meantime by | |||
| 377 | a new request with identical DEFERRED and for the same buffer. | 377 | a new request with identical DEFERRED and for the same buffer. |
| 378 | However, in that situation, the original timeout is kept. | 378 | However, in that situation, the original timeout is kept. |
| 379 | 379 | ||
| 380 | Returns nil." | 380 | Returns a list whose first element is an integer identifying the request |
| 381 | (apply #'jsonrpc--async-request-1 connection method params args) | 381 | as specified in the JSONRPC 2.0 spec." |
| 382 | nil) | 382 | (apply #'jsonrpc--async-request-1 connection method params args)) |
| 383 | 383 | ||
| 384 | (cl-defun jsonrpc-request (connection | 384 | (cl-defun jsonrpc-request (connection |
| 385 | method params &key | 385 | method params &key |
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index df93f899069..f0891e700a8 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el | |||
| @@ -1832,6 +1832,62 @@ Unless IMMEDIATE, send pending changes before making request." | |||
| 1832 | (cancel-on-input)) | 1832 | (cancel-on-input)) |
| 1833 | :cancel-on-input-retval cancel-on-input-retval)) | 1833 | :cancel-on-input-retval cancel-on-input-retval)) |
| 1834 | 1834 | ||
| 1835 | (defvar-local eglot--inflight-async-requests nil | ||
| 1836 | "An plist of symbols to lists of JSONRPC ids. | ||
| 1837 | The ids designate in-flight asynchronous requests that may be cancelled | ||
| 1838 | according to `eglot-advertise-cancellation'.") | ||
| 1839 | |||
| 1840 | (cl-defun eglot--cancel-inflight-async-requests | ||
| 1841 | (&optional (hints '(:textDocument/signatureHelp | ||
| 1842 | :textDocument/hover | ||
| 1843 | :textDocument/documentHighlight | ||
| 1844 | :textDocument/codeAction))) | ||
| 1845 | (when-let* ((server (and hints | ||
| 1846 | eglot-advertise-cancellation | ||
| 1847 | (eglot-current-server)))) | ||
| 1848 | (dolist (hint hints) | ||
| 1849 | (dolist (id (plist-get eglot--inflight-async-requests hint)) | ||
| 1850 | ;; FIXME: in theory, as `jsonrpc-async-request' explains, this | ||
| 1851 | ;; request may never have been sent at all. But that's rare, and | ||
| 1852 | ;; it's only a problem if the server borks on cancellation of | ||
| 1853 | ;; never-sent requests. | ||
| 1854 | (jsonrpc-notify server '$/cancelRequest `(:id ,id))) | ||
| 1855 | (cl-remf eglot--inflight-async-requests hint)))) | ||
| 1856 | |||
| 1857 | (cl-defun eglot--async-request (server | ||
| 1858 | method | ||
| 1859 | params | ||
| 1860 | &key | ||
| 1861 | (success-fn nil success-fn-supplied-p) | ||
| 1862 | (error-fn nil error-fn-supplied-p) | ||
| 1863 | (timeout-fn nil timeout-fn-supplied-p) | ||
| 1864 | (timeout nil timeout-supplied-p) | ||
| 1865 | hint | ||
| 1866 | &aux moreargs) | ||
| 1867 | "Like `jsonrpc-async-request', but for Eglot LSP requests. | ||
| 1868 | HINT argument is a symbol passed as DEFERRED to `jsonrpc-async-request' | ||
| 1869 | and also used as a hint of the request cancellation mechanism (see | ||
| 1870 | `eglot-advertise-cancellation')." | ||
| 1871 | (cl-labels ((clearing-fn (fn) | ||
| 1872 | (lambda (&rest args) | ||
| 1873 | (when fn (apply fn args)) | ||
| 1874 | (cl-remf eglot--inflight-async-requests hint)))) | ||
| 1875 | (eglot--cancel-inflight-async-requests (list hint)) | ||
| 1876 | (when timeout-supplied-p | ||
| 1877 | (setq moreargs (nconc `(:timeout ,timeout) moreargs))) | ||
| 1878 | (when hint | ||
| 1879 | (setq moreargs (nconc `(:deferred ,hint) moreargs))) | ||
| 1880 | (let ((id | ||
| 1881 | (car (apply #'jsonrpc-async-request | ||
| 1882 | server method params | ||
| 1883 | :success-fn (clearing-fn success-fn) | ||
| 1884 | :error-fn (clearing-fn error-fn) | ||
| 1885 | :timeout-fn (clearing-fn timeout-fn) | ||
| 1886 | moreargs)))) | ||
| 1887 | (when (and hint eglot-advertise-cancellation) | ||
| 1888 | (push id | ||
| 1889 | (plist-get eglot--inflight-async-requests hint)))))) | ||
| 1890 | |||
| 1835 | 1891 | ||
| 1836 | ;;; Encoding fever | 1892 | ;;; Encoding fever |
| 1837 | ;;; | 1893 | ;;; |
| @@ -2799,8 +2855,9 @@ buffer." | |||
| 2799 | "Cache of `workspace/Symbol' results used by `xref-find-definitions'.") | 2855 | "Cache of `workspace/Symbol' results used by `xref-find-definitions'.") |
| 2800 | 2856 | ||
| 2801 | (defun eglot--pre-command-hook () | 2857 | (defun eglot--pre-command-hook () |
| 2802 | "Reset some temporary variables." | 2858 | "Reset some state." |
| 2803 | (clrhash eglot--workspace-symbols-cache) | 2859 | (clrhash eglot--workspace-symbols-cache) |
| 2860 | (eglot--cancel-inflight-async-requests) | ||
| 2804 | (setq eglot--last-inserted-char nil)) | 2861 | (setq eglot--last-inserted-char nil)) |
| 2805 | 2862 | ||
| 2806 | (defun eglot--CompletionParams () | 2863 | (defun eglot--CompletionParams () |
| @@ -3644,7 +3701,7 @@ for which LSP on-type-formatting should be requested." | |||
| 3644 | "A member of `eldoc-documentation-functions', for signatures." | 3701 | "A member of `eldoc-documentation-functions', for signatures." |
| 3645 | (when (eglot-server-capable :signatureHelpProvider) | 3702 | (when (eglot-server-capable :signatureHelpProvider) |
| 3646 | (let ((buf (current-buffer))) | 3703 | (let ((buf (current-buffer))) |
| 3647 | (jsonrpc-async-request | 3704 | (eglot--async-request |
| 3648 | (eglot--current-server-or-lose) | 3705 | (eglot--current-server-or-lose) |
| 3649 | :textDocument/signatureHelp (eglot--TextDocumentPositionParams) | 3706 | :textDocument/signatureHelp (eglot--TextDocumentPositionParams) |
| 3650 | :success-fn | 3707 | :success-fn |
| @@ -3661,14 +3718,14 @@ for which LSP on-type-formatting should be requested." | |||
| 3661 | nil)) | 3718 | nil)) |
| 3662 | signatures "\n") | 3719 | signatures "\n") |
| 3663 | :echo (eglot--sig-info active-sig activeParameter t)))))) | 3720 | :echo (eglot--sig-info active-sig activeParameter t)))))) |
| 3664 | :deferred :textDocument/signatureHelp)) | 3721 | :hint :textDocument/signatureHelp)) |
| 3665 | t)) | 3722 | t)) |
| 3666 | 3723 | ||
| 3667 | (defun eglot-hover-eldoc-function (cb &rest _ignored) | 3724 | (defun eglot-hover-eldoc-function (cb &rest _ignored) |
| 3668 | "A member of `eldoc-documentation-functions', for hover." | 3725 | "A member of `eldoc-documentation-functions', for hover." |
| 3669 | (when (eglot-server-capable :hoverProvider) | 3726 | (when (eglot-server-capable :hoverProvider) |
| 3670 | (let ((buf (current-buffer))) | 3727 | (let ((buf (current-buffer))) |
| 3671 | (jsonrpc-async-request | 3728 | (eglot--async-request |
| 3672 | (eglot--current-server-or-lose) | 3729 | (eglot--current-server-or-lose) |
| 3673 | :textDocument/hover (eglot--TextDocumentPositionParams) | 3730 | :textDocument/hover (eglot--TextDocumentPositionParams) |
| 3674 | :success-fn (eglot--lambda ((Hover) contents range) | 3731 | :success-fn (eglot--lambda ((Hover) contents range) |
| @@ -3677,7 +3734,7 @@ for which LSP on-type-formatting should be requested." | |||
| 3677 | (eglot--hover-info contents range)))) | 3734 | (eglot--hover-info contents range)))) |
| 3678 | (funcall cb info | 3735 | (funcall cb info |
| 3679 | :echo (and info (string-match "\n" info)))))) | 3736 | :echo (and info (string-match "\n" info)))))) |
| 3680 | :deferred :textDocument/hover)) | 3737 | :hint :textDocument/hover)) |
| 3681 | t)) | 3738 | t)) |
| 3682 | 3739 | ||
| 3683 | (defun eglot-highlight-eldoc-function (_cb &rest _ignored) | 3740 | (defun eglot-highlight-eldoc-function (_cb &rest _ignored) |
| @@ -3687,7 +3744,7 @@ for which LSP on-type-formatting should be requested." | |||
| 3687 | ;; ignore cb and return nil to say "no doc". | 3744 | ;; ignore cb and return nil to say "no doc". |
| 3688 | (when (eglot-server-capable :documentHighlightProvider) | 3745 | (when (eglot-server-capable :documentHighlightProvider) |
| 3689 | (let ((buf (current-buffer))) | 3746 | (let ((buf (current-buffer))) |
| 3690 | (jsonrpc-async-request | 3747 | (eglot--async-request |
| 3691 | (eglot--current-server-or-lose) | 3748 | (eglot--current-server-or-lose) |
| 3692 | :textDocument/documentHighlight (eglot--TextDocumentPositionParams) | 3749 | :textDocument/documentHighlight (eglot--TextDocumentPositionParams) |
| 3693 | :success-fn | 3750 | :success-fn |
| @@ -3705,7 +3762,7 @@ for which LSP on-type-formatting should be requested." | |||
| 3705 | `(,(lambda (o &rest _) (delete-overlay o)))) | 3762 | `(,(lambda (o &rest _) (delete-overlay o)))) |
| 3706 | ov))) | 3763 | ov))) |
| 3707 | highlights)))) | 3764 | highlights)))) |
| 3708 | :deferred :textDocument/documentHighlight) | 3765 | :hint :textDocument/documentHighlight) |
| 3709 | nil))) | 3766 | nil))) |
| 3710 | 3767 | ||
| 3711 | (defun eglot--imenu-SymbolInformation (res) | 3768 | (defun eglot--imenu-SymbolInformation (res) |
| @@ -4031,7 +4088,7 @@ at point. With prefix argument, prompt for ACTION-KIND." | |||
| 4031 | (bounds (eglot--code-action-bounds)) | 4088 | (bounds (eglot--code-action-bounds)) |
| 4032 | (use-text-p (memq 'eldoc-hint eglot-code-action-indications)) | 4089 | (use-text-p (memq 'eldoc-hint eglot-code-action-indications)) |
| 4033 | tooltip blurb) | 4090 | tooltip blurb) |
| 4034 | (jsonrpc-async-request | 4091 | (eglot--async-request |
| 4035 | (eglot--current-server-or-lose) | 4092 | (eglot--current-server-or-lose) |
| 4036 | :textDocument/codeAction | 4093 | :textDocument/codeAction |
| 4037 | (eglot--code-action-params :beg (car bounds) :end (cadr bounds) | 4094 | (eglot--code-action-params :beg (car bounds) :end (cadr bounds) |
| @@ -4071,7 +4128,7 @@ at point. With prefix argument, prompt for ACTION-KIND." | |||
| 4071 | ,tooltip))))) | 4128 | ,tooltip))))) |
| 4072 | (setq eglot--suggestion-overlay ov))))) | 4129 | (setq eglot--suggestion-overlay ov))))) |
| 4073 | (when use-text-p (funcall cb blurb))) | 4130 | (when use-text-p (funcall cb blurb))) |
| 4074 | :deferred :textDocument/codeAction) | 4131 | :hint :textDocument/codeAction) |
| 4075 | (and use-text-p t)))) | 4132 | (and use-text-p t)))) |
| 4076 | 4133 | ||
| 4077 | 4134 | ||