Compare commits

...

8 commits

Author SHA1 Message Date
Stefan Monnier
a03bc3e98c external-completion.el: Minor changes
* lisp/external-completion.el (external-completion-table): Tweak docstring.
Explicitly handle `lambda`.

* lisp/progmodes/eglot.el (xref-backend-identifier-completion-table):
Rename category to `eglot-xref`.
2022-12-05 23:50:55 -05:00
João Távora
18e790944e Update eglot.el to use the new external-completion-table
* lisp/progmodes/eglot.el
(xref-backend-identifier-completion-table): Update.
2022-12-04 13:24:00 +00:00
João Távora
9eae95a823 Add caching and yet more doc to external-completion.el
* lisp/external-completion.el (cl-lib): Require cl-lib.
(external-completion-table): Rework for caching.
2022-12-04 11:01:49 +00:00
João Távora
1af23aade6 More tweaks to external-completion.el after Stefan's comments
* lisp/external-completion.el (Commentary): Update.
(completion-styles-alist): Name the style 'external'.
(external-completion-table): Change order of arguments.  Use
double-dashed symbols.
(external-completion--try-completion)
(external-completion--all-completions): Use double-dashed symbols.
2022-12-03 23:26:25 +00:00
João Távora
aaaa016056 Speed it up 2022-12-03 13:17:12 +00:00
João Távora
9a62da21c2 Integrate Stefan suggestions but rename it to "external-completion.el"
"External completion" is a much better name.  There are already a
thousand in-Emacs meanings for "backend" and this new style is really
meant to be used for tools _outside_ of Emacs.
2022-12-03 13:15:53 +00:00
João Távora
a049c8f9da Don't override completion-category-defaults in fido-mode
Fido mode's mantra is to use the 'flex' completion style wherever
possible, but this clashes with some completion table categories that
can't use that style, such as the 'backend-completion' category, which
uses the backend-completion-backend-style style.

* lisp/icomplete.el (icomplete--fido-mode-setup): Don't override
completion-category-defaults.
2022-11-25 13:05:40 +00:00
João Távora
c807121fbd Add lisp/backend-completion.el
This completion style is meant to be used with a "programmable
completion" table that interfaces with an external providing
completions, such as a shell utility, an inferior process, an http
server.  The table and external tool are tasked to do the matching of
the pattern string to the potential candidates of completion, and as
such it fully controls the style.

When this completion style is in use, the usual styles configured
by the user or other in `completion-styles' are completely
overriden.  This can be seen as a drawback, but, on the other hand,
the regular the full data set to be available in Emacs' addressing
space, which is often not feasible.

* lisp/backend-completion.el: New file.

* lisp/progmodes/eglot.el (Package-Requires): Require
backend-completion.
(completion-styles-alist): Don't set it.
(eglot--lsp-backend-style-call)
(eglot--lsp-backend-style-try-completion)
(eglot--lsp-backend-style-all-completions): Delete.
2022-11-25 13:03:45 +00:00
3 changed files with 184 additions and 50 deletions

174
lisp/external-completion.el Normal file
View file

@ -0,0 +1,174 @@
;;; external-completion.el --- Let external tools control completion style -*- lexical-binding: t; -*-
;; Copyright (C) 2018-2022 Free Software Foundation, Inc.
;; Version: 0.1
;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
;; Maintainer: João Távora <joaotavora@gmail.com>
;; Keywords:
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; Written by Stefan Monnier circa 2016. Variants of this code have
;; been working stably in SLY and other packages for a long time.
;; The `external' completion style is used with a "programmable
;; completion" table that gathers completions from an external tool
;; such as a shell utility, an inferior process, an http server.
;; The table and external tool are fully in control of the matching of
;; the pattern string to the potential candidates of completion.
;; When `external' is in use, the usual styles configured by the user
;; or other in `completion-styles' are ignored.
;;
;; This compromise is for speed: all other styles need the full data
;; set to be available in Emacs' addressing space, which is often slow
;; if not completely unfeasible.
;;
;; To make use of the `external' style the function
;; `external-completion-table' should be used. See its docstring.
;;; Code:
(require 'cl-lib)
(add-to-list 'completion-styles-alist
'(external
external-completion--try-completion
external-completion--all-completions
"Ad-hoc completion style provided by the completion table."))
(defun external-completion-table ( category lookup
&optional metadata try-completion-function)
"Make completion table using the `external' completion style.
The completion table produced foregoes any styles normally set in
`completion-styles' and will set up an entry for the symbol
CATEGORY in `completion-category-defaults', linking it to the
special completion style `external'.
This style is useful when the caller interfaces with an external
tool that provides completions. This may be a shell utility, an
inferior process, an http server, etc. The external tool does
the matching, and the full set of candidates doesn't need to be
transferred to Emacs's address space. This often results in much
faster overall completion at the expense of the choice of
completion styles.
LOOKUP is a function taking a string PATTERN and a position POINT.
The function should contact the backend and return a list
of strings representing the candidates matching PATTERN given
that POINT is the location of point within it. LOOKUP decides if
PATTERN is interpreted as a substring, a regular expression, or
any other type of matching key. Strings returned by LOOKUP may
be propertized with `completions-common-part' to illustrate the
specific interpretation. To maintain responsiveness in the face
of all but the spiffiest external tools, LOOKUP should detect
timeouts and pending user input with `while-no-input' or
`sit-for' (which see), cancel the request (if that is possible)
and immediately return any non-list.
Calls to LOOKUP are cached. There is no option to flush the cache,
so if you need to do it, create a new completion table by calling
`external-completion-table' again.
CATEGORY is a symbol that should identify the kind of things
being completed, so users can refer to it in `completion-category-override'.
METADATA is an alist of additional properties such as `cycle-sort-function'
to associate with CATEGORY. This means that the caller may still
retain control the sorting of the candidates while the tool
controls the matching.
TRY-COMPLETION-FUNCTION is a function taking arguments
\(PATTERN POINT ALL-COMPLETIONS), where PATTERN
and POINT are as described above and ALL-COMPLETIONS are all
candidates previously gathered by LOOKUP. It should return
a cons cell (NEWPATTERN . NEWPOINT) such that NEWPATTERN is
as long as possible within the constraints that LOOKUP still returns
the same set of candidates as it does for PATTERN and POINT.
This can only be provided if you happen to know what kind of
completion style is used by the external tool and is only worthwhile
to provide if that completion style is fairly simple:
if the tool is using something like \"flex\", it's probably useless."
(unless (assq category completion-category-defaults)
(push `(,category (styles external))
completion-category-defaults))
(let ((cache (make-hash-table :test #'equal)))
(cl-flet ((lookup-internal (string point)
(let* ((key (cons string point))
(probe (gethash key cache 'external--notfound)))
(if (eq probe 'external--notfound)
(puthash key (funcall lookup string point) cache)
probe))))
(lambda (string pred action)
(pcase action
(`metadata
`(metadata (category . ,category) . ,metadata))
(`(external-completion--tryc . ,point)
;; FIXME: Obey `pred'? Pass it to `try-completion-function'?
`(external-completion--tryc
. ,(if try-completion-function
(funcall try-completion-function
string
point
(lookup-internal string point))
(cons string point))))
(`(external-completion--allc . ,point)
(let ((all (lookup-internal string point)))
`(external-completion--allc
. ,(if pred (cl-remove-if-not pred all) all))))
(`(boundaries . ,_) nil)
('lambda t) ;; `test-completion' should assume all results are valid.
(_method
;; FIXME: I suspect many external backends can't make much use of
;; POINT, in which case the call below may actually be equivalent
;; to one that's already in cache but we will fail to reuse it
;; just because POINT is different :-(
(let ((all (lookup-internal string (length string))))
;; This is here for two reasons:
;;
;; * for when users customize `completion-category-overrides'
;; to access this table with a non-`external' completion style.
;; It won't work very well for styles more complex than PCM,
;; but it should work fine for prefix completion.
;;
;; * for when `_method' above can also be `nil' or `lambda'
;; which has some semantics and use I don't fully
;; understand. It doesn't seem to do anything very
;; useful in my tests, but since we have caching it
;; doesn't seem to hurt much either.
(complete-with-action action all string pred))))))))
;; Note: the "tryc", "allc" suffixes are made akward on purpose, so
;; it's easy to pick them apart from the jungle of combinations of
;; "try" and "all" and "completion" that inhabit Emacs's completion
;; logic.
(defun external-completion--call (op string table pred point)
(when (functionp table)
(let ((res (funcall table string pred (cons op point))))
(when (eq op (car-safe res))
(cdr res)))))
(defun external-completion--try-completion (string table pred point)
(external-completion--call 'external-completion--tryc
string table pred point))
(defun external-completion--all-completions (string table pred point)
(external-completion--call 'external-completion--allc
string table pred point))
(provide 'external-completion)
;;; external-completion.el ends here

View file

@ -416,7 +416,6 @@ if that doesn't produce a completion match."
icomplete-scroll (not (null icomplete-vertical-mode))
completion-styles '(flex)
completion-flex-nospace nil
completion-category-defaults nil
completion-ignore-case t
read-buffer-completion-ignore-case t
read-file-name-completion-ignore-case t)))

View file

@ -7,7 +7,7 @@
;; Maintainer: João Távora <joaotavora@gmail.com>
;; URL: https://github.com/joaotavora/eglot
;; Keywords: convenience, languages
;; Package-Requires: ((emacs "26.3") (jsonrpc "1.0.14") (flymake "1.2.1") (project "0.3.0") (xref "1.0.1") (eldoc "1.11.0") (seq "2.23"))
;; Package-Requires: ((emacs "26.3") (jsonrpc "1.0.14") (flymake "1.2.1") (project "0.3.0") (xref "1.0.1") (eldoc "1.11.0") (seq "2.23") (external-completion "0.1"))
;; This is a GNU ELPA :core package. Avoid adding functionality
;; that is not available in the version of Emacs recorded above or any
@ -110,6 +110,7 @@
(require 'filenotify)
(require 'ert)
(require 'array)
(require 'external-completion)
;; ElDoc is preloaded in Emacs, so `require'-ing won't guarantee we are
;; using the latest version from GNU Elpa when we load eglot.el. Use an
@ -2565,7 +2566,7 @@ If BUFFER, switch to it before."
(let ((probe (gethash pat cache :missing)))
(if (eq probe :missing) (puthash pat (refresh pat) cache)
probe)))
(lookup (pat)
(lookup (pat _point)
(let ((res (lookup-1 pat))
(def (and (string= pat "") (gethash :default cache))))
(append def res nil)))
@ -2573,16 +2574,12 @@ If BUFFER, switch to it before."
(cl-getf (get-text-property
0 'eglot--lsp-workspaceSymbol c)
:score 0)))
(lambda (string _pred action)
(pcase action
(`metadata `(metadata
(cycle-sort-function
. ,(lambda (completions)
(cl-sort completions #'> :key #'score)))
(category . eglot-indirection-joy)))
(`(eglot--lsp-tryc . ,point) `(eglot--lsp-tryc . (,string . ,point)))
(`(eglot--lsp-allc . ,_point) `(eglot--lsp-allc . ,(lookup string)))
(_ nil))))))
(external-completion-table
'eglot-xref
#'lookup
`((cycle-sort-function
. ,(lambda (completions)
(cl-sort completions #'> :key #'score))))))))
(defun eglot--recover-workspace-symbol-meta (string)
"Search `eglot--workspace-symbols-cache' for rich entry of STRING."
@ -2595,7 +2592,7 @@ If BUFFER, switch to it before."
eglot--workspace-symbols-cache)))
(add-to-list 'completion-category-overrides
'(eglot-indirection-joy (styles . (eglot--lsp-backend-style))))
'(eglot-indirection-joy (styles . (backend-completion-backend-style))))
(cl-defmethod xref-backend-identifier-at-point ((_backend (eql eglot)))
(let ((attempt
@ -3431,42 +3428,6 @@ If NOERROR, return predicate, else erroring function."
'eglot-managed-mode-hook "1.6")
(provide 'eglot)
;;; Backend completion
;; Written by Stefan Monnier circa 2016. Something to move to
;; minibuffer.el "ASAP" (with all the `eglot--lsp-' replaced by
;; something else. The very same code already in SLY and stable for a
;; long time.
;; This "completion style" delegates all the work to the "programmable
;; completion" table which is then free to implement its own
;; completion style. Typically this is used to take advantage of some
;; external tool which already has its own completion system and
;; doesn't give you efficient access to the prefix completion needed
;; by other completion styles. The table should recognize the symbols
;; 'eglot--lsp-tryc and 'eglot--lsp-allc as ACTION, reply with
;; (eglot--lsp-tryc COMP...) or (eglot--lsp-allc . (STRING . POINT)),
;; accordingly. tryc/allc names made akward/recognizable on purpose.
(add-to-list 'completion-styles-alist
'(eglot--lsp-backend-style
eglot--lsp-backend-style-try-completion
eglot--lsp-backend-style-all-completions
"Ad-hoc completion style provided by the completion table."))
(defun eglot--lsp-backend-style-call (op string table pred point)
(when (functionp table)
(let ((res (funcall table string pred (cons op point))))
(when (eq op (car-safe res))
(cdr res)))))
(defun eglot--lsp-backend-style-try-completion (string table pred point)
(eglot--lsp-backend-style-call 'eglot--lsp-tryc string table pred point))
(defun eglot--lsp-backend-style-all-completions (string table pred point)
(eglot--lsp-backend-style-call 'eglot--lsp-allc string table pred point))
;; Local Variables:
;; bug-reference-bug-regexp: "\\(github#\\([0-9]+\\)\\)"