ctrl+d for soft and ctrl+c for hard abort in term.read, resulting in 'nil' being read, signalling an eof and thus allowing the lua program to be quit that way; removed the exit() function from the lua function; added shell.parse for rudimentary argument parsing (anything starting with - is treated as an option or a list of option (each option name must be exactly one char); added key state tracking and keyboard library

This commit is contained in:
Florian Nücke 2013-10-23 17:10:56 +02:00
parent e9f2fac861
commit a8288b2623
21 changed files with 276 additions and 178 deletions

View File

@ -135,6 +135,6 @@ for k, v in pairs(driver.keyboard.keys) do
driver.keyboard.keys[v] = k driver.keyboard.keys[v] = k
end end
function driver.keyboard.keys.isControl(char) function driver.keyboard.isControl(char)
return type(char) == "number" and (char < 0x20 or (char >= 0x7F and char <= 0x9F)) return type(char) == "number" and (char < 0x20 or (char >= 0x7F and char <= 0x9F))
end end

View File

@ -47,11 +47,8 @@ local sandbox = {
coroutine = { coroutine = {
create = coroutine.create, create = coroutine.create,
resume = coroutine.resume,
running = coroutine.running, running = coroutine.running,
status = coroutine.status, status = coroutine.status
wrap = coroutine.wrap,
yield = coroutine.yield
}, },
math = { math = {
@ -136,8 +133,9 @@ local sandbox = {
} }
sandbox._G = sandbox sandbox._G = sandbox
function sandbox.load(code, source, env) function sandbox.load(ld, source, mode, env)
return load(code, source, "t", env or sandbox) assert((mode or "t") == "t", "unsupported mode")
return load(ld, source, "t", env or sandbox)
end end
function sandbox.checkArg(n, have, ...) function sandbox.checkArg(n, have, ...)
@ -151,7 +149,7 @@ function sandbox.checkArg(n, have, ...)
end end
if not check(...) then if not check(...) then
local msg = string.format("bad argument #%d (%s expected, got %s)", n, table.concat({...}, " or "), have) local msg = string.format("bad argument #%d (%s expected, got %s)", n, table.concat({...}, " or "), have)
--error(debug.traceback(msg, 2), 2) --error(debug.traceback(msg, 2), 0)
error(msg, 2) error(msg, 2)
end end
end end
@ -170,6 +168,100 @@ local function checkDeadline()
end end
end end
function sandbox.coroutine.resume(co, ...)
local args = table.pack(...)
while true do
if not debug.gethook(co) then -- don't reset counter
debug.sethook(co, checkDeadline, "", 10000)
end
local result = table.pack(coroutine.resume(co, table.unpack(args, 1, args.n)))
checkDeadline()
if result[1] then
local isSystemYield = coroutine.status(co) ~= "dead" and result[2] ~= nil
if isSystemYield then
args = table.pack(coroutine.yield(result[2]))
else
return true, table.unpack(result, 3, result.n)
end
else -- error: result = (bool, string)
return table.unpack(result, 1, result.n)
end
end
end
function sandbox.coroutine.yield(...)
return coroutine.yield(nil, ...)
end
function sandbox.coroutine.wrap(f) -- for sandbox's coroutine.resume
local co = sandbox.coroutine.create(f)
return function(...)
local result = table.pack(sandbox.coroutine.resume(co, ...))
if result[1] then
return table.unpack(result, 2, result.n)
else
error(result[2], 0)
end
end
end
function sandbox.pcall(...)
local result = table.pack(pcall(...))
checkDeadline()
return table.unpack(result, 1, result.n)
end
function sandbox.xpcall(...)
local result = table.pack(xpcall(...))
checkDeadline()
return table.unpack(result, 1, result.n)
end
-------------------------------------------------------------------------------
function sandbox.os.shutdown(reboot)
coroutine.yield(reboot ~= nil and reboot ~= false)
end
function sandbox.os.signal(name, timeout)
local waitUntil = os.uptime() + (type(timeout) == "number" and timeout or math.huge)
repeat
local signal = table.pack(coroutine.yield(waitUntil - os.uptime()))
if signal.n > 0 and (name == signal[1] or name == nil) then
return table.unpack(signal, 1, signal.n)
end
until os.uptime() >= waitUntil
end
-------------------------------------------------------------------------------
sandbox.driver = {}
function sandbox.driver.componentType(address)
return nodeName(address)
end
do
local env = setmetatable({ send = sendToAddress },
{ __index = sandbox, __newindex = sandbox })
for name, code in pairs(drivers()) do
local driver, reason = load(code, "=" .. name, "t", env)
if not driver then
print("Failed loading driver '" .. name .. "': " .. reason)
else
local result, reason = xpcall(driver, function(msg)
return debug.traceback(msg, 2)
end)
if not result then
print("Failed initializing driver '" .. name .. "': " ..
(reason or "unknown error"))
end
end
end
end
-------------------------------------------------------------------------------
local function main(args) local function main(args)
local function bootstrap() local function bootstrap()
local fs = sandbox.driver.filesystem local fs = sandbox.driver.filesystem
@ -235,88 +327,6 @@ local function main(args)
end end
end end
function sandbox.coroutine.resume(co, ...)
local args = table.pack(...)
while true do
if not debug.gethook(co) then -- don't reset counter
debug.sethook(co, checkDeadline, "", 10000)
end
local result = table.pack(coroutine.resume(co, table.unpack(args, 1, args.n)))
checkDeadline()
if result[1] then
local isSystemYield = coroutine.status(co) ~= "dead" and result[2] ~= nil
if isSystemYield then
args = table.pack(coroutine.yield(result[2]))
else
return true, table.unpack(result, 3, result.n)
end
else -- error: result = (bool, string)
return table.unpack(result, 1, result.n)
end
end
end
function sandbox.coroutine.yield(...)
return coroutine.yield(nil, ...)
end
function sandbox.pcall(...)
local result = table.pack(pcall(...))
checkDeadline()
return table.unpack(result, 1, result.n)
end
function sandbox.xpcall(...)
local result = table.pack(xpcall(...))
checkDeadline()
return table.unpack(result, 1, result.n)
end
-------------------------------------------------------------------------------
function sandbox.os.shutdown(reboot)
coroutine.yield(reboot ~= nil and reboot ~= false)
end
function sandbox.os.signal(name, timeout)
local waitUntil = os.uptime() + (type(timeout) == "number" and timeout or math.huge)
repeat
local signal = table.pack(coroutine.yield(waitUntil - os.uptime()))
if signal.n > 0 and (name == signal[1] or name == nil) then
return table.unpack(signal, 1, signal.n)
end
until os.uptime() >= waitUntil
end
-------------------------------------------------------------------------------
sandbox.driver = {}
function sandbox.driver.componentType(address)
return nodeName(address)
end
do
local env = setmetatable({ send = sendToAddress },
{ __index = sandbox, __newindex = sandbox })
for name, code in pairs(drivers()) do
local driver, reason = load(code, "=" .. name, "t", env)
if not driver then
print("Failed loading driver '" .. name .. "': " .. reason)
else
local result, reason = xpcall(driver, function(msg)
return debug.traceback(msg, 2)
end)
if not result then
print("Failed initializing driver '" .. name .. "': " ..
(reason or "unknown error"))
end
end
end
end
-------------------------------------------------------------------------------
-- JNLua converts the coroutine to a string immediately, so we can't get the -- JNLua converts the coroutine to a string immediately, so we can't get the
-- traceback later. Because of that we have to do the error handling here. -- traceback later. Because of that we have to do the error handling here.
-- Also, yield once to allow initializing up to here to get a memory baseline. -- Also, yield once to allow initializing up to here to get a memory baseline.

View File

@ -1,10 +1,10 @@
local args = table.pack(...) local args = shell.parse(...)
if args.n == 0 then if args.n == 0 then
for name, value in shell.aliases() do for name, value in shell.aliases() do
print(name, value) print(name, value)
end end
elseif args.n == 1 then elseif #args == 1 then
local value = shell.alias(args[1]) local value = shell.alias(args[1])
if value then if value then
print(value) print(value)

View File

@ -1,10 +1,10 @@
local args = table.pack(...) local args = shell.parse(...)
if args.n == 0 then if #args == 0 then
print("Usage: cat <filename1> [<filename2> [...]]") print("Usage: cat <filename1> [<filename2> [...]]")
return return
end end
for i = 1, args.n do for i = 1, #args do
local file, reason = io.open(shell.resolve(args[i]), "r") local file, reason = io.open(shell.resolve(args[i]), "r")
if not file then if not file then
print(reason) print(reason)

View File

@ -1,5 +1,5 @@
local args = table.pack(...) local args = shell.parse(...)
if args.n == 0 then if #args == 0 then
print("Usage: cd <dirname>") print("Usage: cd <dirname>")
else else
local result, reason = shell.cwd(shell.resolve(args[1])) local result, reason = shell.cwd(shell.resolve(args[1]))

View File

@ -1,5 +1,5 @@
local args = table.pack(...) local args = shell.parse(...)
if args.n < 2 then if #args < 2 then
print("Usage: cp <from> <to>") print("Usage: cp <from> <to>")
return return
end end

View File

@ -1,33 +1,29 @@
local args = table.pack(...) local dirs, options = shell.parse(...)
local options = {}
local dirs = {}
for i = 1, args.n do
if args[i]:usub(1, 1) == "-" then
if args[i]:ulen() > 1 then
options[args[i]:usub(2)] = true
end
else
table.insert(dirs, args[i])
end
end
if #dirs == 0 then if #dirs == 0 then
table.insert(dirs, ".") table.insert(dirs, ".")
end end
for i = 1, #dirs do for i = 1, #dirs do
local path = shell.resolve(dirs[i]) local path = shell.resolve(dirs[i])
if #dirs > 1 then
if i > 1 then print() end
print("/" .. path .. ":")
end
local list, reason = fs.dir(path) local list, reason = fs.dir(path)
if not list then if not list then
print(reason) print(reason)
else else
for f in list do for f in list do
if options.l then if options.a or f:usub(1, 1) ~= "." then
print(f, fs.size(fs.concat(path, f))) if options.l then
else print(f, fs.size(fs.concat(path, f)))
io.write(f .. "\t") else
io.write(f .. "\t")
end
end end
end end
print() if not options.l then
print()
end
end end
end end

View File

@ -1,14 +1,17 @@
print("Lua 5.2.2 Copyright (C) 1994-2013 Lua.org, PUC-Rio") print("Lua 5.2.2 Copyright (C) 1994-2013 Lua.org, PUC-Rio")
local running = true local history = {}
local env = setmetatable({exit=function() running = false end}, {__index=_ENV}) local env = setmetatable({}, {__index=_ENV})
while running and term.isAvailable() do while term.isAvailable() do
io.write("lua> ") term.write("lua> ")
local command = io.read() local command = term.read(history)
if not command then if command == nil then -- eof
return -- eof return
end end
local statement, result = load(command, "=stdin", env) while #history > 10 do
local expression = load("return " .. command, "=stdin", env) table.remove(history, 1)
end
local statement, result = load(command, "=stdin", "t", env)
local expression = load("return " .. command, "=stdin", "t", env)
local code = expression or statement local code = expression or statement
if code then if code then
local result = table.pack(pcall(code)) local result = table.pack(pcall(code))

View File

@ -1,10 +1,10 @@
local args = table.pack(...) local args = shell.parse(...)
if args.n == 0 then if #args == 0 then
print("Usage: mkdir <dirname1> [<dirname2> [...]]") print("Usage: mkdir <dirname1> [<dirname2> [...]]")
return return
end end
for i = 1, args.n do for i = 1, #args do
local path = shell.resolve(args[i]) local path = shell.resolve(args[i])
local result, reason = fs.makeDirectory(path) local result, reason = fs.makeDirectory(path)
if not result then if not result then

View File

@ -1,5 +1,5 @@
local args = table.pack(...) local args = shell.parse(...)
if args.n < 2 then if #args < 2 then
print("Usage: mv <from> <to>") print("Usage: mv <from> <to>")
return return
end end

View File

@ -1,10 +1,10 @@
local args = table.pack(...) local args = shell.parse(...)
if args.n == 0 then if #args == 0 then
print("Usage: rm <filename1> [<filename2> [...]]") print("Usage: rm <filename1> [<filename2> [...]]")
return return
end end
for i = 1, args.n do for i = 1, #args do
local path = shell.resolve(args[i]) local path = shell.resolve(args[i])
if not fs.remove(path) then if not fs.remove(path) then
print(path .. ": no such file, or permission denied") print(path .. ": no such file, or permission denied")

View File

@ -1,3 +1,9 @@
local args = table.pack(...)
if args.n > 0 then
os.execute(table.concat(args, " ", 1, args.n))
return
end
local function trim(s) -- from http://lua-users.org/wiki/StringTrim local function trim(s) -- from http://lua-users.org/wiki/StringTrim
local from = s:match"^%s*()" local from = s:match"^%s*()"
return from > #s and "" or s:match(".*%S", from) return from > #s and "" or s:match(".*%S", from)

View File

@ -1,5 +1,5 @@
local args = table.pack(...) local args = shell.parse(...)
if args.n < 1 then if #args < 1 then
print("Usage: unalias <name>") print("Usage: unalias <name>")
return return
end end

View File

@ -1,8 +1,9 @@
local args = table.pack(...) local args = shell.parse(...)
if args.n == 0 then if #args == 0 then
print("Usage: which <program>") print("Usage: which <program>")
return return
end end
local result, reason = shell.which(args[1]) local result, reason = shell.which(args[1])
if result then if result then
print(result) print(result)

View File

@ -6,7 +6,7 @@ function dofile(filename)
return program() return program()
end end
function loadfile(filename, env) function loadfile(filename, mode, env)
local file, reason = io.open(filename) local file, reason = io.open(filename)
if not file then if not file then
return nil, reason return nil, reason
@ -16,7 +16,7 @@ function loadfile(filename, env)
if not source then if not source then
return nil, reason return nil, reason
end end
return load(source, "=" .. filename, env) return load(source, "=" .. filename, mode, env)
end end
function print(...) function print(...)

View File

@ -20,6 +20,14 @@ function component.list(filter)
end end
end end
function component.isPrimary(address)
local componentType = component.type(address)
if componentType then
return primaries[componentType] == address
end
return false
end
function component.primary(componentType, ...) function component.primary(componentType, ...)
checkArg(1, componentType, "string") checkArg(1, componentType, "string")
local args = table.pack(...) local args = table.pack(...)

View File

@ -95,13 +95,13 @@ function event.cancel(timerId)
timers[timerId] = nil timers[timerId] = nil
end end
function event.interval(timeout, callback) function event.interval(frequency, callback)
local interval = {} local interval = {}
local function onTimer() local function onTimer()
interval.id = event.timer(timeout, onTimer) interval.id = event.timer(frequency, onTimer)
callback() callback()
end end
interval.id = event.timer(timeout, onTimer) interval.id = event.timer(frequency, onTimer)
return interval return interval
end end

View File

@ -0,0 +1,52 @@
keyboard = setmetatable({}, {__index = driver.keyboard})
local pressedChars = {}
local pressedCodes = {}
-------------------------------------------------------------------------------
function keyboard.isKeyDown(charOrCode)
checkArg(1, charOrCode, "string", "number")
if type(charOrCode) == "string" then
return pressedChars[charOrCode]
elseif type(charOrCode) == "number" then
return pressedCodes[charOrCode]
end
end
function keyboard.isControlDown()
return (pressedCodes[keyboard.keys.lcontrol] or pressedCodes[keyboard.keys.rcontrol]) ~= nil
end
function keyboard.isShiftDown()
return (pressedCodes[keyboard.keys.lshift] or pressedCodes[keyboard.keys.rshift]) ~= nil
end
-------------------------------------------------------------------------------
local function onKeyDown(_, address, char, code)
if component.isPrimary(address) then
pressedChars[char] = true
pressedCodes[code] = true
end
end
local function onKeyUp(_, address, char, code)
if component.isPrimary(address) then
pressedChars[char] = nil
pressedCodes[code] = nil
end
end
local function onComponentUnavailable(_, componentType)
if componentType == "keyboard" then
pressedChars = {}
pressedCodes = {}
end
end
return function()
event.listen("key_down", onKeyDown)
event.listen("key_up", onKeyUp)
event.listen("component_unavailable", onComponentUnavailable)
end

View File

@ -25,6 +25,8 @@ os.remove = driver.filesystem.remove
os.rename = driver.filesystem.rename os.rename = driver.filesystem.rename
os.sleep = event.wait
function os.tmpname() function os.tmpname()
if driver.filesystem.exists("tmp") then if driver.filesystem.exists("tmp") then
for i = 1, 10 do for i = 1, 10 do

View File

@ -146,7 +146,7 @@ function shell.execute(program, ...)
pevent = setmetatable(pevent, {__index = event, __metatable = {}}) pevent = setmetatable(pevent, {__index = event, __metatable = {}})
local env = setmetatable({event = pevent}, {__index = _ENV, __metatable = {}}) local env = setmetatable({event = pevent}, {__index = _ENV, __metatable = {}})
program, reason = loadfile(where, env) program, reason = loadfile(where, "t", env)
if not program then if not program then
return nil, reason return nil, reason
end end
@ -170,6 +170,23 @@ function shell.execute(program, ...)
return table.unpack(result, 1, result.n) return table.unpack(result, 1, result.n)
end end
function shell.parse(...)
local params = table.pack(...)
local args = {}
local options = {}
for i = 1, params.n do
local param = params[i]
if param:usub(1, 1) == "-" then
for j = 2, param:ulen() do
options[param:usub(j, j)] = true
end
else
table.insert(args, param)
end
end
return args, options
end
function shell.resolve(path) function shell.resolve(path)
if path:usub(1, 1) == "/" then if path:usub(1, 1) == "/" then
return fs.canonical(path) return fs.canonical(path)

View File

@ -1,14 +1,6 @@
local cursorX, cursorY = 1, 1 local cursorX, cursorY = 1, 1
local cursorBlink = nil local cursorBlink = nil
local function toggleBlink()
cursorBlink.state = not cursorBlink.state
if term.isAvailable() then
local char = cursorBlink.state and cursorBlink.solid or cursorBlink.alt
gpu.set(cursorX, cursorY, char)
end
end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
term = {} term = {}
@ -43,6 +35,13 @@ function term.cursor(col, row)
end end
function term.cursorBlink(enabled) function term.cursorBlink(enabled)
local function toggleBlink()
cursorBlink.state = not cursorBlink.state
if term.isAvailable() then
local char = cursorBlink.state and cursorBlink.solid or cursorBlink.alt
gpu.set(cursorX, cursorY, char)
end
end
local function start(alt) local function start(alt)
if not cursorBlink then if not cursorBlink then
cursorBlink = event.interval(0.5, toggleBlink) cursorBlink = event.interval(0.5, toggleBlink)
@ -74,6 +73,8 @@ function term.cursorBlink(enabled)
else else
stop() stop()
end end
elseif enabled and cursorBlink then
toggleBlink()
end end
return cursorBlink ~= nil return cursorBlink ~= nil
end end
@ -84,11 +85,11 @@ function term.read(history)
history = history or {} history = history or {}
table.insert(history, "") table.insert(history, "")
local current = #history local current = #history
local keys = driver.keyboard.keys
local start = term.cursor() local start = term.cursor()
local cursor, scroll = 1, 0 local cursor, scroll = 1, 0
local keyRepeat = nil local keyRepeat = nil
local result = nil local result = nil
local forceReturn = false -- for eof where result is nil
local function remove() local function remove()
local x = start - 1 + cursor - scroll local x = start - 1 + cursor - scroll
local _, y = term.cursor() local _, y = term.cursor()
@ -160,7 +161,7 @@ function term.read(history)
term.cursorBlink(cursor <= history[current]:ulen() and term.cursorBlink(cursor <= history[current]:ulen() and
history[current]:usub(cursor, cursor) or " ") history[current]:usub(cursor, cursor) or " ")
if blink then if blink then
toggleBlink() term.cursorBlink(true)
end end
end end
local function handleKeyPress(char, code) local function handleKeyPress(char, code)
@ -168,7 +169,7 @@ function term.read(history)
local w, h = gpu.resolution() local w, h = gpu.resolution()
local cancel, blink = false, false local cancel, blink = false, false
term.cursorBlink(false) term.cursorBlink(false)
if code == keys.back then if code == keyboard.keys.back then
if cursor > 1 then if cursor > 1 then
copyIfNecessary() copyIfNecessary()
history[current] = history[current]:usub(1, cursor - 2) .. history[current] = history[current]:usub(1, cursor - 2) ..
@ -180,7 +181,7 @@ function term.read(history)
remove() remove()
end end
cancel = cursor == 1 cancel = cursor == 1
elseif code == keys.delete then elseif code == keyboard.keys.delete then
if cursor <= history[current]:ulen() then if cursor <= history[current]:ulen() then
copyIfNecessary() copyIfNecessary()
history[current] = history[current]:usub(1, cursor - 1) .. history[current] = history[current]:usub(1, cursor - 1) ..
@ -188,7 +189,7 @@ function term.read(history)
remove() remove()
end end
cancel = cursor == history[current]:ulen() + 1 cancel = cursor == history[current]:ulen() + 1
elseif code == keys.left then elseif code == keyboard.keys.left then
if cursor > 1 then if cursor > 1 then
cursor = cursor - 1 cursor = cursor - 1
if cursor - scroll < 1 then if cursor - scroll < 1 then
@ -197,7 +198,7 @@ function term.read(history)
end end
cancel = cursor == 1 cancel = cursor == 1
blink = true blink = true
elseif code == keys.right then elseif code == keyboard.keys.right then
if cursor < history[current]:ulen() + 1 then if cursor < history[current]:ulen() + 1 then
cursor = cursor + 1 cursor = cursor + 1
if cursor - scroll > w - (start - 1) then if cursor - scroll > w - (start - 1) then
@ -206,34 +207,34 @@ function term.read(history)
end end
cancel = cursor == history[current]:ulen() + 1 cancel = cursor == history[current]:ulen() + 1
blink = true blink = true
elseif code == keys.home then elseif code == keyboard.keys.home then
if cursor > 1 then if cursor > 1 then
cursor, scroll = 1, 0 cursor, scroll = 1, 0
render() render()
end end
cancel = true cancel = true
blink = true blink = true
elseif code == keys["end"] then elseif code == keyboard.keys["end"] then
if cursor < history[current]:ulen() + 1 then if cursor < history[current]:ulen() + 1 then
scrollEnd() scrollEnd()
end end
cancel = true cancel = true
blink = true blink = true
elseif code == keys.up then elseif code == keyboard.keys.up then
if current > 1 then if current > 1 then
current = current - 1 current = current - 1
scrollEnd() scrollEnd()
end end
cancel = current == 1 cancel = current == 1
blink = true blink = true
elseif code == keys.down then elseif code == keyboard.keys.down then
if current < #history then if current < #history then
current = current + 1 current = current + 1
scrollEnd() scrollEnd()
end end
cancel = current == #history cancel = current == #history
blink = true blink = true
elseif code == keys.enter then elseif code == keyboard.keys.enter then
if current ~= #history then -- bring entry to front if current ~= #history then -- bring entry to front
history[#history] = history[current] history[#history] = history[current]
table.remove(history, current) table.remove(history, current)
@ -244,7 +245,18 @@ function term.read(history)
table.remove(history, current) table.remove(history, current)
end end
return true return true
elseif not keys.isControl(char) then elseif keyboard.isControlDown() then
if code == keyboard.keys.d then
if history[current] == "" then
forceReturn = true
return true
end
elseif code == keyboard.keys.c then
history[current] = ""
forceReturn = true
return true
end
elseif not keyboard.isControl(char) then
copyIfNecessary() copyIfNecessary()
history[current] = history[current]:usub(1, cursor - 1) .. history[current] = history[current]:usub(1, cursor - 1) ..
string.uchar(char) .. string.uchar(char) ..
@ -259,9 +271,7 @@ function term.read(history)
return cancel return cancel
end end
local function onKeyDown(_, address, char, code) local function onKeyDown(_, address, char, code)
if not component.isAvailable("keyboard") or if not component.isPrimary(address) then
address ~= component.primary("keyboard")
then
return return
end end
if keyRepeat then if keyRepeat then
@ -277,19 +287,12 @@ function term.read(history)
end end
end end
local function onKeyUp(_, address, char, code) local function onKeyUp(_, address, char, code)
if not component.isAvailable("keyboard") or if component.isPrimary(address) and keyRepeat then
address ~= component.primary("keyboard")
then
return
end
if keyRepeat then
keyRepeat = event.cancel(keyRepeat) keyRepeat = event.cancel(keyRepeat)
end end
end end
local function onClipboard(_, address, value) local function onClipboard(_, address, value)
if not component.isAvailable("keyboard") or if not component.isPrimary(address) then
address ~= component.primary("keyboard")
then
return return
end end
copyIfNecessary() copyIfNecessary()
@ -308,7 +311,7 @@ function term.read(history)
event.listen("key_up", onKeyUp) event.listen("key_up", onKeyUp)
event.listen("clipboard", onClipboard) event.listen("clipboard", onClipboard)
term.cursorBlink(true) term.cursorBlink(true)
while term.isAvailable() and not result do while term.isAvailable() and not result and not forceReturn do
event.wait() event.wait()
end end
if history[#history] == "" then if history[#history] == "" then