Advertisement
GravityScore

LuaIDE

Apr 20th, 2013
31,538
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 60.50 KB | None | 0 0
  1.  
  2. --  
  3. --  Lua IDE
  4. --  Made by GravityScore
  5. --  
  6.  
  7.  
  8. --  -------- Variables
  9.  
  10. -- Version
  11. local version = "1.0"
  12. local args = {...}
  13.  
  14. -- Editing
  15. local w, h = term.getSize()
  16. local tabWidth = 2
  17.  
  18. local autosaveInterval = 20
  19. local allowEditorEvent = true
  20. local keyboardShortcutTimeout = 0.4
  21.  
  22. -- Clipboard
  23. local clipboard = nil
  24.  
  25. -- Theme
  26. local theme = {}
  27.  
  28. -- Language
  29. local languages = {}
  30. local curLanguage = {}
  31.  
  32. -- Events
  33. local event_distract = "luaide_distractionEvent"
  34.  
  35. -- Locations
  36. local updateURL = "https://raw.github.com/GravityScore/LuaIDE/master/luaide.lua"
  37. local ideLocation = "/" .. shell.getRunningProgram()
  38. local themeLocation = "/.LuaIDE-Theme"
  39.  
  40. local function isAdvanced() return term.isColor and term.isColor() end
  41.  
  42.  
  43. --  -------- Utilities
  44.  
  45. local function modRead(properties)
  46.     local w, h = term.getSize()
  47.     local defaults = {replaceChar = nil, history = nil, visibleLength = nil, textLength = nil,
  48.         liveUpdates = nil, exitOnKey = nil}
  49.     if not properties then properties = {} end
  50.     for k, v in pairs(defaults) do if not properties[k] then properties[k] = v end end
  51.     if properties.replaceChar then properties.replaceChar = properties.replaceChar:sub(1, 1) end
  52.     if not properties.visibleLength then properties.visibleLength = w end
  53.  
  54.     local sx, sy = term.getCursorPos()
  55.     local line = ""
  56.     local pos = 0
  57.     local historyPos = nil
  58.  
  59.     local function redraw(repl)
  60.         local scroll = 0
  61.         if properties.visibleLength and sx + pos > properties.visibleLength + 1 then
  62.             scroll = (sx + pos) - (properties.visibleLength + 1)
  63.         end
  64.  
  65.         term.setCursorPos(sx, sy)
  66.         local a = repl or properties.replaceChar
  67.         if a then term.write(string.rep(a, line:len() - scroll))
  68.         else term.write(line:sub(scroll + 1, -1)) end
  69.         term.setCursorPos(sx + pos - scroll, sy)
  70.     end
  71.  
  72.     local function sendLiveUpdates(event, ...)
  73.         if type(properties.liveUpdates) == "function" then
  74.             local ox, oy = term.getCursorPos()
  75.             local a, data = properties.liveUpdates(line, event, ...)
  76.             if a == true and data == nil then
  77.                 term.setCursorBlink(false)
  78.                 return line
  79.             elseif a == true and data ~= nil then
  80.                 term.setCursorBlink(false)
  81.                 return data
  82.             end
  83.             term.setCursorPos(ox, oy)
  84.         end
  85.     end
  86.  
  87.     term.setCursorBlink(true)
  88.     while true do
  89.         local e, but, x, y, p4, p5 = os.pullEvent()
  90.  
  91.         if e == "char" then
  92.             local s = false
  93.             if properties.textLength and line:len() < properties.textLength then s = true
  94.             elseif not properties.textLength then s = true end
  95.  
  96.             local canType = true
  97.             if not properties.grantPrint and properties.refusePrint then
  98.                 local canTypeKeys = {}
  99.                 if type(properties.refusePrint) == "table" then
  100.                     for _, v in pairs(properties.refusePrint) do
  101.                         table.insert(canTypeKeys, tostring(v):sub(1, 1))
  102.                     end
  103.                 elseif type(properties.refusePrint) == "string" then
  104.                     for char in properties.refusePrint:gmatch(".") do
  105.                         table.insert(canTypeKeys, char)
  106.                     end
  107.                 end
  108.                 for _, v in pairs(canTypeKeys) do if but == v then canType = false end end
  109.             elseif properties.grantPrint then
  110.                 canType = false
  111.                 local canTypeKeys = {}
  112.                 if type(properties.grantPrint) == "table" then
  113.                     for _, v in pairs(properties.grantPrint) do
  114.                         table.insert(canTypeKeys, tostring(v):sub(1, 1))
  115.                     end
  116.                 elseif type(properties.grantPrint) == "string" then
  117.                     for char in properties.grantPrint:gmatch(".") do
  118.                         table.insert(canTypeKeys, char)
  119.                     end
  120.                 end
  121.                 for _, v in pairs(canTypeKeys) do if but == v then canType = true end end
  122.             end
  123.  
  124.             if s and canType then
  125.                 line = line:sub(1, pos) .. but .. line:sub(pos + 1, -1)
  126.                 pos = pos + 1
  127.                 redraw()
  128.             end
  129.         elseif e == "key" then
  130.             if but == keys.enter then break
  131.             elseif but == keys.left then if pos > 0 then pos = pos - 1 redraw() end
  132.             elseif but == keys.right then if pos < line:len() then pos = pos + 1 redraw() end
  133.             elseif (but == keys.up or but == keys.down) and properties.history then
  134.                 redraw(" ")
  135.                 if but == keys.up then
  136.                     if historyPos == nil and #properties.history > 0 then
  137.                         historyPos = #properties.history
  138.                     elseif historyPos > 1 then
  139.                         historyPos = historyPos - 1
  140.                     end
  141.                 elseif but == keys.down then
  142.                     if historyPos == #properties.history then historyPos = nil
  143.                     elseif historyPos ~= nil then historyPos = historyPos + 1 end
  144.                 end
  145.  
  146.                 if properties.history and historyPos then
  147.                     line = properties.history[historyPos]
  148.                     pos = line:len()
  149.                 else
  150.                     line = ""
  151.                     pos = 0
  152.                 end
  153.  
  154.                 redraw()
  155.                 local a = sendLiveUpdates("history")
  156.                 if a then return a end
  157.             elseif but == keys.backspace and pos > 0 then
  158.                 redraw(" ")
  159.                 line = line:sub(1, pos - 1) .. line:sub(pos + 1, -1)
  160.                 pos = pos - 1
  161.                 redraw()
  162.                 local a = sendLiveUpdates("delete")
  163.                 if a then return a end
  164.             elseif but == keys.home then
  165.                 pos = 0
  166.                 redraw()
  167.             elseif but == keys.delete and pos < line:len() then
  168.                 redraw(" ")
  169.                 line = line:sub(1, pos) .. line:sub(pos + 2, -1)
  170.                 redraw()
  171.                 local a = sendLiveUpdates("delete")
  172.                 if a then return a end
  173.             elseif but == keys["end"] then
  174.                 pos = line:len()
  175.                 redraw()
  176.             elseif properties.exitOnKey then
  177.                 if but == properties.exitOnKey or (properties.exitOnKey == "control" and
  178.                         (but == 29 or but == 157)) then
  179.                     term.setCursorBlink(false)
  180.                     return nil
  181.                 end
  182.             end
  183.         end
  184.         local a = sendLiveUpdates(e, but, x, y, p4, p5)
  185.         if a then return a end
  186.     end
  187.  
  188.     term.setCursorBlink(false)
  189.     if line ~= nil then line = line:gsub("^%s*(.-)%s*$", "%1") end
  190.     return line
  191. end
  192.  
  193.  
  194. --  -------- Themes
  195.  
  196. local defaultTheme = {
  197.     background = "gray",
  198.     backgroundHighlight = "lightGray",
  199.     prompt = "cyan",
  200.     promptHighlight = "lightBlue",
  201.     err = "red",
  202.     errHighlight = "pink",
  203.  
  204.     editorBackground = "gray",
  205.     editorLineHightlight = "lightBlue",
  206.     editorLineNumbers = "gray",
  207.     editorLineNumbersHighlight = "lightGray",
  208.     editorError = "pink",
  209.     editorErrorHighlight = "red",
  210.  
  211.     textColor = "white",
  212.     conditional = "yellow",
  213.     constant = "orange",
  214.     ["function"] = "magenta",
  215.     string = "red",
  216.     comment = "lime"
  217. }
  218.  
  219. local normalTheme = {
  220.     background = "black",
  221.     backgroundHighlight = "black",
  222.     prompt = "black",
  223.     promptHighlight = "black",
  224.     err = "black",
  225.     errHighlight = "black",
  226.  
  227.     editorBackground = "black",
  228.     editorLineHightlight = "black",
  229.     editorLineNumbers = "black",
  230.     editorLineNumbersHighlight = "white",
  231.     editorError = "black",
  232.     editorErrorHighlight = "black",
  233.  
  234.     textColor = "white",
  235.     conditional = "white",
  236.     constant = "white",
  237.     ["function"] = "white",
  238.     string = "white",
  239.     comment = "white"
  240. }
  241.  
  242. local availableThemes = {
  243.     {"Water (Default)", "https://raw.github.com/GravityScore/LuaIDE/master/themes/default.txt"},
  244.     {"Fire", "https://raw.github.com/GravityScore/LuaIDE/master/themes/fire.txt"},
  245.     {"Sublime Text 2", "https://raw.github.com/GravityScore/LuaIDE/master/themes/st2.txt"},
  246.     {"Midnight", "https://raw.github.com/GravityScore/LuaIDE/master/themes/midnight.txt"},
  247.     {"TheOriginalBIT", "https://raw.github.com/GravityScore/LuaIDE/master/themes/bit.txt"},
  248.     {"Superaxander", "https://raw.github.com/GravityScore/LuaIDE/master/themes/superaxander.txt"},
  249.     {"Forest", "https://raw.github.com/GravityScore/LuaIDE/master/themes/forest.txt"},
  250.     {"Night", "https://raw.github.com/GravityScore/LuaIDE/master/themes/night.txt"},
  251.     {"Original", "https://raw.github.com/GravityScore/LuaIDE/master/themes/original.txt"},
  252. }
  253.  
  254. local function loadTheme(path)
  255.     local f = io.open(path)
  256.     local l = f:read("*l")
  257.     local config = {}
  258.     while l ~= nil do
  259.         local k, v = string.match(l, "^(%a+)=(%a+)")
  260.         if k and v then config[k] = v end
  261.         l = f:read("*l")
  262.     end
  263.     f:close()
  264.     return config
  265. end
  266.  
  267. -- Load Theme
  268. if isAdvanced() then theme = defaultTheme
  269. else theme = normalTheme end
  270.  
  271.  
  272. --  -------- Drawing
  273.  
  274. local function centerPrint(text, ny)
  275.     if type(text) == "table" then for _, v in pairs(text) do centerPrint(v) end
  276.     else
  277.         local x, y = term.getCursorPos()
  278.         local w, h = term.getSize()
  279.         term.setCursorPos(w/2 - text:len()/2 + (#text % 2 == 0 and 1 or 0), ny or y)
  280.         print(text)
  281.     end
  282. end
  283.  
  284. local function title(t)
  285.     term.setTextColor(colors[theme.textColor])
  286.     term.setBackgroundColor(colors[theme.background])
  287.     term.clear()
  288.  
  289.     term.setBackgroundColor(colors[theme.backgroundHighlight])
  290.     for i = 2, 4 do term.setCursorPos(1, i) term.clearLine() end
  291.     term.setCursorPos(3, 3)
  292.     term.write(t)
  293. end
  294.  
  295. local function centerRead(wid, begt)
  296.     local function liveUpdate(line, e, but, x, y, p4, p5)
  297.         if isAdvanced() and e == "mouse_click" and x >= w/2 - wid/2 and x <= w/2 - wid/2 + 10
  298.                 and y >= 13 and y <= 15 then
  299.             return true, ""
  300.         end
  301.     end
  302.  
  303.     if not begt then begt = "" end
  304.     term.setTextColor(colors[theme.textColor])
  305.     term.setBackgroundColor(colors[theme.promptHighlight])
  306.     for i = 8, 10 do
  307.         term.setCursorPos(w/2 - wid/2, i)
  308.         term.write(string.rep(" ", wid))
  309.     end
  310.  
  311.     if isAdvanced() then
  312.         term.setBackgroundColor(colors[theme.errHighlight])
  313.         for i = 13, 15 do
  314.             term.setCursorPos(w/2 - wid/2 + 1, i)
  315.             term.write(string.rep(" ", 10))
  316.         end
  317.         term.setCursorPos(w/2 - wid/2 + 2, 14)
  318.         term.write("> Cancel")
  319.     end
  320.  
  321.     term.setBackgroundColor(colors[theme.promptHighlight])
  322.     term.setCursorPos(w/2 - wid/2 + 1, 9)
  323.     term.write("> " .. begt)
  324.     return modRead({visibleLength = w/2 + wid/2, liveUpdates = liveUpdate})
  325. end
  326.  
  327.  
  328. --  -------- Prompt
  329.  
  330. local function prompt(list, dir, isGrid)
  331.     local function draw(sel)
  332.         for i, v in ipairs(list) do
  333.             if i == sel then term.setBackgroundColor(v.highlight or colors[theme.promptHighlight])
  334.             else term.setBackgroundColor(v.bg or colors[theme.prompt]) end
  335.             term.setTextColor(v.tc or colors[theme.textColor])
  336.             for i = -1, 1 do
  337.                 term.setCursorPos(v[2], v[3] + i)
  338.                 term.write(string.rep(" ", v[1]:len() + 4))
  339.             end
  340.  
  341.             term.setCursorPos(v[2], v[3])
  342.             if i == sel then
  343.                 term.setBackgroundColor(v.highlight or colors[theme.promptHighlight])
  344.                 term.write(" > ")
  345.             else term.write(" - ") end
  346.             term.write(v[1] .. " ")
  347.         end
  348.     end
  349.  
  350.     local key1 = dir == "horizontal" and 203 or 200
  351.     local key2 = dir == "horizontal" and 205 or 208
  352.     local sel = 1
  353.     draw(sel)
  354.  
  355.     while true do
  356.         local e, but, x, y = os.pullEvent()
  357.         if e == "key" and but == 28 then
  358.             return list[sel][1]
  359.         elseif e == "key" and but == key1 and sel > 1 then
  360.             sel = sel - 1
  361.             draw(sel)
  362.         elseif e == "key" and but == key2 and ((err == true and sel < #list - 1) or (sel < #list)) then
  363.             sel = sel + 1
  364.             draw(sel)
  365.         elseif isGrid and e == "key" and but == 203 and sel > 2 then
  366.             sel = sel - 2
  367.             draw(sel)
  368.         elseif isGrid and e == "key" and but == 205 and sel < 3 then
  369.             sel = sel + 2
  370.             draw(sel)
  371.         elseif e == "mouse_click" then
  372.             for i, v in ipairs(list) do
  373.                 if x >= v[2] - 1 and x <= v[2] + v[1]:len() + 3 and y >= v[3] - 1 and y <= v[3] + 1 then
  374.                     return list[i][1]
  375.                 end
  376.             end
  377.         end
  378.     end
  379. end
  380.  
  381. local function scrollingPrompt(list)
  382.     local function draw(items, sel, loc)
  383.         for i, v in ipairs(items) do
  384.             local bg = colors[theme.prompt]
  385.             local bghigh = colors[theme.promptHighlight]
  386.             if v:find("Back") or v:find("Return") then
  387.                 bg = colors[theme.err]
  388.                 bghigh = colors[theme.errHighlight]
  389.             end
  390.  
  391.             if i == sel then term.setBackgroundColor(bghigh)
  392.             else term.setBackgroundColor(bg) end
  393.             term.setTextColor(colors[theme.textColor])
  394.             for x = -1, 1 do
  395.                 term.setCursorPos(3, (i * 4) + x + 4)
  396.                 term.write(string.rep(" ", w - 13))
  397.             end
  398.  
  399.             term.setCursorPos(3, i * 4 + 4)
  400.             if i == sel then
  401.                 term.setBackgroundColor(bghigh)
  402.                 term.write(" > ")
  403.             else term.write(" - ") end
  404.             term.write(v .. " ")
  405.         end
  406.     end
  407.  
  408.     local function updateDisplayList(items, loc, len)
  409.         local ret = {}
  410.         for i = 1, len do
  411.             local item = items[i + loc - 1]
  412.             if item then table.insert(ret, item) end
  413.         end
  414.         return ret
  415.     end
  416.  
  417.     -- Variables
  418.     local sel = 1
  419.     local loc = 1
  420.     local len = 3
  421.     local disList = updateDisplayList(list, loc, len)
  422.     draw(disList, sel, loc)
  423.  
  424.     -- Loop
  425.     while true do
  426.         local e, key, x, y = os.pullEvent()
  427.  
  428.         if e == "mouse_click" then
  429.             for i, v in ipairs(disList) do
  430.                 if x >= 3 and x <= w - 11 and y >= i * 4 + 3 and y <= i * 4 + 5 then return v end
  431.             end
  432.         elseif e == "key" and key == 200 then
  433.             if sel > 1 then
  434.                 sel = sel - 1
  435.                 draw(disList, sel, loc)
  436.             elseif loc > 1 then
  437.                 loc = loc - 1
  438.                 disList = updateDisplayList(list, loc, len)
  439.                 draw(disList, sel, loc)
  440.             end
  441.         elseif e == "key" and key == 208 then
  442.             if sel < len then
  443.                 sel = sel + 1
  444.                 draw(disList, sel, loc)
  445.             elseif loc + len - 1 < #list then
  446.                 loc = loc + 1
  447.                 disList = updateDisplayList(list, loc, len)
  448.                 draw(disList, sel, loc)
  449.             end
  450.         elseif e == "mouse_scroll" then
  451.             os.queueEvent("key", key == -1 and 200 or 208)
  452.         elseif e == "key" and key == 28 then
  453.             return disList[sel]
  454.         end
  455.     end
  456. end
  457.  
  458. function monitorKeyboardShortcuts()
  459.     local ta, tb = nil, nil
  460.     local allowChar = false
  461.     local shiftPressed = false
  462.     while true do
  463.         local event, char = os.pullEvent()
  464.         if event == "key" and (char == 42 or char == 52) then
  465.             shiftPressed = true
  466.             tb = os.startTimer(keyboardShortcutTimeout)
  467.         elseif event == "key" and (char == 29 or char == 157 or char == 219 or char == 220) then
  468.             allowEditorEvent = false
  469.             allowChar = true
  470.             ta = os.startTimer(keyboardShortcutTimeout)
  471.         elseif event == "key" and allowChar then
  472.             local name = nil
  473.             for k, v in pairs(keys) do
  474.                 if v == char then
  475.                     if shiftPressed then os.queueEvent("shortcut", "ctrl shift", k:lower())
  476.                     else os.queueEvent("shortcut", "ctrl", k:lower()) end
  477.                     sleep(0.005)
  478.                     allowEditorEvent = true
  479.                 end
  480.             end
  481.             if shiftPressed then os.queueEvent("shortcut", "ctrl shift", char)
  482.             else os.queueEvent("shortcut", "ctrl", char) end
  483.         elseif event == "timer" and char == ta then
  484.             allowEditorEvent = true
  485.             allowChar = false
  486.         elseif event == "timer" and char == tb then
  487.             shiftPressed = false
  488.         end
  489.     end
  490. end
  491.  
  492.  
  493. --  -------- Saving and Loading
  494.  
  495. local function download(url, path)
  496.     for i = 1, 3 do
  497.         local response = http.get(url)
  498.         if response then
  499.             local data = response.readAll()
  500.             response.close()
  501.             if path then
  502.                 local f = io.open(path, "w")
  503.                 f:write(data)
  504.                 f:close()
  505.             end
  506.             return true
  507.         end
  508.     end
  509.  
  510.     return false
  511. end
  512.  
  513. local function saveFile(path, lines)
  514.     local dir = path:sub(1, path:len() - fs.getName(path):len())
  515.     if not fs.exists(dir) then fs.makeDir(dir) end
  516.     if not fs.isDir(path) and not fs.isReadOnly(path) then
  517.         local a = ""
  518.         for _, v in pairs(lines) do a = a .. v .. "\n" end
  519.  
  520.         local f = io.open(path, "w")
  521.         f:write(a)
  522.         f:close()
  523.         return true
  524.     else return false end
  525. end
  526.  
  527. local function loadFile(path)
  528.     if not fs.exists(path) then
  529.         local dir = path:sub(1, path:len() - fs.getName(path):len())
  530.         if not fs.exists(dir) then fs.makeDir(dir) end
  531.         local f = io.open(path, "w")
  532.         f:write("")
  533.         f:close()
  534.     end
  535.  
  536.     local l = {}
  537.     if fs.exists(path) and not fs.isDir(path) then
  538.         local f = io.open(path, "r")
  539.         if f then
  540.             local a = f:read("*l")
  541.             while a do
  542.                 table.insert(l, a)
  543.                 a = f:read("*l")
  544.             end
  545.             f:close()
  546.         end
  547.     else return nil end
  548.  
  549.     if #l < 1 then table.insert(l, "") end
  550.     return l
  551. end
  552.  
  553.  
  554. --  -------- Languages
  555.  
  556. languages.lua = {}
  557. languages.brainfuck = {}
  558. languages.none = {}
  559.  
  560. --  Lua
  561.  
  562. languages.lua.helpTips = {
  563.     "A function you tried to call doesn't exist.",
  564.     "You made a typo.",
  565.     "The index of an array is nil.",
  566.     "The wrong variable type was passed.",
  567.     "A function/variable doesn't exist.",
  568.     "You missed an 'end'.",
  569.     "You missed a 'then'.",
  570.     "You declared a variable incorrectly.",
  571.     "One of your variables is mysteriously nil."
  572. }
  573.  
  574. languages.lua.defaultHelpTips = {
  575.     2, 5
  576. }
  577.  
  578. languages.lua.errors = {
  579.     ["Attempt to call nil."] = {1, 2},
  580.     ["Attempt to index nil."] = {3, 2},
  581.     [".+ expected, got .+"] = {4, 2, 9},
  582.     ["'end' expected"] = {6, 2},
  583.     ["'then' expected"] = {7, 2},
  584.     ["'=' expected"] = {8, 2}
  585. }
  586.  
  587. languages.lua.keywords = {
  588.     ["and"] = "conditional",
  589.     ["break"] = "conditional",
  590.     ["do"] = "conditional",
  591.     ["else"] = "conditional",
  592.     ["elseif"] = "conditional",
  593.     ["end"] = "conditional",
  594.     ["for"] = "conditional",
  595.     ["function"] = "conditional",
  596.     ["if"] = "conditional",
  597.     ["in"] = "conditional",
  598.     ["local"] = "conditional",
  599.     ["not"] = "conditional",
  600.     ["or"] = "conditional",
  601.     ["repeat"] = "conditional",
  602.     ["return"] = "conditional",
  603.     ["then"] = "conditional",
  604.     ["until"] = "conditional",
  605.     ["while"] = "conditional",
  606.  
  607.     ["true"] = "constant",
  608.     ["false"] = "constant",
  609.     ["nil"] = "constant",
  610.  
  611.     ["print"] = "function",
  612.     ["write"] = "function",
  613.     ["sleep"] = "function",
  614.     ["pairs"] = "function",
  615.     ["ipairs"] = "function",
  616.     ["loadstring"] = "function",
  617.     ["loadfile"] = "function",
  618.     ["dofile"] = "function",
  619.     ["rawset"] = "function",
  620.     ["rawget"] = "function",
  621.     ["setfenv"] = "function",
  622.     ["getfenv"] = "function",
  623. }
  624.  
  625. languages.lua.parseError = function(e)
  626.     local ret = {filename = "unknown", line = -1, display = "Unknown!", err = ""}
  627.     if e and e ~= "" then
  628.         ret.err = e
  629.         if e:find(":") then
  630.             ret.filename = e:sub(1, e:find(":") - 1):gsub("^%s*(.-)%s*$", "%1")
  631.             -- The "" is needed to circumvent a CC bug
  632.             e = (e:sub(e:find(":") + 1) .. ""):gsub("^%s*(.-)%s*$", "%1")
  633.             if e:find(":") then
  634.                 ret.line = e:sub(1, e:find(":") - 1)
  635.                 e = e:sub(e:find(":") + 2):gsub("^%s*(.-)%s*$", "%1") .. ""
  636.             end
  637.         end
  638.         ret.display = e:sub(1, 1):upper() .. e:sub(2, -1) .. "."
  639.     end
  640.  
  641.     return ret
  642. end
  643.  
  644. languages.lua.getCompilerErrors = function(code)
  645.     code = "local function ee65da6af1cb6f63fee9a081246f2fd92b36ef2(...)\n\n" .. code .. "\n\nend"
  646.     local fn, err = loadstring(code)
  647.     if not err then
  648.         local _, e = pcall(fn)
  649.         if e then err = e end
  650.     end
  651.  
  652.     if err then
  653.         local a = err:find("]", 1, true)
  654.         if a then err = "string" .. err:sub(a + 1, -1) end
  655.         local ret = languages.lua.parseError(err)
  656.         if tonumber(ret.line) then ret.line = tonumber(ret.line) end
  657.         return ret
  658.     else return languages.lua.parseError(nil) end
  659. end
  660.  
  661. languages.lua.run = function(path, ar)
  662.     local fn, err = loadfile(path)
  663.     setfenv(fn, getfenv())
  664.     if not err then
  665.         _, err = pcall(function() fn(unpack(ar)) end)
  666.     end
  667.     return err
  668. end
  669.  
  670.  
  671. --  Brainfuck
  672.  
  673. languages.brainfuck.helpTips = {
  674.     "Well idk...",
  675.     "Isn't this the whole point of the language?",
  676.     "Ya know... Not being able to debug it?",
  677.     "You made a typo."
  678. }
  679.  
  680. languages.brainfuck.defaultHelpTips = {
  681.     1, 2, 3
  682. }
  683.  
  684. languages.brainfuck.errors = {
  685.     ["No matching '['"] = {1, 2, 3, 4}
  686. }
  687.  
  688. languages.brainfuck.keywords = {}
  689.  
  690. languages.brainfuck.parseError = function(e)
  691.     local ret = {filename = "unknown", line = -1, display = "Unknown!", err = ""}
  692.     if e and e ~= "" then
  693.         ret.err = e
  694.         ret.line = e:sub(1, e:find(":") - 1)
  695.         e = e:sub(e:find(":") + 2):gsub("^%s*(.-)%s*$", "%1") .. ""
  696.         ret.display = e:sub(1, 1):upper() .. e:sub(2, -1) .. "."
  697.     end
  698.  
  699.     return ret
  700. end
  701.  
  702. languages.brainfuck.mapLoops = function(code)
  703.     -- Map loops
  704.     local loopLocations = {}
  705.     local loc = 1
  706.     local line = 1
  707.     for let in string.gmatch(code, ".") do
  708.         if let == "[" then
  709.             loopLocations[loc] = true
  710.         elseif let == "]" then
  711.             local found = false
  712.             for i = loc, 1, -1 do
  713.                 if loopLocations[i] == true then
  714.                     loopLocations[i] = loc
  715.                     found = true
  716.                 end
  717.             end
  718.  
  719.             if not found then
  720.                 return line .. ": No matching '['"
  721.             end
  722.         end
  723.  
  724.         if let == "\n" then line = line + 1 end
  725.         loc = loc + 1
  726.     end
  727.     return loopLocations
  728. end
  729.  
  730. languages.brainfuck.getCompilerErrors = function(code)
  731.     local a = languages.brainfuck.mapLoops(code)
  732.     if type(a) == "string" then return languages.brainfuck.parseError(a)
  733.     else return languages.brainfuck.parseError(nil) end
  734. end
  735.  
  736. languages.brainfuck.run = function(path)
  737.     -- Read from file
  738.     local f = io.open(path, "r")
  739.     local content = f:read("*a")
  740.     f:close()
  741.  
  742.     -- Define environment
  743.     local dataCells = {}
  744.     local dataPointer = 1
  745.     local instructionPointer = 1
  746.  
  747.     -- Map loops
  748.     local loopLocations = languages.brainfuck.mapLoops(content)
  749.     if type(loopLocations) == "string" then return loopLocations end
  750.  
  751.     -- Execute code
  752.     while true do
  753.         local let = content:sub(instructionPointer, instructionPointer)
  754.  
  755.         if let == ">" then
  756.             dataPointer = dataPointer + 1
  757.             if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
  758.         elseif let == "<" then
  759.             if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
  760.             dataPointer = dataPointer - 1
  761.             if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
  762.         elseif let == "+" then
  763.             if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
  764.             dataCells[tostring(dataPointer)] = dataCells[tostring(dataPointer)] + 1
  765.         elseif let == "-" then
  766.             if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
  767.             dataCells[tostring(dataPointer)] = dataCells[tostring(dataPointer)] - 1
  768.         elseif let == "." then
  769.             if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
  770.             if term.getCursorPos() >= w then print("") end
  771.             write(string.char(math.max(1, dataCells[tostring(dataPointer)])))
  772.         elseif let == "," then
  773.             if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
  774.             term.setCursorBlink(true)
  775.             local e, but = os.pullEvent("char")
  776.             term.setCursorBlink(false)
  777.             dataCells[tostring(dataPointer)] = string.byte(but)
  778.             if term.getCursorPos() >= w then print("") end
  779.             write(but)
  780.         elseif let == "/" then
  781.             if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
  782.             if term.getCursorPos() >= w then print("") end
  783.             write(dataCells[tostring(dataPointer)])
  784.         elseif let == "[" then
  785.             if dataCells[tostring(dataPointer)] == 0 then
  786.                 for k, v in pairs(loopLocations) do
  787.                     if k == instructionPointer then instructionPointer = v end
  788.                 end
  789.             end
  790.         elseif let == "]" then
  791.             for k, v in pairs(loopLocations) do
  792.                 if v == instructionPointer then instructionPointer = k - 1 end
  793.             end
  794.         end
  795.  
  796.         instructionPointer = instructionPointer + 1
  797.         if instructionPointer > content:len() then print("") break end
  798.     end
  799. end
  800.  
  801. --  None
  802.  
  803. languages.none.helpTips = {}
  804. languages.none.defaultHelpTips = {}
  805. languages.none.errors = {}
  806. languages.none.keywords = {}
  807.  
  808. languages.none.parseError = function(err)
  809.     return {filename = "", line = -1, display = "", err = ""}
  810. end
  811.  
  812. languages.none.getCompilerErrors = function(code)
  813.     return languages.none.parseError(nil)
  814. end
  815.  
  816. languages.none.run = function(path) end
  817.  
  818.  
  819. -- Load language
  820. curLanguage = languages.lua
  821.  
  822.  
  823. --  -------- Run GUI
  824.  
  825. local function viewErrorHelp(e)
  826.     title("LuaIDE - Error Help")
  827.  
  828.     local tips = nil
  829.     for k, v in pairs(curLanguage.errors) do
  830.         if e.display:find(k) then tips = v break end
  831.     end
  832.  
  833.     term.setBackgroundColor(colors[theme.err])
  834.     for i = 6, 8 do
  835.         term.setCursorPos(5, i)
  836.         term.write(string.rep(" ", 35))
  837.     end
  838.  
  839.     term.setBackgroundColor(colors[theme.prompt])
  840.     for i = 10, 18 do
  841.         term.setCursorPos(5, i)
  842.         term.write(string.rep(" ", 46))
  843.     end
  844.  
  845.     if tips then
  846.         term.setBackgroundColor(colors[theme.err])
  847.         term.setCursorPos(6, 7)
  848.         term.write("Error Help")
  849.  
  850.         term.setBackgroundColor(colors[theme.prompt])
  851.         for i, v in ipairs(tips) do
  852.             term.setCursorPos(7, i + 10)
  853.             term.write("- " .. curLanguage.helpTips[v])
  854.         end
  855.     else
  856.         term.setBackgroundColor(colors[theme.err])
  857.         term.setCursorPos(6, 7)
  858.         term.write("No Error Tips Available!")
  859.  
  860.         term.setBackgroundColor(colors[theme.prompt])
  861.         term.setCursorPos(6, 11)
  862.         term.write("There are no error tips available, but")
  863.         term.setCursorPos(6, 12)
  864.         term.write("you could see if it was any of these:")
  865.  
  866.         for i, v in ipairs(curLanguage.defaultHelpTips) do
  867.             term.setCursorPos(7, i + 12)
  868.             term.write("- " .. curLanguage.helpTips[v])
  869.         end
  870.     end
  871.  
  872.     prompt({{"Back", w - 8, 7}}, "horizontal")
  873. end
  874.  
  875. local function run(path, lines, useArgs)
  876.     local ar = {}
  877.     if useArgs then
  878.         title("LuaIDE - Run " .. fs.getName(path))
  879.         local s = centerRead(w - 13, fs.getName(path) .. " ")
  880.         for m in string.gmatch(s, "[^ \t]+") do ar[#ar + 1] = m:gsub("^%s*(.-)%s*$", "%1") end
  881.     end
  882.    
  883.     saveFile(path, lines)
  884.     term.setCursorBlink(false)
  885.     term.setBackgroundColor(colors.black)
  886.     term.setTextColor(colors.white)
  887.     term.clear()
  888.     term.setCursorPos(1, 1)
  889.     local err = curLanguage.run(path, ar)
  890.  
  891.     term.setBackgroundColor(colors.black)
  892.     print("\n")
  893.     if err then
  894.         if isAdvanced() then term.setTextColor(colors.red) end
  895.         centerPrint("The program has crashed!")
  896.     end
  897.     term.setTextColor(colors.white)
  898.     centerPrint("Press any key to return to LuaIDE...")
  899.     while true do
  900.         local e = os.pullEvent()
  901.         if e == "key" then break end
  902.     end
  903.  
  904.     -- To prevent key from showing up in editor
  905.     os.queueEvent(event_distract)
  906.     os.pullEvent()
  907.  
  908.     if err then
  909.         if curLanguage == languages.lua and err:find("]") then
  910.             err = fs.getName(path) .. err:sub(err:find("]", 1, true) + 1, -1)
  911.         end
  912.  
  913.         while true do
  914.             title("LuaIDE - Error!")
  915.  
  916.             term.setBackgroundColor(colors[theme.err])
  917.             for i = 6, 8 do
  918.                 term.setCursorPos(3, i)
  919.                 term.write(string.rep(" ", w - 5))
  920.             end
  921.             term.setCursorPos(4, 7)
  922.             term.write("The program has crashed!")
  923.  
  924.             term.setBackgroundColor(colors[theme.prompt])
  925.             for i = 10, 14 do
  926.                 term.setCursorPos(3, i)
  927.                 term.write(string.rep(" ", w - 5))
  928.             end
  929.  
  930.             local formattedErr = curLanguage.parseError(err)
  931.             term.setCursorPos(4, 11)
  932.             term.write("Line: " .. formattedErr.line)
  933.             term.setCursorPos(4, 12)
  934.             term.write("Error:")
  935.             term.setCursorPos(5, 13)
  936.  
  937.             local a = formattedErr.display
  938.             local b = nil
  939.             if a:len() > w - 8 then
  940.                 for i = a:len(), 1, -1 do
  941.                     if a:sub(i, i) == " " then
  942.                         b = a:sub(i + 1, -1)
  943.                         a = a:sub(1, i)
  944.                         break
  945.                     end
  946.                 end
  947.             end
  948.  
  949.             term.write(a)
  950.             if b then
  951.                 term.setCursorPos(5, 14)
  952.                 term.write(b)
  953.             end
  954.            
  955.             local opt = prompt({{"Error Help", w/2 - 15, 17}, {"Go To Line", w/2 + 2, 17}},
  956.                 "horizontal")
  957.             if opt == "Error Help" then
  958.                 viewErrorHelp(formattedErr)
  959.             elseif opt == "Go To Line" then
  960.                 -- To prevent key from showing up in editor
  961.                 os.queueEvent(event_distract)
  962.                 os.pullEvent()
  963.  
  964.                 return "go to", tonumber(formattedErr.line)
  965.             end
  966.         end
  967.     end
  968. end
  969.  
  970.  
  971. --  -------- Functions
  972.  
  973. local function goto()
  974.     term.setBackgroundColor(colors[theme.backgroundHighlight])
  975.     term.setCursorPos(2, 1)
  976.     term.clearLine()
  977.     term.write("Line: ")
  978.     local line = modRead({visibleLength = w - 2})
  979.  
  980.     local num = tonumber(line)
  981.     if num and num > 0 then return num
  982.     else
  983.         term.setCursorPos(2, 1)
  984.         term.clearLine()
  985.         term.write("Not a line number!")
  986.         sleep(1.6)
  987.         return nil
  988.     end
  989. end
  990.  
  991. local function setsyntax()
  992.     local opts = {
  993.         "[Lua]   Brainfuck    None ",
  994.         " Lua   [Brainfuck]   None ",
  995.         " Lua    Brainfuck   [None]"
  996.     }
  997.     local sel = 1
  998.  
  999.     term.setCursorBlink(false)
  1000.     term.setBackgroundColor(colors[theme.backgroundHighlight])
  1001.     term.setCursorPos(2, 1)
  1002.     term.clearLine()
  1003.     term.write(opts[sel])
  1004.     while true do
  1005.         local e, but, x, y = os.pullEvent("key")
  1006.         if but == 203 then
  1007.             sel = math.max(1, sel - 1)
  1008.             term.setCursorPos(2, 1)
  1009.             term.clearLine()
  1010.             term.write(opts[sel])
  1011.         elseif but == 205 then
  1012.             sel = math.min(#opts, sel + 1)
  1013.             term.setCursorPos(2, 1)
  1014.             term.clearLine()
  1015.             term.write(opts[sel])
  1016.         elseif but == 28 then
  1017.             if sel == 1 then curLanguage = languages.lua
  1018.             elseif sel == 2 then curLanguage = languages.brainfuck
  1019.             elseif sel == 3 then curLanguage = languages.none end
  1020.             term.setCursorBlink(true)
  1021.             return
  1022.         end
  1023.     end
  1024. end
  1025.  
  1026.  
  1027. --  -------- Re-Indenting
  1028.  
  1029. local tabWidth = 2
  1030.  
  1031. local comments = {}
  1032. local strings = {}
  1033.  
  1034. local increment = {
  1035.     "if%s+.+%s+then%s*$",
  1036.     "for%s+.+%s+do%s*$",
  1037.     "while%s+.+%s+do%s*$",
  1038.     "repeat%s*$",
  1039.     "function%s+[a-zA-Z_0-9]\(.*\)%s*$"
  1040. }
  1041.  
  1042. local decrement = {
  1043.     "end",
  1044.     "until%s+.+"
  1045. }
  1046.  
  1047. local special = {
  1048.     "else%s*$",
  1049.     "elseif%s+.+%s+then%s*$"
  1050. }
  1051.  
  1052. local function check(func)
  1053.     for _, v in pairs(func) do
  1054.         local cLineStart = v["lineStart"]
  1055.         local cLineEnd = v["lineEnd"]
  1056.         local cCharStart = v["charStart"]
  1057.         local cCharEnd = v["charEnd"]
  1058.  
  1059.         if line >= cLineStart and line <= cLineEnd then
  1060.             if line == cLineStart then return cCharStart < charNumb
  1061.             elseif line == cLineEnd then return cCharEnd > charNumb
  1062.             else return true end
  1063.         end
  1064.     end
  1065. end
  1066.  
  1067. local function isIn(line, loc)
  1068.     if check(comments) then return true end
  1069.     if check(strings) then return true end
  1070.     return false
  1071. end
  1072.  
  1073. local function setComment(ls, le, cs, ce)
  1074.     comments[#comments + 1] = {}
  1075.     comments[#comments].lineStart = ls
  1076.     comments[#comments].lineEnd = le
  1077.     comments[#comments].charStart = cs
  1078.     comments[#comments].charEnd = ce
  1079. end
  1080.  
  1081. local function setString(ls, le, cs, ce)
  1082.     strings[#strings + 1] = {}
  1083.     strings[#strings].lineStart = ls
  1084.     strings[#strings].lineEnd = le
  1085.     strings[#strings].charStart = cs
  1086.     strings[#strings].charEnd = ce
  1087. end
  1088.  
  1089. local function map(contents)
  1090.     local inCom = false
  1091.     local inStr = false
  1092.  
  1093.     for i = 1, #contents do
  1094.         if content[i]:find("%-%-%[%[") and not inStr and not inCom then
  1095.             local cStart = content[i]:find("%-%-%[%[")
  1096.             setComment(i, nil, cStart, nil)
  1097.             inCom = true
  1098.         elseif content[i]:find("%-%-%[=%[") and not inStr and not inCom then
  1099.             local cStart = content[i]:find("%-%-%[=%[")
  1100.             setComment(i, nil, cStart, nil)
  1101.             inCom = true
  1102.         elseif content[i]:find("%[%[") and not inStr and not inCom then
  1103.             local cStart = content[i]:find("%[%[")
  1104.             setString(i, nil, cStart, nil)
  1105.             inStr = true
  1106.         elseif content[i]:find("%[=%[") and not inStr and not inCom then
  1107.             local cStart = content[i]:find("%[=%[")
  1108.             setString(i, nil, cStart, nil)
  1109.             inStr = true
  1110.         end
  1111.  
  1112.         if content[i]:find("%]%]") and inStr and not inCom then
  1113.             local cStart, cEnd = content[i]:find("%]%]")
  1114.             strings[#strings].lineEnd = i
  1115.             strings[#strings].charEnd = cEnd
  1116.             inStr = false
  1117.         elseif content[i]:find("%]=%]") and inStr and not inCom then
  1118.             local cStart, cEnd = content[i]:find("%]=%]")
  1119.             strings[#strings].lineEnd = i
  1120.             strings[#strings].charEnd = cEnd
  1121.             inStr = false
  1122.         end
  1123.  
  1124.         if content[i]:find("%]%]") and not inStr and inCom then
  1125.             local cStart, cEnd = content[i]:find("%]%]")
  1126.             comments[#comments].lineEnd = i
  1127.             comments[#comments].charEnd = cEnd
  1128.             inCom = false
  1129.         elseif content[i]:find("%]=%]") and not inStr and inCom then
  1130.             local cStart, cEnd = content[i]:find("%]=%]")
  1131.             comments[#comments].lineEnd = i
  1132.             comments[#comments].charEnd = cEnd
  1133.             inCom = false
  1134.         end
  1135.  
  1136.         if content[i]:find("%-%-") and not inStr and not inCom then
  1137.             local cStart = content[i]:find("%-%-")
  1138.             setComment(i, i, cStart, -1)
  1139.         elseif content[i]:find("'") and not inStr and not inCom then
  1140.             local cStart, cEnd = content[i]:find("'")
  1141.             local nextChar = content[i]:sub(cEnd + 1, string.len(content[i]))
  1142.             local _, cEnd = nextChar:find("'")
  1143.             setString(i, i, cStart, cEnd)
  1144.         elseif content[i]:find('"') and not inStr and not inCom then
  1145.             local cStart, cEnd = content[i]:find('"')
  1146.             local nextChar = content[i]:sub(cEnd + 1, string.len(content[i]))
  1147.             local _, cEnd = nextChar:find('"')
  1148.             setString(i, i, cStart, cEnd)
  1149.         end
  1150.     end
  1151. end
  1152.  
  1153. local function reindent(contents)
  1154.     local err = nil
  1155.     if curLanguage ~= languages.lua then
  1156.         err = "Cannot indent languages other than Lua!"
  1157.     elseif curLanguage.getCompilerErrors(table.concat(contents, "\n")).line ~= -1 then
  1158.         err = "Cannot indent a program with errors!"
  1159.     end
  1160.  
  1161.     if err then
  1162.         term.setCursorBlink(false)
  1163.         term.setCursorPos(2, 1)
  1164.         term.setBackgroundColor(colors[theme.backgroundHighlight])
  1165.         term.clearLine()
  1166.         term.write(err)
  1167.         sleep(1.6)
  1168.         return contents
  1169.     end
  1170.  
  1171.     local new = {}
  1172.     local level = 0
  1173.     for k, v in pairs(contents) do
  1174.         local incrLevel = false
  1175.         local foundIncr = false
  1176.         for _, incr in pairs(increment) do
  1177.             if v:find(incr) and not isIn(k, v:find(incr)) then
  1178.                 incrLevel = true
  1179.             end
  1180.             if v:find(incr:sub(1, -2)) and not isIn(k, v:find(incr)) then
  1181.                 foundIncr = true
  1182.             end
  1183.         end
  1184.  
  1185.         local decrLevel = false
  1186.         if not incrLevel then
  1187.             for _, decr in pairs(decrement) do
  1188.                 if v:find(decr) and not isIn(k, v:find(decr)) and not foundIncr then
  1189.                     level = math.max(0, level - 1)
  1190.                     decrLevel = true
  1191.                 end
  1192.             end
  1193.         end
  1194.  
  1195.         if not decrLevel then
  1196.             for _, sp in pairs(special) do
  1197.                 if v:find(sp) and not isIn(k, v:find(sp)) then
  1198.                     incrLevel = true
  1199.                     level = math.max(0, level - 1)
  1200.                 end
  1201.             end
  1202.         end
  1203.  
  1204.         new[k] = string.rep(" ", level * tabWidth) .. v
  1205.         if incrLevel then level = level + 1 end
  1206.     end
  1207.  
  1208.     return new
  1209. end
  1210.  
  1211.  
  1212. --  -------- Menu
  1213.  
  1214. local menu = {
  1215.     [1] = {"File",
  1216. --      "About",
  1217. --      "Settings",
  1218. --      "",
  1219.         "New File  ^+N",
  1220.         "Open File ^+O",
  1221.         "Save File ^+S",
  1222.         "Close     ^+W",
  1223.         "Print     ^+P",
  1224.         "Quit      ^+Q"
  1225.     }, [2] = {"Edit",
  1226.         "Cut Line   ^+X",
  1227.         "Copy Line  ^+C",
  1228.         "Paste Line ^+V",
  1229.         "Delete Line",
  1230.         "Clear Line"
  1231.     }, [3] = {"Functions",
  1232.         "Go To Line    ^+G",
  1233.         "Re-Indent     ^+I",
  1234.         "Set Syntax    ^+E",
  1235.         "Start of Line ^+<",
  1236.         "End of Line   ^+>"
  1237.     }, [4] = {"Run",
  1238.         "Run Program       ^+R",
  1239.         "Run w/ Args ^+Shift+R"
  1240.     }
  1241. }
  1242.  
  1243. local shortcuts = {
  1244.     -- File
  1245.     ["ctrl n"] = "New File  ^+N",
  1246.     ["ctrl o"] = "Open File ^+O",
  1247.     ["ctrl s"] = "Save File ^+S",
  1248.     ["ctrl w"] = "Close     ^+W",
  1249.     ["ctrl p"] = "Print     ^+P",
  1250.     ["ctrl q"] = "Quit      ^+Q",
  1251.  
  1252.     -- Edit
  1253.     ["ctrl x"] = "Cut Line   ^+X",
  1254.     ["ctrl c"] = "Copy Line  ^+C",
  1255.     ["ctrl v"] = "Paste Line ^+V",
  1256.  
  1257.     -- Functions
  1258.     ["ctrl g"] = "Go To Line    ^+G",
  1259.     ["ctrl i"] = "Re-Indent     ^+I",
  1260.     ["ctrl e"] = "Set Syntax    ^+E",
  1261.     ["ctrl 203"] = "Start of Line ^+<",
  1262.     ["ctrl 205"] = "End of Line   ^+>",
  1263.  
  1264.     -- Run
  1265.     ["ctrl r"] = "Run Program       ^+R",
  1266.     ["ctrl shift r"] = "Run w/ Args ^+Shift+R"
  1267. }
  1268.  
  1269. local menuFunctions = {
  1270.     -- File
  1271. --  ["About"] = function() end,
  1272. --  ["Settings"] = function() end,
  1273.     ["New File  ^+N"] = function(path, lines) saveFile(path, lines) return "new" end,
  1274.     ["Open File ^+O"] = function(path, lines) saveFile(path, lines) return "open" end,
  1275.     ["Save File ^+S"] = function(path, lines) saveFile(path, lines) end,
  1276.     ["Close     ^+W"] = function(path, lines) saveFile(path, lines) return "menu" end,
  1277.     ["Print     ^+P"] = function(path, lines) saveFile(path, lines) return nil end,
  1278.     ["Quit      ^+Q"] = function(path, lines) saveFile(path, lines) return "exit" end,
  1279.  
  1280.     -- Edit
  1281.     ["Cut Line   ^+X"] = function(path, lines, y)
  1282.         clipboard = lines[y] table.remove(lines, y) return nil, lines end,
  1283.     ["Copy Line  ^+C"] = function(path, lines, y) clipboard = lines[y] end,
  1284.     ["Paste Line ^+V"] = function(path, lines, y)
  1285.         if clipboard then table.insert(lines, y, clipboard) end return nil, lines end,
  1286.     ["Delete Line"] = function(path, lines, y) table.remove(lines, y) return nil, lines end,
  1287.     ["Clear Line"] = function(path, lines, y) lines[y] = "" return nil, lines, "cursor" end,
  1288.  
  1289.     -- Functions
  1290.     ["Go To Line    ^+G"] = function() return nil, "go to", goto() end,
  1291.     ["Re-Indent     ^+I"] = function(path, lines)
  1292.         local a = reindent(lines) saveFile(path, lines) return nil, a
  1293.     end,
  1294.     ["Set Syntax    ^+E"] = function(path, lines)
  1295.         setsyntax()
  1296.         if curLanguage == languages.brainfuck and lines[1] ~= "-- Syntax: Brainfuck" then
  1297.             table.insert(lines, 1, "-- Syntax: Brainfuck")
  1298.             return nil, lines
  1299.         end
  1300.     end,
  1301.     ["Start of Line ^+<"] = function() os.queueEvent("key", 199) end,
  1302.     ["End of Line   ^+>"] = function() os.queueEvent("key", 207) end,
  1303.  
  1304.     -- Run
  1305.     ["Run Program       ^+R"] = function(path, lines)
  1306.         saveFile(path, lines)
  1307.         return nil, run(path, lines, false)
  1308.     end,
  1309.     ["Run w/ Args ^+Shift+R"] = function(path, lines)
  1310.         saveFile(path, lines)
  1311.         return nil, run(path, lines, true)
  1312.     end,
  1313. }
  1314.  
  1315. local function drawMenu(open)
  1316.     term.setCursorPos(1, 1)
  1317.     term.setTextColor(colors[theme.textColor])
  1318.     term.setBackgroundColor(colors[theme.backgroundHighlight])
  1319.     term.clearLine()
  1320.     local curX = 0
  1321.     for _, v in pairs(menu) do
  1322.         term.setCursorPos(3 + curX, 1)
  1323.         term.write(v[1])
  1324.         curX = curX + v[1]:len() + 3
  1325.     end
  1326.  
  1327.     if open then
  1328.         local it = {}
  1329.         local x = 1
  1330.         for _, v in pairs(menu) do
  1331.             if open == v[1] then
  1332.                 it = v
  1333.                 break
  1334.             end
  1335.             x = x + v[1]:len() + 3
  1336.         end
  1337.         x = x + 1
  1338.  
  1339.         local items = {}
  1340.         for i = 2, #it do
  1341.             table.insert(items, it[i])
  1342.         end
  1343.  
  1344.         local len = 1
  1345.         for _, v in pairs(items) do if v:len() + 2 > len then len = v:len() + 2 end end
  1346.  
  1347.         for i, v in ipairs(items) do
  1348.             term.setCursorPos(x, i + 1)
  1349.             term.write(string.rep(" ", len))
  1350.             term.setCursorPos(x + 1, i + 1)
  1351.             term.write(v)
  1352.         end
  1353.         term.setCursorPos(x, #items + 2)
  1354.         term.write(string.rep(" ", len))
  1355.         return items, len
  1356.     end
  1357. end
  1358.  
  1359. local function triggerMenu(cx, cy)
  1360.     -- Determine clicked menu
  1361.     local curX = 0
  1362.     local open = nil
  1363.     for _, v in pairs(menu) do
  1364.         if cx >= curX + 3 and cx <= curX + v[1]:len() + 2 then
  1365.             open = v[1]
  1366.             break
  1367.         end
  1368.         curX = curX + v[1]:len() + 3
  1369.     end
  1370.     local menux = curX + 2
  1371.     if not open then return false end
  1372.  
  1373.     -- Flash menu item
  1374.     term.setCursorBlink(false)
  1375.     term.setCursorPos(menux, 1)
  1376.     term.setBackgroundColor(colors[theme.background])
  1377.     term.write(string.rep(" ", open:len() + 2))
  1378.     term.setCursorPos(menux + 1, 1)
  1379.     term.write(open)
  1380.     sleep(0.1)
  1381.     local items, len = drawMenu(open)
  1382.  
  1383.     local ret = true
  1384.  
  1385.     -- Pull events on menu
  1386.     local ox, oy = term.getCursorPos()
  1387.     while type(ret) ~= "string" do
  1388.         local e, but, x, y = os.pullEvent()
  1389.         if e == "mouse_click" then
  1390.             -- If clicked outside menu
  1391.             if x < menux - 1 or x > menux + len - 1 then break
  1392.             elseif y > #items + 2 then break
  1393.             elseif y == 1 then break end
  1394.  
  1395.             for i, v in ipairs(items) do
  1396.                 if y == i + 1 and x >= menux and x <= menux + len - 2 then
  1397.                     -- Flash when clicked
  1398.                     term.setCursorPos(menux, y)
  1399.                     term.setBackgroundColor(colors[theme.background])
  1400.                     term.write(string.rep(" ", len))
  1401.                     term.setCursorPos(menux + 1, y)
  1402.                     term.write(v)
  1403.                     sleep(0.1)
  1404.                     drawMenu(open)
  1405.  
  1406.                     -- Return item
  1407.                     ret = v
  1408.                     break
  1409.                 end
  1410.             end
  1411.         end
  1412.     end
  1413.  
  1414.     term.setCursorPos(ox, oy)
  1415.     term.setCursorBlink(true)
  1416.     return ret
  1417. end
  1418.  
  1419.  
  1420. --  -------- Editing
  1421.  
  1422. local standardsCompletions = {
  1423.     "if%s+.+%s+then%s*$",
  1424.     "for%s+.+%s+do%s*$",
  1425.     "while%s+.+%s+do%s*$",
  1426.     "repeat%s*$",
  1427.     "function%s+[a-zA-Z_0-9]?\(.*\)%s*$",
  1428.     "=%s*function%s*\(.*\)%s*$",
  1429.     "else%s*$",
  1430.     "elseif%s+.+%s+then%s*$"
  1431. }
  1432.  
  1433. local liveCompletions = {
  1434.     ["("] = ")",
  1435.     ["{"] = "}",
  1436.     ["["] = "]",
  1437.     ["\""] = "\"",
  1438.     ["'"] = "'",
  1439. }
  1440.  
  1441. local x, y = 0, 0
  1442. local edw, edh = 0, h - 1
  1443. local offx, offy = 0, 1
  1444. local scrollx, scrolly = 0, 0
  1445. local lines = {}
  1446. local liveErr = curLanguage.parseError(nil)
  1447. local displayCode = true
  1448. local lastEventClock = os.clock()
  1449.  
  1450. local function attemptToHighlight(line, regex, col)
  1451.     local match = string.match(line, regex)
  1452.     if match then
  1453.         if type(col) == "number" then term.setTextColor(col)
  1454.         elseif type(col) == "function" then term.setTextColor(col(match)) end
  1455.         term.write(match)
  1456.         term.setTextColor(colors[theme.textColor])
  1457.         return line:sub(match:len() + 1, -1)
  1458.     end
  1459.     return nil
  1460. end
  1461.  
  1462. local function writeHighlighted(line)
  1463.     if curLanguage == languages.lua then
  1464.         while line:len() > 0 do
  1465.             line = attemptToHighlight(line, "^%-%-%[%[.-%]%]", colors[theme.comment]) or
  1466.                 attemptToHighlight(line, "^%-%-.*", colors[theme.comment]) or
  1467.                 attemptToHighlight(line, "^\".*[^\\]\"", colors[theme.string]) or
  1468.                 attemptToHighlight(line, "^\'.*[^\\]\'", colors[theme.string]) or
  1469.                 attemptToHighlight(line, "^%[%[.-%]%]", colors[theme.string]) or
  1470.                 attemptToHighlight(line, "^[%w_]+", function(match)
  1471.                     if curLanguage.keywords[match] then
  1472.                         return colors[theme[curLanguage.keywords[match]]]
  1473.                     end
  1474.                     return colors[theme.textColor]
  1475.                 end) or
  1476.                 attemptToHighlight(line, "^[^%w_]", colors[theme.textColor])
  1477.         end
  1478.     else term.write(line) end
  1479. end
  1480.  
  1481. local function draw()
  1482.     -- Menu
  1483.     term.setTextColor(colors[theme.textColor])
  1484.     term.setBackgroundColor(colors[theme.editorBackground])
  1485.     term.clear()
  1486.     drawMenu()
  1487.  
  1488.     -- Line numbers
  1489.     offx, offy = tostring(#lines):len() + 1, 1
  1490.     edw, edh = w - offx, h - 1
  1491.  
  1492.     -- Draw text
  1493.     for i = 1, edh do
  1494.         local a = lines[scrolly + i]
  1495.         if a then
  1496.             local ln = string.rep(" ", offx - 1 - tostring(scrolly + i):len()) .. tostring(scrolly + i)
  1497.             local l = a:sub(scrollx + 1, edw + scrollx + 1)
  1498.             ln = ln .. ":"
  1499.  
  1500.             if liveErr.line == scrolly + i then ln = string.rep(" ", offx - 2) .. "!:" end
  1501.  
  1502.             term.setCursorPos(1, i + offy)
  1503.             term.setBackgroundColor(colors[theme.editorBackground])
  1504.             if scrolly + i == y then
  1505.                 if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then
  1506.                     term.setBackgroundColor(colors[theme.editorErrorHighlight])
  1507.                 else term.setBackgroundColor(colors[theme.editorLineHightlight]) end
  1508.                 term.clearLine()
  1509.             elseif scrolly + i == liveErr.line then
  1510.                 term.setBackgroundColor(colors[theme.editorError])
  1511.                 term.clearLine()
  1512.             end
  1513.  
  1514.             term.setCursorPos(1 - scrollx + offx, i + offy)
  1515.             if scrolly + i == y then
  1516.                 if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then
  1517.                     term.setBackgroundColor(colors[theme.editorErrorHighlight])
  1518.                 else term.setBackgroundColor(colors[theme.editorLineHightlight]) end
  1519.             elseif scrolly + i == liveErr.line then term.setBackgroundColor(colors[theme.editorError])
  1520.             else term.setBackgroundColor(colors[theme.editorBackground]) end
  1521.             if scrolly + i == liveErr.line then
  1522.                 if displayCode then term.write(a)
  1523.                 else term.write(liveErr.display) end
  1524.             else writeHighlighted(a) end
  1525.  
  1526.             term.setCursorPos(1, i + offy)
  1527.             if scrolly + i == y then
  1528.                 if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then
  1529.                     term.setBackgroundColor(colors[theme.editorError])
  1530.                 else term.setBackgroundColor(colors[theme.editorLineNumbersHighlight]) end
  1531.             elseif scrolly + i == liveErr.line then
  1532.                 term.setBackgroundColor(colors[theme.editorErrorHighlight])
  1533.             else term.setBackgroundColor(colors[theme.editorLineNumbers]) end
  1534.             term.write(ln)
  1535.         end
  1536.     end
  1537.     term.setCursorPos(x - scrollx + offx, y - scrolly + offy)
  1538. end
  1539.  
  1540. local function drawLine(...)
  1541.     local ls = {...}
  1542.     offx = tostring(#lines):len() + 1
  1543.     for _, ly in pairs(ls) do
  1544.         local a = lines[ly]
  1545.         if a then
  1546.             local ln = string.rep(" ", offx - 1 - tostring(ly):len()) .. tostring(ly)
  1547.             local l = a:sub(scrollx + 1, edw + scrollx + 1)
  1548.             ln = ln .. ":"
  1549.  
  1550.             if liveErr.line == ly then ln = string.rep(" ", offx - 2) .. "!:" end
  1551.  
  1552.             term.setCursorPos(1, (ly - scrolly) + offy)
  1553.             term.setBackgroundColor(colors[theme.editorBackground])
  1554.             if ly == y then
  1555.                 if ly == liveErr.line and os.clock() - lastEventClock > 3 then
  1556.                     term.setBackgroundColor(colors[theme.editorErrorHighlight])
  1557.                 else term.setBackgroundColor(colors[theme.editorLineHightlight]) end
  1558.             elseif ly == liveErr.line then
  1559.                 term.setBackgroundColor(colors[theme.editorError])
  1560.             end
  1561.             term.clearLine()
  1562.  
  1563.             term.setCursorPos(1 - scrollx + offx, (ly - scrolly) + offy)
  1564.             if ly == y then
  1565.                 if ly == liveErr.line and os.clock() - lastEventClock > 3 then
  1566.                     term.setBackgroundColor(colors[theme.editorErrorHighlight])
  1567.                 else term.setBackgroundColor(colors[theme.editorLineHightlight]) end
  1568.             elseif ly == liveErr.line then term.setBackgroundColor(colors[theme.editorError])
  1569.             else term.setBackgroundColor(colors[theme.editorBackground]) end
  1570.             if ly == liveErr.line then
  1571.                 if displayCode then term.write(a)
  1572.                 else term.write(liveErr.display) end
  1573.             else writeHighlighted(a) end
  1574.  
  1575.             term.setCursorPos(1, (ly - scrolly) + offy)
  1576.             if ly == y then
  1577.                 if ly == liveErr.line and os.clock() - lastEventClock > 3 then
  1578.                     term.setBackgroundColor(colors[theme.editorError])
  1579.                 else term.setBackgroundColor(colors[theme.editorLineNumbersHighlight]) end
  1580.             elseif ly == liveErr.line then
  1581.                 term.setBackgroundColor(colors[theme.editorErrorHighlight])
  1582.             else term.setBackgroundColor(colors[theme.editorLineNumbers]) end
  1583.             term.write(ln)
  1584.         end
  1585.     end
  1586.     term.setCursorPos(x - scrollx + offx, y - scrolly + offy)
  1587. end
  1588.  
  1589. local function cursorLoc(x, y, force)
  1590.     local sx, sy = x - scrollx, y - scrolly
  1591.     local redraw = false
  1592.     if sx < 1 then
  1593.         scrollx = x - 1
  1594.         sx = 1
  1595.         redraw = true
  1596.     elseif sx > edw then
  1597.         scrollx = x - edw
  1598.         sx = edw
  1599.         redraw = true
  1600.     end if sy < 1 then
  1601.         scrolly = y - 1
  1602.         sy = 1
  1603.         redraw = true
  1604.     elseif sy > edh then
  1605.         scrolly = y - edh
  1606.         sy = edh
  1607.         redraw = true
  1608.     end if redraw or force then draw() end
  1609.     term.setCursorPos(sx + offx, sy + offy)
  1610. end
  1611.  
  1612. local function executeMenuItem(a, path)
  1613.     if type(a) == "string" and menuFunctions[a] then
  1614.         local opt, nl, gtln = menuFunctions[a](path, lines, y)
  1615.         if type(opt) == "string" then term.setCursorBlink(false) return opt end
  1616.         if type(nl) == "table" then
  1617.             if #lines < 1 then table.insert(lines, "") end
  1618.             y = math.min(y, #lines)
  1619.             x = math.min(x, lines[y]:len() + 1)
  1620.             lines = nl
  1621.         elseif type(nl) == "string" then
  1622.             if nl == "go to" and gtln then
  1623.                 x, y = 1, math.min(#lines, gtln)
  1624.                 cursorLoc(x, y)
  1625.             end
  1626.         end
  1627.     end
  1628.     term.setCursorBlink(true)
  1629.     draw()
  1630.     term.setCursorPos(x - scrollx + offx, y - scrolly + offy)
  1631. end
  1632.  
  1633. local function edit(path)
  1634.     -- Variables
  1635.     x, y = 1, 1
  1636.     offx, offy = 0, 1
  1637.     scrollx, scrolly = 0, 0
  1638.     lines = loadFile(path)
  1639.     if not lines then return "menu" end
  1640.  
  1641.     -- Enable brainfuck
  1642.     if lines[1] == "-- Syntax: Brainfuck" then
  1643.         curLanguage = languages.brainfuck
  1644.     end
  1645.  
  1646.     -- Clocks
  1647.     local autosaveClock = os.clock()
  1648.     local scrollClock = os.clock() -- To prevent redraw flicker
  1649.     local liveErrorClock = os.clock()
  1650.     local hasScrolled = false
  1651.  
  1652.     -- Draw
  1653.     draw()
  1654.     term.setCursorPos(x + offx, y + offy)
  1655.     term.setCursorBlink(true)
  1656.    
  1657.     -- Main loop
  1658.     local tid = os.startTimer(3)
  1659.     while true do
  1660.         local e, key, cx, cy = os.pullEvent()
  1661.         if e == "key" and allowEditorEvent then
  1662.             if key == 200 and y > 1 then
  1663.                 -- Up
  1664.                 x, y = math.min(x, lines[y - 1]:len() + 1), y - 1
  1665.                 drawLine(y, y + 1)
  1666.                 cursorLoc(x, y)
  1667.             elseif key == 208 and y < #lines then
  1668.                 -- Down
  1669.                 x, y = math.min(x, lines[y + 1]:len() + 1), y + 1
  1670.                 drawLine(y, y - 1)
  1671.                 cursorLoc(x, y)
  1672.             elseif key == 203 and x > 1 then
  1673.                 -- Left
  1674.                 x = x - 1
  1675.                 local force = false
  1676.                 if y - scrolly + offy < offy + 1 then force = true end
  1677.                 cursorLoc(x, y, force)
  1678.             elseif key == 205 and x < lines[y]:len() + 1 then
  1679.                 -- Right
  1680.                 x = x + 1
  1681.                 local force = false
  1682.                 if y - scrolly + offy < offy + 1 then force = true end
  1683.                 cursorLoc(x, y, force)
  1684.             elseif (key == 28 or key == 156) and (displayCode and true or y + scrolly - 1 ==
  1685.                     liveErr.line) then
  1686.                 -- Enter
  1687.                 local f = nil
  1688.                 for _, v in pairs(standardsCompletions) do
  1689.                     if lines[y]:find(v) then f = v end
  1690.                 end
  1691.  
  1692.                 local _, spaces = lines[y]:find("^[ ]+")
  1693.                 if not spaces then spaces = 0 end
  1694.                 if f then
  1695.                     table.insert(lines, y + 1, string.rep(" ", spaces + 2))
  1696.                     if not f:find("else", 1, true) and not f:find("elseif", 1, true) then
  1697.                         table.insert(lines, y + 2, string.rep(" ", spaces) ..
  1698.                             (f:find("repeat", 1, true) and "until " or f:find("{", 1, true) and "}" or
  1699.                             "end"))
  1700.                     end
  1701.                     x, y = spaces + 3, y + 1
  1702.                     cursorLoc(x, y, true)
  1703.                 else
  1704.                     local oldLine = lines[y]
  1705.  
  1706.                     lines[y] = lines[y]:sub(1, x - 1)
  1707.                     table.insert(lines, y + 1, string.rep(" ", spaces) .. oldLine:sub(x, -1))
  1708.  
  1709.                     x, y = spaces + 1, y + 1
  1710.                     cursorLoc(x, y, true)
  1711.                 end
  1712.             elseif key == 14 and (displayCode and true or y + scrolly - 1 == liveErr.line) then
  1713.                 -- Backspace
  1714.                 if x > 1 then
  1715.                     local f = false
  1716.                     for k, v in pairs(liveCompletions) do
  1717.                         if lines[y]:sub(x - 1, x - 1) == k then f = true end
  1718.                     end
  1719.  
  1720.                     lines[y] = lines[y]:sub(1, x - 2) .. lines[y]:sub(x + (f and 1 or 0), -1)
  1721.                     drawLine(y)
  1722.                     x = x - 1
  1723.                     cursorLoc(x, y)
  1724.                 elseif y > 1 then
  1725.                     local prevLen = lines[y - 1]:len() + 1
  1726.                     lines[y - 1] = lines[y - 1] .. lines[y]
  1727.                     table.remove(lines, y)
  1728.                     x, y = prevLen, y - 1
  1729.                     cursorLoc(x, y, true)
  1730.                 end
  1731.             elseif key == 199 then
  1732.                 -- Home
  1733.                 x = 1
  1734.                 local force = false
  1735.                 if y - scrolly + offy < offy + 1 then force = true end
  1736.                 cursorLoc(x, y, force)
  1737.             elseif key == 207 then
  1738.                 -- End
  1739.                 x = lines[y]:len() + 1
  1740.                 local force = false
  1741.                 if y - scrolly + offy < offy + 1 then force = true end
  1742.                 cursorLoc(x, y, force)
  1743.             elseif key == 211 and (displayCode and true or y + scrolly - 1 == liveErr.line) then
  1744.                 -- Forward Delete
  1745.                 if x < lines[y]:len() + 1 then
  1746.                     lines[y] = lines[y]:sub(1, x - 1) .. lines[y]:sub(x + 1)
  1747.                     local force = false
  1748.                     if y - scrolly + offy < offy + 1 then force = true end
  1749.                     drawLine(y)
  1750.                     cursorLoc(x, y, force)
  1751.                 elseif y < #lines then
  1752.                     lines[y] = lines[y] .. lines[y + 1]
  1753.                     table.remove(lines, y + 1)
  1754.                     draw()
  1755.                     cursorLoc(x, y)
  1756.                 end
  1757.             elseif key == 15 and (displayCode and true or y + scrolly - 1 == liveErr.line) then
  1758.                 -- Tab
  1759.                 lines[y] = string.rep(" ", tabWidth) .. lines[y]
  1760.                 x = x + 2
  1761.                 local force = false
  1762.                 if y - scrolly + offy < offy + 1 then force = true end
  1763.                 drawLine(y)
  1764.                 cursorLoc(x, y, force)
  1765.             elseif key == 201 then
  1766.                 -- Page up
  1767.                 y = math.min(math.max(y - edh, 1), #lines)
  1768.                 x = math.min(lines[y]:len() + 1, x)
  1769.                 cursorLoc(x, y, true)
  1770.             elseif key == 209 then
  1771.                 -- Page down
  1772.                 y = math.min(math.max(y + edh, 1), #lines)
  1773.                 x = math.min(lines[y]:len() + 1, x)
  1774.                 cursorLoc(x, y, true)
  1775.             end
  1776.         elseif e == "char" and allowEditorEvent and (displayCode and true or
  1777.                 y + scrolly - 1 == liveErr.line) then
  1778.             local shouldIgnore = false
  1779.             for k, v in pairs(liveCompletions) do
  1780.                 if key == v and lines[y]:find(k, 1, true) and lines[y]:sub(x, x) == v then
  1781.                     shouldIgnore = true
  1782.                 end
  1783.             end
  1784.  
  1785.             local addOne = false
  1786.             if not shouldIgnore then
  1787.                 for k, v in pairs(liveCompletions) do
  1788.                     if key == k and lines[y]:sub(x, x) ~= k then key = key .. v addOne = true end
  1789.                 end
  1790.                 lines[y] = lines[y]:sub(1, x - 1) .. key .. lines[y]:sub(x, -1)
  1791.             end
  1792.  
  1793.             x = x + (addOne and 1 or key:len())
  1794.             local force = false
  1795.             if y - scrolly + offy < offy + 1 then force = true end
  1796.             drawLine(y)
  1797.             cursorLoc(x, y, force)
  1798.         elseif e == "mouse_click" and key == 1 then
  1799.             if cy > 1 then
  1800.                 if cx <= offx and cy - offy == liveErr.line - scrolly then
  1801.                     displayCode = not displayCode
  1802.                     drawLine(liveErr.line)
  1803.                 else
  1804.                     local oldy = y
  1805.                     y = math.min(math.max(scrolly + cy - offy, 1), #lines)
  1806.                     x = math.min(math.max(scrollx + cx - offx, 1), lines[y]:len() + 1)
  1807.                     if oldy ~= y then drawLine(oldy, y) end
  1808.                     cursorLoc(x, y)
  1809.                 end
  1810.             else
  1811.                 local a = triggerMenu(cx, cy)
  1812.                 if a then
  1813.                     local opt = executeMenuItem(a, path)
  1814.                     if opt then return opt end
  1815.                 end
  1816.             end
  1817.         elseif e == "shortcut" then
  1818.             local a = shortcuts[key .. " " .. cx]
  1819.             if a then
  1820.                 local parent = nil
  1821.                 local curx = 0
  1822.                 for i, mv in ipairs(menu) do
  1823.                     for _, iv in pairs(mv) do
  1824.                         if iv == a then
  1825.                             parent = menu[i][1]
  1826.                             break
  1827.                         end
  1828.                     end
  1829.                     if parent then break end
  1830.                     curx = curx + mv[1]:len() + 3
  1831.                 end
  1832.                 local menux = curx + 2
  1833.  
  1834.                 -- Flash menu item
  1835.                 term.setCursorBlink(false)
  1836.                 term.setCursorPos(menux, 1)
  1837.                 term.setBackgroundColor(colors[theme.background])
  1838.                 term.write(string.rep(" ", parent:len() + 2))
  1839.                 term.setCursorPos(menux + 1, 1)
  1840.                 term.write(parent)
  1841.                 sleep(0.1)
  1842.                 drawMenu()
  1843.  
  1844.                 -- Execute item
  1845.                 local opt = executeMenuItem(a, path)
  1846.                 if opt then return opt end
  1847.             end
  1848.         elseif e == "mouse_scroll" then
  1849.             if key == -1 and scrolly > 0 then
  1850.                 scrolly = scrolly - 1
  1851.                 if os.clock() - scrollClock > 0.0005 then
  1852.                     draw()
  1853.                     term.setCursorPos(x - scrollx + offx, y - scrolly + offy)
  1854.                 end
  1855.                 scrollClock = os.clock()
  1856.                 hasScrolled = true
  1857.             elseif key == 1 and scrolly < #lines - edh then
  1858.                 scrolly = scrolly + 1
  1859.                 if os.clock() - scrollClock > 0.0005 then
  1860.                     draw()
  1861.                     term.setCursorPos(x - scrollx + offx, y - scrolly + offy)
  1862.                 end
  1863.                 scrollClock = os.clock()
  1864.                 hasScrolled = true
  1865.             end
  1866.         elseif e == "timer" and key == tid then
  1867.             drawLine(y)
  1868.             tid = os.startTimer(3)
  1869.         end
  1870.  
  1871.         -- Draw
  1872.         if hasScrolled and os.clock() - scrollClock > 0.1 then
  1873.             draw()
  1874.             term.setCursorPos(x - scrollx + offx, y - scrolly + offy)
  1875.             hasScrolled = false
  1876.         end
  1877.  
  1878.         -- Autosave
  1879.         if os.clock() - autosaveClock > autosaveInterval then
  1880.             saveFile(path, lines)
  1881.             autosaveClock = os.clock()
  1882.         end
  1883.  
  1884.         -- Errors
  1885.         if os.clock() - liveErrorClock > 1 then
  1886.             local prevLiveErr = liveErr
  1887.             liveErr = curLanguage.parseError(nil)
  1888.             local code = ""
  1889.             for _, v in pairs(lines) do code = code .. v .. "\n" end
  1890.  
  1891.             liveErr = curLanguage.getCompilerErrors(code)
  1892.             liveErr.line = math.min(liveErr.line - 2, #lines)
  1893.             if liveErr ~= prevLiveErr then draw() end
  1894.             liveErrorClock = os.clock()
  1895.         end
  1896.     end
  1897.  
  1898.     return "menu"
  1899. end
  1900.  
  1901.  
  1902. --  -------- Open File
  1903.  
  1904. local function newFile()
  1905.     local wid = w - 13
  1906.  
  1907.     -- Get name
  1908.     title("Lua IDE - New File")
  1909.     local name = centerRead(wid, "/")
  1910.     if not name or name == "" then return "menu" end
  1911.     name = "/" .. name
  1912.  
  1913.     -- Clear
  1914.     title("Lua IDE - New File")
  1915.     term.setTextColor(colors[theme.textColor])
  1916.     term.setBackgroundColor(colors[theme.promptHighlight])
  1917.     for i = 8, 10 do
  1918.         term.setCursorPos(w/2 - wid/2, i)
  1919.         term.write(string.rep(" ", wid))
  1920.     end
  1921.     term.setCursorPos(1, 9)
  1922.     if fs.isDir(name) then
  1923.         centerPrint("Cannot Edit a Directory!")
  1924.         sleep(1.6)
  1925.         return "menu"
  1926.     elseif fs.exists(name) then
  1927.         centerPrint("File Already Exists!")
  1928.         local opt = prompt({{"Open", w/2 - 9, 14}, {"Cancel", w/2 + 2, 14}}, "horizontal")
  1929.         if opt == "Open" then return "edit", name
  1930.         elseif opt == "Cancel" then return "menu" end
  1931.     else return "edit", name end
  1932. end
  1933.  
  1934. local function openFile()
  1935.     local wid = w - 13
  1936.  
  1937.     -- Get name
  1938.     title("Lua IDE - Open File")
  1939.     local name = centerRead(wid, "/")
  1940.     if not name or name == "" then return "menu" end
  1941.     name = "/" .. name
  1942.  
  1943.     -- Clear
  1944.     title("Lua IDE - New File")
  1945.     term.setTextColor(colors[theme.textColor])
  1946.     term.setBackgroundColor(colors[theme.promptHighlight])
  1947.     for i = 8, 10 do
  1948.         term.setCursorPos(w/2 - wid/2, i)
  1949.         term.write(string.rep(" ", wid))
  1950.     end
  1951.     term.setCursorPos(1, 9)
  1952.     if fs.isDir(name) then
  1953.         centerPrint("Cannot Open a Directory!")
  1954.         sleep(1.6)
  1955.         return "menu"
  1956.     elseif not fs.exists(name) then
  1957.         centerPrint("File Doesn't Exist!")
  1958.         local opt = prompt({{"Create", w/2 - 11, 14}, {"Cancel", w/2 + 2, 14}}, "horizontal")
  1959.         if opt == "Create" then return "edit", name
  1960.         elseif opt == "Cancel" then return "menu" end
  1961.     else return "edit", name end
  1962. end
  1963.  
  1964.  
  1965. --  -------- Settings
  1966.  
  1967. local function update()
  1968.     local function draw(status)
  1969.         title("LuaIDE - Update")
  1970.         term.setBackgroundColor(colors[theme.prompt])
  1971.         term.setTextColor(colors[theme.textColor])
  1972.         for i = 8, 10 do
  1973.             term.setCursorPos(w/2 - (status:len() + 4), i)
  1974.             write(string.rep(" ", status:len() + 4))
  1975.         end
  1976.         term.setCursorPos(w/2 - (status:len() + 4), 9)
  1977.         term.write(" - " .. status .. " ")
  1978.  
  1979.         term.setBackgroundColor(colors[theme.errHighlight])
  1980.         for i = 8, 10 do
  1981.             term.setCursorPos(w/2 + 2, i)
  1982.             term.write(string.rep(" ", 10))
  1983.         end
  1984.         term.setCursorPos(w/2 + 2, 9)
  1985.         term.write(" > Cancel ")
  1986.     end
  1987.  
  1988.     if not http then
  1989.         draw("HTTP API Disabled!")
  1990.         sleep(1.6)
  1991.         return "settings"
  1992.     end
  1993.  
  1994.     draw("Updating...")
  1995.     local tID = os.startTimer(10)
  1996.     http.request(updateURL)
  1997.     while true do
  1998.         local e, but, x, y = os.pullEvent()
  1999.         if (e == "key" and but == 28) or
  2000.                 (e == "mouse_click" and x >= w/2 + 2 and x <= w/2 + 12 and y == 9) then
  2001.             draw("Cancelled")
  2002.             sleep(1.6)
  2003.             break
  2004.         elseif e == "http_success" and but == updateURL then
  2005.             local new = x.readAll()
  2006.             local curf = io.open(ideLocation, "r")
  2007.             local cur = curf:read("*a")
  2008.             curf:close()
  2009.  
  2010.             if cur ~= new then
  2011.                 draw("Update Found")
  2012.                 sleep(1.6)
  2013.                 local f = io.open(ideLocation, "w")
  2014.                 f:write(new)
  2015.                 f:close()
  2016.  
  2017.                 draw("Click to Exit")
  2018.                 while true do
  2019.                     local e = os.pullEvent()
  2020.                     if e == "mouse_click" or (not isAdvanced() and e == "key") then break end
  2021.                 end
  2022.                 return "exit"
  2023.             else
  2024.                 draw("No Updates Found!")
  2025.                 sleep(1.6)
  2026.                 break
  2027.             end
  2028.         elseif e == "http_failure" or (e == "timer" and but == tID) then
  2029.             draw("Update Failed!")
  2030.             sleep(1.6)
  2031.             break
  2032.         end
  2033.     end
  2034.  
  2035.     return "settings"
  2036. end
  2037.  
  2038. local function changeTheme()
  2039.     title("LuaIDE - Theme")
  2040.  
  2041.     if isAdvanced() then
  2042.         local disThemes = {"Back"}
  2043.         for _, v in pairs(availableThemes) do table.insert(disThemes, v[1]) end
  2044.         local t = scrollingPrompt(disThemes)
  2045.         local url = nil
  2046.         for _, v in pairs(availableThemes) do if v[1] == t then url = v[2] end end
  2047.  
  2048.         if not url then return "settings" end
  2049.         if t == "Dawn (Default)" then
  2050.             term.setBackgroundColor(colors[theme.backgroundHighlight])
  2051.             term.setCursorPos(3, 3)
  2052.             term.clearLine()
  2053.             term.write("LuaIDE - Loaded Theme!")
  2054.             sleep(1.6)
  2055.  
  2056.             fs.delete(themeLocation)
  2057.             theme = defaultTheme
  2058.             return "menu"
  2059.         end
  2060.  
  2061.         term.setBackgroundColor(colors[theme.backgroundHighlight])
  2062.         term.setCursorPos(3, 3)
  2063.         term.clearLine()
  2064.         term.write("LuaIDE - Downloading...")
  2065.  
  2066.         fs.delete("/.LuaIDE_temp_theme_file")
  2067.         download(url, "/.LuaIDE_temp_theme_file")
  2068.         local a = loadTheme("/.LuaIDE_temp_theme_file")
  2069.  
  2070.         term.setCursorPos(3, 3)
  2071.         term.clearLine()
  2072.         if a then
  2073.             term.write("LuaIDE - Loaded Theme!")
  2074.             fs.delete(themeLocation)
  2075.             fs.move("/.LuaIDE_temp_theme_file", themeLocation)
  2076.             theme = a
  2077.             sleep(1.6)
  2078.             return "menu"
  2079.         end
  2080.        
  2081.         term.write("LuaIDE - Could Not Load Theme!")
  2082.         fs.delete("/.LuaIDE_temp_theme_file")
  2083.         sleep(1.6)
  2084.         return "settings"
  2085.     else
  2086.         term.setCursorPos(1, 8)
  2087.         centerPrint("Themes are not available on")
  2088.         centerPrint("normal computers!")
  2089.     end
  2090. end
  2091.  
  2092. local function settings()
  2093.     title("LuaIDE - Settings")
  2094.  
  2095.     local opt = prompt({{"Change Theme", w/2 - 17, 8}, {"Return to Menu", w/2 - 22, 13},
  2096.         {"Check for Updates", w/2 + 2, 8}, {"Exit IDE", w/2 + 2, 13, bg = colors[theme.err],
  2097.         highlight = colors[theme.errHighlight]}}, "vertical", true)
  2098.     if opt == "Change Theme" then return changeTheme()
  2099.     elseif opt == "Check for Updates" then return update()
  2100.     elseif opt == "Return to Menu" then return "menu"
  2101.     elseif opt == "Exit IDE" then return "exit" end
  2102. end
  2103.  
  2104.  
  2105. --  -------- Menu
  2106.  
  2107. local function menu()
  2108.     title("Welcome to LuaIDE " .. version)
  2109.  
  2110.     local opt = prompt({{"New File", w/2 - 13, 8}, {"Open File", w/2 - 14, 13},
  2111.         {"Settings", w/2 + 2, 8}, {"Exit IDE", w/2 + 2, 13, bg = colors[theme.err],
  2112.         highlight = colors[theme.errHighlight]}}, "vertical", true)
  2113.     if opt == "New File" then return "new"
  2114.     elseif opt == "Open File" then return "open"
  2115.     elseif opt == "Settings" then return "settings"
  2116.     elseif opt == "Exit IDE" then return "exit" end
  2117. end
  2118.  
  2119.  
  2120. --  -------- Main
  2121.  
  2122. local function main(arguments)
  2123.     local opt, data = "menu", nil
  2124.  
  2125.     -- Check arguments
  2126.     if type(arguments) == "table" and #arguments > 0 then
  2127.         local f = "/" .. shell.resolve(arguments[1])
  2128.         if fs.isDir(f) then print("Cannot edit a directory.") end
  2129.         opt, data = "edit", f
  2130.     end
  2131.  
  2132.     -- Main run loop
  2133.     while true do
  2134.         -- Menu
  2135.         if opt == "menu" then opt = menu() end
  2136.  
  2137.         -- Other
  2138.         if opt == "new" then opt, data = newFile()
  2139.         elseif opt == "open" then opt, data = openFile()
  2140.         elseif opt == "settings" then opt = settings()
  2141.         end if opt == "exit" then break end
  2142.  
  2143.         -- Edit
  2144.         if opt == "edit" and data then opt = edit(data) end
  2145.     end
  2146. end
  2147.  
  2148. -- Load Theme
  2149. if fs.exists(themeLocation) then theme = loadTheme(themeLocation) end
  2150. if not theme and isAdvanced() then theme = defaultTheme
  2151. elseif not theme then theme = normalTheme end
  2152.  
  2153. -- Run
  2154. local _, err = pcall(function()
  2155.     parallel.waitForAny(function() main(args) end, monitorKeyboardShortcuts)
  2156. end)
  2157.  
  2158. -- Catch errors
  2159. if err and not err:find("Terminated") then
  2160.     term.setCursorBlink(false)
  2161.     title("LuaIDE - Crash! D:")
  2162.  
  2163.     term.setBackgroundColor(colors[theme.err])
  2164.     for i = 6, 8 do
  2165.         term.setCursorPos(5, i)
  2166.         term.write(string.rep(" ", 36))
  2167.     end
  2168.     term.setCursorPos(6, 7)
  2169.     term.write("LuaIDE Has Crashed! D:")
  2170.  
  2171.     term.setBackgroundColor(colors[theme.background])
  2172.     term.setCursorPos(2, 10)
  2173.     print(err)
  2174.  
  2175.     term.setBackgroundColor(colors[theme.prompt])
  2176.     local _, cy = term.getCursorPos()
  2177.     for i = cy + 1, cy + 4 do
  2178.         term.setCursorPos(5, i)
  2179.         term.write(string.rep(" ", 36))
  2180.     end
  2181.     term.setCursorPos(6, cy + 2)
  2182.     term.write("Please report this error to")
  2183.     term.setCursorPos(6, cy + 3)
  2184.     term.write("GravityScore! ")
  2185.    
  2186.     term.setBackgroundColor(colors[theme.background])
  2187.     if isAdvanced() then centerPrint("Click to Exit...", h - 1)
  2188.     else centerPrint("Press Any Key to Exit...", h - 1) end
  2189.     while true do
  2190.         local e = os.pullEvent()
  2191.         if e == "mouse_click" or (not isAdvanced() and e == "key") then break end
  2192.     end
  2193.  
  2194.     -- Prevent key from being shown
  2195.     os.queueEvent(event_distract)
  2196.     os.pullEvent()
  2197. end
  2198.  
  2199. -- Exit
  2200. term.setBackgroundColor(colors.black)
  2201. term.setTextColor(colors.white)
  2202. term.clear()
  2203. term.setCursorPos(1, 1)
  2204. centerPrint("Thank You for Using Lua IDE " .. version)
  2205. centerPrint("Made by GravityScore")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement