Skip to content

Commit d8fbd01

Browse files
authored
Support clj-reload workflow (#850)
Fixes #849
1 parent 20e646d commit d8fbd01

File tree

8 files changed

+190
-0
lines changed

8 files changed

+190
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ Let's also acknowledge some of the projects leveraged by cider-nrepl:
136136
* [clj-suitable][] - for ClojureScript code completion using runtime inspection
137137
* [tools.trace][] - for tracing
138138
* [tools.namespace][] - for namespace reloading
139+
* [clj-reload][] - for namespace reloading
139140
* [cljfmt][] - for code formatting
140141

141142
## License
@@ -149,6 +150,7 @@ Distributed under the Eclipse Public License, the same as Clojure.
149150
[clj-suitable]: https://github.com/clojure-emacs/clj-suitable
150151
[tools.trace]: https://github.com/clojure/tools.trace
151152
[tools.namespace]: https://github.com/clojure/tools.namespace
153+
[clj-reload]: https://github.com/tonsky/clj-reload
152154
[cljfmt]: https://github.com/weavejester/cljfmt
153155
[vim-replant]: https://github.com/SevereOverfl0w/vim-replant
154156
[vim-fireplace]: https://github.com/tpope/vim-fireplace

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1783,3 +1783,54 @@ Returns::
17831783
* `:status` done
17841784
* `:cider/log-update-consumer` The consumer that was updated.
17851785

1786+
1787+
1788+
=== `cider.clj-reload/reload`
1789+
1790+
Reloads all changed files in dependency order,
1791+
using the io.github.tonsky/clj-reload library. It is bundled with cider-nrepl.
1792+
If that dependency is already in present your project and clj-reload.core/init has been invoked beforehand,
1793+
those configured directories will be honored.
1794+
1795+
Required parameters::
1796+
{blank}
1797+
1798+
Optional parameters::
1799+
{blank}
1800+
1801+
Returns::
1802+
* `:error` A sequence of all causes of the thrown exception when ``status`` is ``:error``.
1803+
* `:progress` Description of current namespace being unloaded/loaded.
1804+
* `:status` ``:ok`` if reloading was successful; otherwise ``:error``.
1805+
1806+
1807+
1808+
=== `cider.clj-reload/reload-all`
1809+
1810+
Reloads all files in dependency order.
1811+
1812+
Required parameters::
1813+
{blank}
1814+
1815+
Optional parameters::
1816+
{blank}
1817+
1818+
Returns::
1819+
* `:error` A sequence of all causes of the thrown exception when ``status`` is ``:error``.
1820+
* `:reloading` Description of current namespace being unloaded/loaded.
1821+
* `:status` ``:ok`` if reloading was successful; otherwise ``:error``.
1822+
1823+
1824+
1825+
=== `cider.clj-reload/reload-clear`
1826+
1827+
Clears the state of clj-reload. This can help recover from a failed load or a circular dependency error.
1828+
1829+
Required parameters::
1830+
{blank}
1831+
1832+
Optional parameters::
1833+
{blank}
1834+
1835+
Returns::
1836+
{blank}

doc/modules/ROOT/pages/usage.adoc

Lines changed: 2 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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.4.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.3.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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,25 @@ if applicable, and re-render the updated value."
635635
"refresh-clear"
636636
{:doc "Clears the state of the refresh middleware. This can help recover from a failed load or a circular dependency error."}}})
637637

