Implement programmable prompt (issue #09)

Replace static _PROMPT/_PROMPT2 with dynamic __prompt(exitcode)/__prompt2(exitcode)
functions. Fallback chain: __prompt() → _PROMPT → "> ". Install a default __prompt
that shows the current directory with ~ abbreviation. Track last exit code from the
REPL loop and pass it to the prompt function.
This commit is contained in:
Cormac Shannon
2026-03-03 23:19:09 +00:00
parent bbc0f24009
commit e8756d5d78
3 changed files with 179 additions and 5 deletions

47
lua.c
View File

@@ -541,19 +541,38 @@ static void lua_initreadline (lua_State *L) {
#endif /* } */
static int last_exit_code = 0; /* tracks exit code for __prompt() */
/*
** Return the string to be used as a prompt by the interpreter. Leave
** the string (or nil, if using the default value) on the stack, to keep
** it anchored.
**
** Fallback chain: __prompt()/__prompt2() → _PROMPT/_PROMPT2 → "> "/">> "
*/
static const char *get_prompt (lua_State *L, int firstline) {
if (lua_getglobal(L, firstline ? "_PROMPT" : "_PROMPT2") == LUA_TNIL)
return (firstline ? LUA_PROMPT : LUA_PROMPT2); /* use the default */
else { /* apply 'tostring' over the value */
const char *fname = firstline ? "__prompt" : "__prompt2";
if (lua_getglobal(L, fname) == LUA_TFUNCTION) {
lua_pushinteger(L, last_exit_code);
if (lua_pcall(L, 1, 1, 0) == LUA_OK) {
const char *p = lua_tostring(L, -1);
if (p != NULL) return p; /* string stays on stack, anchored */
}
lua_pop(L, 1); /* pop error message or non-string result */
} else {
lua_pop(L, 1); /* pop the non-function value */
}
/* fallback: check _PROMPT/_PROMPT2 globals (standard Lua compat) */
if (lua_getglobal(L, firstline ? "_PROMPT" : "_PROMPT2") != LUA_TNIL) {
const char *p = luaL_tolstring(L, -1, NULL);
lua_remove(L, -2); /* remove original value */
return p;
}
lua_pop(L, 1); /* pop nil */
/* final fallback: push default string so pushline can pop it */
lua_pushstring(L, firstline ? LUA_PROMPT : LUA_PROMPT2);
return lua_tostring(L, -1);
}
/* mark in error messages for incomplete statements */
@@ -673,6 +692,26 @@ static int loadline (lua_State *L) {
}
/*
** Install default __prompt function if not already defined (e.g. by config).
** Shows current directory with ~ substitution for HOME.
*/
static void install_default_prompt (lua_State *L) {
if (lua_getglobal(L, "__prompt") == LUA_TNIL) {
luaL_dostring(L,
"function __prompt(exitcode)\n"
" local cwd = os.getenv('PWD') or '?'\n"
" local home = os.getenv('HOME') or ''\n"
" if home ~= '' and cwd:sub(1, #home) == home then\n"
" cwd = '~' .. cwd:sub(#home + 1)\n"
" end\n"
" return cwd .. '> '\n"
"end\n");
}
lua_pop(L, 1); /* pop the getglobal result */
}
/*
** Prints (calling the Lua 'print' function) any values on the stack
*/
@@ -698,6 +737,7 @@ static void doREPL (lua_State *L) {
const char *oldprogname = progname;
progname = NULL; /* no 'progname' on errors in interactive mode */
lua_initreadline(L);
install_default_prompt(L);
setsignal(SIGINT, repl_sigint_handler);
if (sigsetjmp(repl_jmp, 1)) {
/* Ctrl-C during input — print newline, clear stack, re-prompt */
@@ -708,6 +748,7 @@ static void doREPL (lua_State *L) {
while ((status = loadline(L)) != -1) {
if (status == LUA_OK)
status = docall(L, 0, LUA_MULTRET);
last_exit_code = status; /* track for __prompt() */
if (status == LUA_OK) l_print(L);
else report(L, status);
setsignal(SIGINT, repl_sigint_handler); /* re-install after docall */