From 9e66ebd9593e2f002c2fac4149d54822d454ee68 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 7 Nov 2026 10:20:38 +0200 Subject: [PATCH] Leave the reload repairs to cider-nrepl 0.64.1 cider-nrepl 0.64.1 points the aliases and refers on the namespaces its reloads make anew at the new ones, for every client, and puts the session that asked for the reload back in its namespace. Jack-in brings it now, so neorepl's own relinking goes, and its re-entry only runs for a cider-nrepl before 0.64.1, going by describe's cider-version, until that's the oldest one supported. ,reload sends refresh in the REPL's session, so cider-nrepl puts that one back, and sends it again without the session in a ClojureScript one, where refresh answers clojure-only. The REPL's own follow-up, which evaluated in-ns and left *1 holding the namespace, goes too. --- CHANGELOG.md | 3 +- PLAN.md | 8 +- integration/neorepl/cljs_servers_test.clj | 5 +- integration/neorepl/real_servers_test.clj | 8 ++ src/neorepl/features.clj | 156 ++++++++++------------ src/neorepl/jack_in.clj | 2 +- src/neorepl/lookups.clj | 4 +- src/neorepl/repl.clj | 13 +- test/neorepl/features_test.clj | 112 +++++++++------- test/neorepl/main_test.clj | 10 +- test/neorepl/test_server.clj | 6 +- 11 files changed, 165 insertions(+), 162 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6df287e..a26e975 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,11 @@ - [#84](https://github.com/nrepl/neorepl/pull/84): Locations in text, as `where`, `refs`, `deps` and `check` print them, are relative to the directory neorepl runs in, when they're in it. - [#85](https://github.com/nrepl/neorepl/pull/85): The profile summary fits in a terminal, without the columns only CIDER's inspector needs. - [#85](https://github.com/nrepl/neorepl/pull/85): `neorepl profile --format edn` and `json` give the summary as a list of maps, one for each function, rather than the text table. +- [#87](https://github.com/nrepl/neorepl/pull/87): Jack-in brings cider-nrepl 0.64.1, whose reloads point the aliases and refers on the namespaces they make anew at the new ones, and keep the session in its namespace, which neorepl now does itself only for an older cider-nrepl. ### Bug fixes -- [#83](https://github.com/nrepl/neorepl/pull/83): After a reload, the aliases and refers of the namespaces it didn't reload, like the REPL's, point at the reloaded ones, so a function added since resolves through an alias, and requiring with the same alias again works. +- [#87](https://github.com/nrepl/neorepl/pull/87): `,reload` leaves `*1` as it was, rather than setting it to the REPL's namespace. ## 0.5.1 (2026-11-05) diff --git a/PLAN.md b/PLAN.md index 7a37d24..d118abd 100644 --- a/PLAN.md +++ b/PLAN.md @@ -108,8 +108,8 @@ Runtime deps: `org.clojure/clojure`, `nrepl/nrepl`, `org.jline/jline` 4.x. That' - Gotcha: a subscription switches orchard from printing to sending events for the whole server, so while one's open, `neorepl eval` and every other client get no printed traces. The REPL subscribes only once something's traced, and unsubscribes when it ends. A client that goes away without unsubscribing leaves the server sending events to nobody until a traced call finds the connection gone, which drops the subscription since cider-nrepl 0.62.2, losing that one call's trace. - Reloading what changed (2026-11-03, cider-nrepl 0.64.0, with clj-reload 1.0.0). `cider.clj-reload/reload` runs in the session it names, after what's running there, and answers with `:progress` lines, "Reloading N namespaces..." and then "Reloaded N namespaces in M ms" or " failed to load NS" with the error's first line, then `ok`, or `error` with the analyzed causes as `:error` and what `repl-caught` prints on `:err`, and `done`. It reloads the namespaces whose files changed and the ones that require them, in dependency order, leaves alone the ones nothing loaded, and keeps what `defonce` defined, through a failed reload too, which keeps the namespace that failed for the next one. In a ClojureScript session it answers `done` and `clojure-only`, with a word on `:err`. - Gotcha: cider-nrepl loads a middleware's namespace the first time its op comes, and clj-reload notes the files' times when it loads, so on a server that's never reloaded, the first reload only starts noting what changes, and takes about half a second. A file changed before then doesn't get reloaded. neorepl sends that first reload in the background as soon as a server it starts with cider-nrepl is up, so `eval --reload` there sees everything since. On other servers, the first `--reload` or `,autoreload` is where it starts. cider-nrepl starting clj-reload with the server would do away with it. -- Gotcha: clj-reload makes the namespaces it reloads anew, and a session in one goes on in the old one, where a function just added to the file doesn't resolve. Unlike `clojure.tools.namespace.repl/refresh`, it doesn't put the session back, and neither does cider-nrepl's `refresh` op, which calls tools.namespace's `track-reload` itself, so neorepl does, after a reload that reloaded something, evaluating `in-ns` there in a way that leaves `*1`, `*2` and `*3` as they were. babashka resolves names through the namespace's name, so it doesn't go stale there. [cider-nrepl#1078](https://github.com/clojure-emacs/cider-nrepl/pull/1078) does it in cider-nrepl after both reloads, for the session that asks for one, and once a release with it is the oldest one supported, neorepl's can go. -- Gotcha: the same goes for the aliases and refers of the namespaces a reload leaves alone, like the REPL's own, with clj-reload and with cider-nrepl's `refresh`, whose tools.namespace also makes namespaces anew with `remove-ns` (2026-11-06, cider-nrepl 0.64.0 and clj-reload 1.0.0, whose README says so). They stay on the old namespace and its vars, so a function added to the file since doesn't resolve through the alias, and requiring the namespace with the same alias again fails with "Alias o already exists". Calling a function that was there before works, since compiled code finds vars by their names. So after a reload that reloaded something, neorepl has every alias and refer that's on a namespace that's gone point at the one by that name now, and its var by that name, a few milliseconds with cider-nrepl's namespaces loaded. What has nothing new to point at stays as it was, since an `:as-alias` keeps working for keywords, and another client's reload can be halfway through. It goes by the var's own namespace and name, since potemkin's `import-vars` copies another var's `:ns` and `:name` into its metadata. Only Clojure servers have these reloads, so it's Clojure only. [cider-nrepl#1077](https://github.com/clojure-emacs/cider-nrepl/pull/1077) does it in cider-nrepl after both reloads, for every client, and once a release with it is the oldest one supported, neorepl's can go. +- Gotcha: clj-reload makes the namespaces it reloads anew, and a session in one goes on in the old one, where a function just added to the file doesn't resolve. Unlike `clojure.tools.namespace.repl/refresh`, it doesn't put the session back, and up to 0.64.0 neither does cider-nrepl's `refresh` op, which calls tools.namespace's `track-reload` itself. babashka resolves names through the namespace's name, so it doesn't go stale there. cider-nrepl 0.64.1 ([cider-nrepl#1078](https://github.com/clojure-emacs/cider-nrepl/pull/1078)) puts the session that asks for either reload back in its namespace, so neorepl sends them in the session to put back, the REPL's for `,reload` and `,autoreload`, and puts it back itself only for an older cider-nrepl, going by describe's `cider-version`, evaluating `in-ns` there in a way that leaves `*1`, `*2` and `*3` as they were. In a ClojureScript session `refresh` answers `clojure-only`, so `,reload` there sends it again without the session (2026-11-07, cider-nrepl 0.64.1 on nREPL 1.8.0, with piggieback 0.8.0 and shadow-cljs 3.5.7). +- Gotcha: the same goes for the aliases and refers of the namespaces a reload leaves alone, like the REPL's own, with clj-reload and with cider-nrepl's `refresh`, whose tools.namespace also makes namespaces anew with `remove-ns` (2026-11-06, cider-nrepl 0.64.0 and clj-reload 1.0.0, whose README says so). They stay on the old namespace and its vars, so a function added to the file since doesn't resolve through the alias, and requiring the namespace with the same alias again fails with "Alias o already exists". Calling a function that was there before works, since compiled code finds vars by their names. cider-nrepl 0.64.1 ([cider-nrepl#1077](https://github.com/clojure-emacs/cider-nrepl/pull/1077)) has every alias and refer that's on a namespace that's gone point at the one by that name now, and its var by that name, after both reloads, for every client, so neorepl leaves it to cider-nrepl. What has nothing new to point at stays as it was, since an `:as-alias` keeps working for keywords, and it goes by the var's own namespace and name, since potemkin's `import-vars` copies another var's `:ns` and `:name` into its metadata (2026-11-07). - Output from other threads (2026-11-03, nREPL 1.8.0, with piggieback 0.8.0 too, bb 1.13.227 and 1.13.229, and shadow-cljs 3.5.7, which brings nREPL 1.3.1 and can't). `forward-system-output` swaps in `System/out` and `System/err`, and root bindings of `*out*` and `*err*`, which threads no session started print to, that copy what's printed to the session that asked, as `:out` or `:err` with `:source "system"` on that request's id, after its `done`. An evaluation prints to its session's `*out*` as before, unless it writes to `System/out` itself. nREPL keeps one forwarding per session, so asking again replaces it. cider-nrepl 0.64.0's `cider/out-subscribe`, the way before it, sends nothing to a session that hasn't evaluated anything, and its `cider/out-unsubscribe` leaves the session subscribed and fails to send its answer, which carries the whole request ([cider-nrepl#1074](https://github.com/clojure-emacs/cider-nrepl/issues/1074)), so neorepl leaves it be. What comes forwarded is all the server prints, its own noise included, like the `SocketException` traces of [babashka#2227](https://github.com/babashka/babashka/issues/2227). - Gotcha: nREPL never drops a session's forwarding, not even once the session's closed ([nrepl#513](https://github.com/nrepl/nrepl/issues/513)), so every later write goes to a connection that's gone, which fails without a word, and the request it keeps holds on to the session, with its `*1` and `*e`. So the REPL asks in a session of its own, which never evaluates anything, and closes it when it ends. And a client in the server's process gets sent what it prints, and prints it again, forever, so `forward-system-output` doesn't ask a server in neorepl's process, nor one neorepl started, whose output the REPL shows already. - Taps (2026-11-03, cider-nrepl 0.64.0). `cider/tap-subscribe` answers with the subscription's id and `done`, then sends a response for each value tapped, on that message's id, with the status `cider/tap-value` and a `:cider/tap-value`: the value printed short, 8 items, 4 levels and 200 characters at most, as the `:summary`, its `:type`, its `:count` when it's counted, and an `:idx`. `cider/tap-inspect`, with that `:idx` as a string, starts the session's inspector on the value, which cider-nrepl keeps for the last 100 tapped, and says `tap-value-not-retained` for older ones. A subscription from a ClojureScript session polls the runtime's taps instead, which come without an `:idx`, so the REPL subscribes outside its session, for Clojure's. bb 1.13.229 has none of the tap ops. cider-nrepl keeps those 100 values in the server's memory for as long as it runs, subscribed to or not, and once for each subscription, so with CIDER connected too it's the last 50 ([cider-nrepl#1076](https://github.com/clojure-emacs/cider-nrepl/issues/1076)). @@ -317,7 +317,7 @@ Upstream, alongside 0.4. The 0.1 announcement promised the first three. - nREPL: merge [#498](https://github.com/nrepl/nrepl/pull/498), so an interrupted evaluation's last response goes to its own connection, and do the same for `need-input` ([nrepl#183](https://github.com/nrepl/nrepl/issues/183)). The `need-input` one is what makes a borrowed session safe in `,sessions`, so its warning can go for servers that have it. Then the interrupt gotchas: keeping the evaluations queued behind an interrupted one ([nrepl#503](https://github.com/nrepl/nrepl/issues/503)), and letting an interrupt take a queued one off the queue ([nrepl#504](https://github.com/nrepl/nrepl/issues/504)). And dropping `*file*` from what a session saves ([nrepl#505](https://github.com/nrepl/nrepl/issues/505)), keeping an interrupted evaluation's exception from coming after its `done` ([nrepl#506](https://github.com/nrepl/nrepl/issues/506)), quieting the 1.6 warnings about `wrap-cljs-repl` on shadow-cljs ([nrepl#424](https://github.com/nrepl/nrepl/issues/424)), and merging [#508](https://github.com/nrepl/nrepl/pull/508), `TCP_NODELAY` on the server's sockets ([nrepl#507](https://github.com/nrepl/nrepl/issues/507)). A 1.8.1 with these. - babashka: once a release has the fixes on bb's master, raise `:min-bb-version` to it and drop what works around their absence: the plain `ParsedLine` on bb in editor.cljc, `locate` taking every test result's `:file` on bb from `info`, and "as on babashka" in the `quickack` docstring. Done (2026-11-03, bb 1.13.227 and 1.13.229): 1.13.227 is the oldest supported, `parsed-line` is a `CompletingParsedLine` on both runtimes, and `quickack` works on bb. `locate` keeps taking the test's file for bb servers before 1.13.227, as the server can be older than the bb neorepl runs on. With `Status` there, eldoc's line could go in a status area, with room for the doc's first sentence. And the dead session ([babashka#2227](https://github.com/babashka/babashka/issues/2227)), fixed by vendoring nREPL 1.8's session or backporting its fix. - orchard, done: [#438](https://github.com/clojure-emacs/orchard/pull/438), which joins a Javadoc's HTML without spaces and escapes `{@code}`, went out in orchard 0.47.0 on 2026-10-27, and cider-nrepl 0.64.0-alpha3 has it. `orchard-spaces?` in features.clj takes the spaces out for cider-nrepl before that. -- cider-nrepl and orchard, done: orchard 0.48.0 and cider-nrepl 0.64.0, out on 2026-10-28 and 2026-10-29, fix most of open question 4, and jack-in injects 0.64.0. +- cider-nrepl and orchard, done: orchard 0.48.0 and cider-nrepl 0.64.0, out on 2026-10-28 and 2026-10-29, fix most of open question 4, and jack-in injects 0.64.0. cider-nrepl 0.64.1, out on 2026-11-07, keeps the aliases, refers and session on the namespaces a reload makes anew, and jack-in injects it. - ClojureScript: interrupting the Node REPL, see the piggieback gotcha. A ClojureScript patch takes longest to land, so it starts early. - Filing the issues in the open questions: done for nREPL, cider-nrepl, orchard, piggieback and JLine on 2026-10-21, and babashka's, a few at a time, by 2026-10-22. ClojureScript's two are for ask.clojure.org. @@ -395,7 +395,7 @@ Not covered yet: Linux (Docker wasn't running) and Windows. Neither should surpr 1. CI against real servers, see 0.5. So far the tests run on the JVM and bb against an in-process nREPL, with fakes for the cider-nrepl ops, and the real servers get checked by hand and with proof. 2. babashka. Filed: `test-var-query` going quiet for a test that throws outside an assertion, [babashka#2201](https://github.com/babashka/babashka/issues/2201), fixed after 1.13.226, and, a few at a time to keep from flooding the tracker, on 2026-10-21 and 2026-10-22: `test-var-query` by var for a namespace that isn't loaded killing the session ([#2206](https://github.com/babashka/babashka/issues/2206)), `:project?` and `:load-project-ns?` being ignored ([#2207](https://github.com/babashka/babashka/issues/2207)), test results carrying the file of the session's last `load-file` ([#2208](https://github.com/babashka/babashka/issues/2208)), `info` without `:line` or `:column`, resolving built-in files against the working directory ([#2209](https://github.com/babashka/babashka/issues/2209)), the `SocketException` from `nREPL-track-state` after every client that disconnects ([#2210](https://github.com/babashka/babashka/issues/2210)), `inspect-last-exception` ([#2211](https://github.com/babashka/babashka/issues/2211)), `org.jline.utils.Status` (mid-buffer eldoc) and `org.jline.reader.CompletingParsedLine` (for reify) ([#2218](https://github.com/babashka/babashka/issues/2218)), `jdk.net.ExtendedSocketOptions`, for `TCP_QUICKACK` ([#2219](https://github.com/babashka/babashka/issues/2219)), and `source-fn`, which resolves bb's own dependencies the first time it's asked for a built-in, cloning git deps, which is what timed out on fresh Windows runners, and keeps `nil` for good when that fails, which is what failed `source when` for the whole run on 2026-10-18 ([#2220](https://github.com/babashka/babashka/issues/2220)). All of them are fixed on bb's master, not released yet. The 1.13.227-SNAPSHOT dev build fixes each one, on macOS and on Linux in Docker, and passes neorepl's tests; with `TCP_QUICKACK` there, `eval -f` of 100 forms on Linux goes from 4.5s to 154ms. Its `inspect-last-exception`, like cider-nrepl's, inspects what `analyze-last-stacktrace` analyzed, so the stacktrace has to be asked for first. Still open, from 2026-10-22: a session whose client disconnects during an evaluation never answers again, since nREPL 1.7's session thread dies writing the reply, which nREPL 1.8 fixed ([#2227](https://github.com/babashka/babashka/issues/2227)). It's also the `nREPL-session` SocketException in the output of neorepl's tests on bb. bb's server has been a vendored nREPL 1.7 since 1.13.221, so the interrupted evaluation's exception after `done` is nREPL's, [nrepl#506](https://github.com/nrepl/nrepl/issues/506), and so are its sockets without `TCP_NODELAY`, [nrepl#507](https://github.com/nrepl/nrepl/issues/507). On 2026-10-31, what tests print going out under the session's previous message, as it did in cider-nrepl before 0.64.0 ([#2265](https://github.com/babashka/babashka/issues/2265)). On 2026-11-01, `info` knowing nothing about namespaces ([#2270](https://github.com/babashka/babashka/issues/2270)), its `lookup` being the vendored nREPL's, which nrepl#511 covers. On 2026-11-04, `print-method` and `clojure.pprint/write` not seeing `binding`s of `*print-length*` and `*print-level*`, which nREPL's printers count on ([#2274](https://github.com/babashka/babashka/issues/2274)). Still to talk over with borkdude: how `bb repl --connect` and neorepl should relate long term. 3. nREPL. Filed: `need-input` going to the connection that created the session ([nrepl#183](https://github.com/nrepl/nrepl/issues/183)), interrupting an evaluation dropping the ones queued behind it ([nrepl#503](https://github.com/nrepl/nrepl/issues/503)), an interrupt not reaching an evaluation that hasn't started, queued or just sent ([nrepl#504](https://github.com/nrepl/nrepl/issues/504)), `*file*` staying bound after an eval with a `:file` ([nrepl#505](https://github.com/nrepl/nrepl/issues/505)), and an interrupted evaluation's InterruptedException coming after its `done` ([nrepl#506](https://github.com/nrepl/nrepl/issues/506)). [#498](https://github.com/nrepl/nrepl/pull/498), the interrupted evaluation's last response going to its own connection, is merged. The 1.6 warnings about middleware a descriptor names and the stack lacks are [nrepl#424](https://github.com/nrepl/nrepl/issues/424): with piggieback on the classpath, cider-nrepl's descriptors bring eleven of them, and shadow-cljs's own one more, since it expects piggieback's middleware without listing it; one line per missing middleware, and none for `:expects`, would quiet it. On 2026-10-22: the server's sockets going without `TCP_NODELAY`, so on Linux an eval's `done` waits up to 40ms behind its value ([nrepl#507](https://github.com/nrepl/nrepl/issues/507)), which macOS doesn't show. The fix is [#508](https://github.com/nrepl/nrepl/pull/508), which takes 100 evals from nREPL's own client from 4.4s to 90ms on Linux. Until servers have it, neorepl sets `TCP_QUICKACK` before each read, on the JVM and on bb, which has `ExtendedSocketOptions` from 1.13.227. On 2026-11-01, `lookup` finding namespaces, by their name or an alias, [#511](https://github.com/nrepl/nrepl/pull/511), which also leaves `:file` out where there's none, rather than sending an empty list, as it does for a var made with `intern`. On 2026-11-03, closing a session not stopping its `forward-system-output` ([nrepl#513](https://github.com/nrepl/nrepl/issues/513)). -4. cider-nrepl and orchard. Filed: ClojureScript tests not running on shadow-cljs ([cider-nrepl#1054](https://github.com/clojure-emacs/cider-nrepl/issues/1054)), and on 2026-10-21 `test-var-query` never answering for a test whose namespace isn't loaded ([#1057](https://github.com/clojure-emacs/cider-nrepl/issues/1057)), running every test when the tests it names aren't there ([#1058](https://github.com/clojure-emacs/cider-nrepl/issues/1058)), test output going out under the session's previous message ([#1059](https://github.com/clojure-emacs/cider-nrepl/issues/1059)), ClojureScript test runs leaving the report in `*1` ([#1060](https://github.com/clojure-emacs/cider-nrepl/issues/1060)) and `cider/trace-subscribe` answering `done` before its events ([#1061](https://github.com/clojure-emacs/cider-nrepl/issues/1061)). In orchard: the namespace query's NullPointerException once a project namespace fails to load ([orchard#434](https://github.com/clojure-emacs/orchard/issues/434), #70 again), which is what hangs `test-var-query` with `:load-project-ns?`, a traced call that throws getting no exit line or event ([orchard#435](https://github.com/clojure-emacs/orchard/issues/435)), and tracing what orchard's tracing calls, like `clojure.core/map`, taking the server down ([orchard#436](https://github.com/clojure-emacs/orchard/issues/436)). On 2026-10-26, the spaces orchard puts between everything in a Javadoc's HTML and its unescaped `{@code}`, with the fix ([orchard#438](https://github.com/clojure-emacs/orchard/pull/438)). A subscription outliving its connection was fixed in 0.62.2. orchard 0.48.0 and cider-nrepl 0.64.0 fix #1054, #1057, #1058, #1059, orchard#434 and orchard#435. orchard#436 is only half fixed: tracing a function orchard's tracing calls no longer takes the server down, but orchard still traces it, since refusing takes a status in cider-nrepl that CIDER can show. Still open: #1060, and #1061, which waits until most neorepl users have a REPL that takes its subscription without waiting for `done`, from 0.5.0 on. On 2026-11-03, `cider/out-unsubscribe` not unsubscribing and failing to send its answer ([#1074](https://github.com/clojure-emacs/cider-nrepl/issues/1074)), `cider/profile-summary` failing until a profiled function gets called ([#1072](https://github.com/clojure-emacs/cider-nrepl/issues/1072)), no ops to list what's profiled or stop all of it ([#1073](https://github.com/clojure-emacs/cider-nrepl/issues/1073)), a value that can't be printed ending a tap subscription ([#1075](https://github.com/clojure-emacs/cider-nrepl/issues/1075)), tapped values kept once per subscription and after the last one ([#1076](https://github.com/clojure-emacs/cider-nrepl/issues/1076)), and in orchard, profiling maps and multimethods ([orchard#445](https://github.com/clojure-emacs/orchard/issues/445)). After a reload, the aliases and refers left on the namespaces it removed are fixed on master by [#1077](https://github.com/clojure-emacs/cider-nrepl/pull/1077) (2026-11-06), and the session left in one by [#1078](https://github.com/clojure-emacs/cider-nrepl/pull/1078), open since 2026-11-07, neither released yet. +4. cider-nrepl and orchard. Filed: ClojureScript tests not running on shadow-cljs ([cider-nrepl#1054](https://github.com/clojure-emacs/cider-nrepl/issues/1054)), and on 2026-10-21 `test-var-query` never answering for a test whose namespace isn't loaded ([#1057](https://github.com/clojure-emacs/cider-nrepl/issues/1057)), running every test when the tests it names aren't there ([#1058](https://github.com/clojure-emacs/cider-nrepl/issues/1058)), test output going out under the session's previous message ([#1059](https://github.com/clojure-emacs/cider-nrepl/issues/1059)), ClojureScript test runs leaving the report in `*1` ([#1060](https://github.com/clojure-emacs/cider-nrepl/issues/1060)) and `cider/trace-subscribe` answering `done` before its events ([#1061](https://github.com/clojure-emacs/cider-nrepl/issues/1061)). In orchard: the namespace query's NullPointerException once a project namespace fails to load ([orchard#434](https://github.com/clojure-emacs/orchard/issues/434), #70 again), which is what hangs `test-var-query` with `:load-project-ns?`, a traced call that throws getting no exit line or event ([orchard#435](https://github.com/clojure-emacs/orchard/issues/435)), and tracing what orchard's tracing calls, like `clojure.core/map`, taking the server down ([orchard#436](https://github.com/clojure-emacs/orchard/issues/436)). On 2026-10-26, the spaces orchard puts between everything in a Javadoc's HTML and its unescaped `{@code}`, with the fix ([orchard#438](https://github.com/clojure-emacs/orchard/pull/438)). A subscription outliving its connection was fixed in 0.62.2. orchard 0.48.0 and cider-nrepl 0.64.0 fix #1054, #1057, #1058, #1059, orchard#434 and orchard#435. orchard#436 is only half fixed: tracing a function orchard's tracing calls no longer takes the server down, but orchard still traces it, since refusing takes a status in cider-nrepl that CIDER can show. Still open: #1060, and #1061, which waits until most neorepl users have a REPL that takes its subscription without waiting for `done`, from 0.5.0 on. On 2026-11-03, `cider/out-unsubscribe` not unsubscribing and failing to send its answer ([#1074](https://github.com/clojure-emacs/cider-nrepl/issues/1074)), `cider/profile-summary` failing until a profiled function gets called ([#1072](https://github.com/clojure-emacs/cider-nrepl/issues/1072)), no ops to list what's profiled or stop all of it ([#1073](https://github.com/clojure-emacs/cider-nrepl/issues/1073)), a value that can't be printed ending a tap subscription ([#1075](https://github.com/clojure-emacs/cider-nrepl/issues/1075)), tapped values kept once per subscription and after the last one ([#1076](https://github.com/clojure-emacs/cider-nrepl/issues/1076)), and in orchard, profiling maps and multimethods ([orchard#445](https://github.com/clojure-emacs/orchard/issues/445)). After a reload, the aliases and refers left on the namespaces it removed, fixed by [#1077](https://github.com/clojure-emacs/cider-nrepl/pull/1077), and the session left in one, fixed by [#1078](https://github.com/clojure-emacs/cider-nrepl/pull/1078), both out in cider-nrepl 0.64.1 on 2026-11-07. 5. piggieback: evaluating every form of an `eval` message, the way nREPL's own eval does, and loading ClojureScript on the first `cljs-repl` rather than at startup, both open as [#162](https://github.com/nrepl/piggieback/pull/162) and [#161](https://github.com/nrepl/piggieback/pull/161), and `describe` reporting `cljs.user` after an `ns` switch ([piggieback#163](https://github.com/nrepl/piggieback/issues/163)). Its README says ClojureScript evaluations can't be interrupted, which for Node isn't the whole story, see the gotcha. In ClojureScript: interrupting the Node REPL, and two Node REPLs sharing a result queue, see the gotcha, to raise on ask.clojure.org. 6. JLine: `TAIL_TIP` hiding the incremental search's prompt ([jline3#2330](https://github.com/jline/jline3/issues/2330)), `LineDisciplineTerminal` echoing `^C` with ECHO off ([#2331](https://github.com/jline/jline3/issues/2331)) and `setTailTip(null)` throwing ([#2332](https://github.com/jline/jline3/issues/2332)). A tail tip that doesn't start with `[` loses its first word while a word without a `-` is typed, which is by design, for TailTipWidgets' argument lists; eldoc's tips for special forms start with `(`, so they lose it, and starting them with a space would keep them whole. Since 2026-11-01 eldoc goes under the input, and neorepl has no tail tips. 7. nbb. Filed on 2026-11-02: code that throws getting no `eval-error` status ([nbb#425](https://github.com/babashka/nbb/issues/425)), fixed the same day, after 1.6.214, an eval's `:ns` losing to the namespace the last eval left ([#426](https://github.com/babashka/nbb/issues/426)), `in-ns` not switching ([#427](https://github.com/babashka/nbb/issues/427)), sessions sharing the namespace, `*1` and `*e` ([#429](https://github.com/babashka/nbb/issues/429)), `load-file` taking the file's contents for its `:file` ([#430](https://github.com/babashka/nbb/issues/430)), and `info` and `eldoc` answering what they don't find with empty lists and no `no-info` or `no-eldoc` ([#431](https://github.com/babashka/nbb/issues/431)). On 2026-11-04, nREPL's own printers and a session's `*print-length*` being ignored ([#436](https://github.com/babashka/nbb/issues/436)). diff --git a/integration/neorepl/cljs_servers_test.clj b/integration/neorepl/cljs_servers_test.clj index 8011fc4..58d4c97 100644 --- a/integration/neorepl/cljs_servers_test.clj +++ b/integration/neorepl/cljs_servers_test.clj @@ -117,7 +117,7 @@ (deftest repl-test (on-each-server (fn [server] - (let [{:keys [status out]} (run-with server (str ",sessions cljs\n(js/parseInt \"7\")\n" + (let [{:keys [status out]} (run-with server (str ",sessions cljs\n(js/parseInt \"7\")\n,reload\n" ",set print-length 3\n(range 10)\n,set pretty off\n(range 10)\n" ",ns demo.core\n(greet \"r\")\n" ",ns demo.a\n,ns demo.a-test\n(a/f)\n" @@ -126,6 +126,9 @@ "repl")] (is (= 0 status)) (is (str/includes? out "cljs.user=> 7\n")) + (testing "reloading the server's Clojure, which cider-nrepl's refresh won't in the session" + (is (re-find #"cljs\.user=> (Reloaded \S|Nothing changed\n)" out)) + (is (not (str/includes? out "Clojure-only")))) ;; Printed again on the JVM, which takes the limit. (is (str/includes? out "cljs:cljs.user=> (0 1 2 ...)\ncljs:cljs.user=> pretty off\ncljs:cljs.user=> (0 1 2 ...)\n")) (is (str/includes? out "demo.core=> \"hi r\"\n")) diff --git a/integration/neorepl/real_servers_test.clj b/integration/neorepl/real_servers_test.clj index 3ab260a..ef99ea5 100644 --- a/integration/neorepl/real_servers_test.clj +++ b/integration/neorepl/real_servers_test.clj @@ -212,6 +212,14 @@ (is (= ":added\n" (:out (run :clojure "eval" "(demo.core/added)")))) (testing "with the aliases of a namespace it left alone on the one it made anew" (is (= {:status 0 :out ":added\n" :err ""} (run :clojure "eval" "-s" "refreshing" "(c/added)")))) + (testing "and the session it reloads in back in its namespace, made anew" + (with-client [client {:port (discovery/read-port (io/file (project-dir :clojure) ".nrepl-port"))}] + (let [session (nrepl/clone-session client) + values #(keep :value (nrepl/request client {:op "eval" :session session :code %}))] + (values "(in-ns 'demo.core)") + (spit file "\n(defn more [] :more)\n" :append true) + (is (= ["demo.core" "demo.core-test"] (:reloaded (features/reload client [] {:session session} {})))) + (is (= [":more"] (values "(more)")))))) (finally (spit file servers/core) (run :clojure "reload"))))) diff --git a/src/neorepl/features.clj b/src/neorepl/features.clj index 5459a6a..3b2075b 100644 --- a/src/neorepl/features.clj +++ b/src/neorepl/features.clj @@ -22,16 +22,23 @@ (defn- supports? [client op] (contains? (:ops client) op)) +(defn- cider-release + "The server's cider-nrepl version, by describe's :cider-version, as + [[major minor patch] prerelease], with a prerelease like alpha3 or nil, + or nil when there's no version." + [{:keys [cider-version]}] + (when-let [[_ major minor patch prerelease] (some->> cider-version (re-find #"^(\d+)\.(\d+)\.(\d+)(?:-(\w+))?"))] + [(mapv #(Long/parseLong %) [major minor patch]) prerelease])) + (defn- orchard-spaces? "Whether the server's cider-nrepl puts a space either side of each entity in a Javadoc's HTML, as those before 0.64.0-alpha3 do, the first with orchard#438." - [{:keys [cider-version]}] - (let [[_ major minor patch prerelease] (some->> cider-version (re-find #"^(\d+)\.(\d+)\.(\d+)(?:-(\w+))?"))] - (or (nil? major) - (let [release (mapv #(Long/parseLong %) [major minor patch])] - (or (neg? (compare release [0 64 0])) - (and (= [0 64 0] release) (contains? #{"alpha1" "alpha2"} prerelease))))))) + [client] + (let [[release prerelease] (cider-release client)] + (or (nil? release) + (neg? (compare release [0 64 0])) + (and (= [0 64 0] release) (contains? #{"alpha1" "alpha2"} prerelease))))) (defn- no-namespace "The exception saying there's no namespace called ns." @@ -652,51 +659,41 @@ normalize-causes not-empty))) -(def ^:private relink-code - "Has the aliases and refers in every namespace that are on a namespace a - reload made anew point at the new one, and its var by the same name. - cider-nrepl's refresh and clj-reload make the namespaces they reload anew - with remove-ns and leave the others' on the old ones, as clj-reload's - README says, where a function added since isn't there, and the alias - can't be made again. What has nothing new to point at stays as it was, - the way an :as-alias keeps working for keywords. It goes by the var's - own namespace and name, rather than its metadata, which potemkin's - import-vars copies from another var, and a var from Var/create has no - namespace at all. Only Clojure has those reloads, so it does nothing on - babashka. cider-nrepl does the same after its reloads since - cider-nrepl#1077, so this can go once that's in the oldest one supported." - "#?(:bb nil - :clj (clojure.core/let [now #(clojure.core/some-> % clojure.core/ns-name clojure.core/find-ns)] - (clojure.core/doseq [^clojure.lang.Namespace n (clojure.core/all-ns)] - (clojure.core/reduce-kv - (clojure.core/fn [_ a target] - (clojure.core/let [current (now target)] - (clojure.core/when (clojure.core/and current (clojure.core/not (clojure.core/identical? target current))) - (.removeAlias n a) - (.addAlias n a current)))) - nil (.getAliases n)) - (clojure.core/reduce-kv - (clojure.core/fn [_ s v] - (clojure.core/when (clojure.core/instance? clojure.lang.Var v) - (clojure.core/let [target (.-ns ^clojure.lang.Var v) - ^clojure.lang.Namespace current (now target) - ^clojure.lang.Var new (clojure.core/when (clojure.core/and current (clojure.core/not (clojure.core/identical? target current))) - (.findInternedVar current (.-sym ^clojure.lang.Var v)))] - (clojure.core/when (clojure.core/and new (.isPublic new)) - (.unmap n s) - (.refer n s new))))) - nil (.getMappings n)))))") +(def ^:private reenter-code + "Puts the session back in its namespace, by its name, which a reload can + make anew, leaving the session in the old one. The REPL then sets *3 to + *2, *2 to *1 and *1 to the value, so the code sets them a step back and + comes to *1, which leaves all three as they were." + "(clojure.core/let [v1 clojure.core/*1 v2 clojure.core/*2 v3 clojure.core/*3] + (clojure.core/in-ns (clojure.core/ns-name clojure.core/*ns*)) + (set! clojure.core/*2 v3) + (set! clojure.core/*1 v2) + v1)") + +(defn- reenter + "Puts session back in its namespace after a reload that made namespaces + anew, as reenter-code does, when the server's cider-nrepl doesn't: before + 0.64.1, the first with cider-nrepl#1078, so this can go once that's the + oldest one supported." + [client session] + (let [[release] (cider-release client)] + (when (or (nil? release) (neg? (compare release [0 64 1]))) + (nrepl/request client {:op "eval" :session session :code reenter-code + ;; *1 could be anything, even endless. + :nrepl.middleware.print/quota 1})))) (defn reload "Reloads code and returns {:reloaded [the namespaces]}, and the :output loading them printed, like reflection warnings, if any. With nses, a seq of namespace names, those namespaces get reloaded. Without, the ones that - changed since the last reload, which takes cider-nrepl's refresh, and the - aliases and refers on them then get relinked, as relink-code says. A big - project takes a while, so it waits as long as that takes unless opts have - a :timeout-ms." - ([client nses] (reload client nses {})) - ([client nses opts] + changed since the last reload, which takes cider-nrepl's refresh, in + ctx's :session, if it has one. That makes the namespaces anew, and + cider-nrepl, since 0.64.1, has the aliases and refers on them point at + the new ones, and puts the session back in its namespace, as reenter does + for an older one. A big project takes a while, so it waits as long as + that takes unless opts have a :timeout-ms." + ([client nses] (reload client nses {} {})) + ([client nses {:keys [session]} opts] (let [opts (merge {:timeout-ms nil} opts) [reloaded responses] (cond @@ -708,24 +705,30 @@ opts)] (supports? client "refresh") - (let [refresh #(ask client {:op "refresh"} opts) - responses (try - (refresh) - (catch clojure.lang.ExceptionInfo e - ;; cider-nrepl keeps trying to reload a namespace - ;; whose file is gone, until it forgets them all, - ;; which reloads the rest too, so only then. - (if-let [ns (some :error-ns (:responses (ex-data e)))] - (if (str/includes? (ex-message e) (str "Could not locate " - (-> ns (str/replace "-" "_") (str/replace "." "/")) - "__init.class")) - (do (ask client {:op "refresh-clear"} opts) - (refresh)) - (throw e)) - (throw e)))) + (let [refresh #(let [responses (ask client (cond-> {:op "refresh"} session (assoc :session session)) opts)] + ;; cider-nrepl won't in a ClojureScript session, which + ;; has no Clojure namespace to go back to anyway. + (if (some (fn [response] (nrepl/status? response "clojure-only")) responses) + [(ask client {:op "refresh"} opts) nil] + [responses session])) + [responses refreshed-in] + (try + (refresh) + (catch clojure.lang.ExceptionInfo e + ;; cider-nrepl keeps trying to reload a namespace + ;; whose file is gone, until it forgets them all, + ;; which reloads the rest too, so only then. + (if-let [ns (some :error-ns (:responses (ex-data e)))] + (if (str/includes? (ex-message e) (str "Could not locate " + (-> ns (str/replace "-" "_") (str/replace "." "/")) + "__init.class")) + (do (ask client {:op "refresh-clear"} opts) + (refresh)) + (throw e)) + (throw e)))) reloaded (vec (some :reloading responses))] - (when (seq reloaded) - (nrepl/request client {:op "eval" :code relink-code})) + (when (and refreshed-in (seq reloaded)) + (reenter client refreshed-in)) [reloaded responses]) :else @@ -1768,25 +1771,13 @@ opts (fn [_])))) -(def ^:private reenter-code - "Puts the session back in its namespace, by its name, which a reload can - make anew, leaving the session in the old one. The REPL then sets *3 to - *2, *2 to *1 and *1 to the value, so the code sets them a step back and - comes to *1, which leaves all three as they were. cider-nrepl puts the - session that asked for the reload back itself since cider-nrepl#1078, so - this can go once that's in the oldest one supported." - "(clojure.core/let [v1 clojure.core/*1 v2 clojure.core/*2 v3 clojure.core/*3] - (clojure.core/in-ns (clojure.core/ns-name clojure.core/*ns*)) - (set! clojure.core/*2 v3) - (set! clojure.core/*1 v2) - v1)") - (defn reload-changed "Reloads the namespaces whose files changed since the last time, the ones that depend on them after them, with cider-nrepl's clj-reload, which - keeps what defonce defined and leaves alone namespaces nothing loaded, - then puts session back in its namespace and relinks the aliases and - refers on the namespaces it made anew, as relink-code says. It runs in + keeps what defonce defined and leaves alone namespaces nothing loaded. + It makes the namespaces it reloads anew, and cider-nrepl, since 0.64.1, + puts session back in its namespace, as reenter does for an older one, and + has the aliases and refers on them point at the new ones. It runs in session, the way evaluation/evaluate does, whose opts it takes. Returns nil once it's done, or else what evaluate does, saying why not, with :clojurescript? in a ClojureScript session, where there's nothing for it @@ -1814,12 +1805,7 @@ (= "error" status) (assoc result :message "Reloading what changed failed") (not= "ok" status) result ;; Without a count, as from a project's own clj-reload, it may have. - (= "0" @reloaded) nil - :else (do (nrepl/request client {:op "eval" :session session :code reenter-code - ;; *1 could be anything, even endless. - :nrepl.middleware.print/quota 1}) - (nrepl/request client {:op "eval" :code relink-code}) - nil)))) + (not= "0" @reloaded) (do (reenter client session) nil)))) (defn note-changes "Has cider-nrepl's clj-reload, if the server at host and port has it, diff --git a/src/neorepl/jack_in.clj b/src/neorepl/jack_in.clj index fda379c..1adbb97 100644 --- a/src/neorepl/jack_in.clj +++ b/src/neorepl/jack_in.clj @@ -18,7 +18,7 @@ only gets cider-nrepl, since it comes with the nREPL it's made for and runs ClojureScript REPLs itself." {:nrepl "1.8.0" - :cider-nrepl "0.64.0" + :cider-nrepl "0.64.1" :piggieback "0.8.0"}) (defn- builds-with-project? diff --git a/src/neorepl/lookups.clj b/src/neorepl/lookups.clj index c061f9b..a2e6848 100644 --- a/src/neorepl/lookups.clj +++ b/src/neorepl/lookups.clj @@ -61,8 +61,8 @@ :none (fn [_ sym] (str sym " uses no functions"))} {:name "reload" :arg "NS..." :args? true :optional? true :help "Reload the namespaces NS, or the ones that changed" - :find (fn [client {:keys [timeout-ms]} nses] - (features/reload client nses {:timeout-ms timeout-ms})) + :find (fn [client {:keys [timeout-ms] :as ctx} nses] + (features/reload client nses ctx {:timeout-ms timeout-ms})) :text (fn [{:keys [reloaded output]}] (str (some-> output str/trimr (str "\n")) (if (seq reloaded) diff --git a/src/neorepl/repl.clj b/src/neorepl/repl.clj index d990e36..0940b5f 100644 --- a/src/neorepl/repl.clj +++ b/src/neorepl/repl.clj @@ -460,21 +460,10 @@ (nrepl/session-ns client id) (when (= id (:session own)) "this REPL's")])))))) -(defn- reenter - "Puts session back in the namespace called ns, as tools.namespace's refresh - does: reloading code can make a namespace anew, and the session would go - on in the old one." - [client session ns] - ;; Qualified, since the namespace it's in might not refer clojure.core. - (nrepl/request client {:op "eval" :session session - :code (format "(clojure.core/in-ns (clojure.core/symbol %s))" (pr-str ns))})) - (def ^:private follow-ups "What the REPL does after some lookups, by name, given command-output's state and what the lookup found." - {"reload" (fn [{:keys [client session ns]} _] - (reenter client session ns)) - "trace" (fn [{:keys [show-traces]} {:keys [results]}] + {"trace" (fn [{:keys [show-traces]} {:keys [results]}] (when (some #(#{"traced" "already-traced"} (:status %)) results) (show-traces))) "untrace" (fn [{:keys [hide-traces]} {:keys [untraced]}] diff --git a/test/neorepl/features_test.clj b/test/neorepl/features_test.clj index 382a60b..05ca80e 100644 --- a/test/neorepl/features_test.clj +++ b/test/neorepl/features_test.clj @@ -3,6 +3,7 @@ [clojure.string :as str] [clojure.test :refer [are deftest is testing use-fixtures]] [neorepl.features :as features] + [neorepl.lookups :as lookups] [neorepl.nrepl :as nrepl] [neorepl.render :as render] [neorepl.test-server :as test-server :refer [*port* with-client]] @@ -15,6 +16,10 @@ (def ^:private refresh-fails? (atom false)) +(def ^:private clojurescript-session + "The session wrap-fake-cider's refresh takes for a ClojureScript one." + (atom nil)) + (def ^:private refresh-lost? "What wrap-fake-cider's refresh can't find, of gone.ns, until refresh-clear: :gone, its own file, or :dependency, one it requires, or nothing." @@ -25,7 +30,8 @@ (atom nil)) (def ^:private test-requests - "The test ops wrap-fake-cider got, and their var-queries." + "The test ops wrap-fake-cider got, and their var-queries, the inspector's + and refresh, with its session." (atom [])) (defn- wrap-fake-cider @@ -183,27 +189,32 @@ (transport/respond-to msg :status "done")) "refresh-clear" (do (reset! refresh-lost? nil) (transport/respond-to msg :status "done")) - "refresh" (cond - @refresh-lost? - (do (transport/respond-to msg :err (str "Could not locate " - (if (= :gone @refresh-lost?) "gone/ns" "nope/core") - "__init.class on classpath.\n")) - (transport/respond-to msg :status "error" :error-ns "gone.ns") - (transport/respond-to msg :status "done")) + "refresh" (do (swap! test-requests conj (select-keys msg [:op :session])) + (cond + (some-> (:session msg) (= @clojurescript-session)) + (transport/respond-to msg :status ["done" "clojure-only"] + :err "The \"refresh\" op is Clojure-only and isn't available in a ClojureScript REPL.\n") - @refresh-fails? - ;; cider-nrepl prints the error, as the REPL would. - (do (transport/respond-to msg :reloading ["broken.ns"]) - (transport/respond-to msg :err "Syntax error compiling at (broken/ns.clj:1:1).\n") - (transport/respond-to msg :status "error" :error-ns "broken.ns") - (transport/respond-to msg :status "done")) - :else - ;; Made anew, as tools.namespace does. - (do (remove-ns 'changed.ns) - (intern (create-ns 'changed.ns) 'kept true) - (transport/respond-to msg :reloading ["changed.ns"]) - (transport/respond-to msg :err "Reflection warning, changed/ns.clj:3:1\n") - (transport/respond-to msg :status ["ok" "done"]))) + @refresh-lost? + (do (transport/respond-to msg :err (str "Could not locate " + (if (= :gone @refresh-lost?) "gone/ns" "nope/core") + "__init.class on classpath.\n")) + (transport/respond-to msg :status "error" :error-ns "gone.ns") + (transport/respond-to msg :status "done")) + + @refresh-fails? + ;; cider-nrepl prints the error, as the REPL would. + (do (transport/respond-to msg :reloading ["broken.ns"]) + (transport/respond-to msg :err "Syntax error compiling at (broken/ns.clj:1:1).\n") + (transport/respond-to msg :status "error" :error-ns "broken.ns") + (transport/respond-to msg :status "done")) + :else + ;; Made anew, as tools.namespace does. + (do (remove-ns 'changed.ns) + (create-ns 'changed.ns) + (transport/respond-to msg :reloading ["changed.ns"]) + (transport/respond-to msg :err "Reflection warning, changed/ns.clj:3:1\n") + (transport/respond-to msg :status ["ok" "done"])))) "inspect-last-exception" (do (swap! test-requests conj (select-keys msg [:op :index :ex-data :page-size])) (transport/respond-to msg :status "done" :value (pr-str ["Class: " '(:value "clojure.lang.ExceptionInfo" 0)]))) @@ -574,28 +585,15 @@ noting (doto (Thread. #(do (Thread/sleep 300) (deliver noted true))) .start)] (features/reload-changed (assoc client :noting-changes noting) session {}) (is (realized? noted)))) - (testing "with the aliases and refers of a namespace it left alone on the one it made anew" - (let [before @test-server/reloads] - (values (str "(ns neorepl.reloading) (alias 'r 'neorepl.reloaded)" - " (refer 'neorepl.reloaded :only '[kept reloaded-" before "] :rename '{kept k})" - " (def imported 1) (alter-meta! #'imported assoc :ns (the-ns 'neorepl.reloaded) :name 'kept)")) - (reload) - (is (= ["true"] (values (str "r/reloaded-" @test-server/reloads)))) - ;; babashka has no reload that makes namespaces anew, so nothing - ;; relinks the refers there. - (when-not (System/getProperty "babashka.version") - (is (= ["true"] (values "(identical? #'neorepl.reloaded/kept (resolve 'k))"))) - (testing "but not a refer with nothing new to point at, nor a var whose metadata says it's another's, as potemkin's import-vars makes them" - (is (= ["[true 1]"] (values (str "[(some? (resolve 'reloaded-" before ")) @(resolve 'imported)]"))))) - (testing "nor a var with no namespace, from Var/create" - (values "(.refer *ns* 'anonymous (clojure.lang.Var/create :anonymous))") - (is (not-any? :ex (nrepl/request client {:op "eval" :code @#'features/relink-code}))) - (is (= [":anonymous"] (values "@(resolve 'anonymous)"))))))) (testing "with the session back in its namespace, made anew, and *1, *2 and *3 as they were" (values "(in-ns 'neorepl.reloaded)") (values "1 2 3") (reload) - (is (= ["[true 3 2 1]"] (values (str "[reloaded-" @test-server/reloads " *1 *2 *3]"))))) + (is (= ["[true 3 2 1]"] (values (str "[reloaded-" @test-server/reloads " *1 *2 *3]")))) + (testing "but leaving that to cider-nrepl 0.64.1 and after, which the fake isn't" + (features/reload-changed (assoc client :cider-version "0.64.1") session {}) + (is (= ["false"] (values "(identical? *ns* (find-ns 'neorepl.reloaded))"))) + (values "(clojure.core/in-ns 'user)"))) (testing "the server's error, when a file doesn't compile" (let [{:keys [status err message]} (test-server/answering-reloads :error reload)] (is (= "error" status)) @@ -787,14 +785,36 @@ (is (str/starts-with? (features/source client "mapv" user) "(defn mapv"))) (is (= {:reloaded ["changed.ns"] :output "Reflection warning, changed/ns.clj:3:1\n"} (features/reload client []))) - (testing "with the aliases and refers of a namespace it left alone on the one it made anew" - (nrepl/request client {:op "eval" :code "(ns neorepl.refreshing) (alias 'c 'changed.ns) (refer 'changed.ns :only '[kept])"}) + (testing "in no session, unless it's given one, which cider-nrepl then puts back in its namespace" + (reset! test-requests []) (features/reload client []) - (let [values #(keep :value (nrepl/request client {:op "eval" :ns "neorepl.refreshing" :code %}))] - (is (= ["true"] (values "(identical? (find-ns 'changed.ns) (get (ns-aliases *ns*) 'c))"))) - ;; As in reload-changed-test. - (when-not (System/getProperty "babashka.version") - (is (= ["true"] (values "(identical? #'changed.ns/kept (resolve 'kept))")))))) + (let [session (nrepl/clone-session client)] + (features/reload client [] {:session session} {}) + (is (= [{:op "refresh"} {:op "refresh" :session session}] @test-requests)) + (testing "such as the REPL's, through the lookup" + (reset! test-requests []) + (lookups/run "reload" client {:session session} nil) + (is (= [{:op "refresh" :session session}] @test-requests))) + (testing "which neorepl puts back itself before cider-nrepl 0.64.1" + (let [values #(keep :value (nrepl/request client {:op "eval" :session session :code %})) + ;; A namespace made with create-ns refers nothing. + back? "(clojure.core/identical? clojure.core/*ns* (clojure.core/find-ns 'changed.ns))"] + (values "(in-ns 'changed.ns)") + (features/reload client [] {:session session} {}) + (is (= ["true"] (values back?))) + (testing "but not after, when cider-nrepl does, which the fake doesn't" + (features/reload (assoc client :cider-version "0.64.1") [] {:session session} {}) + (is (= ["false"] (values back?)))) + (values "(clojure.core/in-ns 'user)"))) + (testing "but not a ClojureScript one, which cider-nrepl won't refresh in" + (reset! clojurescript-session session) + (reset! test-requests []) + (try + (is (= {:reloaded ["changed.ns"] :output "Reflection warning, changed/ns.clj:3:1\n"} + (features/reload client [] {:session session} {}))) + (is (= [{:op "refresh" :session session} {:op "refresh"}] @test-requests)) + (finally + (reset! clojurescript-session nil)))))) (testing "after forgetting a namespace whose file is gone, which cider-nrepl keeps after" (reset! refresh-lost? :gone) (is (= {:reloaded ["changed.ns"] :output "Reflection warning, changed/ns.clj:3:1\n"} diff --git a/test/neorepl/main_test.clj b/test/neorepl/main_test.clj index 46be1db..b646171 100644 --- a/test/neorepl/main_test.clj +++ b/test/neorepl/main_test.clj @@ -844,13 +844,9 @@ (testing ",where and ,reload" (is (re-find #"^user=> \S*core\.clj:\d+" (repl ",where map\n"))) (is (= "user=> Reloaded clojure.set\nuser=> \n" (repl ",reload clojure.set\n"))) - (testing "which leaves the REPL in the namespace it was in, even when that's made anew" - (is (= "user=> nil\nrepl.anew=> nil\nrepl.anew=> Reloaded clojure.set\nrepl.anew=> true\nrepl.anew=> \n" - ;; in-ns refers nothing to the namespace it makes on the JVM. - (repl (str "(do (in-ns 'repl.anew) nil)\n" - "(do (clojure.core/remove-ns 'repl.anew) (clojure.core/create-ns 'repl.anew) nil)\n" - ",reload clojure.set\n" - "(clojure.core/identical? clojure.core/*ns* (clojure.core/find-ns 'repl.anew))\n")))))) + (testing "which leaves *1 as it was" + (is (= "user=> 3\nuser=> Reloaded clojure.set\nuser=> 3\nuser=> \n" + (repl "(+ 1 2)\n,reload clojure.set\n*1\n"))))) (testing "help, mistakes and quitting" (is (str/includes? (repl ",help\n") ",doc SYM")) (is (= "user=> Usage: ,doc SYM\nuser=> \n" (repl ",doc\n"))) diff --git a/test/neorepl/test_server.clj b/test/neorepl/test_server.clj index 14603e9..e16c225 100644 --- a/test/neorepl/test_server.clj +++ b/test/neorepl/test_server.clj @@ -444,18 +444,18 @@ (defn- remake-reloaded! "Makes the namespace neorepl.reloaded anew, as clj-reload does the ones it reloads, with a var it didn't have before, named after what reloads - holds, as reloaded-1, and one it always has, kept." + holds, as reloaded-1." [] (remove-ns 'neorepl.reloaded) (let [n (create-ns 'neorepl.reloaded)] (binding [*ns* n] (refer 'clojure.core)) - (intern n 'kept true) (intern n (symbol (str "reloaded-" @reloads)) true))) (defn wrap-fake-reload "cider-nrepl's cider.clj-reload/reload, answering as reload-answer says, - with what cider-nrepl 0.64.0 sends. It reloads neorepl.reloaded." + with what cider-nrepl 0.64.0 sends, which doesn't put the session back. + It reloads neorepl.reloaded." [handler] (fn [{:keys [op] :as msg}] (if (= "cider.clj-reload/reload" op)