Add raw mode support to REPL for immediate key handling (Ctrl+L/Ctrl+C/Ctrl+D) and update README with usage notes

This commit is contained in:
2025-10-09 16:57:56 +01:00
parent 21f5ab5335
commit 6f92a80bb1
2 changed files with 95 additions and 11 deletions

View File

@@ -147,6 +147,10 @@ The interactive shell supports:
See [examples/08_repl_guide.luash](examples/08_repl_guide.luash) for a comprehensive guide.
Notes:
- Raw terminal mode (immediate Ctrl+L/Ctrl+C/Ctrl+D) auto-enables when attached to a TTY.
- Force enable/disable with `LUASH_RAW=1` or `LUASH_RAW=0`.
### Debug Mode
```bash
LUASH_DEBUG=1 ./luash script.luash

102
luash
View File

@@ -210,22 +210,96 @@ function start_repl()
return s:match("^%s*(.-)%s*$")
end
-- Raw mode (optional) for immediate key handling like Ctrl+L
local function detect_tty()
local r = os.execute("test -t 0 >/dev/null 2>&1")
if type(r) == "number" then return r == 0 end
return r == true
end
local env_raw = os.getenv("LUASH_RAW")
local raw_mode_enabled = (env_raw == "1") or (env_raw == nil and detect_tty())
local function set_raw_mode(enable)
if enable then
os.execute("stty -echo -icanon -isig min 1 time 0")
else
os.execute("stty sane")
end
end
local function raw_read_line(prompt)
io.write(prompt)
io.flush()
local buf = {}
while true do
local ch = io.read(1)
if not ch then
return nil
end
local b = string.byte(ch)
if b == 10 or b == 13 then -- Enter
io.write("\n")
return table.concat(buf)
elseif b == 12 then -- Ctrl+L
-- Clear screen and reprint banner + prompt + current buffer
repl.buffer = table.concat(buf)
io.write("\27[2J\27[H")
io.flush()
print("🚀 Luash Interactive Shell")
print("Type Lua expressions, use $VAR for env vars, `commands` for shell")
print("Special commands: .help .exit .clear .history (Ctrl+L to clear, Ctrl+D to exit)")
print("")
io.write(prompt)
io.write(repl.buffer)
io.flush()
-- Keep current input for usability, but allow immediate Ctrl+D by not forcing buf non-empty
-- buf remains as-is
elseif b == 3 then -- Ctrl+C
return "__CTRL_C__"
elseif b == 4 then -- Ctrl+D
-- Exit immediately (treat as EOF) regardless of current buffer state
return nil
elseif b == 127 or b == 8 then -- Backspace/Delete
if #buf > 0 then
table.remove(buf)
io.write("\b \b")
io.flush()
end
else
table.insert(buf, ch)
io.write(ch)
io.flush()
end
end
end
-- Main REPL loop
print("🚀 Luash Interactive Shell")
print("Type Lua expressions, use $VAR for env vars, `commands` for shell")
print("Special commands: .help .exit .clear .history (Ctrl+L to clear, Ctrl+D to exit)")
print("")
if raw_mode_enabled then set_raw_mode(true) end
local ok_loop, loop_err = pcall(function()
while true do
local prompt = repl.buffer == "" and "luash> " or " >> "
io.write(prompt)
io.flush()
-- Safely read a line; handle Ctrl+C gracefully
local ok, line = pcall(io.read, "*line")
if not ok then
print("\nInterrupted (Ctrl+C)")
break
local line
if raw_mode_enabled then
line = raw_read_line(prompt)
if line == "__CTRL_C__" then
print("\nInterrupted (Ctrl+C)")
break
end
else
io.write(prompt)
io.flush()
-- Safely read a line; handle Ctrl+C gracefully
local ok, l = pcall(io.read, "*line")
if not ok then
print("\nInterrupted (Ctrl+C)")
break
end
line = l
end
if not line then -- EOF (Ctrl+D)
@@ -233,8 +307,8 @@ function start_repl()
break
end
-- Handle Ctrl+L (form feed) to clear screen
if line:find(string.char(12), 1, true) then
-- Handle Ctrl+L (form feed) to clear screen in non-raw mode
if (not raw_mode_enabled) and line:find(string.char(12), 1, true) then
repl.buffer = ""
io.write("\27[2J\27[H") -- Clear screen and move cursor to top
io.flush()
@@ -342,6 +416,12 @@ Multi-line input supported for functions, loops, etc.]])
end
::continue::
end
end)
if raw_mode_enabled then set_raw_mode(false) end
if not ok_loop then
if raw_mode_enabled then set_raw_mode(false) end
print("\nREPL error: " .. tostring(loop_err))
end
end
-- 1. Check for an input file or REPL mode
@@ -378,7 +458,7 @@ end
-- Preprocessing functions
local function process_env_vars(code)
local lines = {}
local lines = {}
for line in code:gmatch("([^\r\n]*)") do
if not line:match("^%s*%-%-") then -- Skip comments