diff --git a/third_party/linenoise/linenoise.c b/third_party/linenoise/linenoise.c index c49e7b532..3a04e79d4 100644 --- a/third_party/linenoise/linenoise.c +++ b/third_party/linenoise/linenoise.c @@ -793,61 +793,63 @@ static char linenoiseGrow(struct linenoiseState *ls, size_t n) { return 1; } -/* This is an helper function for linenoiseEdit() and is called when the - * user types the key in order to complete the string currently in the - * input. - * - * The state of the editing is encapsulated into the pointed linenoiseState - * structure as described in the structure definition. */ +static size_t linenoiseMaxCompletionLength(linenoiseCompletions *lc) { + size_t i, n, m; + for (m = i = 0; i < lc->len; ++i) { + n = strlen(lc->cvec[i]); + m = MAX(n, m); + } + return m; +} + +/** + * Performs tab completion similar in behavior to bash and readline. + */ static ssize_t linenoiseCompleteLine(struct linenoiseState *ls, char *seq, int size) { ssize_t nread; - size_t i, n, stop; + struct abuf ab; linenoiseCompletions lc; struct linenoiseState saved; + size_t i, j, k, n, perline, itemlen; + // we know that the user pressed tab once nread = 0; - memset(&lc, 0, sizeof(lc)); + bzero(&lc, sizeof(lc)); completionCallback(ls->buf, &lc); - if (!lc.len) { - linenoiseBeep(); - } else { - i = 0; - stop = 0; - while (!stop) { - /* Show completion or original buffer */ - if (i < lc.len) { - saved = *ls; - ls->len = ls->pos = strlen(lc.cvec[i]); - ls->buf = lc.cvec[i]; - linenoiseRefreshLine(ls); - ls->len = saved.len; - ls->pos = saved.pos; - ls->buf = saved.buf; - } else { - linenoiseRefreshLine(ls); - } - if ((nread = linenoiseRead(ls->ifd, seq, size, ls)) <= 0) { - linenoiseFreeCompletions(&lc); - return -1; - } - switch (seq[0]) { - case '\t': - i = (i + 1) % (lc.len + 1); - if (i == lc.len) { - linenoiseBeep(); + if (lc.len == 1) { + // if there's a single completion, complete it, and return + n = strlen(lc.cvec[0]); + if (linenoiseGrow(ls, n + 1)) { + memcpy(ls->buf, lc.cvec[0], n + 1); + ls->len = ls->pos = n; + } + linenoiseRefreshLine(ls); + nread = linenoiseRead(ls->ifd, seq, size, ls); + } else if (lc.len > 1) { + // if there's a multiline completions, then do nothing and wait and + // see if the user presses tab again. if the user does this we then + // print ALL the completions, to above the editing line + nread = linenoiseRead(ls->ifd, seq, size, ls); + if (nread == 1 && seq[0] == '\t') { + itemlen = linenoiseMaxCompletionLength(&lc) + 4; + perline = MAX(1, (ls->ws.ws_col - 1) / itemlen); + abInit(&ab); + abAppends(&ab, "\r\033[K"); + for (i = 0; i < lc.len;) { + for (j = 0; i < lc.len && j < perline; ++j, ++i) { + n = GetMonospaceWidth(lc.cvec[i], strlen(lc.cvec[i]), 0); + abAppends(&ab, lc.cvec[i]); + for (k = n; k < itemlen; ++k) { + abAppendw(&ab, ' '); } - break; - default: - if (i < lc.len) { - n = strlen(lc.cvec[i]); - if (linenoiseGrow(ls, n + 1)) { - memcpy(ls->buf, lc.cvec[i], n + 1); - ls->len = ls->pos = n; - } - } - stop = 1; - break; + } + abAppendw(&ab, READ16LE("\r\n")); } + ab.len -= 2; + abAppends(&ab, "\n"); + linenoiseWriteStr(ls->ofd, ab.b); + linenoiseRefreshLine(ls); + abFree(&ab); } } linenoiseFreeCompletions(&lc); @@ -2074,13 +2076,16 @@ static int linenoiseFallback(const char *prompt, char **res) { * @return chomped allocated string of read line or null on eof/error */ char *linenoise(const char *prompt) { + bool rm; char *res; if (linenoiseFallback(prompt, &res)) return res; fflush(stdout); fflush(stdout); + rm = __replmode; __replmode = true; res = linenoiseRaw(prompt, fileno(stdin), fileno(stdout)); __replmode = false; + __replmode = rm; return res; } diff --git a/third_party/lua/README.cosmo b/third_party/lua/README.cosmo index daec0c54e..9cdfdeee5 100644 --- a/third_party/lua/README.cosmo +++ b/third_party/lua/README.cosmo @@ -20,9 +20,13 @@ PROVENANCE LOCAL MODIFICATIONS + Lua now uses a bestline REPL with bash-style code completion. + Integer literals such as `033` will now be interpreted as octal. The `\e` string literal escape sequence has been added, which is equivalent to `\27` (the Lua version of `\033`) or the ASCII ESC character. It may be used for teletypewriter control like having bold text, which can be encoded elegantly as `\e[1mHELLO\e[0m`. + + Added luaL_traceback2() for function parameters in traceback. diff --git a/third_party/lua/lua.main.c b/third_party/lua/lua.main.c index 51bc6a18c..efb67e72a 100644 --- a/third_party/lua/lua.main.c +++ b/third_party/lua/lua.main.c @@ -11,10 +11,11 @@ #include "libc/calls/struct/sigaction.h" #include "libc/dce.h" #include "libc/log/log.h" -#include "libc/runtime/gc.internal.h" +#include "libc/runtime/gc.h" #include "libc/runtime/stack.h" #include "libc/sysv/consts/exit.h" #include "libc/x/x.h" +#include "third_party/linenoise/linenoise.h" #include "third_party/lua/lauxlib.h" #include "third_party/lua/lprefix.h" #include "third_party/lua/lua.h" @@ -378,68 +379,75 @@ static int handle_luainit (lua_State *L) { #define LUA_MAXINPUT 512 #endif +static bool lua_stdin_is_tty(void) { + return isatty(0); +} -/* -** lua_stdin_is_tty detects whether the standard input is a 'tty' (that -** is, whether we're running lua interactively). -*/ -#if !defined(lua_stdin_is_tty) /* { */ +static bool lua_istartswith(const char *s, const char *prefix) { + for (;;) { + if (!*prefix) return true; + if (!*s) return false; + if (tolower(*s++) != tolower(*prefix++)) return false; + } +} -#if defined(LUA_USE_POSIX) /* { */ +static void lua_readline_addcompletion(linenoiseCompletions *c, char *s) { + char **p = c->cvec; + size_t n = c->len + 1; + if ((p = realloc(p, n * sizeof(*p)))) { + p[n - 1] = s; + c->cvec = p; + c->len = n; + } +} -#define lua_stdin_is_tty() isatty(0) +static void lua_readline_completions(const char *p, linenoiseCompletions *c) { + lua_State *L; + const char *name; + L = globalL; + lua_pushglobaltable(L); + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + name = lua_tostring(L, -2); + if (lua_istartswith(name, p)) { + lua_readline_addcompletion(c, strdup(name)); + } + lua_pop(L, 1); + } + lua_pop(L, 1); +} -#elif defined(LUA_USE_WINDOWS) /* }{ */ +static char *lua_readline_hint(const char *p, const char **ansi1, const char **ansi2) { + char *h = 0; + linenoiseCompletions c = {0}; + lua_readline_completions(p, &c); + if (c.len == 1) h = strdup(c.cvec[0] + strlen(p)); + linenoiseFreeCompletions(&c); + return h; +} +static void lua_initreadline(lua_State *L) { + histpath = xasprintf("%s/.%s_history", _gc(xhomedir()), LUA_PROGNAME); + linenoiseSetCompletionCallback(lua_readline_completions); + linenoiseSetHintsCallback(lua_readline_hint); + linenoiseSetFreeHintsCallback(free); +} -#define lua_stdin_is_tty() _isatty(_fileno(stdin)) +static int lua_readline(lua_State *L, char **b, const char *prompt) { + globalL = L; + linenoiseHistoryLoad(histpath); + return !!(*b = linenoise(prompt)); +} -#else /* }{ */ - -/* ISO C definition */ -#define lua_stdin_is_tty() 1 /* assume stdin is a tty */ - -#endif /* } */ - -#endif /* } */ - - -/* -** lua_readline defines how to show a prompt and then read a line from -** the standard input. -** lua_saveline defines how to "save" a read line in a "history". -** lua_freeline defines how to free a line read by lua_readline. -*/ -#if !defined(lua_readline) /* { */ - -#if defined(LUA_USE_READLINE) /* { */ - -#define lua_initreadline(L) ((void)L, rl_readline_name="lua") -#define lua_readline(L,b,p) ((void)L, ((b)=readline(p)) != NULL) -#define lua_saveline(L,line) ((void)L, add_history(line)) -#define lua_freeline(L,b) ((void)L, free(b)) - -#elif defined(LUA_USE_LINENOISE) -#include "third_party/linenoise/linenoise.h" - -#define lua_initreadline(L) (histpath=xasprintf("%s/.%s_history",gc(xhomedir()),LUA_PROGNAME)) -#define lua_readline(L,b,p) ((void)L, linenoiseHistoryLoad(histpath), ((b)=linenoise(p)) != NULL) -#define lua_saveline(L,line) ((void)L, linenoiseHistoryLoad(histpath), linenoiseHistoryAdd(line), linenoiseHistorySave(histpath)) -#define lua_freeline(L,b) ((void)L, free(b)) - -#else /* }{ */ - -#define lua_initreadline(L) ((void)L) -#define lua_readline(L,b,p) \ - ((void)L, fputs(p, stdout), fflush(stdout), /* show prompt */ \ - fgets(b, LUA_MAXINPUT, stdin) != NULL) /* get line */ -#define lua_saveline(L,line) { (void)L; (void)line; } -#define lua_freeline(L,b) { (void)L; (void)b; } - -#endif /* } */ - -#endif /* } */ +static void lua_saveline(lua_State *L, const char *line) { + linenoiseHistoryLoad(histpath); + linenoiseHistoryAdd(line); + linenoiseHistorySave(histpath); +} +static void lua_freeline (lua_State *L, char *b) { + free(b); +} /* ** Return the string to be used as a prompt by the interpreter. Leave @@ -478,7 +486,6 @@ static int incomplete (lua_State *L, int status) { return 0; /* else... */ } - /* ** Prompt the user, read a line, and push it into the Lua stack. */ @@ -487,7 +494,7 @@ static int pushline (lua_State *L, int firstline) { char *b = buffer; size_t l; const char *prmt = get_prompt(L, firstline); - int readstatus = lua_readline(L, b, prmt); + int readstatus = lua_readline(L, &b, prmt); if (readstatus == 0) return 0; /* no input (prompt will be popped by caller) */ lua_pop(L, 1); /* remove prompt */ @@ -647,6 +654,7 @@ static int pmain (lua_State *L) { int main (int argc, char **argv) { + ShowCrashReports(); int status, result; lua_State *L; /* if (IsModeDbg()) ShowCrashReports(); */ diff --git a/third_party/lua/lua.mk b/third_party/lua/lua.mk index 23561cc2b..52934f9b5 100644 --- a/third_party/lua/lua.mk +++ b/third_party/lua/lua.mk @@ -31,6 +31,7 @@ THIRD_PARTY_LUA_DIRECTDEPS = \ LIBC_STDIO \ LIBC_STR \ LIBC_SYSV \ + LIBC_LOG \ LIBC_TIME \ LIBC_X \ LIBC_TINYMATH \