The last cutting-edge commit out of the lot making the terminal a lot nicer

This commit is contained in:
2026-06-22 22:10:48 +03:00
parent b3a2ed2258
commit a9ecbccf2e
+370 -352
View File
@@ -22,21 +22,21 @@ function module.init()
local readHistory = {} local readHistory = {}
function _PUBLIC.terminal.getHistory(id) function _PUBLIC.terminal.getHistory(id)
checkArg(1,id,"string") checkArg(1, id, "string")
return table.copy(readHistory[id]) return table.copy(readHistory[id])
end end
function _PUBLIC.terminal.setHistory(id,hist) function _PUBLIC.terminal.setHistory(id, hist)
checkArg(1,id,"string") checkArg(1, id, "string")
checkArg(2,hist,"table") checkArg(2, hist, "table")
for i=1,#hist do for i = 1, #hist do
hist[i]=tostring(hist[i]) hist[i] = tostring(hist[i])
end end
readHistory[id]=hist readHistory[id] = hist
end end
function _PUBLIC.terminal.addToHistory(id,hist) function _PUBLIC.terminal.addToHistory(id, hist)
checkArg(1,id,"string") checkArg(1, id, "string")
checkArg(2,hist,"string") checkArg(2, hist, "string")
table.insert(readHistory[id],hist) table.insert(readHistory[id], hist)
end end
local function getColorPalette(depth) local function getColorPalette(depth)
@@ -66,7 +66,7 @@ function module.init()
end end
if depth == 4 then if depth == 4 then
return { return {
-- Closest colors to the 4 bit OC pallete -- Closest colors to the 4 bit OC palette
-- Better than outright failure -- Better than outright failure
["dark"] = { ["dark"] = {
[0] = 0x000000, -- black [0] = 0x000000, -- black
@@ -146,27 +146,20 @@ function module.init()
local cursor = { x = 1, y = 1, X = nil, Y = nil } -- X and Y are managed by ESC s and ESC u local cursor = { x = 1, y = 1, X = nil, Y = nil } -- X and Y are managed by ESC s and ESC u
local printState = 0 -- 0:none 1:in ESC 2:in CSI local printState = 0 -- 0:none 1:in ESC 2:in CSI
local color = { local DEFAULT_FG = ANSIColorPalette["bright"][7] -- TODO make configurable
FG = ANSIColorPalette["bright"][7], BG = ANSIColorPalette["dark"][0], local DEFAULT_BG = ANSIColorPalette["dark"][0]
fg = nil, bg = nil, reverse = false local fg = DEFAULT_FG
} local bg = DEFAULT_BG
color.fg = color.FG local gpuFg = nil
color.bg = color.BG local gpuBg = nil
local reverse = false
local current_codepoint = 0 local current_codepoint = 0
local bytes_remaining = 0 local bytes_remaining = 0
local seq = {} local seq = {}
local writeBuf = {} local writeBuf = ""
local bufStartX = 1
local function update_gpu_colors() local bufStartY = 1
if color.reverse then
gpu.setForeground(color.bg)
gpu.setBackground(color.fg)
else
gpu.setForeground(color.fg)
gpu.setBackground(color.bg)
end
end
local width, height = gpu.getResolution() local width, height = gpu.getResolution()
@@ -174,29 +167,60 @@ function module.init()
return width, height return width, height
end end
gpu.setForeground(color.fg) local function update_gpu_colors()
gpu.setBackground(color.bg) if reverse then
if gpuBg ~= fg then gpu.setBackground(fg) gpuBg = fg end
local function scroll() if gpuFg ~= bg then gpu.setForeground(bg) gpuFg = bg end
if gpu.copy(1, 2, width, height - 1, 0, -1) then else
gpu.setForeground(color.FG) if gpuBg ~= bg then gpu.setBackground(bg) gpuBg = bg end
gpu.setBackground(color.BG) if gpuFg ~= fg then gpu.setForeground(fg) gpuFg = fg end
gpu.fill(1, height, width, 1, " ")
gpu.setForeground(color.fg)
gpu.setBackground(color.bg)
cursor.y = height
end end
end end
local function scroll(n)
if n <= 0 then return end
if writeBuf ~= "" then
update_gpu_colors()
gpu.set(bufStartX, bufStartY, writeBuf)
writeBuf = ""
end
local keep = height - n
if keep > 0 then
gpu.copy(1, n + 1, width, keep, 0, -n)
end
if gpuBg ~= bg then
gpu.setBackground(bg)
gpuBg = bg
end
gpu.fill(1, keep + 1, width, n, " ")
cursor.y = cursor.y - n
if cursor.y > height then cursor.y = height end
if cursor.y < 1 then cursor.y = 1 end
end
local function check_wrap_and_scroll() local function check_wrap_and_scroll()
if cursor.x > width then if cursor.x > width then
cursor.x = 1 cursor.x = 1
cursor.y = cursor.y + 1 cursor.y = cursor.y + 1
end end
scroll(cursor.y - height)
while cursor.y > height do
scroll()
end end
-- must be called before any operation that moves the cursor or changes colors!!!!!!!
local function flush()
if writeBuf == "" then return end
update_gpu_colors()
gpu.set(bufStartX, bufStartY, writeBuf)
writeBuf = ""
end
local function buf_append(s, len)
if writeBuf == "" then
bufStartX = cursor.x
bufStartY = cursor.y
end
writeBuf = writeBuf .. s
cursor.x = cursor.x + len
end end
local function exec_csi() local function exec_csi()
@@ -226,117 +250,84 @@ function module.init()
end end
end end
local function get_param(idx, default) -- TODO maybe use a lookup table like this?
if idx <= #params and params[idx] ~= nil then -- local thing = things[op]
return params[idx] -- if thing ~= nil then thing() end
end
return default
end
if op == 0x48 or op == 0x66 then
local row = get_param(1, 1)
local col = get_param(2, 1)
cursor.y = row
cursor.x = col
if cursor.x < 1 then cursor.x = 1 end
if cursor.y < 1 then cursor.y = 1 end
if cursor.x > width then cursor.x = width end
if cursor.y > height then cursor.y = height end
return
end
if op == 0x41 then if op == 0x41 then
local n = get_param(1, 1) flush()
cursor.y = cursor.y - n cursor.y = cursor.y - (params[1] or 1)
if cursor.y < 1 then cursor.y = 1 end if cursor.y < 1 then cursor.y = 1 end
return elseif op == 0x42 then
end flush()
cursor.y = cursor.y + (params[1] or 1)
if op == 0x42 then
local n = get_param(1, 1)
cursor.y = cursor.y + n
if cursor.y > height then cursor.y = height end if cursor.y > height then cursor.y = height end
return elseif op == 0x43 then
end flush()
cursor.x = cursor.x + (params[1] or 1)
if op == 0x43 then
local n = get_param(1, 1)
cursor.x = cursor.x + n
if cursor.x > width then cursor.x = width end if cursor.x > width then cursor.x = width end
return elseif op == 0x44 then
end flush()
cursor.x = cursor.x - (params[1] or 1)
if op == 0x44 then
local n = get_param(1, 1)
cursor.x = cursor.x - n
if cursor.x < 1 then cursor.x = 1 end if cursor.x < 1 then cursor.x = 1 end
return elseif op == 0x47 or op == 0x60 then
end flush()
cursor.x = params[1] or 1
if op == 0x47 then
local col = get_param(1, 1)
cursor.x = col
if cursor.x < 1 then cursor.x = 1 end if cursor.x < 1 then cursor.x = 1 end
if cursor.x > width then cursor.x = width end if cursor.x > width then cursor.x = width end
return elseif op == 0x48 or op == 0x66 then
end flush()
cursor.y = params[1] or 1
if op == 0x4a then cursor.x = params[2] or 1
local mode = get_param(1, 0) if cursor.x < 1 then cursor.x = 1 end
if cursor.y < 1 then cursor.y = 1 end
if cursor.x > width then cursor.x = width end
if cursor.y > height then cursor.y = height end
elseif op == 0x4a then
flush()
local mode = params[1] or 0
update_gpu_colors()
if mode == 0 then if mode == 0 then
update_gpu_colors() gpu.fill(cursor.x, cursor.y, width - cursor.x + 1, 1, " ")
gpu.fill(cursor.x, cursor.y, width - cursor.x + 1, height - cursor.y + 1, " ") if cursor.y < height then
gpu.fill(1, cursor.y + 1, width, height - cursor.y, " ")
end
elseif mode == 1 then elseif mode == 1 then
update_gpu_colors() if cursor.y > 1 then
gpu.fill(1, 1, cursor.x, cursor.y, " ") gpu.fill(1, 1, width, cursor.y - 1, " ")
end
gpu.fill(1, cursor.y, cursor.x, 1, " ")
elseif mode == 2 then elseif mode == 2 then
update_gpu_colors()
gpu.fill(1, 1, width, height, " ") gpu.fill(1, 1, width, height, " ")
cursor.x = 1 cursor.x = 1
cursor.y = 1 cursor.y = 1
end end
return elseif op == 0x4b then
end flush()
local mode = params[1] or 0
if op == 0x4b then
local mode = get_param(1, 0)
if mode == 0 then
update_gpu_colors() update_gpu_colors()
if mode == 0 then
gpu.fill(cursor.x, cursor.y, width - cursor.x + 1, 1, " ") gpu.fill(cursor.x, cursor.y, width - cursor.x + 1, 1, " ")
elseif mode == 1 then elseif mode == 1 then
update_gpu_colors()
gpu.fill(1, cursor.y, cursor.x, 1, " ") gpu.fill(1, cursor.y, cursor.x, 1, " ")
elseif mode == 2 then elseif mode == 2 then
update_gpu_colors()
gpu.fill(1, cursor.y, width, 1, " ") gpu.fill(1, cursor.y, width, 1, " ")
end end
return elseif op == 0x64 then
flush()
cursor.y = params[1] or 1
if cursor.y < 1 then
cursor.y = 1
end end
if cursor.y > height then
if op == 0x60 then cursor.y = height
local col = get_param(1, 1)
cursor.x = col
if cursor.x < 1 then cursor.x = 1 end
if cursor.x > width then cursor.x = width end
return
end end
elseif op == 0x6d then
if op == 0x64 then flush()
local row = get_param(1, 1)
cursor.y = row
if cursor.y < 1 then cursor.y = 1 end
if cursor.y > height then cursor.y = height end
return
end
if op == 0x6d then
local j = 1 local j = 1
local function parse_extended_color() local function parse_extended_color()
local mode = get_param(j + 1, -1) local mode = params[j + 1] or -1
if mode == 5 then if mode == 5 then
local idx = get_param(j + 2, 0) local idx = params[j + 2] or 0
j = j + 2 j = j + 2
if idx < 8 then if idx < 8 then
return ANSIColorPalette["dark"][idx] return ANSIColorPalette["dark"][idx]
@@ -353,9 +344,9 @@ function module.init()
return (v << 16) | (v << 8) | v return (v << 16) | (v << 8) | v
end end
elseif mode == 2 then elseif mode == 2 then
local r = get_param(j + 2, 0) local r = params[j + 2] or 0
local g = get_param(j + 3, 0) local g = params[j + 3] or 0
local b = get_param(j + 4, 0) local b = params[j + 4] or 0
j = j + 4 j = j + 4
return (r << 16) | (g << 8) | b return (r << 16) | (g << 8) | b
end end
@@ -363,10 +354,9 @@ function module.init()
end end
if #params == 0 then if #params == 0 then
color.reverse = false reverse = false
color.fg = color.FG fg = DEFAULT_FG
color.bg = color.BG bg = DEFAULT_BG
update_gpu_colors()
return return
end end
@@ -374,192 +364,207 @@ function module.init()
local p = params[j] or 0 local p = params[j] or 0
if p == 0 then if p == 0 then
color.reverse = false reverse = false
color.fg = color.FG fg = DEFAULT_FG
color.bg = color.BG bg = DEFAULT_BG
elseif p == 1 then --elseif p == 1 then
elseif p == 2 then --elseif p == 2 then
elseif p == 3 then --elseif p == 3 then
elseif p == 4 then --elseif p == 4 then
elseif p == 5 or p == 6 then --elseif p == 5 or p == 6 then
elseif p == 7 then elseif p == 7 then
color.reverse = true reverse = true
elseif p == 8 then elseif p == 8 then
color.fg = color.bg fg = bg
elseif p == 9 then --elseif p == 9 then
elseif p == 21 then --elseif p == 21 then
elseif p == 22 then --elseif p == 22 then
elseif p == 23 then --elseif p == 23 then
elseif p == 24 then --elseif p == 24 then
elseif p == 25 then --elseif p == 25 then
elseif p == 27 then elseif p == 27 then
color.reverse = false reverse = false
elseif p == 28 then elseif p == 28 then
color.fg = color.FG fg = DEFAULT_FG
elseif p == 29 then --elseif p == 29 then
elseif 30 <= p and p <= 37 then elseif 30 <= p and p <= 37 then
color.fg = ANSIColorPalette["dark"][p - 30] fg = ANSIColorPalette["dark"][p - 30]
elseif p == 38 then elseif p == 38 then
local c = parse_extended_color() local c = parse_extended_color()
if c then color.fg = c end if c then
fg = c
end
elseif p == 39 then elseif p == 39 then
color.fg = color.FG fg = DEFAULT_FG
elseif 40 <= p and p <= 47 then elseif 40 <= p and p <= 47 then
color.bg = ANSIColorPalette["dark"][p - 40] bg = ANSIColorPalette["dark"][p - 40]
elseif p == 48 then elseif p == 48 then
local c = parse_extended_color() local c = parse_extended_color()
if c then color.bg = c end if c then
bg = c
end
elseif p == 49 then elseif p == 49 then
color.bg = color.BG bg = DEFAULT_BG
elseif p == 58 then
parse_extended_color()
elseif p == 59 then
elseif 90 <= p and p <= 97 then elseif 90 <= p and p <= 97 then
color.fg = ANSIColorPalette["bright"][p - 90] fg = ANSIColorPalette["bright"][p - 90]
elseif 100 <= p and p <= 107 then elseif 100 <= p and p <= 107 then
color.bg = ANSIColorPalette["bright"][p - 100] bg = ANSIColorPalette["bright"][p - 100]
end end
j = j + 1 j = j + 1
end end
update_gpu_colors() elseif op == 0x73 then
return flush()
end
if op == 0x73 then
cursor.X = cursor.x cursor.X = cursor.x
cursor.Y = cursor.y cursor.Y = cursor.y
return elseif op == 0x75 then
end flush()
if op == 0x75 then
if cursor.X and cursor.Y then if cursor.X and cursor.Y then
cursor.x = cursor.X cursor.x = cursor.X
cursor.y = cursor.Y cursor.y = cursor.Y
if cursor.x < 1 then cursor.x = 1 end if cursor.x < 1 then
if cursor.y < 1 then cursor.y = 1 end cursor.x = 1
if cursor.x > width then cursor.x = width end end
if cursor.y > height then cursor.y = height end if cursor.y < 1 then
cursor.y = 1
end
if cursor.x > width then
cursor.x = width
end
if cursor.y > height then
cursor.y = height
end
end end
return
end end
end end
function _G._PUBLIC.terminal.writec(byte) function _G._PUBLIC.terminal.write(text)
text = tostring(text)
local len = #text
local i = 1
while i <= len do
local byte = string.byte(text, i)
if byte == 0x1b then if byte == 0x1b then
_PUBLIC.terminal.flush()
printState = 1 printState = 1
seq = {} seq = {}
return i = i + 1
end elseif printState == 1 then
printState = byte == 0x5b and 2 or 0
if printState == 1 then i = i + 1
if byte == 0x5b then elseif printState == 2 then
printState = 2
else
printState = 0
end
return
end
if printState == 2 then
table.insert(seq, byte) table.insert(seq, byte)
if 0x40 <= byte and byte <= 0x7e then if 0x40 <= byte and byte <= 0x7e then
exec_csi() exec_csi()
printState = 0 printState = 0
seq = {} seq = {}
end end
return i = i + 1
end elseif byte == 0xa then
flush()
if byte == 0xa then
_PUBLIC.terminal.flush()
cursor.y = cursor.y + 1 cursor.y = cursor.y + 1
cursor.x = 1 cursor.x = 1
check_wrap_and_scroll() check_wrap_and_scroll()
return bufStartX = cursor.x
end bufStartY = cursor.y
i = i + 1
if byte == 0xd then elseif byte == 0xd then
_PUBLIC.terminal.flush() flush()
cursor.x = 1 cursor.x = 1
return bufStartX = 1
end bufStartY = cursor.y
i = i + 1
if byte == 0x8 then elseif byte == 0x8 then
_PUBLIC.terminal.flush() flush()
if cursor.x > 1 then if cursor.x > 1 then cursor.x = cursor.x - 1 end
cursor.x = cursor.x - 1 i = i + 1
end elseif byte == 0x9 then
return flush()
end
if byte == 0x9 then
_PUBLIC.terminal.flush()
cursor.x = ((cursor.x - 1) // 8) * 8 + 9 cursor.x = ((cursor.x - 1) // 8) * 8 + 9
if cursor.x < 1 then cursor.x = 1 end
if cursor.x > width then cursor.x = width end if cursor.x > width then cursor.x = width end
return i = i + 1
elseif byte >= 0x20 and byte <= 0x7E then
local j = i + 1
while j <= len do
local b = string.byte(text, j)
if b < 0x20 or b > 0x7E then break end
j = j + 1
end end
local runLen = j - i
local space = width - cursor.x + 1
if byte >= 0x20 and byte <= 0x7F then if runLen <= space then
table.insert(writeBuf, string.char(byte)) buf_append(text:sub(i, j - 1), runLen)
cursor.x = cursor.x + 1
if cursor.x > width then if cursor.x > width then
_PUBLIC.terminal.flush() flush()
end cursor.x = 1
cursor.y = cursor.y + 1
check_wrap_and_scroll() check_wrap_and_scroll()
bufStartX = cursor.x
bufStartY = cursor.y
end
i = j
else
buf_append(text:sub(i, i + space - 1), space)
flush()
cursor.x = 1
cursor.y = cursor.y + 1
check_wrap_and_scroll()
bufStartX = cursor.x
bufStartY = cursor.y
i = i + space
end
elseif byte >= 0xC2 and byte <= 0xDF then elseif byte >= 0xC2 and byte <= 0xDF then
current_codepoint = (byte & 0x1F) current_codepoint = (byte & 0x1F)
bytes_remaining = 1 bytes_remaining = 1
i = i + 1
elseif byte >= 0xE0 and byte <= 0xEF then elseif byte >= 0xE0 and byte <= 0xEF then
current_codepoint = (byte & 0x0F) current_codepoint = (byte & 0x0F)
bytes_remaining = 2 bytes_remaining = 2
i = i + 1
elseif byte >= 0xF0 and byte <= 0xF7 then elseif byte >= 0xF0 and byte <= 0xF7 then
current_codepoint = (byte & 0x07) current_codepoint = (byte & 0x07)
bytes_remaining = 3 bytes_remaining = 3
i = i + 1
elseif byte >= 0x80 and byte <= 0xBF and bytes_remaining > 0 then elseif byte >= 0x80 and byte <= 0xBF and bytes_remaining > 0 then
current_codepoint = (current_codepoint << 6) | (byte & 0x3F) current_codepoint = (current_codepoint << 6) | (byte & 0x3F)
bytes_remaining = bytes_remaining - 1 bytes_remaining = bytes_remaining - 1
if bytes_remaining == 0 then if bytes_remaining == 0 then
table.insert(writeBuf, utf8.char(current_codepoint)) buf_append(unicode.char(current_codepoint), 1)
cursor.x = cursor.x + 1
if cursor.x > width then if cursor.x > width then
_PUBLIC.terminal.flush() flush()
end cursor.x = 1
cursor.y = cursor.y + 1
check_wrap_and_scroll() check_wrap_and_scroll()
bufStartX = cursor.x
bufStartY = cursor.y
end
current_codepoint = 0 current_codepoint = 0
end end
i = i + 1
else else
current_codepoint = 0 current_codepoint = 0
bytes_remaining = 0 bytes_remaining = 0
i = i + 1
end
end end
end end
function _G._PUBLIC.terminal.write(text) function _G._PUBLIC.terminal.flush()
text = tostring(text) flush()
for i = 1, #text do
_PUBLIC.terminal.writec(string.byte(text, i))
end
end end
function _G._PUBLIC.terminal.clear() function _G._PUBLIC.terminal.clear()
flush()
update_gpu_colors() update_gpu_colors()
gpu.fill(1, 1, width, height, " ") gpu.fill(1, 1, width, height, " ")
writeBuf = {}
cursor.x = 1 cursor.x = 1
cursor.y = 1 cursor.y = 1
end end
function _G._PUBLIC.terminal.flush()
if #writeBuf == 0 then return end
update_gpu_colors()
gpu.set(cursor.x - #writeBuf, cursor.y, table.concat(writeBuf))
writeBuf = {}
end
function _G.print(...) function _G.print(...)
local args = {...} local args = { ... }
local stringArgs = {} local stringArgs = {}
for _, arg in pairs(args) do for _, arg in pairs(args) do
if type(arg) == "table" then if type(arg) == "table" then
@@ -592,8 +597,8 @@ function module.init()
local historyIdx local historyIdx
if options.readHistoryType then if options.readHistoryType then
if not readHistory[options.readHistoryType] then if not readHistory[options.readHistoryType] then
readHistory[options.readHistoryType] = {text} readHistory[options.readHistoryType] = { text }
elseif readHistory[options.readHistoryType][#readHistory[options.readHistoryType] ] ~= text then elseif readHistory[options.readHistoryType][#readHistory[options.readHistoryType]] ~= text then
table.insert(readHistory[options.readHistoryType], text) table.insert(readHistory[options.readHistoryType], text)
end end
historyIdx = #readHistory[options.readHistoryType] historyIdx = #readHistory[options.readHistoryType]
@@ -602,209 +607,222 @@ function module.init()
local function updateHistory() local function updateHistory()
if not options.readHistoryType then return end if not options.readHistoryType then return end
if historyIdx ~= #readHistory[options.readHistoryType] then return end if historyIdx ~= #readHistory[options.readHistoryType] then return end
readHistory[options.readHistoryType][historyIdx]=text readHistory[options.readHistoryType][historyIdx] = text
end end
local cur = unicode.len(text)+1 local cur = unicode.len(text) + 1
if options.prefix then _PUBLIC.terminal.write(options.prefix) end if options.prefix then _PUBLIC.terminal.write(options.prefix) end
_G._PUBLIC.terminal.flush() flush()
local startX, startY = cursor.x, cursor.y local startX, startY = cursor.x, cursor.y
local fg, bg = gpu.getForeground(), gpu.getBackground()
local cursorBlink = true local cursorBlink = true
local function checkScroll(y) local function checkScroll(y)
for i=1,y-height do if y > height then
scrollDown() local n = y - height
startY=startY-1 scroll(n)
startY = startY - n
y = height
end end
return math.min(y,height) return y
end end
local function set(index, character, invertedColors) -- HACK: Currently, this will uncensor all spaces in the inputted text. local function set(index, character, invertedColors) -- HACK: Currently, this will uncensor all spaces in the inputted text.
if character==nil or character=="" then return end if character == nil or character == "" then return end
if options.censor then if options.censor then
character = character:gsub("[^ ]", options.censor) character = character:gsub("[^ ]", options.censor)
end end
if invertedColors then if invertedColors then
gpu.setForeground(bg) if gpuFg ~= bg then gpu.setForeground(bg); gpuFg = bg end
gpu.setBackground(fg) if gpuBg ~= fg then gpu.setBackground(fg); gpuBg = fg end
else else
gpu.setForeground(fg) if gpuFg ~= fg then gpu.setForeground(fg); gpuFg = fg end
gpu.setBackground(bg) if gpuBg ~= bg then gpu.setBackground(bg); gpuBg = bg end
end end
index=startX+index-1 index = startX + index - 1
local setX, setY = (index-1)%width+1, startY+((index-1)//width+1)-1 local setX = (index - 1) % width + 1
local setY = startY + ((index - 1) // width)
setY = checkScroll(setY) setY = checkScroll(setY)
gpu.set(setX,setY,unicode.sub(character,1,width-setX+1)) local firstLen = width - setX + 1
for i=1,math.ceil((#character+setX-1)/width)+1 do gpu.set(setX, setY, unicode.sub(character, 1, firstLen))
gpu.set(1,setY+i,unicode.sub(character,2-setX+i*width,width+i*width-setX)) local charLen = unicode.len(character)
if charLen > firstLen then
local offset = firstLen + 1
while offset <= charLen do
setY = setY + 1
setY = checkScroll(setY) setY = checkScroll(setY)
local segEnd = offset + width - 1
gpu.set(1, setY, unicode.sub(character, offset, segEnd))
offset = segEnd + 1
end end
end end
local function strDef(a,b) end
if #a==0 then return b end
local function strDef(a, b)
if #a == 0 then return b end
return a return a
end end
local function curPos(cur) local function curPos(c)
return unicode.wlen(unicode.sub(text,1,cur-1))+1 return unicode.wlen(unicode.sub(text, 1, c - 1)) + 1
end end
local function add(chr) local function add(chr)
if type(chr)~="string" or #chr==0 then return end if type(chr) ~= "string" or #chr == 0 then
if unicode.len(text)>=options.maxChars then return end return
if options.maxChars<math.huge then
chr=unicode.sub(chr,1,options.maxChars-unicode.len(text))
end end
text=unicode.sub(text,1,cur-1)..chr..unicode.sub(text,cur) if unicode.len(text) >= options.maxChars then
set(curPos(cur),chr,false) return
cur=math.min(cur+unicode.len(chr),options.maxChars+1) end
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true) if options.maxChars < math.huge then
chr = unicode.sub(chr, 1, options.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), options.maxChars + 1)
set(curPos(cur), strDef(unicode.sub(text, cur, cur), " "), true)
cursorBlink = true cursorBlink = true
set(curPos(cur+1),unicode.sub(text,cur+1),false) set(curPos(cur + 1), unicode.sub(text, cur + 1), false)
end end
local function moveCur(dir) local function moveCur(dir)
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),false) set(curPos(cur), strDef(unicode.sub(text, cur, cur), " "), false)
cur=math.max(math.min(cur+dir,unicode.len(text)+1),1) cur = math.max(math.min(cur + dir, unicode.len(text) + 1), 1)
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true) set(curPos(cur), strDef(unicode.sub(text, cur, cur), " "), true)
cursorBlink = true cursorBlink = true
end end
local function isLetter(chr) local function isLetter(chr)
return not string.find("\x09 :@-./_~?&=%+#",chr,1,true) return not string.find("\x09 :@-./_~?&=%+#", chr, 1, true)
end end
local function nextCur(dir,chr,icur) local function nextCur(dir, chr, icur)
if icur==nil then icur=cur end if icur == nil then icur = cur end
local next = math.max(math.min(icur+dir,unicode.len(text)+1),1) local next = math.max(math.min(icur + dir, unicode.len(text) + 1), 1)
if chr then return unicode.sub(text,next,next) end if chr then return unicode.sub(text, next, next) end
return next return next
end end
local function curAfterWord(dir) local function curAfterWord(dir)
local ncur = cur local ncur = cur
while nextCur(dir,false,ncur)~=ncur and isLetter(nextCur(dir,true,ncur))==(dir==1) do while nextCur(dir, false, ncur) ~= ncur and isLetter(nextCur(dir, true, ncur)) == (dir == 1) do
ncur=nextCur(dir,false,ncur) ncur = nextCur(dir, false, ncur)
end end
while nextCur(dir,false,ncur)~=ncur and isLetter(nextCur(dir,true,ncur))==(dir==-1) do while nextCur(dir, false, ncur) ~= ncur and isLetter(nextCur(dir, true, ncur)) == (dir == -1) do
ncur=nextCur(dir,false,ncur) ncur = nextCur(dir, false, ncur)
end end
return ncur return ncur
end end
local function moveWord(dir) local function moveWord(dir)
if nextCur(dir)==cur then return end if nextCur(dir) == cur then return end
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),false) set(curPos(cur), strDef(unicode.sub(text, cur, cur), " "), false)
cur=curAfterWord(dir) cur = curAfterWord(dir)
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true) set(curPos(cur), strDef(unicode.sub(text, cur, cur), " "), true)
cursorBlink = true cursorBlink = true
end end
local function deleteWord(dir) local function deleteWord(dir)
local after = curAfterWord(dir) local after = curAfterWord(dir)
local lenb = unicode.wlen(text) local lenb = unicode.wlen(text)
if dir==1 then if dir == 1 then
text=unicode.sub(text,1,cur-1)..unicode.sub(text,after) text = unicode.sub(text, 1, cur - 1) .. unicode.sub(text, after)
set(curPos(cur+1),unicode.sub(text,cur+1)..string.rep(" ",lenb-unicode.wlen(text)+1),false) set(curPos(cur + 1), unicode.sub(text, cur + 1) .. string.rep(" ", lenb - unicode.wlen(text) + 1), false)
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true) set(curPos(cur), strDef(unicode.sub(text, cur, cur), " "), true)
else else
text = unicode.sub(text,1,after-1)..unicode.sub(text,cur) text = unicode.sub(text, 1, after - 1) .. unicode.sub(text, cur)
cur=after cur = after
set(curPos(cur+1),unicode.sub(text,cur+1)..string.rep(" ",lenb-unicode.wlen(text)+1),false) set(curPos(cur + 1), unicode.sub(text, cur + 1) .. string.rep(" ", lenb - unicode.wlen(text) + 1), false)
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true) set(curPos(cur), strDef(unicode.sub(text, cur, cur), " "), true)
end end
updateHistory() updateHistory()
cursorBlink = true cursorBlink = true
end end
local function isLine(chr) local function isLine(chr)
return chr=="\n" or chr=="\r" return chr == "\n" or chr == "\r"
end end
--[[ gpu.set(startX,startY,unicode.sub(text,1,width-startX))
for i=1,(#text+startX)//width-1 do set(1, text, false)
gpu.set(startX,startY+i,unicode.sub(text,1+i*width,width-startX+i*width)) set(curPos(cur), " ", true)
end ]]
set(1,text,false)
set(curPos(cur)," ",true)
local function reprint(new) local function reprint(new)
set(1,new..string.rep(" ",unicode.wlen(text)-unicode.wlen(new)+1),false) set(1, new .. string.rep(" ", unicode.wlen(text) - unicode.wlen(new) + 1), false)
cur=unicode.len(new)+1 cur = unicode.len(new) + 1
text=new text = new
set(curPos(cur)," ",true) set(curPos(cur), " ", true)
end end
while true do while true do
local args = {event.pull("key_down", "clipboard", 0.5)} local args = { event.pull("key_down", "clipboard", 0.5) }
local ctrlDown = _PUBLIC.keyboard.getCtrlDown() local ctrlDown = _PUBLIC.keyboard.getCtrlDown()
if args and args[1] == "key_down" and args[4] then if args and args[1] == "key_down" and args[4] then
local key = _PUBLIC.keyboard.keys[args[4]] local key = _PUBLIC.keyboard.keys[args[4]]
if key=="up" and options.readHistoryType then if key == "up" and options.readHistoryType then
historyIdx=math.max(historyIdx-1,1) historyIdx = math.max(historyIdx - 1, 1)
reprint(readHistory[options.readHistoryType][historyIdx]) reprint(readHistory[options.readHistoryType][historyIdx])
elseif key=="down" and options.readHistoryType then elseif key == "down" and options.readHistoryType then
historyIdx=math.min(historyIdx+1,#readHistory[options.readHistoryType]) historyIdx = math.min(historyIdx + 1, #readHistory[options.readHistoryType])
reprint(readHistory[options.readHistoryType][historyIdx]) reprint(readHistory[options.readHistoryType][historyIdx])
elseif key=="left" and ctrlDown then elseif key == "left" and ctrlDown then
moveWord(-1) moveWord(-1)
elseif key=="right" and ctrlDown then elseif key == "right" and ctrlDown then
moveWord(1) moveWord(1)
elseif key=="left" then elseif key == "left" then
moveCur(-1) moveCur(-1)
elseif key=="right" then elseif key == "right" then
moveCur(1) moveCur(1)
elseif key=="home" then elseif key == "home" then
moveCur(-math.huge) moveCur(-math.huge)
elseif key=="end" then elseif key == "end" then
moveCur(math.huge) moveCur(math.huge)
elseif key=="back" and ctrlDown then elseif key == "back" and ctrlDown then
deleteWord(-1) deleteWord(-1)
elseif key=="delete" and ctrlDown then elseif key == "delete" and ctrlDown then
deleteWord(1) deleteWord(1)
elseif key=="back" and cur>1 then elseif key == "back" and cur > 1 then
text=unicode.sub(text,1,cur-2)..unicode.sub(text,cur) text = unicode.sub(text, 1, cur - 2) .. unicode.sub(text, cur)
cur=cur-1 cur = cur - 1
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true) set(curPos(cur), strDef(unicode.sub(text, cur, cur), " "), true)
cursorBlink = true cursorBlink = true
set(curPos(cur)+1,unicode.sub(text,cur+1).." ",false) set(curPos(cur) + 1, unicode.sub(text, cur + 1) .. " ", false)
updateHistory() updateHistory()
elseif key=="delete" then elseif key == "delete" then
text = unicode.sub(text,1,cur-1)..unicode.sub(text,cur+1) text = unicode.sub(text, 1, cur - 1) .. unicode.sub(text, cur + 1)
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true) set(curPos(cur), strDef(unicode.sub(text, cur, cur), " "), true)
cursorBlink = true cursorBlink = true
if cur<=unicode.len(text) then if cur <= unicode.len(text) then
set(curPos(cur+1),unicode.sub(text,cur+1).." ",false) set(curPos(cur + 1), unicode.sub(text, cur + 1) .. " ", false)
end end
updateHistory() updateHistory()
elseif key=="enter" then elseif key == "enter" then
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),false) set(curPos(cur), strDef(unicode.sub(text, cur, cur), " "), false)
break break
elseif not (args[3]<32 or (args[3]>0x7F and args[3]<=0x9F)) then elseif not (args[3] < 32 or (args[3] > 0x7F and args[3] <= 0x9F)) then
add(unicode.char(args[3]) or " ") add(unicode.char(args[3]) or " ")
updateHistory() updateHistory()
end end
elseif args and args[1]=="clipboard" then elseif args and args[1] == "clipboard" then
local clip = args[3] local clip = args[3]
if not args[3] then goto continue end if not clip then goto continue end
while isLine(unicode.sub(clip,1,1)) do clip=unicode.sub(clip,2) 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 while isLine(unicode.sub(clip, -1)) do clip = unicode.sub(clip, 1, -2) end
add(clip) add(clip)
updateHistory() updateHistory()
else else
cursorBlink=not cursorBlink cursorBlink = not cursorBlink
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),cursorBlink) set(curPos(cur), strDef(unicode.sub(text, cur, cur), " "), cursorBlink)
end end
::continue:: ::continue::
end end
if options.readHistoryType then if options.readHistoryType then
if readHistory[options.readHistoryType][#readHistory[options.readHistoryType]]=="" then if readHistory[options.readHistoryType][#readHistory[options.readHistoryType]] == "" then
table.remove(readHistory[options.readHistoryType],#readHistory[options.readHistoryType]) table.remove(readHistory[options.readHistoryType], #readHistory[options.readHistoryType])
end end
if historyIdx<#readHistory[options.readHistoryType] then if historyIdx < #readHistory[options.readHistoryType] then
-- table.remove(readHistory[options.readHistoryType],historyIdx) table.insert(readHistory[options.readHistoryType], text)
table.insert(readHistory[options.readHistoryType],text)
end end
while #readHistory[options.readHistoryType] > 50 do while #readHistory[options.readHistoryType] > 50 do
table.remove(readHistory[options.readHistoryType], 1) table.remove(readHistory[options.readHistoryType], 1)
end end
end end
cursor.x=1 cursor.x = 1
cursor.y=cursor.y+math.ceil((unicode.wlen(text)+startX-1)/width) cursor.y = cursor.y + math.ceil((unicode.wlen(text) + startX - 1) / width)
if cursor.y>height then scroll() end if cursor.y > height then
scroll(cursor.y - height)
end
return text return text
end end
end end