Files
Halyde/halyde/core/termlib.lua
T
Ponali c0929bf639 v2.0.0 - Overhauled the 'read' function in the terminal library, added a new Unicode library, and various functions added to the filesystem library.
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.
2025-07-07 17:45:52 +02:00

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