2025-10-21 00:40:14 +02:00

1506 lines
42 KiB
Lua

local input = {}
local shapescape
local utils
input.init = function(api)
shapescape = api
utils = api.utils
end
local function isInside(x,y,object)
local function n(var)
return type(var) == "number"
end
local x1,y1,x2,y2
if n(object.x1) and n(object.y1) and n(object.x2) and n(object.y2) then
x1,y1,x2,y2 = object.x1,object.y1,object.x2,object.y2
elseif n(object.x) and n(object.y) then
x1,y1 = object.x,object.y
if n(object.w) and n(object.h) then
x2,y2 = x1+(object.w-1),y1+(object.h-1)
else
x2,y2 = x1,y1
end
else
error("Invalid input: "..textutils.serialize(object,{compact=true}),2)
end
if x2 < x1 then
x1,x2 = x2,x1
end
if y2 < y1 then
y1,y2 = y2,y1
end
if x >= x1 and y >= y1 and x <= x2 and y <= y2 then
return true
else
return false
end
end
local function getLines(str)
local lines = {}
local w = 0
for potato in str:gmatch("([^\n]*)\n?") do
table.insert(lines,potato)
if #potato > w then
w = #potato
end
end
return lines,w
end
local function cRestore(restore)
if not restore then
return {bg=term.getBackgroundColor(),fg=term.getTextColor(),cursor={term.getCursorPos()}}
else
term.setBackgroundColor(restore.bg)
term.setTextColor(restore.fg)
term.setCursorPos(unpack(restore.cursor))
end
end
function input.box(x1,y1,x2,y2,tOptions,sReplaceChar,tShape)
local holdTbl = {}
local isHolding
if lUtils then
isHolding = lUtils.isHolding
else
isHolding = function(key)
if type(key) == "string" then
key = keys[key]
end
return not not holdTbl
end
end
local oCursorA
local s
if not tShape then
s = {x1=x1,y1=y1,x2=x2,y2=y2,cursor={x=1,y=1,a=1},scr=0,ref={}}
else
s = tShape
s.cursor={x=1,y=1,a=1}
s.scr=0
s.ref={}
end
s.history = {}
s.rhistory = {}
s.changed = false
local opt = {}
if tOptions then
opt = tOptions
elseif s.opt then
opt = s.opt
else
opt = {}
end
if not opt.overflow and not opt.overflowX and not opt.overflowY then
opt.overflow = "scroll"
opt.overflowX = "scroll"
opt.overflowY = "none"
end
opt["overflow"] = opt.overflow or "scroll" -- none, stretch, scroll or wrap
opt["overflowX"] = opt.overflowX or opt.overflow
opt["overflowY"] = opt.overflowY or opt.overflow
if opt.overflowY == "wrap" then
opt.overflowY = "none"
end
opt["cursorColor"] = opt.cursorColor or s.txtcolor or term.getTextColor()
opt["replaceChar"] = sReplaceChar or opt.replaceChar
opt["minWidth"] = opt.minWidth or s.x2-(s.x1-1)
opt["minHeight"] = opt.minHeight or s.y2-(s.y1-1)
opt["tabSize"] = opt.tabSize or 4
opt["indentChar"] = opt.indentChar or " "
if opt.overflowX == "scroll" then
s.scrollX = 0
end
if opt.overflowY == "scroll" then
s.scrollY = 0
end
--[[opt["maxWidth"] = opt.maxWidth
opt["maxHeight"] = opt.maxHeight]]
s.opt = opt
s.color = s.color or opt.backgroundColor or term.getBackgroundColor()
s.txtcolor = s.txtcolor or opt.textColor or term.getTextColor()
local txtcolor = s.txtcolor
s.txt = opt.text or ""
--s.lines = {s.txt}
s.lines = {} -- real text input, for example password if censored
s.dLines = {} -- rendered text input, buncha asterisks if censored, spaces instead of tab
s.blit = {} -- foreground and background colors
s.state = false
local ref = s.ref
local syntaxes = {
["lua"] = {
lexer="lex",
whitespace=colors.white,
comment=colors.green,
string=colors.red,
escape=colors.orange,
keyword=colors.yellow,
value=colors.yellow,
ident=colors.white,
number=colors.purple,
symbol=colors.orange,
operator=colors.yellow,
unidentified=colors.white,
},
["lua-light"] = {
lexer="lex",
whitespace=colors.black,
comment=colors.lightGray,
string=colors.red,
escape=colors.orange,
keyword=colors.blue,
value=colors.purple,
ident=colors.black,
number=colors.lightBlue,
symbol=colors.orange,
operator=colors.gray,
unidentified=colors.black,
}
}
local syntax
local uservars = {}
local scope = 0
local function lineLn(line)
local findTab = line:find("\t")
local offset = 0
local t = s.opt.tabSize
while findTab do
local l = t-(findTab+offset-1)%t
offset = offset+(l-1)
findTab = line:find("\t",findTab+1)
end
return #line+offset
end
local function fillTable(tTable,tbl,prefix)
--local type = rtype
local docs = s.opt.complete.docs
while tTable do
for k,v in pairs(tTable) do
if type(k) == "string" and not tbl[k] then
if type(v) == "table" then
tbl[k] = {type="table",data={},name=prefix..k}
if docs and docs[tbl[k].name] then
tbl[k].docs = docs[tbl[k].name]
end
--[[scope = scope+1
fillTable(v,tbl[k].data,prefix..k..".")
scope = scope-1]]
elseif type(v) == "string" or type(v) == "number" or type(v) == "boolean" then
tbl[k] = {type=type(v),data=v,name=prefix..k}
if docs and docs[tbl[k].name] then
tbl[k].docs = docs[tbl[k].name]
end
elseif type(v) == "function" then
local obj = {type="function",data=v,name=prefix..k}
local args = {}
if rtype(v) == "table" then
local mt = getmetatable(v)
if rtype(mt) == "table" and rtype(mt._call) == "function" then
v = mt._call
end
end
if rtype(v) == "function" then
local info = debug.getinfo(v)
for t=1,info.nparams do
table.insert(args,debug.getlocal(v,t))
end
if info.isvararg then
table.insert(args,"...")
end
obj.args = args
obj.source = info.short_src
end
if not obj.args then obj.args = {} end
if not obj.source then obj.source = "" end
if docs and docs[obj.name] then
obj.docs = docs[obj.name]
end
tbl[k] = obj
end
end
end
local tMetatable = getmetatable(tTable)
if tMetatable and type(tMetatable.__index) == "table" then
tTable = tMetatable.__index
else
tTable = nil
end
end
end
local keywords = {"and", "break", "do", "else", "elseif", "end", "for", "function", "if", "in", "local", "not", "or", "repeat", "return", "then", "until", "while"}
local function complete(sTxt)
local docs = s.opt.complete.docs
if not sTxt then return {} end
local sSearchText
local fComplete = false
local sSearchText = sTxt:match("^[a-zA-Z0-9_%.:]+")
if not sSearchText then return end
if #sSearchText < #sTxt then
fComplete = true
end
s.opt.complete.selected = nil
local env = s.opt.complete.env
fillTable(env,uservars,"")
local nStart = 1
local nDot = string.find(sSearchText, ".", nStart, true)
local tTable = uservars
while nDot do
local sPart = string.sub(sSearchText, nStart, nDot - 1)
if not tTable[sPart] then return {} end
local value = tTable[sPart].data
if type(value) == "table" then
tTable = value
if type(env[sPart]) == "table" then
fillTable(env[sPart],tTable,"")
env = env[sPart]
end
nStart = nDot + 1
nDot = string.find(sSearchText, ".", nStart, true)
else
return {}
end
end
local nColon = string.find(sSearchText, ":", nStart, true)
if nColon then
local sPart = string.sub(sSearchText, nStart, nColon - 1)
if not tTable[sPart] then return {} end
local value = tTable[sPart].data
if type(value) == "table" then
tTable = value
if type(env[sPart]) == "table" then
fillTable(env[sPart],tTable,"")
env = env[sPart]
end
nStart = nColon + 1
else
return {}
end
end
local sPart = string.sub(sSearchText, nStart)
local prefix = string.sub(sSearchText, 1, nStart-1)
local nPartLength = #sPart
local tResults = {}
local tStrings = {}
local tSorted = {}
local tSeen = {}
if fComplete then
tResults.length = #sTxt
else
tResults.length = nPartLength
end
for k,v in pairs(tTable) do
if not tSeen[k] and type(k) == "string" and not (nColon and v.type ~= "function") then
if (string.find(k, sPart, 1, true) == 1 and not fComplete) or (fComplete and k == sPart) then
if string.match(k, "^[%a_][%a%d_]*$") then
local sResult = string.sub(k, nPartLength + 1)
local display = k
local index = sSearchText..sResult
if v.type == "function" then
local vArgs = utils.instantiate(v.args)
if nColon then
table.remove(vArgs,1)
end
if fComplete then
display = index.."("..table.concat(vArgs,",")..")"
else
display = k.."("..table.concat(vArgs,",")..")"
end
sResult = sResult.."("
end
tStrings[sResult] = {src=(v.source or ""),complete=sResult,type=v.type,display=display}
if fComplete and v.type ~= "function" then return nil end
if docs[index] then
local d = docs[index]
if fComplete then
tStrings[sResult].display = d.name:gsub("^_G%.","")
tStrings[sResult].description = d.summary
else
tStrings[sResult].display = d.name:gsub("^_G%.",""):gsub("^"..prefix,"")
end
end
table.insert(tSorted,sResult)
--tResults[#tResults].id = #tResults
end
end
end
tSeen[k] = true
end
table.sort(tSorted)
for t=1,#tSorted do
tResults[t] = tStrings[tSorted[t]]
tResults[t].id = t
end
--table.sort(tResults)
return tResults
end
local function renderComplete(list) -- make option to use overay like s.opt.complete.overlay = true
local c = s.opt.complete
local LevelOS = c.LevelOS
c.reverse = false
if not c.selected and not list[1].description then
c.selected = list[1]
end
local a = 0
if s.border and s.border.color ~= 0 then
a = 1
end
local scrollX = s.scrollX or 0
local scrollY = s.scrollY or 0
local x,y = s.x1+(s.cursor.x-1)+a-scrollX,s.y1+(s.cursor.y-1)+a-scrollY
x = x-list.length-4
y = y+1
local width = 10
local height = #list
for t=1,#list do
width = math.max(#list[t].display+#list[t].src+5,width)
if list[t].description then
list[t].lines = utils.wordwrap(list[t].description,width-4)
height = height+#list[t].lines
end
end
if c.overlay and LevelOS then
--local wX,wY = LevelOS.self.window.win.getPosition()
local wX,wY = lUtils.getWindowPos(term.current())
local w,h = lOS.oldterm.getSize()
x = x+wX-1
y = y+wY-1
if x < 1 then
x = 1
end
if y+(height-1) > h and y-1-height >= 1 then
y = y-2
c.reverse = true
end
if x+(width-1) > w then
x = w-width+1
end
else
if x < s.x1 then
x = s.x1
end
if y+(height-1) > s.y2 and y-1-height >= s.y1 then
c.reverse = true
y = y-2
end
if x+(width-1) > s.x2 then
x = s.x2-width+1
end
end
local offset = 1
if c.reverse then
offset = -1
end
local x2 = x+(width-1)
abbrevs = {table={"tbl",colors.lime},number={"num",colors.yellow},boolean={"bln",colors.yellow},["function"]={"fnc",colors.cyan},keyword={" ",colors.orange},["nil"]={"nil",colors.red},unknown={"???",colors.pink},string={"str",colors.yellow}}
local function theRender()
if not s.opt.complete.colors then
s.opt.complete.colors = {}
end
local col = s.opt.complete.colors
col.selectedbg = col.selectedbg or colors.lightBlue -- green
col.backgroundColor = col.backgroundColor or colors.gray -- magenta
col.txtcolor = col.txtcolor or colors.white -- white
col.typebg = col.typebg or colors.black -- light gray
col.selectedtypebg = col.selectedtypebg or colors.blue -- green
col.sourcetxtcolor = col.sourcetxtcolor or colors.lightGray -- pink
local cY = y
for t=1,#list do
if c.selected == list[t] then
term.setBackgroundColor(col.selectedtypebg)
else
term.setBackgroundColor(col.typebg)
end
local a = abbrevs[list[t].type] or abbrevs["unknown"]
term.setTextColor(a[2])
term.setCursorPos(x,cY)
term.write(a[1])
if c.selected == list[t] then
term.setBackgroundColor(col.selectedbg)
else
term.setBackgroundColor(col.backgroundColor)
end
term.setTextColor(col.txtcolor)
term.write(" "..list[t].display..string.rep(" ",width-#list[t].display-4-#list[t].src))
term.setCursorPos(x2-(#list[t].src-1),cY)
term.setTextColor(col.sourcetxtcolor)
term.write(list[t].src)
cY = cY+offset
if list[t].lines then
for i,l in ipairs(list[t].lines) do
term.setCursorPos(x,cY)
term.setTextColor(col.sourcetxtcolor)
term.write(" "..l..string.rep(" ",width-#l-4))
cY = cY+offset
end
end
end
end
local x1,y1,x2,y2
if c.overlay and LevelOS then
LevelOS.overlay = theRender
local wX,wY = LevelOS.self.window.win.getPosition()
x1 = x-wX+1
x2 = x1+width-1
y1 = y-wY+1
y2 = y1+((#list-1)*offset)
else
x1 = x
y1 = y
y2 = y1+((#list-1)*offset)
theRender()
end
list.x1 = x1
list.y1 = math.min(y1,y2)
list.y2 = math.max(y1,y2)
list.x2 = x2
end
local function replaceText(txt,pos1,pos2,replace)
return txt:sub(1,pos1-1)..replace..txt:sub(pos2+1,#txt)
end
local function genLines(t)
local syn = s.opt.syntax
if type(syn) == "string" and syntaxes[syn] then
syntax = syntaxes[syn]
syntax.type = syn
elseif type(syn) == "table" and syn.lexer and type(syn.lexer) == "function" then
syntax = syn
elseif type(syn) == "table" and syntaxes[syn.type] then
syntax = syntaxes[syn.type]
for k,v in pairs(syn) do
syntax[k] = v
end
elseif type(syn) == "table" and syn.lexer and ((type(syn.lexer) == "string" and fs.exists(syn.lexer)) or type(syn.lexer) == "function") then
syntax = syn
else
syntax = nil
end
local blit = {}
if s.opt.complete and syntax then
uservars = {}
for k=1,#keywords do
uservars[keywords[k]] = {type="keyword"}
end
uservars["true"] = {type="boolean"}
uservars["false"] = {type="boolean"}
uservars["nil"] = {type="nil"}
_G.debuguservars = uservars
--[[scope = 0
local tTable = s.opt.complete.env
fillTable(tTable,uservars,"")]]
end
if syntax and ((type(syntax.lexer) == "string" and fs.exists(syntax.lexer)) or type(syntax.lexer) == "function") then
if type(syntax.lexer) ~= "function" then
syntax.lexer = dofile(syntax.lexer)
end
local lex = syntax.lexer
local sublines = lex(t)
local line = 1
local l = 0
local ref = 0
--for li in t:gmatch("([^\n]*)\n?") do -- this aint workin and dont forget to add newlines to the blit lines
blit[1] = ""
blit[2] = ""
_G.debugsublines = sublines
for l=1,#sublines do
local elements = sublines[l]
for t=1,#elements do
local col = utils.toBlit(syntax[elements[t].type] or s.txtcolor)
blit[1] = blit[1]..elements[t].data
blit[2] = blit[2]..string.rep(col,#elements[t].data)
if s.opt.complete and #s.txt < 40000 and syntax.type:find("^lua") then
if elements[t].type == "nfunction" then
local el = t+1
local args = {}
while true do
local e = elements[el]
if not e then
break
elseif e.type == "function" or e.type == "whitespace" or e.data == "(" or e.data == "," or e.data == "=" then
el = el+1
elseif e.type == "arg" or e.data == "..." then
table.insert(args,e.data)
el = el+1
else
break
end
end
local parent = {data=uservars}
local el = t-1
local children = {}
while true do
local e = elements[el]
if not e then
break
elseif e.data == "." then
el = el-1
elseif e.type == "ident" then
table.insert(children,1,e.data)
el = el-1
else
break
end
end
for t=1,#children do
if parent.data[children[t]] then
local child = parent.data[children[t]]
if type(child.data) ~= "table" then child.data = {} child.type = "table" end
parent = child
else
parent.data[children[t]] = {data={},type="table",source="Ln "..l}
parent = parent.data[children[t]]
end
end
parent.data[elements[t].data] = {args=args,type="function",source="Ln "..l}
elseif elements[t].type == "ident" then
local el = t+1
local foundEquals = false
local foundValue = false
local naming = true
local prefix = ""
local parent = {data=uservars}
local children = {elements[t].data}
while true do
local e = elements[el]
if not e then
break
elseif e.data == "." and naming then
el = el+1
elseif e.type == "whitespace" or (e.data == "=" and not foundEquals) then
naming = false
el = el+1
if e.data == "=" then
local stop = false
for c=1,#children-1 do
local child = parent.data[children[c]]
if not child then parent.data[children[c]] = {data={},type="table",source="Ln "..l} child = parent.data[children[c]] end
if type(child.data) ~= "table" then child.data = {} child.type = "table" end
parent = child
prefix = prefix..children[c].."."
end
if stop then break end
foundEquals = true
end
elseif e.type == "ident" then
el = el+1
if naming then
table.insert(children,e.data)
else
-- idk do later
foundValue = true
-- add table support
if uservars[e.data] then
end
end
elseif foundEquals and (e.type == "string" or e.type == "number" or (e.type == "operator" and e.data == "-") or e.type == "value" or (e.type == "symbol" and e.data:sub(1,1) == "{") or e.type == "function") then
local vType = e.type
if e.type == "operator" then
vType = "number"
elseif e.type == "value" then
if e.data == "nil" then
vType = "nil"
elseif e.data == "true" or e.data == "false" then
vType = "boolean"
else
vType = "unknown"
end
elseif e.type == "symbol" then
vType = "table"
elseif e.type == "function" then
vType = "unknown"
end
local obj = {name=prefix..children[#children],type=vType,source="Ln "..l}
if vType == "table" then
obj.data = {}
end
parent.data[children[#children]] = obj
break
else
break
end
end
end
end
end
if l < #sublines then
blit[1] = blit[1].."\n"
blit[2] = blit[2].."\n"
end
end
_G.debugBlit2 = {blit[1],blit[2]}
end
-- check for transparency for third blit line
for a=1,#s.lines do
s.lines[a] = nil
s.dLines[a] = nil
s.blit[a] = nil
ref[a] = nil
end
s.lines[1] = ""
s.dLines[1] = ""
local width = s.x2-(s.x1-1)
local height = s.y2-(s.y1-1)
if s.border and s.border.color ~= 0 then
width = width-2
height = height-2
end
local c = 1
local l = s.lines
local dl = s.dLines
local pl = 0
if opt.overflowX == "scroll" then
--local b,e = t:find()
l[1] = ""
dl[1] = ""
while true do
-- line
local b,e = t:find("[^\n]*\n?")
if not b or e == 0 then break end
local line = t:sub(b,e)
l[c] = line
if #blit > 0 then
dl[c] = line
local findTab = dl[c]:find("\t")
local blit2 = blit[2]
if not s.opt.tabSize then
s.opt.tabSize = 4
end
local t = s.opt.tabSize
while findTab do
local l = t-(findTab-1)%t
dl[c] = replaceText(dl[c],findTab,findTab,string.sub(s.opt.indentChar..string.rep(" ",t-#s.opt.indentChar),t-l+1,t))
char = utils.toBlit(syntax.whitespace)
blit2 = replaceText(blit2,findTab,findTab,string.rep(char,l))
findTab = dl[c]:find("\t")
end
local tabArea = string.rep(" ",t)
local findSpace,findSpace2 = dl[c]:find(tabArea)
while findSpace do
dl[c] = replaceText(dl[c],findSpace,findSpace2,s.opt.indentChar..string.rep(" ",t-#s.opt.indentChar))
findSpace,findSpace2 = dl[c]:find(tabArea,findSpace2+1)
end
dl[c] = dl[c]:sub(1+s.scrollX,width+s.scrollX)
s.blit[c] = {dl[c],blit2:sub(b+s.scrollX,b+s.scrollX+(#dl[c]-1)),string.rep(utils.toBlit(s.color),#dl[c])}
blit[1] = blit[1]:sub(e+1,#blit[1])
blit[2] = blit[2]:sub(e+1,#blit[2])
else
dl[c] = line:sub(1+s.scrollX,width+s.scrollX)
s.blit[c] = {dl[c],string.rep(utils.toBlit(s.txtcolor),#dl[c]),string.rep(utils.toBlit(s.color),#dl[c])}
end
if line:sub(#line) == "\n" then
if c+1 > height then
if opt.overflowY == "stretch" then
s.y2 = s.y2+1
elseif opt.overflowY == "none" then
return false
end
end
l[c+1] = ""
dl[c+1] = ""
end
t = t:sub(e+1,#t)
c = c+1
end
else
while true do
local b,e = t:find("%S*%s?")
if not b or e < 1 then break end
local w = e-(b-1)
c = #l
if not dl[c] then dl[c] = l[c] end
if string.find(t:sub(b,e),"\n",nil,true) then
if (opt.overflowY == "stretch" or opt.overflowY == "scroll") or c+1 <= height then
if opt.overflowY == "stretch" and c+1 > height then
s.y2 = s.y2+1
end
b2,e2 = t:sub(b,e):find("\n",nil,true)
e = e2
l[c+1] = ""
dl[c+1] = ""
else
return false
end
end
local of = opt.overflowX
local tW,tH = term.getSize()
if opt.overflowX == "stretch" then
if #dl[c]+w > tW and #dl[c]+w > width then
of = "wrap"
end
end
if #dl[c]+w > width then
if opt.overflowX == "wrap" then
if (opt.overflowY == "stretch" or opt.overflowY == "scroll") or c+1 <= height then
if opt.overflowY == "stretch" and c+1 > height then
s.y2 = s.y2+1
end
if not dl[c]:find("%S") then
e = width-#l[c]
l[c] = l[c]..t:sub(b,e)
l[c+1] = ""
else
--l[c+1] = t:sub(b,e)
e = b-1
l[c+1] = ""
end
else
-- oh no, stop typing
return false
end
elseif opt.overflowX == "stretch" then
s.x2 = s.x2+1
l[c] = l[c]..t:sub(b,e)
dl[c] = dl[c]..t:sub(b,e):gsub("\9"," ")
elseif opt.overflowX == "none" then
return false
end
else
l[c] = l[c]..t:sub(b,e)
dl[c] = dl[c]..t:sub(b,e):gsub("\9"," ")
end
t = t:sub(e+1,#t)
end
end
return true
end
genLines(s.txt)
local function genText()
local txt = ""
ref[1] = 1
for l=1,#s.lines do
txt = txt..s.lines[l]
ref[l+1] = ref[l]+#s.lines[l]
if s.select and s.select[1] < ref[l+1] and s.select[2] >= ref[l] and s.blit and s.blit[l] then
local line = s.lines[l]
local c = utils.toBlit(s.opt.selectColor or colors.blue)
local sel1 = s.select[1]-ref[l]
local sel2 = s.select[2]-ref[l]+2
local findTab = s.lines[l]:find("\t")
local off1 = 0
local off2 = 0
local offT = 0
while findTab do
line = replaceText(line,findTab,findTab," ")
local t = s.opt.tabSize
local a = t-(findTab+offT-1)%t
if sel1 >= findTab then
off1 = off1+a-1
end
if sel2 > findTab then
off2 = off2+a-1
end
if findTab > sel1 and findTab < sel2 then
s.blit[l][1] = replaceText(s.blit[l][1],offT+findTab,offT+findTab+(a-1),string.sub(string.rep("\140",t-1).."\132",t-a+1,t))
end
offT = offT+a-1
findTab = line:find("\t")
end
sel1 = sel1+off1-s.scrollX
sel2 = sel2+off2-s.scrollX
local pos1 = math.max(0,sel1)
local pos2 = math.min(#s.blit[l][1]+1,sel2)
s.blit[l][3] = s.blit[l][3]:sub(1,pos1)..string.rep(c,pos2-(pos1+1))..s.blit[l][3]:sub(pos2,#s.blit[l][1])
if s.opt.selectTxtColor then
local c2 = utils.toBlit(s.opt.selectTxtColor)
s.blit[l][2] = s.blit[l][2]:sub(1,pos1)..string.rep(c2,pos2-(pos1+1))..s.blit[l][2]:sub(pos2,#s.blit[l][1])
end
s.blit[l][1] = s.blit[l][1]:sub(1,pos1)..s.blit[l][1]:sub(pos1+1,pos2-1):gsub(" ","\183")..s.blit[l][1]:sub(pos2,#s.blit[l][1])
end
end
return txt
end
genText()
local function calcCursor()
local width = s.x2-(s.x1-1)
local height = s.y2-(s.y1-1)
if s.border and s.border.color ~= 0 then
width = width-2
height = height-2
end
for r=1,#s.lines do
if ref[r+1] > s.cursor.a or r == #s.lines then
s.cursor.y = r
s.cursor.x = s.cursor.a-(ref[r]-1)
local _,tabs = s.txt:sub(ref[r],s.cursor.a-1):gsub("\9","")
s.cursor.x = s.cursor.x+((s.opt.tabSize-1)*tabs)
break
end
end
if opt.overflowX == "scroll" then
if s.cursor.x < (1+s.scrollX) then
s.scrollX = s.cursor.x-1
elseif s.cursor.x > (width+s.scrollX) then
s.scrollX = s.cursor.x-width
end
if s.scrollX > 0 and lineLn(s.lines[s.cursor.y]) < width then
s.scrollX = 0
end
end
if opt.overflowY == "scroll" then
if s.cursor.y < (1+s.scrollY) then
s.scrollY = s.cursor.y-1
elseif s.cursor.y > (height+s.scrollY) then
s.scrollY = s.cursor.y-height
end
end
end
local function rCalcCursor()
local x = s.cursor.x
local tx = 0
local offset = 0
local line = s.lines[s.cursor.y]
if s.cursor.y == #s.lines then
line = line.."\n"
end
for w in line:gmatch(".") do
tx = tx+1
if w == "\t" then
local l = s.opt.tabSize-(tx-1)%s.opt.tabSize
tx = tx+l-1
offset = offset+l-1
end
if tx >= x then
x = tx-offset
break
end
end
s.cursor.a = ref[s.cursor.y]+(x-1)
end
--s.rCalcCursor = rCalcCursor
local oTxt = ""
local function rAll(nTxt)
if nTxt then
if not genLines(nTxt) then
genLines(s.txt)
s.cursor.a = oCursorA
else
s.txt = nTxt
end
else
genLines(s.txt)
end
genText()
calcCursor()
genLines(s.txt)
genText()
oTxt = s.txt
if s.opt.complete then
s.opt.complete.list = nil
end
end
local uTimer
local function addUndo(event)
if #s.history == 0 then
table.insert(s.history,{txt=s.txt,changed=true,cursor=s.cursor.a})
end
if event == "paste" then
table.insert(s.history,{txt=s.txt,changed=true,cursor=s.cursor.a,description="Paste"})
elseif event == "key" or event == "char" then
--if s.history[#s.history] and s.history[#s.history].time and s.history[#s.history].time > os.epoch("utc")-250
if uTimer then os.cancelTimer(uTimer) end
uTimer = os.startTimer(0.3)
elseif event == "timer" then
table.insert(s.history,{txt=s.txt,changed=s.changed,cursor=s.cursor.a,description="Insert Characters"})
end
while #s.history > 80 do
table.remove(s.history,1)
end
end
local function undo()
if s.history[#s.history-1] then
local h = s.history[#s.history-1]
s.changed = h.changed
s.cursor.a = h.cursor
rAll(h.txt)
table.insert(s.rhistory,s.history[#s.history])
table.remove(s.history,#s.history)
end
end
local function redo()
if s.rhistory[#s.rhistory] then
local h = s.history
local r = s.rhistory[#s.rhistory]
s.changed = r.changed
s.cursor.a = r.cursor
rAll(r.txt)
table.insert(s.history,r)
table.remove(s.rhistory,#s.rhistory)
end
end
local function addText(txt)
local a = 0
if s.border and s.border.color ~= 0 then
a = 1
end
local ttxt = txt
if txt:find("\n") then
if s.opt.overflowY == "none" and s.cursor.y >= s.y2-s.y1+1-a*2 then
return false
else
ttxt = txt:match("(.-)\n")
end
end
if s.opt.overflowX == "none" and lineLn(s.lines[s.cursor.y]..ttxt) > s.x2-s.x1+1-a*2 then
return false
end
local pos1,pos2
if s.select then
pos1 = s.select[1]-1
pos2 = s.select[2]+1
else
pos1 = s.cursor.a-1
pos2 = s.cursor.a
end
s.changed = true
s.txt = s.txt:sub(1,pos1)..txt..s.txt:sub(pos2,#s.txt)
s.cursor.a = pos1+#txt+1
s.select = nil
rAll()
end
local function update(...) -- maybe make it so that click already selects and then the key char etc operations always sub based on select so no if statement needed
opt = s.opt
if not opt.tabSize then
opt.tabSize = 4
end
oCursorA = s.cursor.a
local e = table.pack(...)
if not lUtils then
if e[1] == "key" then
holdTbl[e[2]] = true
elseif e[1] == "key_up" then
holdTbl[e[2]] = nil
end
end
if s.opt.complete and s.opt.complete.LevelOS and s.opt.complete.LevelOS.self.window.events == "all" and e[1]:find("mouse") and type(e[3]) == "number" and type(e[4]) == "number" then
local wX,wY = s.opt.complete.LevelOS.self.window.win.getPosition()
e[3] = e[3]-(wX-1)
e[4] = e[4]-(wY-1)
end
if e[1] == "mouse_click" and s.opt.complete and s.opt.complete.list and #s.opt.complete.list > 0 and not isInside(e[3],e[4],s.opt.complete.list) then
s.opt.complete.list = nil
rAll()
end
if e[1] == "timer" and e[2] == uTimer then
addUndo(e[1])
end
if not s.state then
if e[1] == "mouse_click" and e[3] >= s.x1 and e[4] >= s.y1 and e[3] <= s.x2 and e[4] <= s.y2 then
s.state = true
elseif e[1] == "term_resize" then
rAll()
elseif s.txt ~= oTxt then
rAll()
end
else
if e[1] == "mouse_click" then
if (e[3] < s.x1 or e[3] > s.x2 or e[4] < s.y1 or e[4] > s.y2) and not (s.opt.complete and s.opt.complete.list and #s.opt.complete.list > 0 and isInside(e[3],e[4],s.opt.complete.list)) then -- add support for autocomplete click
term.setCursorBlink(false)
s.state = false
end
end
if s.txt ~= oTxt then
rAll()
end
if e[1] == "char" then
if #s.history == 0 then
table.insert(s.history,{txt=s.txt,changed=false,cursor=s.cursor.a})
end
--[[s.changed = true
s.cursor.a = s.cursor.a+1
rAll(s.txt:sub(1,s.cursor.a-2)..e[2]..s.txt:sub(s.cursor.a-1,#s.txt))]]
addText(e[2])
addUndo(e[1])
if s.opt.complete then
s.opt.complete.complete = complete
s.opt.complete.render = renderComplete
s.opt.complete.list = complete(string.match(s.txt:sub(1,s.cursor.a-1), "[a-zA-Z0-9_%.:]+$"))
end
elseif e[1] == "key" then
local dirs = {
[keys.left] = true,
[keys.right] = true,
[keys.up] = true,
[keys.down] = true,
[keys["end"]] = true,
[keys.home] = true,
}
local deletes = {
[keys.delete] = true,
[keys.backspace] = true,
}
if isHolding(keys.leftCtrl) and dirs[e[2]] then -- e fuck implement ur own isholdign
-- nothing
elseif s.select and dirs[e[2]] then
s.select = nil
rAll()
elseif s.select and deletes[e[2]] then
addText("")
elseif e[2] == keys.left and s.cursor.a > 1 then
s.cursor.a = s.cursor.a-1
--calcCursor()
rAll()
elseif e[2] == keys.right and s.cursor.a <= #s.txt then
s.cursor.a = s.cursor.a+1
--calcCursor()
rAll()
elseif e[2] == keys.up then
local c = s.opt.complete
if c and c.list and #c.list > 0 and c.selected then
local offset = 1
if c.reverse then
offset = -1
end
local sID = c.selected.id-offset
if sID < 1 then
sID = #c.list
elseif sID > #c.list then
sID = 0
end
c.selected = c.list[sID]
elseif s.cursor.y > 1 then
s.cursor.y = s.cursor.y-1
if opt.overflowX == "scroll" then
li = s.lines
else
li = s.dLines
end
local ln = lineLn(li[s.cursor.y])
if s.cursor.x > ln then
s.cursor.x = ln
if s.cursor.y == #s.lines then
s.cursor.x = s.cursor.x+1
end
end
rCalcCursor()
rAll()
end
elseif e[2] == keys.down then
local c = s.opt.complete
if c and c.list and #c.list > 0 and c.selected then
local offset = 1
if c.reverse then
offset = -1
end
local sID = c.selected.id+offset
if sID < 1 then
sID = #c.list
elseif sID > #c.list then
sID = 0
end
c.selected = c.list[sID]
elseif s.cursor.y < #s.lines then
s.cursor.y = s.cursor.y+1
if opt.overflowX == "scroll" then
li = s.lines
else
li = s.dLines
end
local ln = lineLn(li[s.cursor.y])
if s.cursor.x > ln then
s.cursor.x = ln
if s.cursor.y == #s.lines then
s.cursor.x = s.cursor.x+1
end
end
rCalcCursor()
rAll()
end
elseif e[2] == keys.pageUp then
local h = s.y2-(s.y1-1)
s.cursor.y = math.max(s.cursor.y-h,1)
if opt.overflowX == "scroll" then
li = s.lines
else
li = s.dLines
end
local ln = lineLn(li[s.cursor.y])
if s.cursor.x > ln then
s.cursor.x = ln
if s.cursor.y == #s.lines then
s.cursor.x = s.cursor.x+1
end
end
rCalcCursor()
rAll()
elseif e[2] == keys.pageDown then
local h = s.y2-(s.y1-1)
s.cursor.y = math.min(s.cursor.y+h,#s.lines)
if opt.overflowX == "scroll" then
li = s.lines
else
li = s.dLines
end
local ln = lineLn(li[s.cursor.y])
if s.cursor.x > ln then
s.cursor.x = ln
if s.cursor.y == #s.lines then
s.cursor.x = s.cursor.x+1
end
end
rCalcCursor()
rAll()
elseif e[2] == keys["end"] then
if isHolding(keys.leftCtrl) then
s.cursor.a = #s.txt+1
else
if opt.overflowX == "scroll" then
local ln = lineLn(s.lines[s.cursor.y])
s.cursor.x = ln
else
s.cursor.x = #s.dLines[s.cursor.y]
end
if s.cursor.y == #s.lines then
s.cursor.x = s.cursor.x+1
end
rCalcCursor()
end
rAll()
elseif e[2] == keys.home then
if isHolding(keys.leftCtrl) then
s.cursor.a = 1
else
local wp = (s.lines[s.cursor.y]:match("^[\t ]+") or ""):gsub("\t",string.rep(" ",s.opt.tabSize)) -- always beginning of string so always 4 per tab
if s.cursor.x == 1+#wp then
s.cursor.x = 1
else
s.cursor.x = 1+#wp
end
rCalcCursor()
end
rAll()
elseif e[2] == keys.backspace and s.cursor.a > 1 then
s.changed = true
s.txt = s.txt:sub(1,s.cursor.a-2)..s.txt:sub(s.cursor.a,#s.txt)
s.cursor.a = s.cursor.a-1
rAll()
addUndo(e[1])
elseif e[2] == keys.delete and s.cursor.a <= #s.txt then
s.changed = true
s.txt = s.txt:sub(1,s.cursor.a-1)..s.txt:sub(s.cursor.a+1,#s.txt)
rAll()
addUndo(e[1])
elseif e[2] == keys.enter then
local wp = s.lines[s.cursor.y]:match("^[\t ]+") or ""
addText("\n"..wp)
addUndo(e[1])
elseif (isHolding(keys.leftCtrl) and (e[2] == keys.leftBracket or e[2] == keys.rightBracket)) or (s.select and e[2] == keys.tab) then
local sLine = s.cursor.y
local eLine = s.cursor.y
if s.select then
for l=1,#s.lines do
local encountered = false
if (s.select[1] >= s.ref[l] and s.select[1] < s.ref[l+1]) then -- if begin of select is in line
sLine = math.min(sLine,l)
end
if (s.select[2] >= s.ref[l] and s.select[2] < s.ref[l+1]) then -- if end of select is in line
eLine = math.max(eLine,l)
encountered = true
elseif encountered then -- if it already encountered the select and is no longer in selected area it doesnt need to search any further
break
end
end
end
for l=sLine,eLine do
if e[2] == keys.rightBracket or e[2] == keys.tab then
s.lines[l] = "\t"..s.lines[l]
if s.select then
if s.select[1] >= s.ref[l] then
s.select[1] = s.select[1]+1
end
if s.select[2] >= s.ref[l] then
s.select[2] = s.select[2]+1
end
end
if s.cursor.a >= s.ref[l] then
s.cursor.a = s.cursor.a+1
end
calcCursor()
elseif e[2] == keys.leftBracket then
if s.lines[l]:find("^\t") then
s.lines[l] = s.lines[l]:gsub("^\t","")
if s.select then
if s.select[1] > s.ref[l] then
s.select[1] = s.select[1]-1
end
if s.select[2] > s.ref[l] then
s.select[2] = s.select[2]-1
end
end
if s.cursor.a > s.ref[l] then
s.cursor.a = s.cursor.a-1
end
elseif s.lines[l]:find("^ ") then
s.lines[l] = s.lines[l]:gsub("^ ","")
if s.select then
if s.select[1] > s.ref[l] then
s.select[1] = math.max(s.select[1]-4,s.ref[l])
end
if s.select[2] > s.ref[l] then
s.select[2] = math.max(s.select[2]-4,s.ref[l])
end
end
if s.cursor.a > s.ref[l] then
s.cursor.a = math.max(s.cursor.a-4,s.ref[l])
end
end
end
s.txt = genText()
rAll()
end
elseif e[2] == keys.tab then
local c = s.opt.complete
if c and c.list and #c.list > 0 and c.selected then
addText(c.selected.complete)
addUndo("char")
else
addText("\t")
addUndo(e[1])
end
elseif isHolding(keys.leftCtrl) then
if e[2] == keys.z then
undo()
elseif e[2] == keys.y then
redo()
elseif e[2] == keys.a then
s.select = {1,#s.txt}
s.cursor.a = #s.txt+1
rAll()
elseif (e[2] == keys.c or e[2] == keys.x) and ccemux and s.select then
local txt = s.txt:sub(s.select[1],s.select[2])
ccemux.setClipboard(txt)
lOS.clipboard = txt
if e[2] == keys.x then
addText("")
addUndo(e[1])
end
end
end
elseif e[1] == "paste" then
if s.opt.complete then
s.opt.complete.list = nil
end
if #s.history == 0 then
table.insert(s.history,{txt=s.txt,changed=false,cursor=s.cursor.a})
end
local txt = e[2]
if lOS.clipboard then
local nline = lOS.clipboard:find("(.)\n") or #lOS.clipboard
local checkCB = lOS.clipboard:sub(1,nline):gsub("\t","")
if txt == checkCB then
txt = lOS.clipboard
end
end
addText(txt)
addUndo(e[1])
elseif s.opt.complete and s.opt.complete.list and #s.opt.complete.list > 0 and s.opt.complete.selected and (e[1] == "mouse_click" or e[1] == "mouse_scroll" or e[1] == "mouse_up") and isInside(e[3],e[4],s.opt.complete.list) then
local c = s.opt.complete
--local x,y = e[3]-(s.x1-1),e[4]-(s.y1-1)
local el = e[4]-(s.opt.complete.list.y1-1)
if e[1] == "mouse_click" then
c.selected = c.list[el]
elseif e[1] == "mouse_up" and c.selected.id == el then
s.changed = true
s.txt = s.txt:sub(1,s.cursor.a-1)..c.selected.complete..s.txt:sub(s.cursor.a,#s.txt)
s.cursor.a = s.cursor.a+#c.selected.complete
rAll()
addUndo("char")
end
elseif e[1] == "mouse_click" or e[1] == "mouse_scroll" then
s.select = nil
if e[3] >= s.x1 and e[4] >= s.y1 and e[3] <= s.x2 and e[4] <= s.y2 then
if e[1] == "mouse_click" then
local scrollX = s.scrollX or 0
local scrollY = s.scrollY or 0
local x,y = e[3]-(s.x1-1)+scrollX,e[4]-(s.y1-1)-s.scr+scrollY
if not s.lines[y] then
y = #s.lines
end
if x > #s.dLines[y] then
if opt.overflowX == "scroll" then
x = lineLn(s.lines[y])
else
x = #s.dLines[y]
end
if y == #s.lines then
x = x+1
end
end
s.cursor.x,s.cursor.y = x,y
rCalcCursor()
rAll()
elseif opt.overflowY == "scroll" and e[1] == "mouse_scroll" then
local width = s.x2-(s.x1-1)
local height = s.y2-(s.y1-1)
if s.border and s.border.color ~= 0 then
width = width-2
height = height-2
end
if s.scrollY+e[2] >= 0 and s.scrollY+e[2] <= #s.lines-height then
s.scrollY = s.scrollY+e[2]
end
end
end
elseif e[1] == "mouse_drag" then
local pos1
if s.select then
pos1 = s.select[1]
else
pos1 = s.cursor.a
s.select = {pos1,pos1}
end
local scrollX = s.scrollX or 0
local scrollY = s.scrollY or 0
local x,y = e[3]-(s.x1-1)+scrollX,e[4]-(s.y1-1)-s.scr+scrollY
if not s.lines[y] then
y = #s.lines
end
if x > #s.dLines[y] then
if opt.overflowX == "scroll" then
x = lineLn(s.lines[y])
else
x = #s.dLines[y]
end
if y == #s.lines then
x = x+1
end
end
s.cursor.x,s.cursor.y = x,y
rCalcCursor()
local pos2 = s.cursor.a
if s.select then
if pos2 < s.select[1] and not s.select.reversed then
s.select.reversed = true
s.select[2] = s.select[1]-1
elseif pos2 > s.select[2] and s.select.reversed then
s.select.reversed = false
s.select[1] = s.select[2]+1
end
if s.select.reversed then
s.select[1] = pos2
else
s.select[2] = pos2-1
end
end
rAll()
elseif e[1] == "term_resize" then
rAll()
end
end
local lChar = s.txt:sub(s.cursor.a-1,s.cursor.a-1)
if s.opt.complete and (e[1] == "key" or e[1] == "char") and (lChar == "(" or lChar == ",") then
s.opt.complete.list = complete(string.match(s.txt:sub(1,s.cursor.a-1), "[a-zA-Z0-9_%.:%(]+[^%)%(]*$"))
end
end
local function render()
if s.color == 0 then s.color = colors.white end
local restore = cRestore()
term.setBackgroundColor(s.color or term.getBackgroundColor())
local a = 0
if s.border and s.border.color ~= 0 then
a = 1
end
lOS.boxClear(s.x1+a,s.y1+a,s.x2-a,s.y2-a)
local scrollX = s.scrollX or 0
local scrollY = s.scrollY or 0
for y=s.y1+a,s.y2-a do
local l = y-(s.y1-1+a)+s.scr + scrollY
if s.lines[l] then
term.setCursorPos(s.x1+a,y)
term.setBackgroundColor(s.color or term.getBackgroundColor())
term.setTextColor(s.txtcolor or txtcolor)
local line
if s.blit[l] then
line = s.blit[l][1]
else
line = s.dLines[l]
end
if type(opt.replaceChar) == "string" and #opt.replaceChar > 0 then
local pattern = "."
for t=1,#opt.replaceChar-1 do
pattern = pattern..".?"
end
local nLine = line:gsub(pattern,opt.replaceChar)
line = nLine:sub(1,#line)
end
if s.blit[l] then
--s.blit[l][3] = string.rep(lUtils.toBlit(s.color or term.getBackgroundColor()),#s.blit[l][1])
term.blit(line,s.blit[l][2],s.blit[l][3])
else
term.write(line)
end
end
end
if s.opt.complete and s.opt.complete.list and #s.opt.complete.list > 0 then
if s.opt.complete.overlay and s.opt.complete.LevelOS then
s.opt.complete.LevelOS.self.window.events = "all"
lOS.noEvents = 2
end
lOS.noEvents = 2
local ok,err = pcall(renderComplete,s.opt.complete.list)
if not ok then
_G.theterribleerrorrighthere = err
end
elseif s.opt.complete and s.opt.complete.LevelOS and s.opt.complete.LevelOS.self.window.events == "all" then
if s.opt.complete.overlay then
s.opt.complete.LevelOS.self.window.events = nil
lOS.noEvents = false
end
s.opt.complete.LevelOS.overlay = nil
end
if s.state then
local x,y = s.x1+(s.cursor.x-1)+a-scrollX,s.y1+(s.cursor.y-1)+a-scrollY
if isInside(x,y,s) then
term.setTextColor(opt.cursorColor or txtcolor)
term.setCursorPos(x,y)
term.setCursorBlink(true)
else
term.setCursorBlink(false)
end
else
cRestore(restore)
end
end
if not tShape then
s.update = update
s.render = render
else
s.fUpdate = update
s.fRender = render
end
return s
end
function input.read(_sReplaceChar)
local x1,y1 = term.getCursorPos()
local x2,_ = term.getSize()
local box = input.box(x1,y1,x2,y1,{overflowX="scroll",overflowY="none",replaceChar=_sReplaceChar})
box.state = true
while true do
local e = {os.pullEvent()}
if e[1] == "key" and e[2] == keys.enter then
box.state = false
print("")
return box.txt
else
box.update(unpack(e))
box.state = true
box.render()
end
end
end
return input