Skip to content

Instantly share code, notes, and snippets.

@bobbicodes
Last active April 16, 2023 11:40
Show Gist options
  • Save bobbicodes/cc36b036aab209245ab8e5e17ede4bf1 to your computer and use it in GitHub Desktop.
Save bobbicodes/cc36b036aab209245ab8e5e17ede4bf1 to your computer and use it in GitHub Desktop.
Inline eval with Codemirror 6 and scittle
(require '[clojure.string :as str])
(declare cm)
(defn eval-string [s]
(when-some [code (not-empty (str/trim s))]
(try {:result (js/scittle.core.eval_string code)}
(catch js/Error e
{:error (str (.-message e))}))))
(defonce last-result (atom ""))
(defonce eval-tail (atom nil))
(defn update-editor! [text cursor-pos]
(let [end (count (some-> cm .-state .-doc str))]
(.dispatch cm #js{:changes #js{:from 0 :to end :insert text}
:selection #js{:anchor cursor-pos :head cursor-pos}})))
(defn parse-char [level pos]
(case pos
\( (inc level)
\) (dec level)
level))
(defn form-at-cursor
"Takes the string of characters before cursor pos."
[s]
(let [run (rest (reductions parse-char 0 s))]
(->> s
(take (inc (count (take-while #(not= 0 %) run))))
reverse
(apply str))))
(defn eval-at-cursor [viewer]
(let [cursor-pos (some-> cm .-state .-selection .-main .-head)
code (some-> cm .-state .-doc str)]
(let [region (form-at-cursor (reverse (take cursor-pos code)))
region (if (nil? region) nil (eval-string region))]
(if (nil? region) nil (reset! last-result region)))
(update-editor! (str (subs code 0 cursor-pos)
(when-not (= "" (:result @last-result)) " => ")
(:result @last-result)
(reset! eval-tail (subs code cursor-pos (count code))))
cursor-pos)
(.dispatch cm #js{:selection #js{:anchor cursor-pos :head cursor-pos}}))
true)
(defn code-str [s]
(str (rest (read-string (str "(do " s ")")))))
(defn code-seq [s]
(map str (rest (read-string (str "(do " s ")")))))
;; but we really want to find the center points, not the start points.
(defn find-center [[start s]]
[(+ start (int (/ (count s) 2))) s])
;; then just pick the one with the closest center point to the cursor,
;; and evaluate it!
(defn abs [v]
(if (neg? v) (- v) v))
(defn top-level [s pos]
(first (nfirst
(sort-by #(abs (- pos (first %)))
(map find-center
(map vector
(map #(str/last-index-of (code-str s) %) (code-seq s))
(code-seq s)))))))
(defn eval-top-level [viewer]
(let [code (some-> cm .-state .-doc str)
cursor-pos (some-> cm .-state .-selection .-main .-head)
result (reset! last-result (eval-string (top-level code cursor-pos)))]
(update-editor! (str (subs code 0 cursor-pos)
(when-not (= "" (:result @last-result)) " => ")
(:result result)
(subs code cursor-pos))
cursor-pos))
true)
(defn eval-cell [viewer]
(let [code (some-> cm .-state .-doc str)]
(reset! last-result (eval-string (str "(do " (.-doc (.-state viewer)) " )")))
(update-editor! (str code
(when-not (= "" (:result @last-result)) " => ")
(:result @last-result))
(count code)))
true)
(defn clear-eval []
(let [code (some-> cm .-state .-doc str)
cursor-pos (some-> cm .-state .-selection .-main .-head)
result @last-result
splits (str/split code #" => ")]
(when (not= "" @last-result)
(update-editor! (str (first splits) (subs (last splits) (count (str (:result result)))))
cursor-pos)
(reset! last-result "")
(reset! eval-tail ""))))
(def extension
(.of js/cv.keymap
(clj->js [{:key (str "Alt-Enter")
:run #(eval-cell %)}
{:key "Mod-Enter"
:run #(eval-top-level %)}
{:key "Shift-Enter"
:run #(eval-at-cursor %)}
{:key "Escape" :run clear-eval}
{:key "ArrowLeft" :run clear-eval}
{:key "ArrowRight" :run clear-eval}])))
(def cm
(let [doc "(def n 7)
(defn r []
(map inc (range n)))"]
(js/cm.EditorView. #js {:doc doc
:extensions #js [js/cm.basicSetup, (js/lc.clojure), (.highest js/cs.Prec extension)]
:parent (js/document.querySelector "#app")})))
(set! (.-cm_instance js/globalThis) cm)
<html>
<head>
<script async src="https://ga.jspm.io/npm:es-module-shims@1.6.1/dist/es-module-shims.js"></script>
<script src="https://cdn.jsdelivr.net/npm/scittle@0.5.14/dist/scittle.js"></script>
<script>scittle.core.disable_auto_eval();</script>
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/scittle@0.5.14/dist/scittle.reagent.js"> </script>
<script src="https://cdn.jsdelivr.net/npm/scittle@0.5.14/dist/scittle.re-frame.js"> </script>
<script type="importmap">
{
"imports": {
"codemirror": "https://cdn.jsdelivr.net/npm/codemirror/dist/index.js",
"@codemirror/commands": "https://cdn.jsdelivr.net/npm/@codemirror/commands/dist/index.js",
"@codemirror/search": "https://cdn.jsdelivr.net/npm/@codemirror/search/dist/index.js",
"@codemirror/autocomplete": "https://cdn.jsdelivr.net/npm/@codemirror/autocomplete/dist/index.js",
"@codemirror/lint": "https://cdn.jsdelivr.net/npm/@codemirror/lint/dist/index.js",
"crelt": "https://cdn.jsdelivr.net/npm/crelt/index.es.js",
"@nextjournal/lang-clojure": "https://cdn.jsdelivr.net/npm/@nextjournal/lang-clojure/dist/index.js",
"@nextjournal/lezer-clojure": "https://cdn.jsdelivr.net/npm/@nextjournal/lezer-clojure/dist/index.es.js",
"@lezer/highlight": "https://cdn.jsdelivr.net/npm/@lezer/highlight/dist/index.js",
"@lezer/lr": "https://cdn.jsdelivr.net/npm/@lezer/lr/dist/index.js",
"@lezer/common": "https://cdn.jsdelivr.net/npm/@lezer/common/dist/index.js",
"@codemirror/language": "https://cdn.jsdelivr.net/npm/@codemirror/language/dist/index.js",
"@codemirror/state": "https://cdn.jsdelivr.net/npm/@codemirror/state/dist/index.js",
"@codemirror/view": "https://cdn.jsdelivr.net/npm/@codemirror/view/dist/index.js",
"style-mod": "https://cdn.jsdelivr.net/npm/style-mod/src/style-mod.js",
"w3c-keyname": "https://cdn.jsdelivr.net/npm/w3c-keyname/index.es.js"
}
}
</script>
<script type="module" type="application/javascript">
import * as cm from 'codemirror';
import * as lc from '@nextjournal/lang-clojure'
import * as cv from '@codemirror/view';
import * as cs from '@codemirror/state';
globalThis.cm = cm;
globalThis.lc = lc;
globalThis.cv = cv;
globalThis.cs = cs;
scittle.core.eval_script_tags();
</script>
<script type="application/x-scittle" src="cljs/codemirror.cljs"></script>
</head>
<body>
<div id="app">
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment