Skip to content

Commit

Permalink
Fix caching for the auto mode.
Browse files Browse the repository at this point in the history
 - If the current prefix is not in the cache but a complete candidate list
   is found in one of its prefixes, update the cache for the prefix. This
   avoids unnecessary incremental completion requests.
 - If no cache is found, do not allow company to cache candidates.
  • Loading branch information
tigersoldier committed Feb 7, 2018
1 parent 74242f1 commit e09df15
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 18 deletions.
21 changes: 15 additions & 6 deletions README.md
Expand Up @@ -31,12 +31,21 @@ After installing company-lsp, simply add `company-lsp` to `company-backends`:

## Customization

* `company-lsp-cache-candidates`: Can be set to `'auto`, `t`, or `nil`. When
set to `'auto`, candidates will not be cached if the server returns
incomplete completion list. When set to `t`, company caches the completion
candidates and filters the candidates as completion progresses. If set to
`nil`, each incremental completion triggers a completion request to the
language server.
* `company-lsp-cache-candidates`: Can be set to `'auto`, `t`, or `nil`.

When set to `'auto`, company-lsp caches the completion. It sends
incremental completion requests to the server if and only if the
cached results are incomplete. The candidate list may not be
sorted or filtered as the server would for cached completion
results.

When set to `t`, company-mode caches the completion. It won't send
incremental completion requests to the server.

When set to `nil`, results are not cached at all. The candidates
are always sorted and filtered by the server. Use this option if
the server handles caching for incremental completion or
sorting/matching provided by the server is critical.
* `company-lsp-async`: When set to non-nil, fetch completion candidates
asynchronously.
* `company-lsp-enable-snippet`: Set it to non-nil if you want to enable snippet
Expand Down
55 changes: 43 additions & 12 deletions company-lsp.el
Expand Up @@ -41,10 +41,19 @@
(defcustom company-lsp-cache-candidates 'auto
"Whether or not to cache completion candidates.
When set to non-nil, company caches the completion candidates so
company filters the candidates as completion progresses. If set
to nil, each incremental completion triggers a completion request
to the language server."
When set to 'auto, company-lsp caches the completion. It sends
incremental completion requests to the server if and only if the
cached results are incomplete. The candidate list may not be
sorted or filtered as the server would for cached completion
results.
When set to t, company-mode caches the completion. It won't send
incremental completion requests to the server.
When set to nil, results are not cached at all. The candidates
are always sorted and filtered by the server. Use this option if
the server handles caching for incremental completion or
sorting/matching provided by the server is critical."
:type '(choice (const :tag "Respect server response" auto)
(const :tag "Always cache" t)
(const :tag "Never cache" nil))
Expand Down Expand Up @@ -280,9 +289,9 @@ Return a list of strings as the completion candidates."
(when (eq company-lsp-cache-candidates 'auto)
;; Only cache candidates on auto mode. If it's t company caches the
;; candidates for us.
(setq company-lsp--completion-cache
(cons (cons prefix `(:incomplete ,incomplete :candidates ,candidates))
company-lsp--completion-cache)))
(company-lsp--cache-put
prefix
`(:incomplete ,incomplete :candidates ,candidates)))
candidates))

(defun company-lsp--cleanup-cache (_)
Expand All @@ -291,12 +300,37 @@ Return a list of strings as the completion candidates."
(remove-hook 'company-completion-finished-hook #'company-lsp--cleanup-cache)
(remove-hook 'company-completion-cancelled-hook #'company-lsp--cleanup-cache))

(defun company-lsp--cache-put (prefix candidates)
"Set cache for PREFIX to be CANDIDATES.
CANDIDATES is a plist of (:incomplete :candidates). :incomplete is
either t or nil. :candidates is a list of strings."
(setq company-lsp--completion-cache
(cons (cons prefix candidates)
company-lsp--completion-cache)))

(defun company-lsp--cache-get (prefix)
"Get the cached completion for PREFIX.
Return a plist of (:incomplete :candidates) if cache for PREFIX
exists. Otherwise return nil."
(cdr (assoc prefix company-lsp--completion-cache)))
(let ((cache (cdr (assoc prefix company-lsp--completion-cache)))
(len (length prefix))
previous-cache)
(if cache
cache
(cl-dotimes (i len)
(when (setq previous-cache
(cdr (assoc (substring prefix 0 (- len i 1))
company-lsp--completion-cache)))
(if (company-lsp--cache-incomplete-p previous-cache)
(cl-return nil)
(company-lsp--cache-put prefix previous-cache)
(cl-return previous-cache)))))))

(defun company-lsp--cache-incomplete-p (cache-item)
"Determine whether a CACHE-ITEM is incomplete."
(plist-get cache-item :incomplete))

(defun company-lsp--documentation (candidate)
"Get the documentation from the item in the CANDIDATE.
Expand Down Expand Up @@ -360,10 +394,7 @@ See the documentation of `company-backends' for COMMAND and ARG."
(company-lsp--candidates-async arg callback))))
(company-lsp--candidates-sync arg)))
(sorted t)
(no-cache (if (eq company-lsp-cache-candidates 'auto)
(let ((cache (company-lsp--cache-get arg)))
(and cache (plist-get cache :incomplete)))
(not company-lsp-cache-candidates)))
(no-cache (not (eq company-lsp-cache-candidates t)))
(annotation (lsp--annotate arg))
(quickhelp-string (company-lsp--documentation arg))
(match (length arg))
Expand Down
40 changes: 40 additions & 0 deletions test/company-lsp-test.el
Expand Up @@ -126,3 +126,43 @@
(puthash "detail" "(1, 2)" item)
(expect (company-lsp--rust-completion-snippet item)
:to-be nil)))))

(describe "Getting cache"
(it "Should return the cache item if prefix matches"
(let ((company-lsp--completion-cache nil)
(cache-item '(:incomplete nil ("foo" "bar"))))
(company-lsp--cache-put "prefix" cache-item)
(expect (company-lsp--cache-get "prefix")
:to-equal cache-item)))

(it "Should return the cache item of sub-prefix if it's complete"
(let ((cache-item '(:incomplete nil ("foo" "bar"))))
(setq company-lsp--completion-cache nil)
(expect (company-lsp--cache-get "prefix1234")
:to-equal nil)
(company-lsp--cache-put "" cache-item)
(expect (company-lsp--cache-get "prefix1234")
:to-equal cache-item)

(setq company-lsp--completion-cache nil)
(expect (company-lsp--cache-get "prefix1234")
:to-equal nil)
(company-lsp--cache-put "prefix" cache-item)
(expect (company-lsp--cache-get "prefix1234")
:to-equal cache-item)

(setq company-lsp--completion-cache nil)
(expect (company-lsp--cache-get "prefix1234")
:to-equal nil)
(company-lsp--cache-put "prefix123" cache-item)
(expect (company-lsp--cache-get "prefix1234")
:to-equal cache-item)))

(it "Should not return the cache item of sub-prefix if it's incomplete"
(let ((company-lsp--completion-cache nil)
(cache-item '(:incomplete t ("foo" "bar"))))
(company-lsp--cache-put "prefix" cache-item)
(expect (company-lsp--cache-get "prefix1234")
:to-equal nil))))


0 comments on commit e09df15

Please sign in to comment.