diff --git a/janet.1 b/janet.1 index b2afd7c4..6e03808c 100644 --- a/janet.1 +++ b/janet.1 @@ -272,6 +272,11 @@ This variable does nothing in the default configuration of Janet, as PRF is disa cannot be defined for this variable to have an effect. .RE +.B JANET_HISTFILE +.RS +A file location to use for the default shell's REPL history. This relative path is where commands are persisted between sessions. +.RE + .B NO_COLOR .RS Turn off color by default in the repl and in the error handler of scripts. This can be changed at runtime diff --git a/src/core/io.c b/src/core/io.c index b7ff524f..52eb9f73 100644 --- a/src/core/io.c +++ b/src/core/io.c @@ -718,6 +718,18 @@ JANET_CORE_FN(cfun_io_eflush, return janet_wrap_nil(); } +void janet_xprintf(FILE *file, const char *format, ...) { + va_list args; + va_start(args, format); + JanetBuffer buffer; + int32_t len = 0; + while (format[len]) len++; + janet_buffer_init(&buffer, len); + janet_formatbv(&buffer, format, args); + fwrite(buffer.data, buffer.count, 1, file); + janet_buffer_deinit(&buffer); +} + void janet_dynprintf(const char *name, FILE *dflt_file, const char *format, ...) { va_list args; va_start(args, format); diff --git a/src/include/janet.h b/src/include/janet.h index 495a78c2..d6d7fe1f 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -1439,6 +1439,7 @@ JANET_API void janet_loop(void); * } else { * janet_schedule(interrupted_fiber, janet_wrap_nil()); * } + * janet_interpreter_interrupt_handled(NULL); * } * } * @@ -1889,7 +1890,7 @@ JANET_API void janet_vm_free(JanetVM *vm); JANET_API void janet_vm_save(JanetVM *into); JANET_API void janet_vm_load(JanetVM *from); JANET_API void janet_interpreter_interrupt(JanetVM *vm); -JANET_API void janet_interpreter_interrupt_handled(JanetVM *vm); +JANET_API void janet_interpreter_interrupt_handled(JanetVM *vm); /* Call this after running interrupt handler */ JANET_API JanetSignal janet_continue(JanetFiber *fiber, Janet in, Janet *out); JANET_API JanetSignal janet_continue_signal(JanetFiber *fiber, Janet in, Janet *out, JanetSignal sig); JANET_API JanetSignal janet_pcall(JanetFunction *fun, int32_t argn, const Janet *argv, Janet *out, JanetFiber **f); @@ -2051,6 +2052,7 @@ JANET_NO_RETURN JANET_API void janet_panicv(Janet message); JANET_NO_RETURN JANET_API void janet_panic(const char *message); JANET_NO_RETURN JANET_API void janet_panics(JanetString message); JANET_NO_RETURN JANET_API void janet_panicf(const char *format, ...); +JANET_API void janet_xprintf(FILE *file, const char *format, ...); JANET_API void janet_dynprintf(const char *name, FILE *dflt_file, const char *format, ...); #define janet_printf(...) janet_dynprintf("out", stdout, __VA_ARGS__) #define janet_eprintf(...) janet_dynprintf("err", stderr, __VA_ARGS__) diff --git a/src/mainclient/shell.c b/src/mainclient/shell.c index a665acca..4828c444 100644 --- a/src/mainclient/shell.c +++ b/src/mainclient/shell.c @@ -96,9 +96,9 @@ static void simpleline(JanetBuffer *buffer) { #ifndef JANET_SIMPLE_GETLINE /* static state */ -#define JANET_LINE_MAX 1024 +#define JANET_LINE_MAX 4096 #define JANET_MATCH_MAX 256 -#define JANET_HISTORY_MAX 100 +#define JANET_HISTORY_MAX 1000 static JANET_THREAD_LOCAL int gbl_israwmode = 0; static JANET_THREAD_LOCAL const char *gbl_prompt = "> "; static JANET_THREAD_LOCAL int gbl_plen = 2; @@ -112,6 +112,8 @@ static JANET_THREAD_LOCAL int gbl_historyi = 0; static JANET_THREAD_LOCAL JanetByteView gbl_matches[JANET_MATCH_MAX]; static JANET_THREAD_LOCAL int gbl_match_count = 0; static JANET_THREAD_LOCAL int gbl_lines_below = 0; +static JANET_THREAD_LOCAL int gbl_history_loaded = 0; +static JANET_THREAD_LOCAL char *gbl_history_file = NULL; #endif /* Fallback */ @@ -430,6 +432,74 @@ static int insert(char c, int draw) { return 0; } +static void calc_history_file(void) { + char *hist = getenv("JANET_HISTFILE"); + if (hist) { + gbl_history_file = sdup(hist); + } else { + const char *home = (const char *) getenv("HOME"); + if (NULL == home) home = ""; + const char *filename = ".janet_history.jdn"; + size_t homelen = strlen(home); + size_t filelen = strlen(filename); + size_t buflen = homelen + filelen + 1; + char *buffer = janet_malloc(buflen + 1); + if (homelen) memcpy(buffer, home, homelen); + buffer[homelen] = '/'; + memcpy(buffer + homelen + 1, filename, filelen); + buffer[buflen] = '\0'; + gbl_history_file = buffer; + } + /* fprintf(stderr, "history_file: %s\n", gbl_history_file); */ +} + +static void loadhistory(void) { + if (gbl_history_loaded) return; + calc_history_file(); + gbl_history_loaded = 1; + FILE *history_file = fopen(gbl_history_file, "rb"); + if (NULL == history_file) return; + JanetParser p; + janet_parser_init(&p); + int c = 0; + while ((c = fgetc(history_file))) { + if (c == EOF) { + janet_parser_eof(&p); + } else { + janet_parser_consume(&p, c); + } + + while (janet_parser_has_more(&p) && gbl_history_count < JANET_HISTORY_MAX) { + if (janet_parser_status(&p) == JANET_PARSE_ERROR) { + janet_eprintf("bad history file: %s\n", janet_parser_error(&p)); + goto parsing_done; + } + Janet x = janet_parser_produce(&p); + const char *cstr = (const char *) janet_to_string(x); + if (cstr[0]) { /* Drop empty strings */ + gbl_history[gbl_history_count++] = sdup(cstr); + } + } + + if (c == EOF) break; + } +parsing_done: + janet_parser_deinit(&p); + gbl_historyi = 0; + fclose(history_file); +} + +static void savehistory(void) { + if (gbl_history_count <= 1 || !gbl_history_file) return; + FILE *history_file = fopen(gbl_history_file, "wb"); + for (int i = 0; i < gbl_history_count; i++) { + if (gbl_history[i][0]) { /* Drop empty strings */ + janet_xprintf(history_file, "%j\n", janet_cstringv(gbl_history[i])); + } + } + fclose(history_file); +} + static void historymove(int delta) { if (gbl_history_count > 1) { janet_free(gbl_history[gbl_historyi]); @@ -896,6 +966,7 @@ static int line() { case 3: /* ctrl-c */ clearlines(); norawmode(); + savehistory(); #ifdef _WIN32 ExitProcess(1); #else @@ -1089,17 +1160,18 @@ void janet_line_init() { } void janet_line_deinit() { - int i; norawmode(); - for (i = 0; i < gbl_history_count; i++) + for (int i = 0; i < gbl_history_count; i++) janet_free(gbl_history[i]); gbl_historyi = 0; + if (gbl_history_file) janet_free(gbl_history_file); } void janet_line_get(const char *p, JanetBuffer *buffer) { gbl_prompt = p; buffer->count = 0; gbl_historyi = 0; + loadhistory(); if (check_simpleline(buffer)) return; FILE *out = janet_dynfile("err", stderr); if (line()) { @@ -1130,6 +1202,13 @@ static void clear_at_exit(void) { * Entry */ +static void cleanup_at_exit(void) { + /* Deinitialize vm */ + savehistory(); + janet_deinit(); + janet_line_deinit(); +} + int main(int argc, char **argv) { int i, status; JanetArray *args; @@ -1146,6 +1225,7 @@ int main(int argc, char **argv) { #if !defined(JANET_SIMPLE_GETLINE) atexit(clear_at_exit); #endif + atexit(cleanup_at_exit); #if defined(JANET_PRF) uint8_t hash_key[JANET_HASH_KEY_SIZE + 1]; @@ -1193,9 +1273,6 @@ int main(int argc, char **argv) { /* Run the fiber in an event loop */ status = janet_loop_fiber(fiber); - /* Deinitialize vm */ - janet_deinit(); - janet_line_deinit(); - + /* Deinit moved to atexit */ return status; }