From 2df4aeee45137d812a67c336d9836f2a0a3bec4d Mon Sep 17 00:00:00 2001 From: grilledham Date: Wed, 30 Jan 2019 22:32:43 +0000 Subject: [PATCH] debugger --- control.lua | 5 + features/gui/debug/_g_view.lua | 112 +++++++++++++++ features/gui/debug/command.lua | 13 ++ features/gui/debug/global_view.lua | 141 +++++++++++++++++++ features/gui/debug/main_view.lua | 94 +++++++++++++ features/gui/debug/model.lua | 110 +++++++++++++++ features/gui/debug/package_view.lua | 158 ++++++++++++++++++++++ features/gui/debug/redmew_global_view.lua | 137 +++++++++++++++++++ 8 files changed, 770 insertions(+) create mode 100644 features/gui/debug/_g_view.lua create mode 100644 features/gui/debug/command.lua create mode 100644 features/gui/debug/global_view.lua create mode 100644 features/gui/debug/main_view.lua create mode 100644 features/gui/debug/model.lua create mode 100644 features/gui/debug/package_view.lua create mode 100644 features/gui/debug/redmew_global_view.lua diff --git a/control.lua b/control.lua index 89614cb1..9ffa6ac6 100644 --- a/control.lua +++ b/control.lua @@ -106,3 +106,8 @@ end if _DUMP_ENV then require 'utils.dump_env' end + +-- Needs to be at bottom so tokens are registered last. +if _DEBUG then + require 'features.gui.debug.command' +end diff --git a/features/gui/debug/_g_view.lua b/features/gui/debug/_g_view.lua new file mode 100644 index 00000000..df08bf12 --- /dev/null +++ b/features/gui/debug/_g_view.lua @@ -0,0 +1,112 @@ +local Gui = require 'utils.gui' +local Model = require 'features.gui.debug.model' +local Color = require 'resources.color_presets' + +local dump = Model.dump + +local Public = {} + +local ignore = { + _G = true, + assert = true, + collectgarbage = true, + error = true, + getmetatable = true, + ipairs = true, + load = true, + loadstring = true, + next = true, + pairs = true, + pcall = true, + print = true, + rawequal = true, + rawlen = true, + rawget = true, + rawset = true, + select = true, + setmetatable = true, + tonumber = true, + tostring = true, + type = true, + xpcall = true, + _VERSION = true, + module = true, + require = true, + package = true, + unpack = true, + table = true, + string = true, + bit32 = true, + math = true, + debug = true, + serpent = true, + log = true, + table_size = true, + global = true, + remote = true, + commands = true, + settings = true, + rcon = true, + script = true, + util = true, + mod_gui = true, + game = true +} + +local header_name = Gui.uid_name() +local left_panel_name = Gui.uid_name() +local right_panel_name = Gui.uid_name() + +Public.name = '_G' + +function Public.show(container) + local main_flow = container.add {type = 'flow', direction = 'horizontal'} + + local left_panel = main_flow.add {type = 'scroll-pane', name = left_panel_name} + local left_panel_style = left_panel.style + left_panel_style.width = 300 + + for key, value in pairs(_G) do + if not ignore[key] then + local header = + left_panel.add({type = 'flow'}).add {type = 'label', name = header_name, caption = tostring(key)} + Gui.set_data(header, value) + end + end + + local right_panel = main_flow.add {type = 'text-box', name = right_panel_name} + right_panel.word_wrap = true + right_panel.read_only = true + right_panel.selectable = true + + local right_panel_style = right_panel.style + right_panel_style.vertically_stretchable = true + right_panel_style.horizontally_stretchable = true + + Gui.set_data(left_panel, {right_panel = right_panel, selected_header = nil}) +end + +Gui.on_click( + header_name, + function(event) + local element = event.element + local value = Gui.get_data(element) + + local left_panel = element.parent.parent + local left_panel_data = Gui.get_data(left_panel) + local right_panel = left_panel_data.right_panel + local selected_header = left_panel_data.selected_header + + if selected_header then + selected_header.style.font_color = Color.white + end + + element.style.font_color = Color.orange + left_panel_data.selected_header = element + + local content = dump(value) + right_panel.text = content + end +) + +return Public diff --git a/features/gui/debug/command.lua b/features/gui/debug/command.lua new file mode 100644 index 00000000..24a39949 --- /dev/null +++ b/features/gui/debug/command.lua @@ -0,0 +1,13 @@ +local DebugView = require 'features.gui.debug.main_view' +local Command = require 'utils.command' + +Command.add( + 'debug', + { + debug_only = true, + description = 'Opens the debugger' + }, + function(_, player) + DebugView.open_dubug(player) + end +) diff --git a/features/gui/debug/global_view.lua b/features/gui/debug/global_view.lua new file mode 100644 index 00000000..05551655 --- /dev/null +++ b/features/gui/debug/global_view.lua @@ -0,0 +1,141 @@ +local Gui = require 'utils.gui' +local Model = require 'features.gui.debug.model' +local Color = require 'resources.color_presets' + +local dump = Model.dump +local concat = table.concat +local loadstring = loadstring +local pcall = pcall + +local Public = {} + +local ignore = {tokens = true} + +local header_name = Gui.uid_name() +local left_panel_name = Gui.uid_name() +local right_panel_name = Gui.uid_name() +local input_text_box_name = Gui.uid_name() +local refresh_name = Gui.uid_name() + +Public.name = 'global' + +function Public.show(container) + local main_flow = container.add {type = 'flow', direction = 'horizontal'} + + local left_panel = main_flow.add {type = 'scroll-pane', name = left_panel_name} + local left_panel_style = left_panel.style + left_panel_style.width = 300 + + for key, _ in pairs(global) do + if not ignore[key] then + local header = + left_panel.add({type = 'flow'}).add {type = 'label', name = header_name, caption = tostring(key)} + Gui.set_data(header, key) + end + end + + local right_flow = main_flow.add {type = 'flow', direction = 'vertical'} + + local right_top_flow = right_flow.add {type = 'flow', direction = 'horizontal'} + + local input_text_box = right_top_flow.add {type = 'text-box', name = input_text_box_name} + local input_text_box_style = input_text_box.style + input_text_box_style.horizontally_stretchable = true + input_text_box_style.height = 32 + + local refresh_button = + right_top_flow.add {type = 'sprite-button', name = refresh_name, sprite = 'utility/reset', tooltip = 'refresh'} + local refresh_button_style = refresh_button.style + refresh_button_style.width = 32 + refresh_button_style.height = 32 + + local right_panel = right_flow.add {type = 'text-box', name = right_panel_name} + right_panel.word_wrap = true + right_panel.read_only = true + right_panel.selectable = true + + local right_panel_style = right_panel.style + right_panel_style.vertically_stretchable = true + right_panel_style.horizontally_stretchable = true + + local data = { + right_panel = right_panel, + input_text_box = input_text_box, + selected_header = nil, + selected_token_id = nil + } + + Gui.set_data(input_text_box, data) + Gui.set_data(left_panel, data) + Gui.set_data(refresh_button, data) +end + +Gui.on_click( + header_name, + function(event) + local element = event.element + local key = Gui.get_data(element) + + local left_panel = element.parent.parent + local data = Gui.get_data(left_panel) + local right_panel = data.right_panel + local selected_header = data.selected_header + local input_text_box = data.input_text_box + + if selected_header then + selected_header.style.font_color = Color.white + end + + element.style.font_color = Color.orange + data.selected_header = element + + input_text_box.text = concat {"global['", key, "']"} + input_text_box.style.font_color = Color.black + + local content = dump(global[key]) or 'nil' + right_panel.text = content + end +) + +local function update_dump(text_input, data) + local func = loadstring('return ' .. text_input.text) + if not func then + text_input.style.font_color = Color.red + return + end + + local suc, var = pcall(func) + if not suc then + text_input.style.font_color = Color.red + return + end + + text_input.style.font_color = Color.black + + local right_panel = data.right_panel + right_panel.text = dump(var) +end + +Gui.on_text_changed( + input_text_box_name, + function(event) + local element = event.element + local data = Gui.get_data(element) + + update_dump(element, data) + end +) + +Gui.on_click( + refresh_name, + function(event) + local element = event.element + local data = Gui.get_data(element) + + local input_text_box = data.input_text_box + + update_dump(input_text_box, data) + end +) + +return Public diff --git a/features/gui/debug/main_view.lua b/features/gui/debug/main_view.lua new file mode 100644 index 00000000..cd2eb63f --- /dev/null +++ b/features/gui/debug/main_view.lua @@ -0,0 +1,94 @@ +local Gui = require 'utils.gui' +local Color = require 'resources.color_presets' + +local Public = {} + +local pages = { + require 'features.gui.debug.redmew_global_view', + require 'features.gui.debug.global_view', + require 'features.gui.debug.package_view', + require 'features.gui.debug._g_view' +} + +local main_frame_name = Gui.uid_name() +local close_name = Gui.uid_name() +local tab_name = Gui.uid_name() + +function Public.open_dubug(player) + local center = player.gui.center + local frame = center[main_frame_name] + if frame then + return + end + + frame = center.add {type = 'frame', name = main_frame_name, caption = 'Debuggertron 3000', direction = 'vertical'} + local frame_style = frame.style + frame_style.height = 600 + frame_style.width = 900 + + local tab_flow = frame.add {type = 'flow', direction = 'horizontal'} + local container = frame.add {type = 'flow'} + container.style.vertically_stretchable = true + + local data = {} + + for i = 1, #pages do + local page = pages[i] + local tab_button = tab_flow.add({type = 'flow'}).add {type = 'button', name = tab_name, caption = page.name} + local tab_button_style = tab_button.style + + Gui.set_data(tab_button, {index = i, frame_data = data}) + + if i == 1 then + tab_button_style.font_color = Color.orange + + data.selected_index = i + data.selected_tab_button = tab_button + data.container = container + + Gui.set_data(frame, data) + page.show(container) + end + end + + frame.add {type = 'button', name = close_name, caption = 'Close'} +end + +Gui.on_click( + tab_name, + function(event) + local element = event.element + local data = Gui.get_data(element) + + local index = data.index + local frame_data = data.frame_data + local selected_index = frame_data.selected_index + + if selected_index == index then + return + end + + local selected_tab_button = frame_data.selected_tab_button + selected_tab_button.style.font_color = Color.white + + frame_data.selected_tab_button = element + frame_data.selected_index = index + element.style.font_color = Color.orange + + local container = frame_data.container + Gui.clear(container) + pages[index].show(container) + end +) + +Gui.on_click( + close_name, + function(event) + local frame = event.player.gui.center[main_frame_name] + if frame then + Gui.destroy(frame) + end + end +) + +return Public diff --git a/features/gui/debug/model.lua b/features/gui/debug/model.lua new file mode 100644 index 00000000..61cdf691 --- /dev/null +++ b/features/gui/debug/model.lua @@ -0,0 +1,110 @@ +local Gui = require 'utils.gui' + +local gui_names = Gui.names +local type = type +local concat = table.concat +local inspect = table.inspect +local pcall = pcall + +local Public = {} + +local luaObject = {'{', nil, ", name = '", nil, "'}"} +local luaPlayer = {"{LuaPlayer, name = '", nil, "', index = ", nil, '}'} +local luaEntity = {"{LuaEntity, name = '", nil, "', unit_number = ", nil, '}'} +local luaGuiElement = {"{LuaGuiElement, name = '", nil, "'}"} + +local function get_name_safe_inner(obj) + return obj.name +end + +local function get_name_safe(obj) + local s, r = pcall(get_name_safe_inner, obj) + if not s then + return 'nil' + else + return r or 'nil' + end +end + +local function inspect_process(item) + if type(item) ~= 'table' or not item.__self then + return item + end + + if not item.valid then + return '{Invalid LuaObject}' + end + + local help = item.help + if not help then + return '{Invalid LuaObject}' + end + + local obj_type = help():match('Lua%a+') + if obj_type == 'LuaPlayer' then + luaPlayer[2] = item.name or 'nil' + luaPlayer[4] = item.index or 'nil' + + return concat(luaPlayer) + elseif obj_type == 'LuaEntity' then + luaEntity[2] = item.name or 'nil' + luaEntity[4] = item.unit_number or 'nil' + + return concat(luaEntity) + elseif obj_type == 'LuaGuiElement' then + local name = item.name + luaGuiElement[2] = gui_names[name] or name or 'nil' + + return concat(luaGuiElement) + else + luaObject[2] = obj_type + luaObject[4] = get_name_safe(item) + + return concat(luaObject) + end +end + +local inspect_options = {process = inspect_process} +function Public.dump(data) + return inspect(data, inspect_options) +end +local dump = Public.dump + +function Public.dump_ignore_builder(ignore) + local function process(item) + if ignore[item] then + return nil + end + + return inspect_process(item) + end + + local options = {process = process} + return function(data) + return inspect(data, options) + end +end + +function Public.dump_function(func) + local res = {'upvalues:\n'} + + local i = 1 + while true do + local n, v = debug.getupvalue(func, i) + + if n == nil then + break + elseif n ~= '_ENV' then + res[#res + 1] = n + res[#res + 1] = ' = ' + res[#res + 1] = dump(v) + res[#res + 1] = '\n' + end + + i = i + 1 + end + + return concat(res) +end + +return Public diff --git a/features/gui/debug/package_view.lua b/features/gui/debug/package_view.lua new file mode 100644 index 00000000..c75f2ca1 --- /dev/null +++ b/features/gui/debug/package_view.lua @@ -0,0 +1,158 @@ +local Gui = require 'utils.gui' +local Color = require 'resources.color_presets' +local Model = require 'features.gui.debug.model' + +local dump_function = Model.dump_function +local loaded = _G.package.loaded + +local Public = {} + +local ignore = { + _G = true, + package = true, + coroutine = true, + table = true, + string = true, + bit32 = true, + math = true, + debug = true, + serpent = true, + ['utils.math'] = true, + util = true, + ['utils.inspect'] = true, + ['mod-gui'] = true +} + +local file_label_name = Gui.uid_name() +local left_panel_name = Gui.uid_name() +local breadcrumbs_name = Gui.uid_name() +local top_panel_name = Gui.uid_name() +local variable_label_name = Gui.uid_name() +local text_box_name = Gui.uid_name() + +Public.name = 'package' + +function Public.show(container) + local main_flow = container.add {type = 'flow', direction = 'horizontal'} + + local left_panel = main_flow.add {type = 'scroll-pane', name = left_panel_name} + local left_panel_style = left_panel.style + left_panel_style.width = 300 + + for name, file in pairs(loaded) do + if not ignore[name] then + local file_label = + left_panel.add({type = 'flow'}).add {type = 'label', name = file_label_name, caption = name} + Gui.set_data(file_label, file) + end + end + + local right_flow = main_flow.add {type = 'flow', direction = 'vertical'} + + local breadcrumbs = right_flow.add {type = 'label', name = breadcrumbs_name} + + local top_panel = right_flow.add {type = 'scroll-pane', name = top_panel_name} + local top_panel_style = top_panel.style + top_panel_style.height = 200 + + local text_box = right_flow.add {type = 'text-box', name = text_box_name} + text_box.word_wrap = true + text_box.read_only = true + text_box.selectable = true + + local text_box_style = text_box.style + text_box_style.vertically_stretchable = true + text_box_style.horizontally_stretchable = true + + local data = { + left_panel = left_panel, + breadcrumbs = breadcrumbs, + top_panel = top_panel, + text_box = text_box, + selected_file_label = nil, + selected_variable_label = nil + } + + Gui.set_data(left_panel, data) + Gui.set_data(top_panel, data) +end + +Gui.on_click( + file_label_name, + function(event) + local element = event.element + local file = Gui.get_data(element) + + local left_panel = element.parent.parent + local data = Gui.get_data(left_panel) + + local selected_file_label = data.selected_file_label + + if selected_file_label then + selected_file_label.style.font_color = Color.white + end + + element.style.font_color = Color.orange + data.selected_file_label = element + + local top_panel = data.top_panel + local text_box = data.text_box + + Gui.clear(top_panel) + + local file_type = type(file) + + if file_type == 'table' then + for k, v in pairs(file) do + local label = + top_panel.add({type = 'flow'}).add {type = 'label', name = variable_label_name, caption = k} + Gui.set_data(label, v) + end + elseif file_type == 'function' then + text_box.text = dump_function(file) + else + text_box.text = tostring(file) + end + end +) + +Gui.on_click( + variable_label_name, + function(event) + local element = event.element + local variable = Gui.get_data(element) + + local top_panel = element.parent.parent + local data = Gui.get_data(top_panel) + local text_box = data.text_box + + local variable_type = type(variable) + + if variable_type == 'table' then + Gui.clear(top_panel) + for k, v in pairs(variable) do + local label = + top_panel.add({type = 'flow'}).add {type = 'label', name = variable_label_name, caption = k} + Gui.set_data(label, v) + end + return + end + + local selected_label = data.selected_variable_label + + if selected_label and selected_label.valid then + selected_label.style.font_color = Color.white + end + + element.style.font_color = Color.orange + data.selected_variable_label = element + + if variable_type == 'function' then + text_box.text = dump_function(variable) + else + text_box.text = tostring(variable) + end + end +) + +return Public diff --git a/features/gui/debug/redmew_global_view.lua b/features/gui/debug/redmew_global_view.lua new file mode 100644 index 00000000..e3d51691 --- /dev/null +++ b/features/gui/debug/redmew_global_view.lua @@ -0,0 +1,137 @@ +local Gui = require 'utils.gui' +local Global = require 'utils.global' +local Token = require 'utils.token' +local Color = require 'resources.color_presets' +local Model = require 'features.gui.debug.model' + +local dump = Model.dump +local concat = table.concat +local loadstring = loadstring +local pcall = pcall + +local Public = {} + +local header_name = Gui.uid_name() +local left_panel_name = Gui.uid_name() +local right_panel_name = Gui.uid_name() +local input_text_box_name = Gui.uid_name() +local refresh_name = Gui.uid_name() + +Public.name = 'Global' + +function Public.show(container) + local main_flow = container.add {type = 'flow', direction = 'horizontal'} + + local left_panel = main_flow.add {type = 'scroll-pane', name = left_panel_name} + local left_panel_style = left_panel.style + left_panel_style.width = 300 + + for token_id, token_name in pairs(Global.names) do + local header = left_panel.add({type = 'flow'}).add {type = 'label', name = header_name, caption = token_name} + Gui.set_data(header, token_id) + end + + local right_flow = main_flow.add {type = 'flow', direction = 'vertical'} + + local right_top_flow = right_flow.add {type = 'flow', direction = 'horizontal'} + + local input_text_box = right_top_flow.add {type = 'text-box', name = input_text_box_name} + local input_text_box_style = input_text_box.style + input_text_box_style.horizontally_stretchable = true + input_text_box_style.height = 32 + + local refresh_button = + right_top_flow.add {type = 'sprite-button', name = refresh_name, sprite = 'utility/reset', tooltip = 'refresh'} + local refresh_button_style = refresh_button.style + refresh_button_style.width = 32 + refresh_button_style.height = 32 + + local right_panel = right_flow.add {type = 'text-box', name = right_panel_name} + right_panel.word_wrap = true + right_panel.read_only = true + right_panel.selectable = true + + local right_panel_style = right_panel.style + right_panel_style.vertically_stretchable = true + right_panel_style.horizontally_stretchable = true + + local data = { + right_panel = right_panel, + input_text_box = input_text_box, + selected_header = nil + } + + Gui.set_data(input_text_box, data) + Gui.set_data(left_panel, data) + Gui.set_data(refresh_button, data) +end + +Gui.on_click( + header_name, + function(event) + local element = event.element + local token_id = Gui.get_data(element) + + local left_panel = element.parent.parent + local data = Gui.get_data(left_panel) + local right_panel = data.right_panel + local selected_header = data.selected_header + local input_text_box = data.input_text_box + + if selected_header then + selected_header.style.font_color = Color.white + end + + element.style.font_color = Color.orange + data.selected_header = element + + input_text_box.text = concat {'global.tokens[', token_id, ']'} + input_text_box.style.font_color = Color.black + + local content = dump(Token.get_global(token_id)) or 'nil' + right_panel.text = content + end +) + +local function update_dump(text_input, data) + local func = loadstring('return ' .. text_input.text) + if not func then + text_input.style.font_color = Color.red + return + end + + local suc, var = pcall(func) + if not suc then + text_input.style.font_color = Color.red + return + end + + text_input.style.font_color = Color.black + + local right_panel = data.right_panel + right_panel.text = dump(var) +end + +Gui.on_text_changed( + input_text_box_name, + function(event) + local element = event.element + local data = Gui.get_data(element) + + update_dump(element, data) + end +) + +Gui.on_click( + refresh_name, + function(event) + local element = event.element + local data = Gui.get_data(element) + + local input_text_box = data.input_text_box + + update_dump(input_text_box, data) + end +) + +return Public