Repository navigation
repl: decide command or code in one place, and take only full command names in a multi-line input - #46364
Open
robobun wants to merge 1 commit into
Open
repl: decide command or code in one place, and take only full command names in a multi-line input#46364robobun wants to merge 1 commit into
robobun wants to merge 1 commit into
Conversation
`bun repl` took every line whose first byte is `.` as a REPL command. `.5 + 1` printed `Unknown command: .5`. Inside a pending multi-line input, a line such as `.then(f)` or `...rest` was dropped with the same message and the rest of the input still ran. `ReplCommand::classify` now decides what a line is. Enter, Tab and the inline suggestion ask it. - A line that starts with `.` and a digit, or with `..`, is JavaScript. - In a pending input, only a full command name is a command. Any other line that starts with `.` is JavaScript and joins the input. - At the prompt, a name that is no command still prints `Unknown command`, and a name cut short (`.ex`) still runs. - In `.editor` mode every line is JavaScript, for Tab as for Enter.
Collaborator
Author
|
Status: the fix is in this PR (#46364). How I reproduced it, on 1.4.3 and on a release build of main (9b4284f), Linux x64, piped stdin: printf '.5 + 1\n' | bun repl # Unknown command: .5
printf '[1,\n.5]\n' | bun repl # Unknown command: .5]
printf 'const o = {b: 7}\n(o\n.b)\n' | bun repl # Unknown command: .b)
|
Collaborator
Author
|
Updated 4:39 PM PT - Oct 21st, 2026
✅ @robobun, your commit 5ec1b146c65198fcb26bb1f59d5656f4787b9fed passed in 🧪 To try this PR locally: bunx bun-pr 46364That installs a local version of the PR into your bun-46364 --bun |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
bun repltakes every line whose first byte is.as a REPL command..5 + 1printsUnknown command: .5.(ev/.e/)exits, and(o/.s = 1)writes a file named= 1).line[0] == b'.'inRepl::handle_enter(src/runtime/cli/repl.rs:2535), ahead of the continuation check.handle_tabandupdate_suggestionrepeat it.Fix
ReplCommand::classifydecides what a line is: code, a command, or an unknown name. Enter and Tab ask it, the inline suggestion asks its byte rulemay_name..and a digit, or with.., is code. In a multi-line input only a full command name is a command.test/js/bun/repl/repl.test.ts, 11 new tests, all fail on main.Background
.eruns.exit..breakcancels it.node:replhave this rule (src/js/node/repl.js:824)..eand.s = 1)still run commands.Downsides
.br) or a mistyped one (.brek) is code: no message, no Tab completion..s = 1)runs.save(repl: keep multi-line history entries on one line of the history file #38023 prevents it).classify: +32 instructions per Enter (393 to 425), no allocation, +256 bytes of text.Notes
Origin. There is no issue. A check of the REPL docs page (#46308) found it. No user reported it.
When it triggers. A
.in column 0: a number such as.5at the prompt, or a line of a multi-line input that starts with.. A line indented with spaces already works on main, because its first byte is a space. A line indented with TAB does not: a TAB on an empty line is the Tab key and inserts nothing, so a pasted or piped<TAB>...defaults,is entered as...defaults,.Repro. Piped stdin, release builds of main (9b4284f) and of this PR,
node -i26.3./is Enter..5 + 1Unknown command: .51.51.5[/...[1, 2],/...[3]/]Unknown commandtwice, then[][ 1, 2, 3 ][ 1, 2, 3 ]await (Promise.resolve([3, 1, 2])/.then(a => a.sort())/.then(a => a.slice(1))/)Unknown commandtwice, then[ 3, 1, 2 ][ 2, 3 ][ 2, 3 ]const o = { s: 0 }/(o/.s = 1)Session saved to = 1), a file= 1)in the cwd1, no file1const ev = { e: 4 }/(ev/.e/)44const defaults = { retries: 3 }/const cfg = {/<TAB>...defaults,/<TAB>timeout: 50,/}/cfgUnknown command: ...defaults,, then{ timeout: 50 }{ retries: 3, timeout: 50 }{ retries: 3, timeout: 50 }[3, 1, 2]/.sort()Unknown command: .sort()Unknown command: .sort()Invalid REPL keywordThe rule
linelistener: a digit or a second.makes the line code. For a line that starts with.,Number.parseFloat(line)is a number exactly when the second character is an ASCII digit (checked for every string of 2 and 3 characters)..breakincluded. Bun keeps.breakthere, because the docs give it as the way out..editormode Enter already stored every line as text. Tab now agrees: it completes no command name, and it completes a property on a line that starts with.. Node on a terminal completes.bto.breakthere.may_namealone gives the answer ofclassify, with no table lookup on each key.Unknown command, short names at the prompt, and no trim of leading blanks (.helpis code, and that is why chains indented with spaces work).What changes beyond the rows above
.b,.br, ....histor) are code in a multi-line input. First words that run a command there: 40 on main, 9 with this change.function foo() {/.brek/}/typeof foo: main printsUnknown command: .brek, drops the line and definesfoo. This PR printsSyntaxError: Unexpected token '.'at the}and does not definefoo. Node prints the sameSyntaxErrorat the.brekline, because it parses the input at each Enter. Bun counts brackets.// don't do thisopens a string for the bracket counter (repl: recognize regex literals and comments in continuation detection #37327 teaches it comments). Then.quitand.qprintUnknown commandon main and.exexits. With this change the three lines join the input and print nothing..exit,.break,.helpand Ctrl+C work as before..5 + Mat.~/.bun_repl_history, as part of their entry. The file holds one line per row, so after a restart.s = 1)is an entry of its own, and Up then Enter on it runs.saveat the prompt. repl: keep multi-line history entries on one line of the history file #38023 stores a multi-line entry on one row.Not changed
[3, 1, 2]Enter has already run, so.sort()on the next line still printsUnknown command. The docs now say to open a parenthesis first or to use.editor.(o/.save = 1)writes the file on main, with this PR, and in node with piped stdin..copygets no Tab completion and no suggestion, as on main: the whole line is a command. The self-review asked for a tracking issue for this. This PR opens none, and that is the concern addressed in part.handle_ctrl_cand the Ctrl+D arm (repl: clear the typed line on Ctrl+C in every mode, evaluate it on Ctrl+D in editor mode #46083). The decision that an input is complete,is_incomplete_code(repl: recognize regex literals and comments in continuation detection #37327).Relation to open PRs
handle_enter(2 conflict hunks inrepl.rs, 0 in the test file). It keeps the first-byte test, so it does not fix this. The one that lands second folds its rule intoReplCommand::classify: passentered, end the name at a space or a TAB, returnCodewhen a line end follows the name, and keepescape_control_charsin theUnknownCommandarm. To take only repl: read non-terminal stdin as lines of text and take bracketed pastes as text #46248's side of the two hunks brings the bug back on Enter.DIMtoGRAY. It merges with no conflict, and the new suggestion test here uses${DIM}h. The one that lands second changes that line.bun replprints #46308 rewrites the examples of the docs page with the❯prompt. It merges with no conflict. The example added here uses>, as the page on main does.git merge-fileon the three files of this PR).robobun/c1f23755/repl-input, a1588d9) had this rule for Enter in a weaker shape. repl: read non-terminal stdin as lines of text and take bracketed pastes as text #46248 replaced that branch and does not carry the rule.Measurements. Release builds of main (9b4284f) and of this change on it, same directory and toolchain, Linux x64.
perf,valgrind,straceandbloatyare not on the machine. The tools aresize,llvm-nm,llvm-objdump,perf_event_openand gdb.Repl::run_with_vm14,350 to 13,271 bytes,Repl::update_suggestion1,297 to 1,446, newReplCommand::classify1,329. Symbols of thereplmodule: 35 to 36.classify), edit key 2 to 2 (llvm-objdump).instructions:u, (200k - 100k rows) / 100k, median of 5 interleaved runs, A/A floor 0.11): empty line 393.1 to 425.0,.nonexistent3,121.7 to 3,159.7,.break1,292.4 to 1,344.4,[then.break1,817.7 to 1,863.8.write(2)calls per key (gdb counters, (2000 - 1000 keys) / 1000), the same on both builds: empty Enter 0 and 2,.nopeEnter 1 and 10,.breakEnter 1 and 2,aTab 103 and 27,.heTab 2 and 1,.zzTab 1 and 1,.load ./procTab 1 and 1.1Enter: 51.2 and 3 on both (A/A spread 51.12 to 51.27).test/**that start with.in column 0, each entered as a line of an open template literal (116 usable). Main keeps 0, reports 112 as unknown and runs 4 as commands (.b {and.c {, twice each). This PR keeps 116.[1,, after(oand in a template literal, with LF and CRLF, plus 27 Tab cells): 282 differ from main. 40 are.digitand..lines, 186 are short names in a multi-line input, 42 are unknown names in a multi-line input, 14 are Tab cells (1 at the prompt, 7 in a multi-line input, 6 in.editormode). 0 other cells differ: every line at the prompt that is not a number or a spread, and the nine full names in every context, give the same output.const o = {b: 7}/(o/.b/)/.5 + 1: lines that start with.: 0 on main, 2 with this PR.Tests
..test (1), full names only in a multi-line input (3), unknown name is code there (6), the.editorguard (1), Tab asks for more thanCommand(1), the suggestion asksmay_name(1).test/regression/issue/26058.test.ts,test/internal/source-lints/byte-search.test.ts.describe.todoIf(isWindows)block.no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/bun/repl/repl.test.ts