-- -- 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")