638+
(def-wrapper wrap-reload cider.nrepl.middleware.reload/handle-reload
639+
{:doc "Reload middleware."
640+
:requires #{"clone" #'wrap-print}
641+
:handles {"cider.clj-reload/reload"
642+
{:doc "Reloads all changed files in dependency order,
643+
using the io.github.tonsky/clj-reload library. It is bundled with cider-nrepl.
644+
If that dependency is already in present your project and clj-reload.core/init has been invoked beforehand,
645+
those configured directories will be honored."
646+
:returns {"progress" "Description of current namespace being unloaded/loaded."
647+
"status" "`:ok` if reloading was successful; otherwise `:error`."
648+
"error" "A sequence of all causes of the thrown exception when `status` is `:error`."}}
649+
"cider.clj-reload/reload-all"
650+
{:doc "Reloads all files in dependency order."
651+
:returns {"reloading" "Description of current namespace being unloaded/loaded."
652+
"status" "`:ok` if reloading was successful; otherwise `:error`."
653+
"error" "A sequence of all causes of the thrown exception when `status` is `:error`."}}
654+
"cider.clj-reload/reload-clear"
655+
{:doc "Clears the state of clj-reload. This can help recover from a failed load or a circular dependency error."}}})
656+
638657
(def-wrapper wrap-resource cider.nrepl.middleware.resource/handle-resource
639658
{:doc "Middleware that provides the path to resource."
640659
:handles {"resource"

src/cider/nrepl/middleware.clj

Lines changed: 1 addition & 0 deletions
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
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
(ns cider.nrepl.middleware.reload
2+
"Reload changed namespaces.
3+
Alternative to cider.nrepl.middleware.refresh, using clj-reload instead
4+
of tools.namespace."
5+
(:require
6+
[clj-reload.core :as reload]
7+
[clojure.main :refer [repl-caught]]
8+
[clojure.string :as str]
9+
[haystack.analyzer :as analyzer]
10+
[nrepl.middleware.interruptible-eval :refer [*msg*]]
11+
[nrepl.middleware.print :as print]
12+
[nrepl.misc :refer [response-for]]
13+
[nrepl.transport :as transport]
14+
[orchard.misc :as misc]))
15+
16+
(defn- user-reload
17+
"Resolve clj-reload.core/<sym> from the user project or return fallback."
18+
[sym fallback]
19+
(or (some-> (symbol "clj-reload.core" (str sym)) ;; Don't use mrandorsenized version
20+
resolve)
21+
fallback))
22+
23+
(defn- init
24+
"Initialize clj-reload with dirs.
25+
Only used for test, but necessary because of mranderson."
26+
[dirs]
27+
(reload/init {:dirs dirs}))
28+
29+
(defn respond
30+
[{:keys [transport] :as msg} response]
31+
(transport/send transport (response-for msg response)))
32+
33+
(defn operation
34+
[msg]
35+
(let [opts {:log-fn (fn [& args]
36+
(respond msg {:progress (str/join " " args)}))}
37+
reload (user-reload 'reload reload/reload)
38+
unload (user-reload 'unload reload/unload)]
39+
(cond
40+
(:all msg) (reload (assoc opts :all true))
41+
(:clear msg) (unload opts)
42+
:else (reload opts))))
43+
44+
(defn- reload-reply
45+
[{:keys [::print/print-fn transport session id] :as msg}]
46+
(let [{:keys [exec]} (meta session)]
47+
(exec id
48+
(fn []
49+
(try
50+
(operation msg)
51+
(respond msg {:status :ok})
52+
(catch Throwable error
53+
(respond msg {:status :error
54+
;; TODO assoc :file, :line info if available
55+
:error (analyzer/analyze error print-fn)})
56+
(binding [*msg* msg
57+
*err* (print/replying-PrintWriter :err msg {})]
58+
(repl-caught error)))))
59+
60+
(fn [] (respond msg {:status :done})))))
61+
62+
(defn handle-reload [handler msg]
63+
(case (:op msg)
64+
"cider.clj-reload/reload" (reload-reply msg)
65+
"cider.clj-reload/reload-all" (reload-reply (assoc msg :all true))
66+
"cider.clj-reload/reload-clear" (reload-reply (assoc msg :clear true))
67+
(handler msg)))
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
(ns cider.nrepl.middleware.reload-test
2+
(:require
3+
[cider.nrepl.middleware.reload :as rl]
4+
[cider.nrepl.test-session :as session]
5+
[clojure.test :refer :all]))
6+
7+
(use-fixtures :each session/session-fixture)
8+
9+
(def ^:private dirs-to-reload
10+
;; Limit the scope of what we reload, because (for example) reloading the
11+
;; cider.nrepl.middleware.test-session ns causes *session* in that ns to be
12+
;; unloaded, which breaks session-fixture, and hence all of the below tests.
13+
["test/clj/cider/nrepl/middleware/util"])
14+
15+
;; Calling init from reload ns to work around mrandersonized version
16+
;; See cider.nrepl.middleware.refresh-test for another test that suffers from this.
17+
(#'rl/init dirs-to-reload)
18+
19+
(deftest user-reload
20+
(testing "returns fallback if clojure.tools.namespace isn't loaded"
21+
(with-redefs [resolve (constantly nil)]
22+
(is (= :foo (#'rl/user-reload 'reload :foo))))))
23+
24+
(deftest reload-op-test
25+
(testing "reload op works"
26+
(let [response (session/message {:op "cider.clj-reload/reload"})]
27+
;; There is nothing to reload since the files did not change,
28+
;; but the message does come from clj-reload.core/reload.
29+
;; It's two separate messages, but in (:progress response) they are
30+
;; concatenated.
31+
(is (= "Nothing to unloadNothing to reload" (:progress response)))
32+
(is (= #{"done" "ok"} (:status response))))))
33+
34+
(deftest reload-all-op-test
35+
(testing "reload-all op works"
36+
(let [response (session/message {:op "cider.clj-reload/reload-all"})]
37+
(is (seq (:progress response)))
38+
(is (= #{"done" "ok"} (:status response))))))
39+
40+
(deftest reload-clear-op-test
41+
(testing "reload-all op works"
42+
(let [response (session/message {:op "cider.clj-reload/reload-clear"})]
43+
(is (seq (:progress response)))
44+
(is (= "Nothing to unload" (:progress response)))
45+
(is (= #{"done" "ok"} (:status response))))))
46+

0 commit comments

Comments
 (0)