Skip to content

Commit a42b6dd

Browse files
committed
Support clj-reload workflow
Fix #849
1 parent 3824d72 commit a42b6dd

File tree

5 files changed

+141
-68
lines changed

5 files changed

+141
-68
lines changed

doc/modules/ROOT/pages/nrepl-api/ops.adoc

+37-68
Original file line numberDiff line numberDiff line change
@@ -1024,74 +1024,6 @@ Returns::
10241024

10251025

10261026

1027-
=== `refresh`
1028-
1029-
Reloads all changed files in dependency order.
1030-
1031-
Required parameters::
1032-
{blank}
1033-
1034-
Optional parameters::
1035-
* `:after` The namespace-qualified name of a zero-arity function to call after reloading.
1036-
* `:before` The namespace-qualified name of a zero-arity function to call before reloading.
1037-
* `:dirs` List of directories to scan. If no directories given, defaults to all directories on the classpath.
1038-
* `:nrepl.middleware.print/buffer-size` The size of the buffer to use when streaming results. Defaults to 1024.
1039-
* `:nrepl.middleware.print/keys` A seq of the keys in the response whose values should be printed.
1040-
* `:nrepl.middleware.print/options` A map of options to pass to the printing function. Defaults to ``nil``.
1041-
* `:nrepl.middleware.print/print` A fully-qualified symbol naming a var whose function to use for printing. Must point to a function with signature [value writer options].
1042-
* `:nrepl.middleware.print/quota` A hard limit on the number of bytes printed for each value.
1043-
* `:nrepl.middleware.print/stream?` If logical true, the result of printing each value will be streamed to the client over one or more messages.
1044-
1045-
1046-
Returns::
1047-
* `:error` A sequence of all causes of the thrown exception when ``status`` is ``:error``.
1048-
* `:error-ns` The namespace that caused reloading to fail when ``status`` is ``:error``.
1049-
* `:reloading` List of namespaces that will be reloaded.
1050-
* `:status` ``:ok`` if reloading was successful; otherwise ``:error``.
1051-
1052-
1053-
1054-
=== `refresh-all`
1055-
1056-
Reloads all files in dependency order.
1057-
1058-
Required parameters::
1059-
{blank}
1060-
1061-
Optional parameters::
1062-
* `:after` The namespace-qualified name of a zero-arity function to call after reloading.
1063-
* `:before` The namespace-qualified name of a zero-arity function to call before reloading.
1064-
* `:dirs` List of directories to scan. If no directories given, defaults to all directories on the classpath.
1065-
* `:nrepl.middleware.print/buffer-size` The size of the buffer to use when streaming results. Defaults to 1024.
1066-
* `:nrepl.middleware.print/keys` A seq of the keys in the response whose values should be printed.
1067-
* `:nrepl.middleware.print/options` A map of options to pass to the printing function. Defaults to ``nil``.
1068-
* `:nrepl.middleware.print/print` A fully-qualified symbol naming a var whose function to use for printing. Must point to a function with signature [value writer options].
1069-
* `:nrepl.middleware.print/quota` A hard limit on the number of bytes printed for each value.
1070-
* `:nrepl.middleware.print/stream?` If logical true, the result of printing each value will be streamed to the client over one or more messages.
1071-
1072-
1073-
Returns::
1074-
* `:error` A sequence of all causes of the thrown exception when ``status`` is ``:error``.
1075-
* `:error-ns` The namespace that caused reloading to fail when ``status`` is ``:error``.
1076-
* `:reloading` List of namespaces that will be reloaded.
1077-
* `:status` ``:ok`` if reloading was successful; otherwise ``:error``.
1078-
1079-
1080-
1081-
=== `refresh-clear`
1082-
1083-
Clears the state of the refresh middleware. This can help recover from a failed load or a circular dependency error.
1084-
1085-
Required parameters::
1086-
{blank}
1087-
1088-
Optional parameters::
1089-
{blank}
1090-
1091-
Returns::
1092-
{blank}
1093-
1094-
10951027
=== `resource`
10961028

10971029
Obtain the path to a resource.
@@ -1777,3 +1709,40 @@ Returns::
17771709
* `:status` done
17781710
* `:cider/log-update-consumer` The consumer that was updated.
17791711

1712+
1713+
1714+
=== `cider.clj-reload/reload`
1715+
1716+
Reloads all changed files in dependency order,
1717+
using the io.github.tonsky/clj-reload library. It is bundled with cider-nrepl.
1718+
If that dependency is already in present your project and clj-reload.core/init has been invoked beforehand,
1719+
those configured directories will be honored.
1720+
1721+
Required parameters::
1722+
{blank}
1723+
1724+
Optional parameters::
1725+
{blank}
1726+
1727+
Returns::
1728+
* `:error` A sequence of all causes of the thrown exception when ``status`` is ``:error``.
1729+
* `:progress` Description of current namespace being unloaded/loaded.
1730+
* `:status` ``:ok`` if reloading was successful; otherwise ``:error``.
1731+
1732+
1733+
1734+
=== `cider.clj-reload/reload-all`
1735+
1736+
Reloads all files in dependency order.
1737+
1738+
Required parameters::
1739+
{blank}
1740+
1741+
Optional parameters::
1742+
{blank}
1743+
1744+
Returns::
1745+
* `:error` A sequence of all causes of the thrown exception when ``status`` is ``:error``.
1746+
* `:reloading` Description of current namespace being unloaded/loaded.
1747+
* `:status` ``:ok`` if reloading was successful; otherwise ``:error``.
1748+

