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