nextjournal

wilkerlucio 2021-07-03T06:29:23.023Z

hello everyone, I'm using the nextjournal codemirror in a project and I'm trying to figure how to support auto-complete on it.

wilkerlucio 2021-07-03T06:30:07.023900Z

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?

mkvlr 2021-07-03T07:06:35.025500Z

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).

mkvlr 2021-07-03T07:08:02.026500Z

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:

mkvlr 2021-07-03T07:08:03.026900Z

(autocomplete/autocompletion #js {:activateOnTyping true
                                                                                                :override #js [input-method-completion-source]})

mkvlr 2021-07-03T07:08:27.027200Z

and

mkvlr 2021-07-03T07:08:28.027700Z

(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)))))}))))

mkvlr 2021-07-03T07:11:19.029400Z

and

mkvlr 2021-07-03T07:11:20.029600Z

(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")})))

mkvlr 2021-07-03T07:12:28.030100Z

j comes from [applied-science.js-interop :as j] and [nextjournal.clojure-mode.util :as u]

wilkerlucio 2021-07-03T14:16:07.030600Z

@mkvlr thanks! that helps. trying to figure what is token-before, is this something available or I have to implement?

mkvlr 2021-07-03T18:09:35.031800Z

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))}))))

wilkerlucio 2021-07-03T19:29:18.033900Z

cool, thanks! one more thing, can you tell me what is the expected format of suggestions and matches? or that input-method/suggestions fn 🙂

mkvlr 2021-07-03T19:41:32.034200Z

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 ">>")))}))

wilkerlucio 2021-07-03T19:44:25.034400Z

gotcha, this is been very helpful 🙂

mkvlr 2021-07-03T19:44:34.034600Z

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

wilkerlucio 2021-07-03T19:44:37.034800Z

now I'm trying to figure how to send some dynamic data to use on my completion

wilkerlucio 2021-07-03T19:44:50.035Z

because the keywords that I'm going to use as the list, they are dynamic and can keep changing

wilkerlucio 2021-07-03T19:45:16.035200Z

but since we pass input-method-completion-source, I'm not sure how to add something to read during its runtime

wilkerlucio 2021-07-03T19:45:37.035400Z

I guess there may be a codemirror way to pass this, but I'm very noob at it 😅

mkvlr 2021-07-03T19:47:19.035600Z

yeah, you can return a promise from a completion source

wilkerlucio 2021-07-03T19:49:19.036Z

not the issue in this case, is more how can I send this from a parent part of the process down?

mkvlr 2021-07-03T19:49:23.036200Z

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)

wilkerlucio 2021-07-03T19:49:32.036400Z

but nice to hear it supports promises

mkvlr 2021-07-03T19:50:02.036600Z

> from a parent part of the process down? not sure I follow, can you explain more?

wilkerlucio 2021-07-03T19:50:40.036800Z

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

wilkerlucio 2021-07-03T19:50:53.037Z

so Im trying to find a wya to add data to ctx, so I can read there

wilkerlucio 2021-07-03T19:51:02.037200Z

maybe somehow when I construct the extensions?

mkvlr 2021-07-03T19:51:12.037400Z

at Nextjournal we apply codemirrors transactions through re-frame/dispatch-sync

mkvlr 2021-07-03T19:51:25.037600Z

oh yes, you can do that when creating the extension

wilkerlucio 2021-07-03T19:52:13.037800Z

cool, do you have an example?

mkvlr 2021-07-03T19:53:05.038Z

(.. EditorView -editable (of editable?))

mkvlr 2021-07-03T19:53:46.038200Z

you have the ctx you want at EditorState creation time, correct?

wilkerlucio 2021-07-03T19:54:05.038400Z

this of thing is where I get confused on CM6

mkvlr 2021-07-03T19:54:38.038600Z

yeah, this is a Facet, see https://codemirror.net/6/docs/guide/

wilkerlucio 2021-07-03T19:54:42.038800Z

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)))))]))})

mkvlr 2021-07-03T19:56:03.039Z

ok, and what’s the context you need in the completions?

wilkerlucio 2021-07-03T19:56:32.039200Z

lets say I have a vector with all the keywords I use to complete

wilkerlucio 2021-07-03T19:56:57.039400Z

how I setup the editor so its available at ctx when CM6 calls input-method-completion-source?

mkvlr 2021-07-03T19:57:24.039600Z

I think the easiest thing you can try is https://codemirror.net/6/docs/ref/#autocomplete.completeFromList

mkvlr 2021-07-03T19:57:46.039800Z

(given the list doesn’t change)

mkvlr 2021-07-03T19:58:04.040Z

but maybe it even can change and will be called every time if you return a promise

wilkerlucio 2021-07-03T19:58:28.040200Z

makes sense

wilkerlucio 2021-07-03T19:59:18.040400Z

cool, so I create an instance of CompletionSource and drop in my extensions?

mkvlr 2021-07-03T20:00:26.040600Z

yes

wilkerlucio 2021-07-03T20:12:53.040800Z

got it working, thank you very much!

1
mkvlr 2021-07-03T07:10:38.029100Z

this is our functionality in Nextjournal that gives you lets you complete special characters when you type \ so pretty similar to what you want?

mkvlr 2021-07-03T07:14:57.030500Z

hope that helps to get you started

Thomas Schranz 2021-07-03T17:35:05.031700Z

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)

mkvlr 2021-07-03T18:12:07.033400Z

@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.

Thomas Schranz 2021-07-03T18:12:49.033800Z

oh nice @ sidenotes, makes sense