Skip to content

Overhaul REPL input: token completion and whole-form reading - #1

Merged
bbatsov merged 5 commits into
mainfrom
repl-input
Aug 8, 2026
Merged

bbatsov merged 5 commits into
mainfrom
repl-input

Conversation

@bbatsov

@bbatsov bbatsov commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Tab completion used to send the whole line as the completion prefix, which real servers answer with nothing - they complete bare symbol prefixes, so completion only worked when the line held nothing but the prefix. And multi-line definitions could only arrive via --load.

The REPL now completes the trailing token and splices the candidates back into the line, and it reads whole forms: continuation lines until brackets and strings balance for Lisp-family servers, or until the terminating dot for Erlang, with the dialect picked from the server names in describe's version table. Elixir stays on the generic bracket rule since do/end blocks can't be bracket-counted. Tested against the Clojure reference server, dialtone and repartee.

While at it, the README drops the utop-based examples (utop's nREPL server mode lives on an experimental branch that will probably never ship) in favor of Clojure and Erlang ones, and documents the key bindings.

bbatsov added 5 commits August 6, 2026 15:41
The old examples leaned on utop's nREPL server mode, which lives on an
experimental branch that will probably never ship. The new sessions were
captured against real servers - the Clojure reference implementation and
dialtone.
nREPL servers complete bare symbol prefixes ("ma", "lists:re"), not
whole lines, so Tab only ever worked when the line held nothing but
the prefix. Split the line at the last delimiter, ask the server
about the trailing token and splice the candidates back into the
line for linenoise. The splitting lives in a small private
repl_input library so the tests can reach it.
The CLI banner parsed the versions dict inline into a display string,
which left no way for other code to ask what is on the other end of
the connection. Extract the (name, version-string) pairs in
Client.versions and keep only the formatting in the CLI; multi-line
input support is about to need the names to pick an input dialect.

Names without a version-string now show up bare in the banner instead
of being dropped.
Multi-line definitions used to be --load-only. The REPL now keeps
prompting for continuation lines (with a ...> prompt) until the
input reads as a complete form: balanced brackets and closed strings
for Lisp-family servers, a terminating dot for Erlang. The dialect
is picked from the server names in describe's version table; Elixir
deliberately falls back to the generic bracket rule since do/end
blocks can't be bracket-counted. When the heuristic sends too
eagerly the server reports the syntax error it would have reported
anyway.

History is now saved once per form instead of once per line, since
linenoise rewrites the whole file on every save.
The linenoise fork we link ships most of the emacs-flavored readline
set, including Ctrl-R incremental history search, but none of it is
discoverable from the prompt. Spell it out.
@bbatsov
bbatsov merged commit 7531b54 into main Aug 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant