-- LevelOS Utils

if not unpack then
	_G.unpack = table.unpack
end

local write = term.write

if _G.lUtils == nil then
	_G.lUtils = {}
end


if _G.lOS == nil then
	_G.lOS = {}
end

function lUtils.RGBToGrayscale( r, g, b )
	local gr = (r + g + b) / 3
	return gr,gr,gr
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 lUtils.wordwrap(txt,width)
	if not txt then
		error("No text given",2)
	end
	local lines = {}
	for line in txt:gmatch("([^\n]*)\n?") do
		table.insert(lines,"")
		for word in line:gmatch("%S*%s?") do
			if #lines[#lines]+#word > width and #(lines[#lines]:gsub(" ","")) > 0 then
				lines[#lines+1] = ""
			end
			if #lines[#lines]+#word > width then
				local tWord = word
				while #lines[#lines]+#tWord > width do
					lines[#lines] = tWord:sub(1,width)
					table.insert(lines,"")
					tWord = tWord:sub(width+1)
				end
				lines[#lines] = tWord
			else
				lines[#lines] = lines[#lines]..word
			end
		end
	end
	if lines[#lines] == "" then
		lines[#lines] = nil
	end
	if txt:sub(#txt) == "\n" then
		table.insert(lines,"")
	end
	return lines
end

function lUtils.input(x1,y1,x2,y2,tOptions,sReplaceChar,tShape)
	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 rtype = rtype
		if not rtype then
			rtype = type
		end
		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" or type(v) == "nil" 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 = lUtils.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 = lUtils.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.purple},boolean={"bln",colors.purple},["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)
			x2 = x1+width-1
			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] = ""
			blit[3] = ""
			_G.debugsublines = sublines
			for l=1,#sublines do
				local elements = sublines[l]
				for t=1,#elements do
					local col
					local col2 = lUtils.toBlit(s.color)
					if type(syntax[elements[t].type]) == "table" then
						col, col2 = lUtils.toBlit(syntax[elements[t].type][1] or s.txtcolor), lUtils.toBlit(syntax[elements[t].type][2] or s.color)
					else
						col = lUtils.toBlit(syntax[elements[t].type] or s.txtcolor)
					end
					blit[1] = blit[1]..elements[t].data
					blit[2] = blit[2]..string.rep(col,#elements[t].data)
					blit[3] = blit[3]..string.rep(col2,#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"
					blit[3] = blit[3].."\n"
				end
			end
			_G.debugBlit2 = {blit[1],blit[2],blit[3]}
		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]
					local blit3 = blit[3]
					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))
						local col, col2
						if type(syntax.whitespace) == "table" then
							col = lUtils.toBlit(syntax.whitespace[1] or s.txtcolor)
							col2 = lUtils.toBlit(syntax.whitespace[2] or s.color)
						else
							col = lUtils.toBlit(syntax.whitespace or s.txtcolor)
							col2 = lUtils.toBlit(s.color)
						end
						blit2 = replaceText(blit2,findTab,findTab,string.rep(col,l))
						blit3 = replaceText(blit3,findTab,findTab,string.rep(col2,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)),blit3:sub(b+s.scrollX,b+s.scrollX+(#dl[c]-1))}
					blit[1] = blit[1]:sub(e+1,#blit[1])
					blit[2] = blit[2]:sub(e+1,#blit[2])
					blit[3] = blit[3]:sub(e+1,#blit[3])
				else
					dl[c] = line:sub(1+s.scrollX,width+s.scrollX)
					s.blit[c] = {dl[c],string.rep(lUtils.toBlit(s.txtcolor),#dl[c]),string.rep(lUtils.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 = lUtils.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 = lUtils.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 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 lUtils.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 lUtils.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 lUtils.isHolding(keys.leftCtrl) and dirs[e[2]] then
					-- 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 lUtils.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 lUtils.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 (lUtils.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 lUtils.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 s.select then
						local txt = s.txt:sub(s.select[1],s.select[2])
						if ccemux then
							ccemux.setClipboard(txt)
							lOS.clipboard = txt
						else
							s.clipboard = txt
						end
						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 s.clipboard then
					txt = s.clipboard
				elseif 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 lUtils.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]+scrollX 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]+scrollX 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 lUtils.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 lUtils.read(_sReplaceChar)
	local x1,y1 = term.getCursorPos()
	local x2,_ = term.getSize()
	local box = lUtils.input(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

local animationTbl = {}
lUtils.debugAnimationTbl = animationTbl
function lUtils.renderImg(spr,x,y,format,transparency)
	local format
	local img = spr
	local cterm = term.current()
	if not format then
		if type(spr) == "string" then
			format = "nfp"
		elseif type(spr) == "table" then
			format = "bImg"
			if type(spr[1][1]) == "table" then
				if #spr == 1 or not spr.animated then
					spr = spr[1]
				else
					if not animationTbl[spr] then
						animationTbl[spr] = {cFrame=0,oTime=os.epoch("utc")/1000}
					else
						local cFrame = animationTbl[spr].cFrame+0.0001
						local dur = spr[math.ceil(cFrame)].duration or spr.secondsPerFrame or 0.05
						local oTime = animationTbl[spr].oTime
						local cTime = os.epoch("utc")/1000
						local delta = (cTime-oTime)
						animationTbl[spr].cFrame = (cFrame + delta/dur) % #spr
						animationTbl[spr].oTime = cTime
					end
					spr = spr[math.ceil(animationTbl[spr].cFrame+0.0001)]
				end
			end
		end
	end
	if format == "bImg" or format == "lImg" then
		if not spr[1] or not spr[1][1] or type(spr[1][1]) ~= "string" then
			error("Unrecognized format",2)
		end
		local sW,sH = #spr[1][1],#spr
		local w,h = term.getSize()
		for l=1,#spr do
			--[[if not y then
				term.setCursorPos(math.ceil(w/2)-math.floor(sW/2),(math.ceil(h/2)-math.floor(sH/2)+(l-1)))
			else
				term.setCursorPos(x,y+(l-1))
			end]]
			local line
			if y then
				line = y+(l-1)
			end
			local cX,cY = x or math.ceil(w/2)-math.floor(sW/2), line or (math.ceil(h/2)-math.floor(sH/2)+(l-1))
			term.setCursorPos(cX,cY)
			local bl = {}
			bl[1] = spr[l][1]
			if transparency then
				bl[2] = spr[l][2]
				bl[3] = spr[l][3]
				-- thing
				local line
				if cY >= 1 and cY <= h then
					line = {cterm.getLine(cY)}
					for t=1,3 do
						line[t] = line[t]:sub(cX,cX+(#spr[l][1]-1))
					end
				else
					line = {string.rep("f",#spr[l][1])}
					line[2] = line[1]
					line[3] = line[2]
				end
				if #line[2] < #spr[l][1] then
					line[1] = line[1]..string.rep(" ",#spr[l][1]-#line[1])
					line[2] = line[2]..string.rep("f",#spr[l][1]-#line[2])
					line[3] = line[3]..string.rep("f",#spr[l][1]-#line[3])
				end
				local start,final = 0,0
				while true do
					start,final = bl[2]:gsub(" ","T"):find("T")
					if start then
						bl[2] = bl[2]:sub(1,start-1)..line[3]:sub(start,final)..bl[2]:sub(final+1,#bl[2])
					else
						break
					end
				end
				while true do
					start,final = bl[3]:gsub(" ","T"):find("T")
					if start then
						if bl[1]:sub(start,final) == " " or bl[1]:sub(start,final) == "\128" then
							bl[1] = bl[1]:sub(1,start-1)..line[1]:sub(start,final)..bl[1]:sub(final+1,#bl[1])
							bl[2] = bl[2]:sub(1,start-1)..line[2]:sub(start,final)..bl[2]:sub(final+1,#bl[2])
						end
						bl[3] = bl[3]:sub(1,start-1)..line[3]:sub(start,final)..bl[3]:sub(final+1,#bl[3])
					else
						break
					end
				end
			else
				bl[2] = string.gsub(spr[l][2]:gsub(" ","T"),"T",lUtils.toBlit(term.getBackgroundColor()))
				bl[3] = string.gsub(spr[l][3]:gsub(" ","T"),"T",lUtils.toBlit(term.getBackgroundColor()))
			end
			if #bl[1] ~= #bl[2] or #bl[2] ~= #bl[3] then
				_G.debugblitthingy = bl
				if #bl[2] > #bl[1] then
					bl[2] = bl[2]:sub(1, #bl[1])
				elseif #bl[2] < #bl[1] then
					bl[2] = bl[2]..string.rep(lUtils.toBlit(term.getBackgroundColor()), #bl[1]-#bl[2])
				end
				if #bl[3] > #bl[2] then
					bl[3] = bl[3]:sub(1, #bl[2])
				elseif #bl[3] < #bl[2] then
					bl[3] = bl[3]..string.rep(lUtils.toBlit(term.getBackgroundColor()), #bl[2]-#bl[3])
				end
			end
			term.blit(unpack(bl))
		end
	elseif format == "nfp" or format == "nfg" then
		local b,e = string.find(spr,"\n")
		local sW,sH
		local w,h = term.getSize()
		local lines,sW = getLines(spr)
		sH = #lines
		for l=1,sH do
			local line
			if y then
				line = y+(l-1)
			end
			term.setCursorPos(x or math.ceil(w/2)-math.floor(sW/2), line or math.ceil(h/2)-math.floor(sH/2)+(l-1))
			for lX=1,sW do
				local ch = lines[l]:sub(lX,lX)
				if ch ~= " " and ch ~= "" then
					term.blit(" ","f",ch)
				else
					local cx,cy = term.getCursorPos()
					term.setCursorPos(cx+1,cy)
				end
			end
		end
	end
end


function lUtils.getDrawingCharacter(...)
	local e=0
	local arg = {...}
	for t=1,5 do
		if arg[t] then
			e=e+2^(t-1)
		end
	end
	return {char = string.char(arg[6] and 159-e or 128+e), inverted = not not arg[6]}
end

lUtils.asset = {}
function lUtils.asset.load(filename)
	if filename and type(filename) == "string" and fs.exists(filename) and not fs.isDir(filename) then
		local ft = lUtils.getFileType(filename)
		if ft == ".limg" or ft == ".bimg" or ft == ".lconf" then
			local contents = lUtils.fread(filename, true)
			local decodedText = ""
			for _, codepoint in utf8.codes(contents) do
				decodedText = decodedText .. string.char(codepoint)
			end
			local data = textutils.unserialize(decodedText)
			local oterm = term.current()
			local trashwin = window.create(term.current(), 1, 1, 51, 19, false)
			term.redirect(trashwin)
			local ok, _ = pcall(lUtils.renderImg, data)
			term.redirect(oterm)
			if ((ft == ".limg" or ft == ".bimg") and not ok) then
				return textutils.unserialize(contents)
			else
				return data
			end
		end
		return textutils.unserialize(lUtils.fread(filename))
	else
		return false
	end
end


function lUtils.asset.save(asset,filename,compact)
	if compact == nil then compact = true end
	if type(asset) == "table" and not fs.isDir(filename) then
		local ok,output = pcall(textutils.serialize,asset,{compact=compact})
		if not ok then
			error(output,2)
		end
		return lUtils.fwrite(filename,output)
	else
		return false
	end
end


function lUtils.HSVToRGB( hue, saturation, value )
	-- Returns the RGB equivalent of the given HSV-defined color
	-- (adapted from some code found around the web)

	-- If it's achromatic, just return the value
	hue = hue%360
	if saturation == 0 then
		return value,value,value
	end

	-- Get the hue sector
	local hue_sector = math.floor( hue / 60 )
	local hue_sector_offset = ( hue / 60 ) - hue_sector

	local p = value * ( 1 - saturation )
	local q = value * ( 1 - saturation * hue_sector_offset )
	local t = value * ( 1 - saturation * ( 1 - hue_sector_offset ) )

	if hue_sector == 0 then
		return value, t, p
	elseif hue_sector == 1 then
		return q, value, p
	elseif hue_sector == 2 then
		return p, value, t
	elseif hue_sector == 3 then
		return p, q, value
	elseif hue_sector == 4 then
		return t, p, value
	elseif hue_sector == 5 then
		return value, p, q
	end
end


function lUtils.RGBToHSV( red, green, blue )

	local hue, saturation, value

	local min_value = math.min( red, green, blue )
	local max_value = math.max( red, green, blue )

	value = max_value

	local value_delta = max_value - min_value

	-- If the color is not black
	if max_value ~= 0 then
		saturation = value_delta / max_value

	-- If the color is purely black
	else
		saturation = 0
		hue = -1
		return hue, saturation, value
	end

	if red == max_value then
		hue = ( green - blue ) / value_delta
	elseif green == max_value then
		hue = 2 + ( blue - red ) / value_delta
	else
		hue = 4 + ( red - green ) / value_delta
	end

	hue = hue * 60
	if hue < 0 then
		hue = hue + 360
	end

	return hue, saturation, value
end


lUtils.graphics = {}
local g = lUtils.graphics
function g.button(txt,x1,y1,x2,y2,func,border,fg1,bg1,fg2,bg2,bcolor)
	if not y2 then
		return nil
	end
	if not bg1 then
		bg1 = colors.black
	end
	if not fg1 then
		fg1 = colors.white
	end
	if not bcolor then
		bcolor = colors.gray
	end
	if not fg2 then
		fg2 = fg1
	end
	if not bg2 then
		bg2 = colors.gray
	end
	if not border then
		border = true
	end
	local btn = {txt=txt,x1=x1,y1=y1,x2=x2,y2=y2,func=func,border=border,selected=false,colors={bg1=bg1,fg1=fg1,bg2=bg2,fg2=fg2,border=bcolor}}
	function btn.render(...)
		local e = {...}
		local selected = false
		--term.setTextColor(colors.fg1)
		--term.setBackgroundColor(colors.bg1)
		local abc = {}
		if e[1] == "mouse_click" or e[1] == "mouse_up" then
			if e[3] >= btn.x1 and e[4] >= btn.y1 and e[3] <= btn.x2 and e[4] <= btn.y2 then
				if e[1] == "mouse_click" then
					btn.selected = true
				elseif e[1] == "mouse_up" then
					if not btn.func then
						abc[1] = true
					else
						abc = {btn.func()}
					end
				end
			end
		end
		if e[1] == "mouse_up" then
			btn.selected = false
		end
		if btn.selected then
			term.setBackgroundColor(btn.colors.bg2)
		else
			term.setBackgroundColor(btn.colors.bg1)
		end
		term.setTextColor(btn.colors.border)
		if btn.border == true then
			lUtils.border(btn.x1,btn.y1,btn.x2,btn.y2)
		else
			lOS.boxClear(btn.x1,btn.y1,btn.x2,btn.y2)
		end
		if btn.selected then
			term.setTextColor(btn.colors.fg2)
		else
			term.setTextColor(btn.colors.fg1)
		end
		lUtils.textbox(btn.txt,btn.x1+1,btn.y1+1,btn.x2-1,btn.y2-1)
		return unpack(abc)
	end
	return btn
end


function lOS.boxClear(tx1,ty1,tx2,ty2)
	local x1,y1,x2,y2 = tx1,ty1,tx2,ty2
	if x1 > x2 then
		x2,x1 = x1,x2
	end
	if y1 > y2 then
		y2,y1 = y1,y2
	end
	clearline = ""
	for t=x1,x2 do
		clearline = clearline.." "
	end
	for l=y1,y2 do
		term.setCursorPos(x1,l)
		term.write(clearline)
	end
end


local function run2( _tEnv, _sPath, ... )
	local tArgs = table.pack( ... )
	local tEnv = _tEnv
	setmetatable( tEnv, { __index = _G } )
	local fnFile,err
	if fs.exists("window") then
		fnFile, err = loadfile( _sPath, tEnv )
	else
		fnFile, err = loadfile( _sPath, nil, tEnv )
	end
	if fnFile then
		local returned = {pcall( function()
			return table.unpack({fnFile( table.unpack( tArgs, 1, tArgs.n ) )})
		end )}
		local ok, err = returned[1],returned[2]
		table.remove(returned,1)
		if not ok then
			table.remove(returned,1)
			if err and err ~= "" then
				--printError( err )
			end
			return false,err,table.unpack(returned)
		end
		return true,table.unpack(returned)
	end
	if err and err ~= "" then
		--printError( err )
	end
	return false,err
end



local function createShellEnv( sDir , tWindow , sPath)
	local tEnv = {}
	tEnv[ "shell" ] = lUtils.instantiate(shell)
	if sPath then
		tEnv.shell.getRunningProgram = function() return sPath end
	end
	tEnv[ "multishell" ] = multishell

	local package = {}
	package.loaded = {
		_G = _G,
		bit32 = bit32,
		coroutine = coroutine,
		math = math,
		package = package,
		string = string,
		table = table,
	}
	package.path = "?;?.lua;?/init.lua;/rom/modules/main/?;/rom/modules/main/?.lua;/rom/modules/main/?/init.lua;/LevelOS/modules/?.lua;/LevelOS/modules/?/init.lua"
	if turtle then
		package.path = package.path..";/rom/modules/turtle/?;/rom/modules/turtle/?.lua;/rom/modules/turtle/?/init.lua"
	elseif command then
		package.path = package.path..";/rom/modules/command/?;/rom/modules/command/?.lua;/rom/modules/command/?/init.lua"
	end
	package.config = "/\n;\n?\n!\n-"
	package.preload = {}
	package.loaders = {
		function( name )
			if package.preload[name] then
				return package.preload[name]
			else
				return nil, "no field package.preload['" .. name .. "']"
			end
		end,
		function( name )
			local fname = string.gsub(name, "%.", "/")
			local sError = ""
			for pattern in string.gmatch(package.path, "[^;]+") do
				local sPath = string.gsub(pattern, "%?", fname)
				if sPath:sub(1,1) ~= "/" then
					sPath = fs.combine(sDir, sPath)
				end
				if fs.exists(sPath) and not fs.isDir(sPath) then
					local fnFile, sError = loadfile( sPath, tEnv )
					if fnFile then
						return fnFile, sPath
					else
						return nil, sError
					end
				else
					if #sError > 0 then
						sError = sError .. "\n"
					end
					sError = sError .. "no file '" .. sPath .. "'"
				end
			end
			return nil, sError
		end
	}

	local sentinel = {}
	local function require( name )
		if type( name ) ~= "string" then
			error( "bad argument #1 (expected string, got " .. type( name ) .. ")", 2 )
		end
		if package.loaded[name] == sentinel then
			error("Loop detected requiring '" .. name .. "'", 0)
		end
		if package.loaded[name] then
			return package.loaded[name]
		end

		local sError = "Error loading module '" .. name .. "':"
		for n,searcher in ipairs(package.loaders) do
			local loader, err = searcher(name)
			if loader then
				package.loaded[name] = sentinel
				local result = loader( err )
				if result ~= nil then
					package.loaded[name] = result
					return result
				else
					package.loaded[name] = true
					return true
				end
			else
				sError = sError .. "\n" .. err
			end
		end
		error(sError, 2)
	end

	tEnv["package"] = package
	tEnv["require"] = require

	tEnv["LevelOS"] = {self={window=tWindow}}

	local lAPI = tEnv["LevelOS"]
	--local s = tEnv["shell"]
	--local ms = tEnv["multishell"]
	--TEMP DISABLED
	local s = {}
	local ms = {}

	
	
	if not lOS.oldterm then
		lOS.oldterm = oldterm
		if not oldterm then
			lOS.oldterm = term.native() -- DANGEROUS
		end
	end
	local w,h = lOS.oldterm.getSize()
	if tWindow ~= nil and lOS.wins ~= nil then
		local win = tWindow
		local function setWin( x, y, width, height, mode )
			local x,y,width,height,mode = x, y, width, height, mode
			if type(x) == "string" then
				mode = x
				x,y = win.win.getPosition()
				width,height = win.win.getSize()
			elseif type(width) ~= "number" then
				width,height,mode = x,y,width
				x,y = win.win.getPosition()
			end
			win.win.reposition(x,y,width,height)
			os.queueEvent("term_resize")
			if mode then
				if mode ~= "background" and win.winMode == "background" then
					local pID
					for p=1,#lOS.processes do
						if lOS.processes[p] == win then
							pID = p
							break
						end
					end
					if pID then
						os.queueEvent("window_open",pID,tostring(win))
					end
				elseif mode == "background" and win.winMode ~= "background" then
					local wID
					for i=1,#lOS.wins do
						if lOS.wins[i] == win then
							wID = i
							break
						end
					end
					if wID then
						os.queueEvent("window_close",wID,tostring(win))
					end
				end
				win.winMode = mode
			end
		end
		local function pullEvent()
			win.events = "all"
			local e = {os.pullEventRaw()}
			win.events = "default"
			return table.unpack(e)
		end
		local function setTitle(title)
			if type(title) == "string" and title ~= "" then
				win.title = title
				return true
			else
				return false
			end
		end
		local function maximize()
			if win.win ~= nil then
				local w,h = lOS.wAll.getSize()
				local off = 0
				if win.winMode == "windowed" then
					off = 1
				end
				win.snap = {x=true,y=true,oPos={win.win.getPosition()},oSize={win.win.getSize()}}
				win.win.reposition(1,1+off,w,h-lOS.tbSize-off)
				return true
			end
			return false
		end
		local function minimize()
			os.queueEvent("window_minimize",lOS.cWin,tostring(tWindow))
		end
		local function focus()
			for k,v in pairs(lOS.processes) do
				if v == win then
					os.queueEvent("window_focus",k,tostring(v))
					break
				end
			end
		end
		lAPI.pullEvent = pullEvent
		lAPI.setWin = setWin
		lAPI.setTitle = setTitle
		lAPI.maximize = maximize
		lAPI.minimize = minimize
		lAPI.focus = focus
		tWindow.env = tEnv
		local srun = tEnv.shell.run
		
		function ms.launch(env, path, ...)
			local args = {...}
			local function func(win)
				local oEnv = createShellEnv(fs.getDir(path),win)
				env["LevelOS"] = oEnv["LevelOS"]
				return run2(env, path, table.unpack(args))
			end
			lOS.newWin(func,path)
		end

		function ms.getCount()
			return #lOS.wins
		end

		function ms.setTitle(n, title)
			lOS.wins[n].title = title
		end

		function ms.setFocus(n)
			if n >= 1 and n <= #lOS.wins then
				local w = lOS.wins[n]
				table.remove(lOS.wins,n)
				table.insert(lOS.wins,w)
				return true
			else
				return false
			end
		end

		function ms.getTitle(n)
			if n >= 1 and n <= #lOS.wins then
				return lOS.wins[n].title
			end
		end

		function s.execute(command, ...)

			local sPath = s.resolveProgram(command)
			if sPath ~= nil then
				local sTitle = lUtils.getFileName(sPath)
				sTitle = (sTitle:gsub("^%l", string.upper))
				lAPI.setTitle(sTitle)

				local sDir = fs.getDir(sPath)
				local env = createShellEnv(sDir,lAPI.self.window)
				env.arg = { [0] = command, ... }
				local result = run2(env, sPath, ...)

				return result
			else
				printError("No such program")
				return false
			end
		end
		
		--function tEnv.shell.run(...)
			--if ({...})[2] and fs.exists(({...})[2]) then
				--setTitle(lUtils.getFileName(({...})[1]).." - "..lUtils.getFileName(({...})[2]))
			--else
				--setTitle(lUtils.getFileName(({...})[1]))
			--end
			--srun(...)
		--end

		-- do the above once we port everything to lOS.run
		
	end

	return tEnv
end


lOS.createShellEnv = createShellEnv




lUtils.shapescape = {}

if true then
	local shape = lUtils.shapescape

	function shape.createRectangle(x1,y1,x2,y2,fillColor,borderColor)
		local sCol = {fillColor or term.getBackgroundColor(),borderColor or 0}
		local tObj = {event={mouse_click={function() end,-1},mouse_up={function() end,-1},focus={function() end,-1},update={function() end,-1},render={function() end,-1},Coroutine={function() end,-1}},type="rect",color=sCol[1],border={type=1,color=sCol[2]},x1=x1,y1=y1,x2=x2,y2=y2}
		--slide.objs[#slide.objs+1] = tObj
		local w,h = term.getSize()
		lUtils.shapescape.renderSlide({win=window.create(term.current(),1,1,w,h,false),objs={tObj}})
		return tObj
	end


	function shape.createText(txt,x1,y1,x2,y2,fillColor,borderColor,textColor)
		local tObj = shape.createRectangle(x1,y1,x2,y2,fillColor or 0,borderColor or 0)
		tObj.type = "text"
		tObj.txtcolor = textColor or term.getTextColor()
		tObj.txt = txt
		return tObj
	end


	function shape.createWindow(x1,y1,x2,y2)
		local tObj = shape.createRectangle(x1,y1,x2,y2)
		tObj.type = "window"
		tObj.color = colors.black
		tObj.border.color = 0
		tObj.render = nil
		tObj.update = nil
		local w,h = term.getSize()
		lUtils.shapescape.renderSlide({win=window.create(term.current(),1,1,w,h,false),objs={tObj}})
		return tObj
	end

	function shape.createInputbox(x1,y1,x2,y2,fillColor,borderColor,textColor)
		local tObj = shape.createText("",x1,y1,x2,y2,fillColor,0,textColor)
		tObj.type = "input"
		return tObj
	end
end


local align = {}
function align.left(slide,offset)
	local w,h = term.getSize()
	return offset
end
function align.right(slide,offset)
	local w,h = term.getSize()
	return w-offset
end
function align.top(slide,offset)
	local w,h = term.getSize()
	return offset
end
function align.bottom(slide,offset)
	local w,h = term.getSize()
	return h-offset
end
function align.center(slide,offset,vert)
	local w,h = term.getSize()
	if vert then
		return math.ceil(h/2)-offset
	else
		return math.ceil(w/2)-offset
	end
end


local generic = {}

function generic.align(obj)
	--if obj.ox1 == nil or obj.oy1 == nil then
		--obj.ox1,obj.oy1,obj.ox2,obj.oy2 = obj.x1,obj.y1,obj.x2,obj.y2
	--end
	local w,h = term.getSize()
	if obj.snap.Left == "Snap right" then
		if not obj.ox1 then
			obj.ox1 = w-obj.x1
		end
		obj.x1 = align.right(slide,obj.ox1)
	elseif obj.snap.Left == "Snap center" then
		if not obj.ox1 then
			obj.ox1 = math.ceil(w/2)-obj.x1
		end
		obj.x1 = align.center(slide,obj.ox1)
	else
		obj.ox1 = nil
	end
	if obj.snap.Right == "Snap right" then
		if not obj.ox2 then
			obj.ox2 = w-obj.x2
		end
		obj.x2 = align.right(slide,obj.ox2)
	elseif obj.snap.Right == "Snap center" then
		if not obj.ox2 then
			obj.ox2 = math.ceil(w/2)-obj.x2
		end
		obj.x2 = align.center(slide,obj.ox2)
	else
		obj.ox2 = nil
	end
	if obj.snap.Top == "Snap bottom" then
		if not obj.oy1 then
			obj.oy1 = h-obj.y1
		end
		obj.y1 = align.bottom(slide,obj.oy1)
	elseif obj.snap.Top == "Snap center" then
		if not obj.oy1 then
			obj.oy1 = math.ceil(h/2)-obj.y1
		end
		obj.y1 = align.center(slide,obj.oy1,true)
	else
		obj.oy1 = nil
	end
	if obj.snap.Bottom == "Snap bottom" then
		if not obj.oy2 then
			obj.oy2 = h-obj.y2
		end
		obj.y2 = align.bottom(slide,obj.oy2)
	elseif obj.snap.Bottom == "Snap center" then
		if not obj.oy2 then
			obj.oy2 = math.ceil(h/2)-obj.y2
		end
		obj.y2 = align.center(slide,obj.oy2,true)
	else
		obj.oy2 = nil
	end
end


function lUtils.shapescape.addScript(tShp,id,ev,assets,LevelOS,slides)
	if not tShp.event then
		tShp.event = {}
	end
	
	-- run script with environment
	local function getEnv(tShape)
		local tempWin
		if LevelOS then
			tempWin = LevelOS.self.window
		end
		local tEnv = createShellEnv("",tempWin)
		if LevelOS then
			tEnv.LevelOS = LevelOS
		end
		setmetatable(tEnv,{__index=_G})
		tEnv.self = tShape
		return tEnv
	end
	local function getSlide()
		return slides[slides.cSlide]
	end
	local function getSlides()
		return slides
	end
	local function setSlide(n)
		if slides[n] then
			slides.cSlide = n
			os.queueEvent("shapescape_change_slide")
			os.pullEvent()
			return true
		else
			return false
		end
	end
	local function exit(...)
		slides.stop = true
		slides["return"] = {...}
	end
	local getEvent = function() return end
	local tEnv = getEnv(tShp)
	tEnv.shapescape = {getEvent=getEvent,getSlide=getSlide,getSlides=getSlides,setSlide=setSlide,exit=exit}
	tShp.tEnv = tEnv
	local sFunc,err = load(assets[id].content,"@"..assets[id].name,"bt",tEnv)
	if not sFunc then
		function sFunc() printError(err) end
	end
	if ev == "Coroutine" and tShp.type == "window" then
		tShp.event[ev] = {function(tShape,e,...) tShape.tEnv.shapescape.getEvent = function() return unpack(e) end local ok,err = pcall(sFunc,...) if not ok then print(err) end end,id}
	else
		tShp.event[ev] = {function(tShape,e,...) tShape.tEnv.shapescape.getEvent = function() return unpack(e) end local ok,err = pcall(sFunc,...) if not ok then lUtils.popup("Error",err,31,11,{"OK"}) end end,id}
	end
end

function lUtils.shapescape.renderSlide(slide,static,args)
	local oterm = term.current()
	term.redirect(slide.win)
	term.setBackgroundColor(colors.white)
	term.clear()
	local cursor
	for o=1,#slide.objs do
		--[[if slide.objs[o].snap then
			generic.align(slide.objs[o])
		end]]
		if slide.objs[o].type == "rect" or slide.objs[o].type == "text" or slide.objs[o].type == "window" or slide.objs[o].type == "input" then
			local s = slide.objs[o]
			if not slide.objs[o].render then
				local self = slide.objs[o]
				if slide.objs[o].type == "window" then
					if not static then
						function self.render()
							local restore = cRestore()
							if self.snap then
								generic.align(self)
							end
							if self.color ~= 0 then
								if not self.window then
									self.window = window.create(term.current(),self.x1,self.y1,(self.x2-self.x1)+1,(self.y2-self.y1)+1,false)
								end
								local x,y = self.window.getPosition()
								local w,h = self.window.getSize()
								if x ~= self.x1 or y ~= self.y1 or w ~= (self.x2-self.x1)+1 or h ~= (self.y2-self.y1)+1 then
									self.window.reposition(self.x1,self.y1,(self.x2-self.x1)+1,(self.y2-self.y1)+1)
								end
								for l=1,(self.y2-self.y1)+1 do
									term.setCursorPos(self.x1,self.y1+(l-1))
									term.blit(self.window.getLine(l))
								end
							end
							cRestore(restore)
						end
					else
						local lines
						local function genLines()
							lines = {}
							--[[local fg = lUtils.toBlit(self.color or colors.white)
							local bg = lUtils.toBlit(self.color or colors.white)]]
							local fg,bg
							if self.color ~= colors.black then
								fg = lUtils.toBlit(self.color)
								bg = lUtils.toBlit(self.border.color ~= nil and self.border.color or colors.black)
								if bg == nil then
									bg = lUtils.toBlit(colors.black)
								end
							else
								fg = lUtils.toBlit(self.color)
								bg = lUtils.toBlit(self.border.color ~= nil and self.border.color or colors.white)
								if bg == nil then
									bg = lUtils.toBlit(colors.white)
								end
							end
							for y=self.y1,self.y2 do
								lines[#lines+1] = {"","",""}
								for x=self.x1,self.x2 do
									lines[#lines][1] = lines[#lines][1]..string.char(math.random(129,159))
									if math.random(1,2) == 2 then
										lines[#lines][2] = lines[#lines][2]..bg
										lines[#lines][3] = lines[#lines][3]..fg
									else
										lines[#lines][2] = lines[#lines][2]..fg
										lines[#lines][3] = lines[#lines][3]..bg
									end
								end
							end
						end
						genLines()
						local x1,y1,x2,y2 = self.x1,self.y1,self.x2,self.y2
						local c1,c2 = self.color,self.border.color
						function self.render()
							if self.snap then
								generic.align(self)
							end
							if x1 ~= self.x1 or y1 ~= self.y1 or x2 ~= self.x2 or y2 ~= self.y2 or c1 ~= self.color or c2 ~= self.border.color then
								x1,y1,x2,y2 = self.x1,self.y1,self.x2,self.y2
								c1,c2 = self.color,self.border.color
								genLines()
							end
							for l=1,#lines do
								term.setCursorPos(self.x1,self.y1+(l-1))
								term.blit(unpack(lines[l]))
							end
						end
					end
				else
					function self.render()
						local restore = cRestore()
						if self.snap then
							generic.align(self)
						end
						if self.color ~= 0 then
							term.setBackgroundColor(self.color)
							--term.setCursorPos(self.x1,self.y1)
							for y=self.y1,self.y2 do
								term.setCursorPos(self.x1,y)
								term.write(string.rep(" ",self.x2-(self.x1-1)))
							end
							--lOS.boxClear(self.x1,self.y1,self.x2,self.y2)
						end
						if self.border and self.border.color ~= 0 then
							term.setTextColor(self.border.color)
							lUtils.border(self.x1,self.y1,self.x2,self.y2,"transparent")
						end
						if self.image then
							if self.border and self.border.color ~= 0 then
								lUtils.renderImg(self.image,self.x1+1,self.y1+1,nil,true)
							else
								lUtils.renderImg(self.image,self.x1,self.y1,nil,true)
							end
						end
						if self.type == "input" then
							-- gen shape and make it provide shape
							if not self.fRender then
								lUtils.input(self.x1,self.y1,self.x2,self.y2,nil,nil,self)
							end
							self.fRender()
						elseif self.txt then
							if self.color == 0 then
								term.setBackgroundColor(colors.white)
							else
								term.setBackgroundColor(self.color)
							end
							term.setTextColor(self.txtcolor)
							term.setCursorPos(1,1)
							if self.border and self.border.color ~= 0 and self.y2 >= self.y1+2 then
								lUtils.textbox(self.txt,self.x1+1,self.y1+1,self.x2-1,self.y2-1,true)
							else
								if static and self.txt == "" and not self.input then
									lUtils.textbox("...",self.x1,self.y1,self.x2,self.y2,true)
								else
									lUtils.textbox(self.txt,self.x1,self.y1,self.x2,self.y2,true)
								end
							end
						end
						if not self.state then
							cRestore(restore)
						end
					end
				end
			end
			s.render()
			if not static and s.event and s.event.render and s.event.render[1] then
				s.event.render[1](s,nil,args)
			end
			if not slide.objs[o].update then
				local self = slide.objs[o]
				function self.update(args,...)
					-- continue this
					local e = {...}
					if e[1] == "mouse_click" or e[1] == "mouse_up" then
						if e[3] >= self.x1 and e[4] >= self.y1 and e[3] <= self.x2 and e[4] <= self.y2 then
							if self.event[e[1]] and self.event[e[1]][1] then
								self.event[e[1]][1](self,e,table.unpack(args))
							end
						end
						if e[1] == "mouse_up" and self.selected then
							self.selected = false
						end
					end
					if self.event.update then
						-- dt when i can
						self.event.update[1](self,e,table.pack(args))
					end
					if not self.coroutine and self.event.Coroutine[2] >= 0 then
						self.coroutine = coroutine.create(function() self.event.Coroutine[1](self,{},table.unpack(args)) end)
					end
					if self.coroutine then
						local oterm = term.current()
						if self.window then
							if self.snap then
								generic.align(self)
							end
							local x,y = self.window.getPosition()
							local w,h = self.window.getSize()
							if x ~= self.x1 or y ~= self.y1 or w ~= (self.x2-self.x1)+1 or h ~= (self.y2-self.y1)+1 then
								self.window.reposition(self.x1,self.y1,(self.x2-self.x1)+1,(self.y2-self.y1)+1)
							end
							term.redirect(self.window)
							if string.find(e[1],"mouse") and e[3] and e[4] and not (self.tEnv and self.tEnv.LevelOS and self.tEnv.LevelOS.self and self.tEnv.LevelOS.self.window and self.tEnv.LevelOS.self.window.events == "all") then
								e[3] = e[3]-(self.x1-1)
								e[4] = e[4]-(self.y1-1)
							end
						end
						coroutine.resume(self.coroutine,unpack(e))
						-- if blink was enabled set cursor back to this window after everything blablabla
						term.redirect(oterm)

					end
					if self.type == "input" then
						if not self.fUpdate then
							lUtils.input(self.x1,self.y1,self.x2,self.y2,nil,nil,nil,self)
						end
						self.fUpdate(...)
					end
				end
			end
		elseif slide.objs[o].type == "triangle" then

		end
		if not slide.objs[o].remove then
			local self = slide.objs[o]
			local sl = slide
			slide.objs[o].remove = function()
				for s=1,#sl.objs do
					if sl.objs[s] == self then
						sl.objs[s] = nil
						return true
					end
				end
			end
		end
	end
	term.redirect(oterm)
end

function lUtils.shapescape.run(slides,...)
	local args = table.pack(...)
	local oterm = term.current()
	if not slides.cSlide then
		slides.cSlide = 1
	end
	term.setBackgroundColor(colors.white)
	local cTerm = term.current()
	local sWin = window.create(cTerm,1,1,term.getSize())
	sWin.setVisible(false)
	--sWin.setVisible(false)
	sWin.setBackgroundColor(colors.white)
	sWin.setTextColor(colors.black)
	sWin.clear()
	slides[slides.cSlide].win = sWin
	--lUtils.shapescape.renderSlide(slides[slides.cSlide])
	local started = 0
	os.queueEvent("term_resize")
	while not slides.stop do
		local cursor
		local s = slides[slides.cSlide]
		local e
		if started < 2 then
			e = {"shapescape_start"}
			started = started+1
		else
			e = {os.pullEvent()}
		end
		sWin.reposition(1,1,term.getSize())
		local cSlide = slides.cSlide
		local oterm = term.current()
		local function ssUpdateFunc()
			for t=1,#s.objs do
				local o = s.objs[t]
				if o and o.update then
					local ok,err = pcall(function() o.update(args,unpack(e)) end)
					if not ok then
						error(err,0)
					end
					if o.window and o.window.getCursorBlink() == true then
						cursor = {pos={o.window.getCursorPos()},color=o.window.getTextColor()}
						cursor.pos[1] = o.x1+(cursor.pos[1]-1)
						cursor.pos[2] = o.y1+(cursor.pos[2]-1)
					end
				end
			end
		end
		local ssUpdate = coroutine.create(ssUpdateFunc)
		term.redirect(sWin)
		local ok,err = coroutine.resume(ssUpdate)
		if not ok then
			error(err,0)
		end
		while coroutine.status(ssUpdate) ~= "dead" do
			term.redirect(oterm)
			sWin.setVisible(true)
			sWin.setVisible(false)
			local e = {os.pullEvent()}
			term.redirect(sWin)
			coroutine.resume(ssUpdate,table.unpack(e))
		end
		if cSlide ~= slides.cSlide then
			-- fuck you bitchass cunt
			if not slides[slides.cSlide].win then
				local w,h = term.getSize()
				slides[slides.cSlide].win = window.create(cTerm,1,1,w,h,false)
			end
			sWin = slides[slides.cSlide].win
			term.redirect(sWin)
			lUtils.shapescape.renderSlide(slides[slides.cSlide])
		end
		term.setCursorBlink(false)
		lUtils.shapescape.renderSlide(s)
		if cursor then
			term.setCursorPos(unpack(cursor.pos))
			term.setTextColor(cursor.color)
			term.setCursorBlink(true)
		end
		-- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa it doesnt WORK
		term.redirect(oterm)
		sWin.setVisible(true)
		sWin.setVisible(false)
-- invis rendering during update broke eberythinf so now its notv invis
		--for t=1,({sWin.getSize()})[2] do
			--term.setCursorPos(1,t)
			--term.blit(sWin.getLine(t))
		--end
	end
	if not LevelOS then
		term.setBackgroundColor(colors.black)
		term.setTextColor(colors.white)
		term.clear()
		term.setCursorPos(1,1)
	end
	return unpack(slides["return"])
end
		

function lUtils.getArgs(fn)
	local args = {}
	local info = debug.getinfo(fn)
	for i=1, info.nparams do
		args[i] = debug.getlocal(fn,i) or "?"
	end
	if info.vararg then
		args[#args + 1] = "..."
	end
	return args
end


function lUtils.randStr(keyLength,num,symb)
	local upperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	local lowerCase = "abcdefghijklmnopqrstuvwxyz"
	local numbers = "0123456789"
	local symbols = "!@#$%&()*+-,./\\:;<=>?^[]{}"
	
	local characterSet = upperCase .. lowerCase
	if num then
		characterSet = characterSet..numbers
	end
	if symb then
		characterSet = characterSet..symbols
	end
	
	local output = ""
	
	for i = 1, keyLength do
		local rand = math.random(#characterSet)
		output = output .. string.sub(characterSet, rand, rand)
	end
	return output
end


function lUtils.logout()
	lOS.userID = nil
	lOS.username = nil
	if fs.exists("LevelOS/data/account2.txt") then
		fs.delete("LevelOS/data/account2.txt")
	end
	lOS.notification("You are now logged out of Leveloper Services.")
end

function lUtils.login(username,password,isToken,rememberme)
	local res,err
	local xtra = ""
	if isToken then
		res,err = http.post("https://os.leveloper.cc/auth.php","username="..textutils.urlEncode(username).."&token="..textutils.urlEncode(password))
	else
		if rememberme then
			xtra = "&rememberme=true"
		end
		res,err = http.post("https://os.leveloper.cc/auth.php","username="..textutils.urlEncode(username).."&password="..textutils.urlEncode(password)..xtra)
	end
	if res then
		local str = res.readAll()
		local oldstr = str
		str = lUtils.getField(str,"msg")
		if str:find("Welcome") then
			lUtils.fwrite("LevelOS/data/account.txt",username)
			if rememberme then
				local token = lUtils.getField(oldstr,"token")
				lUtils.fwrite("LevelOS/data/account2.txt",token)
			end
			local userID = res.getResponseHeaders()["Set-Cookie"]
			return userID,str
		else
			return nil,str
		end
	else
		return nil,err
	end
end


function lUtils.getField(thing,fieldname)
	if string.find(thing,"<"..fieldname..">",1,true) ~= nil and string.find(thing,"</"..fieldname..">",1,true) ~= nil then
		begin = nil
		ending = nil
		trash,begin = string.find(thing,"<"..fieldname..">",1,true)
		ending,ending2 = string.find(thing,"</"..fieldname..">",begin+1,true)
		if begin ~= nil and ending ~= nil then
			return string.sub(thing,begin+1,ending-1),string.sub(thing,1,trash-1)..string.sub(thing,ending2+1,string.len(thing))
		end
	end
	return nil,thing
end


function lUtils.compare(a, b)
  for k,v in pairs(a) do 
	if (type(v) == "table" and type(b[k]) == "table" and not lUtils.compare(b[k], v)) or b[k] ~= v then return false end 
  end
  for k,v in pairs(b) do 
	if (type(v) == "table" and type(a[k]) == "table" and not lUtils.compare(a[k], v)) or a[k] ~= v then return false end 
  end
  return true
end


function lUtils.centerText(text)
	local x,y = term.getSize()
	local x2,y2 = term.getCursorPos()
	term.setCursorPos(math.floor(x / 2 - text:len() / 2) + 1, y2)
	term.write(text)
end



function lUtils.outline(x1,y1,x2,y2)
	local c1 = term.getTextColor()
	local c2 = term.getBackgroundColor()
	term.setCursorPos(x1,y1)
	term.setBackgroundColor(c1)
	term.setTextColor(c2)
	local a
	for a=x1,x2 do
		term.write("\143")
	end
	for a=y1,y2 do
		term.setCursorPos(x1,y1-1+a)
		term.write("\149")
	end
	term.setBackgroundColor(c2)
	term.setTextColor(c1)
	for a=y1,y2 do
		term.setCursorPos(x2,y1-1+a)
		term.write("\149")
	end
	term.setCursorPos(x1,y2)
	for a=x1,x2 do
		term.write("\131")
	end
end



function lUtils.border(x1,y1,x2,y2,mode,layer)
	term.setCursorPos(x1,y1)
	local l = layer or 3
	local w,h = x2-(x1-1),y2-(y1-1)
	local bg,fg = term.getBackgroundColor(),term.getTextColor()
	local inved = false
	local function inv()
		if not inved then
			term.setBackgroundColor(fg)
			term.setTextColor(bg)
			inved = true
		else
			term.setBackgroundColor(bg)
			term.setTextColor(fg)
			inved = false
		end
	end
	local function setBG()
		if mode and mode == "transparent" then
			if not inved then
				term.setBackgroundColor(lUtils.toColor(({lUtils.getPixel(term.current(),term.getCursorPos())})[3]))
			else
				term.setTextColor(lUtils.toColor(({lUtils.getPixel(term.current(),term.getCursorPos())})[3]))
			end
		end
	end
	--[[local function setFG()
		if mode and mode == "transparent" then
			term.setTextColor(lUtils.toColor(({lUtils.getPixel(term.current(),term.getCursorPos())})[3]))
		end
	end]]
	if l == 1 then
		inv()
		setBG()
	else
		setBG()
	end
	if l == 2 then
		term.write("\156")
	elseif l == 3 then
	   term.write("\151")
	elseif l == 1 then
		term.write("\159")
	end
	if mode and mode == "transparent" then
		for x=1,w-2 do
			if l == 3 then
				setBG()
				term.write("\131")
			elseif l == 2 then
				setBG()
				term.write("\140")
			elseif l == 1 then
				setBG()
				term.write("\143")
			end
		end
	else
		if l == 3 then
			term.write(string.rep("\131",w-2))
		elseif l == 2 then
			term.write(string.rep("\140",w-2))
		elseif l == 1 then
			term.write(string.rep("\143",w-2))
		end
	end
	inv()
	if l == 1 then
		setBG()
	else
		setBG()
	end
	if l == 2 then
		term.write("\147")
	elseif l == 3 then
	   term.write("\148")
	elseif l == 1 then
		term.write("\144")
	end
	for y=y1+1,y2-1 do
		term.setCursorPos(x2,y)
		setBG()
		term.write("\149")
		inv()
		term.setCursorPos(x1,y)
		setBG()
		term.write("\149")
		if mode and mode == "fill" then
			term.write(string.rep(" ",w-2))
		end
		inv()
	end
	term.setCursorPos(x1,y2)
	if l == 3 then
	   setBG()
	   term.write("\138")
	elseif l == 2 then
		inv()
		setBG()
		term.write("\141")
	elseif l == 1 then
		setBG()
		term.write("\130")
	end
	if mode and mode == "transparent" then
		for x=1,w-2 do
			if l == 3 then
				setBG()
				term.write("\143")
			elseif l == 2 then
				setBG()
				term.write("\140")
			elseif l == 1 then
				setBG()
				term.write("\131")
			end
		end
	else
		if l == 3 then
			term.write(string.rep("\143",w-2))
		elseif l == 2 then
			term.write(string.rep("\140",w-2))
		elseif l == 1 then
			term.write(string.rep("\131",w-2))
		end
	end
	if l == 3 then
		setBG()
		term.write("\133")
		inv()
	elseif l == 2 then
		setBG()
		term.write("\142")
	elseif l == 1 then
		setBG()
		term.write("\129")
	end
end


function lUtils.spairs(t, order)
	-- collect the keys
	local keys = {}
	for k in pairs(t) do keys[#keys+1] = k end

	-- if order function given, sort by it by passing the table and keys a, b,
	-- otherwise just sort the keys 
	if order then
		table.sort(keys, function(a,b) return order(t, a, b) end)
	else
		table.sort(keys)
	end

	-- return the iterator function
	local i = 0
	return function()
		i = i + 1
		if keys[i] then
			return keys[i], t[keys[i]]
		end
	end
end


function lUtils.splitStr(str,pat)
   local t = {}  -- NOTE: use {n = 0} in Lua-5.0
   local fpat = "(.-)" .. pat
   local last_end = 1
   local s, e, cap = str:find(fpat, 1)
   while s do
	  if s ~= 1 or cap ~= "" then
		 table.insert(t,cap)
	  end
	  last_end = e+1
	  s, e, cap = str:find(fpat, last_end)
   end
   if last_end <= #str then
	  cap = str:sub(last_end)
	  table.insert(t, cap)
   end
   return t
end



function lUtils.explorer(path,mode)
	if path == nil or path == "" then
		path = "/"
	end
	local title = "Explorer"
	if mode == "SelFile" then
		title = "Select File"
	elseif mode == "SelFolder" then
		title = "Select Folder"
	end
	local w,h = term.getSize()
	local eW,eH = math.ceil(math.min(w*0.75,w-3)),math.ceil(math.min(h*0.75,h-1))
	local a = {lUtils.openWin(title,"Program_Files/LevelOS/Explorer/main.lua "..path.." "..mode,math.ceil(w/2-eW/2),math.ceil(h/2-eH/2),eW,eH,true,false)}
	if a[1] == false then return false end
	table.remove(a,1)
	if type(a[1]) == "table" then
		a = a[1]
	end
	return table.unpack(a)
end



local to_colors, to_blit = {}, {}
for i = 1, 16 do
	to_blit[2^(i-1)] = ("0123456789abcdef"):sub(i, i)
	to_colors[("0123456789abcdef"):sub(i, i)] = 2^(i-1)
end



function lUtils.toColor(theblit)
	return to_colors[theblit] or nil
end



function lUtils.toBlit(thecolor)
	return to_blit[thecolor] or nil
end



function lUtils.instantiate(orig)
	local function deepCopy(o, seen) -- so that "seen" doesn't appear in autocomplete
		seen = seen or {}
		if seen[o] then
			return seen[o]
		end
		local copy
		if type(o) == 'table' then
			copy = {}
			seen[o] = copy
			for k,v in pairs(o) do
				copy[deepCopy(k, seen)] = deepCopy(v, seen)
			end
			setmetatable(copy, deepCopy(getmetatable(o), seen))
		else
			copy = o
		end
		return copy
	end
	return deepCopy(orig)
end



local function tokenise( ... )
	local sLine = table.concat( { ... }, " " )
	local tWords = {}
	local bQuoted = false
	for match in string.gmatch( sLine .. "\"", "(.-)\"" ) do
		if bQuoted then
			table.insert( tWords, match )
		else
			for m in string.gmatch( match, "[^ \t]+" ) do
				table.insert( tWords, m )
			end
		end
		bQuoted = not bQuoted
	end
	return tWords
end





to_colors, to_blit = {}, {}
for i = 1, 16 do
	to_blit[2^(i-1)] = ("0123456789abcdef"):sub(i, i)
	to_colors[("0123456789abcdef"):sub(i, i)] = 2^(i-1)
end


function lOS.eventdelay()
	local oldtime = os.time()
	local timer = os.startTimer(0)
	while true do
		event,id = os.pullEvent("timer")
		if id == timer then
			local newtime = os.time()
			return (newtime-oldtime)*50000
		end
	end
end



function lUtils.getFileType(filename)
	if string.find(filename,"%.%w+$") == nil then
		return ""
	else
		return string.sub(filename,string.find(filename,"%.%w+$"))
	end
end



function lUtils.getFileName(filename,ext)
	local f = filename
	if string.find(filename," ") then
		f = string.sub(filename,1,({string.find(filename," ")})[1]-1)
	end
	f = fs.getName(f)
	if not ext or lUtils.getFileType(f) == ".llnk" then
		f = string.sub(f,1,string.len(f)-string.len(lUtils.getFileType(f)))
	end
	return string.gsub(f,"_"," ")
end



function lUtils.getFilePath(filename)
	local f = filename
	if string.find(filename," ") then
		f = string.sub(filename,1,({string.find(filename," ")})[1]-1)
	end
	return f
end

local strength

function lOS.checkinternet()
	strenth = 0
	local oldtime = os.time()
	a = http.get("https://os.leveloper.cc/ping.php")
	if a == nil or a == false then
		strength = 0
	else
		newtime = os.time()
		if newtime-oldtime < 0.005 then
			strength = 3
		elseif newtime-oldtime < 0.01 then
			strength = 2
		elseif newtime-oldtime >= 0.01 then
			strength = 1
		end
		return strength,newtime-oldtime
	end
	return 0,0
end

function lOS.getInternet()
	return strength or 0
end

function lOS.run( _sCommand, ... )
	local sPath = shell.resolveProgram( _sCommand )
	if sPath ~= nil then
		local tWindow = {}
		local tArgs = {...}
		--term.write(textutils.serialize(tArgs))
		if type(tArgs[1]) == "table" then
			tWindow = tArgs[1]
			table.remove(tArgs,1)
		else
			tWindow = nil
		end
		-- set window title
		local sDir = fs.getDir( sPath )
		local result = {run2( createShellEnv( sDir, tWindow, sPath ), sPath, table.unpack(tArgs) )}
		return table.unpack(result)
	else
		return false,"No such program"
	end
end

function lOS.newWin(func,rPath)
	-- fuck me
end


function lOS.searchfor(arg,path)
	local files = fs.list(path)
	local folders = {}
	local result = {}
	local p = ""
	if path ~= "" then
		p = path.."/"
	else
		p = ""
	end
	for t=1,#files do
		if fs.isDir(p..files[t]) then
			folders[#folders+1] = p..files[t]
		elseif string.find(string.lower(files[t]),string.lower(arg)) then
			result[#result+1] = p..files[t]
		end
	end
	for t=1,#folders do
		local a = lOS.searchfor(arg,folders[t])
		for b=1,#a do
			result[#result+1] = a[b]
		end
	end
	return result
end



function lOS.search(keyword,x,y,w,h,searchfile,animation)
	local lines = {}
	local slp = function() os.sleep(0) end
	for t=1,h do
		lines[t] = {"","",""}
	end
	lines[h] = {"\138","0","8"}
	lines[h-1] = {"\149","8","0"}
	lines[h-2] = {"\151","8","0"}
	lines[1] = {"\151","8","7"}
	lines[2] = {"\149","8","7"}
	for t=1,w-2 do
		if t < math.floor(w/2) then
			lines[1][1] = lines[1][1].."\131"
			lines[1][2] = lines[1][2].."8"
			lines[1][3] = lines[1][3].."7"
			lines[2][1] = lines[2][1].." "
			lines[2][2] = lines[2][2].."0"
			lines[2][3] = lines[2][3].."7"
		else
			lines[1][1] = lines[1][1].."\131"
			lines[1][2] = lines[1][2].."8"
			lines[1][3] = lines[1][3].."0"
			lines[2][1] = lines[2][1].." "
			lines[2][2] = lines[2][2].."0"
			lines[2][3] = lines[2][3].."0"
		end
		lines[h][1] = lines[h][1].."\143"
		lines[h][2] = lines[h][2].."0"
		lines[h][3] = lines[h][3].."8"
		lines[h-1][1] = lines[h-1][1].." "
		lines[h-1][2] = lines[h-1][2].."f"
		lines[h-1][3] = lines[h-1][3].."0"
		lines[h-2][1] = lines[h-2][1].."\131"
		lines[h-2][2] = lines[h-2][2].."8"
		lines[h-2][3] = lines[h-2][3].."0"
	end
	lines[1][1] = lines[1][1].."\148"
	lines[1][2] = lines[1][2].."0"
	lines[1][3] = lines[1][3].."8"
	lines[2][1] = lines[2][1].."\149"
	lines[2][2] = lines[2][2].."0"
	lines[2][3] = lines[2][3].."8"
	lines[h][1] = lines[h][1].."\133"
	lines[h][2] = lines[h][2].."0"
	lines[h][3] = lines[h][3].."8"
	lines[h-1][1] = lines[h-1][1].."\149"
	lines[h-1][2] = lines[h-1][2].."0"
	lines[h-1][3] = lines[h-1][3].."8"
	lines[h-2][1] = lines[h-2][1].."\148"
	lines[h-2][2] = lines[h-2][2].."0"
	lines[h-2][3] = lines[h-2][3].."8"
	term.setCursorPos(x,y+(h-1))
	term.blit(table.unpack(lines[h]))
	term.setCursorPos(x,y+(h-1)-1)
	term.blit(table.unpack(lines[h-2]))
	slp()
	term.setCursorPos(x,y+(h-1)-1)
	term.blit(table.unpack(lines[h-1]))
	term.setCursorPos(x,y+(h-1)-2)
	term.blit(table.unpack(lines[h-2]))
	slp()
	for t=4,h,3 do
		term.setCursorPos(x,y+(h-1)-(t-1))
		term.blit(table.unpack(lines[1]))
		for a=1,t-4 do
			term.setCursorPos(x,y+(h-1)-(t-1)+(a))
			term.blit(table.unpack(lines[2]))
		end
		slp()
	end
	term.setCursorPos(x,y)
	term.blit(table.unpack(lines[1]))
	for a=1,h-4 do
		term.setCursorPos(x,y+a)
		term.blit(table.unpack(lines[2]))
	end
	search = lUtils.makeEditBox("Search",w-3,1)
	search.lines = {""}
	local function searchy()
		while true do
			lUtils.drawEditBox(search,x+2,y+(h-2),0,0,string.len(search.lines[1])+1,1,true,false)
		end
	end
	local scrl = -1
	local sel = 1
	local btns = {}
	local function rendersearch()
		for a=1,h-4 do
			term.setCursorPos(x,y+a)
			term.blit(table.unpack(lines[2]))
		end
		local ox,oy = term.getCursorPos()
		local txtcolor = term.getTextColor()
		term.setCursorPos(x+(w/2),y+1)
		term.setBackgroundColor(colors.white)
		term.setTextColor(colors.black)
--      write("lastkey = "..lastkey.." and os.time() = "..os.time())
		-- selected box
		term.setBackgroundColor(colors.white)
		lOS.boxClear(x+math.ceil(w/2),y+1,x+(w-2),y+(h-4))
		if result[sel] ~= nil then
			local tPath = ""
			if string.gsub(result[sel],fs.getName(result[sel]),"") ~= "" then
				tPath = string.gsub(result[sel],fs.getName(result[sel]),"")
			else
				tPath = "root"
			end
			term.setCursorPos(x+(w/2)+1,y+2)
			local ay = y+2
			local ax = x+(math.floor(w/2))+6
			local aw = (math.ceil(w/2-1)-6)
			term.setTextColor(colors.lightGray)
			write("Name")
			term.setTextColor(colors.black)
			for t=1,math.ceil(string.len(fs.getName(result[sel]))/aw) do
				term.setCursorPos(ax,ay)
				write(string.sub(fs.getName(result[sel]),1+((t-1)*aw),t*aw))
				ay = ay+1
			end
			term.setCursorPos(x+(w/2)+1,ay)
			term.setTextColor(colors.lightGray)
			write("Path")
			term.setTextColor(colors.black)
			for t=1,math.ceil(string.len(tPath)/aw) do
				term.setCursorPos(ax,ay)
				write(string.sub(tPath,1+((t-1)*aw),t*aw))
				ay = ay+1
			end
	if fs.exists(result[sel].."updater") then
		term.setTextColor(colors.gray)
		term.setCursorPos(x+(w/2)+1,ay)
		term.write("(Auto updates)")
		ay = ay+1
	end
	ay = ay+1
	local aline = ""
	for t=ax-5,x+(w-2) do
		aline = aline.." "
	end
	function filetype(filename)
		if string.find(filename,"%.%w+$") == nil then
			return ""
		else
			return string.sub(filename,string.find(filename,"%.%w+$"))
		end
	end
	if ay+1 <= y+(h-4) then
		if filetype(fs.getName(result[sel])) == ".lua" then
			btns = {}
			btns[1] = {" Execute",ax-5,ay}
			btns[2] = {" Edit",ax-5,ay+2}
			btns[3] = {" Create Shortcut",ax-5,ay+4}
		elseif filetype(fs.getName(result[sel])) == ".txt" then
			btns = {}
			btns[1] = {" Edit",ax-5,ay}
			btns[2] = {" Create Shortcut",ax-5,ay+2}
		else
			btns = {}
			btns[1] = {" Execute",ax-5,ay}
			btns[2] = {" Edit",ax-5,ay+2}
			btns[3] = {" Create Shortcut",ax-5,ay+4}
		end
	end
	for t=1,#btns do
		if t==1 then
			term.setCursorPos(ax-5,btns[t][3]-1)
			term.setBackgroundColor(colors.lightGray)
			term.setTextColor(colors.white)
			term.write(string.gsub(aline,"%s","\143"))
		end
		term.setCursorPos(btns[t][2],btns[t][3])
		term.setTextColor(colors.black)
		term.write(btns[t][1]..string.sub(aline,string.len(btns[t][1])+1,string.len(aline)))
		if t < #btns then
			term.setCursorPos(ax-5,btns[t][3]+1)
			term.setTextColor(colors.white)
			term.write(string.gsub(aline,"%s","\140"))
		else
			term.setCursorPos(ax-5,btns[t][3]+1)
			term.setTextColor(colors.lightGray)
			term.setBackgroundColor(colors.white)
			term.write(string.gsub(aline,"%s","\131"))
		end
	end
		end
		for t=1,#result do
			if result[t] ~= nil then
				if sel == t then
					term.setBackgroundColor(colors.lightGray)
				else
					term.setBackgroundColor(colors.gray)
				end
				if fs.exists(result[t].."updater") ~= nil then
					for i=1,#result do
						if result[i] ~= nil and result[i] == result[t].."updater" then
							table.remove(result,i)
						end
					end
				end
				if y+((t-1)*2)-scrl > y and y+((t-1)*2)-scrl <= y+(h-4) then
					term.setCursorPos(x,y+((t-1)*2)-scrl)
					term.setTextColor(colors.lightGray)
					term.write("\149")
					for i=1,(w/2)-2 do
						write(" ")
					end
					term.setCursorPos(x+1,y+((t-1)*2)-scrl)
					term.setTextColor(colors.white)
					if string.len(fs.getName(result[t])) > (w/2)-2 then
						term.write(string.sub(fs.getName(result[t]),1,(w/2)-5).."...")
					else
						term.write(fs.getName(result[t]))
					end
				end
				if 1+y+((t-1)*2)-scrl > y and 1+y+((t-1)*2)-scrl <= y+(h-4) then
					term.setCursorPos(x,1+y+((t-1)*2)-scrl)
					term.setTextColor(colors.lightGray)
					term.write("\149")
					for i=1,(w/2)-2 do
						write(" ")
					end
					term.setCursorPos(x+1,1+y+((t-1)*2)-scrl)
					if sel == t then
						term.setTextColor(colors.gray)
					end
					if string.gsub(result[t],fs.getName(result[t]),"") ~= "" then
						if string.len(string.gsub(result[t],fs.getName(result[t]),"")) > (w/2)-2 then
							term.write(string.sub(string.gsub(result[t],fs.getName(result[t]),""),1,(w/2)-5).."...")
						else
							term.write(string.gsub(result[t],fs.getName(result[t]),""))
						end
					else
						term.write("root")
					end
				end
			end
		end
		term.setCursorPos(ox,oy)
		term.setTextColor(txtcolor)
	end
	lastkey = os.time()
	lastsearch = ""
	result = {}
	term.setBackgroundColor(colors.white)
--	lOS.boxClear(x+math.ceil(w/2),y+1,x+(w-2),y+(h-2))
	local function regevents()
		local atimer = os.startTimer(0.5)
		while true do
			local a = {os.pullEvent()}
			if a[1] == "mouse_click" then
				if a[3] < x or a[3] > x+(w-1) or a[4] < y then -- maybe check underneath search box too but i dont think thats necessary for now
					return false,""
				else
					for t=1,#btns do
						if a[4] == btns[t][3] and a[3] >= btns[t][2] and result[sel] ~= nil then
							if string.gsub(btns[t][1],"%s","") == "Edit" and fs.exists(result[sel].."updater") == true then
								result[sel] = result[sel].."updater"
							end
							return true,result[sel],string.gsub(btns[t][1],"%s","")
						end
					end
				end
			elseif a[1] == "key" then
				lastkey = os.time()
				if a[2] == keys.enter and search.lines[1] ~= "" and result[sel] ~= nil then
					return true,result[sel],"Execute"
				elseif a[2] == keys.down then
					if result[sel+1] ~= nil then
						sel = sel+1
						while y+((sel-1)*2)-scrl <= y do
							scrl = scrl-1
						end
						while 1+y+((sel-1)*2)-scrl > y+(h-4) do
							scrl = scrl+1
						end
						rendersearch()
					end
				elseif a[2] == keys.up then
					if sel > 1 then
						sel = sel-1
						while y+((sel-1)*2)-scrl <= y do
							scrl = scrl-1
						end
						while 1+y+((sel-1)*2)-scrl > y+(h-4) do
							scrl = scrl+1
						end
						rendersearch()
					end
				end
			elseif a[1] == "timer" and a[2] == atimer then
				atimer = os.startTimer(0.5)
				if os.time() > lastkey+0.01 and search.lines[1] ~= lastsearch then
					lastkey = os.time()
					lastsearch = search.lines[1]
					result = lOS.searchfor(search.lines[1],"")
					sel = 1
					rendersearch()
				end
			elseif a[1] == "mouse_scroll" and a[3] >= x and a[3] <= x+((w/2)-1) and a[4] >= y and a[4] <= y+(h-4) then
				if scrl+a[2] >= -1 then
					scrl = scrl+a[2]
					rendersearch()
				end
			end
		end
	end
	i = {}
	parallel.waitForAny(searchy,function() i={regevents()} end)
	term.setCursorBlink(false)
	return table.unpack(i)
end



function lUtils.littlewin(oriwin,w,h)
	local oW,oH = oriwin.getSize()
	local oldwin = {lines={}}
	for y=1,oH do
		oldwin.lines[y] = {oriwin.getLine(y)}
	end
	local newwin = {lines={}}
	local ystep = oH/h
	local xstep = oW/w
	for y=1,h do
		local xstart = math.floor(xstep/2)
		if xstart < 1 then
			xstart = 1
		end
		local ystart = math.floor(ystep/2)
		if ystart < 1 then
			ystart = 1
		end
		local curLine = math.floor(ystart+ystep*(y-1))
		if curLine > oH or curLine <= 0 then
			curLine = 1
		end
		local templine = {oriwin.getLine(curLine)}
		newwin.lines[y] = {"","",""}
		for x=1,w do
			newwin.lines[y][1] = newwin.lines[y][1]..string.sub(templine[1],math.floor(xstart+xstep*(x-1)+0.5),math.floor(xstart+xstep*(x-1)+0.5))
			newwin.lines[y][2] = newwin.lines[y][2]..string.sub(templine[2],math.floor(xstart+xstep*(x-1)+0.5),math.floor(xstart+xstep*(x-1)+0.5))
			newwin.lines[y][3] = newwin.lines[y][3]..string.sub(templine[3],math.floor(xstart+xstep*(x-1)+0.5),math.floor(xstart+xstep*(x-1)+0.5))
		end
	end
	function newwin.render(x,y,noLetters)
		for l=1,#newwin.lines do
			term.setCursorPos(x,y+(l-1))
			if noLetters then
				newwin.lines[l][1] = newwin.lines[l][1]:gsub("[^\127-\160 ]",".")
			end
			term.blit(table.unpack(newwin.lines[l]))
		end
	end
	return newwin
end



function lUtils.fread(filepath, binary)
	local mode = binary and "rb" or "r"
	local fread = fs.open(filepath,mode)
	local thing = fread.readAll()
	fread.close()
	return thing
end

function lUtils.fwrite(filepath,content, binary)
	local mode = binary and "wb" or "w"
	local fwrite = fs.open(filepath,mode)
	fwrite.write(content)
	fwrite.close()
	return true
end



wPattern = "%S+"
function lUtils.makeEditBox(filepath,width,height,sTable)
	if filepath == nil then
		return false
	end
	if width == nil then
		width = getWidth()
	end
	if height == nil then
		height = getHeight()
	end
	if sTable == nil then
		--sTable = {background={colors.white},text={colors.black},keywords={colors.yellow},notes={colors.green},strings={colors.red},menu={colors.yellow,colors.lightGray}}
		sTable = {background={colors.white},text={colors.black},cursor={colors.red}}
	end
	lines = {""}
	return {width=width,height=height,sTable=sTable,lines=lines,filepath=filepath,changed=false}
end



function lUtils.drawEditBox(box,expos,eypos,spx,spy,cpx,cpy,active,enterkey,rChar,changesAllowed)
	if changesAllowed == nil then
		changesAllowed = true
	end
	-- term.setCursorPos(expos,eypos)
	-- print(tostring(changesAllowed))
	-- os.sleep(1)
	if enterkey == nil then
		enterkey = true
	end
	if spx == nil then
		spx = 0
	end
	if spy == nil then
		spy = 0
	end
	if cpx == nil then
		cpx = 1
	end
	if cpy == nil then
		cpy = 1
	end
	if expos == nil then
		expos = box.x
	else
		box.x = expos
	end
	if eypos == nil then
		eypos = box.y
	else
		box.y = eypos
	end
	-- As these abbrevs are pretty unclear, I will now specify them respectively.
	-- editorboxtable,editorxposition,editoryposition,scrollpositionx,scrollpositiony,usercursorpositionx,usercurserpositiony
	local keywords = {["and"]=true,["break"]=true,["do"]=true,["else"]=true,["elseif"]=true,["end"]=true,["false"]=true,["for"]=true,["function"]=true,["if"]=true,["in"]=true,["local"]=true,["nil"]=true,["not"]=true,["or"]=true,["repeat"]=true,["return"]=true,["then"]=true,["true"]=true,["until"]=true,["while"]=true}
	if box.width == nil or box.height == nil or box.sTable == nil or box.lines == nil or box.filepath == nil then
		return false
	end
	if active == nil then
		active = true
	end
	if active == false then
		if box.sTable.background == nil then
			term.setBackgroundColor(colors.black)
		else
			term.setBackgroundColor(box.sTable.background[1])
		end
		lOS.boxClear(expos,eypos,expos+(box.width-1),eypos+(box.height-1))
		ypos = eypos
		for l=1+spy,box.height+spy do
			if box.lines[l] ~= nil then
				if rChar ~= nil then
					term.setTextColor(colors.lightGray)
					term.setCursorPos(expos,ypos)
					for rc=1,string.len(string.sub(box.lines[l],1+spx,box.width+spx)) do
						write(rChar)
					end
				else
					term.setTextColor(colors.lightGray)
					term.setCursorPos(expos,ypos)
					write(string.sub(box.lines[l],1+spx,box.width+spx))
				end
			end
			ypos = ypos+1
		end
		return box,spx,spy,cpx,cpy,false,{}
	end
	while true do
		expos,eypos = box.x,box.y
		if box.sTable.background == nil then
			term.setBackgroundColor(colors.black)
		else
			term.setBackgroundColor(box.sTable.background[1])
		end
		lOS.boxClear(expos,eypos,expos+(box.width-1),eypos+(box.height-1))
		ypos = eypos
		for l=1+spy,box.height+spy do
			instring = false
			innote = false
			if box.lines[l] ~= nil then
				if box.sTable.text == nil then
					term.setTextColor(colors.white)
				else
					term.setTextColor(box.sTable.text[1])
				end
				term.setCursorPos(expos,ypos)
				if rChar ~= nil then
					for rc=1,string.len(string.sub(box.lines[l],1+spx,box.width+spx)) do
						write(rChar)
					end
				else
					wordcount = 1
					if string.find(string.sub(box.lines[l],1+spx,box.width+spx),"%s+") ~= nil then
						if string.find(string.sub(box.lines[l],1+spx,box.width+spx),"%s+") == 1 then
							write(string.match(string.sub(box.lines[l],1+spx,box.width+spx),"%s+"))
						end
					end
					for word in string.gmatch(string.sub(box.lines[l],1+spx,box.width+spx),wPattern) do
						somearg = string.find(word,"--",nil,true)
						if tonumber(somearg) ~= nil and instring == false then
							innote = true
						end
						somearg = nil
						if innote == true and box.sTable.notes ~= nil then
							term.setTextColor(box.sTable.notes[1])
						elseif keywords[word] ~= nil and box.sTable.keywords ~= nil then
							term.setTextColor(box.sTable.keywords[1])
						elseif tonumber(word) ~= nil and box.sTable.numbers ~= nil then
							term.setTextColor(box.sTable.numbers[1])
						else
							term.setTextColor(box.sTable.text[1])
						end
						if wordcount == 1 then
							write(word)
						else
							write(" "..word)
						end
						wordcount = wordcount+1
					end
				end
			end
			ypos = ypos+1
		end
		if cpy > spy and cpy <= spy+(box.height) and cpx > spx and cpx <= spx+(box.width) then
			if box.sTable.cursor ~= nil then
				term.setTextColor(box.sTable.cursor[1])
			else
				term.setTextColor(colors.black)
			end
			term.setCursorPos(cpx-spx-1+expos,cpy-spy-1+eypos)
			term.setCursorBlink(true)
		end
		while cpx > string.len(box.lines[cpy])+1 do
			cpx = cpx-1
		end
		while cpx > spx+(box.width) do
			spx = spx+1
		end
		while cpx <= spx do
			spx = spx-1
		end
		event,button,x,y = os.pullEvent()
		term.setCursorBlink(false)
		if (event == "mouse_click" and (x < expos or x > expos+box.width-1 or y < eypos or y > eypos+box.height-1)) or (event == "key" and button == keys.enter and enterkey == false) then
			if box.sTable.background == nil then
				term.setBackgroundColor(colors.black)
			else
				term.setBackgroundColor(box.sTable.background[1])
			end
			lOS.boxClear(expos,eypos,expos+(box.width-1),eypos+(box.height-1))
			ypos = eypos
			for l=1+spy,box.height+spy do
				if box.lines[l] ~= nil then
					if rChar ~= nil then
						for rc=1,string.len(string.sub(box.lines[l],1+spx,box.width+spx)) do
							write(rChar)
						end
					else
						term.setTextColor(colors.lightGray)
						term.setCursorPos(expos,ypos)
						write(string.sub(box.lines[l],1+spx,box.width+spx))
					end
				end
				ypos = ypos+1
			end
			return box,spx,spy,cpx,cpy,false,{event,button,x,y}
		elseif event == "mouse_scroll" and enterkey == true then
			if button == -1 and spy > 0 then
				spy = spy-1
			elseif button == 1 then
				spy = spy+1
			end
		elseif event == "key" then
			if button == keys.right and cpx < string.len(box.lines[cpy])+1 then
				cpx = cpx+1
				while cpy <= spy do
					spy = spy-1
				end
				while cpy > spy+box.height do
					spy = spy+1
				end
			elseif button == keys.left and cpx > 1 then
				cpx = cpx-1
				while cpy <= spy do
					spy = spy-1
				end
				while cpy > spy+box.height do
					spy = spy+1
				end
			elseif button == keys.down and cpy < #box.lines then
				cpy = cpy+1
				while cpy > spy + (box.height) do
					spy = spy+1
				end
				while cpy <= spy do
					spy = spy-1
				end
			elseif button == keys.up and cpy > 1 then
				cpy = cpy-1
				while cpy <= spy do
					spy = spy-1
				end
				while cpy > spy+box.height do
					spy = spy+1
				end
			elseif button == keys.home then
				cpx = 1
				while cpy <= spy do
					spy = spy-1
				end
				while cpy > spy+box.height do
					spy = spy+1
				end
			elseif button == keys["end"] then
				cpx = string.len(box.lines[cpy])+1
				while cpy <= spy do
					spy = spy-1
				end
				while cpy > spy+box.height do
					spy = spy+1
				end
			elseif button == keys.tab and changesAllowed == true then
				while cpy <= spy do
					spy = spy-1
				end
				while cpy > spy+box.height do
					spy = spy+1
				end
				if box.changed == false then
					box.changed = true
				end
				box.lines[cpy] = string.sub(box.lines[cpy],1,cpx-1).."  "..string.sub(box.lines[cpy],cpx,string.len(box.lines[cpy]))
				cpx = cpx+2
			elseif button == keys.backspace and changesAllowed == true then
				while cpy <= spy do
					spy = spy-1
				end
				while cpy > spy+box.height do
					spy = spy+1
				end
				if box.changed == false then
					box.changed = true
				end
				if cpx == 1 then
					if cpy > 1 then
						cpx = string.len(box.lines[cpy-1])+1
						box.lines[cpy-1] = box.lines[cpy-1]..box.lines[cpy]
						table.remove(box.lines,cpy)
						cpy = cpy-1
					end
				else
					box.lines[cpy] = string.sub(box.lines[cpy],1,cpx-2)..string.sub(box.lines[cpy],cpx,string.len(box.lines[cpy]))
					cpx = cpx-1
				end
			elseif button == keys.enter and enterkey ~= false and changesAllowed == true then
				while cpy <= spy do
					spy = spy-1
				end
				while cpy > spy+box.height-1 do
					spy = spy+1
				end
				if box.changed == false then
					box.changed = true
				end
				table.insert(box.lines,cpy+1,string.sub(box.lines[cpy],cpx,string.len(box.lines[cpy])))
				box.lines[cpy] = string.sub(box.lines[cpy],1,cpx-1)
				cpy = cpy+1
				cpx = 1
			end
		elseif event == "char" and changesAllowed == true then
			while cpy <= spy do
				spy = spy-1
			end
			while cpy > spy+box.height do
				spy = spy+1
			end
			if box.changed == false then
				box.changed = true
			end
			box.lines[cpy] = string.sub(box.lines[cpy],1,cpx-1)..button..string.sub(box.lines[cpy],cpx,string.len(box.lines[cpy]))
			cpx = cpx+1
	elseif event == "paste" and changesAllowed == true then
		while cpy <= spy do
				spy = spy-1
			end
			while cpy > spy+box.height do
				spy = spy+1
			end
			if box.changed == false then
				box.changed = true
			end
			box.lines[cpy] = string.sub(box.lines[cpy],1,cpx-1)..button..string.sub(box.lines[cpy],cpx,string.len(box.lines[cpy]))
			cpx = cpx+string.len(button)
		elseif event == "mouse_click" and y <= eypos+box.height-1 then
			cpx = x - expos + spx+1
			cpy = y - eypos + spy+1
			if cpy > #box.lines then
				cpy = #box.lines
			end
		end
	end
end


function lUtils.textbox(txt,x1,y1,x2,y2,trans,syntax,overflowX,overflowY,scrollX,scrollY,align)
	--[[term.setCursorPos(1,1)
	local bbox = lUtils.makeBox(txt,x1,y1,x2,y2,{background={term.getBackgroundColor()},text={term.getTextColor()},name={colors.cyan},code={colors.red,colors.lightGray},notification={colors.green},title={colors.orange,align="center"}})
	_G.bbox = bbox
	lUtils.printBox(bbox,0,true,trans)]]
	local al = align or "left"
	local oX = overflowX or "wrap"
	local oY = overflowY or "none"
	local w,h = x2-(x1-1),y2-(y1-1)
	local lines
	local blit = {}
	if trans then
		for y=y1,y2 do
			local bl = term.getLine(y)
			table.insert(blit,{"","",bl[3]:sub(x1,x2)})
		end
	else
		local col = lUtils.toBlit(term.getBackgroundColor())
		for y=y1,y2 do
			table.insert(blit,{"","",string.rep(col,w)})
		end
	end
	if oX == "wrap" then
		lines = lUtils.wordwrap(txt,w)
	else
		lines = {}
		for line in txt:gmatch("([^\n]*)\n?") do
			table.insert(lines,line)
		end
		if txt:sub(#txt) == "\n" then
			table.insert(lines,"")
		end
	end
	local tLines = {{lines[1],ref=1}}
	for t=2,#lines do
		tLines[t] = {lines[t],ref=tLines[t-1].ref+#tLines[t-1][1]}
	end
	local syntaxes = {
		["lua"] = {
			whitespace=colors.white,
			comment=colors.green,
			string=colors.red,
			escape=colors.orange,
			keyword=colors.yellow,
			value=colors.yellow,
			ident=colors.cyan,
			number=colors.purple,
			symbol=colors.orange,
			operator=colors.yellow,
			unidentified=colors.white,
		}
	}
	local sType
	if type(syntax) == "string" then
		sType = syntax
		syntax = {type=sType}
	elseif type(syntax) == "table" then
		sType = syntax.type
	end
	if sType and syntaxes[sType] then
		if sType == "lua" and fs.exists("lex") then
			local lex = dofile("lex")
			local elements = lex.lex(txt)
			local line = 1
			for t=1,#elements do
				while tLines[line+1] and elements[t].posFirst >= tLines[line+1].ref do
					line = line+1
				end
				--term.setCursorPos(x+elements[t].posFirst-tLines[line].ref)
				--elements[t].data = elements[t].data:gsub("\t","    ")
				local col = lUtils.toBlit(syntaxes[sType][elements[t].type] or colors.white)
				blit[line][1] = blit[line][1]..elements[t].data
				blit[line][2] = blit[line][2]..string.rep(col,#elements[t].data)
			end
		end
	end
	_G.debugBlit = blit
	for t=1,#blit do
		local x
		local line = lines[t]
		if align == "center" then
			x = (x1+math.ceil(w/2)-1)-(math.ceil(#blit[t][1]/2)-1)
		elseif align == "right" then
			x = x2-(#blit[t][1]-1)
		else
			x = x1
		end
		if #blit[t][3] > #blit[t][1] then
			blit[t][3] = blit[t][3]:sub(x1-(x-1),x1-(x-1)+(#blit[t][1]-1))
		end
		term.setCursorPos(x,y1+(t-1))
		term.blit(unpack(blit[t]))
	end
end

function lUtils.textbox(txt,x1,y1,x2,y2,trans)
	term.setCursorPos(1,1)
	local bbox = lUtils.makeBox(txt,x1,y1,x2,y2,{background={term.getBackgroundColor()},text={term.getTextColor()},name={colors.cyan},code={colors.red,colors.lightGray},notification={colors.green},title={colors.orange,align="center"}})
	_G.bbox = bbox
	lUtils.printBox(bbox,0,true,trans)
end

function lUtils.makeBox(boxTxt,x1,y1,x2,y2,sTable,buttons)
	-- boxTxt = {{{"Introduction",type="title"}},{{"Welcome to ",type="text"},{"LuaCraft",type="name"},{"!",type="text"}}}
	paragraphs = {}
	if boxTxt == nil then
		return false
	end
	if x1 == nil then
		x1 = 1
	end
	if y1 == nil then
		y1 = 1
	end
	if x2 == nil then
		x2 = getWidth()
	end
	if y2 == nil then
		y2 = getHeight()
	end
	if sTable == nil then
		sTable = {background={colors.white},text={colors.black},name={colors.cyan},code={colors.red,colors.lightGray},notification={colors.green},title={colors.orange,align="center"}}
	end
	if _G.type(boxTxt) == "string" then
		while true do
			par1,par2 = string.find(boxTxt,"\n")
			if par2 ~= nil then
				paragraphs[#paragraphs+1] = {{string.sub(boxTxt,1,par1-1),type="text"}}
				if string.len(boxTxt) > par2 then
					boxTxt = string.sub(boxTxt,par2+1,string.len(boxTxt))
				else
					boxTxt = ""
					break
				end
			else
				paragraphs[#paragraphs+1] = {{boxTxt,type="text"}}
				break
			end
		end
	elseif _G.type(boxTxt) == "table" then
		paragraphs = boxTxt
	end
	lines = {{{},tt=""}}
	cline = 1
	for p=1,#paragraphs do
		for t=1,#paragraphs[p] do
			-- paragraphs[1] = {{"Introduction",type="title"}}
			-- paragraphs[1][1] = {"Introduction",type="title"}
			lines[cline][1][#lines[cline][1]+1] = {"",type=paragraphs[p][t].type}
			for word in string.gmatch(paragraphs[p][t][1],"%S+") do
--				write(word)
				if string.len(lines[cline].tt) == 0 then
					if string.len(lines[cline].tt..word) > x2-(x1-1) then
						-- This means a single word takes up MORE than an ENTIRE line. I'll have to write some proper code for that later.
					end
					-- First word in the line, so obviously no space before the word.
					-- wow old me is so stupid I completely forgot about indentation
					-- HAHAHA dumbass old me messed the fuck up
					local indent = ""
					if string.sub(paragraphs[p][t][1],1,1) == " " then
						indent = string.gmatch(paragraphs[p][t][1],"%s+")()
					end
					lines[cline][1][#lines[cline][1]][1] = lines[cline][1][#lines[cline][1]][1]..indent..word
					lines[cline].tt = lines[cline].tt..indent..word
				else
					if string.len(lines[cline].tt.." "..word) > x2-(x1-1) then
						cline = cline+1
						lines[cline] = {{},tt=""}
						lines[cline][1][#lines[cline][1]+1] = {"",type=paragraphs[p][t].type}
						lines[cline][1][#lines[cline][1]][1] = lines[cline][1][#lines[cline][1]][1]..word
						lines[cline].tt = lines[cline].tt..word
					else
						lines[cline][1][#lines[cline][1]][1] = lines[cline][1][#lines[cline][1]][1].." "..word
						lines[cline].tt = lines[cline].tt.." "..word
					end
				end
			end
			print("")
		end
		cline = cline+1
		lines[cline] = {{},tt=""}
	end
	if buttons == nil then
		buttons = {}
	end
	return {x1=x1,y1=y1,x2=x2,y2=y2,sTable=sTable,lines=lines,buttons=buttons}
end

function lUtils.transWrite(txt,autoTextColor)
	local txtcolor = term.getTextColor()
	local dark = {colors.blue,colors.red,colors.purple,colors.green,colors.black,colors.gray,colors.brown,colors.cyan,colors.magenta}
	local light = {colors.white,colors.orange,colors.lightBlue}
	for k,v in ipairs(dark) do
		dark[v] = true
	end
	for k,v in ipairs(light) do
		light[v] = true
	end
	for t=1,string.len(txt) do
		local col = lUtils.toColor(({lUtils.getPixel(term.current(),term.getCursorPos())})[3])
		term.setBackgroundColor(col)
		if autoTextColor then
			if dark[col] and not light[col] then
				term.setTextColor(colors.white)
			else
				term.setTextColor(colors.black)
			end
		end
		term.write(string.sub(txt,t,t))
	end
	term.setTextColor(txtcolor)
end

function lUtils.printBox(box,scrollpos,simple,trans)
	if type(box) ~= "table" then
		error("Invalid argument #1: Expected table",2)
	end
	someabsolutelyrandomthing = true
	while someabsolutelyrandomthing == true do
		term.setTextColor(colors.black)
		if box.x1 == nil or box.y1 == nil or box.x2 == nil or box.y2 == nil or box.sTable == nil or box.lines == nil then
			return false,"Invalid Box."
		end
		if box.sTable.background ~= nil then
			term.setBackgroundColor(box.sTable.background[1])
		else
			term.setBackgroundColor(colors.white)
		end
		if not trans then
			lOS.boxClear(box.x1,box.y1,box.x2,box.y2)
		end
		ypos = box.y1
		for l=1+scrollpos,box.y2-(box.y1-1)+scrollpos do
			if box.lines[l] ~= nil then
				term.setCursorPos(box.x1,ypos)
				for p=1,#box.lines[l][1] do
					if box.sTable[box.lines[l][1][p].type] ~= nil then
						term.setTextColor(box.sTable[box.lines[l][1][p].type][1])
						if box.sTable[box.lines[l][1][p].type][2] ~= nil then
							term.setBackgroundColor(box.sTable[box.lines[l][1][p].type][2])
						end
						if box.sTable[box.lines[l][1][p].type].align ~= nil then
							if box.sTable[box.lines[l][1][p].type].align == "center" then
								cposx,cposy=term.getCursorPos()
								term.setCursorPos(box.x1+math.floor((box.x2-(box.x1-1))/2)-math.floor(string.len(box.lines[l][1][p][1])/2),cposy)
							end
						end
						lUtils.transWrite(box.lines[l][1][p][1])
					end
				end
			end
			ypos = ypos+1
		end
		if box.buttons ~= nil then
			for b=1,#box.buttons do
				if box.buttons[b].type == "button" or box.buttons[b].type == "editor" then
					term.setBackgroundColor(box.buttons[b].bg)
					term.setTextColor(box.buttons[b].txt)
					for t=1,box.buttons[b].y2-(box.buttons[b].y1-1) do
						blankline = ""
						for bl=1,box.buttons[b].x2-(box.buttons[b].x1-1) do
							blankline = blankline.." "
						end
						if box.buttons[b].y1+t-1 >= 1+scrollpos and box.buttons[b].y1+t-1 <= box.y2-(box.y1-1)+scrollpos then
							term.setCursorPos(box.buttons[b].x1+box.x1-1,(box.buttons[b].y1+t-2)+box.y1-scrollpos)
							term.write(blankline)
							term.setCursorPos(box.buttons[b].x1+box.x1-1,(box.buttons[b].y1+t-2)+box.y1-scrollpos)
							if box.buttons[b].text[t] ~= nil then
								term.write(box.buttons[b].text[t])
							end
						end
					end
				end
				if box.buttons[b].type == "editor" then
					if box.buttons[b].y1 >= 1+scrollpos and box.buttons[b].y2 <= box.y2-(box.y1-1)+scrollpos then
						if drawEditBox ~= nil and box.buttons[b].quit == false then
							term.setCursorPos(1,1)
							term.write(box.buttons[b].spx..","..box.buttons[b].spy.." | "..box.buttons[b].cpx..","..box.buttons[b].cpy)
							drawEditBox(box.buttons[b].box,box.buttons[b].x1+box.x1-1,(box.buttons[b].y1-1)+box.y1-scrollpos,box.buttons[b].spx,box.buttons[b].spy,box.buttons[b].cpx,box.buttons[b].cpy,false)
						end
					end
				end
			end
		end
		if simple == true then
			someabsolutelyrandomthing = false
		else
		event,button,x,y = os.pullEvent()
		if event == "mouse_click" and (x < box.x1 or x > box.x2 or y < box.y1 or y > box.y2) then
			term.setBackgroundColor(colors.white)
			ypos = box.y1
			for l=1+scrollpos,box.y2-(box.y1-1)+scrollpos do
				if box.lines[l] ~= nil then
					term.setCursorPos(box.x1,ypos)
					for p=1,#box.lines[l][1] do
						if box.sTable[box.lines[l][1][p].type] ~= nil then
							term.setTextColor(colors.lightGray)
							if box.sTable[box.lines[l][1][p].type][2] ~= nil then
								term.setBackgroundColor(box.sTable[box.lines[l][1][p].type][2])
							end
							if box.sTable[box.lines[l][1][p].type].align ~= nil then
								if box.sTable[box.lines[l][1][p].type].align == "center" then
									cposx,cposy=term.getCursorPos()
									term.setCursorPos(box.x1+math.floor((box.x2-(box.x1-1))/2)-math.floor(string.len(box.lines[l][1][p][1])/2),cposy)
								end
							end
							term.write(box.lines[l][1][p][1])
						end
					end
				end
				ypos = ypos+1
			end
			if box.buttons ~= nil then
				for b=1,#box.buttons do
					term.setBackgroundColor(box.buttons[b].bg)
					term.setTextColor(colors.lightGray)
					for t=1,box.buttons[b].y2-(box.buttons[b].y1-1) do
						blankline = ""
						for bl=1,box.buttons[b].x2-(box.buttons[b].x1-1) do
							blankline = blankline.." "
						end
						if box.buttons[b].y1+t-1 >= 1+scrollpos and box.buttons[b].y1+t-1 <= box.y2-(box.y1-1)+scrollpos then
							term.setCursorPos(box.buttons[b].x1+box.x1-1,(box.buttons[b].y1+t-2)+box.y1-scrollpos)
							term.write(blankline)
							term.setCursorPos(box.buttons[b].x1+box.x1-1,(box.buttons[b].y1+t-2)+box.y1-scrollpos)
							if box.buttons[b].text[t] ~= nil then
								term.write(box.buttons[b].text[t])
							end
						end
					end
				end
			end
			return scrollpos
		elseif event == "mouse_click" then
			for b=1,#box.buttons do
				-- box.buttons[b].x1+box.x1-1,(box.buttons[b].y1+t-2)+box.y1-scrollpos
				if x >= box.buttons[b].x1+box.x1-1 and x <= box.buttons[b].x2+box.x1-1 and y >= (box.buttons[b].y1-1)+box.y1-scrollpos and y <= (box.buttons[b].y2-1)+box.y1-scrollpos then
					newbox,newbutton,newscrollpos = box.buttons[b].func(box,box.buttons[b],scrollpos)
					if newbox ~= nil then
						box = newbox
					end
					if newbutton ~= nil then
						box.buttons[b] = newbutton
					end
					if newscrollpos ~= nil then
						scrollpos = newscrollpos
					end
				end
			end
		elseif event == "mouse_scroll" then
			if button == -1 and scrollpos > 0 then
				scrollpos = scrollpos-1
			elseif button == 1 then
				scrollpos = scrollpos+1
			end
		end
		end
	end
end

function lUtils.getPixel(win,x,y)
	local w,h = win.getSize()
	if x < 1 or x > w or y < 1 or y > h then
	return "0","0","0"
	else
		theline = {win.getLine(y)}
		return string.sub(theline[1],x,x),string.sub(theline[2],x,x),string.sub(theline[3],x,x)
   end
end

local function setParent(win)
	if not win.reposition then return false end
	local varName,value = debug.getupvalue(win.reposition,5)
	local i = 1
	while varName and varName ~= "parent" do
		varName,value = debug.getupvalue(win.reposition,i)
		i = i+1
	end
	if varName == "parent" then
		win.parent = value
		if win.parent.reposition and not win.parent.parent then
			setParent(win.parent)
		end
	end
end

function lUtils.getWindowPos(win)
	setParent(win)
	local tWin = win
	local x,y = tWin.getPosition()
	while tWin.parent and tWin.parent.getPosition do
		tWin = tWin.parent
		local ox,oy = tWin.getPosition()
		x,y = x+ox-1,y+oy-1
	end
	return x,y
end

function lUtils.openWin(title,filepath,x,y,width,height,canresize,canmaximize)
	if canmaximize == nil then
		canmaximize = true
	end
	to_colors, to_blit = {}, {}
	for i = 1, 16 do
		to_blit[2^(i-1)] = ("0123456789abcdef"):sub(i, i)
		to_colors[("0123456789abcdef"):sub(i, i)] = 2^(i-1)
	end
	local OGterm = term.current()
	local OGwin = {lines={}}
	local OGtxt = term.getTextColor()
	local OGbg = term.getBackgroundColor()
	local w,h = term.getSize()
	for t=1,h do
		OGwin.lines[t] = {OGterm.getLine(t)}
	end
	local function getPixel(win,x,y)
		theline = {win.getLine(y)}
		return string.sub(theline[1],x,x),string.sub(theline[2],x,x),string.sub(theline[3],x,x)
	end
	function OGwin.render()
		for l=1,#OGwin.lines do
			term.setCursorPos(1,l)
			term.blit(table.unpack(OGwin.lines[l]))
		end
	end
	local progWin = window.create(term.current(),x+1,y+1,width-2,height-2)
	dragging = false
	dragX = false
	dragY = false
	dragSide = 1
	local function redrawbox()
		term.setBackgroundColor(colors.gray)
		term.setCursorPos(x,y)
		for t=1,width-6 do
			term.write(" ")
		end
		term.setTextColor(colors.white)
		if canmaximize == false then
			term.write("    × ")
		else
			term.write(" +  × ")
		end
		term.setCursorPos(x+1,y)
		term.write(title)
		term.setTextColor(colors.gray)
		for i=1,height-2 do
			term.setCursorPos(x,y+i)
			term.blit(string.char(149),to_blit[colors.gray],({getPixel(progWin,1,i)})[3])
			term.blit(progWin.getLine(i))
			term.blit(string.char(149),({getPixel(progWin,width-2,i)})[3],to_blit[colors.gray])
		end
		local bottomline = {string.char(138),({getPixel(progWin,1,height-2)})[3],to_blit[colors.gray]}
		for i=1,width-2 do
			bottomline[1] = bottomline[1]..string.char(143)
			bottomline[2] = bottomline[2]..({getPixel(progWin,i,height-2)})[3]
			bottomline[3] = bottomline[3]..to_blit[colors.gray]
		end
		bottomline[1] = bottomline[1]..string.char(133)
		bottomline[2] = bottomline[2]..({getPixel(progWin,width-2,height-2)})[3]
		bottomline[3] = bottomline[3]..to_blit[colors.gray]
		term.setCursorPos(x,y+(height-1))
		term.blit(table.unpack(bottomline))
	end
	local endresult = {}
	local function regevents()
		local progCor
		if type(filepath) == "string" then
			local tPath = filepath
			local rPath = string.sub(filepath,string.find(filepath,"%S+"))
			local b,e = string.find(filepath,"%S+")
			local tPath = string.sub(filepath,e+2,string.len(filepath))
			local thingy = {}
			for i in string.gmatch(tPath,"%S+") do
				if i ~= nil and i ~= "" then
					thingy[#thingy+1] = i
				end
			end
			progCor = coroutine.create(function() endresult = {lOS.run(rPath,table.unpack(thingy))} end)
		elseif type(filepath) == "function" then
			progCor = coroutine.create(function() endresult = {filepath()} end)
		else
			error("Invalid filepath",3)
		end
		local stop = false
		e = {}
		while stop == false do
			if coroutine.status(progCor) == "dead" then return end
			term.redirect(progWin)
			progWin.redraw()
			progWin.restoreCursor()
			if (e[1] == "mouse_click" or e[1] == "mouse_drag" or e[1] == "mouse_up" or e[1] == "mouse_scroll" or e[1] == "mouse_move") and lOS.wins[lOS.cWin].events ~= "all" then
				e[3] = e[3]-x
				e[4] = e[4]-y
				if e[3] >= 1 and e[3] <= width-2 and e[4] >= 1 and e[4] <= height-2 then
					coroutine.resume(progCor,table.unpack(e))
				end
			else
				coroutine.resume(progCor,table.unpack(e))
			end
			term.redirect(OGterm)
			redrawbox()
			term.setTextColor(progWin.getTextColor())
			term.setCursorPos(({progWin.getPosition()})[1]+({progWin.getCursorPos()})[1]-1,({progWin.getPosition()})[2]+({progWin.getCursorPos()})[2]-1)
			term.setCursorBlink(progWin.getCursorBlink())
			e = {os.pullEvent()}
			if e[1] == "mouse_drag" then
				if dragX == true then
					if dragSide == 1 then
						width = OGw+(DRx-e[3])
						x = OGx-(DRx-e[3])
					else
						width = OGw-(DRx-e[3])
					end
				end
				if dragY == true then
					height = OGh-(DRy-e[4])
				end
				progWin.reposition(x+1,y+1,width-2,height-2)
				progWin.setVisible(false)
				term.redirect(progWin)
				progWin.redraw()
				progWin.restoreCursor()
				coroutine.resume(progCor,"term_resize")
				term.redirect(OGterm)
				progWin.setVisible(true)
				OGwin.render()
				redrawbox()
			end
			if e[1] == "mouse_drag" and dragging == true then
				x = OGx-(DRx-e[3])
				y = OGy-(DRy-e[4])
				progWin.reposition(x+1,y+1)
				progWin.setVisible(false)
				term.redirect(progWin)
				progWin.redraw()
				progWin.restoreCursor()
				coroutine.resume(progCor,"term_resize")
				term.redirect(OGterm)
				progWin.setVisible(true)
				OGwin.render()
				redrawbox()
			elseif e[1] == "mouse_click" or e[1] == "mouse_up" then
				if e[1] == "mouse_up" then
					dragging = false
					dragX = false
					dragY = false
					OGx,OGy = x,y
					OGw,OGh = width,height
					DRx,DRy = nil,nil
				end
				if e[4] == y and e[3] >= x+(width-3) and e[3] <= x+(width-1) then
					if e[1] == "mouse_click" then
						term.setTextColor(colors.white)
						term.setBackgroundColor(colors.red)
						term.setCursorPos(x+(width-3),y)
						term.write(" × ")
					elseif e[1] == "mouse_up" then
						stop = true
						return false
					end
				elseif e[4] == y and e[3] >= x+(width-6) and e[3] <= x+(width-4) and canmaximize ~= false then
					if e[1] == "mouse_click" then
						term.setTextColor(colors.white)
						term.setBackgroundColor(colors.lightGray)
						term.setCursorPos(x+(width-6),y)
						term.write(" + ")
					elseif e[1] == "mouse_up" then
						--lOS.oWins[#lOS.oWins+1] = {window.create(oldterm,1,2,({oldterm.getSize()})[1],({oldterm.getSize()})[2]-3),progCor,fullscreen=false,minimized=false,filepath="'..rPath..'",icon={string.sub(fs.getName(rPath),1,3),string.sub(fs.getName(rPath),4,6)},ran=false}
						--os.startTimer(0.1)
						--return true
					end
				elseif e[4] == y and e[1] == "mouse_click" then
					dragging = true
					DRx,DRy = e[3],e[4]
					OGx,OGy = x,y
				end
				if e[4] > y and e[4] <= y+(height-1) and (e[3] == x or e[3] == x+(width-1)) and e[1] == "mouse_click" and canresize then
					DRx = e[3]
					OGw = width
					OGx = x
					dragX = true
					if e[3] == x then
						dragSide = 1
					else
						dragSide = 2
					end
				end
				if e[4] == y+(height-1) and e[3] >= x and e[3] <= x+(width-1) and e[1] == "mouse_click" and canresize then
					dragY = true
					DRy = e[4]
					OGh = height
				end
			end
		end
	end
	regevents()
	OGwin.render()
	term.setBackgroundColor(OGbg)
	term.setTextColor(OGtxt)
	return table.unpack(endresult)
end



function lUtils.popup(title,msg,width,height,buttons,redrawscreen,colorScheme)
	colorScheme = colorScheme or {}
	local tbtxt = colorScheme.topbarText or colors.white
	local tbtxt2 = colorScheme.topbarTextHighlight or colors.black
	local tbbg = colorScheme.topbarFill or colors.gray
	local tbbg2 = colorScheme.topbarFillHighlight or colors.white
	local txtcol = colorScheme.text or colors.blue
	local bg = colorScheme.fill or colors.white
	local fg = colorScheme.border or colors.lightGray
	local btnbg = colorScheme.buttonFill or colors.white
	local btnbg2 = colorScheme.buttonFillHighlight or colors.lightBlue
	local btntxt = colorScheme.buttonText or colors.black
	local btntxt2 = colorScheme.buttonTextHighlight or colors.white

	buttons = buttons or {"Continue"}

	local oblink = term.getCursorBlink()
	term.setCursorBlink(false)
	if msg == nil then
		error("No text given",2)
	end
	local OGterm = term.current()
	local OGwin = {lines={}}
	local w,h = term.getSize()
	for t=1,h do
		OGwin.lines[t] = {OGterm.getLine(t)}
	end
	function OGwin.render()
		for l=1,#OGwin.lines do
			term.setCursorPos(1,l)
			term.blit(table.unpack(OGwin.lines[l]))
		end
	end
	local dragging = false
	--[[if width == nil or height == nil then
		width = 21
		height = 7
	end]]
	if not height and not width then
		width = 21
	end

	if height and not width then
		width = 21
		local lines = lUtils.wordwrap(msg, width-2)
		while #lines > height-6 do
			width = width+2
			lines = lUtils.wordwrap(msg, width-2)
		end
	elseif width and not height then
		local lines = lUtils.wordwrap(msg, width-2)
		height = #lines+6
	end

	if height < 6 then height = 6 end
	local popupline = string.rep(" ",width)

	local lines = lUtils.wordwrap(msg, width-2)

	local write = term.write
	local function redrawbox()
		term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
		term.setBackgroundColor(tbbg)
		term.setTextColor(tbtxt)
		write(popupline)
		term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
		write(" "..title)
		term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-3,math.ceil(h/2)-math.floor(height/2))
		write(" × ")
		-- The line below is unreadable now but it just makes the text box for the popup message and then prints it
		term.setBackgroundColor(bg)
		term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+1,math.ceil(h/2)-math.floor(height/2)+1)
		term.write(string.rep(" ", width-2))
		term.setTextColor(txtcol)
		for y=1, height-4 do
			local line = ""
			if lines[y] then
				line = lines[y]
			end
			line = line..string.rep(" ", width-2 - #line)
			term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+1,math.ceil(h/2)-math.floor(height/2)+1+y)
			term.write(line)
		end

		for t=1,height-4 do
			term.setBackgroundColor(bg)
			term.setTextColor(fg)
			term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+t)
			write(string.char(149))
			term.setBackgroundColor(fg)
			term.setTextColor(bg)
			term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-1,math.ceil(h/2)-math.floor(height/2)+t)
			write(string.char(149))
		end
		term.setBackgroundColor(fg)
		term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1-2)
		write(popupline)
		term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1-1)
		write(popupline)
		term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1)
		write(popupline)
		term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1-1)
		write(popupline)
		term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1-1)
		term.setTextColor(btntxt)
		for b=1,#buttons do
			term.setBackgroundColor(fg)
			write(" ")
			term.setBackgroundColor(btnbg)
			write(" "..buttons[b].." ")
		end
	end
	redrawbox()
	while true do
		event,button,x,y = os.pullEvent()
		if event == "mouse_drag" and dragging == true then
			w = OGw-(OGx-x)*2
			h = OGh-(OGy-y)*2
			OGwin.render()
			redrawbox()
		elseif event == "mouse_click" or event == "mouse_up" then
			if event == "mouse_up" and dragging == true then
				dragging = false
			end
			if y == math.ceil(h/2)-math.floor(height/2)+height-2 then
				curX = math.ceil(w/2)-math.floor(width/2)
				for b=1,#buttons do
					if x >= curX+1 and x <= curX+string.len(" "..buttons[b].." ") then
						if event == "mouse_up" or event == "monitor_touch" then
							if redrawscreen ~= nil and redrawscreen == true then
								OGwin.render()
							end
							term.setCursorBlink(oblink)
							return true,b,buttons[b]
						elseif event == "mouse_click" then
							term.setCursorPos(curX+1,math.ceil(h/2)-math.floor(height/2)+height-2)
							term.setBackgroundColor(btnbg2)
							term.setTextColor(btntxt2)
							write(" "..buttons[b].." ")
						end
					end
					curX = curX+string.len(" "..buttons[b].." ")+1
				end
			elseif y == math.ceil(h/2)-math.floor(height/2) and x >= math.ceil(w/2)-math.floor(width/2)+width-3 and x <= math.ceil(w/2)-math.floor(width/2)+width-1 then
				if event == "mouse_click" then
					term.setTextColor(colors.white)
					term.setBackgroundColor(colors.red)
					term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-3,math.ceil(h/2)-math.floor(height/2))
					write(" × ")
				elseif event == "mouse_up" then
					if redrawscreen ~= nil and redrawscreen == true then
						OGwin.render()
					end
					term.setCursorBlink(oblink)
					return false,0,""
				end
			elseif y == math.ceil(h/2)-math.floor(height/2) and event == "mouse_click" then
				dragging = true
				OGx,OGy = x,y
				OGw = w
				OGh = h
			end
			if (x < math.ceil(w/2)-math.floor(width/2) or x > math.ceil(w/2)-math.floor(width/2)+width-1 or y < math.ceil(h/2)-math.floor(height/2) or y > math.ceil(h/2)-math.floor(height/2)+height-1) and event == "mouse_click" then
				for t=1,5 do
					term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
					term.setBackgroundColor(tbbg2)
					term.setTextColor(tbtxt2)
					write(popupline)
					term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
					write(" "..title)
					term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-3,math.ceil(h/2)-math.floor(height/2))
					write(" × ")
					os.sleep(0.1)
					term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
					term.setBackgroundColor(tbbg)
					term.setTextColor(tbtxt)
					write(popupline)
					term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
					write(" "..title)
					term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-3,math.ceil(h/2)-math.floor(height/2))
					write(" × ")
					os.sleep(0.1)
				end
			end
		end
	end
end

function lUtils.inputbox(title,msg,width,height,buttons)
	local OGterm = term.current()
	local OGwin = {lines={}}
	local w,h = term.getSize()
	for t=1,h do
		OGwin.lines[t] = {OGterm.getLine(t)}
	end
	function OGwin.render()
		for l=1,#OGwin.lines do
			term.setCursorPos(1,l)
			term.blit(table.unpack(OGwin.lines[l]))
		end
	end
	dragging = false
	local input = lUtils.makeEditBox("input",width-4,1)
	if width == nil or height == nil then
		width = 21
		height = 7
	end
	if height < 6 then height = 6 end
	popupline = ""
	for pl=1,width do
		popupline = popupline.." "
	end
	local function redrawbox()
	ocursorx,ocursory = term.getCursorPos()
	otext = term.getTextColor()
	term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
	term.setBackgroundColor(colors.gray)
	term.setTextColor(colors.white)
	write(popupline)
	term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
	write(" "..title)
	term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-3,math.ceil(h/2)-math.floor(height/2))
	write(" × ")
	-- The line below is unreadable now but it just makes the text box for the popup message and then prints it
	term.setBackgroundColor(colors.white)
	term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+1,math.ceil(h/2)-math.floor(height/2)+1)
	term.write(string.sub(popupline,2,string.len(popupline)-1))
	term.setTextColor(colors.black)
	term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+1)
	lUtils.printBox(lUtils.makeBox(msg,(math.ceil(w/2)-math.floor(width/2))+1,math.ceil(h/2)-math.floor(height/2)+2,(math.ceil(w/2)-math.floor(width/2)+width-1)-1,math.ceil(h/2)-math.floor(height/2)+height-1-3-3,{background={colors.white},text={colors.blue}}),0,true)
	for t=1,height-4 do
		term.setBackgroundColor(colors.white)
		term.setTextColor(colors.lightGray)
		term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+t)
		write(string.char(149))
		term.setBackgroundColor(colors.lightGray)
		term.setTextColor(colors.white)
		term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-1,math.ceil(h/2)-math.floor(height/2)+t)
		write(string.char(149))
	end
	term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+1,math.ceil(h/2)-math.floor(height/2)+height-1-3-2)
	term.setBackgroundColor(colors.lightGray)
	term.setTextColor(colors.white)
	write("\159")
	for t=1,width-4 do
		write("\143")
	end
	term.setBackgroundColor(colors.white)
	term.setTextColor(colors.lightGray)
	write("\144")
	term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+1,math.ceil(h/2)-math.floor(height/2)+height-1-3-1)
	term.setBackgroundColor(colors.lightGray)
	term.setTextColor(colors.white)
	write("\149")
	term.setTextColor(colors.lightGray)
	term.setBackgroundColor(colors.white)
	term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+1+width-3,math.ceil(h/2)-math.floor(height/2)+height-1-3-1)
	write("\149")
	term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+1,math.ceil(h/2)-math.floor(height/2)+height-1-3)
	write("\130")
	for t=1,width-4 do
		write("\131")
	end
	write("\129")
	term.setBackgroundColor(colors.lightGray)
	term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1-2)
	write(popupline)
	term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1-1)
	write(popupline)
	term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1)
	write(popupline)
	term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1-1)
	write(popupline)
	term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2)+height-1-1)
	term.setTextColor(colors.black)
	for b=1,#buttons do
		term.setBackgroundColor(colors.lightGray)
		write(" ")
		term.setBackgroundColor(colors.white)
		write(" "..buttons[b].." ")
	end
	term.setTextColor(otext)
	term.setCursorPos(ocursorx,ocursory)
	end
	redrawbox()
	local function regevents()
	while true do
		event,button,x,y = os.pullEvent()
		if event == "mouse_drag" and dragging == true then
			w = OGw-(OGx-x)*2
			h = OGh-(OGy-y)*2
			OGwin.render()
			redrawbox()
		elseif event == "mouse_click" or event == "mouse_up" then
			if event == "mouse_up" and dragging == true then
				dragging = false
			end
			if y == math.ceil(h/2)-math.floor(height/2)+height-2 then
				curX = math.ceil(w/2)-math.floor(width/2)
				for b=1,#buttons do
					if x >= curX+1 and x <= curX+string.len(" "..buttons[b].." ") then
						if event == "mouse_up" or event == "monitor_touch" then
							return true,b,buttons[b]
						elseif event == "mouse_click" then
							ocursorx,ocursory = term.getCursorPos()
							otext = term.getTextColor()
							term.setCursorPos(curX+1,math.ceil(h/2)-math.floor(height/2)+height-2)
							term.setBackgroundColor(colors.lightBlue)
							term.setTextColor(colors.black)
							write(" "..buttons[b].." ")
							term.setCursorPos(ocursorx,ocursory)
							term.setTextColor(otext)
						end
					end
					curX = curX+string.len(" "..buttons[b].." ")+1
				end
			elseif y == math.ceil(h/2)-math.floor(height/2) and x >= math.ceil(w/2)-math.floor(width/2)+width-3 and x <= math.ceil(w/2)-math.floor(width/2)+width-1 then
				if event == "mouse_click" then
					term.setTextColor(colors.white)
					term.setBackgroundColor(colors.red)
					term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-3,math.ceil(h/2)-math.floor(height/2))
					write(" × ")
				elseif event == "mouse_up" then
					return false,0,""
				end
			elseif y == math.ceil(h/2)-math.floor(height/2) then
				dragging = true
				OGx,OGy = x,y
				OGw = w
				OGh = h
			else
				redrawbox()
			end
			if (x < math.ceil(w/2)-math.floor(width/2) or x > math.ceil(w/2)-math.floor(width/2)+width-1 or y < math.ceil(h/2)-math.floor(height/2) or y > math.ceil(h/2)-math.floor(height/2)+height-1) and event == "mouse_click" then
				for t=1,5 do
					term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
					term.setBackgroundColor(colors.white)
					term.setTextColor(colors.black)
					write(popupline)
					term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
					write(" "..title)
					term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-3,math.ceil(h/2)-math.floor(height/2))
					write(" × ")
					os.sleep(0.1)
					term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
					term.setBackgroundColor(colors.gray)
					term.setTextColor(colors.white)
					write(popupline)
					term.setCursorPos(math.ceil(w/2)-math.floor(width/2),math.ceil(h/2)-math.floor(height/2))
					write(" "..title)
					term.setCursorPos(math.ceil(w/2)-math.floor(width/2)+width-3,math.ceil(h/2)-math.floor(height/2))
					write(" × ")
					os.sleep(0.1)
				end
			end
		end
	end
	end
	function readbox()
		c = {"mouse_click",1,math.ceil(w/2)-math.floor(width/2)+2,math.ceil(h/2)-math.floor(height/2)+height-1-3-1}
		while true do
			if c[1] == "mouse_click" and c[3] >= math.ceil(w/2)-math.floor(width/2)+2 and c[3] <= math.ceil(w/2)-math.floor(width/2)+2+(input.width-1) and c[4] == math.ceil(h/2)-math.floor(height/2)+height-1-3-1 then
				if input.lines[1] ~= nil then
					lUtils.drawEditBox(input,math.ceil(w/2)-math.floor(width/2)+2,math.ceil(h/2)-math.floor(height/2)+height-1-3-1,0,0,string.len(input.lines[1])+1,1,true,false)
				else
					lUtils.drawEditBox(input,math.ceil(w/2)-math.floor(width/2)+2,math.ceil(h/2)-math.floor(height/2)+height-1-3-1,0,0,1,1,true,false)
				end
			end
			c = {os.pullEvent("mouse_click")}
		end
	end
	local returnthis = {}
	parallel.waitForAny(readbox,function() returnthis = {regevents()} end)
	term.setCursorBlink(false)
	return input.lines[1],table.unpack(returnthis)
end

function lUtils.createButton(x,y,w,h,lines,func,colrs)
	local btn = {x1=x,y1=y,x2=x+(w-1),y2=y+(h-1),func=func,colors=colrs,selected=false}
	if not btn.colors then
		btn.colors = {}
	end
	local c = btn.colors
	if not c.txt then
		c.txt = colors.white
	end
	if not c.fg then
		c.fg = c.bg or colors.lightGray
	end
	if not c.bg then
		c.bg = colors.gray
	end
	if not c.clicked then
		c.clicked = {}
	end
	local cc = c.clicked
	if not cc.fg then
		cc.fg = cc.bg or colors.lightGray
	end
	if not cc.bg then
		cc.bg = c.bg2 or colors.lightGray
	end
	if not cc.txt then
		cc.txt = c.txt2 or colors.white
	end
	function btn.render()
		local ofg,obg = term.getTextColor(),term.getBackgroundColor()
		if btn.selected then
			term.setBackgroundColor(btn.colors.clicked.bg)
			term.setTextColor(btn.colors.clicked.fg)
		else
			term.setBackgroundColor(btn.colors.bg)
			term.setTextColor(btn.colors.fg)
		end
		lUtils.border(btn.x1,btn.y1,btn.x2,btn.y2,"fill")
		if type(lines) == "string" then
			if btn.selected then
				term.setTextColor(btn.colors.clicked.txt)
			else
				term.setTextColor(btn.colors.txt)
			end
			if btn.y1 == btn.y2 then
				term.setCursorPos(btn.x1,btn.y1)
			else
				term.setCursorPos(btn.x1+1,btn.y1+1)
			end
			term.write(lines)
		end
		term.setTextColor(ofg)
		term.setBackgroundColor(obg)
	end
	function btn.update(...)
		local e = {...}
		if (e[1] == "mouse_click" or e[1] == "mouse_up") and e[2] == 1 then
			if e[3] >= btn.x1 and e[4] >= btn.y1 and e[3] <= btn.x2 and e[4] <= btn.y2 then
				if e[1] == "mouse_click" then
					btn.selected = true
					btn.render()
				elseif e[1] == "mouse_up" and btn.selected then
					btn.selected = false
					btn.render()
					return btn.func()
				end
			end
		end
		if e[1] == "mouse_up" then
			btn.selected = false
		end
	end
	return btn
end
function lUtils.createCanvas(x,y,w,h)
	cvs = {}
	if not (x or y) then
		cvs.x1,cvs.y1 = 1,1
	else
		cvs.x1,cvs.y1 = x,y
	end
	if not (w or h) then
		cvs.x2,cvs.y2 = term.getSize()
	else
		cvs.x2,cvs.y2 = x+(w-1),y+(h-1)
	end
	cvs.objs = {}
	function cvs.clear()
		cvs.objs = {}
	end
	function cvs.update(...)
		for o=1,#cvs.objs do
			cvs.objs[o].update(...)
		end
	end
	function cvs.render()
		for o=1,#cvs.objs do
			cvs.objs[o].render()
		end
	end
	function cvs.createButton(...)
		local b = lUtils.createButton(...)
		if b then
			cvs.objs[#cvs.objs+1] = b
			return b
		else
			return false
		end
	end
	return cvs
end
function lUtils.contextmenu(x,y,width,options,colorScheme,dividers)
	-- elements are either string OR table, table contains txt and optionally: disabled (bool), action (function or table (for nested context menu)), color (number)
	local mColors = colorScheme or {}
	mColors.fg = mColors.fg or colors.lightGray
	mColors.bg = mColors.bg or colors.gray
	mColors.txt = mColors.txt or colors.white
	mColors.disabled = mColors.disabled or colors.lightGray
	mColors.divider = mColors.divider or mColors.fg
	mColors.selected = mColors.selected or mColors.select or mColors.fg
	local w = 0
	if not width then
		width = "auto"
	end
	if type(width) == "number" then
		w = width
	end
	local function genContextMenu(opt,x,y,w)
		local h = 2
		local objects = {}
		local cY = 1
		local function addObject(obj,parent)
			obj.y = cY
			obj.parent = opt
			obj.parentObj = parent
			table.insert(objects,obj)
			cY = cY+1
			h = h+1
			if not obj.action then obj.action = "output" end
			if obj.txt then
				local nW
				if type(obj.txt) == "table" then
					nW = #obj.txt[1]+2
				else
					nW = #obj.txt+2
				end
				if type(obj.action) == "table" then
					nW = nW+2
				end
				if nW > w then
					w = nW
				end
			end
		end
		local acceptable = {["string"]=true,["table"]=true}
		local lID = 1
		for i,o in ipairs(opt) do
			if i > 1 and dividers then
				addObject({action="divider"})
			end
			if type(o) == "string" then
				addObject({action="output",txt=o,id=lID})
				lID = lID+1
			elseif type(o) == "table" and acceptable[type(o.txt)] then
				addObject(o)
				o.id = lID
				lID = lID+1
			elseif type(o) == "table" then
				for k,v in ipairs(o) do
					local lID2 = 1
					if type(v) == "string" then
						addObject({action="output",txt=v,id=lID2},o)
						lID2 = lID2+1
					elseif type(v) == "table" and acceptable[type(v.txt)] then
						addObject(v,o)
						v.id = lID2
						lID2 = lID2+1
					end
				end
				o.id = lID
				lID = lID+1
				o.parent = opt
			end
		end
		objects.x,objects.y,objects.w,objects.h = x,y,w,h
		objects.x1,objects.y1 = x,y
		objects.x2,objects.y2 = x+w-1,y+h-1
		objects.rObjs = {}
		objects.scroll = 0
		return objects
	end
	local objects,h
	objects = genContextMenu(options,x,y,w)
	objects.status = "idle"
	local function positionMenu(menu)
		local tW,tH = term.getSize()
		if menu.y+menu.h-1 > tH then
			if menu.y-menu.h+1 >= 1 then
				menu.y = menu.y-menu.h+1
			elseif menu.h <= tH then
				while menu.y+menu.h-1 > tH do
					menu.y = menu.y-1
				end
			else
				menu.y = 1
			end
		end
		-- repeat for X axis
		if menu.x+menu.w-1 > tW then
			if menu.x-menu.w+1 >= 1 then
				menu.x = menu.x-menu.w+1
			elseif menu.w <= tW then
				while menu.x+menu.w-1 > tW do
					menu.x = menu.x-1
				end
			else
				menu.x = 1
				-- turn on scroll
			end
		end
	end
	positionMenu(objects)
	local menus = {objects}
	local function renderMenu(menu)
		local tW,tH = term.getSize()
		while menu.y+menu.h-1 > tH do
			menu.h = menu.h-1
		end
		menu.x1,menu.y1 = menu.x,menu.y
		menu.x2,menu.y2 = menu.x+menu.w-1,menu.y+menu.h-1
		menu.bg = menu.bg or mColors.bg
		menu.fg = menu.fg or mColors.fg
		menu.txt = menu.txt or mColors.txt
		menu.divider = menu.divider or mColors.divider
		menu.disabled = menu.disabled or mColors.disabled
		menu.selColor = menu.selColor or mColors.selected
		dividerline = string.rep("\140",menu.w-2)
		term.setBackgroundColor(menu.bg)
		term.setTextColor(menu.fg)
		lUtils.border(menu.x,menu.y,menu.x+menu.w-1,menu.y+menu.h-1,"fill")
		for k=1+menu.scroll,(menu.h-2)+menu.scroll do
			local v = menu[k]
			term.setCursorPos(menu.x+1,menu.y+v.y-menu.scroll)
			if menu.scroll > 0 and k == 1+menu.scroll then
				term.setBackgroundColor(menu.bg)
				term.setTextColor(menu.txt)
				term.setCursorPos(menu.x+math.ceil(menu.w/2)-1,menu.y+1)
				term.write("\30")
			elseif k == (menu.h-2)+menu.scroll and k < #menu then
				term.setBackgroundColor(menu.bg)
				term.setTextColor(menu.txt)
				term.setCursorPos(menu.x+math.ceil(menu.w/2)-1,menu.y2-1)
				term.write("\31")
			else
				if menu.selected == v then
					term.setBackgroundColor(menu.selColor)
				else
					term.setBackgroundColor(v.bg or menu.bg)
				end
				if v.action == "divider" then
					term.setTextColor(menu.divider)
					term.write(dividerline)
				else
					if v.disabled then
						term.setTextColor(v.color or menu.disabled)
					else
						term.setTextColor(v.color or menu.txt)
					end
					if type(v.txt) == "table" then
						if not v.txt[2] then v.txt[2] = "" end
						if not v.txt[3] then v.txt[3] = "" end
						local bl = {
							v.txt[1]..string.rep(" ",(menu.w-2)-#v.txt[1]),
							v.txt[2]..string.rep(lUtils.toBlit(term.getTextColor()),(menu.w-2)-#v.txt[2]),
							v.txt[3]..string.rep(lUtils.toBlit(term.getBackgroundColor()),(menu.w-2)-#v.txt[3]),
						}
						term.blit(unpack(bl))
					else
						term.write(v.txt..string.rep(" ",(menu.w-2)-#v.txt))
					end
					if type(v.action) == "table" then
						term.setCursorPos(menu.x+menu.w-2,menu.y+v.y-menu.scroll)
						term.write(">")
					end
				end
			end
		end
	end
	function objects.render()
		for m=1,#menus do
			renderMenu(menus[m])
		end
	end
	function objects.update(...)
		if objects.status == "dead" then return false end
		objects.status = "running"
		local e = {...}
		if e[1]:find("mouse") and e[3] and e[4] then
			for m=#menus,1,-1 do
				local menu = menus[m]
				local oselected = menu.selected
				if e[1] == "mouse_click" or e[1] == "mouse_move" then
					menu.selected = nil
				end
				if not lUtils.isInside(e[3],e[4],menu) then
					if e[1] == "mouse_click" then
						table.remove(menus,m)
						if m == 1 then
							objects.status = "dead"
							return
						end
					end
				else
					if e[1] == "mouse_scroll" then
						if e[2] == -1 and menu.scroll > 0 then
							menu.scroll = menu.scroll-1
						elseif e[2] == 1 and (menu.h-2)+menu.scroll < #menu then
							menu.scroll = menu.scroll+1
						end
					elseif e[3] > menu.x and e[3] < menu.x+menu.w-1 then
						if e[4] == menu.y+1 and menu.scroll > 0 then
							if e[1] == "mouse_click" then
								menu.scroll = menu.scroll-1
							end
						elseif e[4] == menu.y2-1 and (menu.h-2)+menu.scroll < #menu then
							if e[1] == "mouse_click" then
								menu.scroll = menu.scroll+1
							end
						else
							for i=1+menu.scroll,(menu.h-2)+menu.scroll do
								local o = menu[i]
								if e[4] == o.y+menu.y-menu.scroll then
									if o.action == "divider" or o.disabled then
										break
									end
									-- check action
									if e[1] == "mouse_up" and menu.selected == o then
										if type(o.action) == "function" then
											o.action(o)
											objects.clicked = o
											objects.status = "dead"
											return o
										elseif type(o.action) == "table" then
											local submenu = genContextMenu(o.action,menu.x+menu.w-1,menu.y+o.y-menu.scroll-1,o.action.w or 0)
											o.action.parent = o
											local tW,tH = term.getSize()
											if submenu.y+submenu.h-1 > tH and (submenu.y+2)-submenu.h+1 >= 1 then
												submenu.y = (submenu.y+2)-submenu.h+1
											else
												positionMenu(submenu)
											end
											table.insert(menus,submenu)
										elseif type(o.action) == "string" then
											-- idk what to return
											objects.clicked = o
											objects.status = "dead"
											return o
										end
									elseif e[1] == "mouse_up" then
										menu.selected = nil
									elseif e[1] == "mouse_click" or e[1] == "mouse_move" then
										menu.selected = o
										if e[1] == "mouse_click" then
											if menu.oselected == menu.selected then
												menu.selected = nil
												menu.oselected = nil
											else
												menu.oselected = o
											end
										end
									end
									break
								end
							end
						end
					end
					break
				end
			end
		end
		objects.status = "idle"
	end
	function objects.run()
		objects.render()
		while objects.status ~= "dead" do
			objects.update(os.pullEvent())
			objects.render()
		end
		return objects.clicked
	end
	return objects
end

function lUtils.clickmenu(x,y,w,options,redrawscreen,disabled,preferredColors)
	local function disable(opt)
		if opt.txt and disabled[opt.txt] then
			opt.disabled = true
		end
		for o=1,#opt do
			if type(opt[o]) == "table" then
				disable(opt[o])
			elseif type(opt[o]) == "string" and disabled[opt[o]] then
				opt[o] = {txt=opt[o],action="output",disabled=true}
			end
		end
	end
	if disabled then
		disable(options)
	end
	local menu = lUtils.contextmenu(x,y,w,options,preferredColors,true)
	local clicked = menu.run()
	if not clicked then
		return false,0
	else
		if clicked.parentObj then
			return true,clicked.parentObj.id,clicked.txt,clicked.id
		else
			return true,clicked.id,clicked.txt
		end
	end
end

function lUtils.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