Merge branch 'master-MC1.8.9' into master-MC1.9.4

This commit is contained in:
payonel 2017-07-04 19:16:13 -07:00
commit ac1360f038
21 changed files with 630 additions and 429 deletions

View File

@ -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()

View File

@ -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

View File

@ -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.

View File

@ -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"
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)
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
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)

View File

@ -2,7 +2,10 @@ local shell = require("shell")
local tty = require("tty")
local fs = require("filesystem")
tty.clear()
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"))

View File

@ -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

View File

@ -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

View File

@ -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
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)
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)
function buffer:getTimeout()
return self.readTimeout
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
function buffer:setTimeout(value)
self.readTimeout = tonumber(value)
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

View File

@ -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)

View File

@ -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

View File

@ -86,3 +86,6 @@ function tty.on_tab(handler, cursor)
end
end
function tty:size()
return #(self.window.ansi_response or "")
end

View File

@ -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 .. '/',

View File

@ -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

View File

@ -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

View File

@ -7,6 +7,7 @@ keyboard.keys = {
c = 0x2E,
d = 0x20,
q = 0x10,
w = 0x11,
back = 0x0E, -- backspace
delete = 0xD3,
down = 0xD0,

View File

@ -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

View File

@ -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
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 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(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,9 +219,9 @@ end
function term.clearLine(window)
window = window or tty.window
local w,h,dx,dy,x,y = as_window(window, tty.getViewport)
window.gpu.fill(dx+1,dy+math.max(1,math.min(y,h)),w,1," ")
window.x=1
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
function term.pull(...)

View File

@ -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

View File

@ -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

View File

@ -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,20 +111,22 @@ 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
cursor:move(0)
cursor:scroll()
out = false
if gpu then
if x < 1 or x > width or y < 1 or y > height then
if cursor then
cursor:move(0)
cursor:scroll()
else
gpu = nil
end
end
x, y = tty.getCursor()
x, y = x + dx, y + dy
end
x, y = tty.getCursor()
x, y = x + dx, y + dy
local gpu = not out and tty.gpu()
local bgColor, bgIsPalette
local fgColor, fgIsPalette
local char_at_cursor
@ -169,7 +164,14 @@ function tty.pull(cursor, timeout, ...)
return table.unpack(signal, 1, signal.n)
end
signal = table.pack(event.pull(math.min(blink_timeout, timeout), ...))
-- 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
delim = "\n"
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

View File

@ -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