diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/echo.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/echo.lua index db036c3e6..f18ce4c5b 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/echo.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/echo.lua @@ -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() diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/list.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/list.lua index 7d504ce69..1036c745f 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/list.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/list.lua @@ -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 diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/sh.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/sh.lua index 337656e0a..9bb706eee 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/sh.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/sh.lua @@ -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. diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/03_io.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/03_io.lua index 83c43245a..1a8fa061b 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/boot/03_io.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/boot/03_io.lua @@ -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) diff --git a/src/main/resources/assets/opencomputers/loot/openos/etc/profile.lua b/src/main/resources/assets/opencomputers/loot/openos/etc/profile.lua index 400f3485e..8f8fecb2d 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/etc/profile.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/etc/profile.lua @@ -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")) diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/buffer.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/buffer.lua index 801c0e26f..4a055030e 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/buffer.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/buffer.lua @@ -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 diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua index ed45c023a..c0be1655b 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua @@ -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 diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/buffered_read.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_buffer.lua similarity index 57% rename from src/main/resources/assets/opencomputers/loot/openos/lib/tools/buffered_read.lua rename to src/main/resources/assets/opencomputers/loot/openos/lib/core/full_buffer.lua index c419d9970..360baed59 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/buffered_read.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_buffer.lua @@ -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 diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_filesystem.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_filesystem.lua index eee2ef421..99328e9eb 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_filesystem.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_filesystem.lua @@ -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) diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_shell.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_shell.lua new file mode 100644 index 000000000..2576b07ee --- /dev/null +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_shell.lua @@ -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 diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_tty.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_tty.lua index 67ab6b9e1..9347e8537 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_tty.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_tty.lua @@ -86,3 +86,6 @@ function tty.on_tab(handler, cursor) end end +function tty:size() + return #(self.window.ansi_response or "") +end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/install_basics.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/install_basics.lua index a193b7cdf..67d218fdd 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/install_basics.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/install_basics.lua @@ -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 .. '/', diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/install_utils.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/install_utils.lua index 0e376abfd..a5704a684 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/install_utils.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/install_utils.lua @@ -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 diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/lua_shell.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/lua_shell.lua index 64a2bdcf1..82f4dbe32 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/lua_shell.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/lua_shell.lua @@ -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 diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/keyboard.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/keyboard.lua index 70d86893f..a0a68907d 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/keyboard.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/keyboard.lua @@ -7,6 +7,7 @@ keyboard.keys = { c = 0x2E, d = 0x20, q = 0x10, + w = 0x11, back = 0x0E, -- backspace delete = 0xD3, down = 0xD0, diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/shell.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/shell.lua index 0a9dc2e9a..1ce19db5d 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/shell.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/shell.lua @@ -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 diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua index 826d96d03..207c681c0 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua @@ -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 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(...) diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/buffered_write.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/tools/buffered_write.lua deleted file mode 100644 index fab12e99c..000000000 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/buffered_write.lua +++ /dev/null @@ -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 diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/fsmod.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/tools/fsmod.lua index c35338069..f2905b898 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/fsmod.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/tools/fsmod.lua @@ -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 diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tty.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/tty.lua index 4462eac63..e54531f24 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/tty.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/tty.lua @@ -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 diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/vt100.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/vt100.lua new file mode 100644 index 000000000..f32488909 --- /dev/null +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/vt100.lua @@ -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