project.clj

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
~(with-meta '[org.clojure/tools.namespace "1.3.0"]
2020
;; :cognitest uses tools.namespace, so we cannot inline it while running tests.
2121
{:inline-dep (not= "true" (System/getenv "SKIP_INLINING_TEST_DEPS"))})
22+
^:inline-dep [io.github.tonsky/clj-reload "0.2.0"]
2223
^:inline-dep [org.clojure/tools.trace "0.7.11"]
2324
^:inline-dep [org.clojure/tools.reader "1.3.6"]
2425
[mx.cider/logjam "0.2.0"]]

src/cider/nrepl.clj

+17
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,23 @@ if applicable, and re-render the updated value."
632632
"refresh-clear"
633633
{:doc "Clears the state of the refresh middleware. This can help recover from a failed load or a circular dependency error."}}})
634634

635+
(def-wrapper wrap-refresh cider.nrepl.middleware.reload/handle-reload
636+
{:doc "Reload middleware."
637+
:requires #{"clone" #'wrap-print}
638+
:handles {"cider.clj-reload/reload"
639+
{:doc "Reloads all changed files in dependency order,
640+
using the io.github.tonsky/clj-reload library. It is bundled with cider-nrepl.
641+
If that dependency is already in present your project and clj-reload.core/init has been invoked beforehand,
642+
those configured directories will be honored."
643+
:returns {"progress" "Description of current namespace being unloaded/loaded."
644+
"status" "`:ok` if reloading was successful; otherwise `:error`."
645+
"error" "A sequence of all causes of the thrown exception when `status` is `:error`."}}
646+
"cider.clj-reload/reload-all"
647+
{:doc "Reloads all files in dependency order."
648+
:returns {"reloading" "Description of current namespace being unloaded/loaded."
649+
"status" "`:ok` if reloading was successful; otherwise `:error`."
650+
"error" "A sequence of all causes of the thrown exception when `status` is `:error`."}}}})
651+
635652
(def-wrapper wrap-resource cider.nrepl.middleware.resource/handle-resource
636653
{:doc "Middleware that provides the path to resource."
637654
:handles {"resource"

src/cider/nrepl/middleware/reload.clj

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
(ns cider.nrepl.middleware.reload
2+
(:require
3+
[clj-reload.core :as reload]
4+
[clojure.main :refer [repl-caught]]
5+
[clojure.string :as str]
6+
[haystack.analyzer :as analyzer]
7+
[nrepl.middleware.interruptible-eval :refer [*msg*]]
8+
[nrepl.middleware.print :as print]
9+
[nrepl.misc :refer [response-for]]
10+
[nrepl.transport :as transport]
11+
[orchard.misc :as misc]))
12+
13+
(defn- user-reload
14+
"clj-reload.core/reload from the user project.
15+
Must be configured via clj-reload.core/init before being called."
16+
[]
17+
(some-> (symbol "clj-reload.core" "reload") ;; Don't use mrandorsenized version
18+
resolve))
19+
20+
(defn respond
21+
[{:keys [transport] :as msg} response]
22+
(transport/send transport (response-for msg response)))
23+
24+
(defn- refresh-reply
25+
[{:keys [::print/print-fn transport session id] :as msg}]
26+
(let [{:keys [exec]} (meta session)]
27+
(exec id
28+
(fn []
29+
(try
30+
(let [reload (or (user-reload) reload/reload)]
31+
(reload (cond-> {:log-fn (fn [& args]
32+
(respond msg {:progress (str/join " " args)}))}
33+
(:all msg) (assoc :only :all)))
34+
(respond msg {:status :ok}))
35+
(catch Throwable error
36+
(respond msg {:status :error
37+
;; TODO assoc :file, :line info if available
38+
:error (analyzer/analyze error print-fn)})
39+
(binding [*msg* msg
40+
*err* (print/replying-PrintWriter :err msg {})]
41+
(repl-caught error)))))
42+
43+
(fn [] (respond msg {:status :done})))))
44+
45+
(defn handle-reload [handler msg]
46+
(case (:op msg)
47+
"cider.clj-reload/reload" (refresh-reply msg)
48+
"cider.clj-reload/reload-all" (refresh-reply (assoc msg :all true))
49+
(handler msg)))
50+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
(ns cider.nrepl.middleware.reload-test
2+
(:require
3+
[cider.nrepl.middleware.reload :as rl]
4+
[cider.nrepl.test-session :as session]
5+
[clj-reload.core :as reload]
6+
[clojure.test :refer :all]))
7+
8+
(use-fixtures :each session/session-fixture)
9+
10+
(def ^:private dirs-to-reload
11+
;; Limit the scope of what we reload, because (for example) reloading the
12+
;; cider.nrepl.middleware.test-session ns causes *session* in that ns to be
13+
;; unloaded, which breaks session-fixture, and hence all of the below tests.
14+
["test/clj/cider/nrepl/middleware/util"])
15+
16+
(reload/init {:dirs dirs-to-reload})
17+
18+
(deftest user-reload
19+
(testing "returns nil if clojure.tools.namespace isn't loaded"
20+
(with-redefs [resolve (constantly nil)]
21+
(is (nil? (#'rl/user-reload))))))
22+
23+
(deftest reload-op-test
24+
(testing "reload op works"
25+
(let [response (session/message {:op "cider.clj-reload/reload"})]
26+
;; There is nothing to reload since the files did not change,
27+
;; but the message does come from clj-reload.core/reload.
28+
(is (= "Nothing to reload" (:progress response)))
29+
(is (= #{"done" "ok"} (:status response))))))
30+
31+
(deftest reload-all-op-test
32+
(testing "reload-all op works"
33+
(let [response (session/message {:op "cider.clj-reload/reload-all"})]
34+
(is (seq (:progress response)))
35+
(is (= #{"done" "ok"} (:status response))))))
36+

0 commit comments

Comments
 (0)