From f60c38ca06f3bc940030738b2d4d6da7988ac251 Mon Sep 17 00:00:00 2001 From: hanez Date: Sat, 27 Sep 2025 08:31:37 +0200 Subject: [PATCH] Fixed some REPL issues like the history and version bump to 0.5. --- CMakeLists.txt | 2 +- src/fun.c | 149 ++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 136 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cdf9e7c..9eb30d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(fun VERSION 0.4.0 LANGUAGES C) +project(fun VERSION 0.5.0 LANGUAGES C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/src/fun.c b/src/fun.c index d42615e..3abbbfc 100644 --- a/src/fun.c +++ b/src/fun.c @@ -52,6 +52,54 @@ #include #endif +/* --------- REPL history (in-memory + file integration) ---------- */ +enum { RL_HIST_MAX = 1000 }; +static char *rl_hist[RL_HIST_MAX]; +static int rl_count = 0; + +/* last history entry (or NULL) */ +static const char* rl_hist_last(void) { + if (rl_count <= 0) return NULL; + return rl_hist[rl_count - 1]; +} + +/* add one line to in-memory history (without trailing newline), dedup consecutive */ +static void rl_hist_add(const char *s) { + if (!s) return; + size_t n = strlen(s); + /* strip single trailing newline if present */ + while (n > 0 && (s[n-1] == '\n' || s[n-1] == '\r')) n--; + if (n == 0) return; + /* dedupe consecutive identical */ + const char *last = rl_hist_last(); + if (last && strncmp(last, s, n) == 0 && last[n] == '\0') return; + + /* allocate and copy */ + char *cpy = (char*)malloc(n + 1); + if (!cpy) return; + memcpy(cpy, s, n); + cpy[n] = '\0'; + + if (rl_count == RL_HIST_MAX) { + free(rl_hist[0]); + memmove(&rl_hist[0], &rl_hist[1], sizeof(rl_hist[0]) * (RL_HIST_MAX - 1)); + rl_count--; + } + rl_hist[rl_count++] = cpy; +} + +/* preload history from file (one line per entry) */ +static void rl_hist_load_file(const char *path) { + if (!path) return; + FILE *f = fopen(path, "r"); + if (!f) return; + char line[4096]; + while (fgets(line, sizeof(line), f)) { + rl_hist_add(line); + } + fclose(f); +} + #ifndef _WIN32 static struct termios g_orig_tios; static int g_raw_enabled = 0; @@ -236,7 +284,7 @@ static int complete_load_path(char *buf, size_t *len_io) { return 1; } -/* Read one line with prompt, handling backspace and :load path completion */ +/* Read one line with prompt, handling backspace, Up/Down history and :load path completion */ static int read_line_edit(char *out, size_t out_cap, const char *prompt) { #ifdef _WIN32 /* fallback to standard fgets on Windows */ @@ -252,9 +300,22 @@ static int read_line_edit(char *out, size_t out_cap, const char *prompt) { } size_t len = 0; + int hist_pos = rl_count; /* rl_count means "editing current input", 0..rl_count-1 indexes history entries */ + char saved_current[4096]; + int has_saved = 0; + + #define RL_REDRAW() do { \ + fputc('\r', stdout); \ + if (prompt) fputs(prompt, stdout); \ + fwrite(out, 1, len, stdout); \ + fputs("\x1b[K", stdout); \ + fflush(stdout); \ + } while(0) + for (;;) { int ch = getchar(); if (ch == EOF) { repl_disable_raw(); return 0; } + if (ch == '\r' || ch == '\n') { fputc('\n', stdout); if (len + 1 < out_cap) { @@ -263,13 +324,69 @@ static int read_line_edit(char *out, size_t out_cap, const char *prompt) { } else { out[len] = '\0'; } + /* add to in-memory history */ + rl_hist_add(out); repl_disable_raw(); return 1; + } else if (ch == 27) { /* ESC sequence */ + int c1 = getchar(); + if (c1 == '[') { + int c2 = getchar(); + if (c2 == 'A') { /* Up */ + if (!has_saved) { /* save current line to restore on Down past newest */ + size_t sl = len < sizeof(saved_current) - 1 ? len : sizeof(saved_current) - 1; + memcpy(saved_current, out, sl); + saved_current[sl] = '\0'; + has_saved = 1; + } + if (hist_pos > 0) { + hist_pos--; + const char *h = rl_hist[hist_pos]; + size_t hl = strlen(h); + if (hl >= out_cap) hl = out_cap - 1; + memcpy(out, h, hl); + out[hl] = '\0'; + len = hl; + RL_REDRAW(); + } else { + fputc('\a', stdout); fflush(stdout); + } + } else if (c2 == 'B') { /* Down */ + if (hist_pos < rl_count) { + hist_pos++; + if (hist_pos == rl_count) { + /* restore saved current */ + if (has_saved) { + size_t hl = strlen(saved_current); + if (hl >= out_cap) hl = out_cap - 1; + memcpy(out, saved_current, hl); + out[hl] = '\0'; + len = hl; + } else { + len = 0; out[0] = '\0'; + } + } else { + const char *h = rl_hist[hist_pos]; + size_t hl = strlen(h); + if (hl >= out_cap) hl = out_cap - 1; + memcpy(out, h, hl); + out[hl] = '\0'; + len = hl; + } + RL_REDRAW(); + } else { + fputc('\a', stdout); fflush(stdout); + } + } else { + /* ignore other CSI sequences */ + } + } } else if (ch == 127 || ch == 8) { /* backspace */ if (len > 0) { - /* if last char was multi-byte we still remove one byte */ len--; + out[len] = '\0'; + /* efficient backspace */ fputs("\b \b", stdout); fflush(stdout); } else { @@ -281,27 +398,20 @@ static int read_line_edit(char *out, size_t out_cap, const char *prompt) { out[len] = '\0'; size_t newlen = len; if (complete_load_path(out, &newlen)) { - /* redraw line */ len = newlen; - /* carriage return + redraw prompt + buffer */ - fputc('\r', stdout); - if (prompt) fputs(prompt, stdout); - fwrite(out, 1, len, stdout); - fflush(stdout); - } else { - /* either no change or listed matches; redraw prompt+buffer anyway */ - fputc('\r', stdout); - if (prompt) fputs(prompt, stdout); - fwrite(out, 1, len, stdout); - fflush(stdout); } + RL_REDRAW(); } else if (ch >= 32 && ch <= 126) { if (len + 1 < out_cap) { out[len++] = (char)ch; + out[len] = '\0'; fputc(ch, stdout); fflush(stdout); + /* when typing after navigating history, we are editing current line; reset hist_pos to end */ + hist_pos = rl_count; } else { fputc('\a', stdout); + fflush(stdout); } } else { /* ignore other control characters */ @@ -685,9 +795,12 @@ int main(int argc, char **argv) { if (!home) home = getenv("USERPROFILE"); /* Windows fallback */ if (home) { snprintf(hist_path, sizeof(hist_path), "%s/.fun_history", home); + /* preload history into memory before opening for append */ + rl_hist_load_file(hist_path); hist = fopen(hist_path, "a+"); } else { snprintf(hist_path, sizeof(hist_path), ".fun_history"); + rl_hist_load_file(hist_path); hist = fopen(hist_path, "a+"); } @@ -726,6 +839,14 @@ int main(int argc, char **argv) { break; } #endif + /* Persist input line to history file (dedupe done in rl_hist_add; file stores as-is) */ + if (hist) { + /* Do not store pure newline lines */ + const char *lp = line; + int only_nl = 1; + while (*lp) { if (*lp != '\n' && *lp != '\r') { only_nl = 0; break; } lp++; } + if (!only_nl) append_history(hist, line); + } /* Command handling (available anytime) */ if (line[0] == ':') {