kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit e7154750753d813668b4616f6ecedc709077f075
parent 24f445586a405a0772b5d45a48b55d7c4fc82c00
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 28 May 2026 22:14:11 -0700

Add dbg line editing and completions

Diffstat:
Mdoc/DBG_TODO.md | 4++--
Mdriver/dbg.c | 267++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mdriver/env.h | 32++++++++++++++++++++++++++++++++
Mdriver/env/common.c | 45+++++++++++++++++++++++++++++++++++++++++++++
Mdriver/env/posix.c | 336+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/env/windows.c | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 730 insertions(+), 5 deletions(-)

diff --git a/doc/DBG_TODO.md b/doc/DBG_TODO.md @@ -126,8 +126,8 @@ experience is solid. ## Shared REPL Work -- [ ] Add line editing and history. -- [ ] Add completion for commands, symbols, locals, files, and breakpoint ids. +- [x] Add line editing and history. +- [x] Add completion for commands, symbols, locals, files, and breakpoint ids. - [ ] Repeat the last stepping command on a blank line. - [ ] Print a compact source context after source-level stops. - [ ] Make stop messages distinguish user breakpoints, internal step diff --git a/driver/dbg.c b/driver/dbg.c @@ -393,6 +393,7 @@ typedef struct DbgState { int has_stop; CfreeStopInfo last_stop; uint64_t jit_counter; + DriverLineHistory line_history; } DbgState; /* SIGINT trampoline. The handler in env.c calls our cb with this state; @@ -2811,6 +2812,266 @@ static int dbg_dispatch(DbgState* s, char* line) { } /* ============================================================ + * Completion + * ============================================================ */ + +static int dbg_slice_has_prefix(CfreeSlice s, const char* prefix, + size_t prefix_len) { + return s.len >= prefix_len && memcmp(s.s, prefix, prefix_len) == 0; +} + +static int dbg_cstr_has_prefix(const char* s, const char* prefix, + size_t prefix_len) { + return driver_strlen(s) >= prefix_len && memcmp(s, prefix, prefix_len) == 0; +} + +static int dbg_completion_seen(DriverLineCompletionList* out, + const char* text, size_t len) { + uint32_t i; + for (i = 0; i < out->count; ++i) { + if (driver_strlen(out->items[i].text) == len && + memcmp(out->items[i].text, text, len) == 0) + return 1; + } + return 0; +} + +static void dbg_completion_add_cstr(DriverLineCompletionList* out, + const char* prefix, size_t prefix_len, + const char* text) { + size_t len = driver_strlen(text); + if (!dbg_cstr_has_prefix(text, prefix, prefix_len)) return; + if (dbg_completion_seen(out, text, len)) return; + (void)driver_line_completion_add(out, text, len); +} + +static void dbg_completion_add_slice(DriverLineCompletionList* out, + const char* prefix, size_t prefix_len, + CfreeSlice text) { + if (!text.s || !dbg_slice_has_prefix(text, prefix, prefix_len)) return; + if (dbg_completion_seen(out, text.s, text.len)) return; + (void)driver_line_completion_add(out, text.s, text.len); +} + +static void dbg_completion_add_u64(DriverLineCompletionList* out, + const char* prefix, size_t prefix_len, + uint64_t v) { + char buf[32]; + size_t n = 0; + char rev[32]; + size_t r = 0; + if (v == 0) { + rev[r++] = '0'; + } else { + while (v && r < sizeof(rev)) { + rev[r++] = (char)('0' + (v % 10u)); + v /= 10u; + } + } + while (r > 0 && n + 1u < sizeof(buf)) buf[n++] = rev[--r]; + buf[n] = '\0'; + dbg_completion_add_cstr(out, prefix, prefix_len, buf); +} + +static void dbg_word_bounds(const char* line, size_t cursor, size_t* start, + size_t* end) { + size_t len = driver_strlen(line); + size_t s = cursor < len ? cursor : len; + size_t e = s; + while (s > 0 && !dbg_isspace((unsigned char)line[s - 1])) --s; + while (e < len && !dbg_isspace((unsigned char)line[e])) ++e; + *start = s; + *end = e; +} + +static size_t dbg_read_word_at(const char* line, size_t start, char* out, + size_t cap) { + size_t i = start; + size_t n = 0; + while (line[i] && dbg_isspace((unsigned char)line[i])) ++i; + while (line[i] && !dbg_isspace((unsigned char)line[i])) { + if (n + 1u < cap) out[n++] = line[i]; + ++i; + } + if (cap) out[n] = '\0'; + return n; +} + +static void dbg_complete_commands(DriverLineCompletionList* out, + const char* prefix, size_t prefix_len) { + static const char* const cmds[] = { + "help", "quit", "exit", "run", "cont", + "continue", "step", "sl", "next", "finish", + "jit", "edit", "expr", "bt", "backtrace", + "where", "break", "info", "ignore", "delete", + "enable", "disable", "print", "set", "jump", + "list", "disasm", "x", "x/i", ":language", + ":lang", "language", "abort", "h", "q", + "r", "c", "s", "sl", "n", + "b", "d", "p", "l", "e", + }; + size_t i; + for (i = 0; i < sizeof(cmds) / sizeof(cmds[0]); ++i) + dbg_completion_add_cstr(out, prefix, prefix_len, cmds[i]); +} + +static void dbg_complete_info(DriverLineCompletionList* out, + const char* prefix, size_t prefix_len) { + static const char* const words[] = { + "b", "break", "breakpoints", "reg", "registers", + "locals", "args", "functions", "func", "variables", + "var", + }; + size_t i; + for (i = 0; i < sizeof(words) / sizeof(words[0]); ++i) + dbg_completion_add_cstr(out, prefix, prefix_len, words[i]); +} + +static void dbg_complete_languages(DriverLineCompletionList* out, + const char* prefix, size_t prefix_len) { + static const char* const langs[] = {"c", "toy", "asm", "wasm", "wat"}; + size_t i; + for (i = 0; i < sizeof(langs) / sizeof(langs[0]); ++i) + dbg_completion_add_cstr(out, prefix, prefix_len, langs[i]); +} + +static void dbg_complete_symbols(DbgState* s, DriverLineCompletionList* out, + const char* prefix, size_t prefix_len, + int funcs, int objects) { + CfreeJitSymIter* it = NULL; + CfreeJitSym sym; + if (!s->jit || cfree_jit_sym_iter_new(s->jit, &it) != CFREE_OK) return; + for (;;) { + CfreeIterResult r = cfree_jit_sym_iter_next(it, &sym); + if (r != CFREE_ITER_ITEM) break; + if ((sym.kind == CFREE_SK_FUNC && funcs) || + (sym.kind == CFREE_SK_OBJ && objects)) + dbg_completion_add_slice(out, prefix, prefix_len, sym.name); + } + cfree_jit_sym_iter_free(it); +} + +static void dbg_complete_locals(DbgState* s, DriverLineCompletionList* out, + const char* prefix, size_t prefix_len) { + CfreeDwarfVarIter* it = NULL; + CfreeDwarfVar v; + if (!s->has_stop || !s->dwarf) return; + if (cfree_dwarf_vars_at_new(s->dwarf, + dbg_pc_rt_to_img(s, s->last_stop.regs.pc), + CFREE_DVRM_LOCAL | CFREE_DVRM_ARG, &it) != + CFREE_OK) + return; + for (;;) { + CfreeIterResult r = cfree_dwarf_vars_at_next(it, &v); + if (r != CFREE_ITER_ITEM) break; + dbg_completion_add_slice(out, prefix, prefix_len, v.name); + } + cfree_dwarf_vars_at_free(it); +} + +static void dbg_complete_files(DbgState* s, DriverLineCompletionList* out, + const char* prefix, size_t prefix_len) { + CfreeDwarfCuIter* cui = NULL; + CfreeDwarfCu cu; + if (!s->dwarf) return; + if (cfree_dwarf_cu_iter_new(s->dwarf, &cui) != CFREE_OK) return; + for (;;) { + CfreeDwarfLineIter* li = NULL; + CfreeDwarfLineRow row; + CfreeIterResult cr = cfree_dwarf_cu_iter_next(cui, &cu); + if (cr != CFREE_ITER_ITEM) break; + if (cfree_dwarf_line_iter_new(s->dwarf, cu.offset, &li) != CFREE_OK) + continue; + for (;;) { + CfreeSlice file = CFREE_SLICE_NULL; + CfreeIterResult lr = cfree_dwarf_line_iter_next(li, &row); + if (lr != CFREE_ITER_ITEM) break; + if (cfree_dwarf_line_file(s->dwarf, cu.offset, row.file_index, &file) == + CFREE_OK) + dbg_completion_add_slice(out, prefix, prefix_len, file); + } + cfree_dwarf_line_iter_free(li); + } + cfree_dwarf_cu_iter_free(cui); +} + +static void dbg_complete_breakpoint_ids(DbgState* s, + DriverLineCompletionList* out, + const char* prefix, + size_t prefix_len) { + uint32_t i; + for (i = 0; i < s->nbps; ++i) + dbg_completion_add_u64(out, prefix, prefix_len, (uint64_t)s->bps[i].id); +} + +static void dbg_complete(void* user, const char* line, size_t cursor, + DriverLineCompletionList* out) { + DbgState* s = (DbgState*)user; + char cmd[WORD_CAP]; + char arg1[WORD_CAP]; + size_t start, end; + size_t prefix_len; + const char* prefix; + + dbg_word_bounds(line, cursor, &start, &end); + out->replace_start = start; + out->replace_end = end; + prefix = line + start; + prefix_len = cursor > start ? cursor - start : 0; + + dbg_read_word_at(line, 0, cmd, sizeof(cmd)); + if (start == 0 || cmd[0] == '\0') { + dbg_complete_commands(out, prefix, prefix_len); + return; + } + + if (driver_streq(cmd, "info")) { + dbg_read_word_at(line, start == 0 ? 0 : driver_strlen(cmd), arg1, + sizeof(arg1)); + if (arg1[0] == '\0' || start <= driver_strlen(cmd) + 1u) { + dbg_complete_info(out, prefix, prefix_len); + return; + } + } + + if (driver_streq(cmd, ":language") || driver_streq(cmd, ":lang") || + driver_streq(cmd, "language")) { + dbg_complete_languages(out, prefix, prefix_len); + return; + } + if (driver_streq(cmd, "jit") || driver_streq(cmd, "edit") || + driver_streq(cmd, "e")) { + dbg_complete_languages(out, prefix, prefix_len); + dbg_complete_symbols(s, out, prefix, prefix_len, 1, 0); + return; + } + if (driver_streq(cmd, "b") || driver_streq(cmd, "break")) { + dbg_complete_symbols(s, out, prefix, prefix_len, 1, 0); + dbg_complete_files(s, out, prefix, prefix_len); + return; + } + if (driver_streq(cmd, "list") || driver_streq(cmd, "l")) { + dbg_complete_files(s, out, prefix, prefix_len); + return; + } + if (driver_streq(cmd, "p") || driver_streq(cmd, "print") || + driver_streq(cmd, "set")) { + dbg_complete_locals(s, out, prefix, prefix_len); + dbg_complete_symbols(s, out, prefix, prefix_len, 0, 1); + return; + } + if (driver_streq(cmd, "d") || driver_streq(cmd, "delete") || + driver_streq(cmd, "enable") || driver_streq(cmd, "disable") || + driver_streq(cmd, "ignore")) { + dbg_complete_breakpoint_ids(s, out, prefix, prefix_len); + return; + } + + dbg_complete_locals(s, out, prefix, prefix_len); + dbg_complete_symbols(s, out, prefix, prefix_len, 1, 1); +} + +/* ============================================================ * REPL * ============================================================ */ @@ -2819,9 +3080,8 @@ static void dbg_repl(DbgState* s) { driver_printf("cfree dbg — 'h' for help, 'q' to quit\n"); for (;;) { int n; - driver_printf("(cfree) "); - driver_flush_stdout(); - n = driver_read_line(line, sizeof(line)); + n = driver_read_line_edit(s->env, "(cfree) ", line, sizeof(line), + &s->line_history, dbg_complete, s); if (n == 0) { /* EOF — Ctrl-D */ driver_printf("\n"); return; @@ -2930,6 +3190,7 @@ int driver_dbg(int argc, char** argv) { dbg_repl(&st); + driver_line_history_fini(&env, &st.line_history); dbg_bps_release_all(&st); dbg_compile_sessions_release(&st); if (st.dwarf) cfree_dwarf_free(st.dwarf); diff --git a/driver/env.h b/driver/env.h @@ -173,6 +173,38 @@ void driver_release_bytes(const CfreeFileIO*, DriverLoad*); * up to the next newline is consumed silently. */ int driver_read_line(char* buf, size_t cap); +typedef struct DriverLineHistory { + char** items; + size_t* sizes; + uint32_t count; + uint32_t cap; +} DriverLineHistory; + +typedef struct DriverLineCompletion { + char* text; + size_t size; +} DriverLineCompletion; + +typedef struct DriverLineCompletionList { + DriverEnv* env; + DriverLineCompletion* items; + uint32_t count; + uint32_t cap; + size_t replace_start; + size_t replace_end; +} DriverLineCompletionList; + +typedef void (*DriverLineCompleteFn)(void* user, const char* line, + size_t cursor, + DriverLineCompletionList* out); + +int driver_line_completion_add(DriverLineCompletionList*, const char* text, + size_t len); +int driver_read_line_edit(DriverEnv*, const char* prompt, char* buf, + size_t cap, DriverLineHistory*, + DriverLineCompleteFn, void* complete_user); +void driver_line_history_fini(DriverEnv*, DriverLineHistory*); + /* Flush the host stdout. The dbg REPL prompt has no trailing newline, so * without an explicit flush the prompt stays buffered until the next * line of output. */ diff --git a/driver/env/common.c b/driver/env/common.c @@ -206,3 +206,48 @@ void driver_printf(const char* fmt, ...) { void driver_flush_stdout(void) { fflush(stdout); } const char* driver_getenv(const char* name) { return getenv(name); } + +int driver_line_completion_add(DriverLineCompletionList* l, const char* text, + size_t len) { + DriverLineCompletion* ni; + char* copy; + uint32_t nc; + size_t old_size; + size_t new_size; + if (!l || !l->env || !text) return 1; + if (l->count == l->cap) { + nc = l->cap ? l->cap * 2u : 16u; + old_size = (size_t)l->cap * sizeof(*l->items); + new_size = (size_t)nc * sizeof(*l->items); + ni = (DriverLineCompletion*)l->env->heap->realloc( + l->env->heap, l->items, old_size, new_size, + _Alignof(DriverLineCompletion)); + if (!ni) return 1; + l->items = ni; + l->cap = nc; + } + copy = (char*)driver_alloc(l->env, len + 1u); + if (!copy) return 1; + driver_memcpy(copy, text, len); + copy[len] = '\0'; + l->items[l->count].text = copy; + l->items[l->count].size = len + 1u; + l->count++; + return 0; +} + +void driver_line_history_fini(DriverEnv* env, DriverLineHistory* h) { + uint32_t i; + if (!env || !h) return; + for (i = 0; i < h->count; ++i) { + if (h->items && h->items[i]) driver_free(env, h->items[i], h->sizes[i]); + } + if (h->items) + driver_free(env, h->items, (size_t)h->cap * sizeof(*h->items)); + if (h->sizes) + driver_free(env, h->sizes, (size_t)h->cap * sizeof(*h->sizes)); + { + DriverLineHistory z = {0}; + *h = z; + } +} diff --git a/driver/env/posix.c b/driver/env/posix.c @@ -15,6 +15,7 @@ #include <sys/mman.h> #include <sys/stat.h> #include <sys/wait.h> +#include <termios.h> #include <time.h> #include <unistd.h> @@ -660,6 +661,341 @@ int driver_read_line(char* buf, size_t cap) { } } +static void line_completion_list_fini(DriverLineCompletionList* l) { + uint32_t i; + if (!l || !l->env) return; + for (i = 0; i < l->count; ++i) { + if (l->items[i].text) + driver_free(l->env, l->items[i].text, l->items[i].size); + } + if (l->items) + driver_free(l->env, l->items, (size_t)l->cap * sizeof(*l->items)); +} + +static int line_history_add(DriverEnv* env, DriverLineHistory* h, + const char* line, size_t len) { + char** ni; + size_t* ns; + char* copy; + uint32_t nc; + size_t old_items_size; + size_t new_items_size; + size_t old_sizes_size; + size_t new_sizes_size; + if (!env || !h || !line || len == 0) return 0; + if (h->count > 0 && h->items[h->count - 1] && + strlen(h->items[h->count - 1]) == len && + memcmp(h->items[h->count - 1], line, len) == 0) + return 0; + if (h->count == h->cap) { + nc = h->cap ? h->cap * 2u : 32u; + old_items_size = (size_t)h->cap * sizeof(*h->items); + new_items_size = (size_t)nc * sizeof(*h->items); + old_sizes_size = (size_t)h->cap * sizeof(*h->sizes); + new_sizes_size = (size_t)nc * sizeof(*h->sizes); + ni = (char**)env->heap->realloc(env->heap, h->items, old_items_size, + new_items_size, _Alignof(char*)); + if (!ni) return 1; + h->items = ni; + ns = (size_t*)env->heap->realloc(env->heap, h->sizes, old_sizes_size, + new_sizes_size, _Alignof(size_t)); + if (!ns) return 1; + h->sizes = ns; + h->cap = nc; + } + copy = (char*)driver_alloc(env, len + 1u); + if (!copy) return 1; + memcpy(copy, line, len); + copy[len] = '\0'; + h->items[h->count] = copy; + h->sizes[h->count] = len + 1u; + h->count++; + return 0; +} + +static void line_redraw(const char* prompt, const char* buf, size_t len, + size_t cursor) { + size_t back = len - cursor; + fputc('\r', stdout); + fputs(prompt, stdout); + if (len) fwrite(buf, 1, len, stdout); + fputs("\033[K", stdout); + if (back) fprintf(stdout, "\033[%zuD", back); + fflush(stdout); +} + +static void line_set(char* buf, size_t cap, size_t* len, size_t* cursor, + const char* src) { + size_t n = strlen(src); + if (n >= cap) n = cap - 1u; + memmove(buf, src, n); + buf[n] = '\0'; + *len = n; + *cursor = n; +} + +static int line_replace(char* buf, size_t cap, size_t* len, size_t* cursor, + size_t start, size_t end, const char* rep, + size_t rep_len) { + size_t tail; + if (start > end || end > *len) return 1; + tail = *len - end; + if (start + rep_len + tail + 1u > cap) return 1; + memmove(buf + start + rep_len, buf + end, tail + 1u); + if (rep_len) memcpy(buf + start, rep, rep_len); + *len = start + rep_len + tail; + *cursor = start + rep_len; + return 0; +} + +static size_t line_common_prefix(const DriverLineCompletionList* l) { + size_t n; + uint32_t i; + if (!l || l->count == 0) return 0; + n = strlen(l->items[0].text); + for (i = 1; i < l->count; ++i) { + size_t j = 0; + const char* s = l->items[i].text; + while (j < n && s[j] && s[j] == l->items[0].text[j]) ++j; + n = j; + } + return n; +} + +static void line_default_complete_range(const char* buf, size_t cursor, + size_t* start, size_t* end) { + size_t s = cursor; + while (s > 0 && buf[s - 1] != ' ' && buf[s - 1] != '\t') --s; + *start = s; + *end = cursor; +} + +static void line_complete(DriverEnv* env, char* buf, size_t cap, size_t* len, + size_t* cursor, const char* prompt, + DriverLineCompleteFn complete, void* complete_user) { + DriverLineCompletionList list; + size_t common; + size_t cur_len; + uint32_t i; + if (!complete) return; + { + DriverLineCompletionList z = {0}; + list = z; + } + list.env = env; + line_default_complete_range(buf, *cursor, &list.replace_start, + &list.replace_end); + complete(complete_user, buf, *cursor, &list); + if (list.replace_end > *len) list.replace_end = *len; + if (list.replace_start > list.replace_end) + list.replace_start = list.replace_end; + if (list.count == 1) { + size_t rn = strlen(list.items[0].text); + if (line_replace(buf, cap, len, cursor, list.replace_start, + list.replace_end, list.items[0].text, rn) != 0) + fputc('\a', stdout); + line_redraw(prompt, buf, *len, *cursor); + line_completion_list_fini(&list); + return; + } + if (list.count > 1) { + common = line_common_prefix(&list); + cur_len = *cursor > list.replace_start ? *cursor - list.replace_start : 0; + if (common > cur_len) { + if (line_replace(buf, cap, len, cursor, list.replace_start, + list.replace_end, list.items[0].text, common) != 0) + fputc('\a', stdout); + line_redraw(prompt, buf, *len, *cursor); + } else { + fputc('\n', stdout); + for (i = 0; i < list.count; ++i) + fprintf(stdout, " %s\n", list.items[i].text); + line_redraw(prompt, buf, *len, *cursor); + } + } else { + fputc('\a', stdout); + fflush(stdout); + } + line_completion_list_fini(&list); +} + +int driver_read_line_edit(DriverEnv* env, const char* prompt, char* buf, + size_t cap, DriverLineHistory* hist, + DriverLineCompleteFn complete, + void* complete_user) { + struct termios orig; + struct termios raw; + char* saved = NULL; + size_t saved_size = 0; + size_t len = 0; + size_t cursor = 0; + uint32_t hist_pos = 0; + int have_saved = 0; + int raw_enabled = 0; + int out_rc = -1; + + if (!buf || cap < 2) return -1; + if (!prompt) prompt = ""; + if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) { + int n; + fputs(prompt, stdout); + fflush(stdout); + n = driver_read_line(buf, cap); + if (n > 0 && hist) (void)line_history_add(env, hist, buf, (size_t)n); + return n; + } + + if (tcgetattr(STDIN_FILENO, &orig) != 0) goto out; + raw = orig; + raw.c_lflag &= (tcflag_t) ~(ICANON | ECHO | IEXTEN); + raw.c_iflag &= (tcflag_t) ~(IXON); + raw.c_cc[VMIN] = 1; + raw.c_cc[VTIME] = 0; + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) != 0) goto out; + raw_enabled = 1; + + saved = (char*)driver_alloc(env, cap); + if (!saved) goto out; + saved_size = cap; + hist_pos = hist ? hist->count : 0; + buf[0] = '\0'; + + fputs(prompt, stdout); + fflush(stdout); + for (;;) { + unsigned char c; + ssize_t r = read(STDIN_FILENO, &c, 1); + if (r < 0) { + if (errno == EINTR) { + out_rc = -2; + goto out; + } + out_rc = -1; + goto out; + } + if (r == 0) { + out_rc = len ? (int)len : 0; + goto out; + } + if (c == '\r' || c == '\n') { + buf[len] = '\0'; + fputc('\n', stdout); + if (hist) (void)line_history_add(env, hist, buf, len); + out_rc = (int)len; + goto out; + } + if (c == 4) { + if (len == 0) { + buf[0] = '\0'; + out_rc = 0; + goto out; + } + continue; + } + if (c == '\t') { + buf[len] = '\0'; + line_complete(env, buf, cap, &len, &cursor, prompt, complete, + complete_user); + continue; + } + if (c == 1) { + cursor = 0; + line_redraw(prompt, buf, len, cursor); + continue; + } + if (c == 5) { + cursor = len; + line_redraw(prompt, buf, len, cursor); + continue; + } + if (c == 11) { + buf[cursor] = '\0'; + len = cursor; + line_redraw(prompt, buf, len, cursor); + continue; + } + if (c == 127 || c == 8) { + if (cursor == 0) { + fputc('\a', stdout); + fflush(stdout); + continue; + } + memmove(buf + cursor - 1u, buf + cursor, len - cursor + 1u); + --cursor; + --len; + line_redraw(prompt, buf, len, cursor); + continue; + } + if (c == 27) { + unsigned char seq[3]; + ssize_t r1 = read(STDIN_FILENO, seq, 1); + ssize_t r2 = read(STDIN_FILENO, seq + 1, 1); + if (r1 != 1 || r2 != 1 || seq[0] != '[') continue; + if (seq[1] == 'A') { + if (hist && hist->count > 0 && hist_pos > 0) { + if (!have_saved) { + memcpy(saved, buf, len + 1u); + have_saved = 1; + } + --hist_pos; + line_set(buf, cap, &len, &cursor, hist->items[hist_pos]); + line_redraw(prompt, buf, len, cursor); + } + } else if (seq[1] == 'B') { + if (hist && have_saved && hist_pos < hist->count) { + ++hist_pos; + if (hist_pos == hist->count) + line_set(buf, cap, &len, &cursor, saved); + else + line_set(buf, cap, &len, &cursor, hist->items[hist_pos]); + line_redraw(prompt, buf, len, cursor); + } + } else if (seq[1] == 'C') { + if (cursor < len) { + ++cursor; + line_redraw(prompt, buf, len, cursor); + } + } else if (seq[1] == 'D') { + if (cursor > 0) { + --cursor; + line_redraw(prompt, buf, len, cursor); + } + } else if (seq[1] == 'H') { + cursor = 0; + line_redraw(prompt, buf, len, cursor); + } else if (seq[1] == 'F') { + cursor = len; + line_redraw(prompt, buf, len, cursor); + } else if (seq[1] >= '1' && seq[1] <= '9') { + ssize_t r3 = read(STDIN_FILENO, seq + 2, 1); + if (r3 == 1 && seq[1] == '3' && seq[2] == '~' && cursor < len) { + memmove(buf + cursor, buf + cursor + 1u, len - cursor); + --len; + line_redraw(prompt, buf, len, cursor); + } + } + continue; + } + if (c >= 32 && c != 127) { + if (len + 1u >= cap) { + fputc('\a', stdout); + fflush(stdout); + continue; + } + memmove(buf + cursor + 1u, buf + cursor, len - cursor + 1u); + buf[cursor] = (char)c; + ++cursor; + ++len; + line_redraw(prompt, buf, len, cursor); + } + } + +out: + if (raw_enabled) tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig); + if (saved) driver_free(env, saved, saved_size); + return out_rc; +} + /* ---------------- dlsym resolver ---------------- */ void* driver_dlsym_resolver(void* user, CfreeSlice name_s) { diff --git a/driver/env/windows.c b/driver/env/windows.c @@ -842,6 +842,57 @@ int driver_read_line(char* buf, size_t cap) { } } +static int win_line_history_add(DriverEnv* env, DriverLineHistory* h, + const char* line, size_t len) { + char** ni; + size_t* ns; + char* copy; + uint32_t nc; + if (!env || !h || !line || len == 0) return 0; + if (h->count == h->cap) { + size_t old_items = (size_t)h->cap * sizeof(*h->items); + size_t new_items; + size_t old_sizes = (size_t)h->cap * sizeof(*h->sizes); + size_t new_sizes; + nc = h->cap ? h->cap * 2u : 32u; + new_items = (size_t)nc * sizeof(*h->items); + new_sizes = (size_t)nc * sizeof(*h->sizes); + ni = (char**)env->heap->realloc(env->heap, h->items, old_items, new_items, + _Alignof(char*)); + if (!ni) return 1; + h->items = ni; + ns = (size_t*)env->heap->realloc(env->heap, h->sizes, old_sizes, new_sizes, + _Alignof(size_t)); + if (!ns) return 1; + h->sizes = ns; + h->cap = nc; + } + copy = (char*)driver_alloc(env, len + 1u); + if (!copy) return 1; + memcpy(copy, line, len); + copy[len] = '\0'; + h->items[h->count] = copy; + h->sizes[h->count] = len + 1u; + h->count++; + return 0; +} + +int driver_read_line_edit(DriverEnv* env, const char* prompt, char* buf, + size_t cap, DriverLineHistory* hist, + DriverLineCompleteFn complete, + void* complete_user) { + int n; + (void)complete; + (void)complete_user; + if (prompt && *prompt) { + fputs(prompt, stdout); + fflush(stdout); + } + n = driver_read_line(buf, cap); + if (n > 0 && hist) (void)win_line_history_add(env, hist, buf, (size_t)n); + return n; +} + /* ============================================================ * dlsym via GetProcAddress over loaded modules * ============================================================ */