c0929bf639
In older versions, the 'read' function in the terminal library (termlib.lua) was most likely vibecoded by Fluxdrive, and was basically very inefficient. The new Unicode library includes functions for getting a code point from a character, and iterating Unicode characters from a string or an iterator function that returns every byte. It is now possible, in the filesystem library, to make virtual 'read streams' (filesystem.makeReadStream), which does the same thing as opening a file with some specific content. There are some new functions in read streams, which allows you to loop through bytes (open(...):iterateBytes), and loop through Unicode characters (open(...):iterateUnicodeChars). The edit app will be updated to v1.2.1 for importing the new Unicode library.
365 lines
11 KiB
Lua
365 lines
11 KiB
Lua
local unicode = import("unicode")
|
|
local event = import("event")
|
|
--local keyboard = import("keyboard")
|
|
|
|
--local ocelot = component.proxy(component.list("ocelot")())
|
|
local component = import("component")
|
|
local computer = import("computer")
|
|
local gpu = component.gpu
|
|
_G.termlib = {}
|
|
termlib.cursorPosX = 1
|
|
termlib.cursorPosY = 1
|
|
termlib.readHistory = {}
|
|
|
|
local width, height = gpu.getResolution()
|
|
|
|
local ANSIColorPalette = {
|
|
["dark"] = {
|
|
[0] = 0x000000,
|
|
[1] = 0x800000,
|
|
[2] = 0x008000,
|
|
[3] = 0x808000,
|
|
[4] = 0x000080,
|
|
[5] = 0x800080,
|
|
[6] = 0x008080,
|
|
[7] = 0xC0C0C0
|
|
},
|
|
["bright"] = {
|
|
[0] = 0x808080,
|
|
[1] = 0xFF0000,
|
|
[2] = 0x00FF00,
|
|
[3] = 0xFFFF00,
|
|
[4] = 0x0000FF,
|
|
[5] = 0xFF00FF,
|
|
[6] = 0x00FFFF,
|
|
[7] = 0xFFFFFF
|
|
}
|
|
}
|
|
|
|
defaultForegroundColor = ANSIColorPalette["bright"][7]
|
|
defaultBackgroundColor = ANSIColorPalette["dark"][0]
|
|
|
|
gpu.setForeground(defaultForegroundColor)
|
|
gpu.setBackground(defaultBackgroundColor)
|
|
|
|
local function scrollDown()
|
|
width, height = gpu.getResolution()
|
|
if gpu.copy(1,1,width,height,0,-1) then
|
|
local prevForeground = gpu.getForeground()
|
|
local prevBackground = gpu.getBackground()
|
|
gpu.setForeground(defaultForegroundColor)
|
|
gpu.setBackground(defaultBackgroundColor)
|
|
gpu.fill(1, height, width, 1, " ")
|
|
gpu.setForeground(prevForeground)
|
|
gpu.setBackground(prevBackground)
|
|
termlib.cursorPosY=height
|
|
end
|
|
end
|
|
|
|
local function newLine()
|
|
termlib.cursorPosX=1
|
|
termlib.cursorPosY = termlib.cursorPosY + 1
|
|
if termlib.cursorPosY>height then
|
|
scrollDown()
|
|
end
|
|
end
|
|
|
|
local function parseCodeNumbers(code)
|
|
o = {}
|
|
for num in code:sub(3,-2):gmatch("[^;]+") do
|
|
table.insert(o,tonumber(num))
|
|
end
|
|
return o
|
|
end
|
|
|
|
function termlib.write(text, textWrap)
|
|
width, height = gpu.getResolution()
|
|
|
|
-- you don't know how tiring this was just for ANSI escape code support
|
|
|
|
if textWrap == nil then
|
|
textWrap = true
|
|
end
|
|
|
|
if not text or not tostring(text) then
|
|
return
|
|
end
|
|
if text:find("\a") then
|
|
computer.beep()
|
|
end
|
|
text = tostring(text)
|
|
text = "\27[0m" .. text:gsub("\t", " ")
|
|
readBreak = 0
|
|
-- readBreak is for when, inside the for loop, there normally would have been an increase in the "i" variable because it has read more than one character.
|
|
-- unfortunately, changing the "i" variable would have unpredictable effects, so to not risk anything, this workaround was done.
|
|
section = ""
|
|
|
|
local function printSection()
|
|
if #section==0 then
|
|
return
|
|
end
|
|
while true do
|
|
gpu.set(termlib.cursorPosX,termlib.cursorPosY,section)
|
|
if unicode.wlen(section) > width - termlib.cursorPosX + 1 and textWrap then
|
|
section = section:sub(width - termlib.cursorPosX + 2)
|
|
newLine()
|
|
else
|
|
termlib.cursorPosX = termlib.cursorPosX+unicode.wlen(section)
|
|
break
|
|
end
|
|
end
|
|
section = ""
|
|
end
|
|
|
|
for i=1,#text do
|
|
if readBreak>0 then
|
|
readBreak = readBreak - 1
|
|
goto continue
|
|
end
|
|
|
|
if string.byte(text,i)==10 then
|
|
printSection()
|
|
newLine()
|
|
elseif string.byte(text,i)==13 then
|
|
printSection()
|
|
termlib.cursorPosX=1
|
|
elseif string.byte(text,i)==0x1b and i<=#text-2 then
|
|
printSection()
|
|
--ocelot.log("0x1b char detected")
|
|
codeType = string.sub(text,i+1,i+1)
|
|
if codeType=="[" then
|
|
-- Control Sequence Introducer
|
|
--ocelot.log("Control Sequence Introducer")
|
|
codeEndIdx = string.find(text,"m",i)
|
|
code = string.sub(text,i,codeEndIdx)
|
|
--ocelot.log("Code: "..code.." ("..i..", "..codeEndIdx..")")
|
|
readBreak = readBreak + #code - 1
|
|
nums = parseCodeNumbers(code)
|
|
codeEnd = code:sub(-1)
|
|
--ocelot.log("Code end: "..codeEnd..", "..#codeEnd)
|
|
if codeEnd == "m" then
|
|
-- Select Graphic Rendition
|
|
--ocelot.log("Select Graphic Rendition, ID "..nums[1])
|
|
if nums[1]>=30 and nums[1]<=37 then
|
|
gpu.setForeground(ANSIColorPalette["dark"][nums[1]%10])
|
|
end
|
|
if nums[1]==39 or nums[1]==0 then
|
|
gpu.setForeground(defaultForegroundColor)
|
|
end
|
|
if nums[1]>=40 and nums[1]<=47 then
|
|
gpu.setBackground(ANSIColorPalette["dark"][nums[1]%10])
|
|
end
|
|
if nums[1]==49 or nums[1]==0 then
|
|
gpu.setBackground(defaultBackgroundColor)
|
|
end
|
|
if nums[1]>=90 and nums[1]<=97 then
|
|
gpu.setForeground(ANSIColorPalette["bright"][nums[1]%10])
|
|
end
|
|
if nums[1]>=100 and nums[1]<=107 then
|
|
gpu.setBackground(ANSIColorPalette["bright"][nums[1]%10])
|
|
end
|
|
end
|
|
end
|
|
else
|
|
--gpu.set(termlib.cursorPosX,termlib.cursorPosY,string.sub(text,i,i))
|
|
section = section..string.sub(text,i,i)
|
|
end
|
|
::continue::
|
|
end
|
|
printSection()
|
|
end
|
|
|
|
function _G.print(...)
|
|
local args = {...}
|
|
local stringArgs = {}
|
|
for _, arg in pairs(args) do
|
|
if tostring(arg) then
|
|
table.insert(stringArgs, tostring(arg))
|
|
end
|
|
end
|
|
termlib.write(table.concat(stringArgs, " ") .. "\n")
|
|
end
|
|
|
|
function _G.clear()
|
|
width, height = gpu.getResolution()
|
|
gpu.setForeground(defaultForegroundColor)
|
|
gpu.setBackground(defaultBackgroundColor)
|
|
gpu.fill(1,1,width,height," ")
|
|
termlib.cursorPosX, termlib.cursorPosY = 1, 1
|
|
end
|
|
|
|
function _G.read(readHistoryType, prefix, defaultText, maxChars)
|
|
checkArg(1, readHistoryType, "string", "nil")
|
|
checkArg(2, prefix, "string", "nil")
|
|
checkArg(3, defaultText, "string", "nil")
|
|
checkArg(4, maxChars, "number", "nil")
|
|
maxChars = maxChars or math.huge
|
|
|
|
local text = defaultText or ""
|
|
|
|
local historyIdx
|
|
if readHistoryType then
|
|
if not termlib.readHistory[readHistoryType] then
|
|
termlib.readHistory[readHistoryType] = {text}
|
|
elseif termlib.readHistory[readHistoryType][#termlib.readHistory[readHistoryType] ] ~= "" then
|
|
table.insert(termlib.readHistory[readHistoryType], text)
|
|
end
|
|
historyIdx = #termlib.readHistory[readHistoryType]
|
|
end
|
|
|
|
local cur = unicode.len(text)+1
|
|
if prefix then termlib.write(prefix) end
|
|
local startX, startY = termlib.cursorPosX, termlib.cursorPosY
|
|
local fg, bg = gpu.getForeground(), gpu.getBackground()
|
|
local cursorBlink = true
|
|
local function get(idx)
|
|
idx=startX+idx-1
|
|
return gpu.get(idx%width,startY+(idx//width))
|
|
end
|
|
local function checkScroll(y)
|
|
for i=1,y-height do
|
|
scrollDown()
|
|
startY=startY-1
|
|
end
|
|
return math.min(y,height)
|
|
end
|
|
local function set(idx,chr,rev)
|
|
if chr==nil or chr=="" then return end
|
|
if rev then
|
|
gpu.setForeground(bg)
|
|
gpu.setBackground(fg)
|
|
else
|
|
gpu.setForeground(fg)
|
|
gpu.setBackground(bg)
|
|
end
|
|
idx=startX+idx-1
|
|
local setX, setY = (idx-1)%width+1, startY+((idx-1)//width+1)-1
|
|
setY = checkScroll(setY)
|
|
gpu.set(setX,setY,unicode.sub(chr,1,width-setX+1))
|
|
for i=1,math.ceil((#chr+setX-1)/width)+1 do
|
|
gpu.set(1,setY+i,unicode.sub(chr,2-setX+i*width,width+i*width-setX))
|
|
setY = checkScroll(setY)
|
|
end
|
|
end
|
|
local function strDef(a,b)
|
|
if #a==0 then return b end
|
|
return a
|
|
end
|
|
local function curPos(cur)
|
|
-- component.ocelot.log(table.concat({cur,unicode.wlen(unicode.sub(text,1,cur)),unicode.len(text)}," "))
|
|
return unicode.wlen(unicode.sub(text,1,cur-1))+1
|
|
end
|
|
local function add(chr)
|
|
if type(chr)~="string" or #chr==0 then return end
|
|
if unicode.len(text)>=maxChars then return end
|
|
if maxChars<math.huge then
|
|
chr=unicode.sub(chr,1,maxChars-unicode.len(text))
|
|
end
|
|
text=unicode.sub(text,1,cur-1)..chr..unicode.sub(text,cur)
|
|
set(curPos(cur),chr,false)
|
|
cur=math.min(cur+unicode.len(chr),maxChars+1)
|
|
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true)
|
|
cursorBlink = true
|
|
set(curPos(cur+1),unicode.sub(text,cur+1),false)
|
|
end
|
|
local function moveCur(dir)
|
|
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),false)
|
|
cur=math.max(math.min(cur+dir,unicode.len(text)+1),1)
|
|
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true)
|
|
cursorBlink = true
|
|
end
|
|
local function isLine(chr)
|
|
return chr=="\n" or chr=="\r"
|
|
end
|
|
--[[ gpu.set(startX,startY,unicode.sub(text,1,width-startX))
|
|
for i=1,(#text+startX)//width-1 do
|
|
gpu.set(startX,startY+i,unicode.sub(text,1+i*width,width-startX+i*width))
|
|
end ]]
|
|
set(1,text,false)
|
|
set(curPos(cur)," ",true)
|
|
|
|
local function reprint(new)
|
|
set(1,new..string.rep(" ",unicode.wlen(text)-unicode.wlen(new)+1),false)
|
|
cur=unicode.len(new)+1
|
|
text=new
|
|
set(curPos(cur)," ",true)
|
|
end
|
|
local function updateHistory()
|
|
if not readHistoryType then return end
|
|
termlib.readHistory[readHistoryType][historyIdx]=text
|
|
end
|
|
|
|
while true do
|
|
local args = {event.pull("key_down", "clipboard", 0.5)}
|
|
if args and args[1] == "key_down" and args[4] then
|
|
local key = keyboard.keys[args[4]]
|
|
if key=="up" and readHistoryType then
|
|
historyIdx=math.max(historyIdx-1,1)
|
|
reprint(termlib.readHistory[readHistoryType][historyIdx])
|
|
elseif key=="down" and readHistoryType then
|
|
historyIdx=math.min(historyIdx+1,#termlib.readHistory[readHistoryType])
|
|
reprint(termlib.readHistory[readHistoryType][historyIdx])
|
|
elseif key=="left" then
|
|
moveCur(-1)
|
|
elseif key=="right" then
|
|
moveCur(1)
|
|
elseif key=="home" then
|
|
moveCur(-math.huge)
|
|
elseif key=="end" then
|
|
moveCur(math.huge)
|
|
elseif key=="back" and cur>1 then
|
|
text=unicode.sub(text,1,cur-2)..unicode.sub(text,cur)
|
|
cur=cur-1
|
|
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true)
|
|
cursorBlink = true
|
|
set(curPos(cur)+1,unicode.sub(text,cur+1).." ",false)
|
|
updateHistory()
|
|
elseif key=="delete" then
|
|
text = unicode.sub(text,1,cur-1)..unicode.sub(text,cur+1)
|
|
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true)
|
|
cursorBlink = true
|
|
if cur<=unicode.len(text) then
|
|
set(curPos(cur+1),unicode.sub(text,cur+1).." ",false)
|
|
end
|
|
updateHistory()
|
|
elseif key=="enter" then
|
|
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),false)
|
|
break
|
|
elseif not (args[3]<32 or (args[3]>0x7F and args[3]<=0x9F)) then
|
|
add(unicode.char(args[3]) or " ")
|
|
updateHistory()
|
|
end
|
|
elseif args and args[1]=="clipboard" then
|
|
local clip = args[3]
|
|
if not args[3] then goto continue end
|
|
while isLine(unicode.sub(clip,1,1)) do clip=unicode.sub(clip,2) end
|
|
while isLine(unicode.sub(clip,-1)) do clip=unicode.sub(clip,1,-2) end
|
|
add(clip)
|
|
updateHistory()
|
|
else
|
|
cursorBlink=not cursorBlink
|
|
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),cursorBlink)
|
|
end
|
|
::continue::
|
|
end
|
|
|
|
if readHistoryType then
|
|
if termlib.readHistory[readHistoryType][#termlib.readHistory[readHistoryType]]=="" then
|
|
table.remove(termlib.readHistory[readHistoryType],#termlib.readHistory[readHistoryType])
|
|
end
|
|
if historyIdx<#termlib.readHistory[readHistoryType] then
|
|
table.remove(termlib.readHistory[readHistoryType],historyIdx)
|
|
table.insert(termlib.readHistory[readHistoryType],text)
|
|
end
|
|
while #termlib.readHistory[readHistoryType] > 50 do
|
|
table.remove(termlib.readHistory[readHistoryType], 1)
|
|
end
|
|
end
|
|
|
|
termlib.cursorPosX=1
|
|
termlib.cursorPosY=termlib.cursorPosY+math.ceil((unicode.wlen(text)+startX-1)/width)
|
|
if termlib.cursorPosY>height then scrollDown() end
|
|
|
|
return text
|
|
end
|