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