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 5971cbf
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 68 deletions.
105 changes: 37 additions & 68 deletions doc/modules/ROOT/pages/nrepl-api/ops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1024,74 +1024,6 @@ Returns::



=== `refresh`

Reloads all changed files in dependency order.

Required parameters::
{blank}

Optional parameters::
* `:after` The namespace-qualified name of a zero-arity function to call after reloading.
* `:before` The namespace-qualified name of a zero-arity function to call before reloading.
* `:dirs` List of directories to scan. If no directories given, defaults to all directories on the classpath.
* `:nrepl.middleware.print/buffer-size` The size of the buffer to use when streaming results. Defaults to 1024.
* `:nrepl.middleware.print/keys` A seq of the keys in the response whose values should be printed.
* `:nrepl.middleware.print/options` A map of options to pass to the printing function. Defaults to ``nil``.
* `: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].
* `:nrepl.middleware.print/quota` A hard limit on the number of bytes printed for each value.
* `:nrepl.middleware.print/stream?` If logical true, the result of printing each value will be streamed to the client over one or more messages.


Returns::
* `:error` A sequence of all causes of the thrown exception when ``status`` is ``:error``.
* `:error-ns` The namespace that caused reloading to fail when ``status`` is ``:error``.
* `:reloading` List of namespaces that will be reloaded.
* `:status` ``:ok`` if reloading was successful; otherwise ``:error``.



=== `refresh-all`

Reloads all files in dependency order.

Required parameters::
{blank}

Optional parameters::
* `:after` The namespace-qualified name of a zero-arity function to call after reloading.
* `:before` The namespace-qualified name of a zero-arity function to call before reloading.
* `:dirs` List of directories to scan. If no directories given, defaults to all directories on the classpath.
* `:nrepl.middleware.print/buffer-size` The size of the buffer to use when streaming results. Defaults to 1024.
* `:nrepl.middleware.print/keys` A seq of the keys in the response whose values should be printed.
* `:nrepl.middleware.print/options` A map of options to pass to the printing function. Defaults to ``nil``.
* `: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].
* `:nrepl.middleware.print/quota` A hard limit on the number of bytes printed for each value.
* `:nrepl.middleware.print/stream?` If logical true, the result of printing each value will be streamed to the client over one or more messages.


Returns::
* `:error` A sequence of all causes of the thrown exception when ``status`` is ``:error``.
* `:error-ns` The namespace that caused reloading to fail when ``status`` is ``:error``.
* `:reloading` List of namespaces that will be reloaded.
* `:status` ``:ok`` if reloading was successful; otherwise ``:error``.



=== `refresh-clear`

Clears the state of the refresh middleware. This can help recover from a failed load or a circular dependency error.

Required parameters::
{blank}

Optional parameters::
{blank}

Returns::
{blank}


=== `resource`

Obtain the path to a resource.
Expand Down Expand Up @@ -1777,3 +1709,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``.

1 change: 1 addition & 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
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-refresh 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
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 5971cbf

Please sign in to comment.