449 lines
12 KiB
Lua
449 lines
12 KiB
Lua
console_mode = "console"
|
|
|
|
history = session.get_entry("commands_history")
|
|
history_pointer = #history
|
|
|
|
local warnings_all = {}
|
|
local errors_all = {}
|
|
|
|
local warning_id = 0
|
|
local error_id = 0
|
|
|
|
local writeables = {}
|
|
local filenames = {}
|
|
local scripts_classification = {}
|
|
|
|
local current_file = {
|
|
filename = "",
|
|
mutable = nil
|
|
}
|
|
|
|
local function xunpack(t)
|
|
if t == nil then
|
|
return nil
|
|
end
|
|
return unpack(t)
|
|
end
|
|
|
|
events.on("core:warning", function (wtype, text, traceback)
|
|
local full = wtype..": "..text
|
|
if table.has(warnings_all, full) then
|
|
return
|
|
end
|
|
local encoded = base64.encode(bjson.tobytes({frames=traceback}))
|
|
document.problemsLog:add(gui.template("problem", {
|
|
type="warning",
|
|
text=full,
|
|
traceback=encoded,
|
|
id=tostring(warning_id)
|
|
}))
|
|
warning_id = warning_id + 1
|
|
table.insert(warnings_all, full)
|
|
end)
|
|
|
|
events.on("core:error", function (msg, traceback)
|
|
local _, endindex = string.find(msg, ": ")
|
|
local full = ""
|
|
for i,frame in ipairs(traceback) do
|
|
full = full..frame.source..tostring(frame.currentline)
|
|
end
|
|
if table.has(errors_all, full) then
|
|
return
|
|
end
|
|
local encoded = base64.encode(bjson.tobytes({frames=traceback}))
|
|
document.problemsLog:add(gui.template("problem", {
|
|
type="error",
|
|
text=msg:sub(endindex),
|
|
traceback=encoded,
|
|
id=tostring(error_id)
|
|
}))
|
|
error_id = error_id + 1
|
|
table.insert(errors_all, full)
|
|
end)
|
|
|
|
local function find_mutable(filename)
|
|
local packid = file.prefix(filename)
|
|
if packid == "core" then
|
|
return
|
|
end
|
|
local saved = writeables[packid]
|
|
if saved then
|
|
return saved..":"..file.path(filename)
|
|
end
|
|
local packinfo = pack.get_info(packid)
|
|
if not packinfo then
|
|
return
|
|
end
|
|
local path = packinfo.path
|
|
if file.is_writeable(path) then
|
|
return file.join(path, file.path(filename))
|
|
end
|
|
end
|
|
|
|
local function refresh_file_title()
|
|
if current_file.filename == "" then
|
|
document.title.text = ""
|
|
return
|
|
end
|
|
local edited = document.editor.edited
|
|
current_file.modified = edited
|
|
document.saveIcon.enabled = edited
|
|
document.title.text = gui.str('File')..' - '..current_file.filename
|
|
..(edited and ' *' or '')
|
|
end
|
|
|
|
function build_files_list(filenames, selected)
|
|
local files_list = document.filesList
|
|
files_list:clear()
|
|
|
|
for _, actual_filename in ipairs(filenames) do
|
|
local filename = actual_filename
|
|
if selected then
|
|
filename = filename:gsub(selected, "**"..selected.."**")
|
|
end
|
|
local parent = file.parent(filename)
|
|
local script_type, unit = xunpack(scripts_classification[actual_filename])
|
|
script_type = script_type or "file"
|
|
files_list:add(gui.template("script_file", {
|
|
path = parent .. (parent[#parent] == ':' and '' or '/'),
|
|
name = file.name(filename),
|
|
type = script_type,
|
|
filename = actual_filename
|
|
}))
|
|
end
|
|
end
|
|
|
|
function filter_files(text)
|
|
local filtered = {}
|
|
for _, filename in ipairs(filenames) do
|
|
if filename:find(text) then
|
|
table.insert(filtered, filename)
|
|
end
|
|
end
|
|
build_files_list(filtered, text)
|
|
end
|
|
|
|
function on_control_combination(keycode)
|
|
if keycode == input.keycode("s") then
|
|
save_current_file()
|
|
elseif keycode == input.keycode("r") then
|
|
run_current_file()
|
|
end
|
|
end
|
|
|
|
function unlock_access()
|
|
if current_file.filename == "" then
|
|
return
|
|
end
|
|
pack.request_writeable(file.prefix(current_file.filename),
|
|
function(token)
|
|
writeables[file.prefix(current_file.filename)] = token
|
|
current_file.mutable = token..":"..file.path(current_file.filename)
|
|
open_file_in_editor(current_file.filename, 0, current_file.mutable)
|
|
end
|
|
)
|
|
end
|
|
|
|
function run_current_file()
|
|
if not current_file.filename then
|
|
return
|
|
end
|
|
local chunk, err = loadstring(document.editor.text, current_file.filename)
|
|
clear_output()
|
|
if not chunk then
|
|
local line, message = err:match(".*:(%d*): (.*)")
|
|
document.output:add(
|
|
string.format(
|
|
"<label color='#FF3030' enabled='false' margin='2'>%s: %s</label>",
|
|
gui.str("Error at line %{0}"):gsub("%%{0}", line), message)
|
|
)
|
|
return
|
|
end
|
|
local script_type, unit = xunpack(scripts_classification[current_file.filename])
|
|
save_current_file()
|
|
|
|
local func = function()
|
|
local stack_size = debug.count_frames()
|
|
xpcall(chunk, function(msg) __vc__error(msg, 1, 1, stack_size) end)
|
|
end
|
|
|
|
if script_type == "block" then
|
|
func = function() block.reload_script(unit) end
|
|
elseif script_type == "item" then
|
|
func = function() item.reload_script(unit) end
|
|
elseif script_type == "world" then
|
|
func = function() world.reload_script(unit) end
|
|
elseif script_type == "hud" then
|
|
func = function() hud.reload_script(unit) end
|
|
end
|
|
local output = core.capture_output(func)
|
|
document.output:add(
|
|
string.format(
|
|
"<label enabled='false' multiline='true' margin='2'>%s</label>",
|
|
output)
|
|
)
|
|
end
|
|
|
|
function save_current_file()
|
|
if not current_file.mutable then
|
|
return
|
|
end
|
|
file.write(current_file.mutable, document.editor.text)
|
|
current_file.modified = false
|
|
document.saveIcon.enabled = false
|
|
document.title.text = gui.str('File')..' - '..current_file.filename
|
|
document.editor.edited = false
|
|
end
|
|
|
|
function open_file_in_editor(filename, line, mutable)
|
|
local editor = document.editor
|
|
local source = file.read(filename):gsub('\t', ' ')
|
|
editor.text = source
|
|
editor.focused = true
|
|
if line then
|
|
time.post_runnable(function()
|
|
editor.caret = editor:linePos(line)
|
|
end)
|
|
end
|
|
document.title.text = gui.str('File') .. ' - ' .. filename
|
|
current_file.filename = filename
|
|
current_file.mutable = mutable or find_mutable(filename)
|
|
document.lockIcon.visible = current_file.mutable == nil
|
|
document.editor.editable = current_file.mutable ~= nil
|
|
document.saveIcon.enabled = current_file.modified
|
|
end
|
|
|
|
function clear_traceback()
|
|
local tb_list = document.traceback
|
|
tb_list:clear()
|
|
tb_list:add("<label enabled='false' margin='2'>@devtools.traceback</label>")
|
|
end
|
|
|
|
function clear_output()
|
|
local output = document.output
|
|
output:clear()
|
|
output:add("<label enabled='false' margin='2'>@devtools.output</label>")
|
|
end
|
|
|
|
events.on("core:open_traceback", function(traceback_b64)
|
|
local traceback = bjson.frombytes(base64.decode(traceback_b64))
|
|
modes:set('debug')
|
|
|
|
clear_traceback()
|
|
|
|
local tb_list = document.traceback
|
|
local srcsize = tb_list.size
|
|
for _, frame in ipairs(traceback.frames) do
|
|
local callback = ""
|
|
local framestr = ""
|
|
if frame.what == "C" then
|
|
framestr = "C/C++ "
|
|
else
|
|
framestr = frame.source..":"..tostring(frame.currentline).." "
|
|
if file.exists(frame.source) then
|
|
callback = string.format(
|
|
"open_file_in_editor('%s', %s)",
|
|
frame.source, frame.currentline-1
|
|
)
|
|
else
|
|
callback = "document.editor.text = 'Could not open source file'"
|
|
end
|
|
end
|
|
if frame.name then
|
|
framestr = framestr.."("..tostring(frame.name)..")"
|
|
end
|
|
local color = "#FFFFFF"
|
|
tb_list:add(gui.template("stack_frame", {
|
|
location=framestr,
|
|
color=color,
|
|
callback=callback,
|
|
enabled=file.exists(frame.source)
|
|
}))
|
|
end
|
|
tb_list.size = srcsize
|
|
end)
|
|
|
|
function setup_variables()
|
|
local pid = hud.get_player()
|
|
local x,y,z = player.get_pos(pid)
|
|
console.set("player", pid)
|
|
console.set('pos.x', x)
|
|
console.set('pos.y', y)
|
|
console.set('pos.z', z)
|
|
local pentity = player.get_entity(pid)
|
|
if pentity > 0 then
|
|
console.set('entity.id', pentity)
|
|
end
|
|
local sentity = player.get_selected_entity(pid)
|
|
if sentity ~= nil then
|
|
console.set('entity.selected', sentity)
|
|
end
|
|
end
|
|
|
|
function on_history_up()
|
|
if history_pointer == 0 then
|
|
return
|
|
end
|
|
document.prompt.text = history[history_pointer]
|
|
document.prompt.caret = -1
|
|
history_pointer = history_pointer - 1
|
|
end
|
|
|
|
function on_history_down()
|
|
if history_pointer >= #history-1 then
|
|
return
|
|
end
|
|
history_pointer = history_pointer + 1
|
|
document.prompt.text = history[history_pointer + 1]
|
|
document.prompt.caret = -1
|
|
end
|
|
|
|
function add_to_history(text)
|
|
table.insert(history, text)
|
|
history_pointer = #history
|
|
end
|
|
|
|
function submit(text)
|
|
if #text == 0 then
|
|
document.prompt.focused = true
|
|
return
|
|
end
|
|
text = text:trim()
|
|
add_to_history(text)
|
|
|
|
if console_mode == "chat" then
|
|
if not text:starts_with("/") then
|
|
text = "chat "..string.escape(text)
|
|
else
|
|
text = text:sub(2)
|
|
end
|
|
end
|
|
|
|
local name
|
|
for s in text:gmatch("%S+") do
|
|
name = s
|
|
break
|
|
end
|
|
if name == nil then
|
|
name = text
|
|
end
|
|
if not rules.get("allow-cheats") and table.has(console.cheats, name) then
|
|
console.log("cheat commands are disabled")
|
|
document.prompt.text = ""
|
|
document.prompt.focused = true
|
|
return
|
|
end
|
|
|
|
document.log.caret = -1
|
|
document.prompt.text = ""
|
|
document.prompt.focused = true
|
|
|
|
setup_variables()
|
|
|
|
if console.submit then
|
|
console.submit(text)
|
|
else
|
|
local status, result = pcall(console.execute, text)
|
|
if result then
|
|
console.log(result)
|
|
end
|
|
end
|
|
end
|
|
|
|
function set_mode(mode)
|
|
local show_prompt = mode == 'chat' or mode == 'console'
|
|
|
|
document.lockIcon.visible = false
|
|
document.editorRoot.visible = mode == 'debug'
|
|
document.editorContainer.visible = mode == 'debug'
|
|
document.logContainer.visible = mode ~= 'debug'
|
|
|
|
if mode == 'debug' then
|
|
document.root.color = {16, 18, 20, 220}
|
|
else
|
|
document.root.color = {0, 0, 0, 128}
|
|
end
|
|
|
|
document.prompt.visible = show_prompt
|
|
if show_prompt then
|
|
document.prompt.focused = true
|
|
end
|
|
console_mode = mode
|
|
end
|
|
|
|
local function collect_scripts(dirname, dest)
|
|
if file.isdir(dirname) then
|
|
local files = file.list(dirname)
|
|
for i, filename in ipairs(files) do
|
|
if file.isdir(filename) then
|
|
collect_scripts(filename, dest)
|
|
elseif file.ext(filename) == "lua" then
|
|
table.insert(dest, filename)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function build_scripts_classification()
|
|
for id, props in pairs(block.properties) do
|
|
scripts_classification[props["script-file"]] = {"block", block.name(id)}
|
|
end
|
|
for id, props in pairs(item.properties) do
|
|
scripts_classification[props["script-file"]] = {"item", item.name(id)}
|
|
end
|
|
local packs = pack.get_installed()
|
|
for _, packid in ipairs(packs) do
|
|
scripts_classification[packid..":scripts/world.lua"] = {"world", packid}
|
|
scripts_classification[packid..":scripts/hud.lua"] = {"hud", packid}
|
|
end
|
|
end
|
|
|
|
local function load_scripts_list()
|
|
local packs = pack.get_installed()
|
|
for _, packid in ipairs(packs) do
|
|
collect_scripts(packid..":modules", filenames)
|
|
end
|
|
|
|
for _, filename in ipairs(filenames) do
|
|
scripts_classification[filename] = {"module"}
|
|
end
|
|
|
|
for _, packid in ipairs(packs) do
|
|
collect_scripts(packid..":scripts", filenames)
|
|
end
|
|
end
|
|
|
|
function on_open(mode)
|
|
if modes == nil then
|
|
modes = RadioGroup({
|
|
chat=document.s_chat,
|
|
console=document.s_console,
|
|
debug=document.s_debug
|
|
}, function (mode)
|
|
set_mode(mode)
|
|
end, mode or "console")
|
|
|
|
local files_list = document.filesList
|
|
|
|
load_scripts_list()
|
|
build_scripts_classification()
|
|
|
|
table.sort(filenames)
|
|
build_files_list(filenames)
|
|
|
|
document.editorContainer:setInterval(200, refresh_file_title)
|
|
|
|
clear_traceback()
|
|
clear_output()
|
|
elseif mode then
|
|
modes:set(mode)
|
|
end
|
|
hud.close("core:ingame_chat")
|
|
end
|
|
|
|
function on_close()
|
|
time.post_runnable(function()
|
|
hud.open_permanent("core:ingame_chat")
|
|
end)
|
|
end
|