Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
- [#29](https://github.com/nrepl/neorepl/pull/29): A test error's location is its test's, rather than the file of the frame that threw with the test's line, and on babashka every failure's is.
- [#29](https://github.com/nrepl/neorepl/pull/29): `neorepl test --timeout` covers loading the tests too, and running out of time there exits 124, rather than 3.
- [#29](https://github.com/nrepl/neorepl/pull/29): `neorepl test` of a namespace without tests says so and exits 1, rather than passing.
- [#30](https://github.com/nrepl/neorepl/pull/30): Symbols that start with a dash, like `->` and `-main`, are arguments to `doc`, `where` and the other lookups, rather than unknown options.
- [#30](https://github.com/nrepl/neorepl/pull/30): `--format edn` and `json` have failures too, like no server to talk to or a usage mistake, as their status and message, where stdout used to be empty.
- [#30](https://github.com/nrepl/neorepl/pull/30): A named session the server lost, as when it restarts, says so: where a new one takes its place, on stderr or in the MCP result, and where it doesn't, like `pst` and `sessions close`.
- [#30](https://github.com/nrepl/neorepl/pull/30): A timeout while an evaluation from before still runs in the session says so, rather than that interrupting failed.
- [#30](https://github.com/nrepl/neorepl/pull/30): `eval -f`, `load` and the MCP `load` tool check a file's delimiters first, saying where they don't balance rather than the reader's `REPL:9:1`, and say when a file can't be read.
- [#30](https://github.com/nrepl/neorepl/pull/30): A namespace that isn't there, a regex that doesn't parse and inspected code that throws get a message saying so, rather than the server's exception.

## 0.3.0 (2026-10-17)

Expand Down
2 changes: 1 addition & 1 deletion PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ Each one is a release people can use. The first three stages below all went into
- MCP, done: `pst` and `apropos` put their lists under `causes` and `vars`, since structured content is an object, which the official SDK checks. Code gets end of input from `*in*`, which carries the protocol. `reload` gives up at the call's timeout. Arguments get checked against the tools' schemas, which say what ports, timeouts and sessions can be, and a line the reader can't take gets a parse error, ids past a long being bigints now. A connection whose server went away gets replaced, and closed, before a call uses it. The default session is `agent`, the skill's, and `--max-output` cuts the whole result.
- Lookups, done: `source` reads the definition from the file and line `info` finds, as CIDER does, which works after `load` and in ClojureScript, and takes it only if it's a definition, since cider-nrepl gives the macros ClojureScript takes from Clojure lines from somewhere else; it falls back to `clojure.repl/source-fn` for files this machine can't read. The eval fallbacks compile in `user` with `*ns*` bound, so they work in a namespace made with `in-ns`. Java members and classes get their doc, from cider-nrepl's Javadoc fragments, and arglists, fields none, and a namespace is named once. `,macroexpand` with cider-nrepl expands in a copy of the session, keeping its `*1`. `--session`, and the MCP tools' `session`, look things up as a session sees them, in its language and namespace; in ClojureScript that's the Clojure namespace under it, which is all `describe` has, so `--ns` names the other.
- Tests, done: a whole-project run with cider-nrepl loads the project's namespaces first, those orchard finds, in a copy of the session, and says which don't load, then runs the tests without `:load-project-ns?`, which never answers when a namespace doesn't load; a server without orchard, like bb's, still gets the flag. A namespace that threw while loading is gone, but counts as loaded to `require` after, its `ns` form having said so, so a reload gets its error again, for every run, named namespaces' too. A test error's location is its test's file, with the line cider-nrepl gives, which is the test's, rather than the file of the frame that threw, and on bb every failure's is, bb giving the file the session loaded last. One deadline covers the run, loading the tests included, and running out while loading them exits 124. A namespace named without tests says so and exits 1, a test namespace that doesn't load says that, and failure headings show files relative to the directory neorepl runs in.
- Errors and output: `--format edn` and `json` print nothing on stdout for most failures, like no server, a server error or no port files. "The server couldn't do eval" and friends carry the exception's whole text, for a namespace that isn't there, a bad regex, or a test namespace that doesn't compile, and `,inspect` of failing code prints the Java trace on cider-nrepl. A server restart gives a named session a fresh start without a word. `doc ->` and `where -main` are taken for options. A timeout waiting on a busy session says interrupting failed. Unbalanced code in `eval -f` and `load` gets the reader's `REPL:9:1`, and unreadable files a raw exception. `pst` shows the REPL's own frames without cider-nrepl, and hides real ones as duplicates with it and with bb. `reload` stays broken once a namespace's file is gone, until cider-nrepl's `refresh-clear`.
- Errors and output, done: failures come out in `--format edn` and `json` too, with their status and message. "The server couldn't do eval" and friends make way for what's wrong: a namespace that isn't there, which nREPL's `lookup` says only with an error, a regex that doesn't parse, checked before it goes out, and inspected code that throws, which cider-nrepl answers with the whole stack trace, cut to its first line and its root cause. A server restart that loses a named session says so where a new one takes its place, on stderr or in the MCP result, and where it doesn't, as for `pst`. `doc ->` and `where -main` take their symbols for arguments, an argument being an option only when it looks like one. A timeout waiting behind an evaluation left in the session says that, and that interrupting the session stops it. `eval -f` and `load` check a file's delimiters first, as `neorepl check` would, and say a file can't be read. Left for the next round: `pst` showing the REPL's own frames without cider-nrepl and hiding real ones as duplicates with it and bb, and `reload` staying broken once a namespace's file is gone, until cider-nrepl's `refresh-clear`.
- Tracing: `,trace map` takes a cider-nrepl server down, orchard tracing its own calls.
- The REPL: output from another thread that doesn't end its line stays hidden, then sticks to the next value. Ctrl-R's search prompt doesn't show, the autosuggestion's tip taking its place. Enter on a frame without source in `,stacktrace` leaves the view, and `,edit` can't open a definition in a jar. `,macroexpand` replaces `*1` with cider-nrepl. Text typed while an evaluation runs shows up in its output and again at the prompt. The plain REPL shows the inspector's `[N]` marks, Ctrl-C's message starts after the `^C`, long eldoc tips wrap, and `NO_COLOR` leaves the completion menu's color. C-x C-s and C-x C-a, the continuation lines' indentation, the vi keymap and the completion menu's namespaces are in this plan but not in the REPL.
- Jack-in: a build tool that isn't installed gets a raw `exec: lein: not found`. `neorepl server --tool shadow-cljs` won't start next to a Clojure server, and the REPL's `--tool` goes to whatever server is up. A port file pointing at something that isn't nREPL takes 10s to give up on. Done: a host that doesn't answer gives up after 10s, rather than 75s. A server that dies leaves its port file and no word why, and Ctrl-C while one starts says it failed. A nested project connects to its parent's server.
Expand Down
13 changes: 11 additions & 2 deletions src/neorepl/cli.clj
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,19 @@
[{:keys [long]}]
(keyword (subs long 2)))

(defn- option?
"Whether arg looks like an option: --name, a short -x, or one of the
short options in by-name with more after it, so that symbols like -> and
-main, the arguments of lookups, don't."
[by-name arg]
(boolean (or (re-matches #"--[^-=].*|-[a-zA-Z](=.*)?" arg)
(and (< 2 (count arg)) (contains? by-name (subs arg 0 2))))))

(defn parse
"Parses args against specs into {:options {...} :args [...]}, or {:error
msg}. Options and arguments can come in any order, values either as the
next argument or after =. -- ends the options; - alone is an argument."
next argument or after =. -- ends the options; - alone is an argument, as
is anything else that doesn't look like an option."
[specs args]
(let [by-name (into {} (for [spec specs
name ((juxt :short :long) spec)
Expand All @@ -29,7 +38,7 @@
(cond
(empty? args) {:options options :args positional}
(= "--" a) {:options options :args (into positional more)}
(or (= "-" a) (not (str/starts-with? a "-"))) (recur more options (conj positional a))
(not (option? by-name a)) (recur more options (conj positional a))
:else
(let [[name inline] (str/split a #"=" 2)
{:keys [arg parse] :or {parse identity} :as spec} (by-name name)]
Expand Down
13 changes: 9 additions & 4 deletions src/neorepl/evaluation.clj
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@
:else (or (some statuses failure-statuses) "ok"))
message (case status
"timeout" (str "Timed out after " timeout "s"
(if interrupted?
" and interrupted the evaluation"
(case interrupted?
true " and interrupted the evaluation"
:busy (str " waiting behind an evaluation from before, still running in the"
" session, which interrupting the session stops, and this one runs after")
", and interrupting the evaluation failed"))
"stopped" "Stopped waiting for the evaluation, which may still be running"
"interrupted" "The evaluation was interrupted"
Expand All @@ -77,7 +79,9 @@
(defn- interrupt!
"Interrupts the evaluation with message id, sent at the time sent-at, whose
responses go to the queue responses, giving the server a second to
confirm. Returns whether it did, or found it done already. A server can
confirm. Returns whether it did, or found it done already, or :busy when
the session's running another evaluation, which this one waits behind,
from a client that went away, say. A server can
find the session idle while the evaluation runs elsewhere, as
shadow-cljs's ClojureScript ones do, but one that's done has sent done
before saying so. It can also find it idle right after the message went
Expand All @@ -94,6 +98,7 @@
(< (- (System/currentTimeMillis) sent-at) 500) (do (Thread/sleep 50)
(recur))
:else false)
:mismatch :busy
false)))

(defn- after
Expand Down Expand Up @@ -201,7 +206,7 @@
(let [try-interrupt (halt!)]
;; Not on the thread that delivers signals and keystrokes.
(future
(when-not (try-interrupt)
(when-not (true? (try-interrupt))
(.put responses ::interrupt-failed)))))
;; Sends msg, unless halted, and says so.
send-msg (fn [msg]
Expand Down
Loading
Loading