diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/clear.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/clear.lua index 613f9fcdc..2b7d0b78f 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/clear.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/clear.lua @@ -1,3 +1,2 @@ -local term = require("term") - -term.clear() \ No newline at end of file +local tty = require("tty") +tty.clear() \ No newline at end of file diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/dmesg.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/dmesg.lua index 038b3d7aa..1373bfc39 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/dmesg.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/dmesg.lua @@ -1,8 +1,8 @@ -local event = require "event" -local term = require "term" +local event = require("event") +local tty = require("tty") local args = {...} -local gpu = term.gpu() +local gpu = tty.gpu() local interactive = io.output().tty local color, isPal, evt if interactive then diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/edit.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/edit.lua index a5f9cbe3d..f9a17f7bf 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/edit.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/edit.lua @@ -2,7 +2,7 @@ local event = require("event") local fs = require("filesystem") local keyboard = require("keyboard") local shell = require("shell") -local term = require("term") +local term = require("term") -- TODO use tty and cursor position instead of global area and gpu local text = require("text") local unicode = require("unicode") diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/free.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/free.lua new file mode 100644 index 000000000..ea3f6d0d0 --- /dev/null +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/free.lua @@ -0,0 +1,8 @@ +local computer = require("computer") +local total = computer.totalMemory() +local max = 0 +for i=1,40 do + max = math.max(max, computer.freeMemory()) + os.sleep(0) -- invokes gc +end +print(string.format("Total%12d\nUsed%13d\nFree%13d", total, total - max, max)) diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/grep.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/grep.lua index 3c66033c1..e5d9d2f67 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/grep.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/grep.lua @@ -8,13 +8,13 @@ https://raw.githubusercontent.com/OpenPrograms/Wobbo-Programs/master/grep/grep.l local fs = require("filesystem") local shell = require("shell") -local term = require("term") +local tty = require("tty") -- Process the command line arguments local args, options = shell.parse(...) -local gpu = term.gpu() +local gpu = tty.gpu() local function printUsage(ostream, msg) local s = ostream or io.stdout @@ -106,7 +106,7 @@ local m_only = pop('o','only-matching') local quiet = pop('q','quiet','silent') local print_count = pop('c','count') -local colorize = pop('color','colour') and io.output().tty and term.isAvailable() +local colorize = pop('color','colour') and io.output().tty and tty.isAvailable() local noop = function(...)return ...;end local setc = colorize and gpu.setForeground or noop diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/install.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/install.lua index 1b9176d51..ce99627e7 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/install.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/install.lua @@ -4,7 +4,7 @@ local shell = require("shell") local options do - local basic, reason = loadfile(package.searchpath("tools/install_basics", package.path), "bt", _G) + local basic, reason = loadfile("/opt/core/install_basics.lua", "bt", _G) if not basic then io.stderr:write("failed to load install: " .. tostring(reason) .. "\n") return 1 diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/less.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/less.lua index c9eb8aa51..486f62b75 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/less.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/less.lua @@ -1,6 +1,6 @@ local keyboard = require("keyboard") local shell = require("shell") -local term = require("term") +local term = require("term") -- using term for negative scroll feature local text = require("text") local unicode = require("unicode") local computer = require("computer") 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 24f7f1610..7d504ce69 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/list.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/list.lua @@ -1,6 +1,5 @@ local fs = require("filesystem") local shell = require("shell") -local term = require("term") local args, ops = shell.parse(...) if #args == 0 then diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/ls.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/ls.lua index 68e2ddfd2..39e6a8561 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/ls.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/ls.lua @@ -1,8 +1,7 @@ -- load complex, if we can (might be low on memory) local ok, why = pcall(function(...) - local full_ls_path = package.searchpath("tools/full_ls", package.path) - return loadfile(full_ls_path, "bt", _G)(...) + return loadfile("/opt/core/full_ls.lua", "bt", _G)(...) end, ...) if not ok then diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/lua.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/lua.lua index e88d86553..c6eba31d4 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/lua.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/lua.lua @@ -2,7 +2,7 @@ local shell = require("shell") local args = shell.parse(...) if #args == 0 then - args = {"/lib/tools/lua_shell.lua"} + args = {"/opt/core/lua_shell.lua"} end local script, reason = loadfile(args[1], nil, setmetatable({},{__index=_ENV})) diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/more.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/more.lua index 5556e691c..af1150c1d 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/more.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/more.lua @@ -1,6 +1,6 @@ local keyboard = require("keyboard") local shell = require("shell") -local term = require("term") +local term = require("term") -- TODO use tty and cursor position instead of global area and gpu local text = require("text") local unicode = require("unicode") diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/mount.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/mount.lua index 8cfd2639d..cfc92bff9 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/mount.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/mount.lua @@ -55,7 +55,7 @@ else io.stderr:write(reason,"\n") return 1 elseif ops.r then - proxy = require("tools/ro_wrapper").wrap(proxy) + proxy = dofile("/opt/core/ro_wrapper.lua").wrap(proxy) end local result, reason = fs.mount(proxy, shell.resolve(args[2])) diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/resolution.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/resolution.lua index 7823d1d96..6c1bfce71 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/resolution.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/resolution.lua @@ -1,8 +1,8 @@ local shell = require("shell") -local term = require("term") +local tty = require("tty") local args = shell.parse(...) -local gpu = term.gpu() +local gpu = tty.gpu() if #args == 0 then local w, h = gpu.getViewport() @@ -29,4 +29,4 @@ if not result then end return 1 end -term.clear() +tty.clear() diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/set.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/set.lua index fa50073a0..6608af51c 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/set.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/set.lua @@ -1,5 +1,3 @@ -local text = require("text") - local args = {...} if #args < 1 then 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 9a7d7b2dd..aa8d94538 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/sh.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/sh.lua @@ -1,6 +1,6 @@ local event = require("event") local shell = require("shell") -local term = require("term") +local tty = require("tty") local text = require("text") local sh = require("sh") @@ -10,39 +10,28 @@ if input[2] then table.insert(args, 1, input[2]) end -local history = {} +local history = {hint = sh.hintHandler} shell.prime() if #args == 0 and (io.stdin.tty or options.i) and not options.c then -- interactive shell. -- source profile - if not term.isAvailable() then event.pull("term_available") end + if not tty.isAvailable() then event.pull("term_available") end loadfile(shell.resolve("source","lua"))("/etc/profile") while true do - if not term.isAvailable() then -- don't clear unless we lost the term - while not term.isAvailable() do + if not tty.isAvailable() then -- don't clear unless we lost the term + while not tty.isAvailable() do event.pull("term_available") end - term.clear() + tty.clear() end - local gpu = term.gpu() - while term.isAvailable() do + local gpu = tty.gpu() + while tty.isAvailable() do local foreground = gpu.setForeground(0xFF0000) - term.write(sh.expand(os.getenv("PS1") or "$ ")) + tty.write(sh.expand(os.getenv("PS1") or "$ ")) gpu.setForeground(foreground) - term.setCursorBlink(true) - local ok, command = pcall(term.read, history, nil, sh.hintHandler) - if not ok then - if command == "interrupted" then -- hard interrupt - io.write("^C\n") - break - elseif not term.isAvailable() then - break - else -- crash? - io.stderr:write("\nshell crashed: " .. tostring(command) .. "\n") - break - end - end + tty.setCursorBlink(true) + local command = tty.read(history) if not command then if command == false then break -- soft interrupt @@ -55,7 +44,7 @@ if #args == 0 and (io.stdin.tty or options.i) and not options.c then return elseif command ~= "" then local result, reason = sh.execute(_ENV, command) - if term.getCursor() > 1 then + if tty.getCursor() > 1 then print() end if not result then diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/shutdown.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/shutdown.lua index ab9ccccb9..510fbc2d3 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/shutdown.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/shutdown.lua @@ -1,5 +1,5 @@ local computer = require("computer") -local term = require("term") +local tty = require("tty") -term.clear() +tty.clear() computer.shutdown() \ No newline at end of file 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 34a58b172..bf637e580 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,5 +1,5 @@ local buffer = require("buffer") -local term = require("term") +local tty = require("tty") local io_open = io.open function io.open(path, mode) @@ -21,26 +21,24 @@ end stdoutStream.close = stdinStream.close stderrStream.close = stdinStream.close -function stdinStream:read(n, dobreak) - stdinHistory.dobreak = dobreak - local result = term.readKeyboard(stdinHistory) - return result +function stdinStream:read() + return tty.read(stdinHistory) end function stdoutStream:write(str) - term.drawText(str, self.wrap ~= false) + tty.drawText(str, self.nowrap) return self end function stderrStream:write(str) - local gpu = term.gpu() + 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 - term.drawText(str, true) + tty.drawText(str) if set_depth then gpu.setForeground(set_depth) @@ -70,30 +68,17 @@ core_stdin.close = stdinStream.close core_stdout.close = stdinStream.close core_stderr.close = stdinStream.close -local fd_map = -{ - -- key name => method name - stdin = 'input', - stdout = 'output', - stderr = 'error' -} - local io_mt = getmetatable(io) or {} io_mt.__index = function(t, k) - if fd_map[k] then - return io[fd_map[k]]() - end -end -io_mt.__newindex = function(t, k, v) - if fd_map[k] then - io[fd_map[k]](v) - else - rawset(io, k, v) - end + return + k == 'stdin' and io.input() or + k == 'stdout' and io.output() or + k == 'stderr' and io.error() or + nil end setmetatable(io, io_mt) -io.stdin = core_stdin -io.stdout = core_stdout -io.stderr = core_stderr +io.input(core_stdin) +io.output(core_stdout) +io.error(core_stderr) diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/93_term.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/93_term.lua index eca706a14..8382903d3 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/boot/93_term.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/boot/93_term.lua @@ -1,20 +1,17 @@ local component = require("component") local computer = require("computer") local event = require("event") -local term = require("term") +local tty = require("tty") local process = require("process") --- this should be the init level process -process.info().data.window = term.internal.open() - event.listen("gpu_bound", function(ename, gpu) - gpu=component.proxy(gpu) - term.bind(gpu) + gpu = component.proxy(gpu) + tty.bind(gpu) computer.pushSignal("term_available") end) local function components_changed(ename, address, type) - local window = term.internal.window() + local window = tty.window if not window then return end @@ -47,7 +44,7 @@ local function components_changed(ename, address, type) window.keyboard = nil end - if (type == "screen" or type == "gpu") and not term.isAvailable() then + if (type == "screen" or type == "gpu") and not tty.isAvailable() then computer.pushSignal("term_unavailable") end end diff --git a/src/main/resources/assets/opencomputers/loot/openos/etc/motd b/src/main/resources/assets/opencomputers/loot/openos/etc/motd index 5d011ae70..4a839de7e 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/etc/motd +++ b/src/main/resources/assets/opencomputers/loot/openos/etc/motd @@ -4,7 +4,7 @@ local component = require("component") local computer = require("computer") local text = require("text") local unicode = require("unicode") -local term = require("term") +local tty = require("tty") if not component.isAvailable("gpu") then return @@ -20,7 +20,7 @@ if f then f:close() local greeting = greetings[math.random(1, #greetings)] if greeting then - local width = math.max(10, term.getViewport()) + local width = math.max(10, tty.getViewport()) for line in text.wrappedLines(greeting, width - 4, width - 4) do table.insert(lines, line) maxWidth = math.max(maxWidth, unicode.len(line)) diff --git a/src/main/resources/assets/opencomputers/loot/openos/init.lua b/src/main/resources/assets/opencomputers/loot/openos/init.lua index 24a244613..28d4ba670 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/init.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/init.lua @@ -13,7 +13,7 @@ do invoke(addr, "close", handle) return load(buffer, "=" .. file, "bt", _G) end]], "=loadfile", "bt", _G)() - loadfile("/lib/tools/boot.lua")(loadfile) + loadfile("/opt/core/boot.lua")(loadfile) end while true do diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/devfs.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/devfs.lua index e8faa2f72..bf90ba849 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/devfs.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/devfs.lua @@ -153,7 +153,7 @@ inject_dynamic_pairs = function(fsnode, path, bStoreUse) }) end -local label_lib = dofile("/lib/tools/device_labeling.lua") +local label_lib = dofile("/opt/core/device_labeling.lua") label_lib.loadRules() api.getDeviceLabel = label_lib.getDeviceLabel api.setDeviceLabel = label_lib.setDeviceLabel @@ -163,7 +163,7 @@ function api.register(public_proxy) if registered then return end registered = true - local start_path = "/lib/tools/devfs/" + local start_path = "/opt/core/devfs/" for starter in fs.list(start_path) do local full_path = start_path .. starter local _,matched = starter:gsub("%.lua$","") diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/io.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/io.lua index 9cc268629..525f40af9 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/io.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/io.lua @@ -47,6 +47,7 @@ end function io.stream(fd,file,mode) checkArg(1,fd,'number') assert(fd>=0,'fd must be >= 0. 0 is input, 1 is stdout, 2 is stderr') + local dio = require("process").info().data.io if file then if type(file) == "string" then local result, reason = io.open(file, mode) @@ -57,9 +58,9 @@ function io.stream(fd,file,mode) elseif not io.type(file) then error("bad argument #1 (string or file expected, got " .. type(file) .. ")", 2) end - require("process").info().data.io[fd] = file + dio[fd] = file end - return require("process").info().data.io[fd] + return dio[fd] end function io.input(file) 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 19cd9aa94..b7112f1dd 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/keyboard.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/keyboard.lua @@ -45,7 +45,7 @@ setmetatable(keyboard.keys, ------------------------------------------------------------------------------- local function getKeyboardAddress(address) - return address or require("term").keyboard() + return address or require("tty").keyboard() end local function getPressedCodes(address) diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/package.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/package.lua index d753917e7..10670c257 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/package.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/package.lua @@ -19,9 +19,6 @@ package.loaded = loaded local preload = {} package.preload = preload -local delayed = {} -package.delayed = delayed - package.searchers = {} function package.searchpath(name, path, sep, rep) @@ -58,30 +55,6 @@ local function preloadSearcher(module) end end -local delay_data = {} -local delay_tools = setmetatable({},{__mode="v"}) - -function delay_data.__index(tbl,key) - local lookup = delay_data.lookup or loadfile(package.searchpath("tools/delayLookup", package.path), "bt", _G) - delay_data.lookup = lookup - return lookup(delay_data, tbl, key) -end -delay_data.__pairs = delay_data.__index -- nil key acts like pairs - -local function delaySearcher(module) - if not delayed[module] then - return "\tno field package.delayed['" .. module .. "']" - end - local filepath, reason = package.searchpath(module, package.path) - if not filepath then - return reason - end - local parser = delay_tools.parser or loadfile(package.searchpath("tools/delayParse", package.path), "bt", _G) - delay_tools.parser = parser - local loader, reason = parser(filepath,delay_data) - return loader, reason -end - local function pathSearcher(module) local filepath, reason = package.searchpath(module, package.path) if filepath then @@ -97,7 +70,6 @@ local function pathSearcher(module) end table.insert(package.searchers, preloadSearcher) -table.insert(package.searchers, delaySearcher) table.insert(package.searchers, pathSearcher) function require(module) diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/process.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/process.lua index b2f1bbba0..015a1ffa7 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/process.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/process.lua @@ -20,13 +20,6 @@ function process.findProcess(co) end ------------------------------------------------------------------------------- -local function parent_data(pre, tbl, k, ...) - if tbl and k then - return parent_data(pre, tbl[k], ...) - end - return setmetatable(pre, {__index=tbl}) -end - function process.load(path, env, init, name) checkArg(1, path, "string", "function") checkArg(2, env, "table", "nil") @@ -106,19 +99,25 @@ function process.load(path, env, init, name) end return select(2, table.unpack(result)) end, true) - process.list[thread] = { + local new_proc = + { path = path, command = name, env = env, - data = parent_data( + data = { handles = {}, - io = parent_data({}, p, "data", "io"), - coroutine_handler = parent_data({}, p, "data", "coroutine_handler"), - }, p, "data"), + io = {}, + coroutine_handler = {} + }, parent = p, instances = setmetatable({}, {__mode="v"}), } + setmetatable(new_proc.data.io, {__index=p.data.io}) + setmetatable(new_proc.data.coroutine_handler, {__index=p.data.coroutine_handler}) + setmetatable(new_proc.data, {__index=p.data}) + process.list[thread] = new_proc + return thread end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/sh.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/sh.lua index e7a441ca8..e1e785f5c 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/sh.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/sh.lua @@ -2,7 +2,6 @@ local event = require("event") local fs = require("filesystem") local process = require("process") local shell = require("shell") -local term = require("term") local text = require("text") local tx = require("transforms") local unicode = require("unicode") @@ -10,11 +9,11 @@ local unicode = require("unicode") local sh = {} sh.internal = {} --- --[[@@]] are not just comments, but custom annotations for delayload methods. --- See package.lua and the api wiki for more information -local function isWordOf(w, vs) return w and #w == 1 and not w[1].qr and tx.first(vs,{{w[1].txt}}) ~= nil end -local function isWord(w,v) return isWordOf(w,{v}) end -local local_env = {event=event,fs=fs,process=process,shell=shell,term=term,text=text,tx=tx,unicode=unicode,isWordOf=isWordOf,isWord=isWord} +function sh.internal.isWordOf(w, vs) + return w and #w == 1 and not w[1].qr and tx.first(vs,{{w[1].txt}}) ~= nil +end + +local isWordOf = sh.internal.isWordOf ------------------------------------------------------------------------------- @@ -172,25 +171,23 @@ function sh.internal.evaluate(word) elseif #word == 1 and word[1].qr then return sh.internal.expand(word) end - local function make_pattern(seg) - local result = seg - for _,glob_rule in ipairs(sh.internal.globbers) do - result = result:gsub("%%%"..glob_rule[1], glob_rule[2]) - local reduced = result - repeat - result = reduced - reduced = result:gsub(text.escapeMagic(glob_rule[2]):rep(2), glob_rule[2]) - until reduced == result - end - return result - end local glob_pattern = '' local has_globits = false for i=1,#word do local part = word[i] local next = part.txt if not part.qr then local escaped = text.escapeMagic(next) - next = make_pattern(escaped) + next = escaped + for _,glob_rule in ipairs(sh.internal.globbers) do + next = next:gsub("%%%"..glob_rule[1], glob_rule[2]) + while true do + local prev = next + next = next:gsub(text.escapeMagic(glob_rule[2]):rep(2), glob_rule[2]) + if prev == next then + break + end + end + end if next ~= escaped then has_globits = true end @@ -353,538 +350,16 @@ function sh.internal.concatn(apack, bpack, bn) apack.n = an + bn end -function --[[@delayloaded-start@]] sh.internal.handleThreadYield(result) - local action = result[2] - if action == nil or type(action) == "number" then - return table.pack(pcall(event.pull, table.unpack(result, 2, result.n))) - else - return table.pack(coroutine.yield(table.unpack(result, 2, result.n))) +setmetatable(sh, +{ + __index = function(tbl, key) + setmetatable(sh.internal, nil) + setmetatable(sh, nil) + dofile("/opt/core/full_sh.lua") + return rawget(tbl, key) end -end --[[@delayloaded-end@]] +}) -function --[[@delayloaded-start@]] sh.internal.buildCommandRedirects(args, thread) - local data = process.info(thread).data - local tokens, ios, handles = args, data.io, data.handles - args = {} - local from_io, to_io, mode - for i = 1, #tokens do - local token = tokens[i] - if token == "<" then - from_io = 0 - mode = "r" - else - local first_index, last_index, from_io_txt, mode_txt, to_io_txt = token:find("(%d*)(>>?)(.*)") - if mode_txt then - mode = mode_txt == ">>" and "a" or "w" - from_io = from_io_txt and tonumber(from_io_txt) or 1 - if to_io_txt ~= "" then - to_io = tonumber(to_io_txt:sub(2)) - ios[from_io] = ios[to_io] - mode = nil - end - else -- just an arg - if not mode then - table.insert(args, token) - else - local file, reason = io.open(shell.resolve(token), mode) - if not file then - io.stderr:write("could not open '" .. token .. "': " .. reason .. "\n") - os.exit(1) - end - table.insert(handles, file) - ios[from_io] = file - end - mode = nil - end - end - end +setmetatable(sh.internal, getmetatable(sh)) - return args -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] sh.internal.buildPipeChain(threads) - local prev_pipe - for i=1,#threads do - local thread = threads[i] - local data = process.info(thread).data - local pio = data.io - - local pipe - if i < #threads then - pipe = require("buffer").new("rw", sh.internal.newMemoryStream()) - pipe:setvbuf("no", 0) - -- buffer close flushes the buffer, but we have no buffer - -- also, when the buffer is closed, read and writes don't pass through - -- simply put, we don't want buffer:close - pipe.close = function(self) self.stream:close() end - pipe.stream.redirect[1] = rawget(pio, 1) - pio[1] = pipe - table.insert(data.handles, pipe) - end - - if prev_pipe then - prev_pipe.stream.redirect[0] = rawget(pio, 0) - prev_pipe.stream.next = thread - pio[0] = prev_pipe - end - - prev_pipe = pipe - end -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] sh.internal.glob(glob_pattern) - local segments = text.split(glob_pattern, {"/"}, true) - local hiddens = tx.foreach(segments,function(e)return e:match("^%%%.")==nil end) - local function is_visible(s,i) - return not hiddens[i] or s:match("^%.") == nil - end - - local function magical(s) - for _,glob_rule in ipairs(sh.internal.globbers) do - if (" "..s):match("[^%%]"..text.escapeMagic(glob_rule[2])) then - return true - end - end - end - - local is_abs = glob_pattern:sub(1, 1) == "/" - local root = is_abs and '' or shell.getWorkingDirectory():gsub("([^/])$","%1/") - local paths = {is_abs and "/" or ''} - local relative_separator = '' - for i,segment in ipairs(segments) do - local enclosed_pattern = string.format("^(%s)/?$", segment) - local next_paths = {} - for _,path in ipairs(paths) do - if fs.isDirectory(root..path) then - if magical(segment) then - for file in fs.list(root..path) do - if file:match(enclosed_pattern) and is_visible(file, i) then - table.insert(next_paths, path..relative_separator..file:gsub("/+$",'')) - end - end - else -- not a globbing segment, just use it raw - local plain = text.removeEscapes(segment) - local fpath = root..path..relative_separator..plain - local hit = path..relative_separator..plain:gsub("/+$",'') - if fs.exists(fpath) then - table.insert(next_paths, hit) - end - end - end - end - paths = next_paths - if not next(paths) then break end - relative_separator = "/" - end - -- if no next_paths were hit here, the ENTIRE glob value is not a path - return paths -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] sh.getMatchingPrograms(baseName) - local result = {} - local result_keys = {} -- cache for fast value lookup - -- TODO only matching files with .lua extension for now, might want to - -- extend this to other extensions at some point? env var? file attrs? - if not baseName or #baseName == 0 then - baseName = "^(.*)%.lua$" - else - baseName = "^(" .. text.escapeMagic(baseName) .. ".*)%.lua$" - end - for basePath in string.gmatch(os.getenv("PATH"), "[^:]+") do - for file in fs.list(shell.resolve(basePath)) do - local match = file:match(baseName) - if match and not result_keys[match] then - table.insert(result, match) - result_keys[match] = true - end - end - end - return result -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] sh.getMatchingFiles(partial_path) - -- name: text of the partial file name being expanded - local name = partial_path:gsub("^.*/", "") - -- here we remove the name text from the partialPrefix - local basePath = unicode.sub(partial_path, 1, -unicode.len(name) - 1) - - local resolvedPath = shell.resolve(basePath) - local result, baseName = {} - - -- note: we strip the trailing / to make it easier to navigate through - -- directories using tab completion (since entering the / will then serve - -- as the intention to go into the currently hinted one). - -- if we have a directory but no trailing slash there may be alternatives - -- on the same level, so don't look inside that directory... (cont.) - if fs.isDirectory(resolvedPath) and name == "" then - baseName = "^(.-)/?$" - else - baseName = "^(" .. text.escapeMagic(name) .. ".-)/?$" - end - - for file in fs.list(resolvedPath) do - local match = file:match(baseName) - if match then - table.insert(result, basePath .. match:gsub("(%s)", "\\%1")) - end - end - -- (cont.) but if there's only one match and it's a directory, *then* we - -- do want to add the trailing slash here. - if #result == 1 and fs.isDirectory(shell.resolve(result[1])) then - result[1] = result[1] .. "/" - end - return result -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] sh.internal.hintHandlerSplit(line) - -- I do not plan on having text tokenizer parse error on - -- trailiing \ in case of future support for multiple line - -- input. But, there are also no hints for it - if line:match("\\$") then return nil end - - local splits, simple = text.internal.tokenize(line,{show_escapes=true}) - if not splits then -- parse error, e.g. unclosed quotes - return nil -- no split, no hints - end - - local num_splits = #splits - - -- search for last statement delimiters - local last_close = 0 - for index = num_splits, 1, -1 do - local word = splits[index] - if isWordOf(word, {";","&&","||","|"}) then - last_close = index - break - end - end - - -- if the very last word of the line is a delimiter - -- consider this a fresh new, empty line - -- this captures edge cases with empty input as well (i.e. no splits) - if last_close == num_splits then - return nil -- no hints on empty command - end - - local last_word = splits[num_splits] - local normal = text.internal.normalize({last_word})[1] - - -- if there is white space following the words - -- and we have at least one word following the last delimiter - -- then in all cases we are looking for ANY arg - if unicode.sub(line, -unicode.len(normal)) ~= normal then - return line, nil, "" - end - - local prefix = unicode.sub(line, 1, -unicode.len(normal) - 1) - - -- renormlizing the string will create 'printed' quality text - normal = text.internal.normalize(text.internal.tokenize(normal), true)[1] - - -- one word: cmd - -- many: arg - if last_close == num_splits - 1 then - return prefix, normal, nil - else - return prefix, nil, normal - end -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] sh.internal.hintHandlerImpl(full_line, cursor) - -- line: text preceding the cursor: we want to hint this part (expand it) - local line = unicode.sub(full_line, 1, cursor - 1) - -- suffix: text following the cursor (if any, else empty string) to append to the hints - local suffix = unicode.sub(full_line, cursor) - - -- hintHandlerSplit helps make the hints work even after delimiters such as ; - -- it also catches parse errors such as unclosed quotes - -- prev: not needed for this hint - -- cmd: the command needing hint - -- arg: the argument needing hint - local prev, cmd, arg = sh.internal.hintHandlerSplit(line) - - -- also, if there is no text to hint, there are no hints - if not prev then -- no hints e.g. unclosed quote, e.g. no text - return {} - end - local result - - local searchInPath = cmd and not cmd:find("/") - if searchInPath then - result = sh.getMatchingPrograms(cmd) - else - -- special arg issue, after equal sign - if arg then - local equal_index = arg:find("=[^=]*$") - if equal_index then - prev = prev .. unicode.sub(arg, 1, equal_index) - arg = unicode.sub(arg, equal_index + 1) - end - end - result = sh.getMatchingFiles(cmd or arg) - end - - -- in very special cases, the suffix should include a blank space to indicate to the user that the hint is discrete - local resultSuffix = suffix - if #result > 0 and unicode.sub(result[1], -1) ~= "/" and - not suffix:sub(1,1):find('%s') and - #result == 1 or searchInPath then - resultSuffix = " " .. resultSuffix - end - - table.sort(result) - for i = 1, #result do - -- the hints define the whole line of text - result[i] = prev .. result[i] .. resultSuffix - end - return result -end --[[@delayloaded-end@]] - --- verifies that no pipes are doubled up nor at the start nor end of words -function --[[@delayloaded-start@]] sh.internal.hasValidPiping(words, pipes) - checkArg(1, words, "table") - checkArg(2, pipes, "table", "nil") - - if #words == 0 then - return true - end - - local semi_split = tx.find(text.syntax, {";"}) -- all symbols before ; in syntax CAN be repeated - pipes = pipes or tx.sub(text.syntax, semi_split + 1) - - local state = "" -- cannot start on a pipe - - for w=1,#words do - local word = words[w] - for p=1,#word do - local part = word[p] - if part.qr then - state = nil - elseif part.txt == "" then - state = nil -- not sure how this is possible (empty part without quotes?) - elseif #text.split(part.txt, pipes, true) == 0 then - local prev = state - state = part.txt - if prev then -- cannot have two pipes in a row - word = nil - break - end - else - state = nil - end - end - if not word then -- bad pipe - break - end - end - - if state then - return false, "parse error near " .. state - else - return true - end -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] sh.internal.boolean_executor(chains, predicator) - local function not_gate(result) - return sh.internal.command_passed(result) and 1 or 0 - end - - local last = true - local boolean_stage = 1 - local negation_stage = 2 - local command_stage = 0 - local stage = negation_stage - local skip = false - - for ci=1,#chains do - local next = chains[ci] - local single = #next == 1 and #next[1] == 1 and not next[1][1].qr and next[1][1].txt - - if single == "||" then - if stage ~= command_stage or #chains == 0 then - return nil, "syntax error near unexpected token '"..single.."'" - end - if sh.internal.command_passed(last) then - skip = true - end - stage = boolean_stage - elseif single == "&&" then - if stage ~= command_stage or #chains == 0 then - return nil, "syntax error near unexpected token '"..single.."'" - end - if not sh.internal.command_passed(last) then - skip = true - end - stage = boolean_stage - elseif not skip then - local chomped = #next - local negate = sh.internal.remove_negation(next) - chomped = chomped ~= #next - if negate then - local prev = predicator - predicator = function(n,i) - local result = not_gate(prev(n,i)) - predicator = prev - return result - end - end - if chomped then - stage = negation_stage - end - if #next > 0 then - last = predicator(next,ci) - stage = command_stage - end - else - skip = false - stage = command_stage - end - end - - if stage == negation_stage then - last = not_gate(last) - end - - return last -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] sh.internal.splitStatements(words, semicolon) - checkArg(1, words, "table") - checkArg(2, semicolon, "string", "nil") - semicolon = semicolon or ";" - - return tx.partition(words, function(g, i, t) - if isWord(g,semicolon) then - return i, i - end - end, true) -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] sh.internal.splitChains(s,pc) - checkArg(1, s, "table") - checkArg(2, pc, "string", "nil") - pc = pc or "|" - return tx.partition(s, function(w) - -- each word has multiple parts due to quotes - if isWord(w,pc) then - return true - end - end, true) -- drop |s -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] sh.internal.groupChains(s) - checkArg(1,s,"table") - return tx.partition(s,function(w)return isWordOf(w,{"&&","||"})end) -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] sh.internal.remove_negation(chain) - if isWord(chain[1],"!") then - table.remove(chain, 1) - return true and not sh.internal.remove_negation(chain) - end - return false -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] sh.internal.newMemoryStream() - local memoryStream = {} - - function memoryStream:close() - self.closed = true - self.redirect = {} - end - - function memoryStream:seek() - return nil, "bad file descriptor" - end - - function memoryStream:read(n) - if self.closed then - return nil -- eof - end - if self.redirect[0] then - -- popen could be using this code path - -- if that is the case, it is important to leave stream.buffer alone - return self.redirect[0]:read(n) - elseif self.buffer == "" then - coroutine.yield() - end - local result = string.sub(self.buffer, 1, n) - self.buffer = string.sub(self.buffer, n + 1) - return result - end - - function memoryStream:write(value) - if not self.redirect[1] and self.closed then - -- if next is dead, ignore all writes - if coroutine.status(self.next) ~= "dead" then - io.stderr:write("attempt to use a closed stream\n") - os.exit(1) - end - elseif self.redirect[1] then - return self.redirect[1]:write(value) - elseif not self.closed then - self.buffer = self.buffer .. value - local result = table.pack(coroutine.resume(self.next)) - if coroutine.status(self.next) == "dead" then - self:close() - end - if not result[1] then - io.stderr:write(tostring(result[2]) .. "\n") - os.exit(1) - end - return self - end - os.exit(0) -- abort the current process: SIGPIPE - end - - local stream = {closed = false, buffer = "", - redirect = {}, result = {}} - local metatable = {__index = memoryStream, - __metatable = "memorystream"} - return setmetatable(stream, metatable) -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] sh.internal.execute_complex(statements, eargs, env) - for si=1,#statements do local s = statements[si] - local chains = sh.internal.groupChains(s) - local last_code = sh.internal.boolean_executor(chains, function(chain, chain_index) - local pipe_parts = sh.internal.splitChains(chain) - local next_args = chain_index == #chains and si == #statements and eargs or {} - return sh.internal.executePipes(pipe_parts, next_args, env) - end) - sh.internal.ec.last = sh.internal.command_result_as_code(last_code) - end - return true -end --[[@delayloaded-end@]] - - -function --[[@delayloaded-start@]] sh.internal.parse_sub(input) - -- cannot use gsub here becuase it is a [C] call, and io.popen needs to yield at times - local packed = {} - -- not using for i... because i can skip ahead - local i, len = 1, #input - - while i < len do - - local fi, si, capture = input:find("`([^`]*)`", i) - - if not fi then - table.insert(packed, input:sub(i)) - break - end - - local sub = io.popen(capture) - local result = input:sub(i, fi - 1) .. sub:read("*a") - sub:close() - -- all whitespace is replaced by single spaces - -- we requote the result because tokenize will respect this as text - table.insert(packed, (text.trim(result):gsub("%s+"," "))) - - i = si+1 - end - - return table.concat(packed) -end --[[@delayloaded-end@]] - -return sh, local_env +return sh 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 af518264a..e38d651cc 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/shell.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/shell.lua @@ -1,5 +1,4 @@ local fs = require("filesystem") -local text = require("text") local unicode = require("unicode") local process = require("process") @@ -107,25 +106,6 @@ function shell.aliases() return pairs(process.info().data.aliases) end -function shell.resolveAlias(command, args) - checkArg(1, command, "string") - checkArg(2, args, "table", "nil") - args = args or {} - local program, lastProgram = command, nil - while true do - local tokens = text.tokenize(shell.getAlias(program) or program) - program = tokens[1] - if program == lastProgram then - break - end - lastProgram = program - for i = #tokens, 2, -1 do - table.insert(args, 1, tokens[i]) - end - end - return program, args -end - function shell.getWorkingDirectory() -- if no env PWD default to / return os.getenv("PWD") or "/" 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 1139e7938..fe77664c5 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua @@ -1,606 +1,78 @@ +local tty = require("tty") local unicode = require("unicode") -local event = require("event") -local process = require("process") -local kb = require("keyboard") -local component = require("component") local computer = require("computer") +local process = require("process") + +local kb = require("keyboard") local keys = kb.keys -local term = {} -term.internal = {} +local term = setmetatable({internal={}}, {__index=tty}) function term.internal.window() return process.info().data.window end -local W = term.internal.window -local local_env = {unicode=unicode,event=event,process=process,W=W,kb=kb} - -local gpu_intercept = {} -local function update_viewport(window, width, height) - window = window or W() - local gpu = window.gpu - if not gpu then return end - if not gpu_intercept[gpu] then - gpu_intercept[gpu] = {} -- only override a gpu once - -- the gpu can change resolution before we get a chance to call events and handle screen_resized - -- unfortunately, we have to handle viewport changes by intercept - local setr, setv = gpu.setResolution, gpu.setViewport - gpu.setResolution = function(...) - gpu_intercept[gpu] = {} - return setr(...) - end - gpu.setViewport = function(...) - gpu_intercept[gpu] = {} - return setv(...) - end +local function as_window(window, func, ...) + local data = process.info().data + if not data.window then + return func(...) end - if not width and not gpu_intercept[gpu][window] then - width, height = gpu.getViewport() - end - if width then - window:resize(width, height) - gpu_intercept[gpu][window] = true - end -end - -local function resize(window, width, height) - window.w, window.h = width, height + local prev = rawget(data, "window") + data.window = window + local ret = table.pack(func(...)) + data.window = prev + return table.unpack(ret, ret.n) end function term.internal.open(...) local dx, dy, w, h = ... - local window = {x=1,y=1,fullscreen=select("#",...)==0,dx=dx or 0,dy=dy or 0,w=w,h=h,blink=true,resize=resize} - event.listen("screen_resized", function(_,addr,w,h) - if term.isAvailable(window) and term.screen(window) == addr and window.fullscreen then - update_viewport(window, w, h) + local window = {fullscreen=select("#",...) == 0, blink = true} + + -- support legacy code using direct manipulation of w and h + -- (e.g. wocchat) instead of using setViewport + setmetatable(window, + { + __index = function(tbl, key) + key = key == "w" and "width" or key == "h" and "height" or key + return rawget(tbl, key) + end, + __newindex = function(tbl, key, value) + key = key == "w" and "width" or key == "h" and "height" or key + return rawset(tbl, key, value) end - end) + }) + + -- first time we open a pty the current tty.window must become the process window + if not term.internal.window() then + local init_index = 2 + while process.info(init_index) do + init_index = init_index + 1 + end + process.info(init_index - 1).data.window = tty.window + tty.window = nil + setmetatable(tty, + { + __index = function(tbl, key) + if key == "window" then + return term.internal.window() + end + end + }) + end + + as_window(window, tty.setViewport, w, h, dx, dy, 1, 1) return window end -function term.getViewport(window) - window = window or W() - update_viewport(window) - return window.w, window.h, window.dx, window.dy, window.x, window.y -end - -function term.setViewport(w,h,dx,dy,x,y,window) - window = window or W() - - dx,dy,x,y = dx or 0,dy or 0,x or 1,y or 1 - if not w or not h then - local gw,gh = window.gpu.getViewport() - w,h = w or gw, h or gh - end - - window.dx,window.dy,window.x,window.y,window.gw,window.gh = dx, dy, x, y, gw, gh - update_viewport(window, w, h) -end - -function term.gpu(window) - window = window or W() - return window.gpu -end - -function term.clear() - local w = W() - local gpu = w.gpu - if not gpu then return end - gpu.fill(1+w.dx,1+w.dy,w.w,w.h," ") - w.x,w.y=1,1 -end - -function term.isAvailable(w) - w = w or W() - return w and not not (w.gpu and w.gpu.getScreen()) -end - -function term.internal.pull(input, timeout, ...) - timeout = timeout or math.huge - - local w = W() - local d, h, dx, dy, x, y = term.getViewport(w) - local out = (x<1 or x>d or y<1 or y>h) - - if input and out then - input:move(0) - y = w.y - input:scroll() - end - - x, y = w.x + dx, w.y + dy - local gpu = (input or not out) and w.gpu - - local bgColor, bgIsPalette - local fgColor, fgIsPalette - local char_at_cursor - local blinking - if gpu then - bgColor, bgIsPalette = gpu.getBackground() - -- it can happen during a type of race condition when a screen is removed - if not bgColor then - return nil, "interrupted" - end - - fgColor, fgIsPalette = gpu.getForeground() - char_at_cursor = gpu.get(x, y) - - blinking = w.blink - if input then - blinking = input.blink - end - end - - -- get the next event - local blinked = false - local done = false - local signal - while true do - if gpu then - if not blinked and not done then - gpu.setForeground(bgColor, bgIsPalette) - gpu.setBackground(fgColor, fgIsPalette) - gpu.set(x, y, char_at_cursor) - gpu.setForeground(fgColor, fgIsPalette) - gpu.setBackground(bgColor, bgIsPalette) - blinked = true - elseif blinked then - gpu.set(x, y, char_at_cursor) - blinked = false - end - end - - if done then - return table.unpack(signal, 1, signal.n) - end - - signal = table.pack(event.pull(math.min(.5, timeout), ...)) - timeout = timeout - .5 - done = signal.n > 1 or timeout < .5 - end -end - -function term.pull(...) - local args = table.pack(...) - local timeout = nil - if type(args[1]) == "number" then - timeout = table.remove(args, 1) - args.n = args.n - 1 - end - return term.internal.pull(nil, timeout, table.unpack(args, 1, args.n)) -end - -function term.read(history,dobreak,hintHandler,pwchar,filter) - if not io.stdin.tty then return io.read() end - local ops = history or {} - ops.dobreak = ops.dobreak - if ops.dobreak==nil then ops.dobreak = dobreak end - ops.hintHandler = ops.hintHandler or hintHandler - ops.pwchar = ops.pwchar or pwchar - ops.filter = ops.filter or filter - return term.readKeyboard(ops) -end - -function term.internal.split(input) - local data,index=input.data,input.index - local dlen = unicode.len(data) - index=math.max(0,math.min(index,dlen)) - local tail=dlen-index - return unicode.sub(data,1,index),tail==0 and""or unicode.sub(data,-tail) -end - -function term.internal.build_vertical_reader(input) - input.sy = 0 - input.scroll = function(_) - _.sy = _.sy + term.internal.scroll(_.w) - _.w.y = math.min(_.w.y,_.w.h) - end - input.move = function(_,n) - local w=_.w - _.index = math.min(math.max(0,_.index+n),unicode.len(_.data)) - local s1,s2 = term.internal.split(_) - s2 = unicode.sub(s2.." ",1,1) - local data_remaining = ("_"):rep(_.promptx-1)..s1..s2 - w.y = _.prompty - _.sy - while true do - local wlen_remaining = unicode.wlen(data_remaining) - if wlen_remaining > w.w then - local line_cut = unicode.wtrunc(data_remaining, w.w+1) - data_remaining = unicode.sub(data_remaining,unicode.len(line_cut)+1) - w.y=w.y+1 - else - w.x = wlen_remaining-unicode.wlen(s2)+1 - break - end - end - end - input.clear_tail = function(_) - local win=_.w - local oi,w,h,dx,dy,ox,oy = _.index,term.getViewport(win) - _:move(math.huge) - _:move(-1) - local ex,ey=win.x,win.y - win.x,win.y,_.index=ox,oy,oi - x=oy==ey and ox or 1 - win.gpu.fill(x+dx,ey+dy,w-x+1,1," ") - end - input.update = function(_,arg) - local w,cursor,suffix=_.w - local s1,s2=term.internal.split(_) - if type(arg) == "number" then - local ndata - if arg < 0 then if _.index<=0 then return end - _:move(-1) - ndata=unicode.sub(s1,1,-2)..s2 - else if _.index>=unicode.len(_.data) then return end - s2=unicode.sub(s2,2) - ndata=s1..s2 - end - suffix=s2 - input:clear_tail() - _.data = ndata - else - _.data=s1..arg..s2 - _.index=_.index+unicode.len(arg) - cursor,suffix=arg,s2 - end - if cursor then _:draw(_.mask(cursor)) end - if suffix and suffix~="" then - local px,py,ps=w.x,w.y,_.sy - _:draw(_.mask(suffix)) - w.x,w.y=px,py-(_.sy-ps) - end - end - input.clear = function(_) - _:move(-math.huge) - _:draw((" "):rep(unicode.wlen(_.data))) - _:move(-math.huge) - _.index=0 - _.data="" - end - input.draw = function(_,text) - _.sy = _.sy + term.drawText(text,true) - end -end - -function term.internal.read_history(history,input,change) - if not change then - if unicode.wlen(input.data) > 0 then - table.insert(history.list,1,input.data) - history.list[(tonumber(os.getenv("HISTSIZE")) or 10)+1]=nil - history.list[0]=nil - end - else - local ni = history.index + change - if ni >= 0 and ni <= #history.list then - history.list[history.index]=input.data - history.index = ni - input:clear() - input:update(history.list[ni]) - end - end -end - -function term.readKeyboard(ops) - checkArg(1,ops,"table") - local filter = ops.filter and function(i) return term.internal.filter(ops.filter,i) end or term.internal.nop - local pwchar = ops.pwchar and function(i) return term.internal.mask(ops.pwchar,i) end or term.internal.nop - local history,db,hints={list=ops,index=0},ops.dobreak,{handler=ops.hintHandler} - local w=W() - local draw=io.stdin.tty and term.drawText or term.internal.nop - local input={w=w,promptx=w.x,prompty=w.y,index=0,data="",mask=pwchar} - input.blink = ops.blink - if input.blink == nil then - input.blink = w.blink - end - - -- two wrap types currently supported, vertical and hortizontal - if ops.nowrap then term.internal.build_horizontal_reader(input) - else term.internal.build_vertical_reader(input) - end - - while true do - local name, address, char, code = term.internal.pull(input) - if not term.isAvailable() then - return - end - - -- we have to keep checking what kb is active in case it is switching during use - -- we could have multiple screens, each with keyboards active - local main_kb = term.keyboard(w) - local main_sc = term.screen(w) - local c - local backup_cache = hints.cache - if name == "interrupted" then - draw("^C\n",true) - return false - elseif address == main_kb or address == main_sc then - if name == "touch" or name == "drag" then - term.internal.onTouch(input,char,code) - elseif name == "clipboard" then - c = term.internal.clipboard(char) - hints.cache = nil - elseif name == "key_down" then - hints.cache = nil - local ctrl = kb.isControlDown(address) - if ctrl and code == keys.d then return - elseif code == keys.tab then - hints.cache = backup_cache - term.internal.tab(input,hints) - elseif (code == keys.enter or code == keys.numpadenter) - and filter(input) then - input:move(math.huge) - if db ~= false then - draw("\n") - end - term.internal.read_history(history,input) - return input.data .. "\n" - elseif code == keys.up then term.internal.read_history(history, input, 1) - elseif code == keys.down then term.internal.read_history(history, input, -1) - elseif code == keys.left then input:move(ctrl and term.internal.ctrl_movement(input, -1) or -1) - elseif code == keys.right then input:move(ctrl and term.internal.ctrl_movement(input, 1) or 1) - elseif code == keys.home then input:move(-math.huge) - elseif code == keys["end"] then input:move( math.huge) - elseif code == keys.back then c = -1 - elseif code == keys.delete then c = 0 - elseif ctrl and char == "w"then -- TODO: cut word - elseif char >= 32 then c = unicode.char(char) - else hints.cache = backup_cache -- ignored chars shouldn't clear hint cache - end - end - -- if we obtained something (c) to handle - if c then - input:update(c) - end - end - 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_wrap = stream.wrap - stream.wrap = wrap == nil and true or wrap - stdout:write(value) - stdout:flush() - stream.wrap = previous_wrap -end - -function term.getCursor() - local w = W() - return w.x,w.y -end - -function term.setCursor(x,y) - local w = W() - w.x,w.y=x,y -end - -function term.drawText(value, wrap, window) - window = window or W() - if not window then return end - local gpu = window.gpu - if not gpu then return end - local w,h,dx,dy,x,y = term.getViewport(window) - local sy = 0 - local vlen = #value - local index = 1 - local cr_last,beeped = false,false - local function scroll(_sy,_y) - return _sy + term.internal.scroll(window,_y-h), math.min(_y,h) - end - local uptime = computer.uptime - local last_sleep = uptime() - while index <= vlen do - if uptime() - last_sleep > 4 then - os.sleep(0) - last_sleep = uptime() - end - local si,ei = value:find("[\t\r\n\a]", index) - si = si or vlen+1 - if index==si then - local delim = value:sub(index, index) - if delim=="\t" then - x=((x-1)-((x-1)%8))+9 - elseif delim=="\r" or (delim=="\n" and not cr_last) then - x,y=1,y+1 - sy,y = scroll(sy,y) - elseif delim=="\a" and not beeped then - computer.beep() - beeped = true - end - cr_last = delim == "\r" - else - sy,y = scroll(sy,y) - si = si - 1 - local next = value:sub(index, si) - local wlen_needed = unicode.wlen(next) - local slen = #next - local wlen_remaining = w - x + 1 - local clean_end = "" - if wlen_remaining < wlen_needed then - next = unicode.wtrunc(next, wlen_remaining + 1) - wlen_needed = unicode.wlen(next) - clean_end = (" "):rep(wlen_remaining-wlen_needed) - if not wrap then - si = math.huge - end - end - gpu.set(x+dx,y+dy,next..clean_end) - x = x + wlen_needed - if wrap and slen ~= #next then - si = si - (slen - #next) - x = 1 - y = y + 1 - end - end - index = si + 1 - end - - window.x,window.y = x,y - return sy -end - -function term.internal.scroll(w,n) - w = w or W() - local gpu,d,h,dx,dy,x,y = w.gpu,term.getViewport(w) - n = n or (y-h) - if n <= 0 then return 0 end - gpu.copy(dx+1,dy+n+1,d,h-n,0,-n) - gpu.fill(dx+1,dy+h-n+1,d,n," ") - return n -end - -function term.internal.nop(...) - return ... -end - -function term.setCursorBlink(enabled) - W().blink=enabled -end - -function term.getCursorBlink() - return W().blink -end - -function term.bind(gpu, window) - window = window or W() - window.gpu = gpu or window.gpu - window.keyboard = nil -- without a keyboard bound, always use the screen's main keyboard (1st) - if window.fullscreen then - term.setViewport(nil,nil,nil,nil,window.x,window.y,window) - end -end - -function term.keyboard(window) - window = window or W() or {} -- this method needs to be safe even if there is no terminal window (e.g. no gpu) - - if window.keyboard then - return window.keyboard - end - - local system_keyboard = component.isAvailable("keyboard") and component.keyboard - system_keyboard = system_keyboard and system_keyboard.address or "no_system_keyboard" - - local screen = term.screen(window) - - if not screen then - -- no screen, no known keyboard, use system primary keyboard if any - return system_keyboard - end - - -- if we are using a gpu bound to the primary scren, then use the primary keyboard - if component.isAvailable("screen") and component.screen.address == screen then - window.keyboard = system_keyboard - else - -- calling getKeyboards() on the screen is costly (time) - -- custom terminals should avoid designs that require - -- this on every key hit - - -- this is expensive (slow!) - window.keyboard = component.invoke(screen, "getKeyboards")[1] or system_keyboard - end - - return window.keyboard -end - -function term.screen(window) - window = window or W() - local gpu = window.gpu - if not gpu then - return nil - end - return gpu.getScreen() -end - -function --[[@delayloaded-start@]] term.scroll(number, window) - -- if zero scroll length is requested, do nothing - if number == 0 then return end - -- window is optional, default to current active terminal - window = window or W() - -- gpu works with global coordinates - local gpu,width,height,dx,dy,x,y = window.gpu,term.getViewport(w) - - -- scroll request can be too large - local abs_number = math.abs(number) - if (abs_number >= height) then - term.clear() - return - end - - -- box positions to shift - local box_height = height - abs_number - local top = 0 - if number > 0 then - top = number -- (e.g. 1 scroll moves box at 2) - end - - gpu.copy(dx + 1, dy + top + 1, width, box_height, 0, -number) - - local fill_top = 0 - if number > 0 then - fill_top = box_height - end - - gpu.fill(dx + 1, dy + fill_top + 1, width, abs_number, " ") -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] term.internal.ctrl_movement(input, dir) - local index, data = input.index, input.data - - local function isEdge(char) - return char == "" or not not char:find("%s") - end - - 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) - if isEdge(a) and not isEdge(b) then return i-(index+1) end - end - return last - index -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] term.internal.onTouch(input,gx,gy) - if input.data == "" then return end - input:move(-math.huge) - local w = W() - gx,gy=gx-w.dx,gy-w.dy - local x2,y2,d = input.w.x,input.w.y,input.w.w - local char_width_to_move = ((gy*d+gx)-(y2*d+x2)) - if char_width_to_move <= 0 then return end - local total_wlen = unicode.wlen(input.data) - if char_width_to_move >= total_wlen then - input:move(math.huge) - else - local chars_to_move = unicode.wtrunc(input.data, char_width_to_move + 1) - input:move(unicode.len(chars_to_move)) - end - -- fake white space can make the index off, redo adjustment for alignment - x2,y2,d = input.w.x,input.w.y,input.w.w - char_width_to_move = ((gy*d+gx)-(y2*d+x2)) - if (char_width_to_move < 0) then - -- using char_width_to_move as a type of index is wrong, but large enough and helps to speed this up - local up_to_cursor = unicode.sub(input.data, input.index+char_width_to_move, input.index) - local full_wlen = unicode.wlen(up_to_cursor) - local without_tail = unicode.wtrunc(up_to_cursor, full_wlen + char_width_to_move + 1) - local chars_cut = unicode.len(up_to_cursor) - unicode.len(without_tail) - input:move(-chars_cut) - end -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] term.internal.build_horizontal_reader(input) - term.internal.build_vertical_reader(input) - input.clear_tail = function(_) - local w,h,dx,dy,x,y = term.getViewport(_.w) - local s1,s2=term.internal.split(_) +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(_) local wlen = math.min(unicode.wlen(s2),w-x+1) - _.w.gpu.fill(x+dx,y+dy,wlen,1," ") + tty.gpu().fill(x+dx,y+dy,wlen,1," ") end - input.move = function(_,n) - local win = _.w + cursor.move = function(_,n) + local win = tty.window local a = _.index local b = math.max(0,math.min(unicode.len(_.data),_.index+n)) _.index = b @@ -609,13 +81,13 @@ function --[[@delayloaded-start@]] term.internal.build_horizontal_reader(input) win.x = win.x + wlen_moved * (n<0 and -1 or 1) _:scroll() end - input.draw = function(_,text) - term.drawText(text,false) + cursor.draw = function(_, text) + tty.drawText(text, true) end - input.scroll = function(_) - local win = _.w + cursor.scroll = function(_) + local win = tty.window local gpu,data,px,i = win.gpu,_.data,_.promptx,_.index - local w,h,dx,dy,x,y = term.getViewport(win) + 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 @@ -640,94 +112,118 @@ function --[[@delayloaded-start@]] term.internal.build_horizontal_reader(input) gpu.set(sx,sy,data) end end - input.clear = function(_) - local win = _.w + cursor.clear = function(_) + local win = tty.window local gpu,data,px=win.gpu,_.data,_.promptx - local w,h,dx,dy,x,y = term.getViewport(win) + local w,h,dx,dy,x,y = tty.getViewport() _.index,_.data,win.x=0,"",px gpu.fill(px+dx,y+dy,w-px+1-dx,1," ") end -end --[[@delayloaded-end@]] +end -function --[[@delayloaded-start@]] term.clearLine(window) - window = window or W() - local w,h,dx,dy,x,y = term.getViewport(window) - window.gpu.fill(dx+1,dy+math.max(1,math.min(y,h)),w,1," ") - window.x=1 -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] term.internal.mask(mask,input) - if not mask then return input end - if type(mask) == "function" then return mask(input) end - return mask:rep(unicode.wlen(input)) -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] term.internal.filter(filter,input) - if not filter then return true - elseif type(filter) == "string" then return input.data:match(filter) - elseif filter(input.data) then return true - else require("computer").beep(2000, 0.1) end -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] term.internal.tab(input,hints) - if not hints.handler then return end - local main_kb = term.keyboard() - -- term may not have a keyboard - -- in which case, we shouldn't be handling tab events - if not main_kb then - return - end - if not hints.cache then - local data = hints.handler - hints.handler = function(...) - if type(data) == "table" then - return data - else - return data(...) or {} +local function inject_filter(handler, filter) + if filter then + if type(filter) == "string" then + local filter_text = filter + filter = function(text) + return text:match(filter_text) end end - hints.cache = hints.handler(input.data, input.index + 1) - hints.cache.i = -1 + + local mt = + { + __newindex = function(tbl, key, value) + if key == "key_down" then + local tty_key_down = value + value = function(handler, cursor, char, code) + if code == keys.enter or code == keys.numpadenter then + if not filter(cursor.data) then + computer.beep(2000, 0.1) + return false -- ignore + end + end + return tty_key_down(handler, cursor, char, code) + end + end + rawset(tbl, key, value) + end + } + setmetatable(handler, mt) + end +end + +local function inject_mask(cursor, dobreak, pwchar) + if not pwchar and dobreak ~= false then + return end - local cache = hints.cache - local cache_size = #cache - - if cache_size == 1 and cache.i == 0 then - -- there was only one solution, and the user is asking for the next - hints.cache = hints.handler(cache[1], input.index + 1) - hints.cache.i = -1 - cache = hints.cache - cache_size = #cache - end - - local change = kb.isShiftDown(main_kb) and -1 or 1 - cache.i = (cache.i + change) % math.max(#cache, 1) - local next = cache[cache.i + 1] - if next then - local tail = unicode.len(input.data) - input.index - input:clear() - input:update(next) - input:move(-tail) - end -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] term.getGlobalArea(window) - local w,h,dx,dy = term.getViewport(window) - return dx+1,dy+1,w,h -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] term.internal.clipboard(char) - local first_line, end_index = char:find("\13?\10") - if first_line then - local after = char:sub(end_index + 1) - if after ~= "" then - require("computer").pushSignal("key_down", term.keyboard(), 13, 28) - require("computer").pushSignal("clipboard", term.keyboard(), after) + if pwchar then + if type(pwchar) == "string" then + local pwchar_text = pwchar + pwchar = function(text) + return text:gsub(".", pwchar_text) + end end - char = char:sub(1, first_line - 1) end - return char -end --[[@delayloaded-end@]] -return term, local_env + local cursor_draw = cursor.draw + cursor.draw = function(cursor, text) + local pre, newline = text:match("(.-)(\n?)$") + if dobreak == false then + newline = "" + end + if pwchar then + pre = pwchar(pre) + end + return cursor_draw(cursor, pre .. newline) + end +end + +function term.read(history, dobreak, hint, pwchar, filter) + if not io.stdin.tty then + return io.read() + end + local handler = history or {} + handler.hint = handler.hint or hint + + local cursor = tty.internal.build_vertical_reader() + if handler.nowrap then + build_horizontal_reader(cursor) + end + + inject_filter(handler, filter) + inject_mask(cursor, dobreak, pwchar) + -- todo, make blinking work from here + -- handler.blink or w.blink + + return tty.read(handler, cursor) +end + +function term.getGlobalArea(window) + local w,h,dx,dy = as_window(window, tty.getViewport) + return dx+1,dy+1,w,h +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 +end + +function term.pull(...) + local args = table.pack(...) + local timeout = nil + if type(args[1]) == "number" then + timeout = table.remove(args, 1) + args.n = args.n - 1 + end + return tty.pull(nil, timeout, table.unpack(args, 1, args.n)) +end + +function term.bind(gpu, window) + return as_window(window, tty.bind, gpu) +end + +return term + diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/text.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/text.lua index 35278fa9c..9e3ce0e86 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/text.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/text.lua @@ -5,23 +5,14 @@ local tx = require("transforms") -- See package.lua and the api wiki for more information local text = {} -local local_env = {tx=tx,unicode=unicode} - text.internal = {} text.syntax = {"^%d?>>?&%d+$",";","&&","||?","^%d?>>?",">>?","<"} -function --[[@delayloaded-start@]] text.detab(value, tabWidth) - checkArg(1, value, "string") - checkArg(2, tabWidth, "number", "nil") - tabWidth = tabWidth or 8 - local function rep(match) - local spaces = tabWidth - match:len() % tabWidth - return match .. string.rep(" ", spaces) - end - local result = value:gsub("([^\n]-)\t", rep) -- truncate results - return result -end --[[@delayloaded-end@]] +function text.trim(value) -- from http://lua-users.org/wiki/StringTrim + local from = string.match(value, "^%s*()") + return from > #value and "" or string.match(value, ".*%S", from) +end -- used in motd function text.padRight(value, length) @@ -34,21 +25,6 @@ function text.padRight(value, length) end end -function --[[@delayloaded-start@]] text.padLeft(value, length) - checkArg(1, value, "string", "nil") - checkArg(2, length, "number") - if not value or unicode.wlen(value) == 0 then - return string.rep(" ", length) - else - return string.rep(" ", length - unicode.wlen(value)) .. value - end -end --[[@delayloaded-end@]] - -function text.trim(value) -- from http://lua-users.org/wiki/StringTrim - local from = string.match(value, "^%s*()") - return from > #value and "" or string.match(value, ".*%S", from) -end - function text.wrap(value, width, maxWidth) checkArg(1, value, "string") checkArg(2, width, "number") @@ -78,29 +54,7 @@ function text.wrappedLines(value, width, maxWidth) end end -------------------------------------------------------------------------------- - --- separate string value into an array of words delimited by whitespace --- groups by quotes --- options is a table used for internal undocumented purposes -function text.tokenize(value, options) - checkArg(1, value, "string") - checkArg(2, options, "table", "nil") - options = options or {} - - local tokens, reason = text.internal.tokenize(value, options) - - if type(tokens) ~= "table" then - return nil, reason - end - - if options.doNotNormalize then - return tokens - end - - return text.internal.normalize(tokens) -end - +-- used by lib/sh function text.escapeMagic(txt) return txt:gsub('[%(%)%.%%%+%-%*%?%[%^%$]', '%%%1') end @@ -109,50 +63,6 @@ function text.removeEscapes(txt) return txt:gsub("%%([%(%)%.%%%+%-%*%?%[%^%$])","%1") end -------------------------------------------------------------------------------- --- like tokenize, but does not drop any text such as whitespace --- splits input into an array for sub strings delimited by delimiters --- delimiters are included in the result if not dropDelims -function --[[@delayloaded-start@]] text.split(input, delimiters, dropDelims, di) - checkArg(1, input, "string") - checkArg(2, delimiters, "table") - checkArg(3, dropDelims, "boolean", "nil") - checkArg(4, di, "number", "nil") - - if #input == 0 then return {} end - di = di or 1 - local result = {input} - if di > #delimiters then return result end - - local function add(part, index, r, s, e) - local sub = part:sub(s,e) - if #sub == 0 then return index end - local subs = r and text.split(sub,delimiters,dropDelims,r) or {sub} - for i=1,#subs do - table.insert(result, index+i-1, subs[i]) - end - return index+#subs - end - - local i,d=1,delimiters[di] - while true do - local next = table.remove(result,i) - if not next then break end - local si,ei = next:find(d) - if si and ei and ei~=0 then -- delim found - i=add(next, i, di+1, 1, si-1) - i=dropDelims and i or add(next, i, false, si, ei) - i=add(next, i, di, ei+1) - else - i=add(next, i, di+1, 1, #next) - end - end - - return result -end --[[@delayloaded-end@]] - ------------------------------------------------------------------------------ - function text.internal.tokenize(value, options) checkArg(1, value, "string") checkArg(2, options, "table", "nil") @@ -237,160 +147,16 @@ function text.internal.words(input, options) return tokens end --- splits each word into words at delimiters --- delimiters are kept as their own words --- quoted word parts are not split -function --[[@delayloaded-start@]] text.internal.splitWords(words, delimiters) - checkArg(1,words,"table") - checkArg(2,delimiters,"table") - - local split_words = {} - local next_word - local function add_part(part) - if next_word then - split_words[#split_words+1] = {} - end - table.insert(split_words[#split_words], part) - next_word = false - end - for wi=1,#words do local word = words[wi] - next_word = true - for pi=1,#word do local part = word[pi] - local qr = part.qr - if qr then - add_part(part) - else - local part_text_splits = text.split(part.txt, delimiters) - tx.foreach(part_text_splits, function(sub_txt, spi) - local delim = #text.split(sub_txt, delimiters, true) == 0 - next_word = next_word or delim - add_part({txt=sub_txt,qr=qr}) - next_word = delim - end) - end - end +setmetatable(text, +{ + __index = function(tbl, key) + setmetatable(text.internal, nil) + setmetatable(text, nil) + dofile("/opt/core/full_text.lua") + return rawget(tbl, key) end +}) - return split_words -end --[[@delayloaded-end@]] +setmetatable(text.internal, getmetatable(text)) -function --[[@delayloaded-start@]] text.internal.normalize(words, omitQuotes) - checkArg(1, words, "table") - checkArg(2, omitQuotes, "boolean", "nil") - local norms = {} - for _,word in ipairs(words) do - local norm = {} - for _,part in ipairs(word) do - norm = tx.concat(norm, not omitQuotes and part.qr and {part.qr[1], part.txt, part.qr[2]} or {part.txt}) - end - norms[#norms+1]=table.concat(norm) - end - return norms -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] text.internal.stream_base(binary) - return - { - binary = binary, - plen = binary and string.len or unicode.len, - psub = binary and string.sub or unicode.sub, - seek = function (handle, whence, to) - if not handle.txt then - return nil, "bad file descriptor" - end - to = to or 0 - local offset = handle:indexbytes() - if whence == "cur" then - offset = offset + to - elseif whence == "set" then - offset = to - elseif whence == "end" then - offset = handle.len + to - end - offset = math.max(0, math.min(offset, handle.len)) - handle:byteindex(offset) - return offset - end, - indexbytes = function (handle) - return handle.psub(handle.txt, 1, handle.index):len() - end, - byteindex = function (handle, offset) - local sub = string.sub(handle.txt, 1, offset) - handle.index = handle.plen(sub) - end, - } -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] text.internal.reader(txt, mode) - checkArg(1, txt, "string") - local reader = setmetatable( - { - txt = txt, - len = string.len(txt), - index = 0, - read = function(_, n) - checkArg(1, n, "number") - if not _.txt then - return nil, "bad file descriptor" - end - if _.index >= _.plen(_.txt) then - return nil - end - local next = _.psub(_.txt, _.index + 1, _.index + n) - _.index = _.index + _.plen(next) - return next - end, - close = function(_) - if not _.txt then - return nil, "bad file descriptor" - end - _.txt = nil - return true - end, - }, {__index=text.internal.stream_base(mode:match("b"))}) - - return require("buffer").new("r", reader) -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] text.internal.writer(ostream, mode, append_txt) - if type(ostream) == "table" then - local mt = getmetatable(ostream) or {} - checkArg(1, mt.__call, "function") - end - checkArg(1, ostream, "function", "table") - checkArg(2, append_txt, "string", "nil") - local writer = setmetatable( - { - txt = "", - index = 0, -- last location of write - len = 0, - write = function(_, ...) - if not _.txt then - return nil, "bad file descriptor" - end - local pre = _.psub(_.txt, 1, _.index) - local vs = {} - local pos = _.psub(_.txt, _.index + 1) - for i,v in ipairs({...}) do - table.insert(vs, v) - end - vs = table.concat(vs) - _.index = _.index + _.plen(vs) - _.txt = pre .. vs .. pos - _.len = string.len(_.txt) - return true - end, - close = function(_) - if not _.txt then - return nil, "bad file descriptor" - end - ostream((append_txt or "") .. _.txt) - _.txt = nil - return true - end, - }, {__index=text.internal.stream_base(mode:match("b"))}) - - return require("buffer").new("w", writer) -end --[[@delayloaded-end@]] - -return text, local_env +return text diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/delayLookup.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/tools/delayLookup.lua deleted file mode 100644 index c0e049b0f..000000000 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/delayLookup.lua +++ /dev/null @@ -1,33 +0,0 @@ -local data,tbl,key = ... -local z = data[tbl] - -if key then -- index - local method = z.methods[key] - local cache = z.cache[key] - if method and not cache then - local file = io.open(z.path,"r") - if file then - file:seek("set", method[1]) - local loaded = load("return function"..file:read(method[2]), "=delayed-"..key,"t",z.env) - file:close() - assert(loaded,"failed to load "..key) - cache = loaded() - --lazy_protect(key, cache) - z.cache[key] = cache - end - end - return cache -else -- pairs - local set,k,v = {} - while true do - k,v = next(tbl,k) - if not k then break end - set[k] = v - end - for k in pairs(z.methods) do - if not set[k] then - set[k] = function(...)return tbl[k](...)end - end - end - return pairs(set) -end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/delayParse.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/tools/delayParse.lua deleted file mode 100644 index 9f7412f7e..000000000 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/delayParse.lua +++ /dev/null @@ -1,67 +0,0 @@ -local filepath,delay_data = ... -local file, reason = io.open(filepath, "r") -if not file then - return reason -end - -local methods = {} -local delay_start_pattern = "^%s*function%s*%-%-%[%[@delayloaded%-start@%]%]%s*(.*)$" -local delay_end_pattern = "^%s*end%s*%-%-%[%[@delayloaded%-end@%]%]%s*$" -local n,buffer,lib_name,current_method,open = 0,{} - -while true do - local line = file:readLine(false) - if current_method then - local closed = not line or line:match(delay_end_pattern) - if closed then - local path,method_name,args = open:match("^(.-)([^%.]+)(%(.*)$") - current_method = current_method-#args - methods[path] = methods[path] or {} - methods[path][method_name] = {current_method,n+#line-current_method} - current_method=nil - end - elseif line then - open = line:match(delay_start_pattern) - if open then - lib_name,open = open:match("^([^%.]+)%.(.*)$") - current_method = n+#line - else - buffer[#buffer+1] = line - end - else - file:close() - break - end - n = n + #line -end - -if not next(methods) or current_method or not lib_name then - return "no methods found or unclosed marker for delayed load" -end - -buffer = table.concat(buffer) -local loader, reason = load(buffer, "="..filepath, "t", _G) -local library, local_env = loader() -if library then - local_env = local_env or {} - local_env[lib_name] = library - - local env = setmetatable(local_env, {__index=_G}) - - for path,pack in pairs(methods) do - local target = library - for name in path:gmatch("[^%.]+") do target = target[name] end - delay_data[target] = - { - methods = pack, - cache = {}, - env = env, - path = filepath - } - setmetatable(target, delay_data) - end - - return function()return library end, filepath -end - -return reason diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/transforms.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/transforms.lua index ebf1b8981..216897e0a 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/transforms.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/transforms.lua @@ -54,109 +54,6 @@ function lib.begins(tbl,v,f,l) return true end --- works like string.sub but on elements of an indexed table -function --[[@delayloaded-start@]] lib.sub(tbl,f,l) - checkArg(1,tbl,'table') - local r,s={},#tbl - f,l=adjust(f,l,s) - l=math.min(l,s) - for i=math.max(f,1),l do - r[#r+1]=tbl[i] - end - return r -end --[[@delayloaded-end@]] - --- if value was made by lib.sub then find can find from whence -function --[[@delayloaded-start@]] lib.find(tbl, sub, first, last) - checkArg(1, tbl, 'table') - checkArg(2, sub, 'table') - local sub_len = #sub - return lib.first(tbl, function(element, index, projected_table) - for n=0,sub_len-1 do - if projected_table[n + index] ~= sub[n + 1] then return nil end - end - return 1, sub_len - end, first, last) -end --[[@delayloaded-end@]] - --- Returns a list of subsets of tbl where partitioner acts as a delimiter. -function --[[@delayloaded-start@]] lib.partition(tbl,partitioner,dropEnds,f,l) - checkArg(1,tbl,'table') - checkArg(2,partitioner,'function','table') - checkArg(3,dropEnds,'boolean','nil') - if type(partitioner)=='table'then - return lib.partition(tbl,function(e,i,tbl) - return lib.first(tbl,partitioner,i) - end,dropEnds,f,l) - end - local s=#tbl - f,l=adjust(f,l,s) - local cut=view(tbl,f,l) - local result={} - local need=true - local exp=function()if need then result[#result+1]={}need=false end end - local i=f - while i<=l do - local e=cut[i] - local ds,de=partitioner(e,i,cut) - -- true==partition here - if ds==true then ds,de=i,i - elseif ds==false then ds,de=nil,nil end - if ds~=nil then - ds,de=adjust(ds,de,l) - ds=ds>=i and ds--no more - end - if not ds then -- false or nil - exp() - table.insert(result[#result],e) - else - local sub=lib.sub(cut,i,not dropEnds and de or (ds-1)) - if #sub>0 then - exp() - result[#result+math.min(#result[#result],1)]=sub - end - -- ensure i moves forward - local ensured=math.max(math.max(de or ds,ds),i) - if de and ds and de= 0 and ni <= #handler then + handler[handler.index] = cursor.data + handler.index = ni + cursor:clear() + cursor:update(handler[ni]) + end +end + +local function tab_handler(handler, cursor) + local hints = handler.hint + if not hints then return end + local main_kb = tty.keyboard() + -- tty may not have a keyboard + -- in which case, we shouldn't be handling tab events + if not main_kb then + return + end + if not handler.cache then + handler.cache = type(hints) == "table" and hints or hints(cursor.data, cursor.index + 1) or {} + handler.cache.i = -1 + end + + local cache = handler.cache + local cache_size = #cache + + if cache_size == 1 and cache.i == 0 then + -- there was only one solution, and the user is asking for the next + handler.cache = hints(cache[1], cursor.index + 1) + handler.cache.i = -1 + cache = handler.cache + cache_size = #cache + end + + local change = kb.isShiftDown(main_kb) and -1 or 1 + cache.i = (cache.i + change) % math.max(#cache, 1) + local next = cache[cache.i + 1] + if next then + local tail = unicode.len(cursor.data) - cursor.index + cursor:clear() + cursor:update(next) + cursor:move(-tail) + end +end + +local function key_down_handler(handler, cursor, char, code) + local c = false + local backup_cache = handler.cache + handler.cache = nil + local ctrl = kb.isControlDown(tty.keyboard()) + if ctrl and code == keys.d then + return --close + elseif code == keys.tab then + handler.cache = backup_cache + tab_handler(handler, cursor) + 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) + 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) + 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 + return c +end + +local screen_cache = {} +local function screen_reset(gpu, addr) + screen_cache[addr or gpu.getScreen() or false] = nil +end + +event.listen("screen_resized", screen_reset) + +function tty.getViewport() + local window = tty.window + local screen = tty.screen() + if window.fullscreen and screen and not screen_cache[screen] then + screen_cache[screen] = true + window.width, window.height = window.gpu.getViewport() + end + + return window.width, window.height, window.dx, window.dy, window.x, window.y +end + +function tty.setViewport(width, height, dx, dy, x, y) + local window = tty.window + dx, dy, x, y = dx or 0, dy or 0, x or 1, y or 1 + window.width, window.height, window.dx, window.dy, window.x, window.y = width, height, dx, dy, x, y +end + +function tty.gpu() + return tty.window.gpu +end + +function tty.clear() + tty.scroll(math.huge) + tty.setCursor(1, 1) +end + +function tty.isAvailable() + local gpu = tty.gpu() + return not not (gpu and gpu.getScreen()) +end + +function tty.pull(cursor, timeout, ...) + timeout = timeout or math.huge + + local width, height, dx, dy, x, y = tty.getViewport() + local out = (x<1 or x>width or y<1 or y>height) + local blink = tty.getCursorBlink() + + if cursor and out then + cursor:move(0) + cursor:scroll() + out = false + 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 + if gpu then + bgColor, bgIsPalette = gpu.getBackground() + -- it can happen during a type of race condition when a screen is removed + if not bgColor then + return nil, "interrupted" + end + + fgColor, fgIsPalette = gpu.getForeground() + char_at_cursor = gpu.get(x, y) + end + + -- get the next event + local blinked = false + local done = false + local signal + while true do + if gpu then + if not blinked and not done then + gpu.setForeground(bgColor, bgIsPalette) + gpu.setBackground(fgColor, fgIsPalette) + gpu.set(x, y, char_at_cursor) + gpu.setForeground(fgColor, fgIsPalette) + gpu.setBackground(bgColor, bgIsPalette) + blinked = true + elseif blinked and (done or blink) then + gpu.set(x, y, char_at_cursor) + blinked = false + end + end + + if done then + return table.unpack(signal, 1, signal.n) + end + + signal = table.pack(event.pull(math.min(.5, timeout), ...)) + timeout = timeout - .5 + done = signal.n > 1 or timeout < .5 + end +end + +function tty.internal.split(cursor) + local data, index = cursor.data, cursor.index + local dlen = unicode.len(data) + index = math.max(0, math.min(index, dlen)) + local tail = dlen - index + return unicode.sub(data, 1, index), tail == 0 and "" or unicode.sub(data, -tail) +end + +function tty.internal.build_vertical_reader() + local x, y = tty.getCursor() + return + { + promptx = x, + prompty = y, + index = 0, + data = "", + sy = 0, + scroll = function(_) + _.sy = _.sy + tty.scroll() + end, + move = function(_,n) + local win = tty.window + _.index = math.min(math.max(0,_.index + n), unicode.len(_.data)) + local s1, s2 = tty.internal.split(_) + s2 = unicode.sub(s2.." ", 1, 1) + local data_remaining = ("_"):rep(_.promptx - 1)..s1..s2 + win.y = _.prompty - _.sy + while true do + local wlen_remaining = unicode.wlen(data_remaining) + if wlen_remaining > win.width then + local line_cut = unicode.wtrunc(data_remaining, win.width + 1) + data_remaining = unicode.sub(data_remaining, unicode.len(line_cut) + 1) + win.y = win.y + 1 + else + win.x = wlen_remaining - unicode.wlen(s2) + 1 + break + end + end + end, + clear_tail = function(_) + local oi, width, height, dx, dy, ox, oy = _.index, tty.getViewport() + _:move(math.huge) + _:move(-1) + local ex, ey = tty.getCursor() + tty.setCursor(ox, oy) + _.index = oi + local x = oy == ey and ox or 1 + tty.gpu().fill(x + dx, ey + dy, width - x + 1, 1, " ") + end, + update = function(_, arg) + local s1, s2 = tty.internal.split(_) + if type(arg) == "string" then + _.data = s1 .. arg .. s2 + _.index = _.index + unicode.len(arg) + _:draw(arg) + else -- number + if arg < 0 then + -- backspace? ignore if at start + if _.index <= 0 then return end + _:move(arg) + s1 = unicode.sub(s1, 1, -1 + arg) + else + -- forward? ignore if at end + if _.index >= unicode.len(_.data) then return end + s2 = unicode.sub(s2, 1 + arg) + end + _:clear_tail() + _.data = s1 .. s2 + end + + -- redraw suffix + if s2 ~= "" then + local ps, px, py = _.sy, tty.getCursor() + _:draw(s2) + tty.setCursor(px, py - (_.sy - ps)) + end + end, + clear = function(_) + _:move(-math.huge) + _:draw((" "):rep(unicode.wlen(_.data))) + _:move(-math.huge) + _.index = 0 + _.data = "" + end, + draw = function(_, text) + _.sy = _.sy + tty.drawText(text) + end + } +end + +function tty.read(handler, cursor) + if not io.stdin.tty then return io.read() end + + checkArg(1, handler, "table") + checkArg(2, cursor, "table", "nil") + + handler.index = 0 + handler.touch = handler.touch or "touch_handler" + handler.drag = handler.drag or "touch_handler" + handler.clipboard = handler.clipboard or "clipboard_handler" + handler.key_down = handler.key_down or key_down_handler + + cursor = cursor or tty.internal.build_vertical_reader() + + while true do + local name, address, char, code = tty.pull(cursor) + -- we may have lost tty during the pull + if not tty.isAvailable() then + return + end + + -- we have to keep checking what kb is active in case it is switching during use + -- we could have multiple screens, each with keyboards active + local main_kb = tty.keyboard() + local main_sc = tty.screen() + if name == "interrupted" then + tty.drawText("^C\n") + return false + elseif address == main_kb or address == main_sc then + local handler_method = handler[name] + if handler_method then + if type(handler_method) == "string" then -- special hack to delay loading tty stuff + handler_method = tty[handler_method] + end + -- nil to end (close) + -- false to ignore + -- true-thy updates cursor + local c, ret = handler_method(handler, cursor, char, code) + if c == nil then + return ret + elseif c then + -- if we obtained something (c) to handle + cursor:update(c) + end + end + end + end +end + +-- cannot use tty.write = io.write because io.write invokes metatable +function tty.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_wrap +end + +function tty.getCursor() + local window = tty.window + return window.x, window.y +end + +function tty.setCursor(x, y) + local window = tty.window + window.x, window.y = x, y +end + +function tty.drawText(value, nowrap) + local gpu = tty.gpu() + if not gpu then + return + end + local sy = 0 + local cr_last, beeped + local uptime = computer.uptime + local last_sleep = uptime() + local last_index = 1 + local width, height, dx, dy = tty.getViewport() + while true do + if uptime() - last_sleep > 1 then + os.sleep(0) + last_sleep = uptime() + 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 + break + end + last_index = ei + 1 + + if segment ~= "" then + local gpu_x, gpu_y = x + dx, y + dy + local tail = "" + local wlen_needed = unicode.wlen(segment) + local wlen_remaining = width - x + 1 + if 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 + -- fake a newline + if not nowrap then + delim = "\n" + end + end + gpu.set(gpu_x, gpu_y, segment..tail) + x = x + wlen_needed + end + + if delim == "\t" then + x = ((x-1) - ((x-1) % 8)) + 9 + elseif delim == "\r" or (delim == "\n" and not cr_last) then + x = 1 + y = y + 1 + elseif delim == "\a" and not beeped then + computer.beep() + beeped = true + end + + tty.setCursor(x, y) + cr_last = delim == "\r" + end + return sy +end + +function tty.setCursorBlink(enabled) + tty.window.blink = enabled +end + +function tty.getCursorBlink() + return tty.window.blink +end + +local gpu_intercept = {} +function tty.bind(gpu) + checkArg(1, gpu, "table") + if not gpu_intercept[gpu] then + gpu_intercept[gpu] = true -- only override a gpu once + -- the gpu can change resolution before we get a chance to call events and handle screen_resized + -- unfortunately, we have to handle viewport changes by intercept + local setr, setv = gpu.setResolution, gpu.setViewport + gpu.setResolution = function(...) + screen_reset(gpu) + return setr(...) + end + gpu.setViewport = function(...) + screen_reset(gpu) + return setv(...) + end + end + local window = tty.window + window.gpu = gpu + window.keyboard = nil -- without a keyboard bound, always use the screen's main keyboard (1st) + screen_reset(gpu) + tty.getViewport() +end + +function tty.keyboard() + -- this method needs to be safe even if there is no terminal window (e.g. no gpu) + local window = tty.window + + if window.keyboard then + return window.keyboard + end + + local system_keyboard = component.isAvailable("keyboard") and component.keyboard + system_keyboard = system_keyboard and system_keyboard.address or "no_system_keyboard" + + local screen = tty.screen() + + if not screen then + -- no screen, no known keyboard, use system primary keyboard if any + return system_keyboard + end + + -- if we are using a gpu bound to the primary scren, then use the primary keyboard + if component.isAvailable("screen") and component.screen.address == screen then + window.keyboard = system_keyboard + else + -- calling getKeyboards() on the screen is costly (time) + -- changes to this design should avoid this on every key hit + + -- this is expensive (slow!) + window.keyboard = component.invoke(screen, "getKeyboards")[1] or system_keyboard + end + + return window.keyboard +end + +function tty.screen() + local gpu = tty.gpu() + if not gpu then + return nil + end + return gpu.getScreen() +end + +function tty.scroll(number) + local gpu = tty.gpu() + if not gpu then + return 0 + end + local width, height, dx, dy, x, y = tty.getViewport() + + local lines = number or (y - height) + if lines == 0 -- if zero scroll length is requested, do nothing + or not number and lines < 0 then -- do not auto scroll back up, only down + return 0 + end + + lines = math.min(lines, height) + lines = math.max(lines,-height) + + -- scroll request can be too large + local abs_lines = math.abs(lines) + local box_height = height - abs_lines + local fill_top = dy + 1 + (lines < 0 and 0 or box_height) + + gpu.copy(dx + 1, dy + 1 + math.max(0, lines), width, box_height, 0, -lines) + gpu.fill(dx + 1, fill_top, width, abs_lines, ' ') + + tty.setCursor(x, math.min(y, height)) + + return lines +end + +setmetatable(tty, +{ + __index = function(tbl, key) + setmetatable(tbl, nil) + dofile("/opt/core/full_tty.lua") + return rawget(tbl, key) + end +}) + +return tty diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/boot.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/boot.lua similarity index 83% rename from src/main/resources/assets/opencomputers/loot/openos/lib/tools/boot.lua rename to src/main/resources/assets/opencomputers/loot/openos/opt/core/boot.lua index 27c109598..7165207e1 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/boot.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/opt/core/boot.lua @@ -1,7 +1,7 @@ -- called from /init.lua local raw_loadfile = ... -_G._OSVERSION = "OpenOS 1.6.1" +_G._OSVERSION = "OpenOS 1.6.2" local component = component local computer = computer @@ -33,20 +33,21 @@ _G.boot_screen = screen local gpu = component.list("gpu", true)() local w, h if gpu and screen then - component.invoke(gpu, "bind", screen) - w, h = component.invoke(gpu, "maxResolution") - component.invoke(gpu, "setResolution", w, h) - component.invoke(gpu, "setBackground", 0x000000) - component.invoke(gpu, "setForeground", 0xFFFFFF) - component.invoke(gpu, "fill", 1, 1, w, h, " ") + gpu = component.proxy(gpu) + gpu.bind(screen) + w, h = gpu.maxResolution() + gpu.setResolution(w, h) + gpu.setBackground(0x000000) + gpu.setForeground(0xFFFFFF) + gpu.fill(1, 1, w, h, " ") end local y = 1 local function status(msg) if gpu and screen then - component.invoke(gpu, "set", 1, y, msg) + gpu.set(1, y, msg) if y == h then - component.invoke(gpu, "copy", 1, 2, w, h - 1, 0, -1) - component.invoke(gpu, "fill", 1, h, w, 1, " ") + gpu.copy(1, 2, w, h - 1, 0, -1) + gpu.fill(1, h, w, 1, " ") else y = y + 1 end @@ -100,12 +101,6 @@ do -- Inject the io modules _G.io = loadfile("/lib/io.lua")() - - --mark modules for delay loaded api - package.delayed["text"] = true - package.delayed["sh"] = true - package.delayed["transforms"] = true - package.delayed["term"] = true end status("Initializing file system...") diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/01_hw.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/01_hw.lua similarity index 87% rename from src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/01_hw.lua rename to src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/01_hw.lua index dd50a6f50..cc4712124 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/01_hw.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/01_hw.lua @@ -4,7 +4,7 @@ local text = require("text") local dcache = {} local pcache = {} -local adapter_pwd = "/lib/tools/devfs/adapters/" +local adapter_pwd = "/opt/core/devfs/adapters/" local adapter_api = {} @@ -87,25 +87,12 @@ return -- first sort the addr, primaries first, then sorted by address lexigraphically local hw_addresses = {} for addr,type in comp.list() do - table.insert(hw_addresses, {addr,type}) + local isPrim = comp.isPrimary(addr) + table.insert(hw_addresses, select(isPrim and 1 or 2, 1, {type,addr})) end - table.sort(hw_addresses, function(a, b) - local aaddr, atype = table.unpack(a) - local baddr, btype = table.unpack(b) - - if atype == btype then - local aprim = comp.isPrimary(aaddr) - local bprim = comp.isPrimary(baddr) - if aprim then return true end - if bprim then return false end - end - - return aaddr < baddr - end) - for _,pair in ipairs(hw_addresses) do - local addr, type = table.unpack(pair) + local type, addr = table.unpack(pair) if not dcache[type] then local adapter_file = adapter_pwd .. type .. ".lua" local loader = loadfile(adapter_file, "bt", _G) diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/02_utils.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/02_utils.lua similarity index 100% rename from src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/02_utils.lua rename to src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/02_utils.lua diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/adapters/computer.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/adapters/computer.lua similarity index 100% rename from src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/adapters/computer.lua rename to src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/adapters/computer.lua diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/adapters/eeprom.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/adapters/eeprom.lua similarity index 100% rename from src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/adapters/eeprom.lua rename to src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/adapters/eeprom.lua diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/adapters/filesystem.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/adapters/filesystem.lua similarity index 100% rename from src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/adapters/filesystem.lua rename to src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/adapters/filesystem.lua diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/adapters/gpu.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/adapters/gpu.lua similarity index 100% rename from src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/adapters/gpu.lua rename to src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/adapters/gpu.lua diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/adapters/internet.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/adapters/internet.lua similarity index 100% rename from src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/adapters/internet.lua rename to src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/adapters/internet.lua diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/adapters/modem.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/adapters/modem.lua similarity index 100% rename from src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/adapters/modem.lua rename to src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/adapters/modem.lua diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/adapters/screen.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/adapters/screen.lua similarity index 100% rename from src/main/resources/assets/opencomputers/loot/openos/lib/tools/devfs/adapters/screen.lua rename to src/main/resources/assets/opencomputers/loot/openos/opt/core/devfs/adapters/screen.lua diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/device_labeling.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/device_labeling.lua similarity index 100% rename from src/main/resources/assets/opencomputers/loot/openos/lib/tools/device_labeling.lua rename to src/main/resources/assets/opencomputers/loot/openos/opt/core/device_labeling.lua diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/full_ls.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/full_ls.lua similarity index 98% rename from src/main/resources/assets/opencomputers/loot/openos/lib/tools/full_ls.lua rename to src/main/resources/assets/opencomputers/loot/openos/opt/core/full_ls.lua index 8c60c7298..689ae5409 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/full_ls.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/opt/core/full_ls.lua @@ -1,6 +1,6 @@ local fs = require("filesystem") local shell = require("shell") -local term = require("term") +local tty = require("tty") local unicode = require("unicode") local dirsArg, ops = shell.parse(...) @@ -32,8 +32,8 @@ if #dirsArg == 0 then end local ec = 0 -local gpu = term.gpu() -local fOut = term.isAvailable() and io.output().tty +local gpu = tty.gpu() +local fOut = tty.isAvailable() and io.output().tty local function perr(msg) io.stderr:write(msg,"\n") ec = 2 end local function stat(names, index) local name = names[index] @@ -272,7 +272,7 @@ local function display(names) return {{color = colorize(info), name = info.name}} end else -- columns - local num_columns, items_per_column, width = 0, 0, term.getViewport() - 1 + local num_columns, items_per_column, width = 0, 0, tty.getViewport() - 1 local function real(x, y) local index = y + ((x-1) * items_per_column) return index <= #names and index or nil diff --git a/src/main/resources/assets/opencomputers/loot/openos/opt/core/full_sh.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/full_sh.lua new file mode 100644 index 000000000..8e806223c --- /dev/null +++ b/src/main/resources/assets/opencomputers/loot/openos/opt/core/full_sh.lua @@ -0,0 +1,549 @@ +local event = require("event") +local fs = require("filesystem") +local process = require("process") +local shell = require("shell") +local text = require("text") +local tx = require("transforms") +local unicode = require("unicode") + +local sh = require("sh") + +local isWordOf = sh.internal.isWordOf + +------------------------------------------------------------------------------- + +function sh.internal.handleThreadYield(result) + local action = result[2] + if action == nil or type(action) == "number" then + return table.pack(pcall(event.pull, table.unpack(result, 2, result.n))) + else + return table.pack(coroutine.yield(table.unpack(result, 2, result.n))) + end +end + +function sh.internal.buildCommandRedirects(args, thread) + local data = process.info(thread).data + local tokens, ios, handles = args, data.io, data.handles + args = {} + local from_io, to_io, mode + for i = 1, #tokens do + local token = tokens[i] + if token == "<" then + from_io = 0 + mode = "r" + else + local first_index, last_index, from_io_txt, mode_txt, to_io_txt = token:find("(%d*)(>>?)(.*)") + if mode_txt then + mode = mode_txt == ">>" and "a" or "w" + from_io = from_io_txt and tonumber(from_io_txt) or 1 + if to_io_txt ~= "" then + to_io = tonumber(to_io_txt:sub(2)) + ios[from_io] = ios[to_io] + mode = nil + end + else -- just an arg + if not mode then + table.insert(args, token) + else + local file, reason = io.open(shell.resolve(token), mode) + if not file then + io.stderr:write("could not open '" .. token .. "': " .. reason .. "\n") + os.exit(1) + end + table.insert(handles, file) + ios[from_io] = file + end + mode = nil + end + end + end + + return args +end + +function sh.internal.buildPipeChain(threads) + local prev_pipe + for i=1,#threads do + local thread = threads[i] + local data = process.info(thread).data + local pio = data.io + + local pipe + if i < #threads then + pipe = require("buffer").new("rw", sh.internal.newMemoryStream()) + pipe:setvbuf("no", 0) + -- buffer close flushes the buffer, but we have no buffer + -- also, when the buffer is closed, read and writes don't pass through + -- simply put, we don't want buffer:close + pipe.close = function(self) self.stream:close() end + pipe.stream.redirect[1] = rawget(pio, 1) + pio[1] = pipe + table.insert(data.handles, pipe) + end + + if prev_pipe then + prev_pipe.stream.redirect[0] = rawget(pio, 0) + prev_pipe.stream.next = thread + pio[0] = prev_pipe + end + + prev_pipe = pipe + end +end + +function sh.internal.glob(glob_pattern) + local segments = text.split(glob_pattern, {"/"}, true) + local hiddens = tx.foreach(segments,function(e)return e:match("^%%%.")==nil end) + local function is_visible(s,i) + return not hiddens[i] or s:match("^%.") == nil + end + + local function magical(s) + for _,glob_rule in ipairs(sh.internal.globbers) do + if (" "..s):match("[^%%]"..text.escapeMagic(glob_rule[2])) then + return true + end + end + end + + local is_abs = glob_pattern:sub(1, 1) == "/" + local root = is_abs and '' or shell.getWorkingDirectory():gsub("([^/])$","%1/") + local paths = {is_abs and "/" or ''} + local relative_separator = '' + for i,segment in ipairs(segments) do + local enclosed_pattern = string.format("^(%s)/?$", segment) + local next_paths = {} + for _,path in ipairs(paths) do + if fs.isDirectory(root..path) then + if magical(segment) then + for file in fs.list(root..path) do + if file:match(enclosed_pattern) and is_visible(file, i) then + table.insert(next_paths, path..relative_separator..file:gsub("/+$",'')) + end + end + else -- not a globbing segment, just use it raw + local plain = text.removeEscapes(segment) + local fpath = root..path..relative_separator..plain + local hit = path..relative_separator..plain:gsub("/+$",'') + if fs.exists(fpath) then + table.insert(next_paths, hit) + end + end + end + end + paths = next_paths + if not next(paths) then break end + relative_separator = "/" + end + -- if no next_paths were hit here, the ENTIRE glob value is not a path + return paths +end + +function sh.getMatchingPrograms(baseName) + local result = {} + local result_keys = {} -- cache for fast value lookup + -- TODO only matching files with .lua extension for now, might want to + -- extend this to other extensions at some point? env var? file attrs? + if not baseName or #baseName == 0 then + baseName = "^(.*)%.lua$" + else + baseName = "^(" .. text.escapeMagic(baseName) .. ".*)%.lua$" + end + for basePath in string.gmatch(os.getenv("PATH"), "[^:]+") do + for file in fs.list(shell.resolve(basePath)) do + local match = file:match(baseName) + if match and not result_keys[match] then + table.insert(result, match) + result_keys[match] = true + end + end + end + return result +end + +function sh.getMatchingFiles(partial_path) + -- name: text of the partial file name being expanded + local name = partial_path:gsub("^.*/", "") + -- here we remove the name text from the partialPrefix + local basePath = unicode.sub(partial_path, 1, -unicode.len(name) - 1) + + local resolvedPath = shell.resolve(basePath) + local result, baseName = {} + + -- note: we strip the trailing / to make it easier to navigate through + -- directories using tab completion (since entering the / will then serve + -- as the intention to go into the currently hinted one). + -- if we have a directory but no trailing slash there may be alternatives + -- on the same level, so don't look inside that directory... (cont.) + if fs.isDirectory(resolvedPath) and name == "" then + baseName = "^(.-)/?$" + else + baseName = "^(" .. text.escapeMagic(name) .. ".-)/?$" + end + + for file in fs.list(resolvedPath) do + local match = file:match(baseName) + if match then + table.insert(result, basePath .. match:gsub("(%s)", "\\%1")) + end + end + -- (cont.) but if there's only one match and it's a directory, *then* we + -- do want to add the trailing slash here. + if #result == 1 and fs.isDirectory(shell.resolve(result[1])) then + result[1] = result[1] .. "/" + end + return result +end + +function sh.internal.hintHandlerSplit(line) + -- I do not plan on having text tokenizer parse error on + -- trailiing \ in case of future support for multiple line + -- input. But, there are also no hints for it + if line:match("\\$") then return nil end + + local splits, simple = text.internal.tokenize(line,{show_escapes=true}) + if not splits then -- parse error, e.g. unclosed quotes + return nil -- no split, no hints + end + + local num_splits = #splits + + -- search for last statement delimiters + local last_close = 0 + for index = num_splits, 1, -1 do + local word = splits[index] + if isWordOf(word, {";","&&","||","|"}) then + last_close = index + break + end + end + + -- if the very last word of the line is a delimiter + -- consider this a fresh new, empty line + -- this captures edge cases with empty input as well (i.e. no splits) + if last_close == num_splits then + return nil -- no hints on empty command + end + + local last_word = splits[num_splits] + local normal = text.internal.normalize({last_word})[1] + + -- if there is white space following the words + -- and we have at least one word following the last delimiter + -- then in all cases we are looking for ANY arg + if unicode.sub(line, -unicode.len(normal)) ~= normal then + return line, nil, "" + end + + local prefix = unicode.sub(line, 1, -unicode.len(normal) - 1) + + -- renormlizing the string will create 'printed' quality text + normal = text.internal.normalize(text.internal.tokenize(normal), true)[1] + + -- one word: cmd + -- many: arg + if last_close == num_splits - 1 then + return prefix, normal, nil + else + return prefix, nil, normal + end +end + +function sh.internal.hintHandlerImpl(full_line, cursor) + -- line: text preceding the cursor: we want to hint this part (expand it) + local line = unicode.sub(full_line, 1, cursor - 1) + -- suffix: text following the cursor (if any, else empty string) to append to the hints + local suffix = unicode.sub(full_line, cursor) + + -- hintHandlerSplit helps make the hints work even after delimiters such as ; + -- it also catches parse errors such as unclosed quotes + -- prev: not needed for this hint + -- cmd: the command needing hint + -- arg: the argument needing hint + local prev, cmd, arg = sh.internal.hintHandlerSplit(line) + + -- also, if there is no text to hint, there are no hints + if not prev then -- no hints e.g. unclosed quote, e.g. no text + return {} + end + local result + + local searchInPath = cmd and not cmd:find("/") + if searchInPath then + result = sh.getMatchingPrograms(cmd) + else + -- special arg issue, after equal sign + if arg then + local equal_index = arg:find("=[^=]*$") + if equal_index then + prev = prev .. unicode.sub(arg, 1, equal_index) + arg = unicode.sub(arg, equal_index + 1) + end + end + result = sh.getMatchingFiles(cmd or arg) + end + + -- in very special cases, the suffix should include a blank space to indicate to the user that the hint is discrete + local resultSuffix = suffix + if #result > 0 and unicode.sub(result[1], -1) ~= "/" and + not suffix:sub(1,1):find('%s') and + #result == 1 or searchInPath then + resultSuffix = " " .. resultSuffix + end + + table.sort(result) + for i = 1, #result do + -- the hints define the whole line of text + result[i] = prev .. result[i] .. resultSuffix + end + return result +end + +-- verifies that no pipes are doubled up nor at the start nor end of words +function sh.internal.hasValidPiping(words, pipes) + checkArg(1, words, "table") + checkArg(2, pipes, "table", "nil") + + if #words == 0 then + return true + end + + local semi_split = tx.find(text.syntax, {";"}) -- all symbols before ; in syntax CAN be repeated + pipes = pipes or tx.sub(text.syntax, semi_split + 1) + + local state = "" -- cannot start on a pipe + + for w=1,#words do + local word = words[w] + for p=1,#word do + local part = word[p] + if part.qr then + state = nil + elseif part.txt == "" then + state = nil -- not sure how this is possible (empty part without quotes?) + elseif #text.split(part.txt, pipes, true) == 0 then + local prev = state + state = part.txt + if prev then -- cannot have two pipes in a row + word = nil + break + end + else + state = nil + end + end + if not word then -- bad pipe + break + end + end + + if state then + return false, "parse error near " .. state + else + return true + end +end + +function sh.internal.boolean_executor(chains, predicator) + local function not_gate(result) + return sh.internal.command_passed(result) and 1 or 0 + end + + local last = true + local boolean_stage = 1 + local negation_stage = 2 + local command_stage = 0 + local stage = negation_stage + local skip = false + + for ci=1,#chains do + local next = chains[ci] + local single = #next == 1 and #next[1] == 1 and not next[1][1].qr and next[1][1].txt + + if single == "||" then + if stage ~= command_stage or #chains == 0 then + return nil, "syntax error near unexpected token '"..single.."'" + end + if sh.internal.command_passed(last) then + skip = true + end + stage = boolean_stage + elseif single == "&&" then + if stage ~= command_stage or #chains == 0 then + return nil, "syntax error near unexpected token '"..single.."'" + end + if not sh.internal.command_passed(last) then + skip = true + end + stage = boolean_stage + elseif not skip then + local chomped = #next + local negate = sh.internal.remove_negation(next) + chomped = chomped ~= #next + if negate then + local prev = predicator + predicator = function(n,i) + local result = not_gate(prev(n,i)) + predicator = prev + return result + end + end + if chomped then + stage = negation_stage + end + if #next > 0 then + last = predicator(next,ci) + stage = command_stage + end + else + skip = false + stage = command_stage + end + end + + if stage == negation_stage then + last = not_gate(last) + end + + return last +end + +function sh.internal.splitStatements(words, semicolon) + checkArg(1, words, "table") + checkArg(2, semicolon, "string", "nil") + semicolon = semicolon or ";" + + return tx.partition(words, function(g, i, t) + if isWordOf(g, {semicolon}) then + return i, i + end + end, true) +end + +function sh.internal.splitChains(s,pc) + checkArg(1, s, "table") + checkArg(2, pc, "string", "nil") + pc = pc or "|" + return tx.partition(s, function(w) + -- each word has multiple parts due to quotes + if isWordOf(w, {pc}) then + return true + end + end, true) -- drop |s +end + +function sh.internal.groupChains(s) + checkArg(1,s,"table") + return tx.partition(s,function(w)return isWordOf(w,{"&&","||"})end) +end + +function sh.internal.remove_negation(chain) + if isWordOf(chain[1], {"!"}) then + table.remove(chain, 1) + return true and not sh.internal.remove_negation(chain) + end + return false +end + +function sh.internal.newMemoryStream() + local memoryStream = {} + + function memoryStream:close() + self.closed = true + self.redirect = {} + end + + function memoryStream:seek() + return nil, "bad file descriptor" + end + + function memoryStream:read(n) + if self.closed then + return nil -- eof + end + if self.redirect[0] then + -- popen could be using this code path + -- if that is the case, it is important to leave stream.buffer alone + return self.redirect[0]:read(n) + elseif self.buffer == "" then + coroutine.yield() + end + local result = string.sub(self.buffer, 1, n) + self.buffer = string.sub(self.buffer, n + 1) + return result + end + + function memoryStream:write(value) + if not self.redirect[1] and self.closed then + -- if next is dead, ignore all writes + if coroutine.status(self.next) ~= "dead" then + io.stderr:write("attempt to use a closed stream\n") + os.exit(1) + end + elseif self.redirect[1] then + return self.redirect[1]:write(value) + elseif not self.closed then + self.buffer = self.buffer .. value + local result = table.pack(coroutine.resume(self.next)) + if coroutine.status(self.next) == "dead" then + self:close() + end + if not result[1] then + io.stderr:write(tostring(result[2]) .. "\n") + os.exit(1) + end + return self + end + os.exit(0) -- abort the current process: SIGPIPE + end + + local stream = {closed = false, buffer = "", + redirect = {}, result = {}} + local metatable = {__index = memoryStream, + __metatable = "memorystream"} + return setmetatable(stream, metatable) +end + +function sh.internal.execute_complex(statements, eargs, env) + for si=1,#statements do local s = statements[si] + local chains = sh.internal.groupChains(s) + local last_code = sh.internal.boolean_executor(chains, function(chain, chain_index) + local pipe_parts = sh.internal.splitChains(chain) + local next_args = chain_index == #chains and si == #statements and eargs or {} + return sh.internal.executePipes(pipe_parts, next_args, env) + end) + sh.internal.ec.last = sh.internal.command_result_as_code(last_code) + end + return true +end + + +function sh.internal.parse_sub(input) + -- cannot use gsub here becuase it is a [C] call, and io.popen needs to yield at times + local packed = {} + -- not using for i... because i can skip ahead + local i, len = 1, #input + + while i < len do + + local fi, si, capture = input:find("`([^`]*)`", i) + + if not fi then + table.insert(packed, input:sub(i)) + break + end + + local sub = io.popen(capture) + local result = input:sub(i, fi - 1) .. sub:read("*a") + sub:close() + -- all whitespace is replaced by single spaces + -- we requote the result because tokenize will respect this as text + table.insert(packed, (text.trim(result):gsub("%s+"," "))) + + i = si+1 + end + + return table.concat(packed) +end + + diff --git a/src/main/resources/assets/opencomputers/loot/openos/opt/core/full_text.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/full_text.lua new file mode 100644 index 000000000..6fdca240a --- /dev/null +++ b/src/main/resources/assets/opencomputers/loot/openos/opt/core/full_text.lua @@ -0,0 +1,246 @@ +local text = require("text") +local tx = require("transforms") +local unicode = require("unicode") + +-- separate string value into an array of words delimited by whitespace +-- groups by quotes +-- options is a table used for internal undocumented purposes +function text.tokenize(value, options) + checkArg(1, value, "string") + checkArg(2, options, "table", "nil") + options = options or {} + + local tokens, reason = text.internal.tokenize(value, options) + + if type(tokens) ~= "table" then + return nil, reason + end + + if options.doNotNormalize then + return tokens + end + + return text.internal.normalize(tokens) +end + +------------------------------------------------------------------------------- +-- like tokenize, but does not drop any text such as whitespace +-- splits input into an array for sub strings delimited by delimiters +-- delimiters are included in the result if not dropDelims +function text.split(input, delimiters, dropDelims, di) + checkArg(1, input, "string") + checkArg(2, delimiters, "table") + checkArg(3, dropDelims, "boolean", "nil") + checkArg(4, di, "number", "nil") + + if #input == 0 then return {} end + di = di or 1 + local result = {input} + if di > #delimiters then return result end + + local function add(part, index, r, s, e) + local sub = part:sub(s,e) + if #sub == 0 then return index end + local subs = r and text.split(sub,delimiters,dropDelims,r) or {sub} + for i=1,#subs do + table.insert(result, index+i-1, subs[i]) + end + return index+#subs + end + + local i,d=1,delimiters[di] + while true do + local next = table.remove(result,i) + if not next then break end + local si,ei = next:find(d) + if si and ei and ei~=0 then -- delim found + i=add(next, i, di+1, 1, si-1) + i=dropDelims and i or add(next, i, false, si, ei) + i=add(next, i, di, ei+1) + else + i=add(next, i, di+1, 1, #next) + end + end + + return result +end + +----------------------------------------------------------------------------- + +-- splits each word into words at delimiters +-- delimiters are kept as their own words +-- quoted word parts are not split +function text.internal.splitWords(words, delimiters) + checkArg(1,words,"table") + checkArg(2,delimiters,"table") + + local split_words = {} + local next_word + local function add_part(part) + if next_word then + split_words[#split_words+1] = {} + end + table.insert(split_words[#split_words], part) + next_word = false + end + for wi=1,#words do local word = words[wi] + next_word = true + for pi=1,#word do local part = word[pi] + local qr = part.qr + if qr then + add_part(part) + else + local part_text_splits = text.split(part.txt, delimiters) + tx.foreach(part_text_splits, function(sub_txt, spi) + local delim = #text.split(sub_txt, delimiters, true) == 0 + next_word = next_word or delim + add_part({txt=sub_txt,qr=qr}) + next_word = delim + end) + end + end + end + + return split_words +end + +function text.internal.normalize(words, omitQuotes) + checkArg(1, words, "table") + checkArg(2, omitQuotes, "boolean", "nil") + local norms = {} + for _,word in ipairs(words) do + local norm = {} + for _,part in ipairs(word) do + norm = tx.concat(norm, not omitQuotes and part.qr and {part.qr[1], part.txt, part.qr[2]} or {part.txt}) + end + norms[#norms+1]=table.concat(norm) + end + return norms +end + +function text.internal.stream_base(binary) + return + { + binary = binary, + plen = binary and string.len or unicode.len, + psub = binary and string.sub or unicode.sub, + seek = function (handle, whence, to) + if not handle.txt then + return nil, "bad file descriptor" + end + to = to or 0 + local offset = handle:indexbytes() + if whence == "cur" then + offset = offset + to + elseif whence == "set" then + offset = to + elseif whence == "end" then + offset = handle.len + to + end + offset = math.max(0, math.min(offset, handle.len)) + handle:byteindex(offset) + return offset + end, + indexbytes = function (handle) + return handle.psub(handle.txt, 1, handle.index):len() + end, + byteindex = function (handle, offset) + local sub = string.sub(handle.txt, 1, offset) + handle.index = handle.plen(sub) + end, + } +end + +function text.internal.reader(txt, mode) + checkArg(1, txt, "string") + local reader = setmetatable( + { + txt = txt, + len = string.len(txt), + index = 0, + read = function(_, n) + checkArg(1, n, "number") + if not _.txt then + return nil, "bad file descriptor" + end + if _.index >= _.plen(_.txt) then + return nil + end + local next = _.psub(_.txt, _.index + 1, _.index + n) + _.index = _.index + _.plen(next) + return next + end, + close = function(_) + if not _.txt then + return nil, "bad file descriptor" + end + _.txt = nil + return true + end, + }, {__index=text.internal.stream_base(mode:match("b"))}) + + return require("buffer").new("r", reader) +end + +function text.internal.writer(ostream, mode, append_txt) + if type(ostream) == "table" then + local mt = getmetatable(ostream) or {} + checkArg(1, mt.__call, "function") + end + checkArg(1, ostream, "function", "table") + checkArg(2, append_txt, "string", "nil") + local writer = setmetatable( + { + txt = "", + index = 0, -- last location of write + len = 0, + write = function(_, ...) + if not _.txt then + return nil, "bad file descriptor" + end + local pre = _.psub(_.txt, 1, _.index) + local vs = {} + local pos = _.psub(_.txt, _.index + 1) + for i,v in ipairs({...}) do + table.insert(vs, v) + end + vs = table.concat(vs) + _.index = _.index + _.plen(vs) + _.txt = pre .. vs .. pos + _.len = string.len(_.txt) + return true + end, + close = function(_) + if not _.txt then + return nil, "bad file descriptor" + end + ostream((append_txt or "") .. _.txt) + _.txt = nil + return true + end, + }, {__index=text.internal.stream_base(mode:match("b"))}) + + return require("buffer").new("w", writer) +end + +function text.detab(value, tabWidth) + checkArg(1, value, "string") + checkArg(2, tabWidth, "number", "nil") + tabWidth = tabWidth or 8 + local function rep(match) + local spaces = tabWidth - match:len() % tabWidth + return match .. string.rep(" ", spaces) + end + local result = value:gsub("([^\n]-)\t", rep) -- truncate results + return result +end + +function text.padLeft(value, length) + checkArg(1, value, "string", "nil") + checkArg(2, length, "number") + if not value or unicode.wlen(value) == 0 then + return string.rep(" ", length) + else + return string.rep(" ", length - unicode.wlen(value)) .. value + end +end diff --git a/src/main/resources/assets/opencomputers/loot/openos/opt/core/full_transforms.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/full_transforms.lua new file mode 100644 index 000000000..214090c27 --- /dev/null +++ b/src/main/resources/assets/opencomputers/loot/openos/opt/core/full_transforms.lua @@ -0,0 +1,122 @@ +local lib = require("transforms") + +local adjust=lib.internal.range_adjust +local view=lib.internal.table_view + +-- works like string.sub but on elements of an indexed table +function lib.sub(tbl,f,l) + checkArg(1,tbl,'table') + local r,s={},#tbl + f,l=adjust(f,l,s) + l=math.min(l,s) + for i=math.max(f,1),l do + r[#r+1]=tbl[i] + end + return r +end + +-- if value was made by lib.sub then find can find from whence +function lib.find(tbl, sub, first, last) + checkArg(1, tbl, 'table') + checkArg(2, sub, 'table') + local sub_len = #sub + return lib.first(tbl, function(element, index, projected_table) + for n=0,sub_len-1 do + if projected_table[n + index] ~= sub[n + 1] then return nil end + end + return 1, sub_len + end, first, last) +end + +-- Returns a list of subsets of tbl where partitioner acts as a delimiter. +function lib.partition(tbl,partitioner,dropEnds,f,l) + checkArg(1,tbl,'table') + checkArg(2,partitioner,'function','table') + checkArg(3,dropEnds,'boolean','nil') + if type(partitioner)=='table'then + return lib.partition(tbl,function(e,i,tbl) + return lib.first(tbl,partitioner,i) + end,dropEnds,f,l) + end + local s=#tbl + f,l=adjust(f,l,s) + local cut=view(tbl,f,l) + local result={} + local need=true + local exp=function()if need then result[#result+1]={}need=false end end + local i=f + while i<=l do + local e=cut[i] + local ds,de=partitioner(e,i,cut) + -- true==partition here + if ds==true then ds,de=i,i + elseif ds==false then ds,de=nil,nil end + if ds~=nil then + ds,de=adjust(ds,de,l) + ds=ds>=i and ds--no more + end + if not ds then -- false or nil + exp() + table.insert(result[#result],e) + else + local sub=lib.sub(cut,i,not dropEnds and de or (ds-1)) + if #sub>0 then + exp() + result[#result+math.min(#result[#result],1)]=sub + end + -- ensure i moves forward + local ensured=math.max(math.max(de or ds,ds),i) + if de and ds and de= total_wlen then + cursor:move(math.huge) + else + local chars_to_move = unicode.wtrunc(cursor.data, char_width_to_move + 1) + cursor:move(unicode.len(chars_to_move)) + end + -- fake white space can make the index off, redo adjustment for alignment + x2, y2, d = win.x, win.y, win.width + char_width_to_move = ((gy*d+gx)-(y2*d+x2)) + if (char_width_to_move < 0) then + -- using char_width_to_move as a type of index is wrong, but large enough and helps to speed this up + local up_to_cursor = unicode.sub(cursor.data, cursor.index+char_width_to_move, cursor.index) + local full_wlen = unicode.wlen(up_to_cursor) + local without_tail = unicode.wtrunc(up_to_cursor, full_wlen + char_width_to_move + 1) + local chars_cut = unicode.len(up_to_cursor) - unicode.len(without_tail) + cursor:move(-chars_cut) + end + return false -- no further cursor update +end + +function tty.clipboard_handler(handler, cursor, char, code) + handler.cache = nil + local first_line, end_index = char:find("\13?\10") + if first_line then + local after = char:sub(end_index + 1) + if after ~= "" then + -- todo look at postponing the text on cursor + require("computer").pushSignal("key_down", tty.keyboard(), 13, 28) + require("computer").pushSignal("clipboard", tty.keyboard(), after) + end + char = char:sub(1, first_line - 1) + end + return char +end + diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/install_basics.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/install_basics.lua similarity index 98% rename from src/main/resources/assets/opencomputers/loot/openos/lib/tools/install_basics.lua rename to src/main/resources/assets/opencomputers/loot/openos/opt/core/install_basics.lua index 9f17b6cab..2dd26910f 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/install_basics.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/opt/core/install_basics.lua @@ -156,7 +156,7 @@ end local source = options.sources[1] local target = options.targets[1] -local utils_path = package.searchpath("tools/install_utils", package.path) +local utils_path = "/opt/core/install_utils.lua" if #options.sources ~= 1 or #options.targets ~= 1 then source, target = loadfile(utils_path, "bt", _G)('select', options) diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/install_utils.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/install_utils.lua similarity index 99% rename from src/main/resources/assets/opencomputers/loot/openos/lib/tools/install_utils.lua rename to src/main/resources/assets/opencomputers/loot/openos/opt/core/install_utils.lua index d70a7b23f..60d39b004 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/install_utils.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/opt/core/install_utils.lua @@ -31,6 +31,7 @@ local function select_prompt(devs, prompt) choice = devs[number] else io.write("Invalid input, please try again: ") + os.sleep(0) end end end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/lua_shell.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/lua_shell.lua similarity index 83% rename from src/main/resources/assets/opencomputers/loot/openos/lib/tools/lua_shell.lua rename to src/main/resources/assets/opencomputers/loot/openos/opt/core/lua_shell.lua index d36821b8a..bc46391dd 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/lua_shell.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/opt/core/lua_shell.lua @@ -1,7 +1,7 @@ local package = require("package") -local term = require("term") +local tty = require("tty") -local gpu = term.gpu() +local gpu = tty.gpu() local function optrequire(...) local success, module = pcall(require, ...) @@ -34,8 +34,6 @@ env = setmetatable({}, { }) env._PROMPT = tostring(env._PROMPT or "lua> ") -local history = {} - local function findTable(t, path) if type(t) ~= "table" then return nil end if not path or #path == 0 then return t end @@ -72,7 +70,8 @@ local function findKeys(t, r, prefix, name) end end -local function hint(line, index) +local read_handler = {} +function read_handler.hint(line, index) line = (line or "") local tail = line:sub(index) line = line:sub(1, index - 1) @@ -92,18 +91,18 @@ local function hint(line, index) end gpu.setForeground(0xFFFFFF) -term.write(_VERSION .. " Copyright (C) 1994-2015 Lua.org, PUC-Rio\n") +tty.write(_VERSION .. " Copyright (C) 1994-2015 Lua.org, PUC-Rio\n") gpu.setForeground(0xFFFF00) -term.write("Enter a statement and hit enter to evaluate it.\n") -term.write("Prefix an expression with '=' to show its value.\n") -term.write("Press Ctrl+D to exit the interpreter.\n") +tty.write("Enter a statement and hit enter to evaluate it.\n") +tty.write("Prefix an expression with '=' to show its value.\n") +tty.write("Press Ctrl+D to exit the interpreter.\n") gpu.setForeground(0xFFFFFF) -while term.isAvailable() do +while tty.isAvailable() do local foreground = gpu.setForeground(0x00FF00) - term.write(env._PROMPT) + tty.write(env._PROMPT) gpu.setForeground(foreground) - local command = term.read(history, nil, hint) + local command = tty.read(read_handler) if not command then -- eof return end @@ -122,10 +121,10 @@ while term.isAvailable() do io.stderr:write(tostring(result[2]) .. "\n") else for i = 2, result.n do - term.write(require("serialization").serialize(result[i], true) .. "\t", true) + tty.write(require("serialization").serialize(result[i], true) .. "\t", true) end - if term.getCursor() > 1 then - term.write("\n") + if tty.getCursor() > 1 then + tty.write("\n") end end else diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/ro_wrapper.lua b/src/main/resources/assets/opencomputers/loot/openos/opt/core/ro_wrapper.lua similarity index 100% rename from src/main/resources/assets/opencomputers/loot/openos/lib/tools/ro_wrapper.lua rename to src/main/resources/assets/opencomputers/loot/openos/opt/core/ro_wrapper.lua