mw-scittle/resources/public/cljs/codemirror.cljs
2023-04-15 15:10:09 -07:00

125 lines
4 KiB
Clojure

(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))))
(form-at-cursor (reverse (take 18 "(map inc (range 8)")))
(defn eval-at-cursor [viewer]
(let [cursor-pos (some-> cm .-state .-selection .-main .-head)
code (first (str/split (str (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 eval-top-level [viewer]
(let [region (str "(do " (.-doc (.-state viewer)) " )")
region (if (nil? region) nil (eval-string region))]
(if (nil? region) nil (reset! last-result region)))
true)
(defn eval-cell [viewer]
(reset! last-result (eval-string (str "(do " (.-doc (.-state viewer)) " )")))
true)
(defn clear-eval []
(when (not= "" @last-result)
(reset! last-result "")
(let [code (-> cm
(some-> .-state .-doc str)
str
(str/split #" => ")
first
(str @eval-tail))
cursor-pos (some-> cm .-state .-selection .-main .-head)]
(update-editor! code (min cursor-pos (count code))))))
(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 "(map inc (range 8))"]
(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)
(let [code "(map inc (range 8)"
cursor-pos 18
s '(\space \c \n \i \space \p \a \m \()
open-parens 1
closed-parens 1]
[(rest s)
(inc open-parens) closed-parens]
(drop (count s) code)
)
;; god dammit this doesn't work and I have no idea why!
;; I stepped through it (above) and it works -
;; output is `(\( \r \a \n \g \e \space \8 \))`
;; but in the loop (below), it gives the empty list.
;; figure this out tomorrow
(let [code "(map inc (range 8)"
cursor-pos 18]
(loop [s (reverse (take cursor-pos code))
open-parens 0
closed-parens 0]
(cond
(= open-parens closed-parens)
(drop (count s) code)
(= \) (first s)) (recur (rest s) open-parens (inc closed-parens))
(= \( (first s)) (recur (rest s) (inc open-parens) closed-parens)
:else (recur (rest s) open-parens closed-parens))
))