edit(editor: string,contents: string,server: REPLServer): Promise<{ text: string; saved: boolean; aborted: boolean; }>
Hands the terminal to an editor, and takes back whatever was saved.
The REPL is holding the TTY in raw mode with a readline attached, and an editor needs both back. Three things have to happen, and the middle one is the one that bites.
server.pause() stops readline. stdin.pause() stops NODE reading fd 0 — without it both this
process and the editor read the same descriptor and race for every byte. nvim loses keystrokes,
and its terminal reports arrive at the prompt instead of at nvim: 32;14;45M and friends, which
are SGR mouse events, spilling into the line after a session that looked fine until you quit.
Raw mode is lifted last, because the editor sets whatever modes it wants and restores them on
exit; anything still owned by Node at that point is what survives to corrupt the next line.
NOT detached. A detached child leads its own process group, which is not the terminal's
foreground group — the first read from the TTY would stop it with SIGTTIN. Inheriting the
terminal while its reader stands down is what "it owns the screen" actually means here.
The buffer travels through a file because that is the only thing an editor talks. It comes back as a string, and the CALLER keeps it: the session's scratchpad lives for as long as the session, so reopening continues the same thought rather than starting a blank one.
A .js extension, because whatever an editor does with syntax and indentation should be what it
would do for the file this text is going to behave like.
saved is whether the editor WROTE the file, read off its mtime — not whether the text came
back different. Those are not the same question, and the difference was a bug: decline a run,
reopen, :wq without touching anything, and the text matched what went in, so nothing was
offered and every other .e looked ignored.
Quitting without writing still means "never mind", because a :q leaves the mtime alone.
aborted is the other way to say never mind, for when you HAVE saved: :cq leaves vim with a
non-zero status, which is the same signal git commit reads to throw a message away. It is the
only "do not use this" an editor can send that the file cannot, because :wq and :w followed
by :q leave a byte-identical file and both exit 0.
import type { REPLServer } from 'node:repl'; import { edit } from './editor.ts'; // Defined, not invoked: it takes over the terminal. function example(server: REPLServer) { return edit('vi', 'const x = 1;', server); // { text, saved, aborted } }