Briefly, Async Completions with CodeMirror
Sun Aug 10, 2014Really quick update, no philosophical thoughts at all. It just seems I got into an uncommon situation, and wanted to share.
CodeMirror has auto-completion. Via show-hint
and various in-browser, language-specific modes. What I wanted to do is have a server-side handler that pulls out completions for a given partial symbol and just lets the front-end display them. The way to do this seems to be neither obvious nor well-documented, given how my googling went. The frustrating thing is that, if you read the show-hint
source code, it's clearly built to support asynchronous hinting, but doesn't make it very clear how to take advantage of that.
The examples involve writing a function that returns an object which can then be used to display a little completions drop-down, which kind of assumes that your completion function is going to immediately return something.
Yes, I could use these promises
that are so fashionable nowadays, if I felt like re-writing show-hint
, but I want something that works out of the box, with minimal tweaking of library code. Here's an approach that seems to work.
...
(register-helpers
"hint"
(create :ajax
(lambda (mirror callback options)
(let* ((cur (chain mirror (get-cursor)))
(tok (chain mirror (get-token-at cur))))
(get "/cl-notebook/system/complete" (create :partial (@ tok string) :package :cl-notebook)
(lambda (res)
(callback
(create :list (or (string->obj res) (new (-array)))
:from (chain -code-mirror (-pos (@ cur line) (@ tok start)))
:to (chain -code-mirror (-pos (@ cur line) (@ tok end)))))))))
:auto
(lambda (mirror options)
(chain -code-mirror commands
(autocomplete mirror (@ -code-mirror hint ajax) (create :async t))))))
...
For the JavaScripters out there
CodeMirror.registerHelper(
"hint", "ajax",
function (mirror, callback, options) {
var cur = mirror.getCursor();
var tok = mirror.getTokenAt(cur);
httpGet("/cl-notebook/system/complete", { partial: tok.string, package: "cl-notebook" },
function (res) {
callback({ list: JSON.parse(res) || [],
from: CodeMirror.Pos(cur.line, tok.start),
to: CodeMirror.Pos(cur.line, tok.end)
})
})
})
CodeMirror.registerHelper(
"hint", "auto",
function (mirror, options) {
CodeMirror.commands.autocomplete(mirror, CodeMirror.hint.ajax, { async: true })
})
The new auto
helper overrides the show-hint
default and makes sure that the ajax
helper gets called on a trigger of autocomplete
. This approach assumes you will only be completing via server hit. If you want multiple completion sources, your custom auto
helper will need to be a bit more complicated than shown. httpGet
is something you'll have to define yourself, or get from a library somewhere, but its intent should be self-explanatory. All you need now is a handler on your server side at that uri that takes those parameters and spits back a list of completions for the given partial word. Mine looks like
(define-json-handler (cl-notebook/system/complete) ((partial :string) (package :keyword))
(let ((p (string-upcase partial))
(res))
(do-symbols (s package)
(when (alexandria:starts-with-subseq p (symbol-name s))
(push s res)))
(sort (mapcar (lambda (s) (string-downcase (symbol-name s)))
(remove-duplicates res))
#'< :key #'length)))
which is about as naive as you can get. It does the job though; even when completing on the empty string (which returns all symbols in the cl-notebook
package), the server comes back with a response in about 10 msec
.
That's that. If you're in the situation I was in a couple hours ago, hopefully you'll stumble upon this and have an easier time.