From 4475d80db9a8eb0db0b7bb88cf2d76794073f46c Mon Sep 17 00:00:00 2001 From: Andrew Lee Date: Mon, 16 Nov 2020 15:31:33 -0500 Subject: Removed unnecessary stuff in programs --- Programs/LuaIDE.bup/Contents/Info.meta | 1 - .../Contents/Resources/resources_here.txt | 1 - Programs/LuaIDE.bup/Contents/bits-UI/luaide.lua | 2224 -------------------- Programs/LuaIDE.bup/Info.meta | 1 + Programs/LuaIDE.bup/luaide.lua | 2224 ++++++++++++++++++++ 5 files changed, 2225 insertions(+), 2226 deletions(-) delete mode 100644 Programs/LuaIDE.bup/Contents/Info.meta delete mode 100644 Programs/LuaIDE.bup/Contents/Resources/resources_here.txt delete mode 100644 Programs/LuaIDE.bup/Contents/bits-UI/luaide.lua create mode 100644 Programs/LuaIDE.bup/Info.meta create mode 100644 Programs/LuaIDE.bup/luaide.lua (limited to 'Programs/LuaIDE.bup') diff --git a/Programs/LuaIDE.bup/Contents/Info.meta b/Programs/LuaIDE.bup/Contents/Info.meta deleted file mode 100644 index 8d1c8b6..0000000 --- a/Programs/LuaIDE.bup/Contents/Info.meta +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Programs/LuaIDE.bup/Contents/Resources/resources_here.txt b/Programs/LuaIDE.bup/Contents/Resources/resources_here.txt deleted file mode 100644 index 8d1c8b6..0000000 --- a/Programs/LuaIDE.bup/Contents/Resources/resources_here.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Programs/LuaIDE.bup/Contents/bits-UI/luaide.lua b/Programs/LuaIDE.bup/Contents/bits-UI/luaide.lua deleted file mode 100644 index 5e06e6b..0000000 --- a/Programs/LuaIDE.bup/Contents/bits-UI/luaide.lua +++ /dev/null @@ -1,2224 +0,0 @@ - --- --- Lua IDE --- Made by GravityScore --- - - - - --- Variables - -local version = "1.1" -local arguments = {...} - - -local w, h = term.getSize() -local tabWidth = 2 - - -local autosaveInterval = 20 -local allowEditorEvent = true -local keyboardShortcutTimeout = 0.4 - - -local clipboard = nil - - -local theme = { - background = colors.gray, - titleBar = colors.lightGray, - - top = colors.lightBlue, - bottom = colors.cyan, - - button = colors.cyan, - buttonHighlighted = colors.lightBlue, - - dangerButton = colors.red, - dangerButtonHighlighted = colors.pink, - - text = colors.white, - folder = colors.lime, - readOnly = colors.red, -} - - -local languages = {} -local currentLanguage = {} - - -local updateURL = "https://raw.github.com/GravityScore/LuaIDE/master/computercraft/ide.lua" -local ideLocation = "/" .. shell.getRunningProgram() -local themeLocation = "/.luaide_theme" - -local function isAdvanced() - return term.isColor and term.isColor() -end - - - - --- -------- Utilities - -local function modRead(properties) - local w, h = term.getSize() - local defaults = {replaceChar = nil, history = nil, visibleLength = nil, textLength = nil, - liveUpdates = nil, exitOnKey = nil} - if not properties then properties = {} end - for k, v in pairs(defaults) do if not properties[k] then properties[k] = v end end - if properties.replaceChar then properties.replaceChar = properties.replaceChar:sub(1, 1) end - if not properties.visibleLength then properties.visibleLength = w end - - local sx, sy = term.getCursorPos() - local line = "" - local pos = 0 - local historyPos = nil - - local function redraw(repl) - local scroll = 0 - if properties.visibleLength and sx + pos > properties.visibleLength + 1 then - scroll = (sx + pos) - (properties.visibleLength + 1) - end - - term.setCursorPos(sx, sy) - local a = repl or properties.replaceChar - if a then term.write(string.rep(a, line:len() - scroll)) - else term.write(line:sub(scroll + 1, -1)) end - term.setCursorPos(sx + pos - scroll, sy) - end - - local function sendLiveUpdates(event, ...) - if type(properties.liveUpdates) == "function" then - local ox, oy = term.getCursorPos() - local a, data = properties.liveUpdates(line, event, ...) - if a == true and data == nil then - term.setCursorBlink(false) - return line - elseif a == true and data ~= nil then - term.setCursorBlink(false) - return data - end - term.setCursorPos(ox, oy) - end - end - - term.setCursorBlink(true) - while true do - local e, but, x, y, p4, p5 = os.pullEvent() - - if e == "char" then - local s = false - if properties.textLength and line:len() < properties.textLength then s = true - elseif not properties.textLength then s = true end - - local canType = true - if not properties.grantPrint and properties.refusePrint then - local canTypeKeys = {} - if type(properties.refusePrint) == "table" then - for _, v in pairs(properties.refusePrint) do - table.insert(canTypeKeys, tostring(v):sub(1, 1)) - end - elseif type(properties.refusePrint) == "string" then - for char in properties.refusePrint:gmatch(".") do - table.insert(canTypeKeys, char) - end - end - for _, v in pairs(canTypeKeys) do if but == v then canType = false end end - elseif properties.grantPrint then - canType = false - local canTypeKeys = {} - if type(properties.grantPrint) == "table" then - for _, v in pairs(properties.grantPrint) do - table.insert(canTypeKeys, tostring(v):sub(1, 1)) - end - elseif type(properties.grantPrint) == "string" then - for char in properties.grantPrint:gmatch(".") do - table.insert(canTypeKeys, char) - end - end - for _, v in pairs(canTypeKeys) do if but == v then canType = true end end - end - - if s and canType then - line = line:sub(1, pos) .. but .. line:sub(pos + 1, -1) - pos = pos + 1 - redraw() - end - elseif e == "key" then - if but == keys.enter then break - elseif but == keys.left then if pos > 0 then pos = pos - 1 redraw() end - elseif but == keys.right then if pos < line:len() then pos = pos + 1 redraw() end - elseif (but == keys.up or but == keys.down) and properties.history then - redraw(" ") - if but == keys.up then - if historyPos == nil and #properties.history > 0 then - historyPos = #properties.history - elseif historyPos > 1 then - historyPos = historyPos - 1 - end - elseif but == keys.down then - if historyPos == #properties.history then historyPos = nil - elseif historyPos ~= nil then historyPos = historyPos + 1 end - end - - if properties.history and historyPos then - line = properties.history[historyPos] - pos = line:len() - else - line = "" - pos = 0 - end - - redraw() - local a = sendLiveUpdates("history") - if a then return a end - elseif but == keys.backspace and pos > 0 then - redraw(" ") - line = line:sub(1, pos - 1) .. line:sub(pos + 1, -1) - pos = pos - 1 - redraw() - local a = sendLiveUpdates("delete") - if a then return a end - elseif but == keys.home then - pos = 0 - redraw() - elseif but == keys.delete and pos < line:len() then - redraw(" ") - line = line:sub(1, pos) .. line:sub(pos + 2, -1) - redraw() - local a = sendLiveUpdates("delete") - if a then return a end - elseif but == keys["end"] then - pos = line:len() - redraw() - elseif properties.exitOnKey then - if but == properties.exitOnKey or (properties.exitOnKey == "control" and - (but == 29 or but == 157)) then - term.setCursorBlink(false) - return nil - end - end - end - local a = sendLiveUpdates(e, but, x, y, p4, p5) - if a then return a end - end - - term.setCursorBlink(false) - if line ~= nil then line = line:gsub("^%s*(.-)%s*$", "%1") end - return line -end - - --- -------- Themes - -local defaultTheme = { - background = "gray", - backgroundHighlight = "lightGray", - prompt = "cyan", - promptHighlight = "lightBlue", - err = "red", - errHighlight = "pink", - - editorBackground = "gray", - editorLineHightlight = "lightBlue", - editorLineNumbers = "gray", - editorLineNumbersHighlight = "lightGray", - editorError = "pink", - editorErrorHighlight = "red", - - textColor = "white", - conditional = "yellow", - constant = "orange", - ["function"] = "magenta", - string = "red", - comment = "lime" -} - -local normalTheme = { - background = "black", - backgroundHighlight = "black", - prompt = "black", - promptHighlight = "black", - err = "black", - errHighlight = "black", - - editorBackground = "black", - editorLineHightlight = "black", - editorLineNumbers = "black", - editorLineNumbersHighlight = "white", - editorError = "black", - editorErrorHighlight = "black", - - textColor = "white", - conditional = "white", - constant = "white", - ["function"] = "white", - string = "white", - comment = "white" -} - -local availableThemes = { - {"Water (Default)", "https://raw.github.com/GravityScore/LuaIDE/master/themes/default.txt"}, - {"Fire", "https://raw.github.com/GravityScore/LuaIDE/master/themes/fire.txt"}, - {"Sublime Text 2", "https://raw.github.com/GravityScore/LuaIDE/master/themes/st2.txt"}, - {"Midnight", "https://raw.github.com/GravityScore/LuaIDE/master/themes/midnight.txt"}, - {"TheOriginalBIT", "https://raw.github.com/GravityScore/LuaIDE/master/themes/bit.txt"}, - {"Superaxander", "https://raw.github.com/GravityScore/LuaIDE/master/themes/superaxander.txt"}, - {"Forest", "https://raw.github.com/GravityScore/LuaIDE/master/themes/forest.txt"}, - {"Night", "https://raw.github.com/GravityScore/LuaIDE/master/themes/night.txt"}, - {"Original", "https://raw.github.com/GravityScore/LuaIDE/master/themes/original.txt"}, -} - -local function loadTheme(path) - local f = io.open(path) - local l = f:read("*l") - local config = {} - while l ~= nil do - local k, v = string.match(l, "^(%a+)=(%a+)") - if k and v then config[k] = v end - l = f:read("*l") - end - f:close() - return config -end - --- Load Theme -if isAdvanced() then theme = defaultTheme -else theme = normalTheme end - - --- -------- Drawing - -local function centerPrint(text, ny) - if type(text) == "table" then for _, v in pairs(text) do centerPrint(v) end - else - local x, y = term.getCursorPos() - local w, h = term.getSize() - term.setCursorPos(w/2 - text:len()/2 + (#text % 2 == 0 and 1 or 0), ny or y) - print(text) - end -end - -local function title(t) - term.setTextColor(colors[theme.textColor]) - term.setBackgroundColor(colors[theme.background]) - term.clear() - - term.setBackgroundColor(colors[theme.backgroundHighlight]) - for i = 2, 4 do term.setCursorPos(1, i) term.clearLine() end - term.setCursorPos(3, 3) - term.write(t) -end - -local function centerRead(wid, begt) - local function liveUpdate(line, e, but, x, y, p4, p5) - if isAdvanced() and e == "mouse_click" and x >= w/2 - wid/2 and x <= w/2 - wid/2 + 10 - and y >= 13 and y <= 15 then - return true, "" - end - end - - if not begt then begt = "" end - term.setTextColor(colors[theme.textColor]) - term.setBackgroundColor(colors[theme.promptHighlight]) - for i = 8, 10 do - term.setCursorPos(w/2 - wid/2, i) - term.write(string.rep(" ", wid)) - end - - if isAdvanced() then - term.setBackgroundColor(colors[theme.errHighlight]) - for i = 13, 15 do - term.setCursorPos(w/2 - wid/2 + 1, i) - term.write(string.rep(" ", 10)) - end - term.setCursorPos(w/2 - wid/2 + 2, 14) - term.write("> Cancel") - end - - term.setBackgroundColor(colors[theme.promptHighlight]) - term.setCursorPos(w/2 - wid/2 + 1, 9) - term.write("> " .. begt) - return modRead({visibleLength = w/2 + wid/2, liveUpdates = liveUpdate}) -end - - --- -------- Prompt - -local function prompt(list, dir, isGrid) - local function draw(sel) - for i, v in ipairs(list) do - if i == sel then term.setBackgroundColor(v.highlight or colors[theme.promptHighlight]) - else term.setBackgroundColor(v.bg or colors[theme.prompt]) end - term.setTextColor(v.tc or colors[theme.textColor]) - for i = -1, 1 do - term.setCursorPos(v[2], v[3] + i) - term.write(string.rep(" ", v[1]:len() + 4)) - end - - term.setCursorPos(v[2], v[3]) - if i == sel then - term.setBackgroundColor(v.highlight or colors[theme.promptHighlight]) - term.write(" > ") - else term.write(" - ") end - term.write(v[1] .. " ") - end - end - - local key1 = dir == "horizontal" and 203 or 200 - local key2 = dir == "horizontal" and 205 or 208 - local sel = 1 - draw(sel) - - while true do - local e, but, x, y = os.pullEvent() - if e == "key" and but == 28 then - return list[sel][1] - elseif e == "key" and but == key1 and sel > 1 then - sel = sel - 1 - draw(sel) - elseif e == "key" and but == key2 and ((err == true and sel < #list - 1) or (sel < #list)) then - sel = sel + 1 - draw(sel) - elseif isGrid and e == "key" and but == 203 and sel > 2 then - sel = sel - 2 - draw(sel) - elseif isGrid and e == "key" and but == 205 and sel < 3 then - sel = sel + 2 - draw(sel) - elseif e == "mouse_click" then - for i, v in ipairs(list) do - if x >= v[2] - 1 and x <= v[2] + v[1]:len() + 3 and y >= v[3] - 1 and y <= v[3] + 1 then - return list[i][1] - end - end - end - end -end - -local function scrollingPrompt(list) - local function draw(items, sel, loc) - for i, v in ipairs(items) do - local bg = colors[theme.prompt] - local bghigh = colors[theme.promptHighlight] - if v:find("Back") or v:find("Return") then - bg = colors[theme.err] - bghigh = colors[theme.errHighlight] - end - - if i == sel then term.setBackgroundColor(bghigh) - else term.setBackgroundColor(bg) end - term.setTextColor(colors[theme.textColor]) - for x = -1, 1 do - term.setCursorPos(3, (i * 4) + x + 4) - term.write(string.rep(" ", w - 13)) - end - - term.setCursorPos(3, i * 4 + 4) - if i == sel then - term.setBackgroundColor(bghigh) - term.write(" > ") - else term.write(" - ") end - term.write(v .. " ") - end - end - - local function updateDisplayList(items, loc, len) - local ret = {} - for i = 1, len do - local item = items[i + loc - 1] - if item then table.insert(ret, item) end - end - return ret - end - - -- Variables - local sel = 1 - local loc = 1 - local len = 3 - local disList = updateDisplayList(list, loc, len) - draw(disList, sel, loc) - - -- Loop - while true do - local e, key, x, y = os.pullEvent() - - if e == "mouse_click" then - for i, v in ipairs(disList) do - if x >= 3 and x <= w - 11 and y >= i * 4 + 3 and y <= i * 4 + 5 then return v end - end - elseif e == "key" and key == 200 then - if sel > 1 then - sel = sel - 1 - draw(disList, sel, loc) - elseif loc > 1 then - loc = loc - 1 - disList = updateDisplayList(list, loc, len) - draw(disList, sel, loc) - end - elseif e == "key" and key == 208 then - if sel < len then - sel = sel + 1 - draw(disList, sel, loc) - elseif loc + len - 1 < #list then - loc = loc + 1 - disList = updateDisplayList(list, loc, len) - draw(disList, sel, loc) - end - elseif e == "mouse_scroll" then - os.queueEvent("key", key == -1 and 200 or 208) - elseif e == "key" and key == 28 then - return disList[sel] - end - end -end - -function monitorKeyboardShortcuts() - local ta, tb = nil, nil - local allowChar = false - local shiftPressed = false - while true do - local event, char = os.pullEvent() - if event == "key" and (char == 42 or char == 52) then - shiftPressed = true - tb = os.startTimer(keyboardShortcutTimeout) - elseif event == "key" and (char == 29 or char == 157 or char == 219 or char == 220) then - allowEditorEvent = false - allowChar = true - ta = os.startTimer(keyboardShortcutTimeout) - elseif event == "key" and allowChar then - local name = nil - for k, v in pairs(keys) do - if v == char then - if shiftPressed then os.queueEvent("shortcut", "ctrl shift", k:lower()) - else os.queueEvent("shortcut", "ctrl", k:lower()) end - sleep(0.005) - allowEditorEvent = true - end - end - if shiftPressed then os.queueEvent("shortcut", "ctrl shift", char) - else os.queueEvent("shortcut", "ctrl", char) end - elseif event == "timer" and char == ta then - allowEditorEvent = true - allowChar = false - elseif event == "timer" and char == tb then - shiftPressed = false - end - end -end - - --- -------- Saving and Loading - -local function download(url, path) - for i = 1, 3 do - local response = http.get(url) - if response then - local data = response.readAll() - response.close() - if path then - local f = io.open(path, "w") - f:write(data) - f:close() - end - return true - end - end - - return false -end - -local function saveFile(path, lines) - local dir = path:sub(1, path:len() - fs.getName(path):len()) - if not fs.exists(dir) then fs.makeDir(dir) end - if not fs.isDir(path) and not fs.isReadOnly(path) then - local a = "" - for _, v in pairs(lines) do a = a .. v .. "\n" end - - local f = io.open(path, "w") - f:write(a) - f:close() - return true - else return false end -end - -local function loadFile(path) - if not fs.exists(path) then - local dir = path:sub(1, path:len() - fs.getName(path):len()) - if not fs.exists(dir) then fs.makeDir(dir) end - local f = io.open(path, "w") - f:write("") - f:close() - end - - local l = {} - if fs.exists(path) and not fs.isDir(path) then - local f = io.open(path, "r") - if f then - local a = f:read("*l") - while a do - table.insert(l, a) - a = f:read("*l") - end - f:close() - end - else return nil end - - if #l < 1 then table.insert(l, "") end - return l -end - - --- -------- Languages - -languages.lua = {} -languages.brainfuck = {} -languages.none = {} - --- Lua - -languages.lua.helpTips = { - "A function you tried to call doesn't exist.", - "You made a typo.", - "The index of an array is nil.", - "The wrong variable type was passed.", - "A function/variable doesn't exist.", - "You missed an 'end'.", - "You missed a 'then'.", - "You declared a variable incorrectly.", - "One of your variables is mysteriously nil." -} - -languages.lua.defaultHelpTips = { - 2, 5 -} - -languages.lua.errors = { - ["Attempt to call nil."] = {1, 2}, - ["Attempt to index nil."] = {3, 2}, - [".+ expected, got .+"] = {4, 2, 9}, - ["'end' expected"] = {6, 2}, - ["'then' expected"] = {7, 2}, - ["'=' expected"] = {8, 2} -} - -languages.lua.keywords = { - ["and"] = "conditional", - ["break"] = "conditional", - ["do"] = "conditional", - ["else"] = "conditional", - ["elseif"] = "conditional", - ["end"] = "conditional", - ["for"] = "conditional", - ["function"] = "conditional", - ["if"] = "conditional", - ["in"] = "conditional", - ["local"] = "conditional", - ["not"] = "conditional", - ["or"] = "conditional", - ["repeat"] = "conditional", - ["return"] = "conditional", - ["then"] = "conditional", - ["until"] = "conditional", - ["while"] = "conditional", - - ["true"] = "constant", - ["false"] = "constant", - ["nil"] = "constant", - - ["print"] = "function", - ["write"] = "function", - ["sleep"] = "function", - ["pairs"] = "function", - ["ipairs"] = "function", - ["loadstring"] = "function", - ["loadfile"] = "function", - ["dofile"] = "function", - ["rawset"] = "function", - ["rawget"] = "function", - ["setfenv"] = "function", - ["getfenv"] = "function", -} - -languages.lua.parseError = function(e) - local ret = {filename = "unknown", line = -1, display = "Unknown!", err = ""} - if e and e ~= "" then - ret.err = e - if e:find(":") then - ret.filename = e:sub(1, e:find(":") - 1):gsub("^%s*(.-)%s*$", "%1") - -- The "" is needed to circumvent a CC bug - e = (e:sub(e:find(":") + 1) .. ""):gsub("^%s*(.-)%s*$", "%1") - if e:find(":") then - ret.line = e:sub(1, e:find(":") - 1) - e = e:sub(e:find(":") + 2):gsub("^%s*(.-)%s*$", "%1") .. "" - end - end - ret.display = e:sub(1, 1):upper() .. e:sub(2, -1) .. "." - end - - return ret -end - -languages.lua.getCompilerErrors = function(code) - code = "local function ee65da6af1cb6f63fee9a081246f2fd92b36ef2(...)\n\n" .. code .. "\n\nend" - local fn, err = loadstring(code) - if not err then - local _, e = pcall(fn) - if e then err = e end - end - - if err then - local a = err:find("]", 1, true) - if a then err = "string" .. err:sub(a + 1, -1) end - local ret = languages.lua.parseError(err) - if tonumber(ret.line) then ret.line = tonumber(ret.line) end - return ret - else return languages.lua.parseError(nil) end -end - -languages.lua.run = function(path, ar) - local fn, err = loadfile(path) - setfenv(fn, getfenv()) - if not err then - _, err = pcall(function() fn(unpack(ar)) end) - end - return err -end - - --- Brainfuck - -languages.brainfuck.helpTips = { - "Well idk...", - "Isn't this the whole point of the language?", - "Ya know... Not being able to debug it?", - "You made a typo." -} - -languages.brainfuck.defaultHelpTips = { - 1, 2, 3 -} - -languages.brainfuck.errors = { - ["No matching '['"] = {1, 2, 3, 4} -} - -languages.brainfuck.keywords = {} - -languages.brainfuck.parseError = function(e) - local ret = {filename = "unknown", line = -1, display = "Unknown!", err = ""} - if e and e ~= "" then - ret.err = e - ret.line = e:sub(1, e:find(":") - 1) - e = e:sub(e:find(":") + 2):gsub("^%s*(.-)%s*$", "%1") .. "" - ret.display = e:sub(1, 1):upper() .. e:sub(2, -1) .. "." - end - - return ret -end - -languages.brainfuck.mapLoops = function(code) - -- Map loops - local loopLocations = {} - local loc = 1 - local line = 1 - for let in string.gmatch(code, ".") do - if let == "[" then - loopLocations[loc] = true - elseif let == "]" then - local found = false - for i = loc, 1, -1 do - if loopLocations[i] == true then - loopLocations[i] = loc - found = true - end - end - - if not found then - return line .. ": No matching '['" - end - end - - if let == "\n" then line = line + 1 end - loc = loc + 1 - end - return loopLocations -end - -languages.brainfuck.getCompilerErrors = function(code) - local a = languages.brainfuck.mapLoops(code) - if type(a) == "string" then return languages.brainfuck.parseError(a) - else return languages.brainfuck.parseError(nil) end -end - -languages.brainfuck.run = function(path) - -- Read from file - local f = io.open(path, "r") - local content = f:read("*a") - f:close() - - -- Define environment - local dataCells = {} - local dataPointer = 1 - local instructionPointer = 1 - - -- Map loops - local loopLocations = languages.brainfuck.mapLoops(content) - if type(loopLocations) == "string" then return loopLocations end - - -- Execute code - while true do - local let = content:sub(instructionPointer, instructionPointer) - - if let == ">" then - dataPointer = dataPointer + 1 - if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end - elseif let == "<" then - if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end - dataPointer = dataPointer - 1 - if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end - elseif let == "+" then - if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end - dataCells[tostring(dataPointer)] = dataCells[tostring(dataPointer)] + 1 - elseif let == "-" then - if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end - dataCells[tostring(dataPointer)] = dataCells[tostring(dataPointer)] - 1 - elseif let == "." then - if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end - if term.getCursorPos() >= w then print("") end - write(string.char(math.max(1, dataCells[tostring(dataPointer)]))) - elseif let == "," then - if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end - term.setCursorBlink(true) - local e, but = os.pullEvent("char") - term.setCursorBlink(false) - dataCells[tostring(dataPointer)] = string.byte(but) - if term.getCursorPos() >= w then print("") end - write(but) - elseif let == "/" then - if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end - if term.getCursorPos() >= w then print("") end - write(dataCells[tostring(dataPointer)]) - elseif let == "[" then - if dataCells[tostring(dataPointer)] == 0 then - for k, v in pairs(loopLocations) do - if k == instructionPointer then instructionPointer = v end - end - end - elseif let == "]" then - for k, v in pairs(loopLocations) do - if v == instructionPointer then instructionPointer = k - 1 end - end - end - - instructionPointer = instructionPointer + 1 - if instructionPointer > content:len() then print("") break end - end -end - --- None - -languages.none.helpTips = {} -languages.none.defaultHelpTips = {} -languages.none.errors = {} -languages.none.keywords = {} - -languages.none.parseError = function(err) - return {filename = "", line = -1, display = "", err = ""} -end - -languages.none.getCompilerErrors = function(code) - return languages.none.parseError(nil) -end - -languages.none.run = function(path) end - - --- Load language -currentLanguage = languages.lua - - --- -------- Run GUI - -local function viewErrorHelp(e) - title("LuaIDE - Error Help") - - local tips = nil - for k, v in pairs(currentLanguage.errors) do - if e.display:find(k) then tips = v break end - end - - term.setBackgroundColor(colors[theme.err]) - for i = 6, 8 do - term.setCursorPos(5, i) - term.write(string.rep(" ", 35)) - end - - term.setBackgroundColor(colors[theme.prompt]) - for i = 10, 18 do - term.setCursorPos(5, i) - term.write(string.rep(" ", 46)) - end - - if tips then - term.setBackgroundColor(colors[theme.err]) - term.setCursorPos(6, 7) - term.write("Error Help") - - term.setBackgroundColor(colors[theme.prompt]) - for i, v in ipairs(tips) do - term.setCursorPos(7, i + 10) - term.write("- " .. currentLanguage.helpTips[v]) - end - else - term.setBackgroundColor(colors[theme.err]) - term.setCursorPos(6, 7) - term.write("No Error Tips Available!") - - term.setBackgroundColor(colors[theme.prompt]) - term.setCursorPos(6, 11) - term.write("There are no error tips available, but") - term.setCursorPos(6, 12) - term.write("you could see if it was any of these:") - - for i, v in ipairs(currentLanguage.defaultHelpTips) do - term.setCursorPos(7, i + 12) - term.write("- " .. currentLanguage.helpTips[v]) - end - end - - prompt({{"Back", w - 8, 7}}, "horizontal") -end - -local function run(path, lines, useArgs) - local ar = {} - if useArgs then - title("LuaIDE - Run " .. fs.getName(path)) - local s = centerRead(w - 13, fs.getName(path) .. " ") - for m in string.gmatch(s, "[^ \t]+") do ar[#ar + 1] = m:gsub("^%s*(.-)%s*$", "%1") end - end - - saveFile(path, lines) - term.setCursorBlink(false) - term.setBackgroundColor(colors.black) - term.setTextColor(colors.white) - term.clear() - term.setCursorPos(1, 1) - local err = currentLanguage.run(path, ar) - - term.setBackgroundColor(colors.black) - print("\n") - if err then - if isAdvanced() then term.setTextColor(colors.red) end - centerPrint("The program has crashed!") - end - term.setTextColor(colors.white) - centerPrint("Press any key to return to LuaIDE...") - while true do - local e = os.pullEvent() - if e == "key" then break end - end - - -- To prevent key from showing up in editor - os.queueEvent("") - os.pullEvent() - - if err then - if currentLanguage == languages.lua and err:find("]") then - err = fs.getName(path) .. err:sub(err:find("]", 1, true) + 1, -1) - end - - while true do - title("LuaIDE - Error!") - - term.setBackgroundColor(colors[theme.err]) - for i = 6, 8 do - term.setCursorPos(3, i) - term.write(string.rep(" ", w - 5)) - end - term.setCursorPos(4, 7) - term.write("The program has crashed!") - - term.setBackgroundColor(colors[theme.prompt]) - for i = 10, 14 do - term.setCursorPos(3, i) - term.write(string.rep(" ", w - 5)) - end - - local formattedErr = currentLanguage.parseError(err) - term.setCursorPos(4, 11) - term.write("Line: " .. formattedErr.line) - term.setCursorPos(4, 12) - term.write("Error:") - term.setCursorPos(5, 13) - - local a = formattedErr.display - local b = nil - if a:len() > w - 8 then - for i = a:len(), 1, -1 do - if a:sub(i, i) == " " then - b = a:sub(i + 1, -1) - a = a:sub(1, i) - break - end - end - end - - term.write(a) - if b then - term.setCursorPos(5, 14) - term.write(b) - end - - local opt = prompt({{"Error Help", w/2 - 15, 17}, {"Go To Line", w/2 + 2, 17}}, - "horizontal") - if opt == "Error Help" then - viewErrorHelp(formattedErr) - elseif opt == "Go To Line" then - -- To prevent key from showing up in editor - os.queueEvent("") - os.pullEvent() - - return "go to", tonumber(formattedErr.line) - end - end - end -end - - --- -------- Functions - -local function goto() - term.setBackgroundColor(colors[theme.backgroundHighlight]) - term.setCursorPos(2, 1) - term.clearLine() - term.write("Line: ") - local line = modRead({visibleLength = w - 2}) - - local num = tonumber(line) - if num and num > 0 then return num - else - term.setCursorPos(2, 1) - term.clearLine() - term.write("Not a line number!") - sleep(1.6) - return nil - end -end - -local function setsyntax() - local opts = { - "[Lua] Brainfuck None ", - " Lua [Brainfuck] None ", - " Lua Brainfuck [None]" - } - local sel = 1 - - term.setCursorBlink(false) - term.setBackgroundColor(colors[theme.backgroundHighlight]) - term.setCursorPos(2, 1) - term.clearLine() - term.write(opts[sel]) - while true do - local e, but, x, y = os.pullEvent("key") - if but == 203 then - sel = math.max(1, sel - 1) - term.setCursorPos(2, 1) - term.clearLine() - term.write(opts[sel]) - elseif but == 205 then - sel = math.min(#opts, sel + 1) - term.setCursorPos(2, 1) - term.clearLine() - term.write(opts[sel]) - elseif but == 28 then - if sel == 1 then currentLanguage = languages.lua - elseif sel == 2 then currentLanguage = languages.brainfuck - elseif sel == 3 then currentLanguage = languages.none end - term.setCursorBlink(true) - return - end - end -end - - --- -------- Re-Indenting - -local tabWidth = 2 - -local comments = {} -local strings = {} - -local increment = { - "if%s+.+%s+then%s*$", - "for%s+.+%s+do%s*$", - "while%s+.+%s+do%s*$", - "repeat%s*$", - "function%s+[a-zA-Z_0-9]\(.*\)%s*$" -} - -local decrement = { - "end", - "until%s+.+" -} - -local special = { - "else%s*$", - "elseif%s+.+%s+then%s*$" -} - -local function check(func) - for _, v in pairs(func) do - local cLineStart = v["lineStart"] - local cLineEnd = v["lineEnd"] - local cCharStart = v["charStart"] - local cCharEnd = v["charEnd"] - - if line >= cLineStart and line <= cLineEnd then - if line == cLineStart then return cCharStart < charNumb - elseif line == cLineEnd then return cCharEnd > charNumb - else return true end - end - end -end - -local function isIn(line, loc) - if check(comments) then return true end - if check(strings) then return true end - return false -end - -local function setComment(ls, le, cs, ce) - comments[#comments + 1] = {} - comments[#comments].lineStart = ls - comments[#comments].lineEnd = le - comments[#comments].charStart = cs - comments[#comments].charEnd = ce -end - -local function setString(ls, le, cs, ce) - strings[#strings + 1] = {} - strings[#strings].lineStart = ls - strings[#strings].lineEnd = le - strings[#strings].charStart = cs - strings[#strings].charEnd = ce -end - -local function map(contents) - local inCom = false - local inStr = false - - for i = 1, #contents do - if content[i]:find("%-%-%[%[") and not inStr and not inCom then - local cStart = content[i]:find("%-%-%[%[") - setComment(i, nil, cStart, nil) - inCom = true - elseif content[i]:find("%-%-%[=%[") and not inStr and not inCom then - local cStart = content[i]:find("%-%-%[=%[") - setComment(i, nil, cStart, nil) - inCom = true - elseif content[i]:find("%[%[") and not inStr and not inCom then - local cStart = content[i]:find("%[%[") - setString(i, nil, cStart, nil) - inStr = true - elseif content[i]:find("%[=%[") and not inStr and not inCom then - local cStart = content[i]:find("%[=%[") - setString(i, nil, cStart, nil) - inStr = true - end - - if content[i]:find("%]%]") and inStr and not inCom then - local cStart, cEnd = content[i]:find("%]%]") - strings[#strings].lineEnd = i - strings[#strings].charEnd = cEnd - inStr = false - elseif content[i]:find("%]=%]") and inStr and not inCom then - local cStart, cEnd = content[i]:find("%]=%]") - strings[#strings].lineEnd = i - strings[#strings].charEnd = cEnd - inStr = false - end - - if content[i]:find("%]%]") and not inStr and inCom then - local cStart, cEnd = content[i]:find("%]%]") - comments[#comments].lineEnd = i - comments[#comments].charEnd = cEnd - inCom = false - elseif content[i]:find("%]=%]") and not inStr and inCom then - local cStart, cEnd = content[i]:find("%]=%]") - comments[#comments].lineEnd = i - comments[#comments].charEnd = cEnd - inCom = false - end - - if content[i]:find("%-%-") and not inStr and not inCom then - local cStart = content[i]:find("%-%-") - setComment(i, i, cStart, -1) - elseif content[i]:find("'") and not inStr and not inCom then - local cStart, cEnd = content[i]:find("'") - local nextChar = content[i]:sub(cEnd + 1, string.len(content[i])) - local _, cEnd = nextChar:find("'") - setString(i, i, cStart, cEnd) - elseif content[i]:find('"') and not inStr and not inCom then - local cStart, cEnd = content[i]:find('"') - local nextChar = content[i]:sub(cEnd + 1, string.len(content[i])) - local _, cEnd = nextChar:find('"') - setString(i, i, cStart, cEnd) - end - end -end - -local function reindent(contents) - local err = nil - if currentLanguage ~= languages.lua then - err = "Cannot indent languages other than Lua!" - elseif currentLanguage.getCompilerErrors(table.concat(contents, "\n")).line ~= -1 then - err = "Cannot indent a program with errors!" - end - - if err then - term.setCursorBlink(false) - term.setCursorPos(2, 1) - term.setBackgroundColor(colors[theme.backgroundHighlight]) - term.clearLine() - term.write(err) - sleep(1.6) - return contents - end - - local new = {} - local level = 0 - for k, v in pairs(contents) do - local incrLevel = false - local foundIncr = false - for _, incr in pairs(increment) do - if v:find(incr) and not isIn(k, v:find(incr)) then - incrLevel = true - end - if v:find(incr:sub(1, -2)) and not isIn(k, v:find(incr)) then - foundIncr = true - end - end - - local decrLevel = false - if not incrLevel then - for _, decr in pairs(decrement) do - if v:find(decr) and not isIn(k, v:find(decr)) and not foundIncr then - level = math.max(0, level - 1) - decrLevel = true - end - end - end - - if not decrLevel then - for _, sp in pairs(special) do - if v:find(sp) and not isIn(k, v:find(sp)) then - incrLevel = true - level = math.max(0, level - 1) - end - end - end - - new[k] = string.rep(" ", level * tabWidth) .. v - if incrLevel then level = level + 1 end - end - - return new -end - - --- -------- Menu - -local menu = { - [1] = {"File", --- "About", --- "Settings", --- "", - "New File ^+N", - "Open File ^+O", - "Save File ^+S", - "Close ^+W", - "Print ^+P", - "Quit ^+Q" - }, [2] = {"Edit", - "Cut Line ^+X", - "Copy Line ^+C", - "Paste Line ^+V", - "Delete Line", - "Clear Line" - }, [3] = {"Functions", - "Go To Line ^+G", - "Re-Indent ^+I", - "Set Syntax ^+E", - "Start of Line ^+<", - "End of Line ^+>" - }, [4] = {"Run", - "Run Program ^+R", - "Run w/ Args ^+Shift+R" - } -} - -local shortcuts = { - -- File - ["ctrl n"] = "New File ^+N", - ["ctrl o"] = "Open File ^+O", - ["ctrl s"] = "Save File ^+S", - ["ctrl w"] = "Close ^+W", - ["ctrl p"] = "Print ^+P", - ["ctrl q"] = "Quit ^+Q", - - -- Edit - ["ctrl x"] = "Cut Line ^+X", - ["ctrl c"] = "Copy Line ^+C", - ["ctrl v"] = "Paste Line ^+V", - - -- Functions - ["ctrl g"] = "Go To Line ^+G", - ["ctrl i"] = "Re-Indent ^+I", - ["ctrl e"] = "Set Syntax ^+E", - ["ctrl 203"] = "Start of Line ^+<", - ["ctrl 205"] = "End of Line ^+>", - - -- Run - ["ctrl r"] = "Run Program ^+R", - ["ctrl shift r"] = "Run w/ Args ^+Shift+R" -} - -local menuFunctions = { - -- File --- ["About"] = function() end, --- ["Settings"] = function() end, - ["New File ^+N"] = function(path, lines) saveFile(path, lines) return "new" end, - ["Open File ^+O"] = function(path, lines) saveFile(path, lines) return "open" end, - ["Save File ^+S"] = function(path, lines) saveFile(path, lines) end, - ["Close ^+W"] = function(path, lines) saveFile(path, lines) return "menu" end, - ["Print ^+P"] = function(path, lines) saveFile(path, lines) return nil end, - ["Quit ^+Q"] = function(path, lines) saveFile(path, lines) return "exit" end, - - -- Edit - ["Cut Line ^+X"] = function(path, lines, y) - clipboard = lines[y] table.remove(lines, y) return nil, lines end, - ["Copy Line ^+C"] = function(path, lines, y) clipboard = lines[y] end, - ["Paste Line ^+V"] = function(path, lines, y) - if clipboard then table.insert(lines, y, clipboard) end return nil, lines end, - ["Delete Line"] = function(path, lines, y) table.remove(lines, y) return nil, lines end, - ["Clear Line"] = function(path, lines, y) lines[y] = "" return nil, lines, "cursor" end, - - -- Functions - ["Go To Line ^+G"] = function() return nil, "go to", goto() end, - ["Re-Indent ^+I"] = function(path, lines) - local a = reindent(lines) saveFile(path, lines) return nil, a - end, - ["Set Syntax ^+E"] = function(path, lines) - setsyntax() - if currentLanguage == languages.brainfuck and lines[1] ~= "-- Syntax: Brainfuck" then - table.insert(lines, 1, "-- Syntax: Brainfuck") - return nil, lines - end - end, - ["Start of Line ^+<"] = function() os.queueEvent("key", 199) end, - ["End of Line ^+>"] = function() os.queueEvent("key", 207) end, - - -- Run - ["Run Program ^+R"] = function(path, lines) - saveFile(path, lines) - return nil, run(path, lines, false) - end, - ["Run w/ Args ^+Shift+R"] = function(path, lines) - saveFile(path, lines) - return nil, run(path, lines, true) - end, -} - -local function drawMenu(open) - term.setCursorPos(1, 1) - term.setTextColor(colors[theme.textColor]) - term.setBackgroundColor(colors[theme.backgroundHighlight]) - term.clearLine() - local curX = 0 - for _, v in pairs(menu) do - term.setCursorPos(3 + curX, 1) - term.write(v[1]) - curX = curX + v[1]:len() + 3 - end - - if open then - local it = {} - local x = 1 - for _, v in pairs(menu) do - if open == v[1] then - it = v - break - end - x = x + v[1]:len() + 3 - end - x = x + 1 - - local items = {} - for i = 2, #it do - table.insert(items, it[i]) - end - - local len = 1 - for _, v in pairs(items) do if v:len() + 2 > len then len = v:len() + 2 end end - - for i, v in ipairs(items) do - term.setCursorPos(x, i + 1) - term.write(string.rep(" ", len)) - term.setCursorPos(x + 1, i + 1) - term.write(v) - end - term.setCursorPos(x, #items + 2) - term.write(string.rep(" ", len)) - return items, len - end -end - -local function triggerMenu(cx, cy) - -- Determine clicked menu - local curX = 0 - local open = nil - for _, v in pairs(menu) do - if cx >= curX + 3 and cx <= curX + v[1]:len() + 2 then - open = v[1] - break - end - curX = curX + v[1]:len() + 3 - end - local menux = curX + 2 - if not open then return false end - - -- Flash menu item - term.setCursorBlink(false) - term.setCursorPos(menux, 1) - term.setBackgroundColor(colors[theme.background]) - term.write(string.rep(" ", open:len() + 2)) - term.setCursorPos(menux + 1, 1) - term.write(open) - sleep(0.1) - local items, len = drawMenu(open) - - local ret = true - - -- Pull events on menu - local ox, oy = term.getCursorPos() - while type(ret) ~= "string" do - local e, but, x, y = os.pullEvent() - if e == "mouse_click" then - -- If clicked outside menu - if x < menux - 1 or x > menux + len - 1 then break - elseif y > #items + 2 then break - elseif y == 1 then break end - - for i, v in ipairs(items) do - if y == i + 1 and x >= menux and x <= menux + len - 2 then - -- Flash when clicked - term.setCursorPos(menux, y) - term.setBackgroundColor(colors[theme.background]) - term.write(string.rep(" ", len)) - term.setCursorPos(menux + 1, y) - term.write(v) - sleep(0.1) - drawMenu(open) - - -- Return item - ret = v - break - end - end - end - end - - term.setCursorPos(ox, oy) - term.setCursorBlink(true) - return ret -end - - --- -------- Editing - -local standardsCompletions = { - "if%s+.+%s+then%s*$", - "for%s+.+%s+do%s*$", - "while%s+.+%s+do%s*$", - "repeat%s*$", - "function%s+[a-zA-Z_0-9]?\(.*\)%s*$", - "=%s*function%s*\(.*\)%s*$", - "else%s*$", - "elseif%s+.+%s+then%s*$" -} - -local liveCompletions = { - ["("] = ")", - ["{"] = "}", - ["["] = "]", - ["\""] = "\"", - ["'"] = "'", -} - -local x, y = 0, 0 -local edw, edh = 0, h - 1 -local offx, offy = 0, 1 -local scrollx, scrolly = 0, 0 -local lines = {} -local liveErr = currentLanguage.parseError(nil) -local displayCode = true -local lastEventClock = os.clock() - -local function attemptToHighlight(line, regex, col) - local match = string.match(line, regex) - if match then - if type(col) == "number" then term.setTextColor(col) - elseif type(col) == "function" then term.setTextColor(col(match)) end - term.write(match) - term.setTextColor(colors[theme.textColor]) - return line:sub(match:len() + 1, -1) - end - return nil -end - -local function writeHighlighted(line) - if currentLanguage == languages.lua then - while line:len() > 0 do - line = attemptToHighlight(line, "^%-%-%[%[.-%]%]", colors[theme.comment]) or - attemptToHighlight(line, "^%-%-.*", colors[theme.comment]) or - attemptToHighlight(line, "^\".*[^\\]\"", colors[theme.string]) or - attemptToHighlight(line, "^\'.*[^\\]\'", colors[theme.string]) or - attemptToHighlight(line, "^%[%[.-%]%]", colors[theme.string]) or - attemptToHighlight(line, "^[%w_]+", function(match) - if currentLanguage.keywords[match] then - return colors[theme[currentLanguage.keywords[match]]] - end - return colors[theme.textColor] - end) or - attemptToHighlight(line, "^[^%w_]", colors[theme.textColor]) - end - else term.write(line) end -end - -local function draw() - -- Menu - term.setTextColor(colors[theme.textColor]) - term.setBackgroundColor(colors[theme.editorBackground]) - term.clear() - drawMenu() - - -- Line numbers - offx, offy = tostring(#lines):len() + 1, 1 - edw, edh = w - offx, h - 1 - - -- Draw text - for i = 1, edh do - local a = lines[scrolly + i] - if a then - local ln = string.rep(" ", offx - 1 - tostring(scrolly + i):len()) .. tostring(scrolly + i) - local l = a:sub(scrollx + 1, edw + scrollx + 1) - ln = ln .. ":" - - if liveErr.line == scrolly + i then ln = string.rep(" ", offx - 2) .. "!:" end - - term.setCursorPos(1, i + offy) - term.setBackgroundColor(colors[theme.editorBackground]) - if scrolly + i == y then - if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then - term.setBackgroundColor(colors[theme.editorErrorHighlight]) - else term.setBackgroundColor(colors[theme.editorLineHightlight]) end - term.clearLine() - elseif scrolly + i == liveErr.line then - term.setBackgroundColor(colors[theme.editorError]) - term.clearLine() - end - - term.setCursorPos(1 - scrollx + offx, i + offy) - if scrolly + i == y then - if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then - term.setBackgroundColor(colors[theme.editorErrorHighlight]) - else term.setBackgroundColor(colors[theme.editorLineHightlight]) end - elseif scrolly + i == liveErr.line then term.setBackgroundColor(colors[theme.editorError]) - else term.setBackgroundColor(colors[theme.editorBackground]) end - if scrolly + i == liveErr.line then - if displayCode then term.write(a) - else term.write(liveErr.display) end - else writeHighlighted(a) end - - term.setCursorPos(1, i + offy) - if scrolly + i == y then - if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then - term.setBackgroundColor(colors[theme.editorError]) - else term.setBackgroundColor(colors[theme.editorLineNumbersHighlight]) end - elseif scrolly + i == liveErr.line then - term.setBackgroundColor(colors[theme.editorErrorHighlight]) - else term.setBackgroundColor(colors[theme.editorLineNumbers]) end - term.write(ln) - end - end - term.setCursorPos(x - scrollx + offx, y - scrolly + offy) -end - -local function drawLine(...) - local ls = {...} - offx = tostring(#lines):len() + 1 - for _, ly in pairs(ls) do - local a = lines[ly] - if a then - local ln = string.rep(" ", offx - 1 - tostring(ly):len()) .. tostring(ly) - local l = a:sub(scrollx + 1, edw + scrollx + 1) - ln = ln .. ":" - - if liveErr.line == ly then ln = string.rep(" ", offx - 2) .. "!:" end - - term.setCursorPos(1, (ly - scrolly) + offy) - term.setBackgroundColor(colors[theme.editorBackground]) - if ly == y then - if ly == liveErr.line and os.clock() - lastEventClock > 3 then - term.setBackgroundColor(colors[theme.editorErrorHighlight]) - else term.setBackgroundColor(colors[theme.editorLineHightlight]) end - elseif ly == liveErr.line then - term.setBackgroundColor(colors[theme.editorError]) - end - term.clearLine() - - term.setCursorPos(1 - scrollx + offx, (ly - scrolly) + offy) - if ly == y then - if ly == liveErr.line and os.clock() - lastEventClock > 3 then - term.setBackgroundColor(colors[theme.editorErrorHighlight]) - else term.setBackgroundColor(colors[theme.editorLineHightlight]) end - elseif ly == liveErr.line then term.setBackgroundColor(colors[theme.editorError]) - else term.setBackgroundColor(colors[theme.editorBackground]) end - if ly == liveErr.line then - if displayCode then term.write(a) - else term.write(liveErr.display) end - else writeHighlighted(a) end - - term.setCursorPos(1, (ly - scrolly) + offy) - if ly == y then - if ly == liveErr.line and os.clock() - lastEventClock > 3 then - term.setBackgroundColor(colors[theme.editorError]) - else term.setBackgroundColor(colors[theme.editorLineNumbersHighlight]) end - elseif ly == liveErr.line then - term.setBackgroundColor(colors[theme.editorErrorHighlight]) - else term.setBackgroundColor(colors[theme.editorLineNumbers]) end - term.write(ln) - end - end - term.setCursorPos(x - scrollx + offx, y - scrolly + offy) -end - -local function cursorLoc(x, y, force) - local sx, sy = x - scrollx, y - scrolly - local redraw = false - if sx < 1 then - scrollx = x - 1 - sx = 1 - redraw = true - elseif sx > edw then - scrollx = x - edw - sx = edw - redraw = true - end if sy < 1 then - scrolly = y - 1 - sy = 1 - redraw = true - elseif sy > edh then - scrolly = y - edh - sy = edh - redraw = true - end if redraw or force then draw() end - term.setCursorPos(sx + offx, sy + offy) -end - -local function executeMenuItem(a, path) - if type(a) == "string" and menuFunctions[a] then - local opt, nl, gtln = menuFunctions[a](path, lines, y) - if type(opt) == "string" then term.setCursorBlink(false) return opt end - if type(nl) == "table" then - if #lines < 1 then table.insert(lines, "") end - y = math.min(y, #lines) - x = math.min(x, lines[y]:len() + 1) - lines = nl - elseif type(nl) == "string" then - if nl == "go to" and gtln then - x, y = 1, math.min(#lines, gtln) - cursorLoc(x, y) - end - end - end - term.setCursorBlink(true) - draw() - term.setCursorPos(x - scrollx + offx, y - scrolly + offy) -end - -local function edit(path) - -- Variables - x, y = 1, 1 - offx, offy = 0, 1 - scrollx, scrolly = 0, 0 - lines = loadFile(path) - if not lines then return "menu" end - - -- Enable brainfuck - if lines[1] == "-- Syntax: Brainfuck" then - currentLanguage = languages.brainfuck - end - - -- Clocks - local autosaveClock = os.clock() - local scrollClock = os.clock() -- To prevent redraw flicker - local liveErrorClock = os.clock() - local hasScrolled = false - - -- Draw - draw() - term.setCursorPos(x + offx, y + offy) - term.setCursorBlink(true) - - -- Main loop - local tid = os.startTimer(3) - while true do - local e, key, cx, cy = os.pullEvent() - if e == "key" and allowEditorEvent then - if key == 200 and y > 1 then - -- Up - x, y = math.min(x, lines[y - 1]:len() + 1), y - 1 - drawLine(y, y + 1) - cursorLoc(x, y) - elseif key == 208 and y < #lines then - -- Down - x, y = math.min(x, lines[y + 1]:len() + 1), y + 1 - drawLine(y, y - 1) - cursorLoc(x, y) - elseif key == 203 and x > 1 then - -- Left - x = x - 1 - local force = false - if y - scrolly + offy < offy + 1 then force = true end - cursorLoc(x, y, force) - elseif key == 205 and x < lines[y]:len() + 1 then - -- Right - x = x + 1 - local force = false - if y - scrolly + offy < offy + 1 then force = true end - cursorLoc(x, y, force) - elseif (key == 28 or key == 156) and (displayCode and true or y + scrolly - 1 == - liveErr.line) then - -- Enter - local f = nil - for _, v in pairs(standardsCompletions) do - if lines[y]:find(v) and x == #lines[y] + 1 then f = v end - end - - local _, spaces = lines[y]:find("^[ ]+") - if not spaces then spaces = 0 end - if f then - table.insert(lines, y + 1, string.rep(" ", spaces + 2)) - if not f:find("else", 1, true) and not f:find("elseif", 1, true) then - table.insert(lines, y + 2, string.rep(" ", spaces) .. - (f:find("repeat", 1, true) and "until " or f:find("{", 1, true) and "}" or - "end")) - end - x, y = spaces + 3, y + 1 - cursorLoc(x, y, true) - else - local oldLine = lines[y] - - lines[y] = lines[y]:sub(1, x - 1) - table.insert(lines, y + 1, string.rep(" ", spaces) .. oldLine:sub(x, -1)) - - x, y = spaces + 1, y + 1 - cursorLoc(x, y, true) - end - elseif key == 14 and (displayCode and true or y + scrolly - 1 == liveErr.line) then - -- Backspace - if x > 1 then - local f = false - for k, v in pairs(liveCompletions) do - if lines[y]:sub(x - 1, x - 1) == k then f = true end - end - - lines[y] = lines[y]:sub(1, x - 2) .. lines[y]:sub(x + (f and 1 or 0), -1) - drawLine(y) - x = x - 1 - cursorLoc(x, y) - elseif y > 1 then - local prevLen = lines[y - 1]:len() + 1 - lines[y - 1] = lines[y - 1] .. lines[y] - table.remove(lines, y) - x, y = prevLen, y - 1 - cursorLoc(x, y, true) - end - elseif key == 199 then - -- Home - x = 1 - local force = false - if y - scrolly + offy < offy + 1 then force = true end - cursorLoc(x, y, force) - elseif key == 207 then - -- End - x = lines[y]:len() + 1 - local force = false - if y - scrolly + offy < offy + 1 then force = true end - cursorLoc(x, y, force) - elseif key == 211 and (displayCode and true or y + scrolly - 1 == liveErr.line) then - -- Forward Delete - if x < lines[y]:len() + 1 then - lines[y] = lines[y]:sub(1, x - 1) .. lines[y]:sub(x + 1) - local force = false - if y - scrolly + offy < offy + 1 then force = true end - drawLine(y) - cursorLoc(x, y, force) - elseif y < #lines then - lines[y] = lines[y] .. lines[y + 1] - table.remove(lines, y + 1) - draw() - cursorLoc(x, y) - end - elseif key == 15 and (displayCode and true or y + scrolly - 1 == liveErr.line) then - -- Tab - lines[y] = string.rep(" ", tabWidth) .. lines[y] - x = x + 2 - local force = false - if y - scrolly + offy < offy + 1 then force = true end - drawLine(y) - cursorLoc(x, y, force) - elseif key == 201 then - -- Page up - y = math.min(math.max(y - edh, 1), #lines) - x = math.min(lines[y]:len() + 1, x) - cursorLoc(x, y, true) - elseif key == 209 then - -- Page down - y = math.min(math.max(y + edh, 1), #lines) - x = math.min(lines[y]:len() + 1, x) - cursorLoc(x, y, true) - end - elseif e == "char" and allowEditorEvent and (displayCode and true or - y + scrolly - 1 == liveErr.line) then - local shouldIgnore = false - for k, v in pairs(liveCompletions) do - if key == v and lines[y]:find(k, 1, true) and lines[y]:sub(x, x) == v then - shouldIgnore = true - end - end - - local addOne = false - if not shouldIgnore then - for k, v in pairs(liveCompletions) do - if key == k and lines[y]:sub(x, x) ~= k then key = key .. v addOne = true end - end - lines[y] = lines[y]:sub(1, x - 1) .. key .. lines[y]:sub(x, -1) - end - - x = x + (addOne and 1 or key:len()) - local force = false - if y - scrolly + offy < offy + 1 then force = true end - drawLine(y) - cursorLoc(x, y, force) - elseif e == "mouse_click" and key == 1 then - if cy > 1 then - if cx <= offx and cy - offy == liveErr.line - scrolly then - displayCode = not displayCode - drawLine(liveErr.line) - else - local oldy = y - y = math.min(math.max(scrolly + cy - offy, 1), #lines) - x = math.min(math.max(scrollx + cx - offx, 1), lines[y]:len() + 1) - if oldy ~= y then drawLine(oldy, y) end - cursorLoc(x, y) - end - else - local a = triggerMenu(cx, cy) - if a then - local opt = executeMenuItem(a, path) - if opt then return opt end - end - end - elseif e == "shortcut" then - local a = shortcuts[key .. " " .. cx] - if a then - local parent = nil - local curx = 0 - for i, mv in ipairs(menu) do - for _, iv in pairs(mv) do - if iv == a then - parent = menu[i][1] - break - end - end - if parent then break end - curx = curx + mv[1]:len() + 3 - end - local menux = curx + 2 - - -- Flash menu item - term.setCursorBlink(false) - term.setCursorPos(menux, 1) - term.setBackgroundColor(colors[theme.background]) - term.write(string.rep(" ", parent:len() + 2)) - term.setCursorPos(menux + 1, 1) - term.write(parent) - sleep(0.1) - drawMenu() - - -- Execute item - local opt = executeMenuItem(a, path) - if opt then return opt end - end - elseif e == "mouse_scroll" then - if key == -1 and scrolly > 0 then - scrolly = scrolly - 1 - if os.clock() - scrollClock > 0.0005 then - draw() - term.setCursorPos(x - scrollx + offx, y - scrolly + offy) - end - scrollClock = os.clock() - hasScrolled = true - elseif key == 1 and scrolly < #lines - edh then - scrolly = scrolly + 1 - if os.clock() - scrollClock > 0.0005 then - draw() - term.setCursorPos(x - scrollx + offx, y - scrolly + offy) - end - scrollClock = os.clock() - hasScrolled = true - end - elseif e == "timer" and key == tid then - drawLine(y) - tid = os.startTimer(3) - end - - -- Draw - if hasScrolled and os.clock() - scrollClock > 0.1 then - draw() - term.setCursorPos(x - scrollx + offx, y - scrolly + offy) - hasScrolled = false - end - - -- Autosave - if os.clock() - autosaveClock > autosaveInterval then - saveFile(path, lines) - autosaveClock = os.clock() - end - - -- Errors - if os.clock() - liveErrorClock > 1 then - local prevLiveErr = liveErr - liveErr = currentLanguage.parseError(nil) - local code = "" - for _, v in pairs(lines) do code = code .. v .. "\n" end - - liveErr = currentLanguage.getCompilerErrors(code) - liveErr.line = math.min(liveErr.line - 2, #lines) - if liveErr ~= prevLiveErr then draw() end - liveErrorClock = os.clock() - end - end - - return "menu" -end - - --- -------- Open File - -local function newFile() - local wid = w - 13 - - -- Get name - title("Lua IDE - New File") - local name = centerRead(wid, "/") - if not name or name == "" then return "menu" end - name = "/" .. name - - -- Clear - title("Lua IDE - New File") - term.setTextColor(colors[theme.textColor]) - term.setBackgroundColor(colors[theme.promptHighlight]) - for i = 8, 10 do - term.setCursorPos(w/2 - wid/2, i) - term.write(string.rep(" ", wid)) - end - term.setCursorPos(1, 9) - if fs.isDir(name) then - centerPrint("Cannot Edit a Directory!") - sleep(1.6) - return "menu" - elseif fs.exists(name) then - centerPrint("File Already Exists!") - local opt = prompt({{"Open", w/2 - 9, 14}, {"Cancel", w/2 + 2, 14}}, "horizontal") - if opt == "Open" then return "edit", name - elseif opt == "Cancel" then return "menu" end - else return "edit", name end -end - -local function openFile() - local wid = w - 13 - - -- Get name - title("Lua IDE - Open File") - local name = centerRead(wid, "/") - if not name or name == "" then return "menu" end - name = "/" .. name - - -- Clear - title("Lua IDE - New File") - term.setTextColor(colors[theme.textColor]) - term.setBackgroundColor(colors[theme.promptHighlight]) - for i = 8, 10 do - term.setCursorPos(w/2 - wid/2, i) - term.write(string.rep(" ", wid)) - end - term.setCursorPos(1, 9) - if fs.isDir(name) then - centerPrint("Cannot Open a Directory!") - sleep(1.6) - return "menu" - elseif not fs.exists(name) then - centerPrint("File Doesn't Exist!") - local opt = prompt({{"Create", w/2 - 11, 14}, {"Cancel", w/2 + 2, 14}}, "horizontal") - if opt == "Create" then return "edit", name - elseif opt == "Cancel" then return "menu" end - else return "edit", name end -end - - --- -------- Settings - -local function update() - local function draw(status) - title("LuaIDE - Update") - term.setBackgroundColor(colors[theme.prompt]) - term.setTextColor(colors[theme.textColor]) - for i = 8, 10 do - term.setCursorPos(w/2 - (status:len() + 4), i) - write(string.rep(" ", status:len() + 4)) - end - term.setCursorPos(w/2 - (status:len() + 4), 9) - term.write(" - " .. status .. " ") - - term.setBackgroundColor(colors[theme.errHighlight]) - for i = 8, 10 do - term.setCursorPos(w/2 + 2, i) - term.write(string.rep(" ", 10)) - end - term.setCursorPos(w/2 + 2, 9) - term.write(" > Cancel ") - end - - if not http then - draw("HTTP API Disabled!") - sleep(1.6) - return "settings" - end - - draw("Updating...") - local tID = os.startTimer(10) - http.request(updateURL) - while true do - local e, but, x, y = os.pullEvent() - if (e == "key" and but == 28) or - (e == "mouse_click" and x >= w/2 + 2 and x <= w/2 + 12 and y == 9) then - draw("Cancelled") - sleep(1.6) - break - elseif e == "http_success" and but == updateURL then - local new = x.readAll() - local curf = io.open(ideLocation, "r") - local cur = curf:read("*a") - curf:close() - - if cur ~= new then - draw("Update Found") - sleep(1.6) - local f = io.open(ideLocation, "w") - f:write(new) - f:close() - - draw("Click to Exit") - while true do - local e = os.pullEvent() - if e == "mouse_click" or (not isAdvanced() and e == "key") then break end - end - return "exit" - else - draw("No Updates Found!") - sleep(1.6) - break - end - elseif e == "http_failure" or (e == "timer" and but == tID) then - draw("Update Failed!") - sleep(1.6) - break - end - end - - return "settings" -end - -local function changeTheme() - title("LuaIDE - Theme") - - if isAdvanced() then - local disThemes = {"Back"} - for _, v in pairs(availableThemes) do table.insert(disThemes, v[1]) end - local t = scrollingPrompt(disThemes) - local url = nil - for _, v in pairs(availableThemes) do if v[1] == t then url = v[2] end end - - if not url then return "settings" end - if t == "Dawn (Default)" then - term.setBackgroundColor(colors[theme.backgroundHighlight]) - term.setCursorPos(3, 3) - term.clearLine() - term.write("LuaIDE - Loaded Theme!") - sleep(1.6) - - fs.delete(themeLocation) - theme = defaultTheme - return "menu" - end - - term.setBackgroundColor(colors[theme.backgroundHighlight]) - term.setCursorPos(3, 3) - term.clearLine() - term.write("LuaIDE - Downloading...") - - fs.delete("/.LuaIDE_temp_theme_file") - download(url, "/.LuaIDE_temp_theme_file") - local a = loadTheme("/.LuaIDE_temp_theme_file") - - term.setCursorPos(3, 3) - term.clearLine() - if a then - term.write("LuaIDE - Loaded Theme!") - fs.delete(themeLocation) - fs.move("/.LuaIDE_temp_theme_file", themeLocation) - theme = a - sleep(1.6) - return "menu" - end - - term.write("LuaIDE - Could Not Load Theme!") - fs.delete("/.LuaIDE_temp_theme_file") - sleep(1.6) - return "settings" - else - term.setCursorPos(1, 8) - centerPrint("Themes are not available on") - centerPrint("normal computers!") - end -end - -local function settings() - title("LuaIDE - Settings") - - local opt = prompt({{"Change Theme", w/2 - 17, 8}, {"Return to Menu", w/2 - 22, 13}, - {"Check for Updates", w/2 + 2, 8}, {"Exit IDE", w/2 + 2, 13, bg = colors[theme.err], - highlight = colors[theme.errHighlight]}}, "vertical", true) - if opt == "Change Theme" then return changeTheme() - elseif opt == "Check for Updates" then return update() - elseif opt == "Return to Menu" then return "menu" - elseif opt == "Exit IDE" then return "exit" end -end - - --- -------- Menu - -local function menu() - title("Welcome to LuaIDE " .. version) - - local opt = prompt({{"New File", w/2 - 13, 8}, {"Open File", w/2 - 14, 13}, - {"Settings", w/2 + 2, 8}, {"Exit IDE", w/2 + 2, 13, bg = colors[theme.err], - highlight = colors[theme.errHighlight]}}, "vertical", true) - if opt == "New File" then return "new" - elseif opt == "Open File" then return "open" - elseif opt == "Settings" then return "settings" - elseif opt == "Exit IDE" then return "exit" end -end - - --- -------- Main - -local function main(arguments) - local opt, data = "menu", nil - - -- Check arguments - if type(arguments) == "table" and #arguments > 0 then - local f = "/" .. shell.resolve(arguments[1]) - if fs.isDir(f) then print("Cannot edit a directory.") end - opt, data = "edit", f - end - - -- Main run loop - while true do - -- Menu - if opt == "menu" then opt = menu() end - - -- Other - if opt == "new" then opt, data = newFile() - elseif opt == "open" then opt, data = openFile() - elseif opt == "settings" then opt = settings() - end if opt == "exit" then break end - - -- Edit - if opt == "edit" and data then opt = edit(data) end - end -end - --- Load Theme -if fs.exists(themeLocation) then theme = loadTheme(themeLocation) end -if not theme and isAdvanced() then theme = defaultTheme -elseif not theme then theme = normalTheme end - --- Run -local _, err = pcall(function() - parallel.waitForAny(function() main(arguments) end, monitorKeyboardShortcuts) -end) - --- Catch errors -if err and not err:find("Terminated") then - term.setCursorBlink(false) - title("LuaIDE - Crash! D:") - - term.setBackgroundColor(colors[theme.err]) - for i = 6, 8 do - term.setCursorPos(5, i) - term.write(string.rep(" ", 36)) - end - term.setCursorPos(6, 7) - term.write("LuaIDE Has Crashed! D:") - - term.setBackgroundColor(colors[theme.background]) - term.setCursorPos(2, 10) - print(err) - - term.setBackgroundColor(colors[theme.prompt]) - local _, cy = term.getCursorPos() - for i = cy + 1, cy + 4 do - term.setCursorPos(5, i) - term.write(string.rep(" ", 36)) - end - term.setCursorPos(6, cy + 2) - term.write("Please report this error to") - term.setCursorPos(6, cy + 3) - term.write("GravityScore! ") - - term.setBackgroundColor(colors[theme.background]) - if isAdvanced() then centerPrint("Click to Exit...", h - 1) - else centerPrint("Press Any Key to Exit...", h - 1) end - while true do - local e = os.pullEvent() - if e == "mouse_click" or (not isAdvanced() and e == "key") then break end - end - - -- Prevent key from being shown - os.queueEvent("") - os.pullEvent() -end - --- Exit -term.setBackgroundColor(colors.black) -term.setTextColor(colors.white) -term.clear() -term.setCursorPos(1, 1) -centerPrint("Thank You for Using Lua IDE " .. version) -centerPrint("Made by GravityScore") \ No newline at end of file diff --git a/Programs/LuaIDE.bup/Info.meta b/Programs/LuaIDE.bup/Info.meta new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/Programs/LuaIDE.bup/Info.meta @@ -0,0 +1 @@ + diff --git a/Programs/LuaIDE.bup/luaide.lua b/Programs/LuaIDE.bup/luaide.lua new file mode 100644 index 0000000..5e06e6b --- /dev/null +++ b/Programs/LuaIDE.bup/luaide.lua @@ -0,0 +1,2224 @@ + +-- +-- Lua IDE +-- Made by GravityScore +-- + + + + +-- Variables + +local version = "1.1" +local arguments = {...} + + +local w, h = term.getSize() +local tabWidth = 2 + + +local autosaveInterval = 20 +local allowEditorEvent = true +local keyboardShortcutTimeout = 0.4 + + +local clipboard = nil + + +local theme = { + background = colors.gray, + titleBar = colors.lightGray, + + top = colors.lightBlue, + bottom = colors.cyan, + + button = colors.cyan, + buttonHighlighted = colors.lightBlue, + + dangerButton = colors.red, + dangerButtonHighlighted = colors.pink, + + text = colors.white, + folder = colors.lime, + readOnly = colors.red, +} + + +local languages = {} +local currentLanguage = {} + + +local updateURL = "https://raw.github.com/GravityScore/LuaIDE/master/computercraft/ide.lua" +local ideLocation = "/" .. shell.getRunningProgram() +local themeLocation = "/.luaide_theme" + +local function isAdvanced() + return term.isColor and term.isColor() +end + + + + +-- -------- Utilities + +local function modRead(properties) + local w, h = term.getSize() + local defaults = {replaceChar = nil, history = nil, visibleLength = nil, textLength = nil, + liveUpdates = nil, exitOnKey = nil} + if not properties then properties = {} end + for k, v in pairs(defaults) do if not properties[k] then properties[k] = v end end + if properties.replaceChar then properties.replaceChar = properties.replaceChar:sub(1, 1) end + if not properties.visibleLength then properties.visibleLength = w end + + local sx, sy = term.getCursorPos() + local line = "" + local pos = 0 + local historyPos = nil + + local function redraw(repl) + local scroll = 0 + if properties.visibleLength and sx + pos > properties.visibleLength + 1 then + scroll = (sx + pos) - (properties.visibleLength + 1) + end + + term.setCursorPos(sx, sy) + local a = repl or properties.replaceChar + if a then term.write(string.rep(a, line:len() - scroll)) + else term.write(line:sub(scroll + 1, -1)) end + term.setCursorPos(sx + pos - scroll, sy) + end + + local function sendLiveUpdates(event, ...) + if type(properties.liveUpdates) == "function" then + local ox, oy = term.getCursorPos() + local a, data = properties.liveUpdates(line, event, ...) + if a == true and data == nil then + term.setCursorBlink(false) + return line + elseif a == true and data ~= nil then + term.setCursorBlink(false) + return data + end + term.setCursorPos(ox, oy) + end + end + + term.setCursorBlink(true) + while true do + local e, but, x, y, p4, p5 = os.pullEvent() + + if e == "char" then + local s = false + if properties.textLength and line:len() < properties.textLength then s = true + elseif not properties.textLength then s = true end + + local canType = true + if not properties.grantPrint and properties.refusePrint then + local canTypeKeys = {} + if type(properties.refusePrint) == "table" then + for _, v in pairs(properties.refusePrint) do + table.insert(canTypeKeys, tostring(v):sub(1, 1)) + end + elseif type(properties.refusePrint) == "string" then + for char in properties.refusePrint:gmatch(".") do + table.insert(canTypeKeys, char) + end + end + for _, v in pairs(canTypeKeys) do if but == v then canType = false end end + elseif properties.grantPrint then + canType = false + local canTypeKeys = {} + if type(properties.grantPrint) == "table" then + for _, v in pairs(properties.grantPrint) do + table.insert(canTypeKeys, tostring(v):sub(1, 1)) + end + elseif type(properties.grantPrint) == "string" then + for char in properties.grantPrint:gmatch(".") do + table.insert(canTypeKeys, char) + end + end + for _, v in pairs(canTypeKeys) do if but == v then canType = true end end + end + + if s and canType then + line = line:sub(1, pos) .. but .. line:sub(pos + 1, -1) + pos = pos + 1 + redraw() + end + elseif e == "key" then + if but == keys.enter then break + elseif but == keys.left then if pos > 0 then pos = pos - 1 redraw() end + elseif but == keys.right then if pos < line:len() then pos = pos + 1 redraw() end + elseif (but == keys.up or but == keys.down) and properties.history then + redraw(" ") + if but == keys.up then + if historyPos == nil and #properties.history > 0 then + historyPos = #properties.history + elseif historyPos > 1 then + historyPos = historyPos - 1 + end + elseif but == keys.down then + if historyPos == #properties.history then historyPos = nil + elseif historyPos ~= nil then historyPos = historyPos + 1 end + end + + if properties.history and historyPos then + line = properties.history[historyPos] + pos = line:len() + else + line = "" + pos = 0 + end + + redraw() + local a = sendLiveUpdates("history") + if a then return a end + elseif but == keys.backspace and pos > 0 then + redraw(" ") + line = line:sub(1, pos - 1) .. line:sub(pos + 1, -1) + pos = pos - 1 + redraw() + local a = sendLiveUpdates("delete") + if a then return a end + elseif but == keys.home then + pos = 0 + redraw() + elseif but == keys.delete and pos < line:len() then + redraw(" ") + line = line:sub(1, pos) .. line:sub(pos + 2, -1) + redraw() + local a = sendLiveUpdates("delete") + if a then return a end + elseif but == keys["end"] then + pos = line:len() + redraw() + elseif properties.exitOnKey then + if but == properties.exitOnKey or (properties.exitOnKey == "control" and + (but == 29 or but == 157)) then + term.setCursorBlink(false) + return nil + end + end + end + local a = sendLiveUpdates(e, but, x, y, p4, p5) + if a then return a end + end + + term.setCursorBlink(false) + if line ~= nil then line = line:gsub("^%s*(.-)%s*$", "%1") end + return line +end + + +-- -------- Themes + +local defaultTheme = { + background = "gray", + backgroundHighlight = "lightGray", + prompt = "cyan", + promptHighlight = "lightBlue", + err = "red", + errHighlight = "pink", + + editorBackground = "gray", + editorLineHightlight = "lightBlue", + editorLineNumbers = "gray", + editorLineNumbersHighlight = "lightGray", + editorError = "pink", + editorErrorHighlight = "red", + + textColor = "white", + conditional = "yellow", + constant = "orange", + ["function"] = "magenta", + string = "red", + comment = "lime" +} + +local normalTheme = { + background = "black", + backgroundHighlight = "black", + prompt = "black", + promptHighlight = "black", + err = "black", + errHighlight = "black", + + editorBackground = "black", + editorLineHightlight = "black", + editorLineNumbers = "black", + editorLineNumbersHighlight = "white", + editorError = "black", + editorErrorHighlight = "black", + + textColor = "white", + conditional = "white", + constant = "white", + ["function"] = "white", + string = "white", + comment = "white" +} + +local availableThemes = { + {"Water (Default)", "https://raw.github.com/GravityScore/LuaIDE/master/themes/default.txt"}, + {"Fire", "https://raw.github.com/GravityScore/LuaIDE/master/themes/fire.txt"}, + {"Sublime Text 2", "https://raw.github.com/GravityScore/LuaIDE/master/themes/st2.txt"}, + {"Midnight", "https://raw.github.com/GravityScore/LuaIDE/master/themes/midnight.txt"}, + {"TheOriginalBIT", "https://raw.github.com/GravityScore/LuaIDE/master/themes/bit.txt"}, + {"Superaxander", "https://raw.github.com/GravityScore/LuaIDE/master/themes/superaxander.txt"}, + {"Forest", "https://raw.github.com/GravityScore/LuaIDE/master/themes/forest.txt"}, + {"Night", "https://raw.github.com/GravityScore/LuaIDE/master/themes/night.txt"}, + {"Original", "https://raw.github.com/GravityScore/LuaIDE/master/themes/original.txt"}, +} + +local function loadTheme(path) + local f = io.open(path) + local l = f:read("*l") + local config = {} + while l ~= nil do + local k, v = string.match(l, "^(%a+)=(%a+)") + if k and v then config[k] = v end + l = f:read("*l") + end + f:close() + return config +end + +-- Load Theme +if isAdvanced() then theme = defaultTheme +else theme = normalTheme end + + +-- -------- Drawing + +local function centerPrint(text, ny) + if type(text) == "table" then for _, v in pairs(text) do centerPrint(v) end + else + local x, y = term.getCursorPos() + local w, h = term.getSize() + term.setCursorPos(w/2 - text:len()/2 + (#text % 2 == 0 and 1 or 0), ny or y) + print(text) + end +end + +local function title(t) + term.setTextColor(colors[theme.textColor]) + term.setBackgroundColor(colors[theme.background]) + term.clear() + + term.setBackgroundColor(colors[theme.backgroundHighlight]) + for i = 2, 4 do term.setCursorPos(1, i) term.clearLine() end + term.setCursorPos(3, 3) + term.write(t) +end + +local function centerRead(wid, begt) + local function liveUpdate(line, e, but, x, y, p4, p5) + if isAdvanced() and e == "mouse_click" and x >= w/2 - wid/2 and x <= w/2 - wid/2 + 10 + and y >= 13 and y <= 15 then + return true, "" + end + end + + if not begt then begt = "" end + term.setTextColor(colors[theme.textColor]) + term.setBackgroundColor(colors[theme.promptHighlight]) + for i = 8, 10 do + term.setCursorPos(w/2 - wid/2, i) + term.write(string.rep(" ", wid)) + end + + if isAdvanced() then + term.setBackgroundColor(colors[theme.errHighlight]) + for i = 13, 15 do + term.setCursorPos(w/2 - wid/2 + 1, i) + term.write(string.rep(" ", 10)) + end + term.setCursorPos(w/2 - wid/2 + 2, 14) + term.write("> Cancel") + end + + term.setBackgroundColor(colors[theme.promptHighlight]) + term.setCursorPos(w/2 - wid/2 + 1, 9) + term.write("> " .. begt) + return modRead({visibleLength = w/2 + wid/2, liveUpdates = liveUpdate}) +end + + +-- -------- Prompt + +local function prompt(list, dir, isGrid) + local function draw(sel) + for i, v in ipairs(list) do + if i == sel then term.setBackgroundColor(v.highlight or colors[theme.promptHighlight]) + else term.setBackgroundColor(v.bg or colors[theme.prompt]) end + term.setTextColor(v.tc or colors[theme.textColor]) + for i = -1, 1 do + term.setCursorPos(v[2], v[3] + i) + term.write(string.rep(" ", v[1]:len() + 4)) + end + + term.setCursorPos(v[2], v[3]) + if i == sel then + term.setBackgroundColor(v.highlight or colors[theme.promptHighlight]) + term.write(" > ") + else term.write(" - ") end + term.write(v[1] .. " ") + end + end + + local key1 = dir == "horizontal" and 203 or 200 + local key2 = dir == "horizontal" and 205 or 208 + local sel = 1 + draw(sel) + + while true do + local e, but, x, y = os.pullEvent() + if e == "key" and but == 28 then + return list[sel][1] + elseif e == "key" and but == key1 and sel > 1 then + sel = sel - 1 + draw(sel) + elseif e == "key" and but == key2 and ((err == true and sel < #list - 1) or (sel < #list)) then + sel = sel + 1 + draw(sel) + elseif isGrid and e == "key" and but == 203 and sel > 2 then + sel = sel - 2 + draw(sel) + elseif isGrid and e == "key" and but == 205 and sel < 3 then + sel = sel + 2 + draw(sel) + elseif e == "mouse_click" then + for i, v in ipairs(list) do + if x >= v[2] - 1 and x <= v[2] + v[1]:len() + 3 and y >= v[3] - 1 and y <= v[3] + 1 then + return list[i][1] + end + end + end + end +end + +local function scrollingPrompt(list) + local function draw(items, sel, loc) + for i, v in ipairs(items) do + local bg = colors[theme.prompt] + local bghigh = colors[theme.promptHighlight] + if v:find("Back") or v:find("Return") then + bg = colors[theme.err] + bghigh = colors[theme.errHighlight] + end + + if i == sel then term.setBackgroundColor(bghigh) + else term.setBackgroundColor(bg) end + term.setTextColor(colors[theme.textColor]) + for x = -1, 1 do + term.setCursorPos(3, (i * 4) + x + 4) + term.write(string.rep(" ", w - 13)) + end + + term.setCursorPos(3, i * 4 + 4) + if i == sel then + term.setBackgroundColor(bghigh) + term.write(" > ") + else term.write(" - ") end + term.write(v .. " ") + end + end + + local function updateDisplayList(items, loc, len) + local ret = {} + for i = 1, len do + local item = items[i + loc - 1] + if item then table.insert(ret, item) end + end + return ret + end + + -- Variables + local sel = 1 + local loc = 1 + local len = 3 + local disList = updateDisplayList(list, loc, len) + draw(disList, sel, loc) + + -- Loop + while true do + local e, key, x, y = os.pullEvent() + + if e == "mouse_click" then + for i, v in ipairs(disList) do + if x >= 3 and x <= w - 11 and y >= i * 4 + 3 and y <= i * 4 + 5 then return v end + end + elseif e == "key" and key == 200 then + if sel > 1 then + sel = sel - 1 + draw(disList, sel, loc) + elseif loc > 1 then + loc = loc - 1 + disList = updateDisplayList(list, loc, len) + draw(disList, sel, loc) + end + elseif e == "key" and key == 208 then + if sel < len then + sel = sel + 1 + draw(disList, sel, loc) + elseif loc + len - 1 < #list then + loc = loc + 1 + disList = updateDisplayList(list, loc, len) + draw(disList, sel, loc) + end + elseif e == "mouse_scroll" then + os.queueEvent("key", key == -1 and 200 or 208) + elseif e == "key" and key == 28 then + return disList[sel] + end + end +end + +function monitorKeyboardShortcuts() + local ta, tb = nil, nil + local allowChar = false + local shiftPressed = false + while true do + local event, char = os.pullEvent() + if event == "key" and (char == 42 or char == 52) then + shiftPressed = true + tb = os.startTimer(keyboardShortcutTimeout) + elseif event == "key" and (char == 29 or char == 157 or char == 219 or char == 220) then + allowEditorEvent = false + allowChar = true + ta = os.startTimer(keyboardShortcutTimeout) + elseif event == "key" and allowChar then + local name = nil + for k, v in pairs(keys) do + if v == char then + if shiftPressed then os.queueEvent("shortcut", "ctrl shift", k:lower()) + else os.queueEvent("shortcut", "ctrl", k:lower()) end + sleep(0.005) + allowEditorEvent = true + end + end + if shiftPressed then os.queueEvent("shortcut", "ctrl shift", char) + else os.queueEvent("shortcut", "ctrl", char) end + elseif event == "timer" and char == ta then + allowEditorEvent = true + allowChar = false + elseif event == "timer" and char == tb then + shiftPressed = false + end + end +end + + +-- -------- Saving and Loading + +local function download(url, path) + for i = 1, 3 do + local response = http.get(url) + if response then + local data = response.readAll() + response.close() + if path then + local f = io.open(path, "w") + f:write(data) + f:close() + end + return true + end + end + + return false +end + +local function saveFile(path, lines) + local dir = path:sub(1, path:len() - fs.getName(path):len()) + if not fs.exists(dir) then fs.makeDir(dir) end + if not fs.isDir(path) and not fs.isReadOnly(path) then + local a = "" + for _, v in pairs(lines) do a = a .. v .. "\n" end + + local f = io.open(path, "w") + f:write(a) + f:close() + return true + else return false end +end + +local function loadFile(path) + if not fs.exists(path) then + local dir = path:sub(1, path:len() - fs.getName(path):len()) + if not fs.exists(dir) then fs.makeDir(dir) end + local f = io.open(path, "w") + f:write("") + f:close() + end + + local l = {} + if fs.exists(path) and not fs.isDir(path) then + local f = io.open(path, "r") + if f then + local a = f:read("*l") + while a do + table.insert(l, a) + a = f:read("*l") + end + f:close() + end + else return nil end + + if #l < 1 then table.insert(l, "") end + return l +end + + +-- -------- Languages + +languages.lua = {} +languages.brainfuck = {} +languages.none = {} + +-- Lua + +languages.lua.helpTips = { + "A function you tried to call doesn't exist.", + "You made a typo.", + "The index of an array is nil.", + "The wrong variable type was passed.", + "A function/variable doesn't exist.", + "You missed an 'end'.", + "You missed a 'then'.", + "You declared a variable incorrectly.", + "One of your variables is mysteriously nil." +} + +languages.lua.defaultHelpTips = { + 2, 5 +} + +languages.lua.errors = { + ["Attempt to call nil."] = {1, 2}, + ["Attempt to index nil."] = {3, 2}, + [".+ expected, got .+"] = {4, 2, 9}, + ["'end' expected"] = {6, 2}, + ["'then' expected"] = {7, 2}, + ["'=' expected"] = {8, 2} +} + +languages.lua.keywords = { + ["and"] = "conditional", + ["break"] = "conditional", + ["do"] = "conditional", + ["else"] = "conditional", + ["elseif"] = "conditional", + ["end"] = "conditional", + ["for"] = "conditional", + ["function"] = "conditional", + ["if"] = "conditional", + ["in"] = "conditional", + ["local"] = "conditional", + ["not"] = "conditional", + ["or"] = "conditional", + ["repeat"] = "conditional", + ["return"] = "conditional", + ["then"] = "conditional", + ["until"] = "conditional", + ["while"] = "conditional", + + ["true"] = "constant", + ["false"] = "constant", + ["nil"] = "constant", + + ["print"] = "function", + ["write"] = "function", + ["sleep"] = "function", + ["pairs"] = "function", + ["ipairs"] = "function", + ["loadstring"] = "function", + ["loadfile"] = "function", + ["dofile"] = "function", + ["rawset"] = "function", + ["rawget"] = "function", + ["setfenv"] = "function", + ["getfenv"] = "function", +} + +languages.lua.parseError = function(e) + local ret = {filename = "unknown", line = -1, display = "Unknown!", err = ""} + if e and e ~= "" then + ret.err = e + if e:find(":") then + ret.filename = e:sub(1, e:find(":") - 1):gsub("^%s*(.-)%s*$", "%1") + -- The "" is needed to circumvent a CC bug + e = (e:sub(e:find(":") + 1) .. ""):gsub("^%s*(.-)%s*$", "%1") + if e:find(":") then + ret.line = e:sub(1, e:find(":") - 1) + e = e:sub(e:find(":") + 2):gsub("^%s*(.-)%s*$", "%1") .. "" + end + end + ret.display = e:sub(1, 1):upper() .. e:sub(2, -1) .. "." + end + + return ret +end + +languages.lua.getCompilerErrors = function(code) + code = "local function ee65da6af1cb6f63fee9a081246f2fd92b36ef2(...)\n\n" .. code .. "\n\nend" + local fn, err = loadstring(code) + if not err then + local _, e = pcall(fn) + if e then err = e end + end + + if err then + local a = err:find("]", 1, true) + if a then err = "string" .. err:sub(a + 1, -1) end + local ret = languages.lua.parseError(err) + if tonumber(ret.line) then ret.line = tonumber(ret.line) end + return ret + else return languages.lua.parseError(nil) end +end + +languages.lua.run = function(path, ar) + local fn, err = loadfile(path) + setfenv(fn, getfenv()) + if not err then + _, err = pcall(function() fn(unpack(ar)) end) + end + return err +end + + +-- Brainfuck + +languages.brainfuck.helpTips = { + "Well idk...", + "Isn't this the whole point of the language?", + "Ya know... Not being able to debug it?", + "You made a typo." +} + +languages.brainfuck.defaultHelpTips = { + 1, 2, 3 +} + +languages.brainfuck.errors = { + ["No matching '['"] = {1, 2, 3, 4} +} + +languages.brainfuck.keywords = {} + +languages.brainfuck.parseError = function(e) + local ret = {filename = "unknown", line = -1, display = "Unknown!", err = ""} + if e and e ~= "" then + ret.err = e + ret.line = e:sub(1, e:find(":") - 1) + e = e:sub(e:find(":") + 2):gsub("^%s*(.-)%s*$", "%1") .. "" + ret.display = e:sub(1, 1):upper() .. e:sub(2, -1) .. "." + end + + return ret +end + +languages.brainfuck.mapLoops = function(code) + -- Map loops + local loopLocations = {} + local loc = 1 + local line = 1 + for let in string.gmatch(code, ".") do + if let == "[" then + loopLocations[loc] = true + elseif let == "]" then + local found = false + for i = loc, 1, -1 do + if loopLocations[i] == true then + loopLocations[i] = loc + found = true + end + end + + if not found then + return line .. ": No matching '['" + end + end + + if let == "\n" then line = line + 1 end + loc = loc + 1 + end + return loopLocations +end + +languages.brainfuck.getCompilerErrors = function(code) + local a = languages.brainfuck.mapLoops(code) + if type(a) == "string" then return languages.brainfuck.parseError(a) + else return languages.brainfuck.parseError(nil) end +end + +languages.brainfuck.run = function(path) + -- Read from file + local f = io.open(path, "r") + local content = f:read("*a") + f:close() + + -- Define environment + local dataCells = {} + local dataPointer = 1 + local instructionPointer = 1 + + -- Map loops + local loopLocations = languages.brainfuck.mapLoops(content) + if type(loopLocations) == "string" then return loopLocations end + + -- Execute code + while true do + local let = content:sub(instructionPointer, instructionPointer) + + if let == ">" then + dataPointer = dataPointer + 1 + if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end + elseif let == "<" then + if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end + dataPointer = dataPointer - 1 + if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end + elseif let == "+" then + if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end + dataCells[tostring(dataPointer)] = dataCells[tostring(dataPointer)] + 1 + elseif let == "-" then + if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end + dataCells[tostring(dataPointer)] = dataCells[tostring(dataPointer)] - 1 + elseif let == "." then + if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end + if term.getCursorPos() >= w then print("") end + write(string.char(math.max(1, dataCells[tostring(dataPointer)]))) + elseif let == "," then + if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end + term.setCursorBlink(true) + local e, but = os.pullEvent("char") + term.setCursorBlink(false) + dataCells[tostring(dataPointer)] = string.byte(but) + if term.getCursorPos() >= w then print("") end + write(but) + elseif let == "/" then + if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end + if term.getCursorPos() >= w then print("") end + write(dataCells[tostring(dataPointer)]) + elseif let == "[" then + if dataCells[tostring(dataPointer)] == 0 then + for k, v in pairs(loopLocations) do + if k == instructionPointer then instructionPointer = v end + end + end + elseif let == "]" then + for k, v in pairs(loopLocations) do + if v == instructionPointer then instructionPointer = k - 1 end + end + end + + instructionPointer = instructionPointer + 1 + if instructionPointer > content:len() then print("") break end + end +end + +-- None + +languages.none.helpTips = {} +languages.none.defaultHelpTips = {} +languages.none.errors = {} +languages.none.keywords = {} + +languages.none.parseError = function(err) + return {filename = "", line = -1, display = "", err = ""} +end + +languages.none.getCompilerErrors = function(code) + return languages.none.parseError(nil) +end + +languages.none.run = function(path) end + + +-- Load language +currentLanguage = languages.lua + + +-- -------- Run GUI + +local function viewErrorHelp(e) + title("LuaIDE - Error Help") + + local tips = nil + for k, v in pairs(currentLanguage.errors) do + if e.display:find(k) then tips = v break end + end + + term.setBackgroundColor(colors[theme.err]) + for i = 6, 8 do + term.setCursorPos(5, i) + term.write(string.rep(" ", 35)) + end + + term.setBackgroundColor(colors[theme.prompt]) + for i = 10, 18 do + term.setCursorPos(5, i) + term.write(string.rep(" ", 46)) + end + + if tips then + term.setBackgroundColor(colors[theme.err]) + term.setCursorPos(6, 7) + term.write("Error Help") + + term.setBackgroundColor(colors[theme.prompt]) + for i, v in ipairs(tips) do + term.setCursorPos(7, i + 10) + term.write("- " .. currentLanguage.helpTips[v]) + end + else + term.setBackgroundColor(colors[theme.err]) + term.setCursorPos(6, 7) + term.write("No Error Tips Available!") + + term.setBackgroundColor(colors[theme.prompt]) + term.setCursorPos(6, 11) + term.write("There are no error tips available, but") + term.setCursorPos(6, 12) + term.write("you could see if it was any of these:") + + for i, v in ipairs(currentLanguage.defaultHelpTips) do + term.setCursorPos(7, i + 12) + term.write("- " .. currentLanguage.helpTips[v]) + end + end + + prompt({{"Back", w - 8, 7}}, "horizontal") +end + +local function run(path, lines, useArgs) + local ar = {} + if useArgs then + title("LuaIDE - Run " .. fs.getName(path)) + local s = centerRead(w - 13, fs.getName(path) .. " ") + for m in string.gmatch(s, "[^ \t]+") do ar[#ar + 1] = m:gsub("^%s*(.-)%s*$", "%1") end + end + + saveFile(path, lines) + term.setCursorBlink(false) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + local err = currentLanguage.run(path, ar) + + term.setBackgroundColor(colors.black) + print("\n") + if err then + if isAdvanced() then term.setTextColor(colors.red) end + centerPrint("The program has crashed!") + end + term.setTextColor(colors.white) + centerPrint("Press any key to return to LuaIDE...") + while true do + local e = os.pullEvent() + if e == "key" then break end + end + + -- To prevent key from showing up in editor + os.queueEvent("") + os.pullEvent() + + if err then + if currentLanguage == languages.lua and err:find("]") then + err = fs.getName(path) .. err:sub(err:find("]", 1, true) + 1, -1) + end + + while true do + title("LuaIDE - Error!") + + term.setBackgroundColor(colors[theme.err]) + for i = 6, 8 do + term.setCursorPos(3, i) + term.write(string.rep(" ", w - 5)) + end + term.setCursorPos(4, 7) + term.write("The program has crashed!") + + term.setBackgroundColor(colors[theme.prompt]) + for i = 10, 14 do + term.setCursorPos(3, i) + term.write(string.rep(" ", w - 5)) + end + + local formattedErr = currentLanguage.parseError(err) + term.setCursorPos(4, 11) + term.write("Line: " .. formattedErr.line) + term.setCursorPos(4, 12) + term.write("Error:") + term.setCursorPos(5, 13) + + local a = formattedErr.display + local b = nil + if a:len() > w - 8 then + for i = a:len(), 1, -1 do + if a:sub(i, i) == " " then + b = a:sub(i + 1, -1) + a = a:sub(1, i) + break + end + end + end + + term.write(a) + if b then + term.setCursorPos(5, 14) + term.write(b) + end + + local opt = prompt({{"Error Help", w/2 - 15, 17}, {"Go To Line", w/2 + 2, 17}}, + "horizontal") + if opt == "Error Help" then + viewErrorHelp(formattedErr) + elseif opt == "Go To Line" then + -- To prevent key from showing up in editor + os.queueEvent("") + os.pullEvent() + + return "go to", tonumber(formattedErr.line) + end + end + end +end + + +-- -------- Functions + +local function goto() + term.setBackgroundColor(colors[theme.backgroundHighlight]) + term.setCursorPos(2, 1) + term.clearLine() + term.write("Line: ") + local line = modRead({visibleLength = w - 2}) + + local num = tonumber(line) + if num and num > 0 then return num + else + term.setCursorPos(2, 1) + term.clearLine() + term.write("Not a line number!") + sleep(1.6) + return nil + end +end + +local function setsyntax() + local opts = { + "[Lua] Brainfuck None ", + " Lua [Brainfuck] None ", + " Lua Brainfuck [None]" + } + local sel = 1 + + term.setCursorBlink(false) + term.setBackgroundColor(colors[theme.backgroundHighlight]) + term.setCursorPos(2, 1) + term.clearLine() + term.write(opts[sel]) + while true do + local e, but, x, y = os.pullEvent("key") + if but == 203 then + sel = math.max(1, sel - 1) + term.setCursorPos(2, 1) + term.clearLine() + term.write(opts[sel]) + elseif but == 205 then + sel = math.min(#opts, sel + 1) + term.setCursorPos(2, 1) + term.clearLine() + term.write(opts[sel]) + elseif but == 28 then + if sel == 1 then currentLanguage = languages.lua + elseif sel == 2 then currentLanguage = languages.brainfuck + elseif sel == 3 then currentLanguage = languages.none end + term.setCursorBlink(true) + return + end + end +end + + +-- -------- Re-Indenting + +local tabWidth = 2 + +local comments = {} +local strings = {} + +local increment = { + "if%s+.+%s+then%s*$", + "for%s+.+%s+do%s*$", + "while%s+.+%s+do%s*$", + "repeat%s*$", + "function%s+[a-zA-Z_0-9]\(.*\)%s*$" +} + +local decrement = { + "end", + "until%s+.+" +} + +local special = { + "else%s*$", + "elseif%s+.+%s+then%s*$" +} + +local function check(func) + for _, v in pairs(func) do + local cLineStart = v["lineStart"] + local cLineEnd = v["lineEnd"] + local cCharStart = v["charStart"] + local cCharEnd = v["charEnd"] + + if line >= cLineStart and line <= cLineEnd then + if line == cLineStart then return cCharStart < charNumb + elseif line == cLineEnd then return cCharEnd > charNumb + else return true end + end + end +end + +local function isIn(line, loc) + if check(comments) then return true end + if check(strings) then return true end + return false +end + +local function setComment(ls, le, cs, ce) + comments[#comments + 1] = {} + comments[#comments].lineStart = ls + comments[#comments].lineEnd = le + comments[#comments].charStart = cs + comments[#comments].charEnd = ce +end + +local function setString(ls, le, cs, ce) + strings[#strings + 1] = {} + strings[#strings].lineStart = ls + strings[#strings].lineEnd = le + strings[#strings].charStart = cs + strings[#strings].charEnd = ce +end + +local function map(contents) + local inCom = false + local inStr = false + + for i = 1, #contents do + if content[i]:find("%-%-%[%[") and not inStr and not inCom then + local cStart = content[i]:find("%-%-%[%[") + setComment(i, nil, cStart, nil) + inCom = true + elseif content[i]:find("%-%-%[=%[") and not inStr and not inCom then + local cStart = content[i]:find("%-%-%[=%[") + setComment(i, nil, cStart, nil) + inCom = true + elseif content[i]:find("%[%[") and not inStr and not inCom then + local cStart = content[i]:find("%[%[") + setString(i, nil, cStart, nil) + inStr = true + elseif content[i]:find("%[=%[") and not inStr and not inCom then + local cStart = content[i]:find("%[=%[") + setString(i, nil, cStart, nil) + inStr = true + end + + if content[i]:find("%]%]") and inStr and not inCom then + local cStart, cEnd = content[i]:find("%]%]") + strings[#strings].lineEnd = i + strings[#strings].charEnd = cEnd + inStr = false + elseif content[i]:find("%]=%]") and inStr and not inCom then + local cStart, cEnd = content[i]:find("%]=%]") + strings[#strings].lineEnd = i + strings[#strings].charEnd = cEnd + inStr = false + end + + if content[i]:find("%]%]") and not inStr and inCom then + local cStart, cEnd = content[i]:find("%]%]") + comments[#comments].lineEnd = i + comments[#comments].charEnd = cEnd + inCom = false + elseif content[i]:find("%]=%]") and not inStr and inCom then + local cStart, cEnd = content[i]:find("%]=%]") + comments[#comments].lineEnd = i + comments[#comments].charEnd = cEnd + inCom = false + end + + if content[i]:find("%-%-") and not inStr and not inCom then + local cStart = content[i]:find("%-%-") + setComment(i, i, cStart, -1) + elseif content[i]:find("'") and not inStr and not inCom then + local cStart, cEnd = content[i]:find("'") + local nextChar = content[i]:sub(cEnd + 1, string.len(content[i])) + local _, cEnd = nextChar:find("'") + setString(i, i, cStart, cEnd) + elseif content[i]:find('"') and not inStr and not inCom then + local cStart, cEnd = content[i]:find('"') + local nextChar = content[i]:sub(cEnd + 1, string.len(content[i])) + local _, cEnd = nextChar:find('"') + setString(i, i, cStart, cEnd) + end + end +end + +local function reindent(contents) + local err = nil + if currentLanguage ~= languages.lua then + err = "Cannot indent languages other than Lua!" + elseif currentLanguage.getCompilerErrors(table.concat(contents, "\n")).line ~= -1 then + err = "Cannot indent a program with errors!" + end + + if err then + term.setCursorBlink(false) + term.setCursorPos(2, 1) + term.setBackgroundColor(colors[theme.backgroundHighlight]) + term.clearLine() + term.write(err) + sleep(1.6) + return contents + end + + local new = {} + local level = 0 + for k, v in pairs(contents) do + local incrLevel = false + local foundIncr = false + for _, incr in pairs(increment) do + if v:find(incr) and not isIn(k, v:find(incr)) then + incrLevel = true + end + if v:find(incr:sub(1, -2)) and not isIn(k, v:find(incr)) then + foundIncr = true + end + end + + local decrLevel = false + if not incrLevel then + for _, decr in pairs(decrement) do + if v:find(decr) and not isIn(k, v:find(decr)) and not foundIncr then + level = math.max(0, level - 1) + decrLevel = true + end + end + end + + if not decrLevel then + for _, sp in pairs(special) do + if v:find(sp) and not isIn(k, v:find(sp)) then + incrLevel = true + level = math.max(0, level - 1) + end + end + end + + new[k] = string.rep(" ", level * tabWidth) .. v + if incrLevel then level = level + 1 end + end + + return new +end + + +-- -------- Menu + +local menu = { + [1] = {"File", +-- "About", +-- "Settings", +-- "", + "New File ^+N", + "Open File ^+O", + "Save File ^+S", + "Close ^+W", + "Print ^+P", + "Quit ^+Q" + }, [2] = {"Edit", + "Cut Line ^+X", + "Copy Line ^+C", + "Paste Line ^+V", + "Delete Line", + "Clear Line" + }, [3] = {"Functions", + "Go To Line ^+G", + "Re-Indent ^+I", + "Set Syntax ^+E", + "Start of Line ^+<", + "End of Line ^+>" + }, [4] = {"Run", + "Run Program ^+R", + "Run w/ Args ^+Shift+R" + } +} + +local shortcuts = { + -- File + ["ctrl n"] = "New File ^+N", + ["ctrl o"] = "Open File ^+O", + ["ctrl s"] = "Save File ^+S", + ["ctrl w"] = "Close ^+W", + ["ctrl p"] = "Print ^+P", + ["ctrl q"] = "Quit ^+Q", + + -- Edit + ["ctrl x"] = "Cut Line ^+X", + ["ctrl c"] = "Copy Line ^+C", + ["ctrl v"] = "Paste Line ^+V", + + -- Functions + ["ctrl g"] = "Go To Line ^+G", + ["ctrl i"] = "Re-Indent ^+I", + ["ctrl e"] = "Set Syntax ^+E", + ["ctrl 203"] = "Start of Line ^+<", + ["ctrl 205"] = "End of Line ^+>", + + -- Run + ["ctrl r"] = "Run Program ^+R", + ["ctrl shift r"] = "Run w/ Args ^+Shift+R" +} + +local menuFunctions = { + -- File +-- ["About"] = function() end, +-- ["Settings"] = function() end, + ["New File ^+N"] = function(path, lines) saveFile(path, lines) return "new" end, + ["Open File ^+O"] = function(path, lines) saveFile(path, lines) return "open" end, + ["Save File ^+S"] = function(path, lines) saveFile(path, lines) end, + ["Close ^+W"] = function(path, lines) saveFile(path, lines) return "menu" end, + ["Print ^+P"] = function(path, lines) saveFile(path, lines) return nil end, + ["Quit ^+Q"] = function(path, lines) saveFile(path, lines) return "exit" end, + + -- Edit + ["Cut Line ^+X"] = function(path, lines, y) + clipboard = lines[y] table.remove(lines, y) return nil, lines end, + ["Copy Line ^+C"] = function(path, lines, y) clipboard = lines[y] end, + ["Paste Line ^+V"] = function(path, lines, y) + if clipboard then table.insert(lines, y, clipboard) end return nil, lines end, + ["Delete Line"] = function(path, lines, y) table.remove(lines, y) return nil, lines end, + ["Clear Line"] = function(path, lines, y) lines[y] = "" return nil, lines, "cursor" end, + + -- Functions + ["Go To Line ^+G"] = function() return nil, "go to", goto() end, + ["Re-Indent ^+I"] = function(path, lines) + local a = reindent(lines) saveFile(path, lines) return nil, a + end, + ["Set Syntax ^+E"] = function(path, lines) + setsyntax() + if currentLanguage == languages.brainfuck and lines[1] ~= "-- Syntax: Brainfuck" then + table.insert(lines, 1, "-- Syntax: Brainfuck") + return nil, lines + end + end, + ["Start of Line ^+<"] = function() os.queueEvent("key", 199) end, + ["End of Line ^+>"] = function() os.queueEvent("key", 207) end, + + -- Run + ["Run Program ^+R"] = function(path, lines) + saveFile(path, lines) + return nil, run(path, lines, false) + end, + ["Run w/ Args ^+Shift+R"] = function(path, lines) + saveFile(path, lines) + return nil, run(path, lines, true) + end, +} + +local function drawMenu(open) + term.setCursorPos(1, 1) + term.setTextColor(colors[theme.textColor]) + term.setBackgroundColor(colors[theme.backgroundHighlight]) + term.clearLine() + local curX = 0 + for _, v in pairs(menu) do + term.setCursorPos(3 + curX, 1) + term.write(v[1]) + curX = curX + v[1]:len() + 3 + end + + if open then + local it = {} + local x = 1 + for _, v in pairs(menu) do + if open == v[1] then + it = v + break + end + x = x + v[1]:len() + 3 + end + x = x + 1 + + local items = {} + for i = 2, #it do + table.insert(items, it[i]) + end + + local len = 1 + for _, v in pairs(items) do if v:len() + 2 > len then len = v:len() + 2 end end + + for i, v in ipairs(items) do + term.setCursorPos(x, i + 1) + term.write(string.rep(" ", len)) + term.setCursorPos(x + 1, i + 1) + term.write(v) + end + term.setCursorPos(x, #items + 2) + term.write(string.rep(" ", len)) + return items, len + end +end + +local function triggerMenu(cx, cy) + -- Determine clicked menu + local curX = 0 + local open = nil + for _, v in pairs(menu) do + if cx >= curX + 3 and cx <= curX + v[1]:len() + 2 then + open = v[1] + break + end + curX = curX + v[1]:len() + 3 + end + local menux = curX + 2 + if not open then return false end + + -- Flash menu item + term.setCursorBlink(false) + term.setCursorPos(menux, 1) + term.setBackgroundColor(colors[theme.background]) + term.write(string.rep(" ", open:len() + 2)) + term.setCursorPos(menux + 1, 1) + term.write(open) + sleep(0.1) + local items, len = drawMenu(open) + + local ret = true + + -- Pull events on menu + local ox, oy = term.getCursorPos() + while type(ret) ~= "string" do + local e, but, x, y = os.pullEvent() + if e == "mouse_click" then + -- If clicked outside menu + if x < menux - 1 or x > menux + len - 1 then break + elseif y > #items + 2 then break + elseif y == 1 then break end + + for i, v in ipairs(items) do + if y == i + 1 and x >= menux and x <= menux + len - 2 then + -- Flash when clicked + term.setCursorPos(menux, y) + term.setBackgroundColor(colors[theme.background]) + term.write(string.rep(" ", len)) + term.setCursorPos(menux + 1, y) + term.write(v) + sleep(0.1) + drawMenu(open) + + -- Return item + ret = v + break + end + end + end + end + + term.setCursorPos(ox, oy) + term.setCursorBlink(true) + return ret +end + + +-- -------- Editing + +local standardsCompletions = { + "if%s+.+%s+then%s*$", + "for%s+.+%s+do%s*$", + "while%s+.+%s+do%s*$", + "repeat%s*$", + "function%s+[a-zA-Z_0-9]?\(.*\)%s*$", + "=%s*function%s*\(.*\)%s*$", + "else%s*$", + "elseif%s+.+%s+then%s*$" +} + +local liveCompletions = { + ["("] = ")", + ["{"] = "}", + ["["] = "]", + ["\""] = "\"", + ["'"] = "'", +} + +local x, y = 0, 0 +local edw, edh = 0, h - 1 +local offx, offy = 0, 1 +local scrollx, scrolly = 0, 0 +local lines = {} +local liveErr = currentLanguage.parseError(nil) +local displayCode = true +local lastEventClock = os.clock() + +local function attemptToHighlight(line, regex, col) + local match = string.match(line, regex) + if match then + if type(col) == "number" then term.setTextColor(col) + elseif type(col) == "function" then term.setTextColor(col(match)) end + term.write(match) + term.setTextColor(colors[theme.textColor]) + return line:sub(match:len() + 1, -1) + end + return nil +end + +local function writeHighlighted(line) + if currentLanguage == languages.lua then + while line:len() > 0 do + line = attemptToHighlight(line, "^%-%-%[%[.-%]%]", colors[theme.comment]) or + attemptToHighlight(line, "^%-%-.*", colors[theme.comment]) or + attemptToHighlight(line, "^\".*[^\\]\"", colors[theme.string]) or + attemptToHighlight(line, "^\'.*[^\\]\'", colors[theme.string]) or + attemptToHighlight(line, "^%[%[.-%]%]", colors[theme.string]) or + attemptToHighlight(line, "^[%w_]+", function(match) + if currentLanguage.keywords[match] then + return colors[theme[currentLanguage.keywords[match]]] + end + return colors[theme.textColor] + end) or + attemptToHighlight(line, "^[^%w_]", colors[theme.textColor]) + end + else term.write(line) end +end + +local function draw() + -- Menu + term.setTextColor(colors[theme.textColor]) + term.setBackgroundColor(colors[theme.editorBackground]) + term.clear() + drawMenu() + + -- Line numbers + offx, offy = tostring(#lines):len() + 1, 1 + edw, edh = w - offx, h - 1 + + -- Draw text + for i = 1, edh do + local a = lines[scrolly + i] + if a then + local ln = string.rep(" ", offx - 1 - tostring(scrolly + i):len()) .. tostring(scrolly + i) + local l = a:sub(scrollx + 1, edw + scrollx + 1) + ln = ln .. ":" + + if liveErr.line == scrolly + i then ln = string.rep(" ", offx - 2) .. "!:" end + + term.setCursorPos(1, i + offy) + term.setBackgroundColor(colors[theme.editorBackground]) + if scrolly + i == y then + if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then + term.setBackgroundColor(colors[theme.editorErrorHighlight]) + else term.setBackgroundColor(colors[theme.editorLineHightlight]) end + term.clearLine() + elseif scrolly + i == liveErr.line then + term.setBackgroundColor(colors[theme.editorError]) + term.clearLine() + end + + term.setCursorPos(1 - scrollx + offx, i + offy) + if scrolly + i == y then + if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then + term.setBackgroundColor(colors[theme.editorErrorHighlight]) + else term.setBackgroundColor(colors[theme.editorLineHightlight]) end + elseif scrolly + i == liveErr.line then term.setBackgroundColor(colors[theme.editorError]) + else term.setBackgroundColor(colors[theme.editorBackground]) end + if scrolly + i == liveErr.line then + if displayCode then term.write(a) + else term.write(liveErr.display) end + else writeHighlighted(a) end + + term.setCursorPos(1, i + offy) + if scrolly + i == y then + if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then + term.setBackgroundColor(colors[theme.editorError]) + else term.setBackgroundColor(colors[theme.editorLineNumbersHighlight]) end + elseif scrolly + i == liveErr.line then + term.setBackgroundColor(colors[theme.editorErrorHighlight]) + else term.setBackgroundColor(colors[theme.editorLineNumbers]) end + term.write(ln) + end + end + term.setCursorPos(x - scrollx + offx, y - scrolly + offy) +end + +local function drawLine(...) + local ls = {...} + offx = tostring(#lines):len() + 1 + for _, ly in pairs(ls) do + local a = lines[ly] + if a then + local ln = string.rep(" ", offx - 1 - tostring(ly):len()) .. tostring(ly) + local l = a:sub(scrollx + 1, edw + scrollx + 1) + ln = ln .. ":" + + if liveErr.line == ly then ln = string.rep(" ", offx - 2) .. "!:" end + + term.setCursorPos(1, (ly - scrolly) + offy) + term.setBackgroundColor(colors[theme.editorBackground]) + if ly == y then + if ly == liveErr.line and os.clock() - lastEventClock > 3 then + term.setBackgroundColor(colors[theme.editorErrorHighlight]) + else term.setBackgroundColor(colors[theme.editorLineHightlight]) end + elseif ly == liveErr.line then + term.setBackgroundColor(colors[theme.editorError]) + end + term.clearLine() + + term.setCursorPos(1 - scrollx + offx, (ly - scrolly) + offy) + if ly == y then + if ly == liveErr.line and os.clock() - lastEventClock > 3 then + term.setBackgroundColor(colors[theme.editorErrorHighlight]) + else term.setBackgroundColor(colors[theme.editorLineHightlight]) end + elseif ly == liveErr.line then term.setBackgroundColor(colors[theme.editorError]) + else term.setBackgroundColor(colors[theme.editorBackground]) end + if ly == liveErr.line then + if displayCode then term.write(a) + else term.write(liveErr.display) end + else writeHighlighted(a) end + + term.setCursorPos(1, (ly - scrolly) + offy) + if ly == y then + if ly == liveErr.line and os.clock() - lastEventClock > 3 then + term.setBackgroundColor(colors[theme.editorError]) + else term.setBackgroundColor(colors[theme.editorLineNumbersHighlight]) end + elseif ly == liveErr.line then + term.setBackgroundColor(colors[theme.editorErrorHighlight]) + else term.setBackgroundColor(colors[theme.editorLineNumbers]) end + term.write(ln) + end + end + term.setCursorPos(x - scrollx + offx, y - scrolly + offy) +end + +local function cursorLoc(x, y, force) + local sx, sy = x - scrollx, y - scrolly + local redraw = false + if sx < 1 then + scrollx = x - 1 + sx = 1 + redraw = true + elseif sx > edw then + scrollx = x - edw + sx = edw + redraw = true + end if sy < 1 then + scrolly = y - 1 + sy = 1 + redraw = true + elseif sy > edh then + scrolly = y - edh + sy = edh + redraw = true + end if redraw or force then draw() end + term.setCursorPos(sx + offx, sy + offy) +end + +local function executeMenuItem(a, path) + if type(a) == "string" and menuFunctions[a] then + local opt, nl, gtln = menuFunctions[a](path, lines, y) + if type(opt) == "string" then term.setCursorBlink(false) return opt end + if type(nl) == "table" then + if #lines < 1 then table.insert(lines, "") end + y = math.min(y, #lines) + x = math.min(x, lines[y]:len() + 1) + lines = nl + elseif type(nl) == "string" then + if nl == "go to" and gtln then + x, y = 1, math.min(#lines, gtln) + cursorLoc(x, y) + end + end + end + term.setCursorBlink(true) + draw() + term.setCursorPos(x - scrollx + offx, y - scrolly + offy) +end + +local function edit(path) + -- Variables + x, y = 1, 1 + offx, offy = 0, 1 + scrollx, scrolly = 0, 0 + lines = loadFile(path) + if not lines then return "menu" end + + -- Enable brainfuck + if lines[1] == "-- Syntax: Brainfuck" then + currentLanguage = languages.brainfuck + end + + -- Clocks + local autosaveClock = os.clock() + local scrollClock = os.clock() -- To prevent redraw flicker + local liveErrorClock = os.clock() + local hasScrolled = false + + -- Draw + draw() + term.setCursorPos(x + offx, y + offy) + term.setCursorBlink(true) + + -- Main loop + local tid = os.startTimer(3) + while true do + local e, key, cx, cy = os.pullEvent() + if e == "key" and allowEditorEvent then + if key == 200 and y > 1 then + -- Up + x, y = math.min(x, lines[y - 1]:len() + 1), y - 1 + drawLine(y, y + 1) + cursorLoc(x, y) + elseif key == 208 and y < #lines then + -- Down + x, y = math.min(x, lines[y + 1]:len() + 1), y + 1 + drawLine(y, y - 1) + cursorLoc(x, y) + elseif key == 203 and x > 1 then + -- Left + x = x - 1 + local force = false + if y - scrolly + offy < offy + 1 then force = true end + cursorLoc(x, y, force) + elseif key == 205 and x < lines[y]:len() + 1 then + -- Right + x = x + 1 + local force = false + if y - scrolly + offy < offy + 1 then force = true end + cursorLoc(x, y, force) + elseif (key == 28 or key == 156) and (displayCode and true or y + scrolly - 1 == + liveErr.line) then + -- Enter + local f = nil + for _, v in pairs(standardsCompletions) do + if lines[y]:find(v) and x == #lines[y] + 1 then f = v end + end + + local _, spaces = lines[y]:find("^[ ]+") + if not spaces then spaces = 0 end + if f then + table.insert(lines, y + 1, string.rep(" ", spaces + 2)) + if not f:find("else", 1, true) and not f:find("elseif", 1, true) then + table.insert(lines, y + 2, string.rep(" ", spaces) .. + (f:find("repeat", 1, true) and "until " or f:find("{", 1, true) and "}" or + "end")) + end + x, y = spaces + 3, y + 1 + cursorLoc(x, y, true) + else + local oldLine = lines[y] + + lines[y] = lines[y]:sub(1, x - 1) + table.insert(lines, y + 1, string.rep(" ", spaces) .. oldLine:sub(x, -1)) + + x, y = spaces + 1, y + 1 + cursorLoc(x, y, true) + end + elseif key == 14 and (displayCode and true or y + scrolly - 1 == liveErr.line) then + -- Backspace + if x > 1 then + local f = false + for k, v in pairs(liveCompletions) do + if lines[y]:sub(x - 1, x - 1) == k then f = true end + end + + lines[y] = lines[y]:sub(1, x - 2) .. lines[y]:sub(x + (f and 1 or 0), -1) + drawLine(y) + x = x - 1 + cursorLoc(x, y) + elseif y > 1 then + local prevLen = lines[y - 1]:len() + 1 + lines[y - 1] = lines[y - 1] .. lines[y] + table.remove(lines, y) + x, y = prevLen, y - 1 + cursorLoc(x, y, true) + end + elseif key == 199 then + -- Home + x = 1 + local force = false + if y - scrolly + offy < offy + 1 then force = true end + cursorLoc(x, y, force) + elseif key == 207 then + -- End + x = lines[y]:len() + 1 + local force = false + if y - scrolly + offy < offy + 1 then force = true end + cursorLoc(x, y, force) + elseif key == 211 and (displayCode and true or y + scrolly - 1 == liveErr.line) then + -- Forward Delete + if x < lines[y]:len() + 1 then + lines[y] = lines[y]:sub(1, x - 1) .. lines[y]:sub(x + 1) + local force = false + if y - scrolly + offy < offy + 1 then force = true end + drawLine(y) + cursorLoc(x, y, force) + elseif y < #lines then + lines[y] = lines[y] .. lines[y + 1] + table.remove(lines, y + 1) + draw() + cursorLoc(x, y) + end + elseif key == 15 and (displayCode and true or y + scrolly - 1 == liveErr.line) then + -- Tab + lines[y] = string.rep(" ", tabWidth) .. lines[y] + x = x + 2 + local force = false + if y - scrolly + offy < offy + 1 then force = true end + drawLine(y) + cursorLoc(x, y, force) + elseif key == 201 then + -- Page up + y = math.min(math.max(y - edh, 1), #lines) + x = math.min(lines[y]:len() + 1, x) + cursorLoc(x, y, true) + elseif key == 209 then + -- Page down + y = math.min(math.max(y + edh, 1), #lines) + x = math.min(lines[y]:len() + 1, x) + cursorLoc(x, y, true) + end + elseif e == "char" and allowEditorEvent and (displayCode and true or + y + scrolly - 1 == liveErr.line) then + local shouldIgnore = false + for k, v in pairs(liveCompletions) do + if key == v and lines[y]:find(k, 1, true) and lines[y]:sub(x, x) == v then + shouldIgnore = true + end + end + + local addOne = false + if not shouldIgnore then + for k, v in pairs(liveCompletions) do + if key == k and lines[y]:sub(x, x) ~= k then key = key .. v addOne = true end + end + lines[y] = lines[y]:sub(1, x - 1) .. key .. lines[y]:sub(x, -1) + end + + x = x + (addOne and 1 or key:len()) + local force = false + if y - scrolly + offy < offy + 1 then force = true end + drawLine(y) + cursorLoc(x, y, force) + elseif e == "mouse_click" and key == 1 then + if cy > 1 then + if cx <= offx and cy - offy == liveErr.line - scrolly then + displayCode = not displayCode + drawLine(liveErr.line) + else + local oldy = y + y = math.min(math.max(scrolly + cy - offy, 1), #lines) + x = math.min(math.max(scrollx + cx - offx, 1), lines[y]:len() + 1) + if oldy ~= y then drawLine(oldy, y) end + cursorLoc(x, y) + end + else + local a = triggerMenu(cx, cy) + if a then + local opt = executeMenuItem(a, path) + if opt then return opt end + end + end + elseif e == "shortcut" then + local a = shortcuts[key .. " " .. cx] + if a then + local parent = nil + local curx = 0 + for i, mv in ipairs(menu) do + for _, iv in pairs(mv) do + if iv == a then + parent = menu[i][1] + break + end + end + if parent then break end + curx = curx + mv[1]:len() + 3 + end + local menux = curx + 2 + + -- Flash menu item + term.setCursorBlink(false) + term.setCursorPos(menux, 1) + term.setBackgroundColor(colors[theme.background]) + term.write(string.rep(" ", parent:len() + 2)) + term.setCursorPos(menux + 1, 1) + term.write(parent) + sleep(0.1) + drawMenu() + + -- Execute item + local opt = executeMenuItem(a, path) + if opt then return opt end + end + elseif e == "mouse_scroll" then + if key == -1 and scrolly > 0 then + scrolly = scrolly - 1 + if os.clock() - scrollClock > 0.0005 then + draw() + term.setCursorPos(x - scrollx + offx, y - scrolly + offy) + end + scrollClock = os.clock() + hasScrolled = true + elseif key == 1 and scrolly < #lines - edh then + scrolly = scrolly + 1 + if os.clock() - scrollClock > 0.0005 then + draw() + term.setCursorPos(x - scrollx + offx, y - scrolly + offy) + end + scrollClock = os.clock() + hasScrolled = true + end + elseif e == "timer" and key == tid then + drawLine(y) + tid = os.startTimer(3) + end + + -- Draw + if hasScrolled and os.clock() - scrollClock > 0.1 then + draw() + term.setCursorPos(x - scrollx + offx, y - scrolly + offy) + hasScrolled = false + end + + -- Autosave + if os.clock() - autosaveClock > autosaveInterval then + saveFile(path, lines) + autosaveClock = os.clock() + end + + -- Errors + if os.clock() - liveErrorClock > 1 then + local prevLiveErr = liveErr + liveErr = currentLanguage.parseError(nil) + local code = "" + for _, v in pairs(lines) do code = code .. v .. "\n" end + + liveErr = currentLanguage.getCompilerErrors(code) + liveErr.line = math.min(liveErr.line - 2, #lines) + if liveErr ~= prevLiveErr then draw() end + liveErrorClock = os.clock() + end + end + + return "menu" +end + + +-- -------- Open File + +local function newFile() + local wid = w - 13 + + -- Get name + title("Lua IDE - New File") + local name = centerRead(wid, "/") + if not name or name == "" then return "menu" end + name = "/" .. name + + -- Clear + title("Lua IDE - New File") + term.setTextColor(colors[theme.textColor]) + term.setBackgroundColor(colors[theme.promptHighlight]) + for i = 8, 10 do + term.setCursorPos(w/2 - wid/2, i) + term.write(string.rep(" ", wid)) + end + term.setCursorPos(1, 9) + if fs.isDir(name) then + centerPrint("Cannot Edit a Directory!") + sleep(1.6) + return "menu" + elseif fs.exists(name) then + centerPrint("File Already Exists!") + local opt = prompt({{"Open", w/2 - 9, 14}, {"Cancel", w/2 + 2, 14}}, "horizontal") + if opt == "Open" then return "edit", name + elseif opt == "Cancel" then return "menu" end + else return "edit", name end +end + +local function openFile() + local wid = w - 13 + + -- Get name + title("Lua IDE - Open File") + local name = centerRead(wid, "/") + if not name or name == "" then return "menu" end + name = "/" .. name + + -- Clear + title("Lua IDE - New File") + term.setTextColor(colors[theme.textColor]) + term.setBackgroundColor(colors[theme.promptHighlight]) + for i = 8, 10 do + term.setCursorPos(w/2 - wid/2, i) + term.write(string.rep(" ", wid)) + end + term.setCursorPos(1, 9) + if fs.isDir(name) then + centerPrint("Cannot Open a Directory!") + sleep(1.6) + return "menu" + elseif not fs.exists(name) then + centerPrint("File Doesn't Exist!") + local opt = prompt({{"Create", w/2 - 11, 14}, {"Cancel", w/2 + 2, 14}}, "horizontal") + if opt == "Create" then return "edit", name + elseif opt == "Cancel" then return "menu" end + else return "edit", name end +end + + +-- -------- Settings + +local function update() + local function draw(status) + title("LuaIDE - Update") + term.setBackgroundColor(colors[theme.prompt]) + term.setTextColor(colors[theme.textColor]) + for i = 8, 10 do + term.setCursorPos(w/2 - (status:len() + 4), i) + write(string.rep(" ", status:len() + 4)) + end + term.setCursorPos(w/2 - (status:len() + 4), 9) + term.write(" - " .. status .. " ") + + term.setBackgroundColor(colors[theme.errHighlight]) + for i = 8, 10 do + term.setCursorPos(w/2 + 2, i) + term.write(string.rep(" ", 10)) + end + term.setCursorPos(w/2 + 2, 9) + term.write(" > Cancel ") + end + + if not http then + draw("HTTP API Disabled!") + sleep(1.6) + return "settings" + end + + draw("Updating...") + local tID = os.startTimer(10) + http.request(updateURL) + while true do + local e, but, x, y = os.pullEvent() + if (e == "key" and but == 28) or + (e == "mouse_click" and x >= w/2 + 2 and x <= w/2 + 12 and y == 9) then + draw("Cancelled") + sleep(1.6) + break + elseif e == "http_success" and but == updateURL then + local new = x.readAll() + local curf = io.open(ideLocation, "r") + local cur = curf:read("*a") + curf:close() + + if cur ~= new then + draw("Update Found") + sleep(1.6) + local f = io.open(ideLocation, "w") + f:write(new) + f:close() + + draw("Click to Exit") + while true do + local e = os.pullEvent() + if e == "mouse_click" or (not isAdvanced() and e == "key") then break end + end + return "exit" + else + draw("No Updates Found!") + sleep(1.6) + break + end + elseif e == "http_failure" or (e == "timer" and but == tID) then + draw("Update Failed!") + sleep(1.6) + break + end + end + + return "settings" +end + +local function changeTheme() + title("LuaIDE - Theme") + + if isAdvanced() then + local disThemes = {"Back"} + for _, v in pairs(availableThemes) do table.insert(disThemes, v[1]) end + local t = scrollingPrompt(disThemes) + local url = nil + for _, v in pairs(availableThemes) do if v[1] == t then url = v[2] end end + + if not url then return "settings" end + if t == "Dawn (Default)" then + term.setBackgroundColor(colors[theme.backgroundHighlight]) + term.setCursorPos(3, 3) + term.clearLine() + term.write("LuaIDE - Loaded Theme!") + sleep(1.6) + + fs.delete(themeLocation) + theme = defaultTheme + return "menu" + end + + term.setBackgroundColor(colors[theme.backgroundHighlight]) + term.setCursorPos(3, 3) + term.clearLine() + term.write("LuaIDE - Downloading...") + + fs.delete("/.LuaIDE_temp_theme_file") + download(url, "/.LuaIDE_temp_theme_file") + local a = loadTheme("/.LuaIDE_temp_theme_file") + + term.setCursorPos(3, 3) + term.clearLine() + if a then + term.write("LuaIDE - Loaded Theme!") + fs.delete(themeLocation) + fs.move("/.LuaIDE_temp_theme_file", themeLocation) + theme = a + sleep(1.6) + return "menu" + end + + term.write("LuaIDE - Could Not Load Theme!") + fs.delete("/.LuaIDE_temp_theme_file") + sleep(1.6) + return "settings" + else + term.setCursorPos(1, 8) + centerPrint("Themes are not available on") + centerPrint("normal computers!") + end +end + +local function settings() + title("LuaIDE - Settings") + + local opt = prompt({{"Change Theme", w/2 - 17, 8}, {"Return to Menu", w/2 - 22, 13}, + {"Check for Updates", w/2 + 2, 8}, {"Exit IDE", w/2 + 2, 13, bg = colors[theme.err], + highlight = colors[theme.errHighlight]}}, "vertical", true) + if opt == "Change Theme" then return changeTheme() + elseif opt == "Check for Updates" then return update() + elseif opt == "Return to Menu" then return "menu" + elseif opt == "Exit IDE" then return "exit" end +end + + +-- -------- Menu + +local function menu() + title("Welcome to LuaIDE " .. version) + + local opt = prompt({{"New File", w/2 - 13, 8}, {"Open File", w/2 - 14, 13}, + {"Settings", w/2 + 2, 8}, {"Exit IDE", w/2 + 2, 13, bg = colors[theme.err], + highlight = colors[theme.errHighlight]}}, "vertical", true) + if opt == "New File" then return "new" + elseif opt == "Open File" then return "open" + elseif opt == "Settings" then return "settings" + elseif opt == "Exit IDE" then return "exit" end +end + + +-- -------- Main + +local function main(arguments) + local opt, data = "menu", nil + + -- Check arguments + if type(arguments) == "table" and #arguments > 0 then + local f = "/" .. shell.resolve(arguments[1]) + if fs.isDir(f) then print("Cannot edit a directory.") end + opt, data = "edit", f + end + + -- Main run loop + while true do + -- Menu + if opt == "menu" then opt = menu() end + + -- Other + if opt == "new" then opt, data = newFile() + elseif opt == "open" then opt, data = openFile() + elseif opt == "settings" then opt = settings() + end if opt == "exit" then break end + + -- Edit + if opt == "edit" and data then opt = edit(data) end + end +end + +-- Load Theme +if fs.exists(themeLocation) then theme = loadTheme(themeLocation) end +if not theme and isAdvanced() then theme = defaultTheme +elseif not theme then theme = normalTheme end + +-- Run +local _, err = pcall(function() + parallel.waitForAny(function() main(arguments) end, monitorKeyboardShortcuts) +end) + +-- Catch errors +if err and not err:find("Terminated") then + term.setCursorBlink(false) + title("LuaIDE - Crash! D:") + + term.setBackgroundColor(colors[theme.err]) + for i = 6, 8 do + term.setCursorPos(5, i) + term.write(string.rep(" ", 36)) + end + term.setCursorPos(6, 7) + term.write("LuaIDE Has Crashed! D:") + + term.setBackgroundColor(colors[theme.background]) + term.setCursorPos(2, 10) + print(err) + + term.setBackgroundColor(colors[theme.prompt]) + local _, cy = term.getCursorPos() + for i = cy + 1, cy + 4 do + term.setCursorPos(5, i) + term.write(string.rep(" ", 36)) + end + term.setCursorPos(6, cy + 2) + term.write("Please report this error to") + term.setCursorPos(6, cy + 3) + term.write("GravityScore! ") + + term.setBackgroundColor(colors[theme.background]) + if isAdvanced() then centerPrint("Click to Exit...", h - 1) + else centerPrint("Press Any Key to Exit...", h - 1) end + while true do + local e = os.pullEvent() + if e == "mouse_click" or (not isAdvanced() and e == "key") then break end + end + + -- Prevent key from being shown + os.queueEvent("") + os.pullEvent() +end + +-- Exit +term.setBackgroundColor(colors.black) +term.setTextColor(colors.white) +term.clear() +term.setCursorPos(1, 1) +centerPrint("Thank You for Using Lua IDE " .. version) +centerPrint("Made by GravityScore") \ No newline at end of file -- cgit v1.2.3