Skip to content

Commit 37a8dbf

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

File tree

7 files changed

+145
-0
lines changed

7 files changed

+145
-0
lines changed

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

+37
Original file line numberDiff line numberDiff line change
@@ -1777,3 +1777,40 @@ Returns::
17771777
* `:status` done
17781778
* `:cider/log-update-consumer` The consumer that was updated.
17791779

1780+
1781+
1782+
=== `cider.clj-reload/reload`
1783+
1784+
Reloads all changed files in dependency order,
1785+
using the io.github.tonsky/clj-reload library. It is bundled with cider-nrepl.
1786+
If that dependency is already in present your project and clj-reload.core/init has been invoked beforehand,
1787+
those configured directories will be honored.
1788+
1789+
Required parameters::
1790+
{blank}
1791+
1792+
Optional parameters::
1793+
{blank}
1794+
1795+
Returns::
1796+
* `:error` A sequence of all causes of the thrown exception when ``status`` is ``:error``.
1797+
* `:progress` Description of current namespace being unloaded/loaded.
1798+
* `:status` ``:ok`` if reloading was successful; otherwise ``:error``.
1799+
1800+
1801+
1802+
=== `cider.clj-reload/reload-all`
1803+
1804+
Reloads all files in dependency order.
1805+
1806+
Required parameters::
1807+
{blank}
1808+
1809+
Optional parameters::
1810+
{blank}
1811+
1812+
Returns::
1813+
* `:error` A sequence of all causes of the thrown exception when ``status`` is ``:error``.
1814+
* `:reloading` Description of current namespace being unloaded/loaded.
1815+
* `:status` ``:ok`` if reloading was successful; otherwise ``:error``.
1816+

doc/modules/ROOT/pages/usage.adoc

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ under `:repl-options`.
4747
cider.nrepl/wrap-spec
4848
cider.nrepl/wrap-profile
4949
cider.nrepl/wrap-refresh
50+
cider.nrepl/wrap-reload
5051
cider.nrepl/wrap-resource
5152
cider.nrepl/wrap-stacktrace
5253
cider.nrepl/wrap-test
@@ -161,6 +162,7 @@ That's how CIDER's nREPL handler is created:
161162
cider.nrepl/wrap-slurp
162163
cider.nrepl/wrap-profile
163164
cider.nrepl/wrap-refresh
165+
cider.nrepl/wrap-reload
164166
cider.nrepl/wrap-resource
165167
cider.nrepl/wrap-spec
166168
cider.nrepl/wrap-stacktrace

project.clj

+2
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"]]
@@ -157,6 +158,7 @@
157158
cider.nrepl/wrap-out
158159
cider.nrepl/wrap-profile
159160
cider.nrepl/wrap-refresh
161+
cider.nrepl/wrap-reload
160162
cider.nrepl/wrap-resource
161163
cider.nrepl/wrap-slurp
162164
cider.nrepl/wrap-spec

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-reload 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.clj

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
cider.nrepl/wrap-slurp
2424
cider.nrepl/wrap-profile
2525
cider.nrepl/wrap-refresh
26+
cider.nrepl/wrap-reload
2627
cider.nrepl/wrap-resource
2728
cider.nrepl/wrap-spec
2829
cider.nrepl/wrap-stacktrace

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)