Advertisement
willwac

CC-Lua IDE-Tre

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