luaide
Description
Made by GravityScore
Installation
Copy one of these commands into your ComputerCraft terminal:
wget:
wget https://raw.githubusercontent.com/cc-tweaked/treasure-disks/master/data/computercraft/lua/treasure/gravity_score/luaide/luaide.lua luaideArchive:
wget https://cc.shobie.xyz/cc/get/gh-cc-tweaked-treasure-disks-data-computercraft-lua-treasure-gravity-score-luaid luaide
Quick Install:
wget https://cc.shobie.xyz/cc/get/gh-cc-tweaked-treasure-disks-data-computercraft-lua-treasure-gravity-score-luaid luaide
Usage
Run: luaide
Tags
Source
View Original SourceCode Preview
--
-- Lua IDE
-- Made by GravityScore
--
-- -------- Variables
-- Version
local version = "1.0"
local args = {...}
-- Editing
local w, h = term.getSize()
local tabWidth = 2
local autosaveInterval = 20
local allowEditorEvent = true
local keyboardShortcutTimeout = 0.4
-- Clipboard
local clipboard = nil
-- Theme
local theme = {}
-- Language
local languages = {}
local curLanguage = {}
-- Events
local event_distract = "luaide_distractionEvent"
-- Locations
--local updateURL = "https://raw.github.com/GravityScore/LuaIDE/master/luaide.lua"
local ideLocation = "/" .. shell.getRunningProgram()
local themeLocation = "/.LuaIDE-Theme"
local function isAdvanced() return term.isColor and term.isColor() end
-- -------- Utilities
local function modRead(properties)
local w, h = term.getSize()
local defaults = {replaceChar = nil, history = nil, visibleLength = nil, textLength = nil,
liveUpdates = nil, exitOnKey = nil}
if not properties then properties = {} end
for k, v in pairs(defaults) do if not properties[k] then properties[k] = v end end
if properties.replaceChar then properties.replaceChar = properties.replaceChar:sub(1, 1) end
if not properties.visibleLength then properties.visibleLength = w end
local sx, sy = term.getCursorPos()
local line = ""
local pos = 0
local historyPos = nil
local function redraw(repl)
local scroll = 0
if properties.visibleLength and sx + pos > properties.visibleLength + 1 then
scroll = (sx + pos) - (properties.visibleLength + 1)
end
term.setCursorPos(sx, sy)
local a = repl or properties.replaceChar
if a then term.write(string.rep(a, line:len() - scroll))
else term.write(line:sub(scroll + 1, -1)) end
term.setCursorPos(sx + pos - scroll, sy)
end
local function sendLiveUpdates(event, ...)
if type(properties.liveUpdates) == "function" then
local ox, oy = term.getCursorPos()
local a, data = properties.liveUpdates(line, event, ...)
if a == true and data == nil then
term.setCursorBlink(false)
return line
elseif a == true and data ~= nil then
term.setCursorBlink(false)
return data
end
term.setCursorPos(ox, oy)
end
end
term.setCursorBlink(true)
while true do
local e, but, x, y, p4, p5 = os.pullEvent()
if e == "char" then
local s = false
if properties.textLength and line:len() < properties.textLength then s = true
elseif not properties.textLength then s = true end
local canType = true
if not properties.grantPrint and properties.refusePrint then
local canTypeKeys = {}
if type(properties.refusePrint) == "table" then
for _, v in pairs(properties.refusePrint) do
table.insert(canTypeKeys, tostring(v):sub(1, 1))
end
elseif type(properties.refusePrint) == "string" then
for char in properties.refusePrint:gmatch(".") do
table.insert(canTypeKeys, char)
end
end
for _, v in pairs(canTypeKeys) do if but == v then canType = false end end
elseif properties.grantPrint then
canType = false
local canTypeKeys = {}
if type(properties.grantPrint) == "table" then
for _, v in pairs(properties.grantPrint) do
table.insert(canTypeKeys, tostring(v):sub(1, 1))
end
elseif type(properties.grantPrint) == "string" then
for char in properties.grantPrint:gmatch(".") do
table.insert(canTypeKeys, char)
end
end
for _, v in pairs(canTypeKeys) do if but == v then canType = true end end
end
if s and canType then
line = line:sub(1, pos) .. but .. line:sub(pos + 1, -1)
pos = pos + 1
redraw()
end
elseif e == "key" then
if but == keys.enter then break
elseif but == keys.left then if pos > 0 then pos = pos - 1 redraw() end
elseif but == keys.right then if pos < line:len() then pos = pos + 1 redraw() end
elseif (but == keys.up or but == keys.down) and properties.history then
redraw(" ")
if but == keys.up then
if historyPos == nil and #properties.history > 0 then
historyPos = #properties.history
elseif historyPos > 1 then
historyPos = historyPos - 1
end
elseif but == keys.down then
if historyPos == #properties.history then historyPos = nil
elseif historyPos ~= nil then historyPos = historyPos + 1 end
end
if properties.history and historyPos then
line = properties.history[historyPos]
pos = line:len()
else
line = ""
pos = 0
end
redraw()
local a = sendLiveUpdates("history")
if a then return a end
elseif but == keys.backspace and pos > 0 then
redraw(" ")
line = line:sub(1, pos - 1) .. line:sub(pos + 1, -1)
pos = pos - 1
redraw()
local a = sendLiveUpdates("delete")
if a then return a end
elseif but == keys.home then
pos = 0
redraw()
elseif but == keys.delete and pos < line:len() then
redraw(" ")
line = line:sub(1, pos) .. line:sub(pos + 2, -1)
redraw()
local a = sendLiveUpdates("delete")
if a then return a end
elseif but == keys["end"] then
pos = line:len()
redraw()
elseif properties.exitOnKey then
if but == properties.exitOnKey or (properties.exitOnKey == "control" and
(but == keys.leftCtrl or but == keys.rightCtrl)) then
term.setCursorBlink(false)
return nil
end
end
end
local a = sendLiveUpdates(e, but, x, y, p4, p5)
if a then return a end
end
term.setCursorBlink(false)
if line ~= nil then line = line:gsub("^%s*(.-)%s*{{code}}quot;, "%1") end
return line
end
-- -------- Themes
local defaultTheme = {
background = "gray",
backgroundHighlight = "lightGray",
prompt = "cyan",
promptHighlight = "lightBlue",
err = "red",
errHighlight = "pink",
editorBackground = "gray",
editorLineHightlight = "lightBlue",
editorLineNumbers = "gray",
editorLineNumbersHighlight = "lightGray",
editorError = "pink",
editorErrorHighlight = "red",
textColor = "white",
conditional = "yellow",
constant = "orange",
["function"] = "magenta",
string = "red",
comment = "lime"
}
local normalTheme = {
background = "black",
backgroundHighlight = "black",
prompt = "black",
promptHighlight = "black",
err = "black",
errHighlight = "black",
editorBackground = "black",
editorLineHightlight = "black",
editorLineNumbers = "black",
editorLineNumbersHighlight = "white",
editorError = "black",
editorErrorHighlight = "black",
textColor = "white",
conditional = "white",
constant = "white",
["function"] = "white",
string = "white",
comment = "white"
}
--[[
local availableThemes = {
{"Water (Default)", "https://raw.github.com/GravityScore/LuaIDE/master/themes/default.txt"},
{"Fire", "https://raw.github.com/GravityScore/LuaIDE/master/themes/fire.txt"},
{"Sublime Text 2", "https://raw.github.com/GravityScore/LuaIDE/master/themes/st2.txt"},
{"Midnight", "https://raw.github.com/GravityScore/LuaIDE/master/themes/midnight.txt"},
{"TheOriginalBIT", "https://raw.github.com/GravityScore/LuaIDE/master/themes/bit.txt"},
{"Superaxander", "https://raw.github.com/GravityScore/LuaIDE/master/themes/superaxander.txt"},
{"Forest", "https://raw.github.com/GravityScore/LuaIDE/master/themes/forest.txt"},
{"Night", "https://raw.github.com/GravityScore/LuaIDE/master/themes/night.txt"},
{"Original", "https://raw.github.com/GravityScore/LuaIDE/master/themes/original.txt"},
}
]]--
local function loadTheme(path)
local f = io.open(path)
local l = f:read("*l")
local config = {}
while l ~= nil do
local k, v = string.match(l, "^(%a+)=(%a+)")
if k and v then config[k] = v end
l = f:read("*l")
end
f:close()
return config
end
-- Load Theme
if isAdvanced() then theme = defaultTheme
else theme = normalTheme end
-- -------- Drawing
local function centerPrint(text, ny)
if type(text) == "table" then for _, v in pairs(text) do centerPrint(v) end
else
local x, y = term.getCursorPos()
local w, h = term.getSize()
term.setCursorPos(w/2 - text:len()/2 + (#text % 2 == 0 and 1 or 0), ny or y)
print(text)
end
end
local function title(t)
term.setTextColor(colors[theme.textColor])
term.setBackgroundColor(colors[theme.background])
term.clear()
term.setBackgroundColor(colors[theme.backgroundHighlight])
for i = 2, 4 do term.setCursorPos(1, i) term.clearLine() end
term.setCursorPos(3, 3)
term.write(t)
end
local function centerRead(wid, begt)
local function liveUpdate(line, e, but, x, y, p4, p5)
if isAdvanced() and e == "mouse_click" and x >= w/2 - wid/2 and x <= w/2 - wid/2 + 10
and y >= 13 and y <= 15 then
return true, ""
end
end
if not begt then begt = "" end
term.setTextColor(colors[theme.textColor])
term.setBackgroundColor(colors[theme.promptHighlight])
for i = 8, 10 do
term.setCursorPos(w/2 - wid/2, i)
term.write(string.rep(" ", wid))
end
if isAdvanced() then
term.setBackgroundColor(colors[theme.errHighlight])
for i = 13, 15 do
term.setCursorPos(w/2 - wid/2 + 1, i)
term.write(string.rep(" ", 10))
end
term.setCursorPos(w/2 - wid/2 + 2, 14)
term.write("> Cancel")
end
term.setBackgroundColor(colors[theme.promptHighlight])
term.setCursorPos(w/2 - wid/2 + 1, 9)
term.write("> " .. begt)
return modRead({visibleLength = w/2 + wid/2, liveUpdates = liveUpdate})
end
-- -------- Prompt
local function prompt(list, dir, isGrid)
local function draw(sel)
for i, v in ipairs(list) do
if i == sel then term.setBackgroundColor(v.highlight or colors[theme.promptHighlight])
else term.setBackgroundColor(v.bg or colors[theme.prompt]) end
term.setTextColor(v.tc or colors[theme.textColor])
for i = -1, 1 do
term.setCursorPos(v[2], v[3] + i)
term.write(string.rep(" ", v[1]:len() + 4))
end
term.setCursorPos(v[2], v[3])
if i == sel then
term.setBackgroundColor(v.highlight or colors[theme.promptHighlight])
term.write(" > ")
else term.write(" - ") end
term.write(v[1] .. " ")
end
end
local key1 = dir == "horizontal" and keys.left or keys.up
local key2 = dir == "horizontal" and keys.right or keys.down
local sel = 1
draw(sel)
while true do
local e, but, x, y = os.pullEvent()
if e == "key" and but == keys.enter then
return list[sel][1]
elseif e == "key" and but == key1 and sel > 1 then
sel = sel - 1
draw(sel)
elseif e == "key" and but == key2 and ((err == true and sel < #list - 1) or (sel < #list)) then
sel = sel + 1
draw(sel)
elseif isGrid and e == "key" and but == keys.left and sel > 2 and #list == 4 then
sel = sel - 2
draw(sel)
elseif isGrid and e == "key" and but == keys.right and sel < 3 and #list == 4 then
sel = sel + 2
draw(sel)
elseif e == "mouse_click" then
for i, v in ipairs(list) do
if x >= v[2] - 1 and x <= v[2] + v[1]:len() + 3 and y >= v[3] - 1 and y <= v[3] + 1 then
return list[i][1]
end
end
end
end
end
local function scrollingPrompt(list)
local function draw(items, sel, loc)
for i, v in ipairs(items) do
local bg = colors[theme.prompt]
local bghigh = colors[theme.promptHighlight]
if v:find("Back") or v:find("Return") then
bg = colors[theme.err]
bghigh = colors[theme.errHighlight]
end
if i == sel then term.setBackgroundColor(bghigh)
else term.setBackgroundColor(bg) end
term.setTextColor(colors[theme.textColor])
for x = -1, 1 do
term.setCursorPos(3, (i * 4) + x + 4)
term.write(string.rep(" ", w - 13))
end
term.setCursorPos(3, i * 4 + 4)
if i == sel then
term.setBackgroundColor(bghigh)
term.write(" > ")
else term.write(" - ") end
term.write(v .. " ")
end
end
local function updateDisplayList(items, loc, len)
local ret = {}
for i = 1, len do
local item = items[i + loc - 1]
if item then table.insert(ret, item) end
end
return ret
end
-- Variables
local sel = 1
local loc = 1
local len = 3
local disList = updateDisplayList(list, loc, len)
draw(disList, sel, loc)
-- Loop
while true do
local e, key, x, y = os.pullEvent()
if e == "mouse_click" then
for i, v in ipairs(disList) do
if x >= 3 and x <= w - 11 and y >= i * 4 + 3 and y <= i * 4 + 5 then return v end
end
elseif e == "key" and key == keys.up then
if sel > 1 then
sel = sel - 1
draw(disList, sel, loc)
elseif loc > 1 then
loc = loc - 1
disList = updateDisplayList(list, loc, len)
draw(disList, sel, loc)
end
elseif e == "key" and key == keys.down then
if sel < len then
sel = sel + 1
draw(disList, sel, loc)
elseif loc + len - 1 < #list then
loc = loc + 1
disList = updateDisplayList(list, loc, len)
draw(disList, sel, loc)
end
elseif e == "mouse_scroll" then
os.queueEvent("key", key == -1 and keys.up or keys.down)
elseif e == "key" and key == keys.enter then
return disList[sel]
end
end
end
function monitorKeyboardShortcuts()
local ta, tb = nil, nil
local allowChar = false
local shiftPressed = false
while true do
local event, char = os.pullEvent()
if event == "key" and (char == keys.leftShift or char == keys.rightShift) then
shiftPressed = true
tb = os.startTimer(keyboardShortcutTimeout)
elseif event == "key" and (char == keys.leftCtrl or char == keys.rightCtrl) then
allowEditorEvent = false
allowChar = true
ta = os.startTimer(keyboardShortcutTimeout)
elseif event == "key" and allowChar then
local name = nil
for k, v in pairs(keys) do
if v == char then
if shiftPressed then os.queueEvent("shortcut", "ctrl shift", k:lower())
else os.queueEvent("shortcut", "ctrl", k:lower()) end
sleep(0.005)
allowEditorEvent = true
end
end
if shiftPressed then os.queueEvent("shortcut", "ctrl shift", char)
else os.queueEvent("shortcut", "ctrl", char) end
elseif event == "timer" and char == ta then
allowEditorEvent = true
allowChar = false
elseif event == "timer" and char == tb then
shiftPressed = false
end
end
end
-- -------- Saving and Loading
--[[local function download(url, path)
for i = 1, 3 do
local response = http.get(url)
if response then
local data = response.readAll()
response.close()
if path then
local f = io.open(path, "w")
f:write(data)
f:close()
end
return true
end
end
return false
end]]
local function saveFile(path, lines)
local dir = path:sub(1, path:len() - fs.getName(path):len())
if not fs.exists(dir) then fs.makeDir(dir) end
if not fs.isDir(path) and not fs.isReadOnly(path) then
local a = ""
for _, v in pairs(lines) do a = a .. v .. "\n" end
local f = io.open(path, "w")
f:write(a)
f:close()
return true
else return false end
end
local function loadFile(path)
if not fs.exists(path) then
local dir = path:sub(1, path:len() - fs.getName(path):len())
if not fs.exists(dir) then fs.makeDir(dir) end
local f = io.open(path, "w")
f:write("")
f:close()
end
local l = {}
if fs.exists(path) and not fs.isDir(path) then
local f = io.open(path, "r")
if f then
local a = f:read("*l")
while a do
table.insert(l, a)
a = f:read("*l")
end
f:close()
end
else return nil end
if #l < 1 then table.insert(l, "") end
return l
end
-- -------- Languages
languages.lua = {}
languages.brainfuck = {}
languages.none = {}
-- Lua
languages.lua.helpTips = {
"A function you tried to call doesn't exist.",
"You made a typo.",
"The index of an array is nil.",
"The wrong variable type was passed.",
"A function/variable doesn't exist.",
"You missed an 'end'.",
"You missed a 'then'.",
"You declared a variable incorrectly.",
"One of your variables is mysteriously nil."
}
languages.lua.defaultHelpTips = {
2, 5
}
languages.lua.errors = {
["Attempt to call nil."] = {1, 2},
["Attempt to index nil."] = {3, 2},
[".+ expected, got .+"] = {4, 2, 9},
["'end' expected"] = {6, 2},
["'then' expected"] = {7, 2},
["'=' expected"] = {8, 2}
}
languages.lua.keywords = {
["and"] = "conditional",
["break"] = "conditional",
["do"] = "conditional",
["else"] = "conditional",
["elseif"] = "conditional",
["end"] = "conditional",
["for"] = "conditional",
["function"] = "conditional",
["if"] = "conditional",
["in"] = "conditional",
["local"] = "conditional",
["not"] = "conditional",
["or"] = "conditional",
["repeat"] = "conditional",
["return"] = "conditional",
["then"] = "conditional",
["until"] = "conditional",
["while"] = "conditional",
["true"] = "constant",
["false"] = "constant",
["nil"] = "constant",
["print"] = "function",
["write"] = "function",
["sleep"] = "function",
["pairs"] = "function",
["ipairs"] = "function",
["load"] = "function",
["loadfile"] = "function",
["rawset"] = "function",
["rawget"] = "function",
}
languages.lua.parseError = function(e)
local ret = {filename = "unknown", line = -1, display = "Unknown!", err = ""}
if e and e ~= "" then
ret.err = e
if e:find(":") then
ret.filename = e:sub(1, e:find(":") - 1):gsub("^%s*(.-)%s*{{code}}quot;, "%1")
-- The "" is needed to circumvent a CC bug
e = (e:sub(e:find(":") + 1) .. ""):gsub("^%s*(.-)%s*{{code}}quot;, "%1")
if e:find(":") then
ret.line = e:sub(1, e:find(":") - 1)
e = e:sub(e:find(":") + 2):gsub("^%s*(.-)%s*{{code}}quot;, "%1") .. ""
end
end
ret.display = e:sub(1, 1):upper() .. e:sub(2, -1) .. "."
end
return ret
end
languages.lua.getCompilerErrors = function(code)
code = "local function ee65da6af1cb6f63fee9a081246f2fd92b36ef2(...)\n\n" .. code .. "\n\nend"
local fn, err = load(code)
if not err then
local _, e = pcall(fn)
if e then err = e end
end
if err then
local a = err:find("]", 1, true)
if a then err = "string" .. err:sub(a + 1, -1) end
local ret = languages.lua.parseError(err)
if tonumber(ret.line) then ret.line = tonumber(ret.line) end
return ret
else return languages.lua.parseError(nil) end
end
languages.lua.run = function(path, ar)
local fn, err = loadfile(path, _ENV)
if not err then
_, err = pcall(function() fn(table.unpack(ar)) end)
end
return err
end
-- Brainfuck
languages.brainfuck.helpTips = {
"Well idk...",
"Isn't this the whole point of the language?",
"Ya know... Not being able to debug it?",
"You made a typo."
}
languages.brainfuck.defaultHelpTips = {
1, 2, 3
}
languages.brainfuck.errors = {
["No matching '['"] = {1, 2, 3, 4}
}
languages.brainfuck.keywords = {}
languages.brainfuck.parseError = function(e)
local ret = {filename = "unknown", line = -1, display = "Unknown!", err = ""}
if e and e ~= "" then
ret.err = e
ret.line = e:sub(1, e:find(":") - 1)
e = e:sub(e:find(":") + 2):gsub("^%s*(.-)%s*{{code}}quot;, "%1") .. ""
ret.display = e:sub(1, 1):upper() .. e:sub(2, -1) .. "."
end
return ret
end
languages.brainfuck.mapLoops = function(code)
-- Map loops
local loopLocations = {}
local loc = 1
local line = 1
for let in string.gmatch(code, ".") do
if let == "[" then
loopLocations[loc] = true
elseif let == "]" then
local found = false
for i = loc, 1, -1 do
if loopLocations[i] == true then
loopLocations[i] = loc
found = true
end
end
if not found then
return line .. ": No matching '['"
end
end
if let == "\n" then line = line + 1 end
loc = loc + 1
end
return loopLocations
end
languages.brainfuck.getCompilerErrors = function(code)
local a = languages.brainfuck.mapLoops(code)
if type(a) == "string" then return languages.brainfuck.parseError(a)
else return languages.brainfuck.parseError(nil) end
end
languages.brainfuck.run = function(path)
-- Read from file
local f = io.open(path, "r")
local content = f:read("*a")
f:close()
-- Define environment
local dataCells = {}
local dataPointer = 1
local instructionPointer = 1
-- Map loops
local loopLocations = languages.brainfuck.mapLoops(content)
if type(loopLocations) == "string" then return loopLocations end
-- Execute code
while true do
local let = content:sub(instructionPointer, instructionPointer)
if let == ">" then
dataPointer = dataPointer + 1
if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
elseif let == "<" then
if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
dataPointer = dataPointer - 1
if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
elseif let == "+" then
if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
dataCells[tostring(dataPointer)] = dataCells[tostring(dataPointer)] + 1
elseif let == "-" then
if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
dataCells[tostring(dataPointer)] = dataCells[tostring(dataPointer)] - 1
elseif let == "." then
if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
if term.getCursorPos() >= w then print("") end
write(string.char(math.max(1, dataCells[tostring(dataPointer)])))
elseif let == "," then
if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
term.setCursorBlink(true)
local e, but = os.pullEvent("char")
term.setCursorBlink(false)
dataCells[tostring(dataPointer)] = string.byte(but)
if term.getCursorPos() >= w then print("") end
write(but)
elseif let == "/" then
if not dataCells[tostring(dataPointer)] then dataCells[tostring(dataPointer)] = 0 end
if term.getCursorPos() >= w then print("") end
write(dataCells[tostring(dataPointer)])
elseif let == "[" then
if dataCells[tostring(dataPointer)] == 0 then
for k, v in pairs(loopLocations) do
if k == instructionPointer then instructionPointer = v end
end
end
elseif let == "]" then
for k, v in pairs(loopLocations) do
if v == instructionPointer then instructionPointer = k - 1 end
end
end
instructionPointer = instructionPointer + 1
if instructionPointer > content:len() then print("") break end
end
end
-- None
languages.none.helpTips = {}
languages.none.defaultHelpTips = {}
languages.none.errors = {}
languages.none.keywords = {}
languages.none.parseError = function(err)
return {filename = "", line = -1, display = "", err = ""}
end
languages.none.getCompilerErrors = function(code)
return languages.none.parseError(nil)
end
languages.none.run = function(path) end
-- Load language
curLanguage = languages.lua
-- -------- Run GUI
local function viewErrorHelp(e)
title("LuaIDE - Error Help")
local tips = nil
for k, v in pairs(curLanguage.errors) do
if e.display:find(k) then tips = v break end
end
term.setBackgroundColor(colors[theme.err])
for i = 6, 8 do
term.setCursorPos(5, i)
term.write(string.rep(" ", 35))
end
term.setBackgroundColor(colors[theme.prompt])
for i = 10, 18 do
term.setCursorPos(5, i)
term.write(string.rep(" ", 46))
end
if tips then
term.setBackgroundColor(colors[theme.err])
term.setCursorPos(6, 7)
term.write("Error Help")
term.setBackgroundColor(colors[theme.prompt])
for i, v in ipairs(tips) do
term.setCursorPos(7, i + 10)
term.write("- " .. curLanguage.helpTips[v])
end
else
term.setBackgroundColor(colors[theme.err])
term.setCursorPos(6, 7)
term.write("No Error Tips Available!")
term.setBackgroundColor(colors[theme.prompt])
term.setCursorPos(6, 11)
term.write("There are no error tips available, but")
term.setCursorPos(6, 12)
term.write("you could see if it was any of these:")
for i, v in ipairs(curLanguage.defaultHelpTips) do
term.setCursorPos(7, i + 12)
term.write("- " .. curLanguage.helpTips[v])
end
end
prompt({{"Back", w - 8, 7}}, "horizontal")
end
local function run(path, lines, useArgs)
local ar = {}
if useArgs then
title("LuaIDE - Run " .. fs.getName(path))
local s = centerRead(w - 13, fs.getName(path) .. " ")
for m in string.gmatch(s, "[^ \t]+") do ar[#ar + 1] = m:gsub("^%s*(.-)%s*{{code}}quot;, "%1") end
end
saveFile(path, lines)
term.setCursorBlink(false)
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.clear()
term.setCursorPos(1, 1)
local err = curLanguage.run(path, ar)
term.setBackgroundColor(colors.black)
print("\n")
if err then
if isAdvanced() then term.setTextColor(colors.red) end
centerPrint("The program has crashed!")
end
term.setTextColor(colors.white)
centerPrint("Press any key to return to LuaIDE...")
while true do
local e = os.pullEvent()
if e == "key" then break end
end
-- To prevent key from showing up in editor
os.queueEvent(event_distract)
os.pullEvent()
if err then
if curLanguage == languages.lua and err:find("]") then
err = fs.getName(path) .. err:sub(err:find("]", 1, true) + 1, -1)
end
while true do
title("LuaIDE - Error!")
term.setBackgroundColor(colors[theme.err])
for i = 6, 8 do
term.setCursorPos(3, i)
term.write(string.rep(" ", w - 5))
end
term.setCursorPos(4, 7)
term.write("The program has crashed!")
term.setBackgroundColor(colors[theme.prompt])
for i = 10, 14 do
term.setCursorPos(3, i)
term.write(string.rep(" ", w - 5))
end
local formattedErr = curLanguage.parseError(err)
term.setCursorPos(4, 11)
term.write("Line: " .. formattedErr.line)
term.setCursorPos(4, 12)
term.write("Error:")
term.setCursorPos(5, 13)
local a = formattedErr.display
local b = nil
if a:len() > w - 8 then
for i = a:len(), 1, -1 do
if a:sub(i, i) == " " then
b = a:sub(i + 1, -1)
a = a:sub(1, i)
break
end
end
end
term.write(a)
if b then
term.setCursorPos(5, 14)
term.write(b)
end
local opt = prompt({{"Error Help", w/2 - 15, 17}, {"Go To Line", w/2 + 2, 17}},
"horizontal")
if opt == "Error Help" then
viewErrorHelp(formattedErr)
elseif opt == "Go To Line" then
-- To prevent key from showing up in editor
os.queueEvent(event_distract)
os.pullEvent()
return "go to", tonumber(formattedErr.line)
end
end
end
end
-- -------- Functions
local function goto()
term.setBackgroundColor(colors[theme.backgroundHighlight])
term.setCursorPos(2, 1)
term.clearLine()
term.write("Line: ")
local line = modRead({visibleLength = w - 2})
local num = tonumber(line)
if num and num > 0 then return num
else
term.setCursorPos(2, 1)
term.clearLine()
term.write("Not a line number!")
sleep(1.6)
return nil
end
end
local function setsyntax()
local opts = {
"[Lua] Brainfuck None ",
" Lua [Brainfuck] None ",
" Lua Brainfuck [None]"
}
local sel = 1
term.setCursorBlink(false)
term.setBackgroundColor(colors[theme.backgroundHighlight])
term.setCursorPos(2, 1)
term.clearLine()
term.write(opts[sel])
while true do
local e, but, x, y = os.pullEvent("key")
if but == keys.left then
sel = math.max(1, sel - 1)
term.setCursorPos(2, 1)
term.clearLine()
term.write(opts[sel])
elseif but == keys.right then
sel = math.min(#opts, sel + 1)
term.setCursorPos(2, 1)
term.clearLine()
term.write(opts[sel])
elseif but == keys.enter or but == keys.numPadEnter then
if sel == 1 then curLanguage = languages.lua
elseif sel == 2 then curLanguage = languages.brainfuck
elseif sel == 3 then curLanguage = languages.none end
term.setCursorBlink(true)
return
end
end
end
-- -------- Re-Indenting
local tabWidth = 2
local comments = {}
local strings = {}
local increment = {
"if%s+.+%s+then%s*{{code}}quot;,
"for%s+.+%s+do%s*{{code}}quot;,
"while%s+.+%s+do%s*{{code}}quot;,
"repeat%s*{{code}}quot;,
"function%s+[a-zA-Z_0-9]\(.*\)%s*{{code}}quot;
}
local decrement = {
"end",
"until%s+.+"
}
local special = {
"else%s*{{code}}quot;,
"elseif%s+.+%s+then%s*{{code}}quot;
}
local function check(func)
for _, v in pairs(func) do
local cLineStart = v["lineStart"]
local cLineEnd = v["lineEnd"]
local cCharStart = v["charStart"]
local cCharEnd = v["charEnd"]
if line >= cLineStart and line <= cLineEnd then
if line == cLineStart then return cCharStart < charNumb
elseif line == cLineEnd then return cCharEnd > charNumb
else return true end
end
end
end
local function isIn(line, loc)
if check(comments) then return true end
if check(strings) then return true end
return false
end
local function setComment(ls, le, cs, ce)
comments[#comments + 1] = {}
comments[#comments].lineStart = ls
comments[#comments].lineEnd = le
comments[#comments].charStart = cs
comments[#comments].charEnd = ce
end
local function setString(ls, le, cs, ce)
strings[#strings + 1] = {}
strings[#strings].lineStart = ls
strings[#strings].lineEnd = le
strings[#strings].charStart = cs
strings[#strings].charEnd = ce
end
local function map(contents)
local inCom = false
local inStr = false
for i = 1, #contents do
if content[i]:find("%-%-%[%[") and not inStr and not inCom then
local cStart = content[i]:find("%-%-%[%[")
setComment(i, nil, cStart, nil)
inCom = true
elseif content[i]:find("%-%-%[=%[") and not inStr and not inCom then
local cStart = content[i]:find("%-%-%[=%[")
setComment(i, nil, cStart, nil)
inCom = true
elseif content[i]:find("%[%[") and not inStr and not inCom then
local cStart = content[i]:find("%[%[")
setString(i, nil, cStart, nil)
inStr = true
elseif content[i]:find("%[=%[") and not inStr and not inCom then
local cStart = content[i]:find("%[=%[")
setString(i, nil, cStart, nil)
inStr = true
end
if content[i]:find("%]%]") and inStr and not inCom then
local cStart, cEnd = content[i]:find("%]%]")
strings[#strings].lineEnd = i
strings[#strings].charEnd = cEnd
inStr = false
elseif content[i]:find("%]=%]") and inStr and not inCom then
local cStart, cEnd = content[i]:find("%]=%]")
strings[#strings].lineEnd = i
strings[#strings].charEnd = cEnd
inStr = false
end
if content[i]:find("%]%]") and not inStr and inCom then
local cStart, cEnd = content[i]:find("%]%]")
comments[#comments].lineEnd = i
comments[#comments].charEnd = cEnd
inCom = false
elseif content[i]:find("%]=%]") and not inStr and inCom then
local cStart, cEnd = content[i]:find("%]=%]")
comments[#comments].lineEnd = i
comments[#comments].charEnd = cEnd
inCom = false
end
if content[i]:find("%-%-") and not inStr and not inCom then
local cStart = content[i]:find("%-%-")
setComment(i, i, cStart, -1)
elseif content[i]:find("'") and not inStr and not inCom then
local cStart, cEnd = content[i]:find("'")
local nextChar = content[i]:sub(cEnd + 1, string.len(content[i]))
local _, cEnd = nextChar:find("'")
setString(i, i, cStart, cEnd)
elseif content[i]:find('"') and not inStr and not inCom then
local cStart, cEnd = content[i]:find('"')
local nextChar = content[i]:sub(cEnd + 1, string.len(content[i]))
local _, cEnd = nextChar:find('"')
setString(i, i, cStart, cEnd)
end
end
end
local function reindent(contents)
local err = nil
if curLanguage ~= languages.lua then
err = "Cannot indent languages other than Lua!"
elseif curLanguage.getCompilerErrors(table.concat(contents, "\n")).line ~= -1 then
err = "Cannot indent a program with errors!"
end
if err then
term.setCursorBlink(false)
term.setCursorPos(2, 1)
term.setBackgroundColor(colors[theme.backgroundHighlight])
term.clearLine()
term.write(err)
sleep(1.6)
return contents
end
local new = {}
local level = 0
for k, v in pairs(contents) do
local incrLevel = false
local foundIncr = false
for _, incr in pairs(increment) do
if v:find(incr) and not isIn(k, v:find(incr)) then
incrLevel = true
end
if v:find(incr:sub(1, -2)) and not isIn(k, v:find(incr)) then
foundIncr = true
end
end
local decrLevel = false
if not incrLevel then
for _, decr in pairs(decrement) do
if v:find(decr) and not isIn(k, v:find(decr)) and not foundIncr then
level = math.max(0, level - 1)
decrLevel = true
end
end
end
if not decrLevel then
for _, sp in pairs(special) do
if v:find(sp) and not isIn(k, v:find(sp)) then
incrLevel = true
level = math.max(0, level - 1)
end
end
end
new[k] = string.rep(" ", level * tabWidth) .. v
if incrLevel then level = level + 1 end
end
return new
end
-- -------- Menu
local menu = {
[1] = {"File",
-- "About",
-- "Settings",
-- "",
"New File ^+N",
"Open File ^+O",
"Save File ^+S",
"Close ^+W",
"Print ^+P",
"Quit ^+Q"
}, [2] = {"Edit",
"Cut Line ^+X",
"Copy Line ^+C",
"Paste Line ^+V",
"Delete Line",
"Clear Line"
}, [3] = {"Functions",
"Go To Line ^+G",
"Re-Indent ^+I",
"Set Syntax ^+E",
"Start of Line ^+<",
"End of Line ^+>"
}, [4] = {"Run",
"Run Program ^+R",
"Run w/ Args ^+Shift+R"
}
}
local shortcuts = {
-- File
["ctrl n"] = "New File ^+N",
["ctrl o"] = "Open File ^+O",
["ctrl s"] = "Save File ^+S",
["ctrl w"] = "Close ^+W",
["ctrl p"] = "Print ^+P",
["ctrl q"] = "Quit ^+Q",
-- Edit
["ctrl x"] = "Cut Line ^+X",
["ctrl c"] = "Copy Line ^+C",
["ctrl v"] = "Paste Line ^+V",
-- Functions
["ctrl g"] = "Go To Line ^+G",
["ctrl i"] = "Re-Indent ^+I",
["ctrl e"] = "Set Syntax ^+E",
["ctrl left"] = "Start of Line ^+<",
["ctrl right"] = "End of Line ^+>",
-- Run
["ctrl r"] = "Run Program ^+R",
["ctrl shift r"] = "Run w/ Args ^+Shift+R"
}
local menuFunctions = {
-- File
-- ["About"] = function() end,
-- ["Settings"] = function() end,
["New File ^+N"] = function(path, lines) saveFile(path, lines) return "new" end,
["Open File ^+O"] = function(path, lines) saveFile(path, lines) return "open" end,
["Save File ^+S"] = function(path, lines) saveFile(path, lines) end,
["Close ^+W"] = function(path, lines) saveFile(path, lines) return "menu" end,
["Print ^+P"] = function(path, lines) saveFile(path, lines) return nil end,
["Quit ^+Q"] = function(path, lines) saveFile(path, lines) return "exit" end,
-- Edit
["Cut Line ^+X"] = function(path, lines, y)
clipboard = lines[y] table.remove(lines, y) return nil, lines end,
["Copy Line ^+C"] = function(path, lines, y) clipboard = lines[y] end,
["Paste Line ^+V"] = function(path, lines, y)
if clipboard then table.insert(lines, y, clipboard) end return nil, lines end,
["Delete Line"] = function(path, lines, y) table.remove(lines, y) return nil, lines end,
["Clear Line"] = function(path, lines, y) lines[y] = "" return nil, lines, "cursor" end,
-- Functions
["Go To Line ^+G"] = function() return nil, "go to", goto() end,
["Re-Indent ^+I"] = function(path, lines)
local a = reindent(lines) saveFile(path, lines) return nil, a
end,
["Set Syntax ^+E"] = function(path, lines)
setsyntax()
if curLanguage == languages.brainfuck and lines[1] ~= "-- Syntax: Brainfuck" then
table.insert(lines, 1, "-- Syntax: Brainfuck")
return nil, lines
end
end,
["Start of Line ^+<"] = function() os.queueEvent("key", keys.home) end,
["End of Line ^+>"] = function() os.queueEvent("key", keys["end"]) end,
-- Run
["Run Program ^+R"] = function(path, lines)
saveFile(path, lines)
return nil, run(path, lines, false)
end,
["Run w/ Args ^+Shift+R"] = function(path, lines)
saveFile(path, lines)
return nil, run(path, lines, true)
end,
}
local function drawMenu(open)
term.setCursorPos(1, 1)
term.setTextColor(colors[theme.textColor])
term.setBackgroundColor(colors[theme.backgroundHighlight])
term.clearLine()
local curX = 0
for _, v in pairs(menu) do
term.setCursorPos(3 + curX, 1)
term.write(v[1])
curX = curX + v[1]:len() + 3
end
if open then
local it = {}
local x = 1
for _, v in pairs(menu) do
if open == v[1] then
it = v
break
end
x = x + v[1]:len() + 3
end
x = x + 1
local items = {}
for i = 2, #it do
table.insert(items, it[i])
end
local len = 1
for _, v in pairs(items) do if v:len() + 2 > len then len = v:len() + 2 end end
for i, v in ipairs(items) do
term.setCursorPos(x, i + 1)
term.write(string.rep(" ", len))
term.setCursorPos(x + 1, i + 1)
term.write(v)
end
term.setCursorPos(x, #items + 2)
term.write(string.rep(" ", len))
return items, len
end
end
local function triggerMenu(cx, cy)
-- Determine clicked menu
local curX = 0
local open = nil
for _, v in pairs(menu) do
if cx >= curX + 3 and cx <= curX + v[1]:len() + 2 then
open = v[1]
break
end
curX = curX + v[1]:len() + 3
end
local menux = curX + 2
if not open then return false end
-- Flash menu item
term.setCursorBlink(false)
term.setCursorPos(menux, 1)
term.setBackgroundColor(colors[theme.background])
term.write(string.rep(" ", open:len() + 2))
term.setCursorPos(menux + 1, 1)
term.write(open)
sleep(0.1)
local items, len = drawMenu(open)
local ret = true
-- Pull events on menu
local ox, oy = term.getCursorPos()
while type(ret) ~= "string" do
local e, but, x, y = os.pullEvent()
if e == "mouse_click" then
-- If clicked outside menu
if x < menux - 1 or x > menux + len - 1 then break
elseif y > #items + 2 then break
elseif y == 1 then break end
for i, v in ipairs(items) do
if y == i + 1 and x >= menux and x <= menux + len - 2 then
-- Flash when clicked
term.setCursorPos(menux, y)
term.setBackgroundColor(colors[theme.background])
term.write(string.rep(" ", len))
term.setCursorPos(menux + 1, y)
term.write(v)
sleep(0.1)
drawMenu(open)
-- Return item
ret = v
break
end
end
end
end
term.setCursorPos(ox, oy)
term.setCursorBlink(true)
return ret
end
-- -------- Editing
local standardsCompletions = {
"if%s+.+%s+then%s*{{code}}quot;,
"for%s+.+%s+do%s*{{code}}quot;,
"while%s+.+%s+do%s*{{code}}quot;,
"repeat%s*{{code}}quot;,
"function%s+[a-zA-Z_0-9]?\(.*\)%s*{{code}}quot;,
"=%s*function%s*\(.*\)%s*{{code}}quot;,
"else%s*{{code}}quot;,
"elseif%s+.+%s+then%s*{{code}}quot;
}
local liveCompletions = {
["("] = ")",
["{"] = "}",
["["] = "]",
["\""] = "\"",
["'"] = "'",
}
local x, y = 0, 0
local edw, edh = 0, h - 1
local offx, offy = 0, 1
local scrollx, scrolly = 0, 0
local lines = {}
local liveErr = curLanguage.parseError(nil)
local displayCode = true
local lastEventClock = os.clock()
local function attemptToHighlight(line, regex, col)
local match = string.match(line, regex)
if match then
if type(col) == "number" then term.setTextColor(col)
elseif type(col) == "function" then term.setTextColor(col(match)) end
term.write(match)
term.setTextColor(colors[theme.textColor])
return line:sub(match:len() + 1, -1)
end
return nil
end
local function writeHighlighted(line)
if curLanguage == languages.lua then
while line:len() > 0 do
line = attemptToHighlight(line, "^%-%-%[%[.-%]%]", colors[theme.comment]) or
attemptToHighlight(line, "^%-%-.*", colors[theme.comment]) or
attemptToHighlight(line, "^\".*[^\\]\"", colors[theme.string]) or
attemptToHighlight(line, "^\'.*[^\\]\'", colors[theme.string]) or
attemptToHighlight(line, "^%[%[.-%]%]", colors[theme.string]) or
attemptToHighlight(line, "^[%w_]+", function(match)
if curLanguage.keywords[match] then
return colors[theme[curLanguage.keywords[match]]]
end
return colors[theme.textColor]
end) or
attemptToHighlight(line, "^[^%w_]", colors[theme.textColor])
end
else term.write(line) end
end
local function draw()
-- Menu
term.setTextColor(colors[theme.textColor])
term.setBackgroundColor(colors[theme.editorBackground])
term.clear()
drawMenu()
-- Line numbers
offx, offy = tostring(#lines):len() + 1, 1
edw, edh = w - offx, h - 1
-- Draw text
for i = 1, edh do
local a = lines[scrolly + i]
if a then
local ln = string.rep(" ", offx - 1 - tostring(scrolly + i):len()) .. tostring(scrolly + i)
local l = a:sub(scrollx + 1, edw + scrollx + 1)
ln = ln .. ":"
if liveErr.line == scrolly + i then ln = string.rep(" ", offx - 2) .. "!:" end
term.setCursorPos(1, i + offy)
term.setBackgroundColor(colors[theme.editorBackground])
if scrolly + i == y then
if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then
term.setBackgroundColor(colors[theme.editorErrorHighlight])
else term.setBackgroundColor(colors[theme.editorLineHightlight]) end
term.clearLine()
elseif scrolly + i == liveErr.line then
term.setBackgroundColor(colors[theme.editorError])
term.clearLine()
end
term.setCursorPos(1 - scrollx + offx, i + offy)
if scrolly + i == y then
if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then
term.setBackgroundColor(colors[theme.editorErrorHighlight])
else term.setBackgroundColor(colors[theme.editorLineHightlight]) end
elseif scrolly + i == liveErr.line then term.setBackgroundColor(colors[theme.editorError])
else term.setBackgroundColor(colors[theme.editorBackground]) end
if scrolly + i == liveErr.line then
if displayCode then term.write(a)
else term.write(liveErr.display) end
else writeHighlighted(a) end
term.setCursorPos(1, i + offy)
if scrolly + i == y then
if scrolly + i == liveErr.line and os.clock() - lastEventClock > 3 then
term.setBackgroundColor(colors[theme.editorError])
else term.setBackgroundColor(colors[theme.editorLineNumbersHighlight]) end
elseif scrolly + i == liveErr.line then
term.setBackgroundColor(colors[theme.editorErrorHighlight])
else term.setBackgroundColor(colors[theme.editorLineNumbers]) end
term.write(ln)
end
end
term.setCursorPos(x - scrollx + offx, y - scrolly + offy)
end
local function drawLine(...)
local ls = {...}
offx = tostring(#lines):len() + 1
for _, ly in pairs(ls) do
local a = lines[ly]
if a then
local ln = string.rep(" ", offx - 1 - tostring(ly):len()) .. tostring(ly)
local l = a:sub(scrollx + 1, edw + scrollx + 1)
ln = ln .. ":"
if liveErr.line == ly then ln = string.rep(" ", offx - 2) .. "!:" end
term.setCursorPos(1, (ly - scrolly) + offy)
term.setBackgroundColor(colors[theme.editorBackground])
if ly == y then
if ly == liveErr.line and os.clock() - lastEventClock > 3 then
term.setBackgroundColor(colors[theme.editorErrorHighlight])
else term.setBackgroundColor(colors[theme.editorLineHightlight]) end
elseif ly == liveErr.line then
term.setBackgroundColor(colors[theme.editorError])
end
term.clearLine()
term.setCursorPos(1 - scrollx + offx, (ly - scrolly) + offy)
if ly == y then
if ly == liveErr.line and os.clock() - lastEventClock > 3 then
term.setBackgroundColor(colors[theme.editorErrorHighlight])
else term.setBackgroundColor(colors[theme.editorLineHightlight]) end
elseif ly == liveErr.line then term.setBackgroundColor(colors[theme.editorError])
else term.setBackgroundColor(colors[theme.editorBackground]) end
if ly == liveErr.line then
if displayCode then term.write(a)
else term.write(liveErr.display) end
else writeHighlighted(a) end
term.setCursorPos(1, (ly - scrolly) + offy)
if ly == y then
if ly == liveErr.line and os.clock() - lastEventClock > 3 then
term.setBackgroundColor(colors[theme.editorError])
else term.setBackgroundColor(colors[theme.editorLineNumbersHighlight]) end
elseif ly == liveErr.line then
term.setBackgroundColor(colors[theme.editorErrorHighlight])
else term.setBackgroundColor(colors[theme.editorLineNumbers]) end
term.write(ln)
end
end
term.setCursorPos(x - scrollx + offx, y - scrolly + offy)
end
local function cursorLoc(x, y, force)
local sx, sy = x - scrollx, y - scrolly
local redraw = false
if sx < 1 then
scrollx = x - 1
sx = 1
redraw = true
elseif sx > edw then
scrollx = x - edw
sx = edw
redraw = true
end if sy < 1 then
scrolly = y - 1
sy = 1
redraw = true
elseif sy > edh then
scrolly = y - edh
sy = edh
redraw = true
end if redraw or force then draw() end
term.setCursorPos(sx + offx, sy + offy)
end
local function executeMenuItem(a, path)
if type(a) == "string" and menuFunctions[a] then
local opt, nl, gtln = menuFunctions[a](path, lines, y)
if type(opt) == "string" then term.setCursorBlink(false) return opt end
if type(nl) == "table" then
if #lines < 1 then table.insert(lines, "") end
y = math.min(y, #lines)
x = math.min(x, lines[y]:len() + 1)
lines = nl
elseif type(nl) == "string" then
if nl == "go to" and gtln then
x, y = 1, math.min(#lines, gtln)
cursorLoc(x, y)
end
end
end
term.setCursorBlink(true)
draw()
term.setCursorPos(x - scrollx + offx, y - scrolly + offy)
end
local function edit(path)
-- Variables
x, y = 1, 1
offx, offy = 0, 1
scrollx, scrolly = 0, 0
lines = loadFile(path)
if not lines then return "menu" end
-- Enable brainfuck
if lines[1] == "-- Syntax: Brainfuck" then
curLanguage = languages.brainfuck
end
-- Clocks
local autosaveClock = os.clock()
local scrollClock = os.clock() -- To prevent redraw flicker
local liveErrorClock = os.clock()
local hasScrolled = false
-- Draw
draw()
term.setCursorPos(x + offx, y + offy)
term.setCursorBlink(true)
-- Main loop
local tid = os.startTimer(3)
while true do
local e, key, cx, cy = os.pullEvent()
if e == "key" and allowEditorEvent then
if key == keys.up and y > 1 then
-- Up
x, y = math.min(x, lines[y - 1]:len() + 1), y - 1
drawLine(y, y + 1)
cursorLoc(x, y)
elseif key == keys.down and y < #lines then
-- Down
x, y = math.min(x, lines[y + 1]:len() + 1), y + 1
drawLine(y, y - 1)
cursorLoc(x, y)
elseif key == keys.left and x > 1 then
-- Left
x = x - 1
local force = false
if y - scrolly + offy < offy + 1 then force = true end
cursorLoc(x, y, force)
elseif key == keys.right and x < lines[y]:len() + 1 then
-- Right
x = x + 1
local force = false
if y - scrolly + offy < offy + 1 then force = true end
cursorLoc(x, y, force)
elseif (key == keys.enter or key == keys.numPadEnter or key == 156) and (displayCode and true or y + scrolly - 1 ==
liveErr.line) then
-- Enter
local f = nil
for _, v in pairs(standardsCompletions) do
if lines[y]:find(v) then f = v end
end
local _, spaces = lines[y]:find("^[ ]+")
if not spaces then spaces = 0 end
if f then
table.insert(lines, y + 1, string.rep(" ", spaces + 2))
if not f:find("else", 1, true) and not f:find("elseif", 1, true) then
table.insert(lines, y + 2, string.rep(" ", spaces) ..
(f:find("repeat", 1, true) and "until " or f:find("{", 1, true) and "}" or
"end"))
end
x, y = spaces + 3, y + 1
cursorLoc(x, y, true)
else
local oldLine = lines[y]
lines[y] = lines[y]:sub(1, x - 1)
table.insert(lines, y + 1, string.rep(" ", spaces) .. oldLine:sub(x, -1))
x, y = spaces + 1, y + 1
cursorLoc(x, y, true)
end
elseif key == keys.backspace and (displayCode and true or y + scrolly - 1 == liveErr.line) then
-- Backspace
if x > 1 then
local f = false
for k, v in pairs(liveCompletions) do
if lines[y]:sub(x - 1, x - 1) == k then f = true end
end
lines[y] = lines[y]:sub(1, x - 2) .. lines[y]:sub(x + (f and 1 or 0), -1)
drawLine(y)
x = x - 1
cursorLoc(x, y)
elseif y > 1 then
local prevLen = lines[y - 1]:len() + 1
lines[y - 1] = lines[y - 1] .. lines[y]
table.remove(lines, y)
x, y = prevLen, y - 1
cursorLoc(x, y, true)
end
elseif key == keys.home then
-- Home
x = 1
local force = false
if y - scrolly + offy < offy + 1 then force = true end
cursorLoc(x, y, force)
elseif key == keys["end"] then
-- End
x = lines[y]:len() + 1
local force = false
if y - scrolly + offy < offy + 1 then force = true end
cursorLoc(x, y, force)
elseif key == keys.delete and (displayCode and true or y + scrolly - 1 == liveErr.line) then
-- Forward Delete
if x < lines[y]:len() + 1 then
lines[y] = lines[y]:sub(1, x - 1) .. lines[y]:sub(x + 1)
local force = false
if y - scrolly + offy < offy + 1 then force = true end
drawLine(y)
cursorLoc(x, y, force)
elseif y < #lines then
lines[y] = lines[y] .. lines[y + 1]
table.remove(lines, y + 1)
draw()
cursorLoc(x, y)
end
elseif key == keys.tab and (displayCode and true or y + scrolly - 1 == liveErr.line) then
-- Tab
lines[y] = string.rep(" ", tabWidth) .. lines[y]
x = x + 2
local force = false
if y - scrolly + offy < offy + 1 then force = true end
drawLine(y)
cursorLoc(x, y, force)
elseif key == keys.pageUp then
-- Page up
y = math.min(math.max(y - edh, 1), #lines)
x = math.min(lines[y]:len() + 1, x)
cursorLoc(x, y, true)
elseif key == keys.pageDown then
-- Page down
y = math.min(math.max(y + edh, 1), #lines)
x = math.min(lines[y]:len() + 1, x)
cursorLoc(x, y, true)
end
elseif e == "char" and allowEditorEvent and (displayCode and true or
y + scrolly - 1 == liveErr.line) then
local shouldIgnore = false
for k, v in pairs(liveCompletions) do
if key == v and lines[y]:find(k, 1, true) and lines[y]:sub(x, x) == v then
shouldIgnore = true
end
end
local addOne = false
if not shouldIgnore then
for k, v in pairs(liveCompletions) do
if key == k and lines[y]:sub(x, x) ~= k then key = key .. v addOne = true end
end
lines[y] = lines[y]:sub(1, x - 1) .. key .. lines[y]:sub(x, -1)
end
x = x + (addOne and 1 or key:len())
local force = false
if y - scrolly + offy < offy + 1 then force = true end
drawLine(y)
cursorLoc(x, y, force)
elseif e == "mouse_click" and key == 1 then
if cy > 1 then
if cx <= offx and cy - offy == liveErr.line - scrolly then
displayCode = not displayCode
drawLine(liveErr.line)
else
local oldy = y
y = math.min(math.max(scrolly + cy - offy, 1), #lines)
x = math.min(math.max(scrollx + cx - offx, 1), lines[y]:len() + 1)
if oldy ~= y then drawLine(oldy, y) end
cursorLoc(x, y)
end
else
local a = triggerMenu(cx, cy)
if a then
local opt = executeMenuItem(a, path)
if opt then return opt end
end
end
elseif e == "shortcut" then
local a = shortcuts[key .. " " .. cx]
if a then
local parent = nil
local curx = 0
for i, mv in ipairs(menu) do
for _, iv in pairs(mv) do
if iv == a then
parent = menu[i][1]
break
end
end
if parent then break end
curx = curx + mv[1]:len() + 3
end
local menux = curx + 2
-- Flash menu item
term.setCursorBlink(false)
term.setCursorPos(menux, 1)
term.setBackgroundColor(colors[theme.background])
term.write(string.rep(" ", parent:len() + 2))
term.setCursorPos(menux + 1, 1)
term.write(parent)
sleep(0.1)
drawMenu()
-- Execute item
local opt = executeMenuItem(a, path)
if opt then return opt end
end
elseif e == "mouse_scroll" then
if key == -1 and scrolly > 0 then
scrolly = scrolly - 1
if os.clock() - scrollClock > 0.0005 then
draw()
term.setCursorPos(x - scrollx + offx, y - scrolly + offy)
end
scrollClock = os.clock()
hasScrolled = true
elseif key == 1 and scrolly < #lines - edh then
scrolly = scrolly + 1
if os.clock() - scrollClock > 0.0005 then
draw()
term.setCursorPos(x - scrollx + offx, y - scrolly + offy)
end
scrollClock = os.clock()
hasScrolled = true
end
elseif e == "timer" and key == tid then
drawLine(y)
tid = os.startTimer(3)
end
-- Draw
if hasScrolled and os.clock() - scrollClock > 0.1 then
draw()
term.setCursorPos(x - scrollx + offx, y - scrolly + offy)
hasScrolled = false
end
-- Autosave
if os.clock() - autosaveClock > autosaveInterval then
saveFile(path, lines)
autosaveClock = os.clock()
end
-- Errors
if os.clock() - liveErrorClock > 1 then
local prevLiveErr = liveErr
liveErr = curLanguage.parseError(nil)
local code = ""
for _, v in pairs(lines) do code = code .. v .. "\n" end
liveErr = curLanguage.getCompilerErrors(code)
liveErr.line = math.min(liveErr.line - 2, #lines)
if liveErr ~= prevLiveErr then draw() end
liveErrorClock = os.clock()
end
end
return "menu"
end
-- -------- Open File
local function newFile()
local wid = w - 13
-- Get name
title("Lua IDE - New File")
local name = centerRead(wid, "/")
if not name or name == "" then return "menu" end
name = "/" .. name
-- Clear
title("Lua IDE - New File")
term.setTextColor(colors[theme.textColor])
term.setBackgroundColor(colors[theme.promptHighlight])
for i = 8, 10 do
term.setCursorPos(w/2 - wid/2, i)
term.write(string.rep(" ", wid))
end
term.setCursorPos(1, 9)
if fs.isDir(name) then
centerPrint("Cannot Edit a Directory!")
sleep(1.6)
return "menu"
elseif fs.exists(name) then
centerPrint("File Already Exists!")
local opt = prompt({{"Open", w/2 - 9, 14}, {"Cancel", w/2 + 2, 14}}, "horizontal")
if opt == "Open" then return "edit", name
elseif opt == "Cancel" then return "menu" end
else return "edit", name end
end
local function openFile()
local wid = w - 13
-- Get name
title("Lua IDE - Open File")
local name = centerRead(wid, "/")
if not name or name == "" then return "menu" end
name = "/" .. name
-- Clear
title("Lua IDE - New File")
term.setTextColor(colors[theme.textColor])
term.setBackgroundColor(colors[theme.promptHighlight])
for i = 8, 10 do
term.setCursorPos(w/2 - wid/2, i)
term.write(string.rep(" ", wid))
end
term.setCursorPos(1, 9)
if fs.isDir(name) then
centerPrint("Cannot Open a Directory!")
sleep(1.6)
return "menu"
elseif not fs.exists(name) then
centerPrint("File Doesn't Exist!")
local opt = prompt({{"Create", w/2 - 11, 14}, {"Cancel", w/2 + 2, 14}}, "horizontal")
if opt == "Create" then return "edit", name
elseif opt == "Cancel" then return "menu" end
else return "edit", name end
end
-- -------- Settings
local function update()
--[[
local function draw(status)
title("LuaIDE - Update")
term.setBackgroundColor(colors[theme.prompt])
term.setTextColor(colors[theme.textColor])
for i = 8, 10 do
term.setCursorPos(w/2 - (status:len() + 4), i)
write(string.rep(" ", status:len() + 4))
end
term.setCursorPos(w/2 - (status:len() + 4), 9)
term.write(" - " .. status .. " ")
term.setBackgroundColor(colors[theme.errHighlight])
for i = 8, 10 do
term.setCursorPos(w/2 + 2, i)
term.write(string.rep(" ", 10))
end
term.setCursorPos(w/2 + 2, 9)
term.write(" > Cancel ")
end
if not http then
draw("HTTP API Disabled!")
sleep(1.6)
return "settings"
end
draw("Updating...")
local tID = os.startTimer(10)
http.request(updateURL)
while true do
local e, but, x, y = os.pullEvent()
if (e == "key" and but == 28) or
(e == "mouse_click" and x >= w/2 + 2 and x <= w/2 + 12 and y == 9) then
draw("Cancelled")
sleep(1.6)
break
elseif e == "http_success" and but == updateURL then
local new = x.readAll()
local curf = io.open(ideLocation, "r")
local cur = curf:read("*a")
curf:close()
if cur ~= new then
draw("Update Found")
sleep(1.6)
local f = io.open(ideLocation, "w")
f:write(new)
f:close()
draw("Click to Exit")
while true do
local e = os.pullEvent()
if e == "mouse_click" or (not isAdvanced() and e == "key") then break end
end
return "exit"
else
draw("No Updates Found!")
sleep(1.6)
break
end
elseif e == "http_failure" or (e == "timer" and but == tID) then
draw("Update Failed!")
sleep(1.6)
break
end
end
]]--
return "settings"
end
local function changeTheme()
title("LuaIDE - Theme")
term.setCursorPos(1, 7)
centerPrint("Themes are not available on the")
centerPrint("treasure disk version of LuaIDE!")
centerPrint("Download the full program from the")
centerPrint("ComputerCraft Forums!")
--[[
if isAdvanced() then
local disThemes = {"Back"}
for _, v in pairs(availableThemes) do table.insert(disThemes, v[1]) end
local t = scrollingPrompt(disThemes)
local url = nil
for _, v in pairs(availableThemes) do if v[1] == t then url = v[2] end end
if not url then return "settings" end
if t == "Dawn (Default)" then
term.setBackgroundColor(colors[theme.backgroundHighlight])
term.setCursorPos(3, 3)
term.clearLine()
term.write("LuaIDE - Loaded Theme!")
sleep(1.6)
fs.delete(themeLocation)
theme = defaultTheme
return "menu"
end
term.setBackgroundColor(colors[theme.backgroundHighlight])
term.setCursorPos(3, 3)
term.clearLine()
term.write("LuaIDE - Downloading...")
fs.delete("/.LuaIDE_temp_theme_file")
download(url, "/.LuaIDE_temp_theme_file")
local a = loadTheme("/.LuaIDE_temp_theme_file")
term.setCursorPos(3, 3)
term.clearLine()
if a then
term.write("LuaIDE - Loaded Theme!")
fs.delete(themeLocation)
fs.move("/.LuaIDE_temp_theme_file", themeLocation)
theme = a
sleep(1.6)
return "menu"
end
term.write("LuaIDE - Could Not Load Theme!")
fs.delete("/.LuaIDE_temp_theme_file")
sleep(1.6)
return "settings"
else
term.setCursorPos(1, 8)
centerPrint("Themes are not available on")
centerPrint("normal computers!")
end
]]--
end
local function settings()
title("LuaIDE - Settings")
local opt = prompt({{"Change Theme", w/2 - 17, 8}, {"Return to Menu", w/2 - 19, 13},
--[[{"Check for Updates", w/2 + 2, 8},]] {"Exit IDE", w/2 + 2, 13, bg = colors[theme.err],
highlight = colors[theme.errHighlight]}}, "vertical", true)
if opt == "Change Theme" then return changeTheme()
-- elseif opt == "Check for Updates" then return update()
elseif opt == "Return to Menu" then return "menu"
elseif opt == "Exit IDE" then return "exit" end
end
-- -------- Menu
local function menu()
title("Welcome to LuaIDE " .. version)
local opt = prompt({{"New File", w/2 - 13, 8}, {"Open File", w/2 - 14, 13},
{"Settings", w/2 + 2, 8}, {"Exit IDE", w/2 + 2, 13, bg = colors[theme.err],
highlight = colors[theme.errHighlight]}}, "vertical", true)
if opt == "New File" then return "new"
elseif opt == "Open File" then return "open"
elseif opt == "Settings" then return "settings"
elseif opt == "Exit IDE" then return "exit" end
end
-- -------- Main
local function main(arguments)
local opt, data = "menu", nil
-- Check arguments
if type(arguments) == "table" and #arguments > 0 then
local f = "/" .. shell.resolve(arguments[1])
if fs.isDir(f) then print("Cannot edit a directory.") end
opt, data = "edit", f
end
-- Main run loop
while true do
-- Menu
if opt == "menu" then opt = menu() end
-- Other
if opt == "new" then opt, data = newFile()
elseif opt == "open" then opt, data = openFile()
elseif opt == "settings" then opt = settings()
end if opt == "exit" then break end
-- Edit
if opt == "edit" and data then opt = edit(data) end
end
end
-- Load Theme
if fs.exists(themeLocation) then theme = loadTheme(themeLocation) end
if not theme and isAdvanced() then theme = defaultTheme
elseif not theme then theme = normalTheme end
-- Run
local _, err = pcall(function()
parallel.waitForAny(function() main(args) end, monitorKeyboardShortcuts)
end)
-- Catch errors
if err and not err:find("Terminated") then
term.setCursorBlink(false)
title("LuaIDE - Crash! D:")
term.setBackgroundColor(colors[theme.err])
for i = 6, 8 do
term.setCursorPos(5, i)
term.write(string.rep(" ", 36))
end
term.setCursorPos(6, 7)
term.write("LuaIDE Has Crashed! D:")
term.setBackgroundColor(colors[theme.background])
term.setCursorPos(2, 10)
print(err)
term.setBackgroundColor(colors[theme.prompt])
local _, cy = term.getCursorPos()
for i = cy + 1, cy + 4 do
term.setCursorPos(5, i)
term.write(string.rep(" ", 36))
end
term.setCursorPos(6, cy + 2)
term.write("Please report this error to")
term.setCursorPos(6, cy + 3)
term.write("GravityScore! ")
term.setBackgroundColor(colors[theme.background])
if isAdvanced() then centerPrint("Click to Exit...", h - 1)
else centerPrint("Press Any Key to Exit...", h - 1) end
while true do
local e = os.pullEvent()
if e == "mouse_click" or (not isAdvanced() and e == "key") then break end
end
-- Prevent key from being shown
os.queueEvent(event_distract)
os.pullEvent()
end
-- Exit
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.clear()
term.setCursorPos(1, 1)
centerPrint("Thank You for Using Lua IDE " .. version)
centerPrint("Made by GravityScore")