Advertisement
Guest User

lua

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