[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
master ad1efe5e675 05/10: Eglot: improve caching in eglot-completion-at-
From: |
João Távora |
Subject: |
master ad1efe5e675 05/10: Eglot: improve caching in eglot-completion-at-point |
Date: |
Sun, 2 Apr 2023 19:15:39 -0400 (EDT) |
branch: master
commit ad1efe5e675216e1f1f342fc9d48018fac718b5e
Author: João Távora <joaotavora@gmail.com>
Commit: João Távora <joaotavora@gmail.com>
Eglot: improve caching in eglot-completion-at-point
When answering the :textDocument/completion request, LSP servers
provide a :isIncomplete flag in the response, which allows Eglot to
know if "further typing should result in recomputing [the completions]
list.
If :isIncomplete is false (i.e. the full set was returned), Eglot
caches the response in a global variable eglot--capf-cache that
persists for the duration of the "completion session", taken to be the
interval between two calls to completion-in-region-mode.
If the cache has been set, and Eglot detects that "further typing" has
happened, it is safe to use the cache instead of making a request to
the server.
Thus eglot--capf-cache-flush, added to completion-in-region-mode-hook,
is used to flush this cache. Since the popular Company completion
package doesn't use completion-in-region-mode, eglot--capf-cache-flush
is also added to its company-after-completion-hook.
* lisp/progmodes/eglot.el (eglot--managed-mode): Set
'completion-in-region-mode-hook and company-after-completion-hook.
(eglot--capf-cache): New variable.
(eglot--capf-cache-flush): New function.
(eglot-completion-at-point): Rework.
* etc/EGLOT-NEWS: Update.
---
etc/EGLOT-NEWS | 22 +++++++++
lisp/progmodes/eglot.el | 121 +++++++++++++++++++++++++++++-------------------
2 files changed, 95 insertions(+), 48 deletions(-)
diff --git a/etc/EGLOT-NEWS b/etc/EGLOT-NEWS
index dd04e677285..09772a1e71a 100644
--- a/etc/EGLOT-NEWS
+++ b/etc/EGLOT-NEWS
@@ -18,6 +18,28 @@ to look up issue github#1234, go to
https://github.com/joaotavora/eglot/issues/1234.
+* Changes in upcoming Eglot 1.14
+
+** Faster, more responsive completion
+
+Eglot takes advantage of LSP's "isIncomplete" flag in responses to
+completion requests to drive new completion-caching mechanism for the
+duration of each completion session. Once a full set of completions
+is obtained for a given position, the server needn't be contacted in
+many scenarios, resulting in significantly less communication
+overhead. This works with the popular Company package and stock
+completion-at-point interfaces.
+
+A variable 'eglot-cache-session-completions', t by default, controls
+this. The mechanism was tested with ccls, jdtls, pylsp, golsp and
+clangd. Notably, the C/C++ language server Clangd version 15 has a
+bug in its "isIcomplete" flag (it is fixed in later versions). If you
+run into problems, disable this mechanism like so:
+
+(add-hook 'c-common-mode-hook
+ (lambda () (setq-local eglot-cache-session-completions nil)))
+
+
* Changes in Eglot 1.13 (15/03/2023)
** ELPA installations on Emacs 26.3 are supported again.
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 04fc7235919..7e329d2e26a 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -1863,6 +1863,8 @@ Use `eglot-managed-p' to determine if current buffer is
managed.")
(unless (eglot--stay-out-of-p 'xref)
(add-hook 'xref-backend-functions 'eglot-xref-backend nil t))
(add-hook 'completion-at-point-functions #'eglot-completion-at-point nil t)
+ (add-hook 'completion-in-region-mode-hook #'eglot--capf-session-flush nil
t)
+ (add-hook 'company-after-completion-hook #'eglot--capf-session-flush nil t)
(add-hook 'change-major-mode-hook #'eglot--managed-mode-off nil t)
(add-hook 'post-self-insert-hook 'eglot--post-self-insert-hook nil t)
(add-hook 'pre-command-hook 'eglot--pre-command-hook nil t)
@@ -1894,6 +1896,8 @@ Use `eglot-managed-p' to determine if current buffer is
managed.")
(remove-hook 'after-save-hook 'eglot--signal-textDocument/didSave t)
(remove-hook 'xref-backend-functions 'eglot-xref-backend t)
(remove-hook 'completion-at-point-functions #'eglot-completion-at-point t)
+ (remove-hook 'completion-in-region-mode-hook #'eglot--capf-session-flush t)
+ (remove-hook 'company-after-completion-hook #'eglot--capf-session-flush t)
(remove-hook 'change-major-mode-hook #'eglot--managed-mode-off t)
(remove-hook 'post-self-insert-hook 'eglot--post-self-insert-hook t)
(remove-hook 'pre-command-hook 'eglot--pre-command-hook t)
@@ -2896,6 +2900,13 @@ for which LSP on-type-formatting should be requested."
:trimFinalNewlines (if delete-trailing-lines t
:json-false))
args)))))
+(defvar eglot-cache-session-completions t
+ "If non-nil Eglot caches data during completion sessions.")
+
+(defvar eglot--capf-session :none "A cache used by
`eglot-completion-at-point'.")
+
+(defun eglot--capf-session-flush (&optional _) (setq eglot--capf-session
:none))
+
(defun eglot-completion-at-point ()
"Eglot's `completion-at-point' function."
;; Commit logs for this function help understand what's going on.
@@ -2911,40 +2922,50 @@ for which LSP on-type-formatting should be requested."
:sortText)))))
(metadata `(metadata (category . eglot)
(display-sort-function . ,sort-completions)))
- resp items (cached-proxies :none)
+ (local-cache :none)
+ (bounds (bounds-of-thing-at-point 'symbol))
+ (orig-pos (point))
+ (resolved (make-hash-table))
(proxies
(lambda ()
- (if (listp cached-proxies) cached-proxies
- (setq resp
- (eglot--request server
- :textDocument/completion
- (eglot--CompletionParams)
- :cancel-on-input t))
- (setq items (append
- (if (vectorp resp) resp (plist-get resp :items))
- nil))
- (setq cached-proxies
- (mapcar
- (jsonrpc-lambda
- (&rest item &key label insertText insertTextFormat
- textEdit &allow-other-keys)
- (let ((proxy
- ;; Snippet or textEdit, it's safe to
- ;; display/insert the label since
- ;; it'll be adjusted. If no usable
- ;; insertText at all, label is best,
- ;; too.
- (cond ((or (eql insertTextFormat 2)
- textEdit
- (null insertText)
- (string-empty-p insertText))
- (string-trim-left label))
- (t insertText))))
- (unless (zerop (length proxy))
- (put-text-property 0 1 'eglot--lsp-item item
proxy))
- proxy))
- items)))))
- (resolved (make-hash-table))
+ (if (listp local-cache) local-cache
+ (let* ((resp (eglot--request server
+ :textDocument/completion
+ (eglot--CompletionParams)
+ :cancel-on-input t))
+ (items (append
+ (if (vectorp resp) resp (plist-get resp :items))
+ nil))
+ (cachep (and (listp resp) items
+ eglot-cache-session-completions
+ (eq (plist-get resp :isIncomplete)
:json-false)))
+ (bounds (or bounds
+ (cons (point) (point))))
+ (proxies
+ (mapcar
+ (jsonrpc-lambda
+ (&rest item &key label insertText insertTextFormat
+ textEdit &allow-other-keys)
+ (let ((proxy
+ ;; Snippet or textEdit, it's safe to
+ ;; display/insert the label since
+ ;; it'll be adjusted. If no usable
+ ;; insertText at all, label is best,
+ ;; too.
+ (cond ((or (eql insertTextFormat 2)
+ textEdit
+ (null insertText)
+ (string-empty-p insertText))
+ (string-trim-left label))
+ (t insertText))))
+ (unless (zerop (length proxy))
+ (put-text-property 0 1 'eglot--lsp-item item
proxy))
+ proxy))
+ items)))
+ ;; (trace-values "Requested" (length proxies) cachep bounds)
+ (setq eglot--capf-session
+ (if cachep (list bounds proxies resolved orig-pos)
:none))
+ (setq local-cache proxies)))))
(resolve-maybe
;; Maybe completion/resolve JSON object `lsp-comp' into
;; another JSON object, if at all possible. Otherwise,
@@ -2957,11 +2978,19 @@ for which LSP on-type-formatting should be requested."
(plist-get lsp-comp :data))
(eglot--request server :completionItem/resolve
lsp-comp :cancel-on-input t)
- lsp-comp)))))
- (bounds (bounds-of-thing-at-point 'symbol)))
+ lsp-comp))))))
+ (unless bounds (setq bounds (cons (point) (point))))
+ (when (and (consp eglot--capf-session)
+ (= (car bounds) (car (nth 0 eglot--capf-session)))
+ (>= (cdr bounds) (cdr (nth 0 eglot--capf-session))))
+ (setq local-cache (nth 1 eglot--capf-session)
+ resolved (nth 2 eglot--capf-session)
+ orig-pos (nth 3 eglot--capf-session))
+ ;; (trace-values "Recalling cache" (length local-cache) bounds
orig-pos)
+ )
(list
- (or (car bounds) (point))
- (or (cdr bounds) (point))
+ (car bounds)
+ (cdr bounds)
(lambda (probe pred action)
(cond
((eq action 'metadata) metadata) ; metadata
@@ -3032,7 +3061,7 @@ for which LSP on-type-formatting should be requested."
:company-require-match 'never
:company-prefix-length
(save-excursion
- (when (car bounds) (goto-char (car bounds)))
+ (goto-char (car bounds))
(when (listp completion-capability)
(looking-back
(regexp-opt
@@ -3040,6 +3069,7 @@ for which LSP on-type-formatting should be requested."
(eglot--bol))))
:exit-function
(lambda (proxy status)
+ (eglot--capf-session-flush)
(when (memq status '(finished exact))
;; To assist in using this whole `completion-at-point'
;; function inside `completion-in-region', ensure the exit
@@ -3063,17 +3093,12 @@ for which LSP on-type-formatting should be requested."
(let ((snippet-fn (and (eql insertTextFormat 2)
(eglot--snippet-expansion-fn))))
(cond (textEdit
- ;; Undo (yes, undo) the newly inserted completion.
- ;; If before completion the buffer was "foo.b" and
- ;; now is "foo.bar", `proxy' will be "bar". We
- ;; want to delete only "ar" (`proxy' minus the
- ;; symbol whose bounds we've calculated before)
- ;; (github#160).
- (delete-region (+ (- (point) (length proxy))
- (if bounds
- (- (cdr bounds) (car bounds))
- 0))
- (point))
+ ;; Revert buffer back to state when the edit
+ ;; was obtained from server. If a `proxy'
+ ;; "bar" was obtained from a buffer with
+ ;; "foo.b", the LSP edit applies to that'
+ ;; state, _not_ the current "foo.bar".
+ (delete-region orig-pos (point))
(eglot--dbind ((TextEdit) range newText) textEdit
(pcase-let ((`(,beg . ,end)
(eglot--range-region range)))
- master updated (093a360251a -> f886ae5cf07), João Távora, 2023/04/02
- master d00e05daa96 04/10: Eglot: take advantage of new Eldoc options for signature doc, João Távora, 2023/04/02
- master a8c1559a663 01/10: Eglot: remove hacky advice of jsonrpc-request, João Távora, 2023/04/02
- master ad1efe5e675 05/10: Eglot: improve caching in eglot-completion-at-point,
João Távora <=
- master 66c48f9e46a 02/10: Eglot: define eglot--ensure-list with defalias, João Távora, 2023/04/02
- master 87f025117b8 08/10: ; Eldoc: fix doc of e-d-functions w.r.t. :origin keyword, João Távora, 2023/04/02
- master f886ae5cf07 10/10: ; * etc/EGLOT-NEWS (Upcoming 1.14): Update., João Távora, 2023/04/02
- master bdb400912e0 06/10: Eglot: load built-in GNU ELPA dependencies explicitly, João Távora, 2023/04/02
- master f2357df91f0 09/10: Eldoc: bump package version to 1.14.0, João Távora, 2023/04/02
- master ecf53a50037 07/10: ; Eglot: removed unused dependency on 'array.el', João Távora, 2023/04/02
- master d69d0b1a296 03/10: Eglot: declare support for markdown also for signatures, João Távora, 2023/04/02