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:
@@ -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
102
luash
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user