Skip to content

Commit a86f61a

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

File tree

5 files changed

+155
-1
lines changed

5 files changed

+155
-1
lines changed

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,6 +1092,53 @@ Returns::
10921092
{blank}
10931093

10941094

1095+
1096+
=== `reload`
1097+
1098+
Reloads all changed files in dependency order, using clj-reload.
1099+
1100+
Required parameters::
1101+
{blank}
1102+
1103+
Optional parameters::
1104+
* `:nrepl.middleware.print/buffer-size` The size of the buffer to use when streaming results. Defaults to 1024.
1105+
* `:nrepl.middleware.print/keys` A seq of the keys in the response whose values should be printed.
1106+
* `:nrepl.middleware.print/options` A map of options to pass to the printing function. Defaults to ``nil``.
1107+
* `: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].
1108+
* `:nrepl.middleware.print/quota` A hard limit on the number of bytes printed for each value.
1109+
* `:nrepl.middleware.print/stream?` If logical true, the result of printing each value will be streamed to the client over one or more messages.
1110+
1111+
1112+
Returns::
1113+
* `:error` A sequence of all causes of the thrown exception when ``status`` is ``:error``.
1114+
* `:progress` Description of current namespace being unloaded/loaded.
1115+
* `:status` ``:ok`` if reloading was successful; otherwise ``:error``.
1116+
1117+
1118+
1119+
=== `reload-all`
1120+
1121+
Reloads all files in dependency order, using clj-reload.
1122+
1123+
Required parameters::
1124+
{blank}
1125+
1126+
Optional parameters::
1127+
* `:nrepl.middleware.print/buffer-size` The size of the buffer to use when streaming results. Defaults to 1024.
1128+
* `:nrepl.middleware.print/keys` A seq of the keys in the response whose values should be printed.
1129+
* `:nrepl.middleware.print/options` A map of options to pass to the printing function. Defaults to ``nil``.
1130+
* `: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].
1131+
* `:nrepl.middleware.print/quota` A hard limit on the number of bytes printed for each value.
1132+
* `:nrepl.middleware.print/stream?` If logical true, the result of printing each value will be streamed to the client over one or more messages.
1133+
1134+
1135+
Returns::
1136+
* `:error` A sequence of all causes of the thrown exception when ``status`` is ``:error``.
1137+
* `:progress` Description of current namespace being unloaded/loaded.
1138+
* `:status` ``:ok`` if reloading was successful; otherwise ``:error``.
1139+
1140+
1141+
10951142
=== `resource`
10961143

10971144
Obtain the path to a resource.

project.clj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@
119119
commons-codec
120120
com.google.code.findbugs/jsr305]]
121121
[cider/piggieback "0.5.3"]
122-
[nubank/matcher-combinators "3.8.8"]]}
122+
[nubank/matcher-combinators "3.8.8"]
123+
[io.github.tonsky/clj-reload "0.2.0"]]}
123124

124125
;; Running the tests with enrich-classpath doing its thing isn't compatible with `lein test`
125126
;; (because there's no such thing as "run `lein test` using this specific classpath"),

src/cider/nrepl.clj

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,20 @@ 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 {"reload"
639+
{:doc "Reloads all changed files in dependency order."
640+
:returns {"progress" "Description of current namespace being unloaded/loaded."
641+
"status" "`:ok` if reloading was successful; otherwise `:error`."
642+
"error" "A sequence of all causes of the thrown exception when `status` is `:error`."}}
643+
"reload-all"
644+
{:doc "Reloads all files in dependency order."
645+
:returns {"reloading" "Description of current namespace being unloaded/loaded."
646+
"status" "`:ok` if reloading was successful; otherwise `:error`."
647+
"error" "A sequence of all causes of the thrown exception when `status` is `:error`."}}}})
648+
635649
(def-wrapper wrap-resource cider.nrepl.middleware.resource/handle-resource
636650
{:doc "Middleware that provides the path to resource."
637651
:handles {"resource"

src/cider/nrepl/middleware/reload.clj

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
(ns cider.nrepl.middleware.reload
2+
(:require
3+
[clojure.main :refer [repl-caught]]
4+
[clojure.string :as str]
5+
[haystack.analyzer :as stacktrace.analyzer]
6+
[nrepl.middleware.interruptible-eval :refer [*msg*]]
7+
[nrepl.middleware.print :as print]
8+
[nrepl.misc :refer [response-for]]
9+
[nrepl.transport :as transport]
10+
[orchard.misc :as misc]))
11+
12+
(defn- user-reload
13+
"clj-reload.core/reload from the user project.
14+
Must be configured via clj-reload.core/init before being called."
15+
[]
16+
(some-> (symbol "clj-reload.core" "reload")
17+
resolve))
18+
19+
(defn respond
20+
[{:keys [transport] :as msg} response]
21+
(transport/send transport (response-for msg response)))
22+
23+
(defn- refresh-reply
24+
[{:keys [::print/print-fn transport session id] :as msg}]
25+
(let [{:keys [exec]} (meta session)]
26+
(exec id
27+
#(try
28+
(let [reload (user-reload)]
29+
(when-not reload
30+
(throw (ex-info "clj-reload.core/reload not found" {})))
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+
:error (stacktrace.analyzer/analyze error print-fn)})
38+
(binding [*msg* msg
39+
*err* (print/replying-PrintWriter :err msg {})]
40+
(repl-caught error))))
41+
42+
#(respond msg {:status :done}))))
43+
44+
(defn handle-reload [handler msg]
45+
(case (:op msg)
46+
"reload" (refresh-reply msg)
47+
"reload-all" (refresh-reply (assoc msg :all true))
48+
(handler msg)))
49+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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-no-user-reload-test
24+
(testing "reload op fails if clj-reload.core/reload is not found"
25+
(with-redefs [resolve (constantly nil)]
26+
(let [response (session/message {:op "reload"})]
27+
(is (= "clj-reload.core/reload not found" (-> response :error first :message)))
28+
(is (= #{"done" "error"} (:status response)))))))
29+
30+
(deftest reload-op-test
31+
(testing "reload op works"
32+
(let [response (session/message {:op "reload"})]
33+
;; There is nothing to reload since the files did not change,
34+
;; but the message does come from clj-reload.core/reload.
35+
(is (= "Nothing to reload" (:progress response)))
36+
(is (= #{"done" "ok"} (:status response))))))
37+
38+
(deftest reload-all-op-test
39+
(testing "reload-all op works"
40+
(let [response (session/message {:op "reload-all"})]
41+
(is (seq (:progress response)))
42+
(is (= #{"done" "ok"} (:status response))))))
43+

0 commit comments

Comments
 (0)