commit cedfcc8aee4a6e9d775a902f56388573592f6546
parent 79fec5699a0062b63c3c49b97b4adafc61a1d5de
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 17 Jun 2026 12:19:11 -0700
dbg: Ctrl-G edits the current REPL line in $EDITOR
The line editor now binds Ctrl-G to drop the currently typed line into
$EDITOR (seeded with the buffer via driver_edit_temp, tagged with the
current JIT language extension), then reloads the edited text back into
the prompt. Complements the `edit` command, which opens an empty buffer.
driver_read_line_edit gains an `edit_suffix` parameter; the POSIX raw-mode
editor intercepts Ctrl-G (0x07), the cooked-mode Windows fallback ignores
it. Updated the cmdline + in-REPL help and the repl-help golden.
Diffstat:
5 files changed, 55 insertions(+), 5 deletions(-)
diff --git a/driver/cmd/dbg.c b/driver/cmd/dbg.c
@@ -128,6 +128,8 @@ void driver_help_dbg(void) {
"snippet\n"
" { ... } same as jit { ... }\n"
" edit [LANG|NAME] edit and append a language snippet\n"
+ " (Ctrl-G edits the current input line "
+ "in $EDITOR)\n"
" expr EXPR | expr { ... } compile and call an expression "
"thunk\n"
" EXPR same as expr EXPR\n"
@@ -2513,6 +2515,8 @@ static void dbg_cmd_help(void) {
"snippet\n"
" { ... } same as jit { ... }\n"
" edit [LANG|NAME], e [...] edit and append a language snippet\n"
+ " Ctrl-G edit the current input line in "
+ "$EDITOR\n"
" expr EXPR | expr { ... } compile and call an expression thunk\n"
" EXPR same as expr EXPR\n"
" LANG defaults to the selected "
@@ -3207,8 +3211,14 @@ static void dbg_repl(DbgState* s) {
/* Phase 3: interactive loop. */
for (;;) {
int n;
- n = driver_read_line_edit(s->env, "(kit) ", line, sizeof(line),
- &s->line_history, dbg_complete, s);
+ char edit_suffix[DBG_JIT_SUFFIX_CAP];
+ /* Ctrl-G edits the typed line in $EDITOR; tag the temp file with the
+ * current language's extension so editors highlight it. Recomputed each
+ * iteration since `:language` can change the default mid-session. */
+ n = driver_read_line_edit(
+ s->env, "(kit) ", line, sizeof(line), &s->line_history, dbg_complete, s,
+ dbg_jit_language_suffix(s->compiler, s->default_jit_lang, edit_suffix,
+ sizeof edit_suffix));
if (n == 0) { /* EOF — Ctrl-D */
driver_printf("\n");
return;
diff --git a/driver/env.h b/driver/env.h
@@ -354,9 +354,15 @@ typedef void (*DriverLineCompleteFn)(void* user, const char* line,
int driver_line_completion_add(DriverLineCompletionList*, const char* text,
size_t len);
+/* Raw-mode line editor with history + tab completion. Ctrl-G drops the
+ * currently typed line into $EDITOR (seeded via driver_edit_temp using
+ * `edit_suffix` for the temp file's extension) and reloads the edited text
+ * back into the buffer, so the prompt is populated with what was typed.
+ * `edit_suffix` may be NULL to use an extension-less temp file; it is ignored
+ * on hosts whose line editor reads in cooked mode (no key interception). */
int driver_read_line_edit(DriverEnv*, const char* prompt, char* buf, size_t cap,
DriverLineHistory*, DriverLineCompleteFn,
- void* complete_user);
+ void* complete_user, const char* edit_suffix);
void driver_line_history_fini(DriverEnv*, DriverLineHistory*);
/* Flush the host stdout. The dbg REPL prompt has no trailing newline, so
diff --git a/driver/env/posix.c b/driver/env/posix.c
@@ -1211,7 +1211,8 @@ static void line_complete(DriverEnv* env, char* buf, size_t cap, size_t* len,
int driver_read_line_edit(DriverEnv* env, const char* prompt, char* buf,
size_t cap, DriverLineHistory* hist,
- DriverLineCompleteFn complete, void* complete_user) {
+ DriverLineCompleteFn complete, void* complete_user,
+ const char* edit_suffix) {
struct termios orig;
struct termios raw;
char* saved = NULL;
@@ -1281,6 +1282,33 @@ int driver_read_line_edit(DriverEnv* env, const char* prompt, char* buf,
}
continue;
}
+ if (c == 7) {
+ /* Ctrl-G: hand the currently typed line to $EDITOR (seeded with the
+ * buffer), then reload whatever was saved. Drop to cooked mode so the
+ * editor owns the terminal, then restore raw mode and redraw. */
+ uint8_t* edited = NULL;
+ size_t edited_size = 0;
+ buf[len] = '\0';
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig);
+ if (driver_edit_temp(env, edit_suffix, (const uint8_t*)buf, len, &edited,
+ &edited_size)) {
+ size_t n = edited_size;
+ /* Editors append a trailing newline; drop trailing CR/LF so the
+ * reloaded prompt stays a single line. */
+ while (n > 0 && (edited[n - 1] == '\n' || edited[n - 1] == '\r')) --n;
+ if (n >= cap) n = cap - 1u;
+ memcpy(buf, edited, n);
+ buf[n] = '\0';
+ len = n;
+ cursor = n;
+ have_saved = 0; /* edited text supersedes history navigation */
+ hist_pos = hist ? hist->count : 0;
+ }
+ if (edited) driver_free(env, edited, edited_size);
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
+ line_redraw(prompt, buf, len, cursor);
+ continue;
+ }
if (c == '\t') {
buf[len] = '\0';
line_complete(env, buf, cap, &len, &cursor, prompt, complete,
diff --git a/driver/env/windows.c b/driver/env/windows.c
@@ -1415,10 +1415,15 @@ static int win_line_history_add(DriverEnv* env, DriverLineHistory* h,
int driver_read_line_edit(DriverEnv* env, const char* prompt, char* buf,
size_t cap, DriverLineHistory* hist,
- DriverLineCompleteFn complete, void* complete_user) {
+ DriverLineCompleteFn complete, void* complete_user,
+ const char* edit_suffix) {
int n;
(void)complete;
(void)complete_user;
+ /* This host reads in cooked mode, so there is no keystroke interception
+ * for a Ctrl-G inline-edit binding; the `edit` REPL command remains the
+ * editor entry point here. */
+ (void)edit_suffix;
if (prompt && *prompt) {
fputs(prompt, stdout);
fflush(stdout);
diff --git a/test/dbg/cases/repl-help/expected b/test/dbg/cases/repl-help/expected
@@ -13,6 +13,7 @@ Commands (abbrev. shown):
jit [LANG|NAME] { ... } compile and append a language snippet
{ ... } same as jit { ... }
edit [LANG|NAME], e [...] edit and append a language snippet
+ Ctrl-G edit the current input line in $EDITOR
expr EXPR | expr { ... } compile and call an expression thunk
EXPR same as expr EXPR
LANG defaults to the selected language