Last active
April 16, 2023 11:40
-
-
Save bobbicodes/cc36b036aab209245ab8e5e17ede4bf1 to your computer and use it in GitHub Desktop.
Inline eval with Codemirror 6 and scittle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<script async src="https://ga.jspm.io/npm:[email protected]/dist/es-module-shims.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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/[email protected]/dist/scittle.reagent.js"> </script> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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