mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-16 10:51:55 -04:00
fixed less, simplified
This commit is contained in:
parent
1fb05ff432
commit
b6bc9d0cea
@ -1,304 +1,99 @@
|
|||||||
local keyboard = require("keyboard")
|
local keyboard = require("keyboard")
|
||||||
local shell = require("shell")
|
local shell = require("shell")
|
||||||
local term = require("term") -- using term for negative scroll feature
|
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(...)
|
local args = shell.parse(...)
|
||||||
if #args > 1 then
|
if #args > 1 then
|
||||||
io.write("Usage: more <filename>\n")
|
io.write("Usage: less <filename>\n")
|
||||||
io.write("- or no args reads stdin\n")
|
io.write("- or no args reads stdin\n")
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
local arg = args[1] or "-"
|
|
||||||
|
|
||||||
local initial_offset
|
local cat_cmd = table.concat({"cat", ...}, " ")
|
||||||
|
if not io.output().tty then
|
||||||
-- test validity of args
|
return os.execute(cat_cmd)
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local width, height = term.getViewport()
|
term.clear()
|
||||||
local max_display = height - 1
|
|
||||||
|
|
||||||
-- mgr is the data manager, it keeps track of what has been loaded
|
local preader = io.popen(cat_cmd)
|
||||||
-- keeps a reasonable buffer, and keeps track of file handles
|
local buffer = setmetatable({}, {__index = function(tbl, index)
|
||||||
local mgr
|
if type(index) ~= "number" or index < 1 then return end
|
||||||
mgr =
|
while #tbl < index do
|
||||||
{
|
local line = preader:read()
|
||||||
lines = {}, -- current buffer
|
if not line then return end
|
||||||
chunk, -- temp from last read line that hasn't finished wrapping
|
table.insert(tbl, line)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
}
|
return tbl[index]
|
||||||
|
end})
|
||||||
|
|
||||||
local function update(num)
|
local index = 1
|
||||||
-- unexpected
|
|
||||||
if num == 0 then
|
local _, height = term.getViewport()
|
||||||
return
|
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
|
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)
|
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
|
end
|
||||||
|
|
||||||
if not update() then
|
local function goforward(n)
|
||||||
return
|
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
|
end
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
local ename, address, char, code, dy = term.pull()
|
local _, y = term.getCursor()
|
||||||
local num
|
local print_next_line = true
|
||||||
if ename == "scroll" then
|
if y == height then
|
||||||
if dy < 0 then
|
print_next_line = false
|
||||||
num = 3
|
term.clearLine()
|
||||||
else
|
io.write(status)
|
||||||
num = -3
|
local _, _, _, code = term.pull("key_down")
|
||||||
end
|
if code == keyboard.keys.q then
|
||||||
elseif ename == "key_down" then
|
term.clearLine()
|
||||||
num = 0
|
os.exit(1) -- abort
|
||||||
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
|
|
||||||
elseif code == keyboard.keys["end"] then
|
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
|
end
|
||||||
elseif ename == "interrupted" then
|
|
||||||
break
|
|
||||||
end
|
end
|
||||||
if num then
|
if print_next_line then
|
||||||
update(num)
|
local line = buffer[index]
|
||||||
|
if line then
|
||||||
|
print(line)
|
||||||
|
index = index + 1
|
||||||
|
else
|
||||||
|
term.setCursor(1, height)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
term.clearLine()
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user