From 943f3c2339091ea95ccc7bc30cd953e033453a4c Mon Sep 17 00:00:00 2001 From: payonel Date: Fri, 31 Aug 2018 18:09:28 -0700 Subject: [PATCH 01/12] make cursor echo safe to call even without gpu ready --- .../assets/opencomputers/loot/openos/lib/core/cursor.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/cursor.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/cursor.lua index 6a3b783c4..e7658e382 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/cursor.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/cursor.lua @@ -68,9 +68,11 @@ end function core_cursor.vertical:echo(arg, num) local win = tty.window + local gpu = win.gpu + if not gpu then return end win.nowrap = self.nowrap if arg == "" then -- special scroll request - local gpu, width, x, y = win.gpu, win.width, win.x, win.y + local width, x, y = win.width, win.x, win.y if x > width then win.x = ((x - 1) % width) + 1 win.y = y + math.floor(x / width) @@ -100,7 +102,6 @@ function core_cursor.vertical:echo(arg, num) win.x, win.y = x, y arg = "" elseif not arg or arg == true then -- blink - local gpu = win.gpu local char = self.char_at_cursor if (arg == nil and not char) or (arg and not self.blinked) then char = char or self:echo("") --scroll and get char @@ -113,7 +114,7 @@ function core_cursor.vertical:echo(arg, num) gpu.setBackground(char[5] or char[3], not not char[5]) end io.write("\0277", "\27[7m", char[1], "\0278") - elseif (arg and self.blinked) or arg == false then + elseif (arg and self.blinked) or (arg == false and char) then self.blinked = false gpu.set(win.x + win.dx, win.y + win.dy, char[1]) if not arg then From ab10c8738faf4a6098cf6a6fc47bee01ea16ce07 Mon Sep 17 00:00:00 2001 From: payonel Date: Tue, 20 Mar 2018 19:30:00 -0700 Subject: [PATCH 02/12] log rc errors to /tmp/event.log closes #2787 --- .../opencomputers/loot/openos/bin/rc.lua | 25 +++++++++++++------ .../loot/openos/lib/core/boot.lua | 7 +++--- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/rc.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/rc.lua index ebb35364f..0487b3618 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/rc.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/rc.lua @@ -38,9 +38,11 @@ local function load(name, args) if result then rc.loaded[name] = env return env + else + return nil, string.format("%s failed to start: %s", fileName, reason) end end - return nil, reason + return nil, string.format("%s failed to load: %s", fileName, reason) end function rc.unload(name) @@ -116,24 +118,33 @@ local function allRunCommand(cmd, ...) return results end -if select("#", ...) == 0 then - local results,reason = allRunCommand("start") +local stream = io.stderr +local write = stream.write + +if select("#", ...) == 0 then + -- if called during boot, pipe errors to onError handler + if _G.runlevel == "S" then + write = function(_, msg) + require("event").onError(msg) + end + end + + local results, reason = allRunCommand("start") if not results then local msg = "rc failed to start:"..tostring(reason) - io.stderr:write(msg) - require("event").onError(msg) + write(stream, msg, "\n") return end for _, result in pairs(results) do local ok, reason = table.unpack(result) if not ok then - io.stderr:write(reason, "\n") + write(stream, reason, "\n") end end else local result, reason = runCommand(...) if not result then - io.stderr:write(reason, "\n") + write(stream, reason, "\n") return 1 end end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua index 2b0f18405..8013be6fe 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua @@ -8,10 +8,11 @@ local computer = computer local unicode = unicode -- Runlevel information. -local runlevel, shutdown = "S", computer.shutdown -computer.runlevel = function() return runlevel end +_G.runlevel = "S" +local shutdown = computer.shutdown +computer.runlevel = function() return _G.runlevel end computer.shutdown = function(reboot) - runlevel = reboot and 6 or 0 + _G.runlevel = reboot and 6 or 0 if os.sleep then computer.pushSignal("shutdown") os.sleep(0.1) -- Allow shutdown processing. From 541e765d7bb709c6d18911c196fc29260aa551fd Mon Sep 17 00:00:00 2001 From: payonel Date: Sat, 24 Mar 2018 21:38:05 -0700 Subject: [PATCH 03/12] need to close process after error handling --- .../resources/assets/opencomputers/loot/openos/lib/process.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 42a669111..f0b157e7a 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/process.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/process.lua @@ -73,11 +73,11 @@ function process.load(path, env, init, name) return 128 -- syserr end, ...) } - process.internal.close(thread, result) --result[1] is false if the exception handler also crashed if not result[1] and type(result[2]) ~= "number" then require("event").onError(string.format("process library exception handler crashed: %s", tostring(result[2]))) end + process.internal.close(thread, result) return select(2, table.unpack(result)) end, true) local new_proc = From ff8323c447948e697f01f3e70c92388b08058c41 Mon Sep 17 00:00:00 2001 From: payonel Date: Thu, 29 Mar 2018 13:45:26 -0700 Subject: [PATCH 04/12] bit32 should always be `require()`, regardless of arch too bad we weren't clearing it from _G on boot, else we would have seen this bug i'm choosing to NOT clear in from _G on boot .. for now .. --- .../resources/assets/opencomputers/loot/openos/lib/uuid.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/uuid.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/uuid.lua index d07f7f610..0007e60e4 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/uuid.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/uuid.lua @@ -1,3 +1,4 @@ +local bit32 = require("bit32") local uuid = {} function uuid.next() @@ -11,7 +12,7 @@ function uuid.next() if result:len() > 0 then result = result .. "-" end - for i = 1,set do + for _ = 1,set do local byte = math.random(0, 255) if pos == 6 then byte = bit32.bor(bit32.band(byte, 0x0F), 0x40) From ec0d879bfbf1548265957c79ca6db0fcd071eee3 Mon Sep 17 00:00:00 2001 From: payonel Date: Sat, 31 Mar 2018 17:15:41 -0700 Subject: [PATCH 05/12] new cursor lib, some vt100 fixes for windows the cursor lib will probably become an official api, but not yet, still in beta an application can define its own cursor, can handle the following methods Here is a rough draft of the api, when it is official there will be an ocdoc article covering this echo Echo does not change the input buffer, does not move the relative cursor position to the input, in is only used for updating the UI. Echo will receive the following "": scroll if needed nil: initialize blink if needed boolean: toggle blink if true, force blink off if false string: write string to output stream keyboard.keys.right and keyboard.keys.left: move the visual cursor left or right. scroll if necessary move number: Intended to move the logical cursor relative to its current input. Should call echo for visual movement update string: add (prepend, insert, or append) text to the input buffer number: remove (backspace, delete) text from the input buffer should call move if the cursor moves due to the text changes handle (name, char, code): called in response to event signals when waiting for input. Typically this is used to handle key_down, touch, drag, and clipboard return nil if the cursor is closing return false if the input is interrupted return true if the input is not done return as the result of the input when completed --- .../opencomputers/loot/openos/bin/ps.lua | 3 +- .../loot/openos/lib/core/cursor.lua | 181 ++++++++ .../loot/openos/lib/core/full_cursor.lua | 76 ++++ .../loot/openos/lib/core/full_tty.lua | 87 ---- .../loot/openos/lib/core/full_vt.lua | 13 +- .../opencomputers/loot/openos/lib/term.lua | 241 +++++----- .../opencomputers/loot/openos/lib/tty.lua | 414 +++++------------- .../opencomputers/loot/openos/lib/vt100.lua | 24 +- 8 files changed, 502 insertions(+), 537 deletions(-) create mode 100644 src/main/resources/assets/opencomputers/loot/openos/lib/core/cursor.lua create mode 100644 src/main/resources/assets/opencomputers/loot/openos/lib/core/full_cursor.lua delete mode 100644 src/main/resources/assets/opencomputers/loot/openos/lib/core/full_tty.lua diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/ps.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/ps.lua index 9a31a8abd..2c960273b 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/ps.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/ps.lua @@ -68,8 +68,7 @@ local cols = end}, {"HANDLES", function(_, p) local count = 0 - for stream,closure in pairs(p.data.handles) do - cprint(string.format("%s %s", stream, closure)) + for _,closure in pairs(p.data.handles) do if closure then count = count + 1 end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/cursor.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/cursor.lua new file mode 100644 index 000000000..4ffd5f613 --- /dev/null +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/cursor.lua @@ -0,0 +1,181 @@ +local unicode = require("unicode") +local kb = require("keyboard") +local keys = kb.keys + +local core_cursor = {} + +local super = {} +function super:move(n) + local s = + n < 0 and self.index > 0 and -1 or + n > 0 and self.index < self.len and 1 or + 0 + if s == 0 then return end + local echo_cmd = (s > 0 and keys.right or keys.left) + self.index = self.index + s + self:echo(echo_cmd) + return self:move(n - s) +end + +-- back is used when arg comes after the cursor +function super:update(arg, back) + local s1 = unicode.sub(self.data, 1, self.index) + local s2 = unicode.sub(self.data, self.index + 1) + if type(arg) == "string" then + self.data = s1 .. arg + self.index = self.index + unicode.len(arg) + self:echo(arg) + self:move(back or 0) + else -- number + local has_tail = arg < 0 or #s2 > 0 + if arg < 0 then + -- backspace? ignore if at start + if self.index <= 0 then return end + self:move(arg) + s1 = unicode.sub(s1, 1, -1 + arg) + else + -- forward? ignore if at end + if self.index >= self.len then return end + s2 = unicode.sub(s2, 1 + arg) + end + self.data = s1 + if has_tail then + self:echo(self.clear) + end + end + self.len = unicode.len(self.data) -- recompute len + if #s2 > 0 then + self:update(s2, -unicode.len(s2)) + end +end + +function super:echo(arg) + if arg == "" then -- special scroll request + local gpu, width, x, y = + self.window.gpu, + self.window.width, + self.window.x, + self.window.y + if x > width then + self.window.x = ((x - 1) % width) + 1 + self.window.y = y + math.floor(x / width) + self.output:write("") + x, y = self.window.x, self.window.y + end + return x > 0 and y > 0 and y <= self.window.height and gpu and + select(2, pcall(gpu.get, x + self.window.dx, y + self.window.dy)) + elseif arg == keys.enter then + arg = "\n" + elseif arg == keys.left then + local x = self.window.x - unicode.wlen(unicode.sub(self.data, self.index + 1, self.index + 1)) + local y = self.window.y + if x < 1 then + x = x + self.window.width - #(self.tails[self.window.dy + y - self.sy - 1] or "") + y = y - 1 + end + self.window.x, self.window.y = x, y + arg = "" + elseif arg == keys.right then + local x = self.window.x + unicode.wlen(unicode.sub(self.data, self.index, self.index)) + local y = self.window.y + local width = self.window.width - #(self.tails[self.window.dy + y - self.sy] or "") + if x > width then + x = x - width + y = y + 1 + end + self.window.x, self.window.y = x, y + arg = "" + elseif type(arg) == "boolean" or arg == nil then -- blink + local char = self.char_at_cursor + if (arg == nil and not char) or (arg and not self.blinked) then + char = char or self:echo("") --scroll and get char + if not char then return false end + self.blinked = true + arg = "\0277\27[7m" .. char .. "\0278" + elseif (arg and self.blinked) or arg == false then + self.blinked = false + arg, char = "\0277" .. char .. "\0278", arg and char + end + self.char_at_cursor = char + end + return not arg or self.output:write(arg) +end + +function super:handle(name, char, code) + if name == "clipboard" then + return core_cursor.clipboard(self, char, code) + elseif name == "touch" or name == "drag" then + return core_cursor.touch(self, char, code) + elseif name == "interrupted" then + self:echo("^C\n") + return false, name + elseif name ~= "key_down" then + return true -- handled + end + + local data = self.data + local value = false + local backup_cache = self.cache + self.cache = nil + local ctrl = kb.isControlDown() + if ctrl and code == keys.d then + return --nil:close + elseif code == keys.tab then + self.cache = backup_cache + core_cursor.tab(self) + elseif code == keys.enter or code == keys.numpadenter then + self:move(self.len) + self:echo(keys.enter) + if data:find("%S") and data ~= self[1] then + table.insert(self, 1, data) + self[(tonumber(os.getenv("HISTSIZE")) or 10)+1] = nil + end + self[0] = nil + return data .. "\n" + elseif code == keys.up or code == keys.down then + local ni = self.hindex + (code == keys.up and 1 or -1) + if ni >= 0 and ni <= #self then + self[self.hindex] = data + self.hindex = ni + self:move(self.len) + self:update(-self.len) + self:update(self[ni]) + end + elseif code == keys.left or code == keys.back or code == keys.w and ctrl then + value = ctrl and ((unicode.sub(data, 1, self.index):find("%s[^%s]+%s*$") or 0) - self.index) or -1 + if code == keys.left then + self:move(value) + value = false -- don't also update (cut) + end + elseif code == keys.right then + self:move(ctrl and ((data:find("%s[^%s]", self.index + 1) or self.len) - self.index) or 1) + elseif code == keys.home then self:move(-self.len) + elseif code == keys["end"] then self:move( self.len) + elseif code == keys.delete then value = 1 + elseif char >= 32 then value = unicode.char(char) + else self.cache = backup_cache -- ignored chars shouldn't clear hint cache + end + if value then + self:update(value) + end + return true +end + +function core_cursor.new(base, window, output) + local result = base or {} + result.tails = {} + result.data = "" + result.index = 0 + result.len = 0 + result.sy = 0 + result.hindex = 0 + result.window = window + result.output = output + result.clear = "\27[J" -- echo'd to clear the input text in the tty + result.super = super + return setmetatable(result, { __index = super }) +end + +require("package").delay(core_cursor, "/lib/core/full_cursor.lua") + +return core_cursor diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_cursor.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_cursor.lua new file mode 100644 index 000000000..55048e5b6 --- /dev/null +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_cursor.lua @@ -0,0 +1,76 @@ +local core_cursor = require("core/cursor") +local unicode = require("unicode") +local kb = require("keyboard") + +function core_cursor.touch(cursor, gx, gy) + if cursor.data == "" then + return true + end + local win = cursor.window + gx, gy = gx - win.dx, gy - win.dy + while true do + local x, y, d = win.x, win.y, win.width + local dx = ((gy*d+gx)-(y*d+x)) + if dx == 1 then + dx = unicode.wlen(unicode.sub(cursor.data, cursor.index + 1, cursor.index + 1)) == 2 and 0 or dx + end + if dx == 0 then + break + end + cursor:move(dx > 0 and 1 or -1) + if x == win.x and y == win.y then + break + end + end + return true +end + +function core_cursor.clipboard(cursor, char) + cursor.cache = nil + local first_line, end_index = char:find("\13?\10") + local after = "" + if first_line then + after = char:sub(end_index + 1) + char = char:sub(1, first_line - 1) + end + cursor:update(char) + if after ~= "" then + -- todo look at postponing the text on cursor + local keyboard = require("tty").keyboard() + require("computer").pushSignal("key_down", keyboard, 13, 28) + require("computer").pushSignal("clipboard", keyboard, after) + end + return first_line and char or true +end + +function core_cursor.tab(cursor) + local hints = cursor.hint + if not hints then return end + if not cursor.cache then + cursor.cache = + type(hints) == "table" and hints or + hints(cursor.data, cursor.index + 1) or + {} + cursor.cache.i = -1 + end + + local cache = cursor.cache + + if #cache == 1 and cache.i == 0 then + -- there was only one solution, and the user is asking for the next + cursor.cache = hints(cache[1], cursor.index + 1) + if not cursor.cache then return end + cursor.cache.i = -1 + cache = cursor.cache + end + + local change = kb.isShiftDown() 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:move(cursor.len) + cursor:update(-cursor.len) + cursor:update(next, -tail) + end +end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_tty.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_tty.lua deleted file mode 100644 index c52eae35b..000000000 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_tty.lua +++ /dev/null @@ -1,87 +0,0 @@ -local tty = require("tty") -local unicode = require("unicode") -local kb = require("keyboard") - -function tty.touch_handler(_, cursor, gx, gy) - if cursor.data == "" then - return false - end - cursor:move(-math.huge) - local win = tty.window - gx, gy = gx - win.dx, gy - win.dy - local x2, y2, d = win.x, win.y, win.width - local char_width_to_move = ((gy*d+gx)-(y2*d+x2)) - if char_width_to_move <= 0 then - return false - end - local total_wlen = unicode.wlen(cursor.data) - if char_width_to_move >= 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 -tty.drag_handler = tty.touch_handler - -function tty.clipboard_handler(handler, _, char, _) - 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 - -function tty.on_tab(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 - - if #cache == 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) - if not handler.cache then return end - handler.cache.i = -1 - cache = handler.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 diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_vt.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_vt.lua index de6a64eb1..df4e1c467 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_vt.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_vt.lua @@ -1,4 +1,5 @@ local vt100 = require("vt100") +local unicode = require("unicode") local rules = vt100.rules @@ -45,9 +46,9 @@ end -- [2K clear entire line local function clear_line(window, _, n) n = tonumber(n) or 0 - local x = n == 0 and window.x or 1 - local rep = n == 1 and window.x or window.width - window.gpu.fill(x, window.y, rep, 1, " ") + local x = (n == 0 and window.x or 1) + local rep = n == 1 and window.x or (window.width - x + 1) + window.gpu.fill(x + window.dx, window.y + window.dy, rep, 1, " ") end rules[{"%[", "[012]?", "K"}] = clear_line @@ -59,8 +60,8 @@ rules[{"%[", "[012]?", "J"}] = function(window, _, n) clear_line(window, _, n) n = tonumber(n) or 0 local y = n == 0 and (window.y + 1) or 1 - local rep = n == 1 and (window.y - 1) or window.height - window.gpu.fill(1, y, window.width, rep, " ") + local rep = n == 1 and (window.y - 1) or (window.height - 1) + window.gpu.fill(1 + window.dx, y + window.dy, window.width, rep, " ") end -- [H move cursor to upper left corner @@ -81,7 +82,7 @@ end -- D scroll up one line -- moves cursor down -- E move to next line (acts the same ^, but x=1) -- M scroll down one line -- moves cursor up -rules[{"%[", "[DEM]"}] = function(window, _, dir) +rules[{"[DEM]"}] = function(window, _, dir) if dir == "D" then window.y = window.y + 1 elseif dir == "E" then 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 77e63253b..55dad9c35 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua @@ -1,15 +1,14 @@ local tty = require("tty") -local unicode = require("unicode") local computer = require("computer") local process = require("process") +local event = require("event") +local core_cursor = require("core/cursor") +local unicode = require("unicode") local kb = require("keyboard") local keys = kb.keys --- tty is bisected into a delay loaded library --- term indexing will fail to use full_tty unless tty is fully loaded --- accessing tty.full_tty [a nonexistent field] will cause that full load -local term = setmetatable({internal={},tty.full_tty}, {__index=tty}) +local term = setmetatable({internal={}}, {__index=tty}) function term.internal.window() return process.info().data.window @@ -17,7 +16,7 @@ end local function as_window(window, func, ...) local data = process.info().data - if not data.window then + if not data.window or not window then return func(...) end local prev = rawget(data, "window") @@ -29,7 +28,7 @@ end function term.internal.open(...) local dx, dy, w, h = ... - local window = {fullscreen=select("#",...) == 0, blink = true} + local window = {fullscreen=select("#",...) == 0, blink = true, output_buffer = ""} -- support legacy code using direct manipulation of w and h -- (e.g. wocchat) instead of using setViewport @@ -67,72 +66,79 @@ function term.internal.open(...) return window end -local function build_horizontal_reader(cursor) - cursor.clear_tail = function(self) - local w,_,dx,dy,x,y = tty.getViewport() - local _,s2=tty.split(self) - local wlen = math.min(unicode.wlen(s2),w-x+1) - tty.gpu().fill(x+dx,y+dy,wlen,1," ") - end - cursor.move = function(self, n) - local win = tty.window - local a = self.index - local b = math.max(0,math.min(unicode.len(self.data), self.index+n)) - self.index = b - a, b = a < b and a or b, a < b and b or a - local wlen_moved = unicode.wlen(unicode.sub(self.data, a + 1, b)) - win.x = win.x + wlen_moved * (n<0 and -1 or 1) - self:scroll() - end - cursor.draw = function(_, text) - local nowrap = tty.window.nowrap - tty.window.nowrap = true - tty.stream:write(text) - tty.window.nowrap = nowrap - end - cursor.scroll = function(self, goback, prev_x) - local win = tty.window - win.x = goback and prev_x or win.x - local x = win.x - local w = win.width - local data,px,i = self.data, self.promptx, self.index - local available = w-px+1 - if x > w then - local blank - if i == unicode.len(data) then - available,blank=available-1," " - else - i,blank=i+1,"" +local function horizontal_echo(self, arg) + if arg == "" then -- special scroll request + local w = self.window + local width = w.width + if w.x >= width then + -- render all text from 1 to width from end + -- the width that matters depends on the following char width + local next_overlap = math.max(unicode.wlen(unicode.sub(self.data, self.index + 1, self.index + 1)) - 1, 0) + width = width - next_overlap + if w.x > width then + local s1, s2 = + unicode.sub(self.data, self.vindex + 1, self.index), + unicode.sub(self.data, self.index + 1) + -- vindex is really the only way we know where input started + local s1wlen = unicode.wlen(s1) + -- adjust is how far to push the string back + local adjust = w.x - width + local wlen_available = s1wlen - adjust + 1 -- +1 because wtrunc removes the final position + -- now we have to resize s2 to fit in wlen_available, from the end, not the front + local trunc = unicode.wtrunc(unicode.reverse(s1), wlen_available) + -- is it faster to reverse again, or take wlen and sub, probably just reverse + trunc = unicode.reverse(trunc) + -- a double wide may have just been cut + local cutwlen = s1wlen - unicode.wlen(trunc) + -- we have to move to position 1, which should be at vindex, or s2wlen back from x + w.x = w.x - s1wlen + self.output:write(trunc .. s2 .. (" "):rep(cutwlen)) + self.vindex = self.index - unicode.len(trunc) + w.x = width - cutwlen + 1 end - data = unicode.sub(data,1,i) - local rev = unicode.reverse(data) - local ending = unicode.wtrunc(rev, available+1) - data = unicode.reverse(ending) - win.x = self.promptx - self:draw(data..blank) - -- wide chars may place the cursor not exactly at the end - win.x = math.min(w, self.promptx + unicode.wlen(data)) - -- x could be negative, we scroll it back into view - elseif x < self.promptx then - data = unicode.sub(data, self.index+1) - if unicode.wlen(data) > available then - data = unicode.wtrunc(data,available+1) - end - win.x = self.promptx - self:draw(data) - win.x = math.max(px, math.min(w, x)) + elseif w.x < 1 then + -- render all text from 1 to width from end + local s2 = unicode.sub(self.data, self.index + 1) + w.x = 1 + self.output:write(s2) + w.x = 1 + self.vindex = self.index end + -- scroll is safe now, return as normal below + elseif arg == keys.left then + if self.index < self.vindex then + self.window.x = 0 + return self:echo("") + end + elseif arg == keys.right then + self.window.x = self.window.x + unicode.wlen(unicode.sub(self.data, self.index, self.index)) + return self:echo("") end - cursor.clear = function(self) - local win = tty.window - local gpu, px = win.gpu, self.promptx - local w,_,dx,dy,_,y = tty.getViewport() - self.index, self.data, win.x = 0, "", px - gpu.fill(px+dx,y+dy,w-px+1-dx,1," ") - end + return self.super.echo(self, arg) end -local function inject_filter(handler, filter) +local function horizontal_update(self, arg, back) + if back then + -- if we're just going to render arg and move back, and we're not wrapping, just render arg + -- back may be more or less from current x + local arg_len = unicode.len(arg) + local x = self.window.x + self.output:write(arg) + self.window.x = x + self.data = self.data .. arg + self.len = self.len + arg_len -- recompute len + self:move(self.len - self.index + back) -- back is negative from end + return true + end + return self.super.update(self, arg, back) +end + +local function add_cursor_handlers(cursor, ops) + -- cursor inheritance gives super: to access base methods + -- function cursor:method(...) + -- return self.super.method(self, ...) + -- end + local filter = ops.filter or cursor.filter if filter then if type(filter) == "string" then local filter_text = filter @@ -141,42 +147,40 @@ local function inject_filter(handler, filter) end end - handler.key_down = function(self, cursor, char, code) - if code == keys.enter or code == keys.numpadenter then - if not filter(cursor.data) then + function cursor:handle(name, char, code) + if name == "key_down" and (code == keys.enter or code == keys.numpadenter) then + if not filter(self.data) then computer.beep(2000, 0.1) - return false -- ignore + return true -- handled end end - return tty.key_down_handler(self, cursor, char, code) + return self.super.handle(self, name, char, code) end end -end -local function inject_mask(cursor, dobreak, pwchar) - if not pwchar and dobreak ~= false then - return - end - - if pwchar then + local pwchar = ops.pwchar or cursor.pwchar + local nobreak = ops.dobreak == false or cursor.dobreak == false + if pwchar or nobreak or cursor.nowrap then if type(pwchar) == "string" then local pwchar_text = pwchar pwchar = function(text) return text:gsub(".", pwchar_text) end + end + function cursor:echo(arg) + if pwchar and type(arg) == "string" and #arg > 0 then -- "" is used for scrolling + arg = pwchar(arg) + elseif nobreak and arg == keys.enter then + arg = "" + end + local echo = cursor.nowrap and horizontal_echo or self.super.echo + return echo(self, arg) end end - - local cursor_draw = cursor.draw - cursor.draw = function(self, text) - local pre, newline = text:match("(.-)(\n?)$") - if dobreak == false then - newline = "" - end - if pwchar then - pre = pwchar(pre) - end - return cursor_draw(self, pre .. newline) + if cursor.nowrap then + cursor.update = horizontal_update + tty.window.nowrap = true + cursor.vindex = 0 -- visual/virtual index end end @@ -190,32 +194,25 @@ function term.write(value, wrap) end function term.read(history, dobreak, hint, pwchar, filter) - history = history or {} - local handler = history - handler.hint = handler.hint or hint + local cursor = history or {} + cursor.hint = cursor.hint or hint - local cursor = tty.build_vertical_reader() - if handler.nowrap then - build_horizontal_reader(cursor) - end + add_cursor_handlers(cursor, { + dobreak = dobreak, + pwchar = pwchar, + filter = filter, + }) - inject_filter(handler, filter) - inject_mask(cursor, dobreak, pwchar or history.pwchar) - handler.cursor = cursor - - return tty.read(handler) + return tty.read(cursor) end -function term.getGlobalArea(window) - local w,h,dx,dy = as_window(window, tty.getViewport) +function term.getGlobalArea() + local w,h,dx,dy = tty.getViewport() return dx+1,dy+1,w,h end -function term.clearLine(window) - window = window or tty.window - local w, h, dx, dy, _, y = as_window(window, tty.getViewport) - window.gpu.fill(dx + 1, dy + math.max(1, math.min(y, h)), w, 1, " ") - window.x = 1 +function term.clearLine() + term.write("\27[2K\27[999D") end function term.setCursorBlink(enabled) @@ -228,19 +225,16 @@ end function term.pull(...) local args = table.pack(...) - local timeout = nil + local timeout = math.huge if type(args[1]) == "number" then - timeout = table.remove(args, 1) + timeout = computer.uptime() + table.remove(args, 1) args.n = args.n - 1 end - local stdin_stream = io.stdin.stream - if stdin_stream.pull then - return stdin_stream:pull(timeout, table.unpack(args, 1, args.n)) - end - -- if stdin does not have pull() we can build the result - local result = io.read(1) - if result then - return "clipboard", nil, result + local cursor = core_cursor.new(nil, tty.window, tty.stream) -- cursors can blink (base arg is optional) + while cursor:echo() and timeout >= computer.uptime() do + local s = table.pack(event.pull(.5, table.unpack(args, 1, args.n))) + cursor:echo(not s[1]) + if s.n > 1 then return table.unpack(s, 1, s.n) end end end @@ -248,12 +242,7 @@ function term.bind(gpu, window) return as_window(window, tty.bind, gpu) end -function term.scroll(...) - if io.stdout.tty then - return io.stdout.stream.scroll(...) - end -end - +term.scroll = tty.stream.scroll term.internal.run_in_window = as_window return term diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tty.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/tty.lua index c7ad2093c..0cfc65f00 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/tty.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/tty.lua @@ -1,9 +1,7 @@ local unicode = require("unicode") local event = require("event") -local kb = require("keyboard") local component = require("component") local computer = require("computer") -local keys = kb.keys local tty = {} tty.window = @@ -14,55 +12,11 @@ tty.window = dy = 0, x = 1, y = 1, + output_buffer = "", } tty.stream = {} -function tty.key_down_handler(handler, cursor, char, code) - local data = cursor.data - 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 - tty.on_tab(handler, cursor) - elseif code == keys.enter or code == keys.numpadenter then - cursor:move(math.huge) - cursor:draw("\n") - if data:find("%S") and data ~= handler[1] then - table.insert(handler, 1, data) - handler[(tonumber(os.getenv("HISTSIZE")) or 10)+1]=nil - end - handler[0]=nil - return nil, data .. "\n" - elseif code == keys.up or code == keys.down then - local ni = handler.index + (code == keys.up and 1 or -1) - if ni >= 0 and ni <= #handler then - handler[handler.index] = data - handler.index = ni - cursor:clear() - cursor:update(handler[ni]) - end - elseif code == keys.left or code == keys.back or code == keys.w and ctrl then - local value = ctrl and ((unicode.sub(data, 1, cursor.index):find("%s[^%s]+%s*$") or 0) - cursor.index) or -1 - if code == keys.left then - cursor:move(value) - else - c = value - end - elseif code == keys.right then cursor:move(ctrl and ((data:find("%s[^%s]", cursor.index + 1) or math.huge) - cursor.index) or 1) - elseif code == keys.home then cursor:move(-math.huge) - elseif code == keys["end"] then cursor:move( math.huge) - elseif code == keys.delete then c = 1 - 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 @@ -73,7 +27,7 @@ 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 + if window.fullscreen and screen and not screen_cache[screen]then screen_cache[screen] = true window.width, window.height = window.gpu.getViewport() end @@ -101,189 +55,126 @@ function tty.isAvailable() return not not (gpu and gpu.getScreen()) end -function tty.stream:pull(timeout, ...) - timeout = timeout or math.huge - local blink_timeout = tty.window.blink and .5 or math.huge - - local width, height, dx, dy, x, y = tty.getViewport() - local gpu = tty.gpu() - if x < 1 or x > width or y < 1 or y > height then - gpu = nil - end - local char_at_cursor - local blinked - if gpu then - blinked, char_at_cursor = pcall(gpu.get, x + dx, y + dy) - if not blinked then - return nil, "interrupted" - end - io.write("\0277\27[7m", char_at_cursor, "\0278") - end - - -- get the next event - while true do - local signal = table.pack(event.pull(math.min(blink_timeout, timeout), ...)) - - timeout = timeout - blink_timeout - local done = signal.n > 1 or timeout < blink_timeout - if gpu then - if not blinked and not done then - io.write("\0277\27[7m", char_at_cursor, "\0278") - blinked = true - elseif blinked and (done or tty.window.blink) then - io.write("\0277", char_at_cursor, "\0278") - blinked = false - end - end - - if done then - return table.unpack(signal, 1, signal.n) - end - end -end - -function tty.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.build_vertical_reader() - return - { - promptx = tty.window.x, - prompty = tty.window.y, - index = 0, - data = "", - sy = 0, - scroll = function(self, goback, prev_x, prev_y) - local width, x = tty.window.width, tty.getCursor() - 1 - tty.setCursor(x % width + 1, tty.window.y + math.floor(x / width)) - self:draw("") - if goback then - tty.setCursor(prev_x, prev_y - self.sy) - end - end, - move = function(self, n) - local win = tty.window - self.index = math.min(math.max(0, self.index + n), unicode.len(self.data)) - local s1, s2 = tty.split(self) - s2 = unicode.sub(s2.." ", 1, 1) - local data_remaining = ("_"):rep(self.promptx - 1)..s1..s2 - win.y = self.prompty - self.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(self) - local oi, width, _, dx, dy, ox, oy = self.index, tty.getViewport() - self:move(math.huge) - self:move(-1) - local _, ey = tty.getCursor() - tty.setCursor(ox, oy) - self.index = oi - local cx = oy == ey and ox or 1 - tty.gpu().fill(cx + dx, ey + dy, width - cx + 1, 1, " ") - end, - update = function(self, arg) - local s1, s2 = tty.split(self) - if type(arg) == "string" then - self.data = s1 .. arg .. s2 - self.index = self.index + unicode.len(arg) - self:draw(arg) - else -- number - if arg < 0 then - -- backspace? ignore if at start - if self.index <= 0 then return end - self:move(arg) - s1 = unicode.sub(s1, 1, -1 + arg) - else - -- forward? ignore if at end - if self.index >= unicode.len(self.data) then return end - s2 = unicode.sub(s2, 1 + arg) - end - self:clear_tail() - self.data = s1 .. s2 - end - - -- redraw suffix - local prev_x, prev_y = tty.getCursor() - prev_y = prev_y + self.sy -- scroll will remove it - self:draw(s2) - self:scroll(s2 ~= "", prev_x, prev_y) - end, - clear = function(self) - self:move(-math.huge) - self:draw((" "):rep(unicode.wlen(self.data))) - self:move(-math.huge) - self.index = 0 - self.data = "" - end, - draw = function(self, text) - self.sy = self.sy + tty.stream:write(text) - end - } -end - -function tty.read(handler) - tty.window.handler = handler +function tty.read(cursor) + tty.window.cursor = cursor local stdin = io.stdin local result = table.pack(pcall(stdin.readLine, stdin, false)) - tty.window.handler = nil + tty.window.cursor = nil return select(2, assert(table.unpack(result))) end -- PLEASE do not use this method directly, use io.read or term.read -function tty.stream:read() - local handler = tty.window.handler or {} - local cursor = handler.cursor or tty.build_vertical_reader() +function tty.stream.read() + local cursor = require("core/cursor").new(tty.window.cursor, tty.window, tty.stream) + tty.window.cursor = cursor -- used by output to update the sy offset + local ret, why - tty.window.handler = nil - handler.index = 0 + -- address checks + local address_check = + { + key_down = tty.keyboard, + clipboard = tty.keyboard, + touch = tty.screen, + drag = tty.screen + } - while true do - local name, address, char, code = self:pull() - -- 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 - self:write("^C\n") - return false, name - elseif address == main_kb or address == main_sc then - local handler_method = handler[name] or - -- this handler listing hack is to delay load tty - ({key_down=1, touch=1, drag=1, clipboard=1})[name] and tty[name .. "_handler"] - if handler_method then - -- nil to end (close) - -- false to ignore - -- 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) + while cursor:echo() do + local name, address, char, code = event.pull(tty.window.blink and .5 or math.huge) + + -- we may have lost the cursor during pull + if not cursor:echo(not name) then + ret, why = nil, nil + break + elseif name then + local filter_address = address_check[name] + if not filter_address or filter_address() == address then + ret, why = cursor:handle(name, char, code) + if ret ~= true then + break end end end end + + tty.window.cursor = nil + return ret, why +end + +-- PLEASE do not use this method directly, use io.write or term.write +function tty.stream:write(value) + local gpu = tty.gpu() + if not gpu then + return + end + local window = tty.window + local cursor = window.cursor or {sy=0, tails={}} + local beeped + local uptime = computer.uptime + local last_sleep = uptime() + window.output_buffer = window.output_buffer .. value + while true do + if uptime() - last_sleep > 1 then + os.sleep(0) + last_sleep = uptime() + end + + local ansi_print = require("vt100").parse(window) + + -- scroll before parsing next line + -- the value may only have been a newline + cursor.sy = cursor.sy + self.scroll() + -- we may have needed to scroll one last time [nowrap adjustments] + -- or the vt100 parse is incomplete, print nothing else + if #window.output_buffer == 0 or not ansi_print then + break + end + + local x, y = tty.getCursor() + + local _, ei, delim = unicode.sub(window.output_buffer, 1, window.width):find("([\27\t\r\n\a])") + local segment = ansi_print .. (ei and window.output_buffer:sub(1, ei - 1) or window.output_buffer) + + if segment ~= "" then + local gpu_x, gpu_y = x + window.dx, y + window.dy + local tail = "" + local wlen_needed = unicode.wlen(segment) + local wlen_remaining = window.width - x + 1 + if wlen_remaining < wlen_needed then + segment = unicode.wtrunc(segment, wlen_remaining + 1) + local wlen_used = unicode.wlen(segment) + tail = wlen_used < wlen_remaining and " " or "" + cursor.tails[gpu_y - cursor.sy] = tail + if not window.nowrap then + -- we have to reparse the delimeter + ei = #segment + -- fake a newline + delim = "\n" + end + wlen_needed = wlen_used + end + gpu.set(gpu_x, gpu_y, segment..tail) + x = x + wlen_needed + end + + window.output_buffer = ei and window.output_buffer:sub(ei + 1) or "" + + if delim == "\t" then + x = ((x-1) - ((x-1) % 8)) + 9 + elseif delim == "\r" or (delim == "\n" and not window.cr_last) then + x = 1 + y = y + 1 + elseif delim == "\a" and not beeped then + computer.beep() + beeped = true + elseif delim == "\27" then + window.output_buffer = delim .. window.output_buffer + end + + tty.setCursor(x, y) + window.cr_last = delim == "\r" + end + return cursor.sy end function tty.getCursor() @@ -296,86 +187,6 @@ function tty.setCursor(x, y) window.x, window.y = x, y end --- PLEASE do not use this method directly, use io.write or term.write -function tty.stream:write(value) - local gpu = tty.gpu() - if not gpu then - return - end - local window = tty.window - local sy = 0 - local beeped - local uptime = computer.uptime - local last_sleep = uptime() - while true do - if uptime() - last_sleep > 1 then - os.sleep(0) - last_sleep = uptime() - end - - local ansi_print = "" - if window.ansi_escape then - -- parse the instruction in segment - -- [ (%d+;)+ %d+m - window.ansi_escape = window.ansi_escape .. value - value, ansi_print = require("vt100").parse(window) - end - - -- scroll before parsing next line - -- the value may only have been a newline - sy = sy + self.scroll() - -- we may have needed to scroll one last time [nowrap adjustments] - if #value == 0 then - break - end - - local x, y = tty.getCursor() - - local _, ei, delim = unicode.sub(value, 1, window.width):find("([\27\t\r\n\a])", #ansi_print + 1) - local segment = ansi_print .. (ei and value:sub(1, ei - 1) or value) - - if segment ~= "" then - local gpu_x, gpu_y = x + window.dx, y + window.dy - local tail = "" - local wlen_needed = unicode.wlen(segment) - local wlen_remaining = window.width - x + 1 - if wlen_remaining < wlen_needed then - segment = unicode.wtrunc(segment, wlen_remaining + 1) - local wlen_used = unicode.wlen(segment) - -- we can clear the line because we already know remaining < needed - tail = (" "):rep(wlen_remaining - wlen_used) - if not window.nowrap then - -- we have to reparse the delimeter - ei = #segment - -- fake a newline - delim = "\n" - wlen_needed = wlen_used - end - end - gpu.set(gpu_x, gpu_y, segment..tail) - x = x + wlen_needed - end - - value = ei and value:sub(ei + 1) or "" - - if delim == "\t" then - x = ((x-1) - ((x-1) % 8)) + 9 - elseif delim == "\r" or (delim == "\n" and not window.cr_last) then - x = 1 - y = y + 1 - elseif delim == "\a" and not beeped then - computer.beep() - beeped = true - elseif delim == "\27" then -- ansi escape - window.ansi_escape = "" - end - - tty.setCursor(x, y) - window.cr_last = delim == "\r" - end - return sy -end - local gpu_intercept = {} function tty.bind(gpu) checkArg(1, gpu, "table") @@ -394,7 +205,7 @@ function tty.bind(gpu) end end local window = tty.window - if not window.gpu or window.gpu == gpu then + if window.gpu ~= gpu then window.gpu = gpu window.keyboard = nil -- without a keyboard bound, always use the screen's main keyboard (1st) tty.getViewport() @@ -420,7 +231,7 @@ function tty.keyboard() return system_keyboard end - -- if we are using a gpu bound to the primary scren, then use the primary keyboard + -- if we are using a gpu bound to the primary screen, then use the primary keyboard if component.isAvailable("screen") and component.screen.address == screen then window.keyboard = system_keyboard else @@ -479,7 +290,6 @@ function tty.stream.scroll(lines) gpu.fill(dx + 1, fill_top, width, abs_lines, ' ') tty.setCursor(x, math.max(1, math.min(y, height))) - return lines end @@ -489,6 +299,4 @@ tty.stream.close = bfd tty.stream.seek = bfd tty.stream.handle = "tty" -require("package").delay(tty, "/lib/core/full_tty.lua") - return tty diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/vt100.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/vt100.lua index a161ad5be..5abc8bd79 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/vt100.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/vt100.lua @@ -79,23 +79,22 @@ rules[{"[78]"}] = save_attributes -- u restore cursor position rules[{"%[", "[su]"}] = save_attributes --- returns 2 values --- value: parsed text --- ansi_print: failed to parse +-- returns: anything that failed to parse function vt100.parse(window) - local ansi = window.ansi_escape - window.ansi_escape = nil + if window.output_buffer:sub(1, 1) ~= "\27" then + return "" + end local any_valid for rule,action in pairs(rules) do - local last_index = 0 + local last_index = 1 -- start at 1 to skip the \27 local captures = {} for _,pattern in ipairs(rule) do - if last_index >= #ansi then + if last_index >= #window.output_buffer then any_valid = true break end - local si, ei, capture = ansi:find("^(" .. pattern .. ")", last_index + 1) + local si, ei, capture = window.output_buffer:find("^(" .. pattern .. ")", last_index + 1) if not si then break end @@ -105,7 +104,8 @@ function vt100.parse(window) if #captures == #rule then action(window, table.unpack(captures)) - return ansi:sub(last_index + 1), "" + window.output_buffer = window.output_buffer:sub(last_index + 1) + return "" end end @@ -113,18 +113,16 @@ function vt100.parse(window) -- maybe it did satisfy a rule, load more rules full = true dofile("/lib/core/full_vt.lua") - window.ansi_escape = ansi return vt100.parse(window) end if not any_valid then -- malformed - return ansi, "\27" + window.output_buffer = window.output_buffer:sub(2) + return "\27" end -- else, still consuming - window.ansi_escape = ansi - return "", "" end return vt100 From 95598f6262de7c171536c87c5e743634e5a03796 Mon Sep 17 00:00:00 2001 From: payonel Date: Sat, 31 Mar 2018 17:38:12 -0700 Subject: [PATCH 06/12] should check timeout before blinking --- .../resources/assets/opencomputers/loot/openos/lib/term.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 55dad9c35..999127967 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua @@ -231,7 +231,7 @@ function term.pull(...) args.n = args.n - 1 end local cursor = core_cursor.new(nil, tty.window, tty.stream) -- cursors can blink (base arg is optional) - while cursor:echo() and timeout >= computer.uptime() do + while timeout >= computer.uptime() and cursor:echo() do local s = table.pack(event.pull(.5, table.unpack(args, 1, args.n))) cursor:echo(not s[1]) if s.n > 1 then return table.unpack(s, 1, s.n) end From be921a2d0f031e88137c6c172f3209fbb457010e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Sat, 26 May 2018 22:05:04 +0200 Subject: [PATCH 07/12] Maven path now via environment variable. --- build.properties | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.properties b/build.properties index c1ee0e1e3..21bc010ca 100644 --- a/build.properties +++ b/build.properties @@ -53,7 +53,5 @@ waila.version=1.6.0-B3_1.8.8 wrcbe.version=2.0.0.19 cofhcore.version=4.1.1.156 -maven.url=file:///home/www/maven.cil.li/web - curse.project.id=223008 curse.project.releaseType=release From 64035ff0c312b0c3ff7e868d75e5c9c4ac5325f9 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Wed, 4 Jul 2018 10:48:40 +0200 Subject: [PATCH 08/12] replace hardcoded hookInterval with bogomips calculator --- .../assets/opencomputers/lua/machine.lua | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/main/resources/assets/opencomputers/lua/machine.lua b/src/main/resources/assets/opencomputers/lua/machine.lua index d8c3eb540..d2751bfb0 100644 --- a/src/main/resources/assets/opencomputers/lua/machine.lua +++ b/src/main/resources/assets/opencomputers/lua/machine.lua @@ -1,4 +1,44 @@ -local hookInterval = 100 +local hookInterval = 10000 +local function calcHookInterval() + local bogomipsDivider = 0.05 + local bogomipsDeadline = computer.realTime() + bogomipsDivider + local ipsCount = 0 + local bogomipsBusy = true + local function calcBogoMips() + ipsCount = ipsCount + hookInterval + if computer.realTime() > bogomipsDeadline then + bogomipsBusy = false + end + end + -- The following is a bit of nonsensical-seeming code attempting + -- to cover Lua's VM sufficiently for the IPS calculation. + local bogomipsTmpA = {{["b"]=3, ["d"]=9}} + local function c(k) + if k <= 2 then + bogomipsTmpA[1].d = k / 2.0 + end + end + debug.sethook(calcBogoMips, "", hookInterval) + while bogomipsBusy do + local st = "" + for k=2,4 do + st = st .. "a" .. k + c(k) + if k >= 3 then + bogomipsTmpA[1].b = bogomipsTmpA[1].b * (k ^ k) + end + end + end + debug.sethook() + return ipsCount / bogomipsDivider +end + +local ipsCount = calcHookInterval() +-- Since our IPS might still be too generous (hookInterval needs to run at most +-- every 0.05 seconds), we divide it further by 10 relative to that. +hookInterval = (ipsCount * 0.005) +if hookInterval < 1000 then hookInterval = 1000 end + local deadline = math.huge local hitDeadline = false local function checkDeadline() From 5bdbadc125cc9ba8031743c3e05be8bc527d893f Mon Sep 17 00:00:00 2001 From: SquidDev Date: Tue, 17 Apr 2018 14:08:16 +0100 Subject: [PATCH 09/12] Add bundled cable support for ComputerCraft --- .../BundledRedstoneProvider.scala | 43 +++++++++++++++++++ .../computercraft/ModComputerCraft.scala | 1 + 2 files changed, 44 insertions(+) create mode 100644 src/main/scala/li/cil/oc/integration/computercraft/BundledRedstoneProvider.scala diff --git a/src/main/scala/li/cil/oc/integration/computercraft/BundledRedstoneProvider.scala b/src/main/scala/li/cil/oc/integration/computercraft/BundledRedstoneProvider.scala new file mode 100644 index 000000000..feddf6b0c --- /dev/null +++ b/src/main/scala/li/cil/oc/integration/computercraft/BundledRedstoneProvider.scala @@ -0,0 +1,43 @@ +package li.cil.oc.integration.computercraft + +import dan200.computercraft.api.ComputerCraftAPI +import dan200.computercraft.api.redstone.IBundledRedstoneProvider +import li.cil.oc.common.tileentity.traits.BundledRedstoneAware +import li.cil.oc.integration.util.BundledRedstone +import li.cil.oc.integration.util.BundledRedstone.RedstoneProvider +import li.cil.oc.util.BlockPosition +import net.minecraft.world.World +import net.minecraftforge.common.util.ForgeDirection + +object BundledRedstoneProvider extends IBundledRedstoneProvider with RedstoneProvider { + def init() { + ComputerCraftAPI.registerBundledRedstoneProvider(this) + BundledRedstone.addProvider(this) + } + + override def getBundledRedstoneOutput(world: World, x: Int, y: Int, z: Int, side: Int): Int = + world.getTileEntity(x, y, z) match { + case tile: BundledRedstoneAware => + var result = 0 + val colours = tile.bundledOutput(ForgeDirection.VALID_DIRECTIONS(side)) + for (colour <- 0 to 15) { + if (colours(colour) > 0) result |= 1 << colour + } + result + case _ => -1 + } + + override def computeInput(pos: BlockPosition, side: ForgeDirection): Int = 0 + + override def computeBundledInput(pos: BlockPosition, side: ForgeDirection): Array[Int] = { + val offset = pos.offset(side) + val strength = ComputerCraftAPI.getBundledRedstoneOutput(pos.world.get, offset.x, offset.y, offset.z, side.getOpposite.ordinal()) + if (strength >= 0) { + val strengths = new Array[Int](16) + for (colour <- 0 to 15) { + strengths(colour) = if ((strength & (1 << colour)) == 0) 0 else 15 + } + strengths + } else null + } +} diff --git a/src/main/scala/li/cil/oc/integration/computercraft/ModComputerCraft.scala b/src/main/scala/li/cil/oc/integration/computercraft/ModComputerCraft.scala index 8001d88fe..11ef7ba7e 100644 --- a/src/main/scala/li/cil/oc/integration/computercraft/ModComputerCraft.scala +++ b/src/main/scala/li/cil/oc/integration/computercraft/ModComputerCraft.scala @@ -9,6 +9,7 @@ object ModComputerCraft extends ModProxy { override def initialize() { PeripheralProvider.init() + BundledRedstoneProvider.init() Driver.add(DriverComputerCraftMedia) Driver.add(new DriverPeripheral()) From 1a6756248c010fdceff6f50ea4f3023c9362931a Mon Sep 17 00:00:00 2001 From: payonel Date: Thu, 23 Aug 2018 23:09:25 -0700 Subject: [PATCH 10/12] openos pre-1.7.3 patching 1. as always, code cleanup and memory savings 2. allow force (-f) option with cp 3. cursor library, separating logic from tty [ the cursor library is robust and heavily tested, but the api is not finalized and is considered beta ] 4. fix autorun on ro fs 5. /bin/ls: run all filesystem calls in lua, safe from c-boundaries 6. vt100: small fix with clear line (1-off error) --- .../opencomputers/loot/openos/bin/cp.lua | 1 + .../opencomputers/loot/openos/bin/sh.lua | 3 +- .../loot/openos/boot/00_base.lua | 3 - .../loot/openos/boot/01_process.lua | 2 +- .../opencomputers/loot/openos/boot/02_os.lua | 31 +- .../loot/openos/boot/90_filesystem.lua | 2 +- .../loot/openos/boot/94_shell.lua | 4 +- .../loot/openos/lib/core/cursor.lua | 284 +++++++++++------- .../loot/openos/lib/core/devfs/02_utils.lua | 19 +- .../loot/openos/lib/core/full_buffer.lua | 4 +- .../loot/openos/lib/core/full_cursor.lua | 119 +++++--- .../loot/openos/lib/core/full_event.lua | 14 + .../loot/openos/lib/core/full_filesystem.lua | 128 +++++++- .../loot/openos/lib/core/full_ls.lua | 84 +++--- .../loot/openos/lib/core/full_text.lua | 11 +- .../loot/openos/lib/core/full_vt.lua | 3 +- .../loot/openos/lib/core/lua_shell.lua | 8 +- .../opencomputers/loot/openos/lib/event.lua | 15 - .../loot/openos/lib/filesystem.lua | 71 +---- .../opencomputers/loot/openos/lib/process.lua | 6 +- .../opencomputers/loot/openos/lib/sh.lua | 6 +- .../opencomputers/loot/openos/lib/term.lua | 123 ++------ .../loot/openos/lib/tools/fsmod.lua | 68 ----- .../opencomputers/loot/openos/lib/tty.lua | 63 ++-- .../opencomputers/loot/openos/lib/vt100.lua | 7 +- 25 files changed, 543 insertions(+), 536 deletions(-) delete mode 100644 src/main/resources/assets/opencomputers/loot/openos/lib/tools/fsmod.lua diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/cp.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/cp.lua index 9b68c7190..d173e219a 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/cp.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/cp.lua @@ -23,6 +23,7 @@ options = { cmd = "cp", i = options.i, + f = options.f, n = options.n, r = options.r, u = options.u, 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 e702e1629..0f4a03df3 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/sh.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/sh.lua @@ -23,7 +23,8 @@ if #args == 0 then end io.write(sh.expand(os.getenv("PS1") or "$ ")) end - local command = tty.read(input_handler) + tty.window.cursor = input_handler + local command = io.stdin:readLine(false) if command then command = text.trim(command) if command == "exit" then diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/00_base.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/00_base.lua index c8a56ac4b..7bd7b3aba 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/boot/00_base.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/boot/00_base.lua @@ -32,14 +32,11 @@ end function print(...) local args = table.pack(...) local stdout = io.stdout - local old_mode, old_size = stdout:setvbuf() - stdout:setvbuf("line") local pre = "" for i = 1, args.n do stdout:write(pre, tostring(args[i])) pre = "\t" end stdout:write("\n") - stdout:setvbuf(old_mode, old_size) stdout:flush() end diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/01_process.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/01_process.lua index 9c2e9808b..f6e793956 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/boot/01_process.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/boot/01_process.lua @@ -9,7 +9,7 @@ _G.coroutine = setmetatable( resume = function(co, ...) local proc = process.info(co) -- proc is nil if the process closed, natural resume will likely complain the coroutine is dead - -- but if proc is dead and an aborted coroutine is alive, it doesn't have any proc data like stack info + -- but if proc is dead and an orphan coroutine is alive, it doesn't have any proc data like stack info -- if the user really wants to resume it, let them return (proc and proc.data.coroutine_handler.resume or _coroutine.resume)(co, ...) end diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/02_os.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/02_os.lua index 5a40d04bf..314635696 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/boot/02_os.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/boot/02_os.lua @@ -1,19 +1,7 @@ local computer = require("computer") -local event = require("event") local fs = require("filesystem") -local shell = require("shell") local info = require("process").info - -os.execute = function(command) - if not command then - return type(shell) == "table" - end - return shell.execute(command) -end - -function os.exit(code) - error({reason="terminated", code=code}, 0) -end +local event = require("event") function os.getenv(varname) local env = info().data.vars @@ -34,9 +22,6 @@ function os.setenv(varname, value) return value end -os.remove = fs.remove -os.rename = fs.rename - function os.sleep(timeout) checkArg(1, timeout, "number", "nil") local deadline = computer.uptime() + (timeout or 0) @@ -45,18 +30,6 @@ function os.sleep(timeout) until computer.uptime() >= deadline end -function os.tmpname() - local path = os.getenv("TMPDIR") or "/tmp" - if fs.exists(path) then - for _ = 1, 10 do - local name = fs.concat(path, tostring(math.random(1, 0x7FFFFFFF))) - if not fs.exists(name) then - return name - end - end - end -end - os.setenv("PATH", "/bin:/usr/bin:/home/bin:.") os.setenv("TMP", "/tmp") -- Deprecated os.setenv("TMPDIR", "/tmp") @@ -64,3 +37,5 @@ os.setenv("TMPDIR", "/tmp") if computer.tmpAddress() then fs.mount(computer.tmpAddress(), "/tmp") end + +require("package").delay(os, "/lib/core/full_filesystem.lua") diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/90_filesystem.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/90_filesystem.lua index 03506eaa1..578037e9f 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/boot/90_filesystem.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/boot/90_filesystem.lua @@ -17,7 +17,7 @@ local function onComponentAdded(_, address, componentType) end name = fs.concat("/mnt", name) fs.mount(proxy, name) - if fs.isAutorunEnabled() then + if not fs.exists("/etc/filesystem.cfg") or fs.isAutorunEnabled() then local file = shell.resolve(fs.concat(name, "autorun"), "lua") or shell.resolve(fs.concat(name, ".autorun"), "lua") if file then diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/94_shell.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/94_shell.lua index 445b75d9d..13c9560fc 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/boot/94_shell.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/boot/94_shell.lua @@ -1,4 +1,6 @@ -- there doesn't seem to be a reason to update $HOSTNAME after the init signal -- as user space /etc/profile comes after this point anyways -loadfile("/bin/hostname.lua")("--update") +if require("filesystem").exists("/etc/hostname") then + loadfile("/bin/hostname.lua")("--update") +end os.setenv("SHELL","/bin/sh.lua") diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/cursor.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/cursor.lua index 4ffd5f613..6a3b783c4 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/cursor.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/cursor.lua @@ -1,32 +1,48 @@ local unicode = require("unicode") local kb = require("keyboard") +local tty = require("tty") +local text = require("text") +local computer = require("computer") local keys = kb.keys local core_cursor = {} -local super = {} -function super:move(n) - local s = - n < 0 and self.index > 0 and -1 or - n > 0 and self.index < self.len and 1 or - 0 - if s == 0 then return end - local echo_cmd = (s > 0 and keys.right or keys.left) - self.index = self.index + s - self:echo(echo_cmd) - return self:move(n - s) +core_cursor.vertical = {} + +function core_cursor.vertical:move(n) + local s = math.max(math.min(self.index + n, self.len), 0) + if s == self.index then return end + local echo_cmd = keys.left + local from = s + 1 + local to = self.index + if s > self.index then + echo_cmd, from, to = keys.right, to + 1, s + end + self.index = s + local step = unicode.wlen(unicode.sub(self.data, from, to)) + self:echo(echo_cmd, step) end -- back is used when arg comes after the cursor -function super:update(arg, back) +function core_cursor.vertical:update(arg, back) + if not arg then + self.tails = {} + self.data = "" + self.index = 0 + self.sy = 0 + self.hindex = 0 + end local s1 = unicode.sub(self.data, 1, self.index) local s2 = unicode.sub(self.data, self.index + 1) if type(arg) == "string" then + if back == false then + arg, s2 = arg .. s2, "" + else + self.index = self.index + unicode.len(arg) + self:echo(arg) + end self.data = s1 .. arg - self.index = self.index + unicode.len(arg) - self:echo(arg) - self:move(back or 0) - else -- number + elseif arg then -- number local has_tail = arg < 0 or #s2 > 0 if arg < 0 then -- backspace? ignore if at start @@ -44,136 +60,190 @@ function super:update(arg, back) end end self.len = unicode.len(self.data) -- recompute len + self:move(back or 0) if #s2 > 0 then self:update(s2, -unicode.len(s2)) end end -function super:echo(arg) +function core_cursor.vertical:echo(arg, num) + local win = tty.window + win.nowrap = self.nowrap if arg == "" then -- special scroll request - local gpu, width, x, y = - self.window.gpu, - self.window.width, - self.window.x, - self.window.y + local gpu, width, x, y = win.gpu, win.width, win.x, win.y if x > width then - self.window.x = ((x - 1) % width) + 1 - self.window.y = y + math.floor(x / width) - self.output:write("") - x, y = self.window.x, self.window.y + win.x = ((x - 1) % width) + 1 + win.y = y + math.floor(x / width) + io.write("") -- tty.stream:write knows how to scroll vertically + x, y = win.x, win.y end - return x > 0 and y > 0 and y <= self.window.height and gpu and - select(2, pcall(gpu.get, x + self.window.dx, y + self.window.dy)) - elseif arg == keys.enter then - arg = "\n" + if x <= 0 or y <= 0 or y > win.height or not gpu then return end + return table.pack(select(2, pcall(gpu.get, x + win.dx, y + win.dy))) elseif arg == keys.left then - local x = self.window.x - unicode.wlen(unicode.sub(self.data, self.index + 1, self.index + 1)) - local y = self.window.y - if x < 1 then - x = x + self.window.width - #(self.tails[self.window.dy + y - self.sy - 1] or "") + local x = win.x - num + local y = win.y + while x < 1 do + x = x + win.width - #(self.tails[win.dy + y - self.sy - 1] or "") y = y - 1 end - self.window.x, self.window.y = x, y + win.x, win.y = x, y arg = "" elseif arg == keys.right then - local x = self.window.x + unicode.wlen(unicode.sub(self.data, self.index, self.index)) - local y = self.window.y - local width = self.window.width - #(self.tails[self.window.dy + y - self.sy] or "") - if x > width then + local x = win.x + num + local y = win.y + while true do + local width = win.width - #(self.tails[win.dy + y - self.sy] or "") + if x <= width then break end x = x - width y = y + 1 end - self.window.x, self.window.y = x, y + win.x, win.y = x, y arg = "" - elseif type(arg) == "boolean" or arg == nil then -- blink + elseif not arg or arg == true then -- blink + local gpu = win.gpu local char = self.char_at_cursor if (arg == nil and not char) or (arg and not self.blinked) then char = char or self:echo("") --scroll and get char - if not char then return false end + if not char[1] then return false end self.blinked = true - arg = "\0277\27[7m" .. char .. "\0278" + if not arg then + io.write("\0277") + char.saved = win.saved + gpu.setForeground(char[4] or char[2], not not char[4]) + gpu.setBackground(char[5] or char[3], not not char[5]) + end + io.write("\0277", "\27[7m", char[1], "\0278") elseif (arg and self.blinked) or arg == false then self.blinked = false - arg, char = "\0277" .. char .. "\0278", arg and char + gpu.set(win.x + win.dx, win.y + win.dy, char[1]) + if not arg then + win.saved = char.saved + io.write("\0278") + char = nil + end end self.char_at_cursor = char + return true end - return not arg or self.output:write(arg) + return io.write(arg) end -function super:handle(name, char, code) +function core_cursor.vertical:handle(name, char, code) if name == "clipboard" then - return core_cursor.clipboard(self, char, code) + self.cache = nil -- this stops tab completion + local newline = char:find("\10") or #char + local printable_prefix, remainder = char:sub(1, newline), char:sub(newline + 1) + self:update(printable_prefix) + self:update(remainder, false) elseif name == "touch" or name == "drag" then - return core_cursor.touch(self, char, code) + core_cursor.touch(self, char, code) elseif name == "interrupted" then self:echo("^C\n") return false, name - elseif name ~= "key_down" then - return true -- handled - end - - local data = self.data - local value = false - local backup_cache = self.cache - self.cache = nil - local ctrl = kb.isControlDown() - if ctrl and code == keys.d then - return --nil:close - elseif code == keys.tab then - self.cache = backup_cache - core_cursor.tab(self) - elseif code == keys.enter or code == keys.numpadenter then - self:move(self.len) - self:echo(keys.enter) - if data:find("%S") and data ~= self[1] then - table.insert(self, 1, data) - self[(tonumber(os.getenv("HISTSIZE")) or 10)+1] = nil - end - self[0] = nil - return data .. "\n" - elseif code == keys.up or code == keys.down then - local ni = self.hindex + (code == keys.up and 1 or -1) - if ni >= 0 and ni <= #self then - self[self.hindex] = data - self.hindex = ni + elseif name == "key_down" then + local data = self.data + local backup_cache = self.cache + self.cache = nil + local ctrl = kb.isControlDown() + if ctrl and code == keys.d then + return --nil:close + elseif code == keys.tab then + self.cache = backup_cache + core_cursor.tab(self) + elseif code == keys.enter or code == keys.numpadenter then self:move(self.len) - self:update(-self.len) - self:update(self[ni]) + self:update("\n") + elseif code == keys.up or code == keys.down then + local ni = self.hindex + (code == keys.up and 1 or -1) + if ni >= 0 and ni <= #self then + self[self.hindex] = data + self.hindex = ni + self:move(self.len) + self:update(-self.len) + self:update(self[ni]) + end + elseif code == keys.left or code == keys.back or code == keys.w and ctrl then + local value = ctrl and ((unicode.sub(data, 1, self.index):find("%s[^%s]+%s*$") or 0) - self.index) or -1 + if code == keys.left then + self:move(value) + else + self:update(value) + end + elseif code == keys.right then + self:move(ctrl and ((data:find("%s[^%s]", self.index + 1) or self.len) - self.index) or 1) + elseif code == keys.home then self:move(-self.len) + elseif code == keys["end"] then self:move( self.len) + elseif code == keys.delete then self:update(1) + elseif char >= 32 then self:update(unicode.char(char)) + else self.cache = backup_cache -- ignored chars shouldn't clear hint cache end - elseif code == keys.left or code == keys.back or code == keys.w and ctrl then - value = ctrl and ((unicode.sub(data, 1, self.index):find("%s[^%s]+%s*$") or 0) - self.index) or -1 - if code == keys.left then - self:move(value) - value = false -- don't also update (cut) - end - elseif code == keys.right then - self:move(ctrl and ((data:find("%s[^%s]", self.index + 1) or self.len) - self.index) or 1) - elseif code == keys.home then self:move(-self.len) - elseif code == keys["end"] then self:move( self.len) - elseif code == keys.delete then value = 1 - elseif char >= 32 then value = unicode.char(char) - else self.cache = backup_cache -- ignored chars shouldn't clear hint cache - end - if value then - self:update(value) end return true end -function core_cursor.new(base, window, output) - local result = base or {} - result.tails = {} - result.data = "" - result.index = 0 - result.len = 0 - result.sy = 0 - result.hindex = 0 - result.window = window - result.output = output - result.clear = "\27[J" -- echo'd to clear the input text in the tty - result.super = super - return setmetatable(result, { __index = super }) +-- echo'd to clear the input text in the tty +core_cursor.vertical.clear = "\27[J" + +function core_cursor.new(base, index) + -- if base has defined any methods, those are called first + -- any new methods here are "super" methods to base + base = base or {} + base.super = base.super or index or core_cursor.vertical + setmetatable(base, getmetatable(base) or { __index = base.super }) + if not base.data then + base:update() + end + return base +end + +function core_cursor.read(cursor) + local last = cursor.next or "" + cursor.next = nil + if #last > 0 then + cursor:handle("clipboard", last) + end + + -- address checks + local address_check = + { + key_down = tty.keyboard, + clipboard = tty.keyboard, + touch = tty.screen, + drag = tty.screen, + drop = tty.screen + } + + while true do + local next_line = cursor.data:find("\10") + if next_line then + local result = cursor.data:sub(1, next_line) + local overflow = cursor.data:sub(next_line + 1) + local history = text.trim(result) + if history ~= "" and history ~= cursor[1] then + table.insert(cursor, 1, history) + cursor[(tonumber(os.getenv("HISTSIZE")) or 10) + 1] = nil + end + cursor[0] = nil + cursor:update() + cursor.next = overflow + return result + end + + cursor:echo() + local pack = table.pack(computer.pullSignal(tty.window.blink and .5 or math.huge)) + local name = pack[1] + cursor:echo(not name) + + if name then + local filter_address = address_check[name] + if not filter_address or filter_address() == pack[2] then + local ret, why = cursor:handle(name, table.unpack(pack, 3, pack.n)) + if not ret then + return ret, why + end + end + end + end end require("package").delay(core_cursor, "/lib/core/full_cursor.lua") diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/devfs/02_utils.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/devfs/02_utils.lua index a34c49abc..90ea24711 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/devfs/02_utils.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/devfs/02_utils.lua @@ -18,7 +18,7 @@ return }, null = { - open = function(mode) + open = function() return { read = function() end, @@ -34,9 +34,9 @@ return end return { - read = function(self, n) + read = function(_, n) local chars = {} - for i=1,n do + for _=1,n do table.insert(chars,string.char(math.random(0,255))) end return table.concat(chars) @@ -47,4 +47,17 @@ return return math.huge end }, + zero = + { + open = function() + return + { + read = function(_, n) return ("\0"):rep(n) end, + write = function() end + } + end, + size = function() + return math.huge + end + }, } diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_buffer.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_buffer.lua index b3e2382ca..98d5fae55 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_buffer.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_buffer.lua @@ -142,7 +142,7 @@ function buffer:readBytesOrChars(readChunk, n) sub = unicode.sub end local data = "" - repeat + while len(data) ~= n do if len(self.bufferRead) == 0 then local result, reason = readChunk(self) if not result then @@ -156,7 +156,7 @@ function buffer:readBytesOrChars(readChunk, n) local left = n - len(data) data = data .. sub(self.bufferRead, 1, left) self.bufferRead = sub(self.bufferRead, left + 1) - until len(data) == n + end return data end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_cursor.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_cursor.lua index 55048e5b6..671ac1838 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_cursor.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_cursor.lua @@ -1,46 +1,29 @@ local core_cursor = require("core/cursor") local unicode = require("unicode") local kb = require("keyboard") +local tty = require("tty") + +core_cursor.horizontal = {} function core_cursor.touch(cursor, gx, gy) - if cursor.data == "" then - return true - end - local win = cursor.window - gx, gy = gx - win.dx, gy - win.dy - while true do - local x, y, d = win.x, win.y, win.width - local dx = ((gy*d+gx)-(y*d+x)) - if dx == 1 then - dx = unicode.wlen(unicode.sub(cursor.data, cursor.index + 1, cursor.index + 1)) == 2 and 0 or dx - end - if dx == 0 then - break - end - cursor:move(dx > 0 and 1 or -1) - if x == win.x and y == win.y then - break + if cursor.len > 0 then + local win = tty.window + gx, gy = gx - win.dx, gy - win.dy + while true do + local x, y, d = win.x, win.y, win.width + local dx = ((gy*d+gx)-(y*d+x)) + if dx == 1 then + dx = unicode.wlen(unicode.sub(cursor.data, cursor.index + 1, cursor.index + 1)) == 2 and 0 or dx + end + if dx == 0 then + break + end + cursor:move(dx > 0 and 1 or -1) + if x == win.x and y == win.y then + break + end end end - return true -end - -function core_cursor.clipboard(cursor, char) - cursor.cache = nil - local first_line, end_index = char:find("\13?\10") - local after = "" - if first_line then - after = char:sub(end_index + 1) - char = char:sub(1, first_line - 1) - end - cursor:update(char) - if after ~= "" then - -- todo look at postponing the text on cursor - local keyboard = require("tty").keyboard() - require("computer").pushSignal("key_down", keyboard, 13, 28) - require("computer").pushSignal("clipboard", keyboard, after) - end - return first_line and char or true end function core_cursor.tab(cursor) @@ -74,3 +57,67 @@ function core_cursor.tab(cursor) cursor:update(next, -tail) end end + +function core_cursor.horizontal:scroll(num, final_index) + self:move(self.vindex - self.index) -- go to left edge + -- shift (v)index by num + self.vindex = self.vindex + num + self.index = self.index + num + + self:echo("\0277".. -- remember the location + unicode.sub(self.data, self.index + 1).. -- write part after + "\27[K\0278") -- clear tail and restore left edge + + self:move(final_index - self.index) -- move to final_index location +end + +function core_cursor.horizontal:echo(arg, num) + local w = tty.window + w.nowrap = self.nowrap + if arg == "" then -- special scroll request + local width = w.width + if w.x >= width then + -- the width that matters depends on the following char width + width = width - math.max(unicode.wlen(unicode.sub(self.data, self.index + 1, self.index + 1)) - 1, 0) + if w.x > width then + local s1 = unicode.sub(self.data, self.vindex + 1, self.index) + self:scroll(unicode.len(unicode.wtrunc(s1, w.x - width + 1)), self.index) + end + end + -- scroll is safe now, return as normal below + elseif arg == kb.keys.left then + if self.index < self.vindex then + local s2 = unicode.sub(self.data, self.index + 1) + w.x = w.x - num + unicode.wlen(unicode.sub(s2, 1, self.vindex - self.index)) + local current_x = w.x + self:echo(s2) + w.x = current_x + self.vindex = self.index + return true + end + elseif arg == kb.keys.right then + w.x = w.x + num + return self:echo("") -- scroll + end + return core_cursor.vertical.echo(self, arg, num) +end + +function core_cursor.horizontal:update(arg, back) + if back then + -- if we're just going to render arg and move back, and we're not wrapping, just render arg + -- back may be more or less from current x + self:update(arg, false) + local x = tty.window.x + self:echo(arg) -- nowrap echo + tty.window.x = x + self:move(self.len - self.index + back) -- back is negative from end + return true + elseif not arg then -- reset + self.nowrap = true + self.clear = "\27[K" + self.vindex = 0 -- visual/virtual index + end + return core_cursor.vertical.update(self, arg, back) +end + +setmetatable(core_cursor.horizontal, { __index = core_cursor.vertical }) diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_event.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_event.lua index d1e5710d7..ef90a8b06 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_event.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_event.lua @@ -59,3 +59,17 @@ function event.ignore(name, callback) return false end +function event.onError(message) + local log = io.open("/tmp/event.log", "a") + if log then + pcall(log.write, log, tostring(message), "\n") + log:close() + end +end + +function event.timer(interval, callback, times) + checkArg(1, interval, "number") + checkArg(2, callback, "function") + checkArg(3, times, "number", "nil") + return event.register(false, callback, interval, times) +end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_filesystem.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_filesystem.lua index 108373619..1a67f1763 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_filesystem.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_filesystem.lua @@ -1,5 +1,6 @@ local filesystem = require("filesystem") local component = require("component") +local shell = require("shell") function filesystem.makeDirectory(path) if filesystem.exists(path) then @@ -179,8 +180,8 @@ local function bind_proxy(path) -- turn /tmp/foo into foo local rest = real:sub(#real_fs_path + 1) local function wrap_relative(fp) - return function(path, ...) - return fp(filesystem.concat(rest, path), ...) + return function(mpath, ...) + return fp(filesystem.concat(rest, mpath), ...) end end local bind = { @@ -236,3 +237,126 @@ function filesystem.internal.proxy(filter, options) end return proxy end + +function filesystem.remove(path) + local function removeVirtual() + local _, _, vnode, vrest = filesystem.findNode(filesystem.path(path), false, true) + -- vrest represents the remaining path beyond vnode + -- vrest is nil if vnode reaches the full path + -- thus, if vrest is NOT NIL, then we SHOULD NOT remove children nor links + if not vrest then + local name = filesystem.name(path) + if vnode.children[name] or vnode.links[name] then + vnode.children[name] = nil + vnode.links[name] = nil + while vnode and vnode.parent and not vnode.fs and not next(vnode.children) and not next(vnode.links) do + vnode.parent.children[vnode.name] = nil + vnode = vnode.parent + end + return true + end + end + -- return false even if vrest is nil because this means it was a expected + -- to be a real file + return false + end + local function removePhysical() + local node, rest = filesystem.findNode(path) + if node.fs and rest then + return node.fs.remove(rest) + end + return false + end + local success = removeVirtual() + success = removePhysical() or success -- Always run. + if success then return true + else return nil, "no such file or directory" + end +end + +function filesystem.rename(oldPath, newPath) + if filesystem.isLink(oldPath) then + local _, _, vnode, _ = filesystem.findNode(filesystem.path(oldPath)) + local target = vnode.links[filesystem.name(oldPath)] + local result, reason = filesystem.link(target, newPath) + if result then + filesystem.remove(oldPath) + end + return result, reason + else + local oldNode, oldRest = filesystem.findNode(oldPath) + local newNode, newRest = filesystem.findNode(newPath) + if oldNode.fs and oldRest and newNode.fs and newRest then + if oldNode.fs.address == newNode.fs.address then + return oldNode.fs.rename(oldRest, newRest) + else + local result, reason = filesystem.copy(oldPath, newPath) + if result then + return filesystem.remove(oldPath) + else + return nil, reason + end + end + end + return nil, "trying to read from or write to virtual directory" + end +end + +local isAutorunEnabled = nil +local function saveConfig() + local root = filesystem.get("/") + if root and not root.isReadOnly() then + local f = filesystem.open("/etc/filesystem.cfg", "w") + if f then + f:write("autorun="..tostring(isAutorunEnabled)) + f:close() + end + end +end + +function filesystem.isAutorunEnabled() + if isAutorunEnabled == nil then + local env = {} + local config = loadfile("/etc/filesystem.cfg", nil, env) + if config then + pcall(config) + isAutorunEnabled = not not env.autorun + else + isAutorunEnabled = true + end + saveConfig() + end + return isAutorunEnabled +end + +function filesystem.setAutorunEnabled(value) + checkArg(1, value, "boolean") + isAutorunEnabled = value + saveConfig() +end + +os.remove = filesystem.remove +os.rename = filesystem.rename + +os.execute = function(command) + if not command then + return type(shell) == "table" + end + return shell.execute(command) +end + +function os.exit(code) + error({reason="terminated", code=code}, 0) +end + +function os.tmpname() + local path = os.getenv("TMPDIR") or "/tmp" + if filesystem.exists(path) then + for _ = 1, 10 do + local name = filesystem.concat(path, tostring(math.random(1, 0x7FFFFFFF))) + if not filesystem.exists(name) then + return name + end + end + end +end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_ls.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_ls.lua index 57e178153..491ac46b8 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_ls.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_ls.lua @@ -36,27 +36,6 @@ end local ec = 0 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] - if type(name) == "table" then - return name - end - local info = {} - info.key = name - info.path = name:sub(1, 1) == "/" and "" or names.path - info.full_path = fs.concat(info.path, name) - info.isDir = fs.isDirectory(info.full_path) - info.name = name:gsub("/+$", "") .. (ops.p and info.isDir and "/" or "") - info.sort_name = info.name:gsub("^%.","") - info.isLink, info.link = fs.isLink(info.full_path) - info.size = info.isLink and 0 or fs.size(info.full_path) - info.time = fs.lastModified(info.full_path) - info.fs = fs.get(info.full_path) - info.ext = info.name:match("(%.[^.]+)$") or "" - names[index] = info - return info -end -local function toArray(i) local r={} for n in i do r[#r+1]=n end return r end local set_color = function() end local function colorize() return end if fOut and not ops["no-color"] then @@ -89,7 +68,7 @@ function msft.tail(names) end local totalSize, totalFiles, totalDirs = 0, 0, 0 for i=1,#names do - local info = stat(names, i) + local info = names[i] if info.isDir then totalDirs = totalDirs + 1 else @@ -168,15 +147,10 @@ local function filter(names) if ops.a then return names end - local set = {} - for key, value in pairs(names) do - if type(key) == "number" then - local info = stat(names, key) - if fs.name(info.name):sub(1, 1) ~= "." then - table.insert(set, names[key]) - end - else - set[key] = value + local set = { path = names.path } + for _, info in ipairs(names) do + if fs.name(info.name):sub(1, 1) ~= "." then + set[#set + 1] = info end end return set @@ -185,10 +159,9 @@ end local function sort(names) local once = false local function ni(v) - local vname = type(v) == "string" and v or v.key for i=1,#names do - local info = stat(names, i) - if info.key == vname then + local info = names[i] + if info.key == v.key then return i end end @@ -196,8 +169,8 @@ local function sort(names) local function sorter(key) once = true table.sort(names, function(a, b) - local ast = stat(names, ni(a)) - local bst = stat(names, ni(b)) + local ast = names[ni(a)] + local bst = names[ni(b)] return ast[key] > bst[key] end) end @@ -218,7 +191,7 @@ local function dig(names, dirs, dir) if ops.R then local di = 1 for i=1,#names do - local info = stat(names, i) + local info = names[i] if info.isDir then local path = dir..(dir:sub(-1) == "/" and "" or "/") table.insert(dirs, di, path..info.name) @@ -238,12 +211,12 @@ local function display(names) local max_size_width = 1 local max_date_width = 0 for i=1,lines.n do - local info = stat(names, i) + local info = names[i] max_size_width = math.max(max_size_width, formatSize(info.size):len()) max_date_width = math.max(max_date_width, formatDate(info.time):len()) end mt.__index = function(_, index) - local info = stat(names, index) + local info = names[index] local file_type = info.isLink and 'l' or info.isDir and 'd' or 'f' local link_target = info.isLink and string.format(" -> %s", info.link:gsub("/+$", "") .. (info.isDir and "/" or "")) or "" local write_mode = info.fs.isReadOnly() and '-' or 'w' @@ -257,7 +230,7 @@ local function display(names) elseif ops["1"] or not fOut then lines.n = #names mt.__index = function(_, index) - local info = stat(names, index) + local info = names[index] return {{color = colorize(info), name = info.name}} end else -- columns @@ -271,7 +244,7 @@ local function display(names) for r=1,items_per_column do local ri = real(column_index, r) if not ri then break end - local info = stat(names, ri) + local info = names[ri] max = math.max(max, unicode.wlen(info.name)) end return max @@ -297,7 +270,7 @@ local function display(names) __index=function(_, column_index) local ri = real(column_index, line_index) if not ri then return end - local info = stat(names, ri) + local info = names[ri] local name = info.name return {color = colorize(info), name = name .. string.rep(' ', max_name(column_index) - unicode.wlen(name) + (column_index < num_columns and 2 or 0))} end, @@ -313,6 +286,7 @@ local function display(names) set_color(e.color) io.write(e.name) end + set_color() print() end msft.tail(names) @@ -325,6 +299,23 @@ if #dirsArg > 1 or ops.R then io.write(path,":\n") end end + +local function stat(path, name) + local info = {} + info.key = name + info.path = name:sub(1, 1) == "/" and "" or path + info.full_path = fs.concat(info.path, name) + info.isDir = fs.isDirectory(info.full_path) + info.name = name:gsub("/+$", "") .. (ops.p and info.isDir and "/" or "") + info.sort_name = info.name:gsub("^%.","") + info.isLink, info.link = fs.isLink(info.full_path) + info.size = info.isLink and 0 or fs.size(info.full_path) + info.time = fs.lastModified(info.full_path) + info.fs = fs.get(info.full_path) + info.ext = info.name:match("(%.[^.]+)$") or "" + return info +end + local function displayDirList(dirs) while #dirs > 0 do local dir = table.remove(dirs, 1) @@ -334,8 +325,10 @@ local function displayDirList(dirs) if not list then perr(reason) else - local names = toArray(list) - names.path = path + local names = { path = path } + for name in list do + names[#names + 1] = stat(path, name) + end display(dig(sort(filter(names)), dirs, dir)) end end @@ -352,7 +345,7 @@ for _,dir in ipairs(dirsArg) do elseif fs.isDirectory(path) then table.insert(dir_set, dir) else -- file or link - table.insert(file_set, dir) + table.insert(file_set, stat(dir, dir)) end end @@ -366,7 +359,6 @@ end) io.output():flush() io.output():setvbuf("no") -set_color() assert(ok, msg) diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_text.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_text.lua index dc7f2eb4d..2f9c33e64 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_text.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_text.lua @@ -1,6 +1,7 @@ local text = require("text") local tx = require("transforms") local unicode = require("unicode") +local process = require("process") -- separate string value into an array of words delimited by whitespace -- groups by quotes @@ -91,7 +92,7 @@ function text.internal.splitWords(words, delimiters) add_part(part) else local part_text_splits = text.split(part.txt, delimiters) - tx.foreach(part_text_splits, function(sub_txt, spi) + tx.foreach(part_text_splits, function(sub_txt) local delim = #text.split(sub_txt, delimiters, true) == 0 next_word = next_word or delim add_part({txt=sub_txt,qr=qr}) @@ -177,7 +178,8 @@ function text.internal.reader(txt, mode) _.txt = nil return true end, - }, {__index=text.internal.stream_base(mode:match("b"))}) + }, {__index=text.internal.stream_base((mode or ""):match("b"))}) + process.closeOnExit(reader) return require("buffer").new("r", reader) end @@ -201,7 +203,7 @@ function text.internal.writer(ostream, mode, append_txt) local pre = _.psub(_.txt, 1, _.index) local vs = {} local pos = _.psub(_.txt, _.index + 1) - for i,v in ipairs({...}) do + for _,v in ipairs({...}) do table.insert(vs, v) end vs = table.concat(vs) @@ -218,7 +220,8 @@ function text.internal.writer(ostream, mode, append_txt) _.txt = nil return true end, - }, {__index=text.internal.stream_base(mode:match("b"))}) + }, {__index=text.internal.stream_base((mode or ""):match("b"))}) + process.closeOnExit(writer) return require("buffer").new("w", writer) end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_vt.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_vt.lua index df4e1c467..ee4b4f494 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_vt.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_vt.lua @@ -1,5 +1,4 @@ local vt100 = require("vt100") -local unicode = require("unicode") local rules = vt100.rules @@ -60,7 +59,7 @@ rules[{"%[", "[012]?", "J"}] = function(window, _, n) clear_line(window, _, n) n = tonumber(n) or 0 local y = n == 0 and (window.y + 1) or 1 - local rep = n == 1 and (window.y - 1) or (window.height - 1) + local rep = n == 1 and (window.y - 1) or (window.height) window.gpu.fill(1 + window.dx, y + window.dy, window.width, rep, " ") end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/lua_shell.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/lua_shell.lua index 7f01aee54..12cf51f94 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/lua_shell.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/lua_shell.lua @@ -1,5 +1,5 @@ local package = require("package") -local tty = require("tty") +local term = require("term") local function optrequire(...) local success, module = pcall(require, ...) @@ -91,9 +91,9 @@ io.write("\27[33mEnter a statement and hit enter to evaluate it.\n") io.write("Prefix an expression with '=' to show its value.\n") io.write("Press Ctrl+D to exit the interpreter.\n\27[37m") -while tty.isAvailable() do +while term.isAvailable() do io.write(env._PROMPT) - local command = tty.read(read_handler) + local command = term.read(read_handler) if not command then -- eof return end @@ -122,7 +122,7 @@ while tty.isAvailable() do if not ok then io.stderr:write("crashed serializing result: ", tostring(why)) end - if tty.getCursor() > 1 then + if term.getCursor() > 1 then io.write("\n") end end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/event.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/event.lua index 52ad8761b..37cd42dde 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/event.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/event.lua @@ -102,14 +102,6 @@ function event.listen(name, callback) return event.register(name, callback, math.huge, math.huge) end -function event.onError(message) - local log = io.open("/tmp/event.log", "a") - if log then - pcall(log.write, log, tostring(message), "\n") - log:close() - end -end - function event.pull(...) local args = table.pack(...) if type(args[1]) == "string" then @@ -149,13 +141,6 @@ function event.pullFiltered(...) until computer.uptime() >= deadline end -function event.timer(interval, callback, times) - checkArg(1, interval, "number") - checkArg(2, callback, "function") - checkArg(3, times, "number", "nil") - return event.register(false, callback, interval, times) -end - -- users may expect to find event.push to exist event.push = computer.pushSignal diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/filesystem.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/filesystem.lua index bdb315026..ae36a963e 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/filesystem.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/filesystem.lua @@ -2,7 +2,6 @@ local component = require("component") local unicode = require("unicode") local filesystem = {} -local isAutorunEnabled = nil local mtab = {name="", children={}, links={}} local fstab = {} @@ -21,17 +20,6 @@ local function segments(path) return parts end -local function saveConfig() - local root = filesystem.get("/") - if root and not root.isReadOnly() then - local f = filesystem.open("/etc/filesystem.cfg", "w") - if f then - f:write("autorun="..tostring(isAutorunEnabled)) - f:close() - end - end -end - local function findNode(path, create, resolve_links) checkArg(1, path, "string") local visited = {} @@ -94,27 +82,6 @@ end ------------------------------------------------------------------------------- -function filesystem.isAutorunEnabled() - if isAutorunEnabled == nil then - local env = {} - local config = loadfile("/etc/filesystem.cfg", nil, env) - if config then - pcall(config) - isAutorunEnabled = not not env.autorun - else - isAutorunEnabled = true - end - saveConfig() - end - return isAutorunEnabled -end - -function filesystem.setAutorunEnabled(value) - checkArg(1, value, "boolean") - isAutorunEnabled = value - saveConfig() -end - function filesystem.canonical(path) local result = table.concat(segments(path), "/") if unicode.sub(path, 1, 1) == "/" then @@ -298,14 +265,6 @@ function filesystem.list(path) end end -function filesystem.remove(path) - return require("tools/fsmod").remove(path, findNode) -end - -function filesystem.rename(oldPath, newPath) - return require("tools/fsmod").rename(oldPath, newPath, findNode) -end - function filesystem.open(path, mode) checkArg(1, path, "string") mode = tostring(mode or "r") @@ -327,30 +286,22 @@ function filesystem.open(path, mode) return nil, reason end - local function create_handle_method(key) - return function(self, ...) - if not self.handle then - return nil, "file is closed" - end - return self.fs[key](self.handle, ...) - end - end - - local stream = - { + return setmetatable({ fs = node.fs, handle = handle, - close = function(self) - if self.handle then - self.fs.close(self.handle) + }, {__index = function(tbl, key) + if not tbl.fs[key] then return end + if not tbl.handle then + return nil, "file is closed" + end + return function(self, ...) + local h = self.handle + if key == "close" then self.handle = nil end + return self.fs[key](h, ...) end - } - stream.read = create_handle_method("read") - stream.seek = create_handle_method("seek") - stream.write = create_handle_method("write") - return stream + end}) end filesystem.findNode = findNode 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 f0b157e7a..d535a5639 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/process.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/process.lua @@ -73,11 +73,15 @@ function process.load(path, env, init, name) return 128 -- syserr end, ...) } + --result[1] is false if the exception handler also crashed if not result[1] and type(result[2]) ~= "number" then - require("event").onError(string.format("process library exception handler crashed: %s", tostring(result[2]))) + pcall(require("event").onError, string.format("process library exception handler crashed: %s", tostring(result[2]))) end + + -- onError opens a file, you can't open a file without a process, we close the process last process.internal.close(thread, result) + return select(2, table.unpack(result)) end, true) local new_proc = 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 1f923161e..f3890ac53 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/sh.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/sh.lua @@ -73,11 +73,11 @@ function sh.internal.resolveActions(input, resolver, resolved) resolved[key] = resolver(key) local value = resolved[key] if value and key ~= value then - local replacement_tokens, reason = sh.internal.resolveActions(value, resolver, resolved) + local replacement_tokens, resolve_reason = sh.internal.resolveActions(value, resolver, resolved) if not replacement_tokens then - return replacement_tokens, reason + return replacement_tokens, resolve_reason end - simple = simple and reason + simple = simple and resolve_reason words = tx.concat(replacement_tokens, words) next = table.remove(words,1) end 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 999127967..3868fc79b 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua @@ -3,17 +3,12 @@ local computer = require("computer") local process = require("process") local event = require("event") local core_cursor = require("core/cursor") -local unicode = require("unicode") local kb = require("keyboard") local keys = kb.keys local term = setmetatable({internal={}}, {__index=tty}) -function term.internal.window() - return process.info().data.window -end - local function as_window(window, func, ...) local data = process.info().data if not data.window or not window then @@ -45,99 +40,33 @@ function term.internal.open(...) }) -- 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 + if rawget(tty, "window") then + for _,p in pairs(process.list) do + if not p.parent then + p.data.window = tty.window + break + end end - process.info(init_index - 1).data.window = tty.window tty.window = nil setmetatable(tty, { __index = function(_, key) if key == "window" then - return term.internal.window() + return process.info().data.window end end }) end as_window(window, tty.setViewport, w, h, dx, dy, 1, 1) + as_window(window, tty.bind, tty.gpu()) return window end -local function horizontal_echo(self, arg) - if arg == "" then -- special scroll request - local w = self.window - local width = w.width - if w.x >= width then - -- render all text from 1 to width from end - -- the width that matters depends on the following char width - local next_overlap = math.max(unicode.wlen(unicode.sub(self.data, self.index + 1, self.index + 1)) - 1, 0) - width = width - next_overlap - if w.x > width then - local s1, s2 = - unicode.sub(self.data, self.vindex + 1, self.index), - unicode.sub(self.data, self.index + 1) - -- vindex is really the only way we know where input started - local s1wlen = unicode.wlen(s1) - -- adjust is how far to push the string back - local adjust = w.x - width - local wlen_available = s1wlen - adjust + 1 -- +1 because wtrunc removes the final position - -- now we have to resize s2 to fit in wlen_available, from the end, not the front - local trunc = unicode.wtrunc(unicode.reverse(s1), wlen_available) - -- is it faster to reverse again, or take wlen and sub, probably just reverse - trunc = unicode.reverse(trunc) - -- a double wide may have just been cut - local cutwlen = s1wlen - unicode.wlen(trunc) - -- we have to move to position 1, which should be at vindex, or s2wlen back from x - w.x = w.x - s1wlen - self.output:write(trunc .. s2 .. (" "):rep(cutwlen)) - self.vindex = self.index - unicode.len(trunc) - w.x = width - cutwlen + 1 - end - elseif w.x < 1 then - -- render all text from 1 to width from end - local s2 = unicode.sub(self.data, self.index + 1) - w.x = 1 - self.output:write(s2) - w.x = 1 - self.vindex = self.index - end - -- scroll is safe now, return as normal below - elseif arg == keys.left then - if self.index < self.vindex then - self.window.x = 0 - return self:echo("") - end - elseif arg == keys.right then - self.window.x = self.window.x + unicode.wlen(unicode.sub(self.data, self.index, self.index)) - return self:echo("") - end - return self.super.echo(self, arg) -end +local function create_cursor(history, ops) + local cursor = history or {} + cursor.hint = ops.hint or cursor.hint -local function horizontal_update(self, arg, back) - if back then - -- if we're just going to render arg and move back, and we're not wrapping, just render arg - -- back may be more or less from current x - local arg_len = unicode.len(arg) - local x = self.window.x - self.output:write(arg) - self.window.x = x - self.data = self.data .. arg - self.len = self.len + arg_len -- recompute len - self:move(self.len - self.index + back) -- back is negative from end - return true - end - return self.super.update(self, arg, back) -end - -local function add_cursor_handlers(cursor, ops) - -- cursor inheritance gives super: to access base methods - -- function cursor:method(...) - -- return self.super.method(self, ...) - -- end local filter = ops.filter or cursor.filter if filter then if type(filter) == "string" then @@ -160,32 +89,29 @@ local function add_cursor_handlers(cursor, ops) local pwchar = ops.pwchar or cursor.pwchar local nobreak = ops.dobreak == false or cursor.dobreak == false - if pwchar or nobreak or cursor.nowrap then + if pwchar or nobreak then if type(pwchar) == "string" then local pwchar_text = pwchar pwchar = function(text) return text:gsub(".", pwchar_text) end end - function cursor:echo(arg) - if pwchar and type(arg) == "string" and #arg > 0 then -- "" is used for scrolling + function cursor:echo(arg, ...) + if pwchar and type(arg) == "string" and #arg > 0 and not arg:match("^\27") then -- "" is used for scrolling arg = pwchar(arg) - elseif nobreak and arg == keys.enter then + elseif nobreak and arg == "\n" then arg = "" end - local echo = cursor.nowrap and horizontal_echo or self.super.echo - return echo(self, arg) + return self.super.echo(self, arg, ...) end end - if cursor.nowrap then - cursor.update = horizontal_update - tty.window.nowrap = true - cursor.vindex = 0 -- visual/virtual index - end + + return core_cursor.new(cursor, cursor.nowrap and core_cursor.horizontal) end -- cannot use term.write = io.write because io.write invokes metatable function term.write(value, wrap) + io.stdout:flush() local previous_nowrap = tty.window.nowrap tty.window.nowrap = wrap == false io.write(value) @@ -194,16 +120,13 @@ function term.write(value, wrap) end function term.read(history, dobreak, hint, pwchar, filter) - local cursor = history or {} - cursor.hint = cursor.hint or hint - - add_cursor_handlers(cursor, { + tty.window.cursor = create_cursor(history, { dobreak = dobreak, pwchar = pwchar, filter = filter, + hint = hint }) - - return tty.read(cursor) + return io.stdin:readLine(false) end function term.getGlobalArea() @@ -230,7 +153,7 @@ function term.pull(...) timeout = computer.uptime() + table.remove(args, 1) args.n = args.n - 1 end - local cursor = core_cursor.new(nil, tty.window, tty.stream) -- cursors can blink (base arg is optional) + local cursor = core_cursor.new() while timeout >= computer.uptime() and cursor:echo() do local s = table.pack(event.pull(.5, table.unpack(args, 1, args.n))) cursor:echo(not s[1]) diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/fsmod.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/tools/fsmod.lua deleted file mode 100644 index f2905b898..000000000 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/tools/fsmod.lua +++ /dev/null @@ -1,68 +0,0 @@ -local filesystem = require("filesystem") - -local lib = {} -function lib.remove(path, findNode) - local function removeVirtual() - local _, _, vnode, vrest = findNode(filesystem.path(path), false, true) - -- vrest represents the remaining path beyond vnode - -- vrest is nil if vnode reaches the full path - -- thus, if vrest is NOT NIL, then we SHOULD NOT remove children nor links - if not vrest then - local name = filesystem.name(path) - if vnode.children[name] or vnode.links[name] then - vnode.children[name] = nil - vnode.links[name] = nil - while vnode and vnode.parent and not vnode.fs and not next(vnode.children) and not next(vnode.links) do - vnode.parent.children[vnode.name] = nil - vnode = vnode.parent - end - return true - end - end - -- return false even if vrest is nil because this means it was a expected - -- to be a real file - return false - end - local function removePhysical() - local node, rest = findNode(path) - if node.fs and rest then - return node.fs.remove(rest) - end - return false - end - local success = removeVirtual() - success = removePhysical() or success -- Always run. - if success then return true - else return nil, "no such file or directory" - end -end - -function lib.rename(oldPath, newPath, findNode) - if filesystem.isLink(oldPath) then - local _, _, vnode, _ = findNode(filesystem.path(oldPath)) - local target = vnode.links[filesystem.name(oldPath)] - local result, reason = filesystem.link(target, newPath) - if result then - filesystem.remove(oldPath) - end - return result, reason - else - local oldNode, oldRest = findNode(oldPath) - local newNode, newRest = findNode(newPath) - if oldNode.fs and oldRest and newNode.fs and newRest then - if oldNode.fs.address == newNode.fs.address then - return oldNode.fs.rename(oldRest, newRest) - else - local result, reason = filesystem.copy(oldPath, newPath) - if result then - return filesystem.remove(oldPath) - else - return nil, reason - end - end - end - return nil, "trying to read from or write to virtual directory" - end -end - -return lib diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tty.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/tty.lua index 0cfc65f00..91b6c6d66 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/tty.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/tty.lua @@ -27,7 +27,7 @@ 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 + if window.fullscreen and screen and not screen_cache[screen] then screen_cache[screen] = true window.width, window.height = window.gpu.getViewport() end @@ -55,50 +55,21 @@ function tty.isAvailable() return not not (gpu and gpu.getScreen()) end -function tty.read(cursor) - tty.window.cursor = cursor - - local stdin = io.stdin - local result = table.pack(pcall(stdin.readLine, stdin, false)) - tty.window.cursor = nil - return select(2, assert(table.unpack(result))) -end - -- PLEASE do not use this method directly, use io.read or term.read function tty.stream.read() - local cursor = require("core/cursor").new(tty.window.cursor, tty.window, tty.stream) - tty.window.cursor = cursor -- used by output to update the sy offset - local ret, why + local core = require("core/cursor") + local cursor = core.new(tty.window.cursor) + -- the window is given the cursor to allow sy updates [needed for wide char wrapping] + -- even if the user didn't set a cursor, we need one to read + tty.window.cursor = cursor - -- address checks - local address_check = - { - key_down = tty.keyboard, - clipboard = tty.keyboard, - touch = tty.screen, - drag = tty.screen - } + local ok, result, reason = xpcall(core.read, debug.traceback, cursor) - while cursor:echo() do - local name, address, char, code = event.pull(tty.window.blink and .5 or math.huge) - - -- we may have lost the cursor during pull - if not cursor:echo(not name) then - ret, why = nil, nil - break - elseif name then - local filter_address = address_check[name] - if not filter_address or filter_address() == address then - ret, why = cursor:handle(name, char, code) - if ret ~= true then - break - end - end - end + if not ok or not result then + pcall(cursor.update, cursor) end - tty.window.cursor = nil - return ret, why + return select(2, assert(ok, result, reason)) end -- PLEASE do not use this method directly, use io.write or term.write @@ -108,13 +79,13 @@ function tty.stream:write(value) return end local window = tty.window - local cursor = window.cursor or {sy=0, tails={}} + local cursor = window.cursor or {sy = 0, tails = {}} local beeped local uptime = computer.uptime local last_sleep = uptime() window.output_buffer = window.output_buffer .. value while true do - if uptime() - last_sleep > 1 then + if uptime() - last_sleep > 3 then os.sleep(0) last_sleep = uptime() end @@ -142,8 +113,8 @@ function tty.stream:write(value) local wlen_remaining = window.width - x + 1 if wlen_remaining < wlen_needed then segment = unicode.wtrunc(segment, wlen_remaining + 1) - local wlen_used = unicode.wlen(segment) - tail = wlen_used < wlen_remaining and " " or "" + wlen_needed = unicode.wlen(segment) + tail = wlen_needed < wlen_remaining and " " or "" cursor.tails[gpu_y - cursor.sy] = tail if not window.nowrap then -- we have to reparse the delimeter @@ -151,13 +122,13 @@ function tty.stream:write(value) -- fake a newline delim = "\n" end - wlen_needed = wlen_used end gpu.set(gpu_x, gpu_y, segment..tail) x = x + wlen_needed end - window.output_buffer = ei and window.output_buffer:sub(ei + 1) or "" + window.output_buffer = ei and window.output_buffer:sub(ei + 1) or + unicode.sub(window.output_buffer, window.width + 1) if delim == "\t" then x = ((x-1) - ((x-1) % 8)) + 9 @@ -183,6 +154,8 @@ function tty.getCursor() end function tty.setCursor(x, y) + checkArg(1, x, "number") + checkArg(2, y, "number") local window = tty.window window.x, window.y = x, y end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/vt100.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/vt100.lua index 5abc8bd79..d42e64d9c 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/vt100.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/vt100.lua @@ -21,14 +21,14 @@ rules[{"%[", "[%d;]*", "m"}] = function(window, _, number_text) for _,part in ipairs(parts) do local num = tonumber(part[1].txt) last_was_break, num = not num, num or last_was_break and 0 - - if num == 7 then + + local flip = num == 7 + if flip then if not window.flip then local rgb, pal = bg(window.gpu.getForeground()) fg(pal or rgb, not not pal) fg, bg = bg, fg end - window.flip = true elseif num == 5 then window.blink = true elseif num == 0 then @@ -46,6 +46,7 @@ rules[{"%[", "[%d;]*", "m"}] = function(window, _, number_text) set(color) end end + window.flip = flip end end From 2483c1d7d8a948975cc20840497d9ad0ca410785 Mon Sep 17 00:00:00 2001 From: payonel Date: Wed, 29 Aug 2018 02:36:59 -0700 Subject: [PATCH 11/12] update cc bundled cable support for 1.10 --- .../computercraft/BundledRedstoneProvider.scala | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/scala/li/cil/oc/integration/computercraft/BundledRedstoneProvider.scala b/src/main/scala/li/cil/oc/integration/computercraft/BundledRedstoneProvider.scala index feddf6b0c..a3d7f9945 100644 --- a/src/main/scala/li/cil/oc/integration/computercraft/BundledRedstoneProvider.scala +++ b/src/main/scala/li/cil/oc/integration/computercraft/BundledRedstoneProvider.scala @@ -7,7 +7,8 @@ import li.cil.oc.integration.util.BundledRedstone import li.cil.oc.integration.util.BundledRedstone.RedstoneProvider import li.cil.oc.util.BlockPosition import net.minecraft.world.World -import net.minecraftforge.common.util.ForgeDirection +import net.minecraft.util.EnumFacing +import net.minecraft.util.math.BlockPos object BundledRedstoneProvider extends IBundledRedstoneProvider with RedstoneProvider { def init() { @@ -15,11 +16,11 @@ object BundledRedstoneProvider extends IBundledRedstoneProvider with RedstonePro BundledRedstone.addProvider(this) } - override def getBundledRedstoneOutput(world: World, x: Int, y: Int, z: Int, side: Int): Int = - world.getTileEntity(x, y, z) match { + override def getBundledRedstoneOutput(world: World, blockPos: BlockPos, enumFacing: EnumFacing): Int = + world.getTileEntity(blockPos) match { case tile: BundledRedstoneAware => var result = 0 - val colours = tile.bundledOutput(ForgeDirection.VALID_DIRECTIONS(side)) + val colours = tile.bundledOutput(enumFacing) for (colour <- 0 to 15) { if (colours(colour) > 0) result |= 1 << colour } @@ -27,11 +28,11 @@ object BundledRedstoneProvider extends IBundledRedstoneProvider with RedstonePro case _ => -1 } - override def computeInput(pos: BlockPosition, side: ForgeDirection): Int = 0 + override def computeInput(pos: BlockPosition, side: EnumFacing): Int = 0 - override def computeBundledInput(pos: BlockPosition, side: ForgeDirection): Array[Int] = { + override def computeBundledInput(pos: BlockPosition, side: EnumFacing): Array[Int] = { val offset = pos.offset(side) - val strength = ComputerCraftAPI.getBundledRedstoneOutput(pos.world.get, offset.x, offset.y, offset.z, side.getOpposite.ordinal()) + val strength = ComputerCraftAPI.getBundledRedstoneOutput(pos.world.get, offset.toBlockPos, side.getOpposite) if (strength >= 0) { val strengths = new Array[Int](16) for (colour <- 0 to 15) { From ef4bdd363ba2ba3c73fb4a3fe3cb5817ade0a3ea Mon Sep 17 00:00:00 2001 From: payonel Date: Fri, 31 Aug 2018 20:13:53 -0700 Subject: [PATCH 12/12] cherry pick 366d94f08da608d3be30d70ce91e248fa6b4357e to 1.10 Support for the block property system in the geolyzer (closes #2746, #2184) * works correctly with Beetroot * supports chorus * geolyzer.analyze() returns the properties table * geolyzer.analyze() returns metadata again * fix warning EventHandlerVanilla.scala: method getBlockHardness in class Block is deprecated --- .../vanilla/EventHandlerVanilla.scala | 60 ++++++++++++++----- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/src/main/scala/li/cil/oc/integration/vanilla/EventHandlerVanilla.scala b/src/main/scala/li/cil/oc/integration/vanilla/EventHandlerVanilla.scala index c90549b6b..0332f710c 100644 --- a/src/main/scala/li/cil/oc/integration/vanilla/EventHandlerVanilla.scala +++ b/src/main/scala/li/cil/oc/integration/vanilla/EventHandlerVanilla.scala @@ -6,6 +6,9 @@ import li.cil.oc.util.BlockPosition import li.cil.oc.util.ExtendedWorld._ import net.minecraft.block.Block import net.minecraft.block.BlockCrops +import net.minecraft.block.BlockStem +import net.minecraft.block.properties.PropertyInteger +import net.minecraft.block.state.IBlockState import net.minecraft.init.Blocks import net.minecraftforge.fluids.FluidRegistry import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -36,10 +39,11 @@ object EventHandlerVanilla { val z = blockPos.z + rz val index = (rx - e.minX) + ((rz - e.minZ) + (ry - e.minY) * d) * w if (world.isBlockLoaded(pos) && !world.isAirBlock(pos)) { - val block = world.getBlockState(pos).getBlock + val blockState = world.getBlockState(pos) + val block = blockState.getBlock if (block != Blocks.AIR && (includeReplaceable || isFluid(block) || !block.isReplaceable(world, blockPos.toBlockPos))) { val distance = math.sqrt(rx * rx + ry * ry + rz * rz).toFloat - e.data(index) = e.data(index) * distance * Settings.get.geolyzerNoise + block.getBlockHardness(world.getBlockState(pos), world, pos) + e.data(index) = e.data(index) * distance * Settings.get.geolyzerNoise + blockState.getBlockHardness(world, pos) } else e.data(index) = 0 } @@ -49,33 +53,59 @@ object EventHandlerVanilla { private def isFluid(block: Block) = FluidRegistry.lookupFluidForBlock(block) != null + private def getGrowth(blockState: IBlockState) = { + blockState.getPropertyNames().find(prop => {prop.isInstanceOf[PropertyInteger] && prop.getName() == "age"}) match { + case Some(prop) => { + val propAge = prop.asInstanceOf[PropertyInteger] + Some((blockState.getValue(propAge).toFloat / propAge.getAllowedValues().max) max 0 min 1) + } + case None => None + } + } + @SubscribeEvent def onGeolyzerAnalyze(e: GeolyzerEvent.Analyze) { val world = e.host.world - val blockState = world.getBlockState(e.pos) + val blockState = world.getBlockState(e.pos).getActualState(world, e.pos) val block = blockState.getBlock e.data += "name" -> Block.REGISTRY.getNameForObject(block) - e.data += "hardness" -> Float.box(block.getBlockHardness(world.getBlockState(e.pos), world, e.pos)) + e.data += "hardness" -> Float.box(blockState.getBlockHardness(world, e.pos)) e.data += "harvestLevel" -> Int.box(block.getHarvestLevel(blockState)) e.data += "harvestTool" -> block.getHarvestTool(blockState) e.data += "color" -> Int.box(block.getMapColor(blockState).colorValue) + // backward compatibility + e.data += "metadata" -> Int.box({ + try { + block.getMetaFromState(blockState) + } catch { + case _ :IllegalArgumentException => 0 + } + }) + + e.data += "properties" -> { + var props:Map[String, Any] = Map(); + for (prop <- blockState.getProperties().keySet()) { + props += prop.getName() -> blockState.getValue(prop) + } + props + } + if (Settings.get.insertIdsInConverters) { e.data += "id" -> Int.box(Block.getIdFromBlock(block)) } - if (block.isInstanceOf[BlockCrops] || block == Blocks.MELON_STEM || block == Blocks.PUMPKIN_STEM || block == Blocks.CARROTS || block == Blocks.POTATOES) { - e.data += "growth" -> Float.box((block.getMetaFromState(blockState) / 7f) max 0 min 1) - } - if (block == Blocks.COCOA) { - e.data += "growth" -> Float.box(((block.getMetaFromState(blockState) >> 2) / 2f) max 0 min 1) - } - if (block == Blocks.NETHER_WART) { - e.data += "growth" -> Float.box((block.getMetaFromState(blockState) / 3f) max 0 min 1) - } - if (block == Blocks.MELON_BLOCK || block == Blocks.PUMPKIN || block == Blocks.CACTUS || block == Blocks.REEDS) { - e.data += "growth" -> Float.box(1f) + { + if (block.isInstanceOf[BlockCrops] || block.isInstanceOf[BlockStem] || block == Blocks.COCOA || block == Blocks.NETHER_WART || block == Blocks.CHORUS_FLOWER) { + getGrowth(blockState) + } else if (block == Blocks.MELON_BLOCK || block == Blocks.PUMPKIN || block == Blocks.CACTUS || block == Blocks.REEDS || block == Blocks.CHORUS_PLANT) { + Some(1f) + } else { + None + } + } foreach { growth => + e.data += "growth" -> Float.box(growth) } } }