Merge remote-tracking branch 'upstream/master-MC1.8.9' into master-MC1.9.4

This commit is contained in:
payonel 2017-09-10 01:03:23 -07:00
commit e810b556e1
18 changed files with 348 additions and 241 deletions

View File

@ -17,18 +17,18 @@ for i = 1, #args do
if args[i] == "-" then if args[i] == "-" then
file, reason = io.stdin, "missing stdin" file, reason = io.stdin, "missing stdin"
else else
file, reason = io.open(shell.resolve(args[i])) file, reason = fs.open(shell.resolve(args[i]))
end end
if not file then if not file then
io.stderr:write(string.format("cat: %s: %s\n", args[i], tostring(reason))) io.stderr:write(string.format("cat: %s: %s\n", args[i], tostring(reason)))
ec = 1 ec = 1
else else
repeat repeat
local line = file:read("*L") local chunk = file:read(2048)
if line then if chunk then
io.write(line) io.write(chunk)
end end
until not line until not chunk
file:close() file:close()
end end
end end

View File

@ -1,17 +1,22 @@
local args, options = require("shell").parse(...) local args, options = require("shell").parse(...)
if options.help then if options.help then
print([[`echo` writes the provided string(s) to the standard output. io.write([[
`echo` writes the provided string(s) to the standard output.
-n do not output the trialing newline -n do not output the trialing newline
-e enable interpretation of backslash escapes -e enable interpretation of backslash escapes
--help display this help and exit]]) --help display this help and exit
]])
return return
end end
if options.e then if options.e then
for index,arg in ipairs(args) do for index,arg in ipairs(args) do
-- use lua load here to interpret escape sequences such as \27
-- instead of writing my own language to interpret them myself
-- note that in a real terminal, \e is used for \27
args[index] = assert(load("return \"" .. arg:gsub('"', [[\"]]) .. "\""))() args[index] = assert(load("return \"" .. arg:gsub('"', [[\"]]) .. "\""))()
end end
end end
io.write(table.concat(args," ")) io.write(table.concat(args," "))
if not options.n then if not options.n then
print() io.write("\n")
end end

View File

@ -68,7 +68,7 @@ local function confirm()
if bForce then if bForce then
return true return true
end end
local r = io.read("*l") local r = io.read()
return r == 'y' or r == 'yes' return r == 'y' or r == 'yes'
end end

View File

@ -23,7 +23,7 @@ if #args == 0 then
end end
io.write(sh.expand(os.getenv("PS1") or "$ ")) io.write(sh.expand(os.getenv("PS1") or "$ "))
end end
local command = tty:read(input_handler) local command = tty.read(input_handler)
if command then if command then
command = text.trim(command) command = text.trim(command)
if command == "exit" then if command == "exit" then

View File

@ -1,4 +1,5 @@
local shell = require('shell') local shell = require("shell")
local tty = require("tty")
local args, options = shell.parse(...) local args, options = shell.parse(...)
if options.help then if options.help then
@ -50,4 +51,11 @@ for _,v in ipairs(args) do
total_time = total_time + time_type_multiplier(time_type) * interval total_time = total_time + time_type_multiplier(time_type) * interval
end end
os.sleep(total_time) local ins = io.stdin.stream
local pull = ins.pull
local start = 1
if not pull then
pull = require("event").pull
start = 2
end
pull(select(start, ins, total_time, "interrupted"))

View File

@ -1,14 +1,14 @@
local buffer = require("buffer") local buffer = require("buffer")
local tty = require("tty") local tty_stream = require("tty").stream
local core_stdin = buffer.new("r", tty) local core_stdin = buffer.new("r", tty_stream)
local core_stdout = buffer.new("w", tty) local core_stdout = buffer.new("w", tty_stream)
local core_stderr = buffer.new("w", setmetatable( local core_stderr = buffer.new("w", setmetatable(
{ {
write = function(_, str) write = function(_, str)
return tty:write("\27[31m"..str.."\27[37m") return tty_stream:write("\27[31m"..str.."\27[37m")
end end
}, {__index=tty})) }, {__index=tty_stream}))
core_stdout:setvbuf("no") core_stdout:setvbuf("no")
core_stderr:setvbuf("no") core_stderr:setvbuf("no")
@ -16,9 +16,9 @@ core_stdin.tty = true
core_stdout.tty = true core_stdout.tty = true
core_stderr.tty = true core_stderr.tty = true
core_stdin.close = tty.close core_stdin.close = tty_stream.close
core_stdout.close = tty.close core_stdout.close = tty_stream.close
core_stderr.close = tty.close core_stderr.close = tty_stream.close
local io_mt = getmetatable(io) or {} local io_mt = getmetatable(io) or {}
io_mt.__index = function(_, k) io_mt.__index = function(_, k)

View File

@ -7,7 +7,6 @@ if tty.isAvailable() then
io.write("\27[40m\27[37m") io.write("\27[40m\27[37m")
tty.clear() tty.clear()
end end
tty.setCursorBlink(true)
end end
dofile("/etc/motd") dofile("/etc/motd")

View File

@ -1,7 +1,7 @@
-- called from /init.lua -- called from /init.lua
local raw_loadfile = ... local raw_loadfile = ...
_G._OSVERSION = "OpenOS 1.6.7" _G._OSVERSION = "OpenOS 1.6.8"
local component = component local component = component
local computer = computer local computer = computer

View File

@ -85,7 +85,3 @@ function tty.on_tab(handler, cursor)
cursor:move(-tail) cursor:move(-tail)
end end
end end
function tty:size()
return #(self.window.ansi_response or "")
end

View File

@ -10,12 +10,11 @@ end
local env -- forward declare for binding in metamethod local env -- forward declare for binding in metamethod
env = setmetatable({}, { env = setmetatable({}, {
__index = function(t, k) __index = function(_, k)
_ENV[k] = _ENV[k] or optrequire(k) _ENV[k] = _ENV[k] or optrequire(k)
return _ENV[k] return _ENV[k]
end, end,
__pairs = function(self) __pairs = function(t)
local t = self
return function(_, key) return function(_, key)
local k, v = next(t, key) local k, v = next(t, key)
if not k and t == env then if not k and t == env then
@ -94,7 +93,7 @@ io.write("Press Ctrl+D to exit the interpreter.\n\27[37m")
while tty.isAvailable() do while tty.isAvailable() do
io.write(env._PROMPT) io.write(env._PROMPT)
local command = tty:read(read_handler) local command = tty.read(read_handler)
if not command then -- eof if not command then -- eof
return return
end end

View File

@ -150,4 +150,11 @@ function process.internal.continue(co, ...)
return table.unpack(result, 2, result.n) return table.unpack(result, 2, result.n)
end end
function process.running(level) -- kept for backwards compat, prefer process.info
local info = process.info(level)
if info then
return info.path, info.env, info.command
end
end
return process return process

View File

@ -17,40 +17,40 @@ function serialization.serialize(value, pretty)
["until"]=true, ["while"]=true} ["until"]=true, ["while"]=true}
local id = "^[%a_][%w_]*$" local id = "^[%a_][%w_]*$"
local ts = {} local ts = {}
local function s(v, l) local result_pack = {}
local t = type(v) local function recurse(current_value, depth)
if t == "nil" then local t = type(current_value)
return "nil" if t == "number" then
elseif t == "boolean" then if current_value ~= current_value then
return v and "true" or "false" table.insert(result_pack, "0/0")
elseif t == "number" then elseif current_value == math.huge then
if v ~= v then table.insert(result_pack, "math.huge")
return "0/0" elseif current_value == -math.huge then
elseif v == math.huge then table.insert(result_pack, "-math.huge")
return "math.huge"
elseif v == -math.huge then
return "-math.huge"
else else
return tostring(v) table.insert(result_pack, tostring(current_value))
end end
elseif t == "string" then elseif t == "string" then
return string.format("%q", v):gsub("\\\n","\\n") table.insert(result_pack, (string.format("%q", current_value):gsub("\\\n","\\n")))
elseif t == "table" and pretty and getmetatable(v) and getmetatable(v).__tostring then elseif
return tostring(v) t == "nil" or
t == "boolean" or
pretty and (t ~= "table" or (getmetatable(current_value) or {}).__tostring) then
table.insert(result_pack, tostring(current_value))
elseif t == "table" then elseif t == "table" then
if ts[v] then if ts[current_value] then
if pretty then if pretty then
return "recursion" table.insert(result_pack, "recursion")
return
else else
error("tables with cycles are not supported") error("tables with cycles are not supported")
end end
end end
ts[v] = true ts[current_value] = true
local i, r = 1, nil
local f local f
if pretty then if pretty then
local ks, sks, oks = {}, {}, {} local ks, sks, oks = {}, {}, {}
for k in local_pairs(v) do for k in local_pairs(current_value) do
if type(k) == "number" then if type(k) == "number" then
table.insert(ks, k) table.insert(ks, k)
elseif type(k) == "string" then elseif type(k) == "string" then
@ -72,46 +72,51 @@ function serialization.serialize(value, pretty)
n = n + 1 n = n + 1
local k = ks[n] local k = ks[n]
if k ~= nil then if k ~= nil then
return k, v[k] return k, current_value[k]
else else
return nil return nil
end end
end) end)
else else
f = table.pack(local_pairs(v)) f = table.pack(local_pairs(current_value))
end end
local i = 1
local first = true
table.insert(result_pack, "{")
for k, v in table.unpack(f) do for k, v in table.unpack(f) do
if r then if not first then
r = r .. "," .. (pretty and ("\n" .. string.rep(" ", l)) or "") table.insert(result_pack, ",")
else if pretty then
r = "{" table.insert(result_pack, "\n" .. string.rep(" ", depth))
end end
end
first = nil
local tk = type(k) local tk = type(k)
if tk == "number" and k == i then if tk == "number" and k == i then
i = i + 1 i = i + 1
r = r .. s(v, l + 1) recurse(v, depth + 1)
else else
if tk == "string" and not kw[k] and string.match(k, id) then if tk == "string" and not kw[k] and string.match(k, id) then
r = r .. k table.insert(result_pack, k)
else else
r = r .. "[" .. s(k, l + 1) .. "]" table.insert(result_pack, "[")
recurse(k, depth + 1)
table.insert(result_pack, "]")
end end
r = r .. "=" .. s(v, l + 1) table.insert(result_pack, "=")
recurse(v, depth + 1)
end end
end end
ts[v] = nil -- allow writing same table more than once ts[current_value] = nil -- allow writing same table more than once
return (r or "{") .. "}" table.insert(result_pack, "}")
else
if pretty then
return tostring(v)
else else
error("unsupported type: " .. t) error("unsupported type: " .. t)
end end
end end
end recurse(value, 1)
local result = s(value, 1) local result = table.concat(result_pack)
local limit = type(pretty) == "number" and pretty or 10
if pretty then if pretty then
local limit = type(pretty) == "number" and pretty or 10
local truncate = 0 local truncate = 0
while limit > 0 and truncate do while limit > 0 and truncate do
truncate = string.find(result, "\n", truncate + 1, true) truncate = string.find(result, "\n", truncate + 1, true)

View File

@ -70,7 +70,7 @@ end
local function build_horizontal_reader(cursor) local function build_horizontal_reader(cursor)
cursor.clear_tail = function(self) cursor.clear_tail = function(self)
local w,_,dx,dy,x,y = tty.getViewport() local w,_,dx,dy,x,y = tty.getViewport()
local _,s2=tty.internal.split(self) local _,s2=tty.split(self)
local wlen = math.min(unicode.wlen(s2),w-x+1) local wlen = math.min(unicode.wlen(s2),w-x+1)
tty.gpu().fill(x+dx,y+dy,wlen,1," ") tty.gpu().fill(x+dx,y+dy,wlen,1," ")
end end
@ -87,15 +87,16 @@ local function build_horizontal_reader(cursor)
cursor.draw = function(_, text) cursor.draw = function(_, text)
local nowrap = tty.window.nowrap local nowrap = tty.window.nowrap
tty.window.nowrap = true tty.window.nowrap = true
tty:write(text) tty.stream:write(text)
tty.window.nowrap = nowrap tty.window.nowrap = nowrap
end end
cursor.scroll = function(self) cursor.scroll = function(self, goback, prev_x)
local win = tty.window local win = tty.window
local gpu,data,px,i = win.gpu, self.data, self.promptx, self.index win.x = goback and prev_x or win.x
local w,_,dx,dy,x,y = tty.getViewport() local x = win.x
win.x = math.max(self.promptx, math.min(w, x)) local w = win.width
local available,sx,sy = w-px+1,px+dx,y+dy local data,px,i = self.data, self.promptx, self.index
local available = w-px+1
if x > w then if x > w then
local blank local blank
if i == unicode.len(data) then if i == unicode.len(data) then
@ -107,14 +108,19 @@ local function build_horizontal_reader(cursor)
local rev = unicode.reverse(data) local rev = unicode.reverse(data)
local ending = unicode.wtrunc(rev, available+1) local ending = unicode.wtrunc(rev, available+1)
data = unicode.reverse(ending) data = unicode.reverse(ending)
gpu.set(sx,sy,data..blank) win.x = self.promptx
self:draw(data..blank)
-- wide chars may place the cursor not exactly at the end
win.x = math.min(w, self.promptx + unicode.wlen(data)) win.x = math.min(w, self.promptx + unicode.wlen(data))
-- x could be negative, we scroll it back into view
elseif x < self.promptx then elseif x < self.promptx then
data = unicode.sub(data, self.index+1) data = unicode.sub(data, self.index+1)
if unicode.wlen(data) > available then if unicode.wlen(data) > available then
data = unicode.wtrunc(data,available+1) data = unicode.wtrunc(data,available+1)
end end
gpu.set(sx,sy,data) win.x = self.promptx
self:draw(data)
win.x = math.max(px, math.min(w, x))
end end
end end
cursor.clear = function(self) cursor.clear = function(self)
@ -135,26 +141,16 @@ local function inject_filter(handler, filter)
end end
end end
local mt = handler.key_down = function(self, cursor, char, code)
{
__newindex = function(tbl, key, value)
if key == "key_down" then
local tty_key_down = value
value = function(_handler, cursor, char, code)
if code == keys.enter or code == keys.numpadenter then if code == keys.enter or code == keys.numpadenter then
if not filter(cursor.data) then if not filter(cursor.data) then
computer.beep(2000, 0.1) computer.beep(2000, 0.1)
return false -- ignore return false -- ignore
end end
end end
return tty_key_down(_handler, cursor, char, code) return tty.key_down_handler(self, cursor, char, code)
end end
end end
rawset(tbl, key, value)
end
}
setmetatable(handler, mt)
end
end end
local function inject_mask(cursor, dobreak, pwchar) local function inject_mask(cursor, dobreak, pwchar)
@ -198,15 +194,16 @@ function term.read(history, dobreak, hint, pwchar, filter)
local handler = history local handler = history
handler.hint = handler.hint or hint handler.hint = handler.hint or hint
local cursor = tty.internal.build_vertical_reader() local cursor = tty.build_vertical_reader()
if handler.nowrap then if handler.nowrap then
build_horizontal_reader(cursor) build_horizontal_reader(cursor)
end end
inject_filter(handler, filter) inject_filter(handler, filter)
inject_mask(cursor, dobreak, pwchar or history.pwchar) inject_mask(cursor, dobreak, pwchar or history.pwchar)
handler.cursor = cursor
return tty:read(handler, cursor) return tty.read(handler)
end end
function term.getGlobalArea(window) function term.getGlobalArea(window)
@ -221,6 +218,14 @@ function term.clearLine(window)
window.x = 1 window.x = 1
end end
function term.setCursorBlink(enabled)
tty.window.blink = enabled
end
function term.getCursorBlink()
return tty.window.blink
end
function term.pull(...) function term.pull(...)
local args = table.pack(...) local args = table.pack(...)
local timeout = nil local timeout = nil
@ -228,7 +233,15 @@ function term.pull(...)
timeout = table.remove(args, 1) timeout = table.remove(args, 1)
args.n = args.n - 1 args.n = args.n - 1
end end
return tty.pull(nil, timeout, table.unpack(args, 1, args.n)) local stdin_stream = io.stdin.stream
if stdin_stream.pull then
return stdin_stream:pull(nil, timeout, table.unpack(args, 1, args.n))
end
-- if stdin does not have pull() we can build the result
local result = io.read(1)
if result then
return "clipboard", nil, result
end
end end
function term.bind(gpu, window) function term.bind(gpu, window)

View File

@ -77,17 +77,29 @@ local function get_box_thread_handle(handles, bCreate)
end end
function box_thread:resume() function box_thread:resume()
if self:status() ~= "suspended" then local mt = getmetatable(self)
return nil, "cannot resume " .. self:status() .. " thread" if mt.__status ~= "suspended" then
return nil, "cannot resume " .. mt.__status .. " thread"
end end
getmetatable(self).__status = "running" mt.__status = "running"
-- register the thread to wake up
if coroutine.status(self.pco.root) == "suspended" and not mt.reg then
mt.register(0)
end
return true
end end
function box_thread:suspend() function box_thread:suspend()
if self:status() ~= "running" then local mt = getmetatable(self)
return nil, "cannot suspend " .. self:status() .. " thread" if mt.__status ~= "running" then
return nil, "cannot suspend " .. mt.__status .. " thread"
end end
getmetatable(self).__status = "suspended" mt.__status = "suspended"
local pco_status = coroutine.status(self.pco.root)
if pco_status == "running" or pco_status == "normal" then
mt.coma()
end
return true
end end
function box_thread:status() function box_thread:status()
@ -113,29 +125,27 @@ function box_thread:attach(parent)
if not proc then return nil, "thread failed to attach, process not found" end if not proc then return nil, "thread failed to attach, process not found" end
if mt.attached == proc then return self end -- already attached if mt.attached == proc then return self end -- already attached
local waiting_handler
if mt.attached then if mt.attached then
local prev_btHandle = assert(get_box_thread_handle(mt.attached.data.handles), "thread panic: no thread handle") local prev_btHandle = assert(get_box_thread_handle(mt.attached.data.handles), "thread panic: no thread handle")
for i,h in ipairs(prev_btHandle) do for i,h in ipairs(prev_btHandle) do
if h == self then if h == self then
table.remove(prev_btHandle, i) table.remove(prev_btHandle, i)
if mt.id then
waiting_handler = assert(mt.attached.data.handlers[mt.id], "thread panic: no event handler")
mt.attached.data.handlers[mt.id] = nil
end
break break
end end
end end
end end
-- registration happens on the attached proc, unregister before reparenting
local waiting_handler = mt.unregister()
-- attach to parent or the current process -- attach to parent or the current process
mt.attached = proc mt.attached = proc
local handles = proc.data.handles
-- this process may not have a box_thread manager handle -- this process may not have a box_thread manager handle
local btHandle = get_box_thread_handle(handles, true) local btHandle = get_box_thread_handle(proc.data.handles, true)
table.insert(btHandle, self) table.insert(btHandle, self)
-- register on the new parent
if waiting_handler then -- event-waiting if waiting_handler then -- event-waiting
mt.register(waiting_handler.timeout - computer.uptime()) mt.register(waiting_handler.timeout - computer.uptime())
end end
@ -143,6 +153,23 @@ function box_thread:attach(parent)
return self return self
end end
function thread.current()
local proc = process.findProcess()
local thread_root
while proc do
if thread_root then
for _,bt in ipairs(get_box_thread_handle(proc.data.handles) or {}) do
if bt.pco.root == thread_root then
return bt
end
end
else
thread_root = proc.data.coroutine_handler.root
end
proc = proc.parent
end
end
function thread.create(fp, ...) function thread.create(fp, ...)
checkArg(1, fp, "function") checkArg(1, fp, "function")
@ -153,8 +180,8 @@ function thread.create(fp, ...)
mt.__status = "running" mt.__status = "running"
local fp_co = t.pco.create(fp) local fp_co = t.pco.create(fp)
-- run fp_co until dead -- run fp_co until dead
-- pullSignal will yield_all past this point -- pullSignal will yield_past this point
-- but yield will return here, we pullSignal from here to yield_all -- but yield will return here, we pullSignal from here to yield_past
local args = table.pack(...) local args = table.pack(...)
while true do while true do
local result = table.pack(t.pco.resume(fp_co, table.unpack(args, 1, args.n))) local result = table.pack(t.pco.resume(fp_co, table.unpack(args, 1, args.n)))
@ -188,7 +215,7 @@ function thread.create(fp, ...)
--special resume to keep track of process death --special resume to keep track of process death
function mt.private_resume(...) function mt.private_resume(...)
mt.id = nil mt.unregister()
-- this thread may have been killed -- this thread may have been killed
if t:status() == "dead" then return end if t:status() == "dead" then return end
local result = table.pack(t.pco.resume(t.pco.root, ...)) local result = table.pack(t.pco.resume(t.pco.root, ...))
@ -209,17 +236,50 @@ function thread.create(fp, ...)
timeout, -- wait for the time specified by the caller timeout, -- wait for the time specified by the caller
1, -- we only want this thread to wake up once 1, -- we only want this thread to wake up once
mt.attached.data.handlers) -- optional arg, to specify our own handlers mt.attached.data.handlers) -- optional arg, to specify our own handlers
mt.reg = mt.attached.data.handlers[mt.id]
end
function mt.unregister()
local id = mt.id
local reg = mt.reg
mt.id = nil
mt.reg = nil
-- before just removing a handler, make sure it is still ours
if id and mt.attached and mt.attached.data.handlers[id] == reg then
mt.attached.data.handlers[id] = nil
return reg
end
end
function mt.coma()
mt.unregister() -- we should not wake up again (until resumed)
while mt.__status == "suspended" do
t.pco.yield_past(t.pco.root, 0)
end
end end
function mt.process.data.pull(_, timeout) function mt.process.data.pull(_, timeout)
--[==[
yield_past(root) will yield until out of this thread
registration puts in a callback to resume this thread
Subsequent registrations are necessary in case the thread is suspended
This thread yields when suspended, entering a coma state
-> coma state: yield without registration
resume will regsiter a wakeup call, breaks coma
subsequent yields need not specify a timeout because
we already legitimately resumed only to find out we had been suspended
3 places register for wake up
1. computer.pullSignal [this path]
2. t:attach(proc) will unregister and re-register
3. t:resume() of a suspended thread
]==]
mt.register(timeout) mt.register(timeout)
-- yield_past(root) will yield until out of this thread local event_data = table.pack(t.pco.yield_past(t.pco.root, timeout))
-- the callback will resume this stack mt.coma()
local event_data
repeat
event_data = table.pack(t.pco.yield_past(t.pco.root, timeout))
-- during sleep, we may have been suspended
until t:status() ~= "suspended"
return table.unpack(event_data, 1, event_data.n) return table.unpack(event_data, 1, event_data.n)
end end

View File

@ -16,7 +16,7 @@ tty.window =
y = 1, y = 1,
} }
tty.internal = {} tty.stream = {}
function tty.key_down_handler(handler, cursor, char, code) function tty.key_down_handler(handler, cursor, char, code)
local data = cursor.data local data = cursor.data
@ -92,7 +92,7 @@ function tty.gpu()
end end
function tty.clear() function tty.clear()
tty.scroll(math.huge) tty.stream.scroll(math.huge)
tty.setCursor(1, 1) tty.setCursor(1, 1)
end end
@ -101,46 +101,26 @@ function tty.isAvailable()
return not not (gpu and gpu.getScreen()) return not not (gpu and gpu.getScreen())
end end
function tty.pull(cursor, timeout, ...) function tty.stream:blink(done)
local blink = tty.getCursorBlink()
timeout = timeout or math.huge
local blink_timeout = blink and .5 or math.huge
local gpu = tty.gpu()
local width, height, dx, dy, x, y = tty.getViewport() local width, height, dx, dy, x, y = tty.getViewport()
local gpu = tty.gpu()
if gpu then if not gpu or x < 1 or x > width or y < 1 or y > height then
if x < 1 or x > width or y < 1 or y > height then return true
if cursor then
cursor:move(0)
cursor:scroll()
else
gpu = nil
end end
end
x, y = tty.getCursor()
x, y = x + dx, y + dy x, y = x + dx, y + dy
end local blinked, bgColor, bgIsPalette, fgColor, fgIsPalette, char_at_cursor = table.unpack(self.blink_cache or {})
if done == nil then -- reset
local bgColor, bgIsPalette blinked = false
local fgColor, fgIsPalette
local char_at_cursor
if gpu then
bgColor, bgIsPalette = gpu.getBackground() bgColor, bgIsPalette = gpu.getBackground()
-- it can happen during a type of race condition when a screen is removed -- it can happen during a type of race condition when a screen is removed
if not bgColor then if not bgColor then
return nil, "interrupted" return
end end
fgColor, fgIsPalette = gpu.getForeground() fgColor, fgIsPalette = gpu.getForeground()
char_at_cursor = gpu.get(x, y) char_at_cursor = gpu.get(x, y)
end end
-- get the next event
local blinked = false
local done = false
local signal
while true do
if gpu then
if not blinked and not done then if not blinked and not done then
gpu.setForeground(bgColor, bgIsPalette) gpu.setForeground(bgColor, bgIsPalette)
gpu.setBackground(fgColor, fgIsPalette) gpu.setBackground(fgColor, fgIsPalette)
@ -148,30 +128,39 @@ function tty.pull(cursor, timeout, ...)
gpu.setForeground(fgColor, fgIsPalette) gpu.setForeground(fgColor, fgIsPalette)
gpu.setBackground(bgColor, bgIsPalette) gpu.setBackground(bgColor, bgIsPalette)
blinked = true blinked = true
elseif blinked and (done or blink) then elseif blinked and (done or tty.window.blink) then
gpu.set(x, y, char_at_cursor) gpu.set(x, y, char_at_cursor)
blinked = false blinked = false
end end
self.blink_cache = table.pack(blinked, bgColor, bgIsPalette, fgColor, fgIsPalette, char_at_cursor)
return true
end end
function tty.stream:pull(timeout, ...)
timeout = timeout or math.huge
local blink_timeout = tty.window.blink and .5 or math.huge
-- it can happen during a type of race condition when a screen is removed
if not self:blink() then
return nil, "interrupted"
end
-- get the next event
while true do
local signal = table.pack(event.pull(math.min(blink_timeout, timeout), ...))
timeout = timeout - blink_timeout
local done = signal.n > 1 or timeout < blink_timeout
self:blink(done)
if done then if done then
return table.unpack(signal, 1, signal.n) return table.unpack(signal, 1, signal.n)
end end
-- if vt100 ansi codes have anything buffered for read, return that first
if tty.window.ansi_response then
signal = {"clipboard", tty.keyboard(), tty.window.ansi_response, n=3}
tty.window.ansi_response = nil
else
signal = table.pack(event.pull(math.min(blink_timeout, timeout), ...))
end
timeout = timeout - blink_timeout
done = signal.n > 1 or timeout < blink_timeout
end end
end end
function tty.internal.split(cursor) function tty.split(cursor)
local data, index = cursor.data, cursor.index local data, index = cursor.data, cursor.index
local dlen = unicode.len(data) local dlen = unicode.len(data)
index = math.max(0, math.min(index, dlen)) index = math.max(0, math.min(index, dlen))
@ -179,22 +168,26 @@ function tty.internal.split(cursor)
return unicode.sub(data, 1, index), tail == 0 and "" or unicode.sub(data, -tail) return unicode.sub(data, 1, index), tail == 0 and "" or unicode.sub(data, -tail)
end end
function tty.internal.build_vertical_reader() function tty.build_vertical_reader()
local x, y = tty.getCursor()
return return
{ {
promptx = x, promptx = tty.window.x,
prompty = y, prompty = tty.window.y,
index = 0, index = 0,
data = "", data = "",
sy = 0, sy = 0,
scroll = function(self) scroll = function(self, goback, prev_x, prev_y)
self.sy = self.sy + tty.scroll() local width, x = tty.window.width, tty.getCursor() - 1
tty.setCursor(x % width + 1, tty.window.y + math.floor(x / width))
self:draw("")
if goback then
tty.setCursor(prev_x, prev_y - self.sy)
end
end, end,
move = function(self, n) move = function(self, n)
local win = tty.window local win = tty.window
self.index = math.min(math.max(0, self.index + n), unicode.len(self.data)) self.index = math.min(math.max(0, self.index + n), unicode.len(self.data))
local s1, s2 = tty.internal.split(self) local s1, s2 = tty.split(self)
s2 = unicode.sub(s2.." ", 1, 1) s2 = unicode.sub(s2.." ", 1, 1)
local data_remaining = ("_"):rep(self.promptx - 1)..s1..s2 local data_remaining = ("_"):rep(self.promptx - 1)..s1..s2
win.y = self.prompty - self.sy win.y = self.prompty - self.sy
@ -221,7 +214,7 @@ function tty.internal.build_vertical_reader()
tty.gpu().fill(cx + dx, ey + dy, width - cx + 1, 1, " ") tty.gpu().fill(cx + dx, ey + dy, width - cx + 1, 1, " ")
end, end,
update = function(self, arg) update = function(self, arg)
local s1, s2 = tty.internal.split(self) local s1, s2 = tty.split(self)
if type(arg) == "string" then if type(arg) == "string" then
self.data = s1 .. arg .. s2 self.data = s1 .. arg .. s2
self.index = self.index + unicode.len(arg) self.index = self.index + unicode.len(arg)
@ -242,11 +235,10 @@ function tty.internal.build_vertical_reader()
end end
-- redraw suffix -- redraw suffix
if s2 ~= "" then local prev_x, prev_y = tty.getCursor()
local ps, px, py = self.sy, tty.getCursor() prev_y = prev_y + self.sy -- scroll will remove it
self:draw(s2) self:draw(s2)
tty.setCursor(px, py - (self.sy - ps)) self:scroll(s2 ~= "", prev_x, prev_y)
end
end, end,
clear = function(self) clear = function(self)
self:move(-math.huge) self:move(-math.huge)
@ -256,28 +248,30 @@ function tty.internal.build_vertical_reader()
self.data = "" self.data = ""
end, end,
draw = function(self, text) draw = function(self, text)
self.sy = self.sy + tty:write(text) self.sy = self.sy + tty.stream:write(text)
end end
} }
end end
-- PLEASE do not use this method directly, use io.read or tty.read function tty.read(handler)
function tty.read(_, handler, cursor) tty.window.handler = handler
checkArg(1, handler, "table", "number")
checkArg(2, cursor, "table", "nil")
if not io.stdin.tty then local stdin = io.stdin
return io.stdin:readLine(false) local result = table.pack(pcall(stdin.readLine, stdin, false))
tty.window.handler = nil
return select(2, assert(table.unpack(result)))
end end
if type(handler) ~= "table" then -- PLEASE do not use this method directly, use io.read or term.read
handler = {} function tty.stream:read()
end local handler = tty.window.handler or {}
local cursor = handler.cursor or tty.build_vertical_reader()
tty.window.handler = nil
handler.index = 0 handler.index = 0
cursor = cursor or tty.internal.build_vertical_reader()
while true do while true do
local name, address, char, code = tty.pull(cursor) local name, address, char, code = self:pull()
-- we may have lost tty during the pull -- we may have lost tty during the pull
if not tty.isAvailable() then if not tty.isAvailable() then
return return
@ -288,7 +282,7 @@ function tty.read(_, handler, cursor)
local main_kb = tty.keyboard() local main_kb = tty.keyboard()
local main_sc = tty.screen() local main_sc = tty.screen()
if name == "interrupted" then if name == "interrupted" then
tty:write("^C\n") self:write("^C\n")
return false return false
elseif address == main_kb or address == main_sc then elseif address == main_kb or address == main_sc then
local handler_method = handler[name] or local handler_method = handler[name] or
@ -321,10 +315,7 @@ function tty.setCursor(x, y)
end end
-- PLEASE do not use this method directly, use io.write or term.write -- PLEASE do not use this method directly, use io.write or term.write
function tty.write(_, value) function tty.stream:write(value)
if not io.stdout.tty then
return io.write(value)
end
local gpu = tty.gpu() local gpu = tty.gpu()
if not gpu then if not gpu then
return return
@ -370,7 +361,7 @@ function tty.write(_, value)
-- scroll before parsing next line -- scroll before parsing next line
-- the value may only have been a newline -- the value may only have been a newline
sy = sy + tty.scroll() sy = sy + self.scroll()
-- we may have needed to scroll one last time [nowrap adjustments] -- we may have needed to scroll one last time [nowrap adjustments]
if #value == 0 then if #value == 0 then
break break
@ -386,15 +377,18 @@ function tty.write(_, value)
local tail = "" local tail = ""
local wlen_needed = unicode.wlen(segment) local wlen_needed = unicode.wlen(segment)
local wlen_remaining = window.width - x + 1 local wlen_remaining = window.width - x + 1
if not window.nowrap and wlen_remaining < wlen_needed then if wlen_remaining < wlen_needed then
segment = unicode.wtrunc(segment, wlen_remaining + 1) segment = unicode.wtrunc(segment, wlen_remaining + 1)
wlen_needed = unicode.wlen(segment) local wlen_used = unicode.wlen(segment)
-- we can clear the line because we already know remaining < needed -- we can clear the line because we already know remaining < needed
tail = (" "):rep(wlen_remaining - wlen_needed) tail = (" "):rep(wlen_remaining - wlen_used)
if not window.nowrap then
-- we have to reparse the delimeter -- we have to reparse the delimeter
ei = #segment ei = #segment
-- fake a newline -- fake a newline
delim = "\n" delim = "\n"
wlen_needed = wlen_used
end
end end
gpu.set(gpu_x, gpu_y, segment..tail) gpu.set(gpu_x, gpu_y, segment..tail)
x = x + wlen_needed x = x + wlen_needed
@ -420,14 +414,6 @@ function tty.write(_, value)
return sy return sy
end end
function tty.setCursorBlink(enabled)
tty.window.blink = enabled
end
function tty.getCursorBlink()
return tty.window.blink
end
local gpu_intercept = {} local gpu_intercept = {}
function tty.bind(gpu) function tty.bind(gpu)
checkArg(1, gpu, "table") checkArg(1, gpu, "table")
@ -446,11 +432,13 @@ function tty.bind(gpu)
end end
end end
local window = tty.window local window = tty.window
if not window.gpu or window.gpu == gpu then
window.gpu = gpu window.gpu = gpu
window.keyboard = nil -- without a keyboard bound, always use the screen's main keyboard (1st) window.keyboard = nil -- without a keyboard bound, always use the screen's main keyboard (1st)
screen_reset(gpu)
tty.getViewport() tty.getViewport()
end end
screen_reset(gpu)
end
function tty.keyboard() function tty.keyboard()
-- this method needs to be safe even if there is no terminal window (e.g. no gpu) -- this method needs to be safe even if there is no terminal window (e.g. no gpu)
@ -492,17 +480,29 @@ function tty.screen()
return gpu.getScreen() return gpu.getScreen()
end end
function tty.scroll(number) function tty.stream.scroll(lines)
local gpu = tty.gpu() local gpu = tty.gpu()
if not gpu then if not gpu then
return 0 return 0
end end
local width, height, dx, dy, x, y = tty.getViewport() local width, height, dx, dy, x, y = tty.getViewport()
local lines = number or (y - height) -- nil lines indicates a request to auto scroll
if lines == 0 -- if zero scroll length is requested, do nothing -- auto scroll is when the cursor has gone below the bottom on the terminal
or not number and lines < 0 then -- do not auto scroll back up, only down -- and the text is scroll up, pulling the cursor back into view
return 0
-- lines<0 scrolls up (text down)
-- lines>0 scrolls down (text up)
-- no lines count given, the user is asking to auto scroll y back into view
if not lines then
if y < 1 then
lines = y - 1 -- y==0 scrolls back -1
elseif y > height then
lines = y - height -- y==height+1 scroll forward 1
else
return 0 -- do nothing
end
end end
lines = math.min(lines, height) lines = math.min(lines, height)
@ -516,16 +516,16 @@ function tty.scroll(number)
gpu.copy(dx + 1, dy + 1 + math.max(0, lines), width, box_height, 0, -lines) gpu.copy(dx + 1, dy + 1 + math.max(0, lines), width, box_height, 0, -lines)
gpu.fill(dx + 1, fill_top, width, abs_lines, ' ') gpu.fill(dx + 1, fill_top, width, abs_lines, ' ')
tty.setCursor(x, math.min(y, height)) tty.setCursor(x, math.max(1, math.min(y, height)))
return lines return lines
end end
-- stream methods -- stream methods
local function bfd() return nil, "tty: invalid operation" end local function bfd() return nil, "tty: invalid operation" end
tty.close = bfd tty.stream.close = bfd
tty.seek = bfd tty.stream.seek = bfd
tty.handle = "tty" tty.stream.handle = "tty"
require("package").delay(tty, "/lib/core/full_tty.lua") require("package").delay(tty, "/lib/core/full_tty.lua")

View File

@ -120,7 +120,9 @@ end
-- [6n get the cursor position [ EscLine;ColumnR Response: cursor is at v,h ] -- [6n get the cursor position [ EscLine;ColumnR Response: cursor is at v,h ]
rules[{"%[", "6", "n"}] = function(window) rules[{"%[", "6", "n"}] = function(window)
window.ansi_response = string.format("%s%d;%dR", string.char(0x1b), window.y, window.x) -- this solution puts the response on stdin, but it isn't echo'd
-- I'm personally fine with the lack of echo
io.stdin.bufferRead = string.format("%s%s%d;%dR", io.stdin.bufferRead, string.char(0x1b), window.y, window.x)
end end
-- D scroll up one line -- moves cursor down -- D scroll up one line -- moves cursor down
@ -137,10 +139,8 @@ rules[{"[DEM]"}] = function(window, dir)
end end
end end
-- 7 save cursor position and attributes local function save_attributes(window, save)
-- 8 restore cursor position and attributes if save then
rules[{"[78]"}] = function(window, restore)
if restore == "8" then
local data = window.saved or {1, 1, {0x0}, {0xffffff}} local data = window.saved or {1, 1, {0x0}, {0xffffff}}
window.x = data[1] window.x = data[1]
window.y = data[2] window.y = data[2]
@ -151,6 +151,18 @@ rules[{"[78]"}] = function(window, restore)
end end
end end
-- 7 save cursor position and attributes
-- 8 restore cursor position and attributes
rules[{"[78]"}] = function(window, restore)
save_attributes(window, restore == "8")
end
-- s save cursor position
-- u restore cursor position
rules[{"%[", "[su]"}] = function(window, _, restore)
save_attributes(window, restore == "u")
end
function vt100.parse(window) function vt100.parse(window)
local ansi = window.ansi_escape local ansi = window.ansi_escape
window.ansi_escape = nil window.ansi_escape = nil

View File

@ -0,0 +1 @@
{ignore=true}

View File

@ -73,6 +73,7 @@ Laire # Perry Rhodan
Loader 1340 # Borderlands 2 Loader 1340 # Borderlands 2
LordFokas # Contributor LordFokas # Contributor
Marvin # Hitchhiker's Guide to the Galaxy Marvin # Hitchhiker's Guide to the Galaxy
Mawhrin-skel # The Player of Games (Iain M Banks)
Michiyo # Contributor Michiyo # Contributor
*Mute # Analogue: A Hate Story / Hate Plus *Mute # Analogue: A Hate Story / Hate Plus
Mycroft Holmes # The Moon Is a Harsh Mistress Mycroft Holmes # The Moon Is a Harsh Mistress
@ -87,6 +88,7 @@ Replicator # Stargate
Robby # Forbidden Planet Robby # Forbidden Planet
Roomba # Under your couch... wait. Roomba # Under your couch... wait.
Rosie # The Jetsons Rosie # The Jetsons
Skaffen-Amtiskaw # Use of weapons (Iain M Banks)
Shakey # The first general-purpose mobile robot that could reason about its actions. Shakey # The first general-purpose mobile robot that could reason about its actions.
SHODAN # System Shock SHODAN # System Shock
Skynet # Terminator Skynet # Terminator