Advertisement
SupremoHaLO

Codigo IDE Lua

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