mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-16 18:55:03 -04:00
Merge branch 'master-MC1.8.9' into master-MC1.9.4
This commit is contained in:
commit
ac1360f038
@ -2,9 +2,15 @@ local args, options = require("shell").parse(...)
|
||||
if options.help then
|
||||
print([[`echo` writes the provided string(s) to the standard output.
|
||||
-n do not output the trialing newline
|
||||
-e enable interpretation of backslash escapes
|
||||
--help display this help and exit]])
|
||||
return
|
||||
end
|
||||
if options.e then
|
||||
for index,arg in ipairs(args) do
|
||||
args[index] = assert(load("return \"" .. arg:gsub('"', [[\"]]) .. "\""))()
|
||||
end
|
||||
end
|
||||
io.write(table.concat(args," "))
|
||||
if not options.n then
|
||||
print()
|
||||
|
@ -10,11 +10,12 @@ local arg = args[1]
|
||||
local path = shell.resolve(arg)
|
||||
|
||||
if ops.help then
|
||||
print([[Usage: list [path]
|
||||
io.write([[Usage: list [path]
|
||||
path:
|
||||
optional argument (defaults to ./)
|
||||
Displays a list of files in the given path with no added formatting
|
||||
Intended for low memory systems]])
|
||||
Intended for low memory systems
|
||||
]])
|
||||
return 0
|
||||
end
|
||||
|
||||
@ -28,5 +29,5 @@ if why then
|
||||
end
|
||||
|
||||
for item in fs.list(real) do
|
||||
print(item)
|
||||
io.write(item, '\n')
|
||||
end
|
||||
|
@ -10,11 +10,9 @@ if input[2] then
|
||||
table.insert(args, 1, input[2])
|
||||
end
|
||||
|
||||
local history = {hint = sh.hintHandler}
|
||||
shell.prime()
|
||||
local update_gpu = io.output().tty
|
||||
local interactive = io.input().tty
|
||||
local foreground
|
||||
|
||||
if #args == 0 then
|
||||
while true do
|
||||
@ -22,34 +20,31 @@ if #args == 0 then
|
||||
while not tty.isAvailable() do
|
||||
event.pull("term_available")
|
||||
end
|
||||
if not foreground and interactive then -- first time run AND interactive
|
||||
if interactive == true then -- first time run AND interactive
|
||||
interactive = 0
|
||||
tty.setReadHandler({hint = sh.hintHandler})
|
||||
dofile("/etc/profile.lua")
|
||||
end
|
||||
foreground = tty.gpu().setForeground(0xFF0000)
|
||||
io.write(sh.expand(os.getenv("PS1") or "$ "))
|
||||
tty.gpu().setForeground(foreground)
|
||||
tty.setCursorBlink(true)
|
||||
end
|
||||
local command = tty.read(history)
|
||||
local command = io.read()
|
||||
if command then
|
||||
command = text.trim(command)
|
||||
if command == "exit" then
|
||||
return
|
||||
elseif command ~= "" then
|
||||
local result, reason = sh.execute(_ENV, command)
|
||||
if update_gpu and tty.getCursor() > 1 then
|
||||
io.write("\n")
|
||||
end
|
||||
if not result then
|
||||
io.stderr:write((reason and tostring(reason) or "unknown error") .. "\n")
|
||||
end
|
||||
end
|
||||
elseif command == nil then -- command==false is a soft interrupt, ignore it
|
||||
if interactive then
|
||||
io.write("exit\n") -- pipe closed
|
||||
end
|
||||
elseif not interactive then
|
||||
return -- eof
|
||||
end
|
||||
if update_gpu and tty.getCursor() > 1 then
|
||||
io.write("\n")
|
||||
end
|
||||
end
|
||||
else
|
||||
-- execute command.
|
||||
|
@ -1,57 +1,14 @@
|
||||
local buffer = require("buffer")
|
||||
local tty = require("tty")
|
||||
|
||||
local stdinStream = {handle="stdin"}
|
||||
local stdoutStream = {handle="stdout"}
|
||||
local stderrStream = {handle="stderr"}
|
||||
local stdinHistory = {}
|
||||
|
||||
local function badFileDescriptor()
|
||||
return nil, "bad file descriptor"
|
||||
local core_stdin = buffer.new("r", tty)
|
||||
local core_stdout = buffer.new("w", tty)
|
||||
local core_stderr = buffer.new("w", setmetatable(
|
||||
{
|
||||
write = function(_, str)
|
||||
return tty:write("\27[31m"..str.."\27[37m")
|
||||
end
|
||||
|
||||
function stdinStream.close()
|
||||
return nil, "cannot close standard file"
|
||||
end
|
||||
stdoutStream.close = stdinStream.close
|
||||
stderrStream.close = stdinStream.close
|
||||
|
||||
function stdinStream.read()
|
||||
return tty.read(stdinHistory)
|
||||
end
|
||||
|
||||
function stdoutStream:write(str)
|
||||
tty.drawText(str, self.nowrap)
|
||||
return self
|
||||
end
|
||||
|
||||
function stderrStream:write(str)
|
||||
local gpu = tty.gpu()
|
||||
local set_depth = gpu and gpu.getDepth() and gpu.getDepth() > 1
|
||||
|
||||
if set_depth then
|
||||
set_depth = gpu.setForeground(0xFF0000)
|
||||
end
|
||||
|
||||
tty.drawText(str)
|
||||
|
||||
if set_depth then
|
||||
gpu.setForeground(set_depth)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
stdinStream.seek = badFileDescriptor
|
||||
stdinStream.write = badFileDescriptor
|
||||
stdoutStream.read = badFileDescriptor
|
||||
stdoutStream.seek = badFileDescriptor
|
||||
stderrStream.read = badFileDescriptor
|
||||
stderrStream.seek = badFileDescriptor
|
||||
|
||||
local core_stdin = buffer.new("r", stdinStream)
|
||||
local core_stdout = buffer.new("w", stdoutStream)
|
||||
local core_stderr = buffer.new("w", stderrStream)
|
||||
}, {__index=tty}))
|
||||
|
||||
core_stdout:setvbuf("no")
|
||||
core_stderr:setvbuf("no")
|
||||
@ -59,9 +16,9 @@ core_stdin.tty = true
|
||||
core_stdout.tty = true
|
||||
core_stderr.tty = true
|
||||
|
||||
core_stdin.close = stdinStream.close
|
||||
core_stdout.close = stdinStream.close
|
||||
core_stderr.close = stdinStream.close
|
||||
core_stdin.close = tty.close
|
||||
core_stdout.close = tty.close
|
||||
core_stderr.close = tty.close
|
||||
|
||||
local io_mt = getmetatable(io) or {}
|
||||
io_mt.__index = function(_, k)
|
||||
|
@ -2,7 +2,10 @@ local shell = require("shell")
|
||||
local tty = require("tty")
|
||||
local fs = require("filesystem")
|
||||
|
||||
if tty.isAvailable() then
|
||||
tty:write("\27[40m\27[37m")
|
||||
tty.clear()
|
||||
end
|
||||
dofile("/etc/motd")
|
||||
|
||||
shell.setAlias("dir", "ls")
|
||||
@ -27,7 +30,7 @@ os.setenv("HOME", "/home")
|
||||
os.setenv("IFS", " ")
|
||||
os.setenv("MANPATH", "/usr/man:.")
|
||||
os.setenv("PAGER", "/bin/more")
|
||||
os.setenv("PS1", "$HOSTNAME$HOSTNAME_SEPARATOR$PWD # ")
|
||||
os.setenv("PS1", "\27[40m\27[31m$HOSTNAME$HOSTNAME_SEPARATOR$PWD # \27[37m")
|
||||
os.setenv("LS_COLORS", "{FILE=0xFFFFFF,DIR=0x66CCFF,LINK=0xFFAA00,['*.lua']=0x00FF00}")
|
||||
|
||||
shell.setWorkingDirectory(os.getenv("HOME"))
|
||||
|
@ -9,6 +9,8 @@ local metatable = {
|
||||
|
||||
function buffer.new(mode, stream)
|
||||
local result = {
|
||||
closed = false,
|
||||
tty = false,
|
||||
mode = {},
|
||||
stream = stream,
|
||||
bufferRead = "",
|
||||
@ -116,15 +118,10 @@ function buffer:read(...)
|
||||
self:flush()
|
||||
end
|
||||
|
||||
local formats = table.pack(...)
|
||||
if formats.n == 0 then
|
||||
if select("#", ...) == 0 then
|
||||
return self:readLine(true)
|
||||
end
|
||||
return require("tools/buffered_read").read(self, readChunk, formats)
|
||||
end
|
||||
|
||||
function buffer:seek(whence, offset)
|
||||
return require("tools/buffered_read").seek(self, whence, offset)
|
||||
return self:formatted_read(readChunk, ...)
|
||||
end
|
||||
|
||||
function buffer:setvbuf(mode, size)
|
||||
@ -142,14 +139,6 @@ function buffer:setvbuf(mode, size)
|
||||
return self.bufferMode, self.bufferSize
|
||||
end
|
||||
|
||||
function buffer:getTimeout()
|
||||
return self.readTimeout
|
||||
end
|
||||
|
||||
function buffer:setTimeout(value)
|
||||
self.readTimeout = tonumber(value)
|
||||
end
|
||||
|
||||
function buffer:write(...)
|
||||
if self.closed then
|
||||
return nil, "bad file descriptor"
|
||||
@ -172,7 +161,7 @@ function buffer:write(...)
|
||||
if self.bufferMode == "no" then
|
||||
result, reason = self.stream:write(arg)
|
||||
else
|
||||
result, reason = require("tools/buffered_write").write(self, arg)
|
||||
result, reason = self:buffered_write(arg)
|
||||
end
|
||||
|
||||
if not result then
|
||||
@ -183,4 +172,6 @@ function buffer:write(...)
|
||||
return self
|
||||
end
|
||||
|
||||
require("package").delay(buffer, "/lib/core/full_buffer.lua")
|
||||
|
||||
return buffer
|
||||
|
@ -1,7 +1,7 @@
|
||||
-- called from /init.lua
|
||||
local raw_loadfile = ...
|
||||
|
||||
_G._OSVERSION = "OpenOS 1.6.6"
|
||||
_G._OSVERSION = "OpenOS 1.6.7"
|
||||
|
||||
local component = component
|
||||
local computer = computer
|
||||
|
@ -1,143 +1,15 @@
|
||||
local buffer = require("buffer")
|
||||
local unicode = require("unicode")
|
||||
local adv_buf = {}
|
||||
|
||||
function adv_buf.readNumber(self, readChunk)
|
||||
local len, sub
|
||||
if self.mode.b then
|
||||
len = rawlen
|
||||
sub = string.sub
|
||||
else
|
||||
len = unicode.len
|
||||
sub = unicode.sub
|
||||
function buffer:getTimeout()
|
||||
return self.readTimeout
|
||||
end
|
||||
|
||||
local buffer = ""
|
||||
local white_done
|
||||
|
||||
local function peek()
|
||||
if len(self.bufferRead) == 0 then
|
||||
local result, reason = readChunk(self)
|
||||
if not result then
|
||||
return result, reason
|
||||
end
|
||||
end
|
||||
return sub(self.bufferRead, 1, 1)
|
||||
function buffer:setTimeout(value)
|
||||
self.readTimeout = tonumber(value)
|
||||
end
|
||||
|
||||
local function pop()
|
||||
local n = sub(self.bufferRead, 1, 1)
|
||||
self.bufferRead = sub(self.bufferRead, 2)
|
||||
return n
|
||||
end
|
||||
|
||||
local function take()
|
||||
buffer = buffer .. pop()
|
||||
end
|
||||
|
||||
while true do
|
||||
local peeked = peek()
|
||||
if not peeked then
|
||||
break
|
||||
end
|
||||
|
||||
if peeked:match("[%s]") then
|
||||
if white_done then
|
||||
break
|
||||
end
|
||||
pop()
|
||||
else
|
||||
white_done = true
|
||||
if not tonumber(buffer .. peeked .. "0") then
|
||||
break
|
||||
end
|
||||
take() -- add pop to buffer
|
||||
end
|
||||
end
|
||||
|
||||
return tonumber(buffer)
|
||||
end
|
||||
|
||||
function adv_buf.readBytesOrChars(self, readChunk, n)
|
||||
n = math.max(n, 0)
|
||||
local len, sub
|
||||
if self.mode.b then
|
||||
len = rawlen
|
||||
sub = string.sub
|
||||
else
|
||||
len = unicode.len
|
||||
sub = unicode.sub
|
||||
end
|
||||
local buffer = ""
|
||||
repeat
|
||||
if len(self.bufferRead) == 0 then
|
||||
local result, reason = readChunk(self)
|
||||
if not result then
|
||||
if reason then
|
||||
return nil, reason
|
||||
else -- eof
|
||||
return #buffer > 0 and buffer or nil
|
||||
end
|
||||
end
|
||||
end
|
||||
local left = n - len(buffer)
|
||||
buffer = buffer .. sub(self.bufferRead, 1, left)
|
||||
self.bufferRead = sub(self.bufferRead, left + 1)
|
||||
until len(buffer) == n
|
||||
return buffer
|
||||
end
|
||||
|
||||
function adv_buf.readAll(self, readChunk)
|
||||
repeat
|
||||
local result, reason = readChunk(self)
|
||||
if not result and reason then
|
||||
return nil, reason
|
||||
end
|
||||
until not result -- eof
|
||||
local result = self.bufferRead
|
||||
self.bufferRead = ""
|
||||
return result
|
||||
end
|
||||
|
||||
function adv_buf.read(self, readChunk, formats)
|
||||
self.timeout = require("computer").uptime() + self.readTimeout
|
||||
local function read(n, format)
|
||||
if type(format) == "number" then
|
||||
return adv_buf.readBytesOrChars(self, readChunk, format)
|
||||
else
|
||||
local first_char_index = 1
|
||||
if type(format) ~= "string" then
|
||||
error("bad argument #" .. n .. " (invalid option)")
|
||||
elseif unicode.sub(format, 1, 1) == "*" then
|
||||
first_char_index = 2
|
||||
end
|
||||
format = unicode.sub(format, first_char_index, first_char_index)
|
||||
if format == "n" then
|
||||
return adv_buf.readNumber(self, readChunk)
|
||||
elseif format == "l" then
|
||||
return self:readLine(true, self.timeout)
|
||||
elseif format == "L" then
|
||||
return self:readLine(false, self.timeout)
|
||||
elseif format == "a" then
|
||||
return adv_buf.readAll(self, readChunk)
|
||||
else
|
||||
error("bad argument #" .. n .. " (invalid format)")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local results = {}
|
||||
for i = 1, formats.n do
|
||||
local result, reason = read(i, formats[i])
|
||||
if result then
|
||||
results[i] = result
|
||||
elseif reason then
|
||||
return nil, reason
|
||||
end
|
||||
end
|
||||
return table.unpack(results, 1, formats.n)
|
||||
end
|
||||
|
||||
function adv_buf.seek(self, whence, offset)
|
||||
function buffer:seek(whence, offset)
|
||||
whence = tostring(whence or "cur")
|
||||
assert(whence == "set" or whence == "cur" or whence == "end",
|
||||
"bad argument #1 (set, cur or end expected, got " .. whence .. ")")
|
||||
@ -159,4 +31,192 @@ function adv_buf.seek(self, whence, offset)
|
||||
end
|
||||
end
|
||||
|
||||
return adv_buf
|
||||
function buffer:buffered_write(arg)
|
||||
local result, reason
|
||||
if self.bufferMode == "full" then
|
||||
if self.bufferSize - #self.bufferWrite < #arg then
|
||||
result, reason = self:flush()
|
||||
if not result then
|
||||
return nil, reason
|
||||
end
|
||||
end
|
||||
if #arg > self.bufferSize then
|
||||
result, reason = self.stream:write(arg)
|
||||
else
|
||||
self.bufferWrite = self.bufferWrite .. arg
|
||||
result = self
|
||||
end
|
||||
else--if self.bufferMode == "line" then
|
||||
local l
|
||||
repeat
|
||||
local idx = arg:find("\n", (l or 0) + 1, true)
|
||||
if idx then
|
||||
l = idx
|
||||
end
|
||||
until not idx
|
||||
if l or #arg > self.bufferSize then
|
||||
result, reason = self:flush()
|
||||
if not result then
|
||||
return nil, reason
|
||||
end
|
||||
end
|
||||
if l then
|
||||
result, reason = self.stream:write(arg:sub(1, l))
|
||||
if not result then
|
||||
return nil, reason
|
||||
end
|
||||
arg = arg:sub(l + 1)
|
||||
end
|
||||
if #arg > self.bufferSize then
|
||||
result, reason = self.stream:write(arg)
|
||||
else
|
||||
self.bufferWrite = self.bufferWrite .. arg
|
||||
result = self
|
||||
end
|
||||
end
|
||||
return result, reason
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------------------------------
|
||||
|
||||
function buffer:readNumber(readChunk)
|
||||
local len, sub
|
||||
if self.mode.b then
|
||||
len = rawlen
|
||||
sub = string.sub
|
||||
else
|
||||
len = unicode.len
|
||||
sub = unicode.sub
|
||||
end
|
||||
|
||||
local number_text = ""
|
||||
local white_done
|
||||
|
||||
local function peek()
|
||||
if len(self.bufferRead) == 0 then
|
||||
local result, reason = readChunk(self)
|
||||
if not result then
|
||||
return result, reason
|
||||
end
|
||||
end
|
||||
return sub(self.bufferRead, 1, 1)
|
||||
end
|
||||
|
||||
local function pop()
|
||||
local n = sub(self.bufferRead, 1, 1)
|
||||
self.bufferRead = sub(self.bufferRead, 2)
|
||||
return n
|
||||
end
|
||||
|
||||
while true do
|
||||
local peeked = peek()
|
||||
if not peeked then
|
||||
break
|
||||
end
|
||||
|
||||
if peeked:match("[%s]") then
|
||||
if white_done then
|
||||
break
|
||||
end
|
||||
pop()
|
||||
else
|
||||
white_done = true
|
||||
if not tonumber(number_text .. peeked .. "0") then
|
||||
break
|
||||
end
|
||||
number_text = number_text .. pop() -- add pop to number_text
|
||||
end
|
||||
end
|
||||
|
||||
return tonumber(number_text)
|
||||
end
|
||||
|
||||
function buffer:readBytesOrChars(readChunk, n)
|
||||
n = math.max(n, 0)
|
||||
local len, sub
|
||||
if self.mode.b then
|
||||
len = rawlen
|
||||
sub = string.sub
|
||||
else
|
||||
len = unicode.len
|
||||
sub = unicode.sub
|
||||
end
|
||||
local data = ""
|
||||
repeat
|
||||
if len(self.bufferRead) == 0 then
|
||||
local result, reason = readChunk(self)
|
||||
if not result then
|
||||
if reason then
|
||||
return nil, reason
|
||||
else -- eof
|
||||
return #data > 0 and data or nil
|
||||
end
|
||||
end
|
||||
end
|
||||
local left = n - len(data)
|
||||
data = data .. sub(self.bufferRead, 1, left)
|
||||
self.bufferRead = sub(self.bufferRead, left + 1)
|
||||
until len(data) == n
|
||||
return data
|
||||
end
|
||||
|
||||
function buffer:readAll(readChunk)
|
||||
repeat
|
||||
local result, reason = readChunk(self)
|
||||
if not result and reason then
|
||||
return nil, reason
|
||||
end
|
||||
until not result -- eof
|
||||
local result = self.bufferRead
|
||||
self.bufferRead = ""
|
||||
return result
|
||||
end
|
||||
|
||||
function buffer:formatted_read(readChunk, ...)
|
||||
self.timeout = require("computer").uptime() + self.readTimeout
|
||||
local function read(n, format)
|
||||
if type(format) == "number" then
|
||||
return self:readBytesOrChars(readChunk, format)
|
||||
else
|
||||
local first_char_index = 1
|
||||
if type(format) ~= "string" then
|
||||
error("bad argument #" .. n .. " (invalid option)")
|
||||
elseif unicode.sub(format, 1, 1) == "*" then
|
||||
first_char_index = 2
|
||||
end
|
||||
format = unicode.sub(format, first_char_index, first_char_index)
|
||||
if format == "n" then
|
||||
return self:readNumber(readChunk)
|
||||
elseif format == "l" then
|
||||
return self:readLine(true, self.timeout)
|
||||
elseif format == "L" then
|
||||
return self:readLine(false, self.timeout)
|
||||
elseif format == "a" then
|
||||
return self:readAll(readChunk)
|
||||
else
|
||||
error("bad argument #" .. n .. " (invalid format)")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local results = {}
|
||||
local formats = table.pack(...)
|
||||
for i = 1, formats.n do
|
||||
local result, reason = read(i, formats[i])
|
||||
if result then
|
||||
results[i] = result
|
||||
elseif reason then
|
||||
return nil, reason
|
||||
end
|
||||
end
|
||||
return table.unpack(results, 1, formats.n)
|
||||
end
|
||||
|
||||
function buffer:size()
|
||||
local len = self.mode.b and rawlen or unicode.len
|
||||
local size = len(self.bufferRead)
|
||||
if self.stream.size then
|
||||
size = size + self.stream:size()
|
||||
end
|
||||
return size
|
||||
end
|
@ -121,7 +121,7 @@ function filesystem.copy(fromPath, toPath)
|
||||
local data = false
|
||||
local input, reason = filesystem.open(fromPath, "rb")
|
||||
if input then
|
||||
local output, reason = filesystem.open(toPath, "wb")
|
||||
local output = filesystem.open(toPath, "wb")
|
||||
if output then
|
||||
repeat
|
||||
data, reason = input:read(1024)
|
||||
|
@ -0,0 +1,25 @@
|
||||
local shell = require("shell")
|
||||
local process = require("process")
|
||||
|
||||
function shell.aliases()
|
||||
return pairs(process.info().data.aliases)
|
||||
end
|
||||
|
||||
function shell.execute(command, env, ...)
|
||||
local sh, reason = shell.getShell()
|
||||
if not sh then
|
||||
return false, reason
|
||||
end
|
||||
local proc = process.load(sh, nil, nil, command)
|
||||
local result = table.pack(process.internal.continue(proc, env, command, ...))
|
||||
if result.n == 0 then return true end
|
||||
return table.unpack(result, 1, result.n)
|
||||
end
|
||||
|
||||
function shell.getPath()
|
||||
return os.getenv("PATH")
|
||||
end
|
||||
|
||||
function shell.setPath(value)
|
||||
os.setenv("PATH", value)
|
||||
end
|
@ -86,3 +86,6 @@ function tty.on_tab(handler, cursor)
|
||||
end
|
||||
end
|
||||
|
||||
function tty:size()
|
||||
return #(self.window.ansi_response or "")
|
||||
end
|
||||
|
@ -89,12 +89,10 @@ for dev, path in pairs(devices) do
|
||||
end
|
||||
|
||||
local target = targets[1]
|
||||
if #targets ~= 1 then
|
||||
utils = loadfile(utils_path, "bt", _G)
|
||||
target = utils("select", "targets", options, targets)
|
||||
-- if there is only 1 target, the source selection cannot include it
|
||||
if #targets == 1 then
|
||||
devices[targets[1].dev] = nil
|
||||
end
|
||||
if not target then return end
|
||||
devices[target.dev] = nil
|
||||
|
||||
for dev, path in pairs(devices) do
|
||||
local address = dev.address
|
||||
@ -127,13 +125,29 @@ for dev, path in pairs(devices) do
|
||||
end
|
||||
end
|
||||
|
||||
-- Ask the user to select a source
|
||||
local source = sources[1]
|
||||
if #sources ~= 1 then
|
||||
utils = utils or loadfile(utils_path, "bt", _G)
|
||||
utils = loadfile(utils_path, "bt", _G)
|
||||
source = utils("select", "sources", options, sources)
|
||||
end
|
||||
if not source then return end
|
||||
|
||||
-- Remove the source from the target options
|
||||
for index,entry in ipairs(targets) do
|
||||
if entry.dev == source.dev then
|
||||
table.remove(targets, index)
|
||||
target = targets[1]
|
||||
end
|
||||
end
|
||||
|
||||
-- Ask the user to select a target
|
||||
if #targets ~= 1 then
|
||||
utils = utils or loadfile(utils_path, "bt", _G)
|
||||
target = utils("select", "targets", options, targets)
|
||||
end
|
||||
if not target then return end
|
||||
|
||||
options =
|
||||
{
|
||||
from = source.path .. '/',
|
||||
|
@ -53,6 +53,20 @@ if cmd == "select" then
|
||||
end
|
||||
os.exit(1)
|
||||
end
|
||||
local index_of_rw_source
|
||||
for index,entry in ipairs(devices) do
|
||||
if not entry.dev.isReadOnly() then
|
||||
if index_of_rw_source then
|
||||
-- this means there was another rw source, no special action required
|
||||
index_of_rw_source = nil
|
||||
break
|
||||
end
|
||||
index_of_rw_source = index
|
||||
end
|
||||
end
|
||||
if index_of_rw_source then
|
||||
table.remove(devices, index_of_rw_source)
|
||||
end
|
||||
return select_prompt(devices, "What do you want to install?")
|
||||
elseif arg == "targets" then
|
||||
if #devices == 0 then
|
||||
|
@ -1,8 +1,6 @@
|
||||
local package = require("package")
|
||||
local tty = require("tty")
|
||||
|
||||
local gpu = tty.gpu()
|
||||
|
||||
local function optrequire(...)
|
||||
local success, module = pcall(require, ...)
|
||||
if success then
|
||||
@ -32,7 +30,7 @@ env = setmetatable({}, {
|
||||
end
|
||||
end,
|
||||
})
|
||||
env._PROMPT = tostring(env._PROMPT or "lua> ")
|
||||
env._PROMPT = tostring(env._PROMPT or "\27[32mlua> \27[37m")
|
||||
|
||||
local function findTable(t, path)
|
||||
if type(t) ~= "table" then return nil end
|
||||
@ -70,8 +68,7 @@ local function findKeys(t, r, prefix, name)
|
||||
end
|
||||
end
|
||||
|
||||
local read_handler = {}
|
||||
function read_handler.hint(line, index)
|
||||
tty.setReadHandler({hint = function(line, index)
|
||||
line = (line or "")
|
||||
local tail = line:sub(index)
|
||||
line = line:sub(1, index - 1)
|
||||
@ -88,21 +85,16 @@ function read_handler.hint(line, index)
|
||||
table.insert(hints, key .. tail)
|
||||
end
|
||||
return hints
|
||||
end
|
||||
end})
|
||||
|
||||
gpu.setForeground(0xFFFFFF)
|
||||
io.write(_VERSION .. " Copyright (C) 1994-2017 Lua.org, PUC-Rio\n")
|
||||
gpu.setForeground(0xFFFF00)
|
||||
io.write("Enter a statement and hit enter to evaluate it.\n")
|
||||
io.write("\27[37m".._VERSION .. " Copyright (C) 1994-2017 Lua.org, PUC-Rio\n")
|
||||
io.write("\27[33mEnter a statement and hit enter to evaluate it.\n")
|
||||
io.write("Prefix an expression with '=' to show its value.\n")
|
||||
io.write("Press Ctrl+D to exit the interpreter.\n")
|
||||
gpu.setForeground(0xFFFFFF)
|
||||
io.write("Press Ctrl+D to exit the interpreter.\n\27[37m")
|
||||
|
||||
while tty.isAvailable() do
|
||||
local foreground = gpu.setForeground(0x00FF00)
|
||||
io.write(env._PROMPT)
|
||||
gpu.setForeground(foreground)
|
||||
local command = tty.read(read_handler)
|
||||
local command = io.read()
|
||||
if not command then -- eof
|
||||
return
|
||||
end
|
||||
|
@ -7,6 +7,7 @@ keyboard.keys = {
|
||||
c = 0x2E,
|
||||
d = 0x20,
|
||||
q = 0x10,
|
||||
w = 0x11,
|
||||
back = 0x0E, -- backspace
|
||||
delete = 0xD3,
|
||||
down = 0xD0,
|
||||
|
@ -55,10 +55,6 @@ function shell.setAlias(alias, value)
|
||||
process.info().data.aliases[alias] = value
|
||||
end
|
||||
|
||||
function shell.aliases()
|
||||
return pairs(process.info().data.aliases)
|
||||
end
|
||||
|
||||
function shell.getWorkingDirectory()
|
||||
-- if no env PWD default to /
|
||||
return os.getenv("PWD") or "/"
|
||||
@ -77,14 +73,6 @@ function shell.setWorkingDirectory(dir)
|
||||
end
|
||||
end
|
||||
|
||||
function shell.getPath()
|
||||
return os.getenv("PATH")
|
||||
end
|
||||
|
||||
function shell.setPath(value)
|
||||
os.setenv("PATH", value)
|
||||
end
|
||||
|
||||
function shell.resolve(path, ext)
|
||||
checkArg(1, path, "string")
|
||||
|
||||
@ -102,7 +90,7 @@ function shell.resolve(path, ext)
|
||||
checkArg(2, ext, "string")
|
||||
-- search for name in PATH if no dir was given
|
||||
-- no dir was given if path has no /
|
||||
local search_in = path:find("/") and dir or shell.getPath()
|
||||
local search_in = path:find("/") and dir or os.getenv("PATH")
|
||||
for search_path in string.gmatch(search_in, "[^:]+") do
|
||||
-- resolve search_path because they may be relative
|
||||
local search_name = fs.concat(shell.resolve(search_path), name)
|
||||
@ -119,17 +107,6 @@ function shell.resolve(path, ext)
|
||||
return nil, "file not found"
|
||||
end
|
||||
|
||||
function shell.execute(command, env, ...)
|
||||
local sh, reason = shell.getShell()
|
||||
if not sh then
|
||||
return false, reason
|
||||
end
|
||||
local proc = process.load(sh, nil, nil, command)
|
||||
local result = table.pack(process.internal.continue(proc, env, command, ...))
|
||||
if result.n == 0 then return true end
|
||||
return table.unpack(result, 1, result.n)
|
||||
end
|
||||
|
||||
function shell.parse(...)
|
||||
local params = table.pack(...)
|
||||
local args = {}
|
||||
@ -162,4 +139,6 @@ end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
require("package").delay(shell, "/lib/core/full_shell.lua")
|
||||
|
||||
return shell
|
||||
|
@ -24,7 +24,7 @@ local function as_window(window, func, ...)
|
||||
data.window = window
|
||||
local ret = table.pack(func(...))
|
||||
data.window = prev
|
||||
return table.unpack(ret, ret.n)
|
||||
return table.unpack(ret, 1, ret.n)
|
||||
end
|
||||
|
||||
function term.internal.open(...)
|
||||
@ -55,7 +55,7 @@ function term.internal.open(...)
|
||||
tty.window = nil
|
||||
setmetatable(tty,
|
||||
{
|
||||
__index = function(tbl, key)
|
||||
__index = function(_, key)
|
||||
if key == "window" then
|
||||
return term.internal.window()
|
||||
end
|
||||
@ -68,32 +68,34 @@ function term.internal.open(...)
|
||||
end
|
||||
|
||||
local function build_horizontal_reader(cursor)
|
||||
cursor.clear_tail = function(_)
|
||||
local w,h,dx,dy,x,y = tty.getViewport()
|
||||
local s1,s2=tty.internal.split(_)
|
||||
cursor.clear_tail = function(self)
|
||||
local w,_,dx,dy,x,y = tty.getViewport()
|
||||
local _,s2=tty.internal.split(self)
|
||||
local wlen = math.min(unicode.wlen(s2),w-x+1)
|
||||
tty.gpu().fill(x+dx,y+dy,wlen,1," ")
|
||||
end
|
||||
cursor.move = function(_,n)
|
||||
cursor.move = function(self, n)
|
||||
local win = tty.window
|
||||
local a = _.index
|
||||
local b = math.max(0,math.min(unicode.len(_.data),_.index+n))
|
||||
_.index = b
|
||||
local a = self.index
|
||||
local b = math.max(0,math.min(unicode.len(self.data), self.index+n))
|
||||
self.index = b
|
||||
a, b = a < b and a or b, a < b and b or a
|
||||
local wlen_moved = unicode.wlen(unicode.sub(_.data,a+1,b))
|
||||
local wlen_moved = unicode.wlen(unicode.sub(self.data, a + 1, b))
|
||||
win.x = win.x + wlen_moved * (n<0 and -1 or 1)
|
||||
_:scroll()
|
||||
self:scroll()
|
||||
end
|
||||
cursor.draw = function(_, text)
|
||||
tty.drawText(text, true)
|
||||
local nowrap = tty.window.nowrap
|
||||
tty.window.nowrap = true
|
||||
tty:write(text)
|
||||
tty.window.nowrap = nowrap
|
||||
end
|
||||
cursor.scroll = function(_)
|
||||
cursor.scroll = function(self)
|
||||
local win = tty.window
|
||||
local gpu,data,px,i = win.gpu,_.data,_.promptx,_.index
|
||||
local w,h,dx,dy,x,y = tty.getViewport()
|
||||
win.x = math.max(_.promptx, math.min(w, x))
|
||||
local len = unicode.len(data)
|
||||
local available,sx,sy,last = w-px+1,px+dx,y+dy,i==len
|
||||
local gpu,data,px,i = win.gpu, self.data, self.promptx, self.index
|
||||
local w,_,dx,dy,x,y = tty.getViewport()
|
||||
win.x = math.max(self.promptx, math.min(w, x))
|
||||
local available,sx,sy = w-px+1,px+dx,y+dy
|
||||
if x > w then
|
||||
local blank
|
||||
if i == unicode.len(data) then
|
||||
@ -106,20 +108,20 @@ local function build_horizontal_reader(cursor)
|
||||
local ending = unicode.wtrunc(rev, available+1)
|
||||
data = unicode.reverse(ending)
|
||||
gpu.set(sx,sy,data..blank)
|
||||
win.x=math.min(w,_.promptx+unicode.wlen(data))
|
||||
elseif x < _.promptx then
|
||||
data = unicode.sub(data,_.index+1)
|
||||
win.x=math.min(w,self.promptx+unicode.wlen(data))
|
||||
elseif x < self.promptx then
|
||||
data = unicode.sub(data, self.index+1)
|
||||
if unicode.wlen(data) > available then
|
||||
data = unicode.wtrunc(data,available+1)
|
||||
end
|
||||
gpu.set(sx,sy,data)
|
||||
end
|
||||
end
|
||||
cursor.clear = function(_)
|
||||
cursor.clear = function(self)
|
||||
local win = tty.window
|
||||
local gpu,data,px=win.gpu,_.data,_.promptx
|
||||
local w,h,dx,dy,x,y = tty.getViewport()
|
||||
_.index,_.data,win.x=0,"",px
|
||||
local gpu, px = win.gpu, self.promptx
|
||||
local w,_,dx,dy,_,y = tty.getViewport()
|
||||
self.index, self.data, win.x = 0, "", px
|
||||
gpu.fill(px+dx,y+dy,w-px+1-dx,1," ")
|
||||
end
|
||||
end
|
||||
@ -138,14 +140,14 @@ local function inject_filter(handler, filter)
|
||||
__newindex = function(tbl, key, value)
|
||||
if key == "key_down" then
|
||||
local tty_key_down = value
|
||||
value = function(handler, cursor, char, code)
|
||||
value = function(_handler, cursor, char, code)
|
||||
if code == keys.enter or code == keys.numpadenter then
|
||||
if not filter(cursor.data) then
|
||||
computer.beep(2000, 0.1)
|
||||
return false -- ignore
|
||||
end
|
||||
end
|
||||
return tty_key_down(handler, cursor, char, code)
|
||||
return tty_key_down(_handler, cursor, char, code)
|
||||
end
|
||||
end
|
||||
rawset(tbl, key, value)
|
||||
@ -170,7 +172,7 @@ local function inject_mask(cursor, dobreak, pwchar)
|
||||
end
|
||||
|
||||
local cursor_draw = cursor.draw
|
||||
cursor.draw = function(cursor, text)
|
||||
cursor.draw = function(self, text)
|
||||
local pre, newline = text:match("(.-)(\n?)$")
|
||||
if dobreak == false then
|
||||
newline = ""
|
||||
@ -178,19 +180,17 @@ local function inject_mask(cursor, dobreak, pwchar)
|
||||
if pwchar then
|
||||
pre = pwchar(pre)
|
||||
end
|
||||
return cursor_draw(cursor, pre .. newline)
|
||||
return cursor_draw(self, pre .. newline)
|
||||
end
|
||||
end
|
||||
|
||||
-- cannot use term.write = io.write because io.write invokes metatable
|
||||
function term.write(value, wrap)
|
||||
local stdout = io.output()
|
||||
local stream = stdout and stdout.stream
|
||||
local previous_nowrap = stream.nowrap
|
||||
stream.nowrap = wrap == false
|
||||
stdout:write(value)
|
||||
stdout:flush()
|
||||
stream.nowrap = previous_nowrap
|
||||
local previous_nowrap = tty.window.nowrap
|
||||
tty.window.nowrap = wrap == false
|
||||
io.write(value)
|
||||
io.stdout:flush()
|
||||
tty.window.nowrap = previous_nowrap
|
||||
end
|
||||
|
||||
function term.read(history, dobreak, hint, pwchar, filter)
|
||||
@ -209,7 +209,7 @@ function term.read(history, dobreak, hint, pwchar, filter)
|
||||
inject_filter(handler, filter)
|
||||
inject_mask(cursor, dobreak, pwchar or history.pwchar)
|
||||
|
||||
return tty.read(handler, cursor)
|
||||
return tty:read(handler, cursor)
|
||||
end
|
||||
|
||||
function term.getGlobalArea(window)
|
||||
@ -219,7 +219,7 @@ end
|
||||
|
||||
function term.clearLine(window)
|
||||
window = window or tty.window
|
||||
local w,h,dx,dy,x,y = as_window(window, tty.getViewport)
|
||||
local w, h, dx, dy, _, y = as_window(window, tty.getViewport)
|
||||
window.gpu.fill(dx + 1, dy + math.max(1, math.min(y, h)), w, 1, " ")
|
||||
window.x = 1
|
||||
end
|
||||
|
@ -1,50 +0,0 @@
|
||||
local unicode = require("unicode")
|
||||
local adv_buf = {}
|
||||
|
||||
function adv_buf.write(self, arg)
|
||||
local result, reason
|
||||
if self.bufferMode == "full" then
|
||||
if self.bufferSize - #self.bufferWrite < #arg then
|
||||
result, reason = self:flush()
|
||||
if not result then
|
||||
return nil, reason
|
||||
end
|
||||
end
|
||||
if #arg > self.bufferSize then
|
||||
result, reason = self.stream:write(arg)
|
||||
else
|
||||
self.bufferWrite = self.bufferWrite .. arg
|
||||
result = self
|
||||
end
|
||||
else--if self.bufferMode == "line" then
|
||||
local l
|
||||
repeat
|
||||
local idx = arg:find("\n", (l or 0) + 1, true)
|
||||
if idx then
|
||||
l = idx
|
||||
end
|
||||
until not idx
|
||||
if l or #arg > self.bufferSize then
|
||||
result, reason = self:flush()
|
||||
if not result then
|
||||
return nil, reason
|
||||
end
|
||||
end
|
||||
if l then
|
||||
result, reason = self.stream:write(arg:sub(1, l))
|
||||
if not result then
|
||||
return nil, reason
|
||||
end
|
||||
arg = arg:sub(l + 1)
|
||||
end
|
||||
if #arg > self.bufferSize then
|
||||
result, reason = self.stream:write(arg)
|
||||
else
|
||||
self.bufferWrite = self.bufferWrite .. arg
|
||||
result = self
|
||||
end
|
||||
end
|
||||
return result, reason
|
||||
end
|
||||
|
||||
return adv_buf
|
@ -3,7 +3,7 @@ local filesystem = require("filesystem")
|
||||
local lib = {}
|
||||
function lib.remove(path, findNode)
|
||||
local function removeVirtual()
|
||||
local node, rest, vnode, vrest = findNode(filesystem.path(path), false, true)
|
||||
local _, _, vnode, vrest = findNode(filesystem.path(path), false, true)
|
||||
-- vrest represents the remaining path beyond vnode
|
||||
-- vrest is nil if vnode reaches the full path
|
||||
-- thus, if vrest is NOT NIL, then we SHOULD NOT remove children nor links
|
||||
@ -39,7 +39,7 @@ end
|
||||
|
||||
function lib.rename(oldPath, newPath, findNode)
|
||||
if filesystem.isLink(oldPath) then
|
||||
local node, rest, vnode, vrest = findNode(filesystem.path(oldPath))
|
||||
local _, _, vnode, _ = findNode(filesystem.path(oldPath))
|
||||
local target = vnode.links[filesystem.name(oldPath)]
|
||||
local result, reason = filesystem.link(target, newPath)
|
||||
if result then
|
||||
|
@ -3,6 +3,7 @@ local event = require("event")
|
||||
local kb = require("keyboard")
|
||||
local component = require("component")
|
||||
local computer = require("computer")
|
||||
local process = require("process")
|
||||
local keys = kb.keys
|
||||
|
||||
local tty = {}
|
||||
@ -18,31 +19,13 @@ tty.window =
|
||||
|
||||
tty.internal = {}
|
||||
|
||||
local function ctrl_movement(cursor, dir)
|
||||
local index, data = cursor.index, cursor.data
|
||||
|
||||
local last=dir<0 and 0 or unicode.len(data)
|
||||
local start=index+dir+1
|
||||
for i=start,last,dir do
|
||||
local a,b = unicode.sub(data, i-1, i-1), unicode.sub(data, i, i)
|
||||
a = a == "" or a:find("%s")
|
||||
b = b == "" or b:find("%s")
|
||||
if a and not b then return i - (index + 1) end
|
||||
end
|
||||
return last - index
|
||||
end
|
||||
|
||||
local function read_history(handler, cursor, change)
|
||||
local ni = handler.index + change
|
||||
if ni >= 0 and ni <= #handler then
|
||||
handler[handler.index] = cursor.data
|
||||
handler.index = ni
|
||||
cursor:clear()
|
||||
cursor:update(handler[ni])
|
||||
end
|
||||
function tty.setReadHandler(handler)
|
||||
checkArg(1, handler, "table")
|
||||
process.info().data.handler = handler
|
||||
end
|
||||
|
||||
function tty.key_down_handler(handler, cursor, char, code)
|
||||
local data = cursor.data
|
||||
local c = false
|
||||
local backup_cache = handler.cache
|
||||
handler.cache = nil
|
||||
@ -55,21 +38,31 @@ function tty.key_down_handler(handler, cursor, char, code)
|
||||
elseif code == keys.enter or code == keys.numpadenter then
|
||||
cursor:move(math.huge)
|
||||
cursor:draw("\n")
|
||||
if #cursor.data > 0 then
|
||||
table.insert(handler, 1, cursor.data)
|
||||
if #data > 0 then
|
||||
table.insert(handler, 1, data)
|
||||
handler[(tonumber(os.getenv("HISTSIZE")) or 10)+1]=nil
|
||||
handler[0]=nil
|
||||
end
|
||||
return nil, cursor.data .. "\n"
|
||||
elseif code == keys.up then read_history(handler, cursor, 1)
|
||||
elseif code == keys.down then read_history(handler, cursor, -1)
|
||||
elseif code == keys.left then cursor:move(ctrl and ctrl_movement(cursor, -1) or -1)
|
||||
elseif code == keys.right then cursor:move(ctrl and ctrl_movement(cursor, 1) or 1)
|
||||
return nil, data .. "\n"
|
||||
elseif code == keys.up or code == keys.down then
|
||||
local ni = handler.index + (code == keys.up and 1 or -1)
|
||||
if ni >= 0 and ni <= #handler then
|
||||
handler[handler.index] = data
|
||||
handler.index = ni
|
||||
cursor:clear()
|
||||
cursor:update(handler[ni])
|
||||
end
|
||||
elseif code == keys.left or code == keys.back or code == keys.w and ctrl then
|
||||
local value = ctrl and ((unicode.sub(data, 1, cursor.index):find("%s[^%s]+%s*$") or 0) - cursor.index) or -1
|
||||
if code == keys.left then
|
||||
cursor:move(value)
|
||||
else
|
||||
c = value
|
||||
end
|
||||
elseif code == keys.right then cursor:move(ctrl and ((data:find("%s[^%s]", cursor.index + 1) or math.huge) - cursor.index) or 1)
|
||||
elseif code == keys.home then cursor:move(-math.huge)
|
||||
elseif code == keys["end"] then cursor:move( math.huge)
|
||||
elseif code == keys.back then c = -1
|
||||
elseif code == keys.delete then c = 1
|
||||
--elseif ctrl and char == "w"then -- TODO: cut word
|
||||
elseif char >= 32 then c = unicode.char(char)
|
||||
else handler.cache = backup_cache -- ignored chars shouldn't clear hint cache
|
||||
end
|
||||
@ -118,19 +111,21 @@ function tty.pull(cursor, timeout, ...)
|
||||
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 out = (x<1 or x>width or y<1 or y>height)
|
||||
|
||||
if cursor and out then
|
||||
if gpu then
|
||||
if x < 1 or x > width or y < 1 or y > height then
|
||||
if cursor then
|
||||
cursor:move(0)
|
||||
cursor:scroll()
|
||||
out = false
|
||||
else
|
||||
gpu = nil
|
||||
end
|
||||
end
|
||||
|
||||
x, y = tty.getCursor()
|
||||
x, y = x + dx, y + dy
|
||||
local gpu = not out and tty.gpu()
|
||||
end
|
||||
|
||||
local bgColor, bgIsPalette
|
||||
local fgColor, fgIsPalette
|
||||
@ -169,7 +164,14 @@ function tty.pull(cursor, timeout, ...)
|
||||
return table.unpack(signal, 1, signal.n)
|
||||
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
|
||||
@ -260,17 +262,21 @@ function tty.internal.build_vertical_reader()
|
||||
self.data = ""
|
||||
end,
|
||||
draw = function(self, text)
|
||||
self.sy = self.sy + tty.drawText(text)
|
||||
self.sy = self.sy + tty:write(text)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
function tty.read(handler, cursor)
|
||||
if not io.stdin.tty then return io.read() end
|
||||
|
||||
checkArg(1, handler, "table")
|
||||
-- read n bytes, n is unused
|
||||
function tty.read(_, handler, cursor)
|
||||
checkArg(1, handler, "table", "number")
|
||||
checkArg(2, cursor, "table", "nil")
|
||||
|
||||
if type(handler) == "number" then
|
||||
-- standard read as a stream, asking for n bytes
|
||||
handler = process.info().data.handler or {}
|
||||
end
|
||||
|
||||
handler.index = 0
|
||||
cursor = cursor or tty.internal.build_vertical_reader()
|
||||
|
||||
@ -286,10 +292,12 @@ function tty.read(handler, cursor)
|
||||
local main_kb = tty.keyboard()
|
||||
local main_sc = tty.screen()
|
||||
if name == "interrupted" then
|
||||
tty.drawText("^C\n")
|
||||
tty:write("^C\n")
|
||||
return false
|
||||
elseif address == main_kb or address == main_sc then
|
||||
local handler_method = handler[name] or tty[name .. "_handler"]
|
||||
local handler_method = handler[name] or
|
||||
-- this handler listing hack is to delay load tty
|
||||
({key_down=1, touch=1, drag=1, clipboard=1})[name] and tty[name .. "_handler"]
|
||||
if handler_method then
|
||||
-- nil to end (close)
|
||||
-- false to ignore
|
||||
@ -316,67 +324,97 @@ function tty.setCursor(x, y)
|
||||
window.x, window.y = x, y
|
||||
end
|
||||
|
||||
function tty.drawText(value, nowrap)
|
||||
function tty.write(_, value)
|
||||
local gpu = tty.gpu()
|
||||
if not gpu then
|
||||
return
|
||||
end
|
||||
local window = tty.window
|
||||
local sy = 0
|
||||
local cr_last, beeped
|
||||
local beeped
|
||||
local uptime = computer.uptime
|
||||
local last_sleep = uptime()
|
||||
local last_index = 1
|
||||
local width, _, dx, dy = tty.getViewport()
|
||||
while true do
|
||||
if uptime() - last_sleep > 1 then
|
||||
os.sleep(0)
|
||||
last_sleep = uptime()
|
||||
end
|
||||
|
||||
local ansi_print = ""
|
||||
if window.ansi_escape then
|
||||
-- parse the instruction in segment
|
||||
-- [ (%d+;)+ %d+m
|
||||
window.ansi_escape = window.ansi_escape .. value
|
||||
local color_attributes = {tonumber(window.ansi_escape:match("^%[(%d%d)m"))}
|
||||
if not color_attributes[1] then
|
||||
color_attributes, ansi_print, value = require("vt100").parse(window)
|
||||
else
|
||||
value = window.ansi_escape:sub(5)
|
||||
end
|
||||
for _,catt in ipairs(color_attributes) do
|
||||
local colors = {0x0,0xff0000,0x00ff00,0xffff00,0x0000ff,0xff00ff,0x00ffff,0xffffff}
|
||||
catt = catt - 29
|
||||
local method = "setForeground"
|
||||
if catt > 10 then
|
||||
method = "setBackground"
|
||||
catt = catt - 10
|
||||
end
|
||||
local c = colors[catt]
|
||||
if c then
|
||||
gpu[method](c)
|
||||
end
|
||||
window.ansi_escape = nil -- might happen multiple times, that's fine
|
||||
end
|
||||
end
|
||||
|
||||
-- scroll before parsing next line
|
||||
-- the value may only have been a newline
|
||||
sy = sy + tty.scroll()
|
||||
local x, y = tty.getCursor()
|
||||
|
||||
local si, ei, segment, delim = value:find("([^\t\r\n\a]*)([\t\r\n\a]?)", last_index)
|
||||
if si > ei then
|
||||
-- we may have needed to scroll one last time [nowrap adjustments]
|
||||
if #value == 0 then
|
||||
break
|
||||
end
|
||||
last_index = ei + 1
|
||||
|
||||
local x, y = tty.getCursor()
|
||||
|
||||
local _, ei, delim = unicode.sub(value, 1, window.width):find("([\27\t\r\n\a])", #ansi_print + 1)
|
||||
local segment = ansi_print .. (ei and value:sub(1, ei - 1) or value)
|
||||
|
||||
if segment ~= "" then
|
||||
local gpu_x, gpu_y = x + dx, y + dy
|
||||
local gpu_x, gpu_y = x + window.dx, y + window.dy
|
||||
local tail = ""
|
||||
local wlen_needed = unicode.wlen(segment)
|
||||
local wlen_remaining = width - x + 1
|
||||
if wlen_remaining < wlen_needed then
|
||||
local wlen_remaining = window.width - x + 1
|
||||
if not window.nowrap and wlen_remaining < wlen_needed then
|
||||
segment = unicode.wtrunc(segment, wlen_remaining + 1)
|
||||
wlen_needed = unicode.wlen(segment)
|
||||
-- we can clear the line because we already know remaining < needed
|
||||
tail = (" "):rep(wlen_remaining - wlen_needed)
|
||||
-- we have to reparse the delimeter
|
||||
last_index = si + #segment
|
||||
ei = #segment
|
||||
-- fake a newline
|
||||
if not nowrap then
|
||||
delim = "\n"
|
||||
end
|
||||
end
|
||||
gpu.set(gpu_x, gpu_y, segment..tail)
|
||||
x = x + wlen_needed
|
||||
end
|
||||
|
||||
value = ei and value:sub(ei + 1) or ""
|
||||
|
||||
if delim == "\t" then
|
||||
x = ((x-1) - ((x-1) % 8)) + 9
|
||||
elseif delim == "\r" or (delim == "\n" and not cr_last) then
|
||||
elseif delim == "\r" or (delim == "\n" and not window.cr_last) then
|
||||
x = 1
|
||||
y = y + 1
|
||||
elseif delim == "\a" and not beeped then
|
||||
computer.beep()
|
||||
beeped = true
|
||||
elseif delim == "\27" then -- ansi escape
|
||||
window.ansi_escape = ""
|
||||
end
|
||||
|
||||
tty.setCursor(x, y)
|
||||
cr_last = delim == "\r"
|
||||
window.cr_last = delim == "\r"
|
||||
end
|
||||
return sy
|
||||
end
|
||||
@ -482,6 +520,12 @@ function tty.scroll(number)
|
||||
return lines
|
||||
end
|
||||
|
||||
-- stream methods
|
||||
local function bfd() return nil, "tty: invalid operation" end
|
||||
tty.close = bfd
|
||||
tty.seek = bfd
|
||||
tty.handle = "tty"
|
||||
|
||||
require("package").delay(tty, "/lib/core/full_tty.lua")
|
||||
|
||||
return tty
|
||||
|
@ -0,0 +1,166 @@
|
||||
local vt100 = {}
|
||||
|
||||
-- runs patterns on ansi until failure
|
||||
-- returns valid:boolean, completed_index:nil|number
|
||||
function vt100.validate(ansi, patterns)
|
||||
local last_index = 0
|
||||
local captures = {}
|
||||
for _,pattern in ipairs(patterns) do
|
||||
if last_index >= #ansi then
|
||||
return true
|
||||
end
|
||||
local si, ei, capture = ansi:find("^(" .. pattern .. ")", last_index + 1)
|
||||
if not si then -- failed to match
|
||||
return
|
||||
end
|
||||
captures[#captures + 1] = capture
|
||||
last_index = ei
|
||||
end
|
||||
return true, last_index, captures
|
||||
end
|
||||
|
||||
local rules = {}
|
||||
|
||||
-- colors
|
||||
-- [%d+;%d+;..%d+m
|
||||
rules[{"%[", "[%d;]*", "m"}] = function(_, _, number_text)
|
||||
local numbers = {}
|
||||
number_text:gsub("[^;]*", function(num)
|
||||
local n = tonumber(num) or 0
|
||||
if n == 0 then
|
||||
numbers[#numbers + 1] = 40
|
||||
numbers[#numbers + 1] = 37
|
||||
else
|
||||
numbers[#numbers + 1] = n
|
||||
end
|
||||
end)
|
||||
return numbers
|
||||
end
|
||||
|
||||
-- [?7[hl] wrap mode
|
||||
rules[{"%[", "%?", "7", "[hl]"}] = function(window, _, _, _, nowrap)
|
||||
window.nowrap = nowrap == "l"
|
||||
end
|
||||
|
||||
-- helper scroll function
|
||||
local function set_cursor(window, x, y)
|
||||
window.x = math.min(math.max(x, 1), window.width)
|
||||
window.y = math.min(math.max(y, 1), window.height)
|
||||
end
|
||||
|
||||
-- -- These DO NOT SCROLL
|
||||
-- [(%d+)A move cursor up n lines
|
||||
-- [(%d+)B move cursor down n lines
|
||||
-- [(%d+)C move cursor right n lines
|
||||
-- [(%d+)D move cursor left n lines
|
||||
rules[{"%[", "%d+", "[ABCD]"}] = function(window, _, n, dir)
|
||||
local dx, dy = 0, 0
|
||||
n = tonumber(n)
|
||||
if dir == "A" then
|
||||
dy = -n
|
||||
elseif dir == "B" then
|
||||
dy = n
|
||||
elseif dir == "C" then
|
||||
dx = n
|
||||
else -- D
|
||||
dx = -n
|
||||
end
|
||||
set_cursor(window, window.x + dx, window.y + dy)
|
||||
end
|
||||
|
||||
-- [Line;ColumnH Move cursor to screen location v,h
|
||||
-- [Line;Columnf ^ same
|
||||
rules[{"%[", "%d+", ";", "%d+", "[Hf]"}] = function(window, _, y, _, x)
|
||||
set_cursor(window, tonumber(x), tonumber(y))
|
||||
end
|
||||
|
||||
-- [K clear line from cursor right
|
||||
-- [0K ^ same
|
||||
-- [1K clear line from cursor left
|
||||
-- [2K clear entire line
|
||||
local function clear_line(window, _, n)
|
||||
n = tonumber(n) or 0
|
||||
local x = n == 0 and window.x or 1
|
||||
local rep = n == 1 and window.x or window.width
|
||||
window.gpu.set(x, window.y, (" "):rep(rep))
|
||||
end
|
||||
rules[{"%[", "[012]?", "K"}] = clear_line
|
||||
|
||||
-- [J clear screen from cursor down
|
||||
-- [0J ^ same
|
||||
-- [1J clear screen from cursor up
|
||||
-- [2J clear entire screen
|
||||
rules[{"%[", "[012]?", "J"}] = function(window, _, n)
|
||||
clear_line(window, _, n)
|
||||
n = tonumber(n) or 0
|
||||
local y = n == 0 and (window.y + 1) or 1
|
||||
local rep = n == 1 and (window.y - 1) or window.height
|
||||
window.gpu.fill(1, y, window.width, rep, " ")
|
||||
end
|
||||
|
||||
-- [H move cursor to upper left corner
|
||||
-- [;H ^ same
|
||||
-- [f ^ same
|
||||
-- [;f ^ same
|
||||
rules[{"%[;?", "[Hf]"}] = function(window)
|
||||
set_cursor(window, 1, 1)
|
||||
end
|
||||
|
||||
-- [6n get the cursor position [ EscLine;ColumnR Response: cursor is at v,h ]
|
||||
rules[{"%[", "6", "n"}] = function(window)
|
||||
window.ansi_response = string.format("%s%d;%dR", string.char(0x1b), window.y, window.x)
|
||||
end
|
||||
|
||||
-- D scroll up one line -- moves cursor down
|
||||
-- E move to next line (acts the same ^, but x=1)
|
||||
-- M scroll down one line -- moves cursor up
|
||||
rules[{"[DEM]"}] = function(window, dir)
|
||||
if dir == "D" then
|
||||
window.y = window.y + 1
|
||||
elseif dir == "E" then
|
||||
window.y = window.y + 1
|
||||
window.x = 1
|
||||
else -- M
|
||||
window.y = window.y - 1
|
||||
end
|
||||
end
|
||||
|
||||
-- 7 save cursor position and attributes
|
||||
-- 8 restore cursor position and attributes
|
||||
rules[{"[78]"}] = function(window, restore)
|
||||
if restore == "8" then
|
||||
local data = window.saved or {1, 1, {0x0}, {0xffffff}}
|
||||
window.x = data[1]
|
||||
window.y = data[2]
|
||||
window.gpu.setBackground(table.unpack(data[3]))
|
||||
window.gpu.setForeground(table.unpack(data[4]))
|
||||
else
|
||||
window.saved = {window.x, window.y, {window.gpu.getBackground()}, {window.gpu.getForeground()}}
|
||||
end
|
||||
end
|
||||
|
||||
function vt100.parse(window)
|
||||
local ansi = window.ansi_escape
|
||||
window.ansi_escape = nil
|
||||
local any_valid
|
||||
|
||||
for rule,action in pairs(rules) do
|
||||
local ok, completed, captures = vt100.validate(ansi, rule)
|
||||
if completed then
|
||||
return action(window, table.unpack(captures)) or {}, "", ansi:sub(completed + 1)
|
||||
elseif ok then
|
||||
any_valid = true
|
||||
end
|
||||
end
|
||||
|
||||
if not any_valid then
|
||||
-- malformed
|
||||
return {}, string.char(0x1b), ansi
|
||||
end
|
||||
|
||||
-- else, still consuming
|
||||
window.ansi_escape = ansi
|
||||
return {}, "", ""
|
||||
end
|
||||
|
||||
return vt100
|
Loading…
x
Reference in New Issue
Block a user