Advertisement
Kodos

[OC]LuaIDE

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