diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/cat.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/cat.lua index fcaeb88e8..fce0f42bc 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/cat.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/cat.lua @@ -2,18 +2,17 @@ local shell = require("shell") local fs = require("filesystem") local args = shell.parse(...) -local ec = 0 if #args == 0 then args = {"-"} end -local input_method, input_param = "read", require("tty").window.width +local input_method, input_param = "read", require("tty").getViewport() for i = 1, #args do local arg = shell.resolve(args[i]) if fs.isDirectory(arg) then io.stderr:write(string.format('cat %s: Is a directory\n', arg)) - ec = 1 + os.exit(1) else local file, reason if args[i] == "-" then @@ -24,7 +23,7 @@ for i = 1, #args do end if not file then io.stderr:write(string.format("cat: %s: %s\n", args[i], tostring(reason))) - ec = 1 + os.exit(1) else repeat local chunk = file[input_method](file, input_param) @@ -36,5 +35,3 @@ for i = 1, #args do end end end - -return ec diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/less.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/less.lua index 486f62b75..b3e49131f 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/less.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/less.lua @@ -1,304 +1,99 @@ local keyboard = require("keyboard") local shell = require("shell") local term = require("term") -- using term for negative scroll feature -local text = require("text") -local unicode = require("unicode") -local computer = require("computer") -local tx = require("transforms") - -if not io.output().tty then - return loadfile(shell.resolve("cat", "lua"), "bt", _G)(...) -end local args = shell.parse(...) if #args > 1 then - io.write("Usage: more \n") + io.write("Usage: less \n") io.write("- or no args reads stdin\n") return 1 end -local arg = args[1] or "-" -local initial_offset - --- test validity of args -do - if arg == "-" then - if not io.stdin then - io.stderr:write("this process has no stdin\n") - return 1 - end - -- stdin may not be core_stdin - initial_offset = io.stdin:seek("cur") - else - local file, reason = io.open(shell.resolve(arg)) - if not file then - io.stderr:write(reason,'\n') - return 1 - end - initial_offset = file:seek("cur") - file:close() - end +local cat_cmd = table.concat({"cat", ...}, " ") +if not io.output().tty then + return os.execute(cat_cmd) end -local width, height = term.getViewport() -local max_display = height - 1 +term.clear() --- mgr is the data manager, it keeps track of what has been loaded --- keeps a reasonable buffer, and keeps track of file handles -local mgr -mgr = -{ - lines = {}, -- current buffer - chunk, -- temp from last read line that hasn't finished wrapping - lines_released = 0, - can_seek = initial_offset, - capacity = math.max(1, math.min(max_display * 10, computer.freeMemory() / 2 / width)), - size = 0, - file = nil, - path = arg ~= "-" and shell.resolve(arg) or nil, - open = function() - mgr.file = mgr.path and io.open(mgr.path) or io.stdin - end, - top_of_file = max_display, - total_lines = nil, -- nil means unknown - latest_line = nil, -- used for status improvements - rollback = function() - if not mgr.can_seek then - return false - end - if not mgr.file then - mgr.open() - elseif not mgr.file:seek("set", 0) then - mgr.close() - return false - end - mgr.lines_released = 0 - mgr.lines = {} - mgr.size = 0 - return true - end, - at = function(line_number) - local index = line_number - mgr.lines_released - if index < 1 then - index = index + mgr.capacity - if #mgr.lines ~= mgr.capacity or index <= mgr.size then - return nil - end - elseif index > mgr.size then - return nil - end - return mgr.lines[index] -- cached - end, - load = function(line_number) - local index = line_number - mgr.lines_released - if mgr.total_lines and mgr.total_lines < line_number then - return nil - end - if mgr.at(line_number) then - return true - end - -- lines[index] is line (lines_released + index) in the file - -- thus index == line_number - lines_released - if index <= 0 then - -- we have previously freed some of the buffer, and now the user wants it back - if not mgr.rollback() then - -- TODO how to nicely fail if can_seek == false - -- or if no more buffers - error("cannot load prior data") - end - return mgr.load(line_number) -- retry - end - if mgr.read_next() then - return mgr.load(line_number) -- retry - end - -- ran out of file, could not reach line_number - end, - write = function(line_number) - local line = mgr.at(line_number) - if not line then return false end - term.write(line) - end, - close = function() - if mgr.file then - mgr.file:close() - mgr.file = nil - end - end, - last = function() - -- return the last line_number available right now in the cache - return mgr.size + mgr.lines_released - end, - check_capacity = function(release) - -- if we have reached capacity - if mgr.size >= mgr.capacity then - if release then - mgr.lines_released = mgr.lines_released + mgr.size - mgr.size = 0 - end - return true - end - end, - insert = function(line) - if mgr.check_capacity() then return false end - mgr.size = mgr.size + 1 - mgr.lines[mgr.size] = line - -- latest_line is not used for computation, just for status reports - mgr.latest_line = math.max(mgr.latest_line or 0, mgr.size + mgr.lines_released) - return true - end, - read_next = function() - -- total_lines indicates we've reached the end previously - -- but have we just prior to this reached the end? - if mgr.last() == mgr.total_lines then - -- then there is no more after that point - return nil - end - if not mgr.file then - mgr.open() - end - mgr.check_capacity(true) - if not mgr.chunk then - mgr.chunk = mgr.file:read("*l") - if not mgr.chunk then - mgr.total_lines = mgr.size + mgr.lines_released -- now file length is known - mgr.close() - end - end - while mgr.chunk do - local wrapped, next = text.wrap(text.detab(mgr.chunk), width, width) - -- insert fails if capacity is full - if not mgr.insert(wrapped) then - return mgr.last() - end - mgr.chunk = next - end - - return mgr.last() - end, - scroll = function(num) - if num < 0 then - num = math.max(num, mgr.top_of_file) - if num >= 0 then - return true -- nothing to scroll - end - end - - term.setCursor(1, height) - local y = height - term.clearLine() - - if num < 0 then - term.scroll(num) -- push text down - mgr.top_of_file = mgr.top_of_file - num - y = 1 - term.setCursor(1, y) -- ready to write lines above - num = -num -- now print forward - end - - local range - while num > 0 do - -- trigger load of data if needed - local line_number = y - mgr.top_of_file - - if not mgr.load(line_number) then -- nothing more to read from the file - return range ~= nil -- first time it is nil - end - - -- print num range of what is available, scroll to show it (if bottom of screen) - range = math.min(num, mgr.last() - line_number + 1) - - if y == height then - range = math.min(range, max_display) - term.scroll(range) - y = y - range - term.setCursor(1, y) - mgr.top_of_file = mgr.top_of_file - range - end - - for i=1,range do - mgr.write(line_number + i - 1) - term.setCursor(1, y + i) - end - y = y + range - - num = num - range - end - - return true - end, - print_status = function() - local first = mgr.top_of_file >= 1 and 1 or 1 - mgr.top_of_file - local perc = not mgr.total_lines and "--" or tostring((max_display - mgr.top_of_file) / mgr.total_lines * 100):gsub("%..*","") - local last_plus = mgr.total_lines and "" or "+" - local status = string.format("%s lines %d-%d/%s %s%%", mgr.path or "-", first, max_display - mgr.top_of_file, tostring(mgr.total_lines or mgr.latest_line)..last_plus, perc) - - local gpu = term.gpu() - local sf, sb = gpu.setForeground, gpu.setBackground - local b_color, b_is_palette = gpu.getBackground() - local f_color, f_is_palette = gpu.getForeground() - sf(b_color, b_is_palette) - sb(f_color, f_is_palette) - term.write(status) - sb(b_color, b_is_palette) - sf(f_color, f_is_palette) +local preader = io.popen(cat_cmd) +local buffer = setmetatable({}, {__index = function(tbl, index) + if type(index) ~= "number" or index < 1 then return end + while #tbl < index do + local line = preader:read() + if not line then return end + table.insert(tbl, line) end -} + return tbl[index] +end}) -local function update(num) - -- unexpected - if num == 0 then - return +local index = 1 + +local _, height = term.getViewport() +local status = ":" + +local function goback(n) + n = math.min(index - height, n) + if n <= 0 then return end -- make no back scroll, we're at the top + term.scroll(-n) + index = index - n + for y = 1, math.min(height - 1, n) do + term.setCursor(1, y) + print(buffer[index - height + y]) end - - -- if this a positive direction, and we didn't previously know this was the end of the stream, give the user a once chance - local end_is_known = mgr.total_lines - -- clear buttom line, for status - local ok = mgr.scroll(num or max_display) - - -- print status term.setCursor(1, height) - -- we have to clear again in case we scrolled up - term.clearLine() - mgr.print_status() - return not end_is_known or ok end -if not update() then - return +local function goforward(n) + if not buffer[index] then return end -- nothing to do + for test_index = index, index + n do + if not buffer[test_index] then + n = math.min(n, test_index - index) + break + end + end + + term.clearLine() + term.scroll(n) + if n >= height then + index = index + (n - height) + 1 + n = height - 1 + end + term.setCursor(1, height - n) + + return true end while true do - local ename, address, char, code, dy = term.pull() - local num - if ename == "scroll" then - if dy < 0 then - num = 3 - else - num = -3 - end - elseif ename == "key_down" then - num = 0 - if code == keyboard.keys.q or code == keyboard.keys.d and keyboard.isControlDown() then - break - elseif code == keyboard.keys.space or code == keyboard.keys.pageDown then - num = max_display - elseif code == keyboard.keys.pageUp then - num = -max_display - elseif code == keyboard.keys.enter or code == keyboard.keys.down then - num = 1 - elseif code == keyboard.keys.up then - num = -1 - elseif code == keyboard.keys.home then - num = -math.huge + local _, y = term.getCursor() + local print_next_line = true + if y == height then + print_next_line = false + term.clearLine() + io.write(status) + local _, _, _, code = term.pull("key_down") + if code == keyboard.keys.q then + term.clearLine() + os.exit(1) -- abort elseif code == keyboard.keys["end"] then - num = math.huge + print_next_line = goforward(math.huge) + elseif code == keyboard.keys.space or code == keyboard.keys.pageDown then + print_next_line = goforward(height - 1) + elseif code == keyboard.keys.enter or code == keyboard.keys.down then + print_next_line = goforward(1) + elseif code == keyboard.keys.up then + goback(1) + elseif code == keyboard.keys.pageUp then + goback(height - 1) end - elseif ename == "interrupted" then - break end - if num then - update(num) + if print_next_line then + local line = buffer[index] + if line then + print(line) + index = index + 1 + else + term.setCursor(1, height) + end end end - -term.clearLine() diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/more.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/more.lua index 687a5ed17..7cde40194 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/more.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/more.lua @@ -1,7 +1,6 @@ -local buffer = require("buffer") local keyboard = require("keyboard") local shell = require("shell") -local tty = require("tty") +local term = require("term") local args = shell.parse(...) if #args > 1 then @@ -10,48 +9,36 @@ if #args > 1 then return 1 end -local function clear_line() - tty.window.x = 1 -- move cursor to start of line - io.write("\27[2K") -- clear line +local cat_cmd = table.concat({"cat", ...}, " ") +if not io.output().tty then + return os.execute(cat_cmd) end -if io.output().tty then - io.write("\27[2J\27[H") +term.clear() - local intercept_active = true - local original_stream = io.stdout.stream - local custom_stream = setmetatable({ - scroll = function(...) - local _, height, _, _, _, y = tty.getViewport() - local lines_below = height - y - if intercept_active and lines_below < 1 then - intercept_active = false - original_stream.scroll(-lines_below) -- if zero no scroll action is made [good] - tty.setCursor(1, height) -- move to end - clear_line() - io.write(":") -- status - local _, _, _, code = original_stream:pull(nil, "key_down") -- nil timeout is math.huge - if code == keyboard.keys.q then - clear_line() - os.exit(1) -- abort - elseif code == keyboard.keys["end"] then - io.stdout.stream.scroll = nil -- remove handler - elseif code == keyboard.keys.space or code == keyboard.keys.pageDown then - io.write("\27[2J\27[H") -- clear whole screen, get new page drawn; move cursor to 1,1 - elseif code == keyboard.keys.enter or code == keyboard.keys.down then - clear_line() -- remove status bar - original_stream.scroll(1) -- move everything up one - tty.setCursor(1, height - 1) - end - intercept_active = true - end - return original_stream.scroll(...) +local preader = io.popen(cat_cmd) +local intercept = true +while true do + local _, height, _, _, _, y = term.getViewport() + if intercept and y == height then + term.clearLine() + io.write(":") -- status + ::INPUT:: + local _, _, _, code = term.pull("key_down") + if code == keyboard.keys.q then + term.clearLine() + os.exit(1) -- abort + elseif code == keyboard.keys["end"] then + intercept = false + elseif code == keyboard.keys.space or code == keyboard.keys.pageDown then + term.clear() -- clear whole screen, get new page drawn; move cursor to 1,1 + elseif code == keyboard.keys.enter or code == keyboard.keys.down then + term.clearLine() -- remove status bar + term.scroll(1) -- move everything up one + term.setCursor(1, height - 1) + else + goto INPUT end - }, {__index=original_stream}) - - local custom_output_buffer = buffer.new("w", custom_stream) - custom_output_buffer:setvbuf("no") - io.output(custom_output_buffer) + end + print(preader:read() or os.exit()) end - -return loadfile(shell.resolve("cat", "lua"))(...) 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 0f4a03df3..a95088df1 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/sh.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/sh.lua @@ -30,6 +30,7 @@ if #args == 0 then if command == "exit" then return elseif command ~= "" then + tty.window.cursor = nil -- the spawned process should use its own cursor local result, reason = sh.execute(_ENV, command) if not result then io.stderr:write((reason and tostring(reason) or "unknown error") .. "\n") 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 e7658e382..88ff296b9 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 @@ -69,6 +69,14 @@ end function core_cursor.vertical:echo(arg, num) local win = tty.window local gpu = win.gpu + + -- we should not use io.write + -- the cursor should echo to the stream it is reading from + -- this makes sense because a process may redirect its io + -- but a cursor reading from a given stdin tty should also + -- echo to that same stream + local out = io.stdin.stream + if not gpu then return end win.nowrap = self.nowrap if arg == "" then -- special scroll request @@ -76,7 +84,7 @@ function core_cursor.vertical:echo(arg, num) if x > width then win.x = ((x - 1) % width) + 1 win.y = y + math.floor(x / width) - io.write("") -- tty.stream:write knows how to scroll vertically + out:write("") -- tty.stream:write knows how to scroll vertically x, y = win.x, win.y end if x <= 0 or y <= 0 or y > win.height or not gpu then return end @@ -108,25 +116,25 @@ function core_cursor.vertical:echo(arg, num) if not char[1] then return false end self.blinked = true if not arg then - io.write("\0277") + out: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") + out:write("\0277\27[7m"..char[1].."\0278") 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 win.saved = char.saved - io.write("\0278") + out:write("\0278") char = nil end end self.char_at_cursor = char return true end - return io.write(arg) + return out:write(arg) end function core_cursor.vertical:handle(name, char, code) diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_sh.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_sh.lua index 2a7334737..9f4de610b 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_sh.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/full_sh.lua @@ -85,7 +85,7 @@ function sh.internal.openCommandRedirects(redirects) if type(to_io) == "number" then -- io to io -- from_io and to_io should be numbers - ios[from_io] = ios[to_io] + ios[from_io] = io.dup(ios[to_io]) else -- to_io should be a string local file, reason = io.open(shell.resolve(to_io), mode) diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/io.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/io.lua index 400761a3a..251616c21 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/io.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/io.lua @@ -108,6 +108,19 @@ function io.write(...) return io.output():write(...) end +function io.dup(fd) + return setmetatable({ + close = function(self) self._closed = true end, + }, {__index = function(_, key) + local fd_value = fd[key] + if type(fd_value) ~= "function" then return fd_value end + return function(self, ...) + if self._closed then return nil, "closed stream" end + return fd_value(fd, ...) + end + end}) +end + ------------------------------------------------------------------------------- return io diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/pipe.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/pipe.lua index 4b551b241..069ce2127 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/pipe.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/pipe.lua @@ -81,21 +81,18 @@ local pipe_stream = if coroutine.status(self.next) == "suspended" then self:continue() end - self.redirect = {} end, seek = function() return nil, "bad file descriptor" end, write = function(self, value) - if not self.redirect[1] and self.closed then + if self.closed then -- if next is dead, ignore all writes if coroutine.status(self.next) ~= "dead" then io.stderr:write("attempt to use a closed stream\n") os.exit(1) end - elseif self.redirect[1] then - return self.redirect[1]:write(value) - elseif not self.closed then + else self.buffer = self.buffer .. value return self:continue(true) end @@ -105,11 +102,7 @@ local pipe_stream = if self.closed then return nil -- eof end - if self.redirect[0] then - -- popen could be using this code path - -- if that is the case, it is important to leave stream.buffer alone - return self.redirect[0]:read(n) - elseif self.buffer == "" then + if self.buffer == "" then -- the pipe_stream write resume is waiting on this process B (A|B) to yield -- yield here requests A to output again. However, B may elsewhere want a -- natural yield (i.e. for events). To differentiate this yield from natural @@ -139,7 +132,7 @@ function pipe.buildPipeChain(progs) local piped_stream if i < #progs then - local handle = setmetatable({redirect = {rawget(pio, 1)},buffer = ""}, {__index = pipe_stream}) + local handle = setmetatable({buffer = ""}, {__index = pipe_stream}) process.closeOnExit(handle, proc) piped_stream = buffer.new("rw", handle) piped_stream:setvbuf("no", 1024) @@ -147,7 +140,6 @@ function pipe.buildPipeChain(progs) end if prev_piped_stream then - prev_piped_stream.stream.redirect[0] = rawget(pio, 0) prev_piped_stream.stream.next = thread pio[0] = prev_piped_stream end 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 d535a5639..41bb7f8bf 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/process.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/process.lua @@ -97,7 +97,9 @@ function process.load(path, env, init, name) parent = p, instances = setmetatable({}, {__mode="v"}), } - setmetatable(new_proc.data.io, {__index=p.data.io}) + for i,fd in pairs(p.data.io) do + new_proc.data.io[i] = io.dup(fd) + end setmetatable(new_proc.data, {__index=p.data}) process.list[thread] = new_proc