diff --git a/demo/deps.edn b/demo/deps.edn index 1d56ebd..30833aa 100644 --- a/demo/deps.edn +++ b/demo/deps.edn @@ -5,6 +5,10 @@ org.pinkgorilla/webly {:mvn/version "0.7.693"} ; ui modules for demo notebooks: org.pinkgorilla/ui-highcharts {:mvn/version "0.0.29"} + + ; run by default in docs-services + nrepl/nrepl {:mvn/version "1.2.0"} + cider/cider-nrepl {:mvn/version "0.49.1"} } :aliases diff --git a/demo/resources/ext/demo.edn b/demo/resources/ext/demo.edn index 9b79bc6..e1de441 100644 --- a/demo/resources/ext/demo.edn +++ b/demo/resources/ext/demo.edn @@ -1,10 +1,12 @@ {:name "demo-reval" :lazy true :depends-on #{:reval-ui} + :cljs-namespace [demo.page.overlay] + :cljs-ns-bindings {'demo.page.overlay {'overlay-page demo.page.overlay/overlay-page}} :cljs-routes {"" reval.page.viewer/viewer-page ; "devtools/repl" :repl - + "frepl" demo.page.overlay/overlay-page } ; diff --git a/demo/resources/reval/demo-services.edn b/demo/resources/reval/demo-services.edn index dd38677..5a46774 100644 --- a/demo/resources/reval/demo-services.edn +++ b/demo/resources/reval/demo-services.edn @@ -24,6 +24,15 @@ :stop (webly.app.app/stop-webly this)} + :nrepl {:start (nrepl.server/start-server + :bind "0.0.0.0" + :port 9100 + :middleware cider.nrepl.middleware/cider-middleware + :handler cider.nrepl/cider-nrepl-handler) + :stop (.close this)} + + + ; }} diff --git a/demo/src/demo/frepl.clj b/demo/src/demo/frepl.clj new file mode 100644 index 0000000..c1fb695 --- /dev/null +++ b/demo/src/demo/frepl.clj @@ -0,0 +1,131 @@ +(ns demo.frepl + (:require + [clojure.string :as string] + [modular.system] + [reval.document.notebook] + [reval.kernel.protocol :refer [kernel-eval]] + [promesa.core :as p])) + + ;; first lets get the running reval instance +(def s (modular.system/system :reval)) + +(def code + (reval.document.notebook/load-src "notebook.study.movies" :clj)) + +code + +(p/await (kernel-eval {:kernel :clj + :ns "notebook.study.movies" + :code code})) + + +(p/await (kernel-eval {:kernel :clj + :ns "user" + :code code})) + + + (p/await (kernel-eval {:code "(println 3) (def x 777) (defn f [] 99) (+ 3 4)" + :kernel :clj + :ns "bongotrott" + :id 1})) + +(defn add-ns [ns] + (str "(in-ns '" ns ")\r\n")) + +(def code-ns (add-ns "notebook.study.movies")) + + +code-ns +(p/await (kernel-eval {:kernel :clj + :ns "user" + :code code-ns})) + + + + +(load-string code) + +(def code2 + (let [ns "notebook.study.movies" + code-ns (if (and ns (not (string/blank? ns))) + (str "(in-ns '" ns " ) ") + "nil") + code-with-ns (str code-ns " [ " code " (str *ns*) ]")] + code-with-ns)) + +code2 + +(load-string code2) + + +(def code3 + (let [ns "notebook.study.movies" + code-ns (if (and ns (not (string/blank? ns))) + (str "(in-ns '" ns " ) ") + "nil") + code-with-ns (str code-ns code )] + code-with-ns)) + +code3 + +(load-string code3) + +(load-string "(in-ns 'xxx) (def a 1)") + + +(load-string "(in-ns 'xxx) *ns*") + +(load-string "(ns abc)(def x 777) (defn f [] 99) (+ 3 4)") + +(load-string "(in-ns 'abc)(def x 777) (defn f [] 99) (+ 3 4)") + +(load-string "(in-ns 'ddd)(def x 777) (defn f [] 99) (+ 3 4)") + +(load-string "(in-ns 'ddd)(def x 777)") + +(load-string "(in-ns 'ddd)(def x 777) (defn y [] 4)") + +(load-string "(ns xxx) *ns*") + + +(load-string "*ns*") + +(load-string "(ns yyy) *ns*") + + + +(p/await (kernel-eval {:kernel :clj + :ns "notebook.study.movies" + :code "*ns*"})) + +(p/await (kernel-eval {:kernel :clj + :ns "notebook.study.movies" + :code code-with-ns})) + + +(def code2 "(ns bongo) (def x 34) (+ x 4) *ns*") + + +(p/await (kernel-eval {:kernel :clj + ;:ns "user" + :code code2})) + + +(p/await (kernel-eval {:kernel :clj + :ns "bongo" + :code "*ns*"})) + + +(load-string "*ns*") + +(load-string "(ns bongo) (def y 36)") + +(load-string "(in-ns 'bongo) y") + + +; (in-ns 'yippie2) +(load-string "(ns yippie7 (:require [clojure.pprint :refer [print-table]])) + (def movies [{:a 1} {:a 2}]) + (print-table movies) + *ns* + ") diff --git a/demo/src/demo/notebook.clj b/demo/src/demo/notebook.clj index 215ab43..3669a95 100644 --- a/demo/src/demo/notebook.clj +++ b/demo/src/demo/notebook.clj @@ -1,36 +1,47 @@ (ns demo.notebook (:require + [modular.system] [reval.document.notebook :refer [eval-notebook load-notebook]] ;[scratchpad.core :refer [show! show-as clear!]] ; [demo.init] ; side effects )) + +;; first lets get the running reval instance +(def s (modular.system/system :reval)) + + ;; checkout one notebook.. -(-> (eval-notebook "notebook.study.movies") +(eval-notebook s "notebook.study.movies") + + +(-> (eval-notebook s "notebook.study.movies") meta) -(-> (eval-notebook "user.notebook.movies") + + +(-> (eval-notebook s "user.notebook.movies") :content count) -(->> (eval-notebook "user.notebook.movies") +(->> (eval-notebook s "user.notebook.movies") (show-as :p/notebook)) -(load-notebook "user.notebook.movies") +(load-notebook s "user.notebook.movies") -(-> (load-notebook "user.bongo.xyr") +(-> (load-notebook s "user.bongo.xyr") println) -(-> (load-notebook "") +(-> (load-notebook s "") println) -(-> (load-notebook nil) +(-> (load-notebook s nil) println) ;; eval an notebook that does not exist: -(->> (eval-notebook "user.notebook.image") ; this does not exist !! +(->> (eval-notebook s "user.notebook.image") ; this does not exist !! (show-as :p/notebook)) ;; we will get a notebook that contains an error. ;; eval a list of notebooks @@ -42,7 +53,7 @@ ;; show notebook in scratchpad -(->> (eval-notebook "user.notebook.movies") +(->> (eval-notebook s "user.notebook.movies") show!) diff --git a/demo/src/demo/page/overlay.cljs b/demo/src/demo/page/overlay.cljs new file mode 100644 index 0000000..183a890 --- /dev/null +++ b/demo/src/demo/page/overlay.cljs @@ -0,0 +1,31 @@ +(ns demo.page.overlay + (:require + [reval.frepl :refer [show-floating-repl show-floating-repl-namespace]])) + + +(defn overlay-page [{:keys [route-params query-params handler] :as route}] + [:div + [:h1 "I am a normal reagent page. But I can add a floating repl."] + [:a {:on-click #(show-floating-repl {:code "(+ 1 2 3)"})} + [:p "show code (floating)"]] + [:a {:on-click #(show-floating-repl {:code "(+ 1 2 3)" + :render-fn 'reval.viz.render-fn/reagent + :data ^{:hiccup true} + [:span {:style {:color "blue"}} "25"]} + )} + [:p "show code (eval result)"]] + + [:a {:on-click #(show-floating-repl-namespace {:ns "notebook.study.movies" + :kernel :clj + })} + [:p "show code (namespace)"]] + + + [:a {:on-click #(show-floating-repl-namespace {:ns "demo.notebook.highcharts" + :kernel :clj})} + [:p "show code (highcharts)"]] + + + + ]) + diff --git a/demo/src/notebook/study/movies.clj b/demo/src/notebook/study/movies.clj index f9866f6..67ce6f5 100644 --- a/demo/src/notebook/study/movies.clj +++ b/demo/src/notebook/study/movies.clj @@ -13,6 +13,16 @@ {:name "Matrix" :year 1999 :studio "Warner Bros" :producer "Joel Silver"} {:name "Dr. Strange" :year 2016 :studio "Marvel Studios" :producer "Scott Derrickson"}]) +*ns* + (print-table movies) +(def more-movies + (conj movies {:name "Harry Potter" :year 2020}) + ) + +(count more-movies) + + + diff --git a/reval-ui/deps.edn b/reval-ui/deps.edn index 6252215..d4f10cb 100644 --- a/reval-ui/deps.edn +++ b/reval-ui/deps.edn @@ -1,14 +1,15 @@ {:paths ["src" "resources"] :deps - {org.pinkgorilla/reval {:local/root "../reval" :deps/manifest :deps} + {nano-id/nano-id {:mvn/version "1.1.0"} + org.pinkgorilla/reval {:local/root "../reval" :deps/manifest :deps} ; deps to ui extensions used in reval org.pinkgorilla/pinkie {:mvn/version "0.5.232"} ; error boundary - org.pinkgorilla/ui-repl {:mvn/version "0.1.105"} ; reagent - org.pinkgorilla/ui-input {:mvn/version "0.2.47"} ; spaces + org.pinkgorilla/ui-repl {:mvn/version "0.1.107"} ; reagent + org.pinkgorilla/ui-input {:mvn/version "0.2.48"} ; spaces, rnd, overlay org.pinkgorilla/ui-codemirror {:mvn/version "0.1.48"} org.pinkgorilla/ui-highlightjs {:mvn/version "0.1.19"} - org.pinkgorilla/clj-service {:mvn/version "0.3.18"}} + org.pinkgorilla/clj-service {:mvn/version "0.3.20"}} :aliases {; github ci MULTI-DEPS VERSION diff --git a/reval-ui/resources/ext/reval-ui.edn b/reval-ui/resources/ext/reval-ui.edn index 93e77ab..34a0471 100644 --- a/reval-ui/resources/ext/reval-ui.edn +++ b/reval-ui/resources/ext/reval-ui.edn @@ -1,7 +1,7 @@ {:name "reval-ui" ; build :lazy true - :depends-on #{:ui-codemirror :ui-repl} + :depends-on #{:ui-codemirror :ui-repl :ui-rnd} :cljs-namespace [reval.viz.data reval.viz.render-fn reval.type.cljs ; side-effects; load protocols for cljs and sci @@ -18,7 +18,9 @@ 'kernel-eval reval.kernel.protocol/kernel-eval} 'reval.page.viewer {'viewer-page reval.page.viewer/viewer-page} 'reval.page.repl {'repl-page reval.page.repl/repl-page} - 'reval.notebook-ui.editor {'block-for reval.notebook-ui.editor/block-for}} + 'reval.notebook-ui.editor {'block-for reval.notebook-ui.editor/block-for} + 'reval.frepl {'show-floating-repl reval.frepl/show-floating-repl + 'show-floating-repl-namespace reval.frepl/show-floating-repl-namespace}} ; run ;:api-routes {"rdocument/" {"ns" reval.document-handler/wrapped-get-ns-list diff --git a/reval-ui/src/reval/frepl.cljs b/reval-ui/src/reval/frepl.cljs new file mode 100644 index 0000000..2f45b24 --- /dev/null +++ b/reval-ui/src/reval/frepl.cljs @@ -0,0 +1,174 @@ +(ns reval.frepl + (:require + [taoensso.timbre :refer-macros [debugf info warn warnf error]] + [reagent.core :as r] + [promesa.core :as p] + [ui.overlay :refer [overlay-add overlay-remove]] + [ui.rnd :refer [rnd]] + [nano-id.core :refer [nano-id]] + [clojure.string :refer [blank?]] + ; codemirror + [ui.codemirror.api :as api] + [ui.codemirror.codemirror :refer [codemirror get-editor]] + ; kernel + [reval.kernel.protocol :refer [kernel-eval]] + [reval.kernel.clj-remote] ; side effects + [reval.viz.show :refer [show-data]] + [reval.helper.ui-helper :refer [text2]] + [reval.notebook-ui.clj-result :refer [evalerr]] + ; clj service + [goldly.service.core :refer [clj]])) + +;; codemirror + +(defn cm-get-code [editor-id] + (-> (get-editor editor-id) + (api/get-code))) + +(defn cm-set-code [editor-id code] + (let [c (get-editor editor-id)] + (api/set-code c code) + (api/focus c))) + +(def cm-opts {:lineWrapping false}) + +(defn cm-editor [editor-id] + [:<> + ;[theme/style-codemirror-fullscreen] ; fullscreen is not correct name, 100% width/height is better name. + ]) + +;; eval cljs + +(defn eval-code [id opts r-a] + (let [code (cm-get-code id) + opts (assoc opts :code code) + rp (kernel-eval opts)] ; :ns @cur-ns + (info "eval code: " opts) + (-> rp + (p/then (fn [er] + (info "eval code result: " (pr-str er)) + (reset! r-a er))) + (p/catch (fn [err] + (info "eval code error: " (pr-str err))))))) + +(defn show-data-extended [{:keys [_id _ns _src err err-sci out data render-fn] :as segment}] + (let [scode (:code segment)] + [:div.flex.flex-col + ;(pr-str segment) + ;(when scode [highlightjs scode]) + (when err + [evalerr err]) + #_(when err-sci + [evalerr-sci err-sci]) + (when (not (blank? out)) + [:div.bg-blue-200.max-w-full.overflow-x-auto + [text2 out]]) + (when render-fn + [:div.mt-1.mb-1 + [show-data render-fn data]])])) + +(defn floating-window [id {:keys [kernel ns render-fn data] + :or {kernel :clj}}] + (let [data-a (r/atom {:data data :render-fn render-fn}) + opts {:ns ns :kernel kernel} + show-data-a (r/atom false) + eval-to-result (fn [] + (let [rp (eval-code id opts data-a)] + (p/then rp (fn [res] + (reset! show-data-a true)))))] + (fn [id _ _] + (let [{:keys [data render-fn err out]} @data-a] + [:div {:style {:display "grid" + :grid-template-rows "34px 1fr" + :width "100%" + :height "100%"}} + [:style ".my-codemirror > .CodeMirror { + font-family: monospace; + height: 100%; + min-height: 100%; + max-height: 100%; + }"] + [:style ".toolbar-item { + padding: 1px; + cursor: pointer; + margin-right: 5px; + border-radius: 3px; + transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);} + .toolbar-item:hover { + background: rgb(228, 228, 228); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); + }"] + + ;; TOOLBAR MENU + [:div {:display "flex" + :width "100%" + :flexdirection "column" + :justifycontent "space-between" + :class "bg-gray-300"} + [:span.toolbar-item + {:on-click #(swap! show-data-a not)} + (if @show-data-a "code" "result")] + (when-not @show-data-a + [:button.bg-gray-400.m-1.p-1.toolbar-item {:on-click #(eval-to-result) + ;#(eval-code id opts data-a) + }"eval"]) + ;[:span.toolbar-item "menu"] + [:button + {:class "bg-gray-400 m-1 p-1 toolbar-item" + :style {;:margin-left "auto" ; align one flex child to the right + :float "right"} + :on-click #(overlay-remove id)} "x"]] + ;; CODEMIRROR + ;[cm-editor id] + [:div.my-codemirror.w-full.h-full {:style {:max-height "100%" + :max-width "100%" + :height "100%" + :width "100%" + :display (when @show-data-a "none")}} + [codemirror id cm-opts]] + ;; RESULT + [:div {:style {:overflow "hidden" + :box-shadow "0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23)" + :max-height "100%" + :max-width "100%" + :height "100%" + :width "100%" + :display (when-not @show-data-a "none")}} + (cond + (or (and data render-fn) + out + err) + ;[show-data render-fn data] + [show-data-extended @data-a] + render-fn [:div "no viz data"] + :else [:div "no render-fn"])]])))) + +(defn show-floating-repl [{:keys [kernel code ns data render-fn] + :or {kernel :clj + code ""} :as opts}] + (let [id (str (nano-id 5))] + (overlay-add id [rnd {:bounds "window" + :default {:width 200 + :height 400 + :x 50 + :y 60} + :style {:display "flex" + ;:alignItems "center" + :justifyContent "center" + :border "solid 2px #ddd" + :background "#f0f0f0"}} + [floating-window id opts]]) + (cm-set-code id code))) + +(defn show-floating-repl-namespace + [{:keys [kernel ns data render-fn] + :as opts}] + (info "loading namespace source: " kernel ns) + (-> (clj {:timeout 1000} 'reval.document.notebook/load-src ns kernel) + (p/then (fn [code] + (info "source loaded: " code) + (show-floating-repl {:kernel kernel + :code code + :ns ns}))) + (p/catch (fn [err] + (error "could not load ns: " ns kernel " error: " err))))) \ No newline at end of file diff --git a/reval-ui/src/reval/kernel/clj_remote.cljs b/reval-ui/src/reval/kernel/clj_remote.cljs index 6c0dfde..11c287a 100644 --- a/reval-ui/src/reval/kernel/clj_remote.cljs +++ b/reval-ui/src/reval/kernel/clj_remote.cljs @@ -9,7 +9,7 @@ (defonce cur-ns (r/atom "user")) (defn eval-clj [segment] - (let [segment (merge segment {:ns @cur-ns}) + (let [segment (merge {:ns @cur-ns} segment) _ (info "eval clj: " segment) rp (clj {:timeout 120000} 'reval.viz.eval/viz-eval-blocking segment)] (p/then rp (fn [r] diff --git a/reval/src/reval/kernel/clj_eval.clj b/reval/src/reval/kernel/clj_eval.clj index e8bcc5d..df6a371 100644 --- a/reval/src/reval/kernel/clj_eval.clj +++ b/reval/src/reval/kernel/clj_eval.clj @@ -49,30 +49,30 @@ [{:keys [id code ns] :or {id (guuid)}}] (let [code-ns (if (and ns (not (string/blank? ns))) - (str "(ns " ns " ) ") - "nil") + (str "(in-ns '" ns " ) ") + "") ; _ (error "code-to-set-ns: " code-ns) - code-with-ns (str code-ns " [ " code " (str *ns*) ]") - ; _ (error "full code: " code-with-ns) + ;code-with-ns (str code-ns " [ " code " (str *ns*) ]") + code-with-ns (str code-ns code) er-code (clj-eval-raw code-with-ns) ; _ (error "er-code: " er-code) ; [[nil] "notebook.study.movies"] {:keys [value]} er-code - ns-after (if (:err er-code) - ns ; on compile exception the ns does not change - (last value)) - values-new (drop-last value) - last-value (last values-new) + ;ns-after (if (:err er-code) + ; ns ; on compile exception the ns does not change + ; (last value)) + ;values-new (drop-last value) + ;last-value (last values-new) ;er-code (clj-eval-raw code) ;er-ns-after (clj-eval-raw "*ns*") ;ns-after (-> er-ns-after :value str) ; (str *ns*) + ;last-value value + ;new-ns (:value (clj-eval-raw "(str *ns*)")) r (merge er-code {:id id :code code - :ns ns-after} - {:value last-value - :code code})] - + :ns ns ; new-ns ;:ns ns-after + })] ;(info "eval-result: " r) r)) diff --git a/reval/test/reval/kernel/clj_eval_test.clj b/reval/test/reval/kernel/clj_eval_test.clj index 88b8ae8..2f5e3f3 100644 --- a/reval/test/reval/kernel/clj_eval_test.clj +++ b/reval/test/reval/kernel/clj_eval_test.clj @@ -1,18 +1,24 @@ (ns reval.kernel.clj-eval-test (:require [clojure.test :refer [deftest is]] - [reval.kernel.clj-eval :refer [clj-eval]])) + [promesa.core :as p] + [reval.kernel.clj-eval :refer [clj-eval]] + [reval.kernel.protocol :refer [kernel-eval]])) (deftest clj-eval-test - (let [er1 (clj-eval {:code "(println 3) (def x 777) (defn f [] 99) (+ 3 4)" - :ns "bongotrott" - :id 1}) - er2 (clj-eval {:code "x" - :ns "bongotrott" - :id 2}) - er3 (clj-eval {:code "(f)" - :ns "bongotrott" - :id 3})] + (let [er1 (p/await (kernel-eval {:code "(ns bongotrott) (println 3)(def x 777) (defn f [] 99) (+ 3 4)" + ;"(println 3) (def x 777) (defn f [] 99) (+ 3 4)" + :kernel :clj + :ns "bongotrott" + :id 1})) + er2 (p/await (kernel-eval {:code "x" + :kernel :clj + :ns "bongotrott" + :id 2})) + er3 (p/await (kernel-eval {:code "(f)" + :kernel :clj + :ns "bongotrott" + :id 3}))] ; check that eval result is correct (is (= (:value er1) 7)) (is (= (:out er1) "3\n"))