2020-04-12 01:59:56 +10:00
local vtansi = { }
2020-06-11 12:56:07 +10:00
vtansi.sequences = {
[ 28 ] = " \n " , -- newline
[ 200 ] = " \27 [A " , -- up
[ 203 ] = " \27 [D " , -- left
[ 205 ] = " \27 [C " , -- right
[ 208 ] = " \27 [B " , -- down
2020-06-21 21:31:19 +10:00
[ 201 ] = " \27 [5~ " , -- page up
2020-06-11 12:56:07 +10:00
[ 209 ] = " \27 [6~ " -- page down
}
2020-05-12 17:55:05 +10:00
function vtansi . vtemu ( gpu ) -- table -- function -- takes GPU component proxy *gpu* and returns a function to write to it in a manner like an ANSI terminal
2020-04-12 01:59:56 +10:00
local colours = { 0x0 , 0xFF0000 , 0x00FF00 , 0xFFFF00 , 0x0000FF , 0xFF00FF , 0x00B6FF , 0xFFFFFF }
local mx , my = gpu.maxResolution ( )
local cx , cy = 1 , 1
local pc = " "
local lc = " "
local mode = 0 -- 0 normal, 1 escape, 2 command
local lw = true
local sx , sy = 1 , 1
local cs = " "
local bg , fg = 0 , 0xFFFFFF
-- setup
gpu.setResolution ( mx , my )
gpu.fill ( 1 , 1 , mx , my , " " )
local function checkCursor ( )
if cx > mx and lw then
cx , cy = 1 , cy + 1
end
if cy > my then
gpu.copy ( 1 , 2 , mx , my - 1 , 0 , - 1 )
gpu.fill ( 1 , my , mx , 1 , " " )
cy = my
end
if cy < 1 then cy = 1 end
if cx < 1 then cx = 1 end
end
local function termwrite ( s )
local wb = " "
local lb , ec = nil , nil
local function flushwb ( )
while wb : len ( ) > 0 do
checkCursor ( )
local wl = wb : sub ( 1 , mx - cx + 1 )
wb = wb : sub ( wl : len ( ) + 1 )
gpu.set ( cx , cy , wl )
cx = cx + wl : len ( )
end
end
local rs = " "
s = s : gsub ( " \8 " , " \27 [D " )
pc = gpu.get ( cx , cy )
gpu.setForeground ( fg )
gpu.setBackground ( bg )
gpu.set ( cx , cy , pc )
for cc in s : gmatch ( " . " ) do
if mode == 0 then
if cc == " \n " then
flushwb ( )
cx , cy = 1 , cy + 1
2020-06-21 21:31:19 +10:00
checkCursor ( )
2020-04-12 01:59:56 +10:00
elseif cc == " \t " then
wb = wb .. ( " " ) : rep ( 8 * ( ( cx + 9 ) // 8 ) )
elseif cc == " \27 " then
flushwb ( )
mode = 1
else
wb = wb .. cc
end
elseif mode == 1 then
if cc == " [ " then
mode = 2
else
mode = 0
end
elseif mode == 2 then
if cc : match ( " [%d;] " ) then
cs = cs .. cc
else
mode = 0
local tA = { }
for s in cs : gmatch ( " %d+ " ) do
tA [ # tA + 1 ] = tonumber ( s )
end
if cc == " H " then
2020-04-12 03:49:14 +10:00
cx , cy = math.min ( mx , tA [ 1 ] or 1 ) , math.min ( my , tA [ 2 ] or 1 )
2020-04-12 01:59:56 +10:00
elseif cc == " A " then
2020-06-21 21:31:19 +10:00
for i = 1 , ( tA [ 1 ] or 1 ) do
cy = cy - 1
checkCursor ( )
end
2020-04-12 01:59:56 +10:00
elseif cc == " B " then
2020-06-21 21:31:19 +10:00
for i = 1 , ( tA [ 1 ] or 1 ) do
cy = cy + 1
checkCursor ( )
end
2020-04-12 01:59:56 +10:00
elseif cc == " C " then
2020-06-21 21:31:19 +10:00
for i = 1 , ( tA [ 1 ] or 1 ) do
cx = cx + 1
checkCursor ( )
end
2020-04-12 01:59:56 +10:00
elseif cc == " D " then
2020-06-21 21:31:19 +10:00
for i = 1 , ( tA [ 1 ] or 1 ) do
cx = cx - 1
checkCursor ( )
end
2020-04-12 01:59:56 +10:00
elseif cc == " s " then
sx , sy = cx , cy
elseif cc == " u " then
cx , cy = sx , sy
elseif cc == " n " and tA [ 1 ] == 6 then
rs = string.format ( " %s \27 [%d;%dR " , rs , cx , cy )
2020-04-12 03:49:14 +10:00
dprint ( string.format ( " reporting %d;%d as current cursor position " , cx , cy ) )
2020-04-12 01:59:56 +10:00
elseif cc == " K " and tA [ 1 ] == 1 then
gpu.fill ( 1 , cy , cx , 1 , " " )
elseif cc == " K " and tA [ 1 ] == 2 then
gpu.fill ( cx , cy , mx , 1 , " " )
elseif cc == " K " then
gpu.fill ( 1 , cy , mx , 1 , " " )
elseif cc == " J " and tA [ 1 ] == 1 then
gpu.fill ( 1 , 1 , mx , cy , " " )
elseif cc == " J " and tA [ 1 ] == 2 then
gpu.fill ( 1 , 1 , mx , my , " " )
cx , cy = 1 , 1
elseif cc == " J " then
gpu.fill ( 1 , cy , mx , my , " " )
elseif cc == " m " then
for _ , num in ipairs ( tA ) do
if num == 0 then
2020-06-11 12:56:07 +10:00
fg , bg , ec , lb = 0xFFFFFF , 0 , false , true
2020-04-12 01:59:56 +10:00
elseif num == 7 then
local nfg , nbg = bg , fg
fg , bg = nfg , nbg
elseif num > 29 and num < 38 then
fg = colours [ num - 29 ]
elseif num > 39 and num < 48 then
bg = colours [ num - 39 ]
2020-04-26 08:01:53 +10:00
elseif num == 100 or num == 8 then -- disable local echo
2020-04-12 01:59:56 +10:00
ec = false
elseif num == 101 then -- disable line mode
lb = false
end
end
gpu.setForeground ( fg )
gpu.setBackground ( bg )
end
cs = " "
checkCursor ( )
end
end
end
flushwb ( )
checkCursor ( )
pc = gpu.get ( cx , cy )
gpu.setForeground ( bg )
gpu.setBackground ( fg )
gpu.set ( cx , cy , pc )
gpu.setForeground ( fg )
gpu.setBackground ( bg )
return rs , lb , ec
end
return termwrite
end
2020-05-12 17:55:05 +10:00
function vtansi . vtsession ( gpua , scra ) -- string string -- table -- creates a process to handle the GPU and screen address combination *gpua*/*scra*. Returns read, write and "close" functions.
2020-04-12 01:59:56 +10:00
local gpu = component.proxy ( gpua )
gpu.bind ( scra )
local write = vtansi.vtemu ( gpu )
local kba = { }
for k , v in ipairs ( component.invoke ( scra , " getKeyboards " ) ) do
kba [ v ] = true
end
2020-06-11 12:56:07 +10:00
local buf , lbuf , echo = " " , false , false
os.spawn ( function ( )
2020-04-12 01:59:56 +10:00
while true do
2020-06-11 12:56:07 +10:00
local ty , ka , ch , kc = coroutine.yield ( )
2020-04-12 01:59:56 +10:00
if ty == " key_down " and kba [ ka ] then
2020-06-11 12:56:07 +10:00
local outs
if ch > 0 then
outs = string.char ( ch )
end
outs = vtansi.sequences [ kc ] or outs
if outs then
if echo then write ( outs ) end
buf = buf .. outs
2020-04-12 01:59:56 +10:00
end
end
end
2020-06-11 12:56:07 +10:00
end , string.format ( " ttyd[%s:%s] " , gpua : sub ( 1 , 8 ) , scra : sub ( 1 , 8 ) ) )
2020-04-12 01:59:56 +10:00
local function bread ( n )
2020-06-11 12:56:07 +10:00
coroutine.yield ( )
local r = buf
buf = " "
2020-04-12 01:59:56 +10:00
return r
end
local function bwrite ( d )
local ba , lb , ec = write ( d )
buf = buf .. ba
if lb ~= nil then
lbuf = lb
end
if ec ~= nil then
echo = ec
end
end
return bread , bwrite , function ( ) io.write ( " \27 [2J \27 [H " ) end
end
return vtansi