Fixed some REPL issues like the history and version bump to 0.5.
This commit is contained in:
parent
32276ee898
commit
f60c38ca06
2 changed files with 136 additions and 15 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.16)
|
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 11)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
|
||||||
149
src/fun.c
149
src/fun.c
|
|
@ -52,6 +52,54 @@
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#endif
|
#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
|
#ifndef _WIN32
|
||||||
static struct termios g_orig_tios;
|
static struct termios g_orig_tios;
|
||||||
static int g_raw_enabled = 0;
|
static int g_raw_enabled = 0;
|
||||||
|
|
@ -236,7 +284,7 @@ static int complete_load_path(char *buf, size_t *len_io) {
|
||||||
return 1;
|
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) {
|
static int read_line_edit(char *out, size_t out_cap, const char *prompt) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
/* fallback to standard fgets on Windows */
|
/* 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;
|
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 (;;) {
|
for (;;) {
|
||||||
int ch = getchar();
|
int ch = getchar();
|
||||||
if (ch == EOF) { repl_disable_raw(); return 0; }
|
if (ch == EOF) { repl_disable_raw(); return 0; }
|
||||||
|
|
||||||
if (ch == '\r' || ch == '\n') {
|
if (ch == '\r' || ch == '\n') {
|
||||||
fputc('\n', stdout);
|
fputc('\n', stdout);
|
||||||
if (len + 1 < out_cap) {
|
if (len + 1 < out_cap) {
|
||||||
|
|
@ -263,13 +324,69 @@ static int read_line_edit(char *out, size_t out_cap, const char *prompt) {
|
||||||
} else {
|
} else {
|
||||||
out[len] = '\0';
|
out[len] = '\0';
|
||||||
}
|
}
|
||||||
|
/* add to in-memory history */
|
||||||
|
rl_hist_add(out);
|
||||||
repl_disable_raw();
|
repl_disable_raw();
|
||||||
return 1;
|
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) {
|
} else if (ch == 127 || ch == 8) {
|
||||||
/* backspace */
|
/* backspace */
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
/* if last char was multi-byte we still remove one byte */
|
|
||||||
len--;
|
len--;
|
||||||
|
out[len] = '\0';
|
||||||
|
/* efficient backspace */
|
||||||
fputs("\b \b", stdout);
|
fputs("\b \b", stdout);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -281,27 +398,20 @@ static int read_line_edit(char *out, size_t out_cap, const char *prompt) {
|
||||||
out[len] = '\0';
|
out[len] = '\0';
|
||||||
size_t newlen = len;
|
size_t newlen = len;
|
||||||
if (complete_load_path(out, &newlen)) {
|
if (complete_load_path(out, &newlen)) {
|
||||||
/* redraw line */
|
|
||||||
len = newlen;
|
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) {
|
} else if (ch >= 32 && ch <= 126) {
|
||||||
if (len + 1 < out_cap) {
|
if (len + 1 < out_cap) {
|
||||||
out[len++] = (char)ch;
|
out[len++] = (char)ch;
|
||||||
|
out[len] = '\0';
|
||||||
fputc(ch, stdout);
|
fputc(ch, stdout);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
/* when typing after navigating history, we are editing current line; reset hist_pos to end */
|
||||||
|
hist_pos = rl_count;
|
||||||
} else {
|
} else {
|
||||||
fputc('\a', stdout);
|
fputc('\a', stdout);
|
||||||
|
fflush(stdout);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* ignore other control characters */
|
/* ignore other control characters */
|
||||||
|
|
@ -685,9 +795,12 @@ int main(int argc, char **argv) {
|
||||||
if (!home) home = getenv("USERPROFILE"); /* Windows fallback */
|
if (!home) home = getenv("USERPROFILE"); /* Windows fallback */
|
||||||
if (home) {
|
if (home) {
|
||||||
snprintf(hist_path, sizeof(hist_path), "%s/.fun_history", 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+");
|
hist = fopen(hist_path, "a+");
|
||||||
} else {
|
} else {
|
||||||
snprintf(hist_path, sizeof(hist_path), ".fun_history");
|
snprintf(hist_path, sizeof(hist_path), ".fun_history");
|
||||||
|
rl_hist_load_file(hist_path);
|
||||||
hist = fopen(hist_path, "a+");
|
hist = fopen(hist_path, "a+");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -726,6 +839,14 @@ int main(int argc, char **argv) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
#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) */
|
/* Command handling (available anytime) */
|
||||||
if (line[0] == ':') {
|
if (line[0] == ':') {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue