mirror of
https://github.com/NeoFlock/neonucleus.git
synced 2025-09-25 01:23:31 +02:00
285 lines
7.7 KiB
Lua
285 lines
7.7 KiB
Lua
local unicode = require("unicode")
|
|
local event = require("event")
|
|
local component = require("component")
|
|
local computer = require("computer")
|
|
|
|
local tty = {}
|
|
tty.window =
|
|
{
|
|
fullscreen = true,
|
|
blink = true,
|
|
dx = 0,
|
|
dy = 0,
|
|
x = 1,
|
|
y = 1,
|
|
output_buffer = "",
|
|
}
|
|
|
|
tty.stream = {}
|
|
|
|
local screen_cache = {}
|
|
local function screen_reset(gpu, addr)
|
|
screen_cache[addr or gpu.getScreen() or false] = nil
|
|
end
|
|
|
|
event.listen("screen_resized", screen_reset)
|
|
|
|
function tty.getViewport()
|
|
local window = tty.window
|
|
local screen = tty.screen()
|
|
if window.fullscreen and screen and not screen_cache[screen] then
|
|
screen_cache[screen] = true
|
|
window.width, window.height = window.gpu.getViewport()
|
|
end
|
|
|
|
return window.width, window.height, window.dx, window.dy, window.x, window.y
|
|
end
|
|
|
|
function tty.setViewport(width, height, dx, dy, x, y)
|
|
checkArg(1, width, "number")
|
|
checkArg(2, height, "number")
|
|
local window = tty.window
|
|
dx, dy, x, y = dx or 0, dy or 0, x or 1, y or 1
|
|
window.width, window.height, window.dx, window.dy, window.x, window.y = width, height, dx, dy, x, y
|
|
end
|
|
|
|
function tty.gpu()
|
|
return tty.window.gpu
|
|
end
|
|
|
|
function tty.clear()
|
|
tty.stream.scroll(math.huge)
|
|
tty.setCursor(1, 1)
|
|
end
|
|
|
|
function tty.isAvailable()
|
|
local gpu = tty.gpu()
|
|
return not not (gpu and gpu.getScreen())
|
|
end
|
|
|
|
-- PLEASE do not use this method directly, use io.read or term.read
|
|
function tty.stream.read()
|
|
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
|
|
|
|
local ok, result, reason = xpcall(core.read, debug.traceback, cursor)
|
|
|
|
if not ok or not result then
|
|
pcall(cursor.update, cursor)
|
|
end
|
|
|
|
return select(2, assert(ok, result, reason))
|
|
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 {}
|
|
cursor.sy = cursor.sy or 0
|
|
cursor.tails = cursor.tails or {}
|
|
local beeped
|
|
local uptime = computer.uptime
|
|
local last_sleep = uptime()
|
|
window.output_buffer = window.output_buffer .. value
|
|
while true do
|
|
if uptime() - last_sleep > 3 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\b\v\15])")
|
|
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)
|
|
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
|
|
ei = #segment
|
|
-- fake a newline
|
|
delim = "\n"
|
|
end
|
|
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
|
|
unicode.sub(window.output_buffer, window.width + 1)
|
|
|
|
if delim == "\t" then
|
|
x = ((x-1) - ((x-1) % 8)) + 9
|
|
elseif delim == "\r" then
|
|
x = 1
|
|
elseif delim == "\n" then
|
|
x = 1
|
|
y = y + 1
|
|
elseif delim == "\b" then
|
|
x = x - 1
|
|
elseif delim == "\v" then
|
|
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)
|
|
end
|
|
return cursor.sy
|
|
end
|
|
|
|
function tty.getCursor()
|
|
local window = tty.window
|
|
return window.x, window.y
|
|
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
|
|
|
|
local gpu_intercept = {}
|
|
function tty.bind(gpu)
|
|
checkArg(1, gpu, "table")
|
|
if not gpu_intercept[gpu] then
|
|
gpu_intercept[gpu] = true -- only override a gpu once
|
|
-- the gpu can change resolution before we get a chance to call events and handle screen_resized
|
|
-- unfortunately, we have to handle viewport changes by intercept
|
|
local setr, setv = gpu.setResolution, gpu.setViewport
|
|
gpu.setResolution = function(...)
|
|
screen_reset(gpu)
|
|
return setr(...)
|
|
end
|
|
gpu.setViewport = function(...)
|
|
screen_reset(gpu)
|
|
return setv(...)
|
|
end
|
|
end
|
|
local window = tty.window
|
|
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()
|
|
end
|
|
screen_reset(gpu)
|
|
end
|
|
|
|
function tty.keyboard()
|
|
-- this method needs to be safe even if there is no terminal window (e.g. no gpu)
|
|
local window = tty.window
|
|
|
|
if window.keyboard then
|
|
return window.keyboard
|
|
end
|
|
|
|
local system_keyboard = component.isAvailable("keyboard") and component.keyboard
|
|
system_keyboard = system_keyboard and system_keyboard.address or "no_system_keyboard"
|
|
|
|
local screen = tty.screen()
|
|
|
|
if not screen then
|
|
-- no screen, no known keyboard, use system primary keyboard if any
|
|
return system_keyboard
|
|
end
|
|
|
|
-- if we are using a gpu bound to the primary screen, then use the primary keyboard
|
|
if component.isAvailable("screen") and component.screen.address == screen then
|
|
window.keyboard = system_keyboard
|
|
else
|
|
-- calling getKeyboards() on the screen is costly (time)
|
|
-- changes to this design should avoid this on every key hit
|
|
|
|
-- this is expensive (slow!)
|
|
window.keyboard = component.invoke(screen, "getKeyboards")[1] or system_keyboard
|
|
end
|
|
|
|
return window.keyboard
|
|
end
|
|
|
|
function tty.screen()
|
|
local gpu = tty.gpu()
|
|
if not gpu then
|
|
return nil
|
|
end
|
|
return gpu.getScreen()
|
|
end
|
|
|
|
function tty.stream.scroll(lines)
|
|
local gpu = tty.gpu()
|
|
if not gpu then
|
|
return 0
|
|
end
|
|
local width, height, dx, dy, x, y = tty.getViewport()
|
|
|
|
-- nil lines indicates a request to auto scroll
|
|
-- auto scroll is when the cursor has gone below the bottom on the terminal
|
|
-- and the text is scroll up, pulling the cursor back into view
|
|
|
|
-- lines<0 scrolls up (text down)
|
|
-- lines>0 scrolls down (text up)
|
|
|
|
-- no lines count given, the user is asking to auto scroll y back into view
|
|
if not lines then
|
|
if y < 1 then
|
|
lines = y - 1 -- y==0 scrolls back -1
|
|
elseif y > height then
|
|
lines = y - height -- y==height+1 scroll forward 1
|
|
else
|
|
return 0 -- do nothing
|
|
end
|
|
end
|
|
|
|
lines = math.min(lines, height)
|
|
lines = math.max(lines,-height)
|
|
|
|
-- scroll request can be too large
|
|
local abs_lines = math.abs(lines)
|
|
local box_height = height - abs_lines
|
|
local fill_top = dy + 1 + (lines < 0 and 0 or box_height)
|
|
|
|
gpu.copy(dx + 1, dy + 1 + math.max(0, lines), width, box_height, 0, -lines)
|
|
gpu.fill(dx + 1, fill_top, width, abs_lines, ' ')
|
|
|
|
tty.setCursor(x, math.max(1, math.min(y, height)))
|
|
return lines
|
|
end
|
|
|
|
-- stream methods
|
|
local function bfd() return nil, "tty: invalid operation" end
|
|
tty.stream.close = bfd
|
|
tty.stream.seek = bfd
|
|
tty.stream.handle = "tty"
|
|
|
|
return tty
|