hello everyone, I'm using the nextjournal codemirror in a project and I'm trying to figure how to support auto-complete on it.
in my case the auto-complete is simple, context free, I like to provide a list of keywords that should auto-complete when the user types :, does somebody knows how to configure such thing?
hi @wilkerlucio, sure thing. You’ll have to add an extension to get autocompletion. So add https://codemirror.net/6/docs/ref/#autocomplete.autocompletion to the EditorState (the relevant of the demo setup would be https://github.com/nextjournal/clojure-mode/blob/a83c87cd2bd2049b70613f360336a096d15c5518/demo/src/nextjournal/clojure_mode/demo.cljs#L69).
our language data doesn’t contain completions yet (something we should add in the future) but it sounds like you’ll want to specify an override anyway, this should help:
(autocomplete/autocompletion #js {:activateOnTyping true
:override #js [input-method-completion-source]})and
(def input-method-completion-source
(j/fn [^:js {:as ^js ctx :keys [state pos]}]
(let [{:keys [text from to]} (token-before state pos)]
(when (str/starts-with? text \\)
#js{:from (inc from)
:options (let [{:keys [suggestions matches]} (input-method/suggestions (subs text 1))]
(to-array
(cons (j/obj :label ""
:input-method true
:apply apply-input-method
:detail "\\")
(for [[prefix matches] (conj suggestions ["" matches])
match matches]
(j/obj :label (str (subs text 1) prefix)
:input-method true
:apply apply-input-method
:detail match)))))}))))and
(j/defn apply-input-method [^js view ^:js {insert :detail} from to]
(.dispatch view (j/lit {:changes [{:from (dec from)
:to to
:insert insert}]
:selection {:anchor (+ (dec from) (count insert))}
:annotations (u/user-event-annotation "noformat")})))j comes from [applied-science.js-interop :as j] and [nextjournal.clojure-mode.util :as u]
@mkvlr thanks! that helps. trying to figure what is token-before, is this something available or I have to implement?
oh, that’s this:
(defn token-before [^js state pos]
(j/let [^:js {line-from :from :keys [text]} (.. state -doc (lineAt pos))]
(if-let [text (re-find #"\\[^ ]*$" (subs text 0 (- pos line-from)))]
{:from (- pos (count text))
:to pos
:text text}
(j/let [^:js {:as node :keys [from to]} (n/tree state pos -1)]
;; TODO: for python completions are currently broken in cm6 because
;; given a line `pandas.|` this would only return the dot.
{:from from
:to to
:text (subs text (- from line-from) (- to line-from))}))))cool, thanks! one more thing, can you tell me what is the expected format of suggestions and matches? or that input-method/suggestions fn 🙂
see below but that’s more incidental to our problem (we’re using a trie because the lookup table is very large)
(defn suggestions
"Takes a (possibly empty) `prefix` string, returns exact `matches` and
all possible leaves in the prefix trie branching."
[prefix]
(let [subtrie (get-in keymap-trie prefix)]
{:matches (get subtrie ">>" [])
:suggestions (into ()
(comp (filter vector?) ;; find all leaves
(take 400)) ;; only relevant for an empty prefix
(tree-seq map? branch-fn (dissoc subtrie ">>")))}))
gotcha, this is been very helpful 🙂
this is all a bit complex in our Nextjournal code because we still support cm5 & 6, probably better to stick to the cm6 apis directly instead of copying all that
now I'm trying to figure how to send some dynamic data to use on my completion
because the keywords that I'm going to use as the list, they are dynamic and can keep changing
but since we pass input-method-completion-source, I'm not sure how to add something to read during its runtime
I guess there may be a codemirror way to pass this, but I'm very noob at it 😅
yeah, you can return a promise from a completion source
https://codemirror.net/6/docs/ref/#autocomplete.CompletionSource
not the issue in this case, is more how can I send this from a parent part of the process down?
and you have the state as an argument in the completion source, so you should be able to do anything you want (check where you’re at in the doc and what the context is etc)
but nice to hear it supports promises
> from a parent part of the process down? not sure I follow, can you explain more?
yes, when I run input-method-completion-source, since I'm just passing the function (and CM calls it), CM sends ctx in that call, and only that
so Im trying to find a wya to add data to ctx, so I can read there
maybe somehow when I construct the extensions?
at Nextjournal we apply codemirrors transactions through re-frame/dispatch-sync
oh yes, you can do that when creating the extension
cool, do you have an example?
(.. EditorView -editable (of editable?))
you have the ctx you want at EditorState creation time, correct?
this of thing is where I get confused on CM6
yeah, this is a Facet, see https://codemirror.net/6/docs/guide/
I'm creating it like this:
(.create EditorState #js {:doc (or (if state @state source) "")
:extensions (cond-> #js [extensions]
readonly
(.concat #js [(.of (.-editable EditorView) "false")])
state
(.concat #js [(.of (.-updateListener EditorView)
(fn [^js v]
(let [str (.. v -state -doc toString)]
(if (not= str @state)
(state str)))))]))})ok, and what’s the context you need in the completions?
lets say I have a vector with all the keywords I use to complete
how I setup the editor so its available at ctx when CM6 calls input-method-completion-source?
I think the easiest thing you can try is https://codemirror.net/6/docs/ref/#autocomplete.completeFromList
(given the list doesn’t change)
but maybe it even can change and will be called every time if you return a promise
makes sense
cool, so I create an instance of CompletionSource and drop in my extensions?
yes
got it working, thank you very much!
this is our functionality in Nextjournal that gives you lets you complete special characters when you type \ so pretty similar to what you want?
hope that helps to get you started
have you ever thought of adding a view to nextjournal where the computation output is next to the prose that comes before it? (iirc coffeescript documentation had a view like that)
@thomas.schranz not yet. We did some work in a branch on sidenotes similar to the ones in https://edwardtufte.github.io/tufte-css/. Somewhere down the road I’d like folks to be able to fully customize how a document is rendered, allowing you to bring in your own views.
oh nice @ sidenotes, makes sense