diff --git a/src/jolt/core.janet b/src/jolt/core.janet index 000cd84..ac2a4c7 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -1532,6 +1532,25 @@ (pr-render buf (in pair 1))) (buffer/push-string buf "}")) +(defn- name-of + "Extract a plain name string from a string, symbol struct, or a namespace/var + table (reading its :name) — never recurses into the cyclic ns structure." + [x] + (cond + (nil? x) nil + (string? x) x + (and (struct? x) (= :symbol (get x :jolt/type))) (x :name) + (or (struct? x) (table? x)) (name-of (get x :name)) + (string x))) + +(defn- var-display + "Render a Jolt var as #'ns/name. A var's :meta/:ns refs are cyclic, so this + reads only its :name and :ns name — printing the var's pairs would loop." + [v] + (let [nm (name-of (v :name)) + ns (name-of (v :ns))] + (if ns (string "#'" ns "/" nm) (string "#'" nm)))) + (set pr-render (fn [buf v] (cond @@ -1551,6 +1570,7 @@ (number? v) (buffer/push-string buf (fmt-number v)) (and (struct? v) (= :symbol (v :jolt/type))) (buffer/push-string buf (if (v :ns) (string (v :ns) "/" (v :name)) (v :name))) + (and (table? v) (= :jolt/var (get v :jolt/type))) (buffer/push-string buf (var-display v)) (core-sorted-map? v) (pr-render-pairs buf (sorted-map-entries v)) (core-sorted-set? v) (pr-render-seq buf (v :items) "#{" "}") (lazy-seq? v) (pr-render-seq buf (realize-for-iteration v) "(" ")") @@ -1580,6 +1600,7 @@ (keyword? v) (string ":" (string v)) (and (struct? v) (= :symbol (v :jolt/type))) (if (v :ns) (string (v :ns) "/" (v :name)) (v :name)) + (and (table? v) (= :jolt/var (get v :jolt/type))) (var-display v) (number? v) (fmt-number v) (= true v) "true" (= false v) "false" diff --git a/src/jolt/jolt/nrepl.clj b/src/jolt/jolt/nrepl.clj index 27f4f42..55bb4a6 100644 --- a/src/jolt/jolt/nrepl.clj +++ b/src/jolt/jolt/nrepl.clj @@ -90,16 +90,13 @@ ; regardless of the ambient ns. (defn- eval-in-ns [ns-str form] (in-ns (symbol ns-str)) - (let [result (try {:val (eval form) :ns (:name (the-ns))} + ; Bind the value before reading the ns: jolt evaluates map-literal values + ; right-to-left, so the result ns must be captured *after* eval runs any in-ns. + (let [result (try (let [v (eval form)] {:val v :ns (:name (the-ns))}) (catch Throwable e {:err e :ns (:name (the-ns))}))] (in-ns 'jolt.nrepl) result)) -; pr-str on a var loops forever on its cyclic ns refs, so render vars (def/defn -; results) ourselves as #'ns/name rather than printing them. -(defn- render-value [v ns-str] - (if (var? v) (str "#'" ns-str "/" (get v :name)) (pr-str v))) - (defn- eval-handler [server msg send!] ; current-ns is global ctx state shared by all fibers, so set the eval ns ; explicitly each time: requested :ns, else the session's last ns, else user. @@ -122,7 +119,7 @@ (flush-out) (swap! server assoc :eval-ns ns) (when err (throw err)) - (respond {"ns" ns "value" (render-value val ns)}) + (respond {"ns" ns "value" (pr-str val)}) (recur (next forms) ns)))) (janet/setdyn :out old-out) (respond {"status" ["done"]})) diff --git a/test/integration/nrepl-test.janet b/test/integration/nrepl-test.janet index 2b18f3e..fba05b8 100644 --- a/test/integration/nrepl-test.janet +++ b/test/integration/nrepl-test.janet @@ -64,6 +64,14 @@ (check "ns field reported" "(some #(get % \"ns\") (jolt.nrepl/client-eval c \"(+ 1 1)\" s))" "user") +# in-ns switches the session ns, and it persists to the next eval +(check "in-ns switches ns" + "(some #(get % \"ns\") (jolt.nrepl/client-eval c \"(in-ns (quote foo.bar))\" s))" "foo.bar") +(check "ns persists across evals" + "(some #(get % \"ns\") (jolt.nrepl/client-eval c \"(+ 2 2)\" s))" "foo.bar") +# explicit :ns on the message overrides +(ev "(jolt.nrepl/request c {\"op\" \"eval\" \"code\" \"(+ 1 1)\" \"session\" s \"ns\" \"user\"})") + # eval error -> eval-error status, and the connection keeps working afterward (check "eval error status" "(boolean (some #(let [st (get % \"status\")] (and (sequential? st) (some (fn [x] (= \"eval-error\" x)) st))) (jolt.nrepl/client-eval c \"(/ 1 :z)\" s)))" diff --git a/test/spec/strings-spec.janet b/test/spec/strings-spec.janet index e3739e1..e57b6e6 100644 --- a/test/spec/strings-spec.janet +++ b/test/spec/strings-spec.janet @@ -13,6 +13,11 @@ ["string? true" "true" "(string? \"x\")"] ["pr-str vector" "\"[1 2 3]\"" "(pr-str [1 2 3])"] ["pr-str quotes str" "\"\\\"hi\\\"\"" "(pr-str \"hi\")"] + # a var's :meta/:ns refs are cyclic — pr-str/str render it as #'ns/name + # rather than recursing into (and looping on) the var's fields. + ["pr-str of a var" "\"#'user/vv\"" "(pr-str (def vv 1))"] + ["str of a var" "\"#'user/ww\"" "(str (def ww 2))"] + ["pr-str of a defn" "\"#'user/gg\"" "(pr-str (defn gg [x] x))"] ["seq of string" "[\\a \\b]" "(seq \"ab\")"]) (defspec "clojure.string"