Advertisement
RespawnTime

LuaIDE Optimized

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