From b3aa1d9c63d83b7ef5b6a92acc458e59011c750b Mon Sep 17 00:00:00 2001 From: Cormac Shannon <> Date: Wed, 4 Mar 2026 23:53:04 +0000 Subject: [PATCH] Redesign prompt system: revert #09, use standard _PROMPT (issue #21) Revert the bespoke __prompt()/__prompt2() function mechanism from issue #09 and return to stock Lua's _PROMPT/_PROMPT2 globals (which already support dynamic prompts via __tostring metatables). Set default _PROMPT = "lush> " in doREPL if not already defined by config. --- issues/09-prompt.md | 3 +- issues/21-prompt-redesign.md | 2 +- lua.c | 55 +++++---------------- testes/lush/prompt.lua | 96 +++++++++--------------------------- 4 files changed, 38 insertions(+), 118 deletions(-) diff --git a/issues/09-prompt.md b/issues/09-prompt.md index 59c84492..e58f358d 100644 --- a/issues/09-prompt.md +++ b/issues/09-prompt.md @@ -1,6 +1,7 @@ # 09 — Programmable prompt -**Status:** done +**Status:** open +**Blocked by:** #08 Users should be able to customize the shell prompt by defining a Lua function, similar to how other shells use `PS1`/`PROMPT_COMMAND`/`precmd`. diff --git a/issues/21-prompt-redesign.md b/issues/21-prompt-redesign.md index c58314a1..838c74db 100644 --- a/issues/21-prompt-redesign.md +++ b/issues/21-prompt-redesign.md @@ -1,6 +1,6 @@ # 21 — Prompt redesign -**Status:** open +**Status:** done Reconsider the prompt system. Some observations: diff --git a/lua.c b/lua.c index f89010c8..0e6485c2 100644 --- a/lua.c +++ b/lua.c @@ -541,38 +541,19 @@ 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) { - 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) { + 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 *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 */ @@ -692,26 +673,6 @@ 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 */ @@ -737,7 +698,14 @@ 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); + /* set default _PROMPT if not already defined (e.g. by config) */ + if (lua_getglobal(L, "_PROMPT") == LUA_TNIL) { + lua_pop(L, 1); + lua_pushliteral(L, "lush> "); + lua_setglobal(L, "_PROMPT"); + } else { + lua_pop(L, 1); + } setsignal(SIGINT, repl_sigint_handler); if (sigsetjmp(repl_jmp, 1)) { /* Ctrl-C during input — print newline, clear stack, re-prompt */ @@ -748,7 +716,6 @@ 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 */ diff --git a/testes/lush/prompt.lua b/testes/lush/prompt.lua index 076f22fb..cc13dfb5 100644 --- a/testes/lush/prompt.lua +++ b/testes/lush/prompt.lua @@ -1,7 +1,7 @@ -- testes/lush/prompt.lua --- Tests for programmable prompt (issue #09). +-- Tests for prompt system (issue #21). -print "testing programmable prompt" +print "testing prompt system" -- helper: get a unique temp directory local tmpbase = os.tmpname() @@ -39,95 +39,47 @@ local function run_lua(input, config_content) end --- ===== TEST 1: Default __prompt is installed ===== --- After starting the REPL, __prompt should be a function +-- ===== TEST 1: Default _PROMPT is "lush> " ===== do - local out = run_lua('"print(type(__prompt))"') - assert(out:find("function"), - "default __prompt should be installed as function: " .. out) + local out = run_lua('"print(_PROMPT)"') + assert(out:find("lush> ", 1, true), + "default _PROMPT should be 'lush> ': " .. out) end --- ===== TEST 2: Default __prompt returns cwd ===== +-- ===== TEST 2: _PROMPT can be overridden by config ===== do - local cwd = os.getenv("PWD") or "" - local home = os.getenv("HOME") or "" - local expected - if home ~= "" and cwd:sub(1, #home) == home then - expected = "~" .. cwd:sub(#home + 1) - else - expected = cwd - end - local out = run_lua('"print(__prompt(0))"') - assert(out:find(expected, 1, true), - "default __prompt should return cwd: expected '" .. - expected .. "' in: " .. out) -end - - --- ===== TEST 3: Custom __prompt() overrides default ===== -do - local out = run_lua('"print(__prompt(0))"', - 'function __prompt() return "custom> " end\n') + local out = run_lua('"print(_PROMPT)"', + '_PROMPT = "custom> "\n') assert(out:find("custom> ", 1, true), - "custom __prompt should be used: " .. out) + "config should override _PROMPT: " .. out) end --- ===== TEST 4: __prompt() receives exit code ===== +-- ===== TEST 3: _PROMPT with __tostring metatable (dynamic prompt) ===== do - local out = run_lua( - '"error()" "print(__prompt())" "os.exit()"', - 'function __prompt(code)\n' - .. ' return "exit:" .. tostring(code) .. "> "\n' - .. 'end\n') - -- After error(), the next prompt call should receive non-zero code. - -- But since we're calling __prompt() manually via print, we test - -- the mechanism differently: call it with a code directly. - local out2 = run_lua('"print(__prompt(42))"', - 'function __prompt(code)\n' - .. ' return "exit:" .. tostring(code) .. "> "\n' - .. 'end\n') - assert(out2:find("exit:42> ", 1, true), - "exit code should be passed to __prompt: " .. out2) + local out = run_lua('"print(_PROMPT)"', + '_PROMPT = setmetatable({}, { __tostring = function() return "dynamic> " end })\n') + assert(out:find("dynamic> ", 1, true), + "_PROMPT with __tostring should work: " .. out) end --- ===== TEST 5: __prompt2 for continuation ===== +-- ===== TEST 4: _PROMPT2 works for continuation ===== do - local out = run_lua('"print(type(__prompt2))"') - -- __prompt2 is not installed by default - assert(out:find("nil") or out:find("function"), - "__prompt2 should be nil or function: " .. out) + local out = run_lua('"print(_PROMPT2)"', + '_PROMPT2 = "...> "\n') + assert(out:find("...> ", 1, true), + "_PROMPT2 should be settable: " .. out) end --- ===== TEST 6: Broken __prompt() falls back gracefully ===== +-- ===== TEST 5: _PROMPT = nil falls back gracefully ===== do - local out = run_lua('"print(42)"', - 'function __prompt() error("boom") end\n') + -- Set _PROMPT to nil during the REPL; get_prompt falls back to LUA_PROMPT. + local out = run_lua('"_PROMPT = nil" "print(42)"') assert(out:find("42"), - "broken __prompt should still allow REPL to work: " .. out) -end - - --- ===== TEST 7: _PROMPT still works (backward compat) ===== -do - -- When __prompt is defined (default), it takes priority. - -- To test _PROMPT fallback, unset __prompt. - local out = run_lua('"print(42)"', - '__prompt = nil\n_PROMPT = "old> "\n') - assert(out:find("42"), - "_PROMPT fallback should still work: " .. out) -end - - --- ===== TEST 8: Config-defined __prompt is not overwritten ===== -do - local out = run_lua('"print(__prompt(0))"', - 'function __prompt() return "from_config> " end\n') - assert(out:find("from_config> ", 1, true), - "config __prompt should not be overwritten: " .. out) + "_PROMPT = nil should still allow REPL to work: " .. out) end