opus/sys/modules/opus/terminal.lua

474 lines
9.1 KiB
Lua
Raw Normal View History

local Canvas = require('opus.ui.canvas')
2019-02-06 04:03:57 +00:00
2017-10-08 21:45:01 +00:00
local colors = _G.colors
local term = _G.term
local _gsub = string.gsub
2016-12-11 19:24:52 +00:00
2017-10-08 21:45:01 +00:00
local Terminal = { }
2017-10-05 17:07:48 +00:00
local mapColorToGray = {
[ colors.white ] = colors.white,
[ colors.orange ] = colors.lightGray,
[ colors.magenta ] = colors.lightGray,
[ colors.lightBlue ] = colors.lightGray,
[ colors.yellow ] = colors.lightGray,
[ colors.lime ] = colors.lightGray,
[ colors.pink ] = colors.lightGray,
[ colors.gray ] = colors.gray,
[ colors.lightGray ] = colors.lightGray,
[ colors.cyan ] = colors.lightGray,
[ colors.purple ] = colors.gray,
[ colors.blue ] = colors.gray,
[ colors.brown ] = colors.gray,
[ colors.green ] = colors.lightGray,
[ colors.red ] = colors.gray,
[ colors.black ] = colors.black,
}
2019-02-06 04:03:57 +00:00
-- Replacement for window api with scrolling and buffering
function Terminal.window(parent, sx, sy, w, h, isVisible)
isVisible = isVisible ~= false
if not w or not h then
w, h = parent.getSize()
end
local win = { }
2020-05-10 20:04:20 +00:00
local maxScroll
2019-02-06 04:03:57 +00:00
local cx, cy = 1, 1
local blink = false
2020-05-05 23:25:58 +00:00
local _bg, _fg = colors.black, colors.white
2019-02-06 04:03:57 +00:00
win.canvas = Canvas({
2019-02-06 04:03:57 +00:00
x = sx,
y = sy,
width = w,
height = h,
isColor = parent.isColor(),
2019-02-12 22:03:50 +00:00
offy = 0,
bg = _bg,
fg = _fg,
2019-02-06 04:03:57 +00:00
})
local function update()
if isVisible then
win.canvas:render(parent)
2019-02-06 04:03:57 +00:00
win.setCursorPos(cx, cy)
2018-01-24 22:39:38 +00:00
end
end
2019-02-06 04:03:57 +00:00
local function scrollTo(y)
y = math.max(0, y)
y = math.min(#win.canvas.lines - win.canvas.height, y)
2018-01-24 22:39:38 +00:00
if y ~= win.canvas.offy then
win.canvas.offy = y
win.canvas:dirty()
2019-02-06 04:03:57 +00:00
update()
2018-01-24 22:39:38 +00:00
end
end
2019-02-06 04:03:57 +00:00
function win.write(str)
str = tostring(str) or ''
win.canvas:write(cx, cy + win.canvas.offy, str, win.canvas.bg, win.canvas.fg)
2019-02-06 04:03:57 +00:00
win.setCursorPos(cx + #str, cy)
update()
end
function win.blit(str, fg, bg)
win.canvas:blit(cx, cy + win.canvas.offy, str, bg, fg)
2019-02-06 04:03:57 +00:00
win.setCursorPos(cx + #str, cy)
update()
end
2018-01-24 22:39:38 +00:00
2019-02-06 04:03:57 +00:00
function win.clear()
win.canvas.offy = 0
for i = #win.canvas.lines, win.canvas.height + 1, -1 do
win.canvas.lines[i] = nil
2019-02-06 04:03:57 +00:00
end
win.canvas:clear()
2019-02-06 04:03:57 +00:00
update()
2018-01-24 22:39:38 +00:00
end
function win.getLine(n)
local line = win.canvas.lines[n]
return line.text, line.fg, line.bg
end
2018-01-24 22:39:38 +00:00
function win.clearLine()
win.canvas:clearLine(cy + win.canvas.offy)
2019-02-06 04:03:57 +00:00
win.setCursorPos(cx, cy)
update()
end
2018-01-24 22:39:38 +00:00
2019-02-06 04:03:57 +00:00
function win.getCursorPos()
return cx, cy
end
function win.setCursorPos(x, y)
2019-05-03 19:30:09 +00:00
cx, cy = math.floor(x), math.floor(y)
2019-02-18 08:17:29 +00:00
if isVisible then
parent.setCursorPos(cx + win.canvas.x - 1, cy + win.canvas.y - 1)
2019-02-18 08:17:29 +00:00
end
2019-02-06 04:03:57 +00:00
end
function win.getCursorBlink()
return blink
end
2019-02-06 04:03:57 +00:00
function win.setCursorBlink(b)
blink = b
2019-02-18 08:17:29 +00:00
if isVisible then
parent.setCursorBlink(b)
end
2019-02-06 04:03:57 +00:00
end
function win.isColor()
return win.canvas.isColor
2019-02-06 04:03:57 +00:00
end
win.isColour = win.isColor
function win.setTextColor(c)
win.canvas.fg = c
2019-02-06 04:03:57 +00:00
end
win.setTextColour = win.setTextColor
2019-02-07 08:10:05 +00:00
function win.getPaletteColor(n)
2019-02-09 00:21:20 +00:00
if parent.getPaletteColor then
return parent.getPaletteColor(n)
end
return 0, 0, 0
2019-02-07 08:10:05 +00:00
end
win.getPaletteColour = win.getPaletteColor
function win.setPaletteColor(n, r, g, b)
2019-02-09 00:21:20 +00:00
if parent.setPaletteColor then
return parent.setPaletteColor(n, r, g, b)
end
2019-02-07 08:10:05 +00:00
end
win.setPaletteColour = win.setPaletteColor
2019-02-06 04:03:57 +00:00
function win.setBackgroundColor(c)
win.canvas.bg = c
2019-02-06 04:03:57 +00:00
end
win.setBackgroundColour = win.setBackgroundColor
function win.getSize()
return win.canvas.width, win.canvas.height
2019-02-06 04:03:57 +00:00
end
2018-01-24 22:39:38 +00:00
2019-02-06 04:03:57 +00:00
function win.scroll(n)
n = n or 1
if n > 0 then
local lines = #win.canvas.lines
2019-02-12 01:25:12 +00:00
for i = 1, n do
win.canvas.lines[lines + i] = { }
win.canvas:clearLine(lines + i)
2018-01-24 22:39:38 +00:00
end
2020-05-10 20:04:20 +00:00
while #win.canvas.lines > (maxScroll or win.canvas.height) do
table.remove(win.canvas.lines, 1)
2019-02-06 04:03:57 +00:00
end
scrollTo(#win.canvas.lines)
win.canvas:dirty()
2019-02-06 04:03:57 +00:00
update()
2018-01-24 22:39:38 +00:00
end
end
2019-02-06 04:03:57 +00:00
function win.getTextColor()
return win.canvas.fg
2018-01-24 22:39:38 +00:00
end
2019-02-06 04:03:57 +00:00
win.getTextColour = win.getTextColor
2018-01-24 22:39:38 +00:00
2019-02-06 04:03:57 +00:00
function win.getBackgroundColor()
return win.canvas.bg
2019-02-06 04:03:57 +00:00
end
win.getBackgroundColour = win.getBackgroundColor
function win.setVisible(visible)
if visible ~= isVisible then
isVisible = visible
if isVisible then
win.canvas:dirty()
2019-02-06 04:03:57 +00:00
update()
end
2018-01-24 22:39:38 +00:00
end
2019-02-06 04:03:57 +00:00
end
2018-01-24 22:39:38 +00:00
2019-02-06 04:03:57 +00:00
function win.redraw()
if isVisible then
win.canvas:dirty()
2019-02-12 22:03:50 +00:00
update()
2018-01-24 22:39:38 +00:00
end
2019-02-06 04:03:57 +00:00
end
2018-01-24 22:39:38 +00:00
2019-02-06 04:03:57 +00:00
function win.restoreCursor()
2019-04-27 23:33:21 +00:00
if isVisible then
win.setCursorPos(cx, cy)
win.setTextColor(win.canvas.fg)
2019-04-27 23:33:21 +00:00
win.setCursorBlink(blink)
end
2018-01-24 22:39:38 +00:00
end
2019-02-06 04:03:57 +00:00
function win.getPosition()
return win.canvas.x, win.canvas.y
2018-01-24 22:39:38 +00:00
end
2019-02-06 04:03:57 +00:00
function win.reposition(x, y, width, height)
2020-05-10 20:04:20 +00:00
if not maxScroll then
win.canvas:move(x, y)
win.canvas:resize(width or win.canvas.width, height or win.canvas.height)
return
end
-- special processing for scrolling terminal like windows
local delta = height - win.canvas.height
if delta > 0 then -- grow
for _ = 1, delta do
win.canvas.lines[#win.canvas.lines + 1] = { }
win.canvas:clearLine(#win.canvas.lines)
end
elseif delta < 0 then -- shrink
for _ = delta + 1, 0 do
if cy < win.canvas.height then
win.canvas.lines[#win.canvas.lines] = nil
else
cy = cy - 1
win.canvas.offy = win.canvas.offy + 1
end
end
end
win.canvas:resizeBuffer(width, #win.canvas.lines)
win.canvas.height = height
win.canvas.width = width
win.canvas:move(x, y)
update()
2019-02-06 04:03:57 +00:00
end
--[[ Additional methods ]]--
2018-01-24 22:39:38 +00:00
function win.scrollDown()
scrollTo(win.canvas.offy + 1)
2018-01-24 22:39:38 +00:00
end
2019-02-06 04:03:57 +00:00
function win.scrollUp()
scrollTo(win.canvas.offy - 1)
2019-02-06 04:03:57 +00:00
end
2018-01-24 22:39:38 +00:00
2019-02-06 04:03:57 +00:00
function win.scrollTop()
scrollTo(0)
2018-01-24 22:39:38 +00:00
end
2019-02-06 04:03:57 +00:00
function win.scrollBottom()
scrollTo(#win.canvas.lines)
2019-02-06 04:03:57 +00:00
end
function win.setMaxScroll(ms)
maxScroll = ms
end
function win.getCanvas()
return win.canvas
2019-02-06 04:03:57 +00:00
end
function win.getParent()
return parent
end
2020-05-31 02:05:53 +00:00
function win.writeX(sText)
-- expect(1, sText, "string", "number")
local nLinesPrinted = 0
local function newLine()
if cy + 1 <= win.canvas.height then
cx, cy = 1, cy + 1
else
cx, cy = 1, win.canvas.height
win.scroll(1)
end
nLinesPrinted = nLinesPrinted + 1
end
-- Print the line with proper word wrapping
sText = tostring(sText)
while #sText > 0 do
local whitespace = string.match(sText, "^[ \t]+")
if whitespace then
-- Print whitespace
win.write(whitespace)
sText = string.sub(sText, #whitespace + 1)
end
local newline = string.match(sText, "^\n")
if newline then
-- Print newlines
newLine()
sText = string.sub(sText, 2)
end
local text = string.match(sText, "^[^ \t\n]+")
if text then
sText = string.sub(sText, #text + 1)
if #text > win.canvas.width then
-- Print a multiline word
while #text > 0 do
if cx > win.canvas.width then
newLine()
end
win.write(text)
text = string.sub(text, win.canvas.width - cx + 2)
end
else
-- Print a word normally
if cx + #text - 1 > win.canvas.width then
newLine()
end
win.write(text)
end
end
end
return nLinesPrinted
end
function win.print(...)
local vis = isVisible
isVisible = false
local nLinesPrinted = 0
local nLimit = select("#", ...)
for n = 1, nLimit do
local s = tostring(select(n, ...))
if n < nLimit then
s = s .. "\t"
end
nLinesPrinted = nLinesPrinted + win.writeX(s)
end
nLinesPrinted = nLinesPrinted + win.writeX("\n")
isVisible = vis
update()
return nLinesPrinted
end
win.canvas:clear()
2019-02-06 04:03:57 +00:00
return win
2018-01-20 12:18:13 +00:00
end
-- get windows contents
function Terminal.getContents(win)
if not win.getLine then
error('window is required')
end
2018-01-24 22:39:38 +00:00
local lines = { }
local _, h = win.getSize()
2018-01-20 12:18:13 +00:00
for i = 1, h do
local text, fg, bg = win.getLine(i)
lines[i] = {
2018-01-24 22:39:38 +00:00
text = text,
fg = fg,
bg = bg,
}
end
2018-01-20 12:18:13 +00:00
2018-01-24 22:39:38 +00:00
return lines
2018-01-13 20:17:26 +00:00
end
function Terminal.colorToGrayscale(c)
return mapColorToGray[c]
end
2018-01-24 22:39:38 +00:00
function Terminal.toGrayscale(ct)
2018-01-24 22:39:38 +00:00
local methods = { 'setBackgroundColor', 'setBackgroundColour',
'setTextColor', 'setTextColour' }
for _,v in pairs(methods) do
local fn = ct[v]
ct[v] = function(c)
fn(mapColorToGray[c])
2018-01-24 22:39:38 +00:00
end
end
local bcolors = {
[ '1' ] = '8',
[ '2' ] = '8',
[ '3' ] = '8',
[ '4' ] = '8',
[ '5' ] = '8',
[ '6' ] = '8',
[ '9' ] = '8',
[ 'a' ] = '7',
[ 'b' ] = '7',
[ 'c' ] = '7',
[ 'd' ] = '8',
[ 'e' ] = '7',
}
local function translate(s)
if s then
s = _gsub(s, "%w", bcolors)
end
return s
end
local fn = ct.blit
ct.blit = function(text, fg, bg)
fn(text, translate(fg), translate(bg))
end
2016-12-11 19:24:52 +00:00
end
2016-12-18 19:38:48 +00:00
function Terminal.getNullTerm(ct)
2018-01-24 22:39:38 +00:00
local nt = Terminal.copy(ct)
2016-12-18 19:38:48 +00:00
2018-01-24 22:39:38 +00:00
local methods = { 'blit', 'clear', 'clearLine', 'scroll',
'setCursorBlink', 'setCursorPos', 'write' }
for _,v in pairs(methods) do
nt[v] = function() end
end
2016-12-18 19:38:48 +00:00
2018-01-24 22:39:38 +00:00
return nt
2016-12-18 19:38:48 +00:00
end
2017-04-09 00:06:31 +00:00
function Terminal.copy(it, ot)
2018-01-24 22:39:38 +00:00
ot = ot or { }
for k,v in pairs(it) do
if type(v) == 'function' then
ot[k] = v
end
end
return ot
2016-12-11 19:24:52 +00:00
end
function Terminal.mirror(ct, dt)
2019-02-20 06:24:37 +00:00
local t = { }
2018-01-24 22:39:38 +00:00
for k,f in pairs(ct) do
2019-02-20 06:24:37 +00:00
t[k] = function(...)
2018-01-24 22:39:38 +00:00
local ret = { f(...) }
if dt[k] then
dt[k](...)
end
return table.unpack(ret)
end
end
2019-02-20 06:24:37 +00:00
return t
2016-12-11 19:24:52 +00:00
end
2017-05-06 01:43:17 +00:00
function Terminal.readPassword(prompt)
2018-01-24 22:39:38 +00:00
if prompt then
term.write(prompt)
end
local fn = term.current().write
term.current().write = function() end
local s
pcall(function() s = _G.read(prompt) end)
term.current().write = fn
if s == '' then
return
end
return s
2017-05-06 01:43:17 +00:00
end
2016-12-11 19:24:52 +00:00
return Terminal