Skip to content

Commit

Permalink
Support clj-reload workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
filipesilva committed Feb 23, 2024
1 parent 3824d72 commit 37a8dbf
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 0 deletions.
37 changes: 37 additions & 0 deletions doc/modules/ROOT/pages/nrepl-api/ops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1777,3 +1777,40 @@ Returns::
* `:status` done
* `:cider/log-update-consumer` The consumer that was updated.



=== `cider.clj-reload/reload`

Reloads all changed files in dependency order,
using the io.github.tonsky/clj-reload library. It is bundled with cider-nrepl.
If that dependency is already in present your project and clj-reload.core/init has been invoked beforehand,
those configured directories will be honored.

Required parameters::
{blank}

Optional parameters::
{blank}

Returns::
* `:error` A sequence of all causes of the thrown exception when ``status`` is ``:error``.
* `:progress` Description of current namespace being unloaded/loaded.
* `:status` ``:ok`` if reloading was successful; otherwise ``:error``.



=== `cider.clj-reload/reload-all`

Reloads all files in dependency order.

Required parameters::
{blank}

Optional parameters::
{blank}

Returns::
* `:error` A sequence of all causes of the thrown exception when ``status`` is ``:error``.
* `:reloading` Description of current namespace being unloaded/loaded.
* `:status` ``:ok`` if reloading was successful; otherwise ``:error``.

2 changes: 2 additions & 0 deletions doc/modules/ROOT/pages/usage.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ under `:repl-options`.
cider.nrepl/wrap-spec
cider.nrepl/wrap-profile
cider.nrepl/wrap-refresh
cider.nrepl/wrap-reload
cider.nrepl/wrap-resource
cider.nrepl/wrap-stacktrace
cider.nrepl/wrap-test
Expand Down Expand Up @@ -161,6 +162,7 @@ That's how CIDER's nREPL handler is created:
cider.nrepl/wrap-slurp
cider.nrepl/wrap-profile
cider.nrepl/wrap-refresh
cider.nrepl/wrap-reload
cider.nrepl/wrap-resource
cider.nrepl/wrap-spec
cider.nrepl/wrap-stacktrace
Expand Down
2 changes: 2 additions & 0 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
~(with-meta '[org.clojure/tools.namespace "1.3.0"]
;; :cognitest uses tools.namespace, so we cannot inline it while running tests.
{:inline-dep (not= "true" (System/getenv "SKIP_INLINING_TEST_DEPS"))})
^:inline-dep [io.github.tonsky/clj-reload "0.2.0"]
^:inline-dep [org.clojure/tools.trace "0.7.11"]
^:inline-dep [org.clojure/tools.reader "1.3.6"]
[mx.cider/logjam "0.2.0"]]
Expand Down Expand Up @@ -157,6 +158,7 @@
cider.nrepl/wrap-out
cider.nrepl/wrap-profile
cider.nrepl/wrap-refresh
cider.nrepl/wrap-reload
cider.nrepl/wrap-resource
cider.nrepl/wrap-slurp
cider.nrepl/wrap-spec
Expand Down
17 changes: 17 additions & 0 deletions src/cider/nrepl.clj
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,23 @@ if applicable, and re-render the updated value."
"refresh-clear"
{:doc "Clears the state of the refresh middleware. This can help recover from a failed load or a circular dependency error."}}})

(def-wrapper wrap-reload cider.nrepl.middleware.reload/handle-reload
{:doc "Reload middleware."
:requires #{"clone" #'wrap-print}
:handles {"cider.clj-reload/reload"
{:doc "Reloads all changed files in dependency order,
using the io.github.tonsky/clj-reload library. It is bundled with cider-nrepl.
If that dependency is already in present your project and clj-reload.core/init has been invoked beforehand,
those configured directories will be honored."
:returns {"progress" "Description of current namespace being unloaded/loaded."
"status" "`:ok` if reloading was successful; otherwise `:error`."
"error" "A sequence of all causes of the thrown exception when `status` is `:error`."}}
"cider.clj-reload/reload-all"
{:doc "Reloads all files in dependency order."
:returns {"reloading" "Description of current namespace being unloaded/loaded."
"status" "`:ok` if reloading was successful; otherwise `:error`."
"error" "A sequence of all causes of the thrown exception when `status` is `:error`."}}}})

(def-wrapper wrap-resource cider.nrepl.middleware.resource/handle-resource
{:doc "Middleware that provides the path to resource."
:handles {"resource"
Expand Down
1 change: 1 addition & 0 deletions src/cider/nrepl/middleware.clj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
cider.nrepl/wrap-slurp
cider.nrepl/wrap-profile
cider.nrepl/wrap-refresh
cider.nrepl/wrap-reload
cider.nrepl/wrap-resource
cider.nrepl/wrap-spec
cider.nrepl/wrap-stacktrace
Expand Down
50 changes: 50 additions & 0 deletions src/cider/nrepl/middleware/reload.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
(ns cider.nrepl.middleware.reload
(:require
[clj-reload.core :as reload]
[clojure.main :refer [repl-caught]]
[clojure.string :as str]
[haystack.analyzer :as analyzer]
[nrepl.middleware.interruptible-eval :refer [*msg*]]
[nrepl.middleware.print :as print]
[nrepl.misc :refer [response-for]]
[nrepl.transport :as transport]
[orchard.misc :as misc]))

(defn- user-reload
"clj-reload.core/reload from the user project.
Must be configured via clj-reload.core/init before being called."
[]
(some-> (symbol "clj-reload.core" "reload") ;; Don't use mrandorsenized version
resolve))

(defn respond
[{:keys [transport] :as msg} response]
(transport/send transport (response-for msg response)))

(defn- refresh-reply
[{:keys [::print/print-fn transport session id] :as msg}]
(let [{:keys [exec]} (meta session)]
(exec id
(fn []
(try
(let [reload (or (user-reload) reload/reload)]
(reload (cond-> {:log-fn (fn [& args]
(respond msg {:progress (str/join " " args)}))}
(:all msg) (assoc :only :all)))
(respond msg {:status :ok}))
(catch Throwable error
(respond msg {:status :error
;; TODO assoc :file, :line info if available
:error (analyzer/analyze error print-fn)})
(binding [*msg* msg
*err* (print/replying-PrintWriter :err msg {})]
(repl-caught error)))))

(fn [] (respond msg {:status :done})))))

(defn handle-reload [handler msg]
(case (:op msg)
"cider.clj-reload/reload" (refresh-reply msg)
"cider.clj-reload/reload-all" (refresh-reply (assoc msg :all true))
(handler msg)))

36 changes: 36 additions & 0 deletions test/clj/cider/nrepl/middleware/reload_test.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
(ns cider.nrepl.middleware.reload-test
(:require
[cider.nrepl.middleware.reload :as rl]
[cider.nrepl.test-session :as session]
[clj-reload.core :as reload]
[clojure.test :refer :all]))

(use-fixtures :each session/session-fixture)

(def ^:private dirs-to-reload
;; Limit the scope of what we reload, because (for example) reloading the
;; cider.nrepl.middleware.test-session ns causes *session* in that ns to be
;; unloaded, which breaks session-fixture, and hence all of the below tests.
["test/clj/cider/nrepl/middleware/util"])

(reload/init {:dirs dirs-to-reload})

(deftest user-reload
(testing "returns nil if clojure.tools.namespace isn't loaded"
(with-redefs [resolve (constantly nil)]
(is (nil? (#'rl/user-reload))))))

(deftest reload-op-test
(testing "reload op works"
(let [response (session/message {:op "cider.clj-reload/reload"})]
;; There is nothing to reload since the files did not change,
;; but the message does come from clj-reload.core/reload.
(is (= "Nothing to reload" (:progress response)))
(is (= #{"done" "ok"} (:status response))))))

(deftest reload-all-op-test
(testing "reload-all op works"
(let [response (session/message {:op "cider.clj-reload/reload-all"})]
(is (seq (:progress response)))
(is (= #{"done" "ok"} (:status response))))))

0 comments on commit 37a8dbf

Please sign in to comment.