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.
|
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
|
### Debug Mode
|
||||||
```bash
|
```bash
|
||||||
LUASH_DEBUG=1 ./luash script.luash
|
LUASH_DEBUG=1 ./luash script.luash
|
||||||
|
|||||||
102
luash
102
luash
@@ -210,22 +210,96 @@ function start_repl()
|
|||||||
return s:match("^%s*(.-)%s*$")
|
return s:match("^%s*(.-)%s*$")
|
||||||
end
|
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
|
-- Main REPL loop
|
||||||
print("🚀 Luash Interactive Shell")
|
print("🚀 Luash Interactive Shell")
|
||||||
print("Type Lua expressions, use $VAR for env vars, `commands` for 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("Special commands: .help .exit .clear .history (Ctrl+L to clear, Ctrl+D to exit)")
|
||||||
print("")
|
print("")
|
||||||
|
|
||||||
|
if raw_mode_enabled then set_raw_mode(true) end
|
||||||
|
local ok_loop, loop_err = pcall(function()
|
||||||
while true do
|
while true do
|
||||||
local prompt = repl.buffer == "" and "luash> " or " >> "
|
local prompt = repl.buffer == "" and "luash> " or " >> "
|
||||||
io.write(prompt)
|
local line
|
||||||
io.flush()
|
if raw_mode_enabled then
|
||||||
|
line = raw_read_line(prompt)
|
||||||
-- Safely read a line; handle Ctrl+C gracefully
|
if line == "__CTRL_C__" then
|
||||||
local ok, line = pcall(io.read, "*line")
|
print("\nInterrupted (Ctrl+C)")
|
||||||
if not ok then
|
break
|
||||||
print("\nInterrupted (Ctrl+C)")
|
end
|
||||||
break
|
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
|
end
|
||||||
|
|
||||||
if not line then -- EOF (Ctrl+D)
|
if not line then -- EOF (Ctrl+D)
|
||||||
@@ -233,8 +307,8 @@ function start_repl()
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Handle Ctrl+L (form feed) to clear screen
|
-- Handle Ctrl+L (form feed) to clear screen in non-raw mode
|
||||||
if line:find(string.char(12), 1, true) then
|
if (not raw_mode_enabled) and line:find(string.char(12), 1, true) then
|
||||||
repl.buffer = ""
|
repl.buffer = ""
|
||||||
io.write("\27[2J\27[H") -- Clear screen and move cursor to top
|
io.write("\27[2J\27[H") -- Clear screen and move cursor to top
|
||||||
io.flush()
|
io.flush()
|
||||||
@@ -342,6 +416,12 @@ Multi-line input supported for functions, loops, etc.]])
|
|||||||
end
|
end
|
||||||
::continue::
|
::continue::
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
-- 1. Check for an input file or REPL mode
|
-- 1. Check for an input file or REPL mode
|
||||||
@@ -378,7 +458,7 @@ end
|
|||||||
|
|
||||||
-- Preprocessing functions
|
-- Preprocessing functions
|
||||||
local function process_env_vars(code)
|
local function process_env_vars(code)
|
||||||
local lines = {}
|
local lines = {}
|
||||||
for line in code:gmatch("([^\r\n]*)") do
|
for line in code:gmatch("([^\r\n]*)") do
|
||||||
if not line:match("^%s*%-%-") then -- Skip comments
|
if not line:match("^%s*%-%-") then -- Skip comments
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user