mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-13 02:13:06 +00:00
## About The Pull Request A couple of fixes/updates watermelon asked for. Of particular note is the ability to suppress logging runtimes that the state was involved in causing. ## Why It's Good For The Game There were a few formatting issues caused by converting the UI to React Typescript. As for suppressing runtime logging, sometimes the runtimes being logged aren't important to the lua code being run, so it would make sense to toggle whether such runtimes should be saved to a state's log. ## Changelog 🆑 admin: The layout of the lua editor has been tweaked slightly. admin: In the lua editor, you can now toggle whether to log runtimes the viewed state is involved in. /🆑
285 lines
9.8 KiB
Plaintext
285 lines
9.8 KiB
Plaintext
/datum/lua_editor
|
|
var/datum/lua_state/current_state
|
|
|
|
/// Arguments for a function call or coroutine resume
|
|
var/list/arguments = list()
|
|
|
|
/// If not set, the global table will not be shown in the lua editor
|
|
var/show_global_table = FALSE
|
|
|
|
/// The log page we are currently on
|
|
var/page = 0
|
|
|
|
/// If set, we will force the editor's modal to be this
|
|
var/force_modal
|
|
|
|
/// If set, we will force the editor to look at this chunk
|
|
var/force_view_chunk
|
|
|
|
/// If set, we will force the script input to be this
|
|
var/force_input
|
|
|
|
/// If set, the latest code execution performed from the editor raised an error, and this is the message from that error
|
|
var/last_error
|
|
|
|
/datum/lua_editor/New(state, _quick_log_index)
|
|
. = ..()
|
|
if(state)
|
|
current_state = state
|
|
LAZYADDASSOCLIST(SSlua.editors, text_ref(current_state), src)
|
|
|
|
/datum/lua_editor/ui_interact(mob/user, datum/tgui/ui)
|
|
ui = SStgui.try_update_ui(user, src, ui)
|
|
if(!ui)
|
|
ui = new(user, src, "LuaEditor", "Lua")
|
|
ui.set_autoupdate(FALSE)
|
|
ui.open()
|
|
|
|
/datum/lua_editor/Destroy(force)
|
|
. = ..()
|
|
if(current_state)
|
|
LAZYREMOVEASSOC(SSlua.editors, text_ref(current_state), src)
|
|
|
|
/datum/lua_editor/ui_state(mob/user)
|
|
return GLOB.debug_state
|
|
|
|
/datum/lua_editor/ui_data(mob/user)
|
|
var/list/data = list()
|
|
data["ss_lua_init"] = SSlua.initialized
|
|
if(!SSlua.initialized)
|
|
return data
|
|
|
|
data["noStateYet"] = !current_state
|
|
data["showGlobalTable"] = show_global_table
|
|
if(current_state)
|
|
if(current_state.log)
|
|
var/list/logs = current_state.log.Copy((page*50)+1, min((page+1)*50+1, current_state.log.len+1))
|
|
for(var/i in 1 to logs.len)
|
|
var/list/log = logs[i]
|
|
log = log.Copy()
|
|
if(log["return_values"])
|
|
log["return_values"] = kvpify_list(prepare_lua_editor_list(deep_copy_without_cycles(log["return_values"])))
|
|
logs[i] = log
|
|
data["stateLog"] = logs
|
|
data["page"] = page
|
|
data["pageCount"] = CEILING(current_state.log.len/50, 1)
|
|
data["tasks"] = current_state.get_tasks()
|
|
if(show_global_table)
|
|
current_state.get_globals()
|
|
var/list/values = current_state.globals["values"]
|
|
values = deep_copy_without_cycles(values)
|
|
values = prepare_lua_editor_list(values)
|
|
values = kvpify_list(values)
|
|
var/list/variants = current_state.globals["variants"]
|
|
data["globals"] = list("values" = values, "variants" = variants)
|
|
if(last_error)
|
|
data["lastError"] = last_error
|
|
last_error = null
|
|
data["supressRuntimes"] = current_state.supress_runtimes
|
|
data["states"] = list()
|
|
for(var/datum/lua_state/state as anything in SSlua.states)
|
|
data["states"] += state.display_name
|
|
data["callArguments"] = kvpify_list(prepare_lua_editor_list(deep_copy_without_cycles(arguments)))
|
|
if(force_modal)
|
|
data["forceModal"] = force_modal
|
|
force_modal = null
|
|
if(force_view_chunk)
|
|
data["forceViewChunk"] = force_view_chunk
|
|
force_view_chunk = null
|
|
if(force_input)
|
|
data["force_input"] = force_input
|
|
force_input = null
|
|
return data
|
|
|
|
/datum/lua_editor/proc/traverse_list(list/path, list/root, traversal_depth_offset = 0)
|
|
var/top_affected_list_depth = LAZYLEN(path)-traversal_depth_offset // The depth of the element to get
|
|
if(top_affected_list_depth)
|
|
var/list/current_list = root
|
|
// We kvpify the list to the depth of the element to get - this allows us to reach list elements contained within a assoc list's key
|
|
var/list/path_list = kvpify_list(current_list, top_affected_list_depth-1)
|
|
while(LAZYLEN(path) > traversal_depth_offset)
|
|
// Navigate to the index of the next path element within the current path element
|
|
var/list/path_element = popleft(path)
|
|
var/list/list_element = path_list[path_element["index"]]
|
|
|
|
// Enter the next path element - be it the key or the value
|
|
switch(path_element["type"])
|
|
if("key")
|
|
path_list = list_element["key"]
|
|
if("value")
|
|
path_list = list_element["value"]
|
|
else
|
|
to_chat(usr, span_warning("invalid path element type \[[path_element["type"]]] for list traversal (expected \"key\" or \"value\""))
|
|
return
|
|
// The element we are entering SHOULD be a list, unless we're at the end of the path
|
|
if(!islist(path_list) && LAZYLEN(path))
|
|
to_chat(usr, span_warning("invalid path element \[[path_list]] for list traversal (expected a list)"))
|
|
return
|
|
current_list = path_list
|
|
return current_list
|
|
else
|
|
return root
|
|
|
|
/datum/lua_editor/proc/run_code(code)
|
|
var/ckey = usr.ckey
|
|
current_state.ckey_last_runner = ckey
|
|
var/result = current_state.load_script(code)
|
|
var/index_with_result = current_state.log_result(result)
|
|
if(result["status"] == "error")
|
|
last_error = result["message"]
|
|
message_admins("[key_name(usr)] executed [length(code)] bytes of lua code. [ADMIN_LUAVIEW_CHUNK(current_state, index_with_result)]")
|
|
|
|
/datum/lua_editor/ui_act(action, list/params)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
if(!check_rights_for(usr.client, R_DEBUG))
|
|
return
|
|
if(action == "runCodeFile")
|
|
params["code"] = file2text(input(usr, "Input File") as null|file)
|
|
if(isnull(params["code"]))
|
|
return
|
|
action = "runCode"
|
|
switch(action)
|
|
if("newState")
|
|
var/state_name = params["name"]
|
|
if(!length(state_name))
|
|
return TRUE
|
|
var/datum/lua_state/new_state = new(state_name)
|
|
if(QDELETED(new_state))
|
|
return
|
|
SSlua.states += new_state
|
|
LAZYREMOVEASSOC(SSlua.editors, text_ref(current_state), src)
|
|
current_state = new_state
|
|
LAZYADDASSOCLIST(SSlua.editors, text_ref(current_state), src)
|
|
page = 0
|
|
return TRUE
|
|
if("switchState")
|
|
var/state_index = params["index"]
|
|
LAZYREMOVEASSOC(SSlua.editors, text_ref(current_state), src)
|
|
current_state = SSlua.states[state_index]
|
|
LAZYADDASSOCLIST(SSlua.editors, text_ref(current_state), src)
|
|
page = 0
|
|
return TRUE
|
|
if("runCode")
|
|
run_code(params["code"])
|
|
return TRUE
|
|
if("runFile")
|
|
var/code_file = input(usr, "Select a script to run.", "Lua") as file|null
|
|
if(!code_file)
|
|
return TRUE
|
|
var/code = file2text(code_file)
|
|
run_code(code)
|
|
return TRUE
|
|
if("moveArgUp")
|
|
var/list/path = params["path"]
|
|
var/list/target_list = traverse_list(path, arguments, traversal_depth_offset = 1)
|
|
var/index = popleft(path)["index"]
|
|
target_list.Swap(index-1, index)
|
|
return TRUE
|
|
if("moveArgDown")
|
|
var/list/path = params["path"]
|
|
var/list/target_list = traverse_list(path, arguments, traversal_depth_offset = 1)
|
|
var/index = popleft(path)["index"]
|
|
target_list.Swap(index, index+1)
|
|
return TRUE
|
|
if("removeArg")
|
|
var/list/path = params["path"]
|
|
var/list/target_list = traverse_list(path, arguments, traversal_depth_offset = 1)
|
|
var/index = popleft(path)["index"]
|
|
target_list.Cut(index, index+1)
|
|
return TRUE
|
|
if("addArg")
|
|
var/list/path = params["path"]
|
|
var/list/target_list = traverse_list(path, arguments)
|
|
if(target_list != arguments)
|
|
usr?.client?.mod_list_add(target_list, null, "a lua editor", "arguments")
|
|
else
|
|
var/list/vv_val = usr?.client?.vv_get_value(restricted_classes = list(VV_RESTORE_DEFAULT))
|
|
var/class = vv_val["class"]
|
|
if(!class)
|
|
return
|
|
LAZYADD(arguments, list(vv_val["value"]))
|
|
return TRUE
|
|
if("callFunction")
|
|
var/list/recursive_indices = params["indices"]
|
|
var/list/current_list = kvpify_list(current_state.globals["values"])
|
|
var/list/current_variants = current_state.globals["variants"]
|
|
var/function = list()
|
|
while(LAZYLEN(recursive_indices))
|
|
var/index = popleft(recursive_indices)
|
|
var/list/element = current_list[index]
|
|
var/key = element["key"]
|
|
var/value = element["value"]
|
|
var/list/variant_pair = current_variants[index]
|
|
var/key_variant = variant_pair["key"]
|
|
if(key_variant == "function" || key_variant == "thread" || key_variant == "userdata" || key_variant == "error_as_value")
|
|
to_chat(usr, span_warning("invalid table key \[[key]] for function call (expected text, num, path, list, or ref, got [key_variant])"))
|
|
return
|
|
function += key
|
|
if(islist(value))
|
|
current_list = value
|
|
current_variants = variant_pair["value"]
|
|
else
|
|
if(variant_pair["value"] != "function")
|
|
to_chat(usr, span_warning("invalid value \[[value]] for function call (expected list or function)"))
|
|
return
|
|
var/result = current_state.call_function(arglist(list(function) + arguments))
|
|
current_state.log_result(result)
|
|
if(result["status"] == "error")
|
|
last_error = result["message"]
|
|
arguments.Cut()
|
|
return
|
|
if("resumeTask")
|
|
var/task_index = params["index"]
|
|
SSlua.queue_resume(current_state, task_index, arguments)
|
|
arguments.Cut()
|
|
return TRUE
|
|
if("killTask")
|
|
var/is_sleep = params["is_sleep"]
|
|
var/index = params["index"]
|
|
SSlua.kill_task(current_state, is_sleep, index)
|
|
return TRUE
|
|
if("vvReturnValue")
|
|
var/log_entry_index = params["entryIndex"]
|
|
var/list/log_entry = current_state.log[log_entry_index]
|
|
var/thing_to_debug = traverse_list(params["indices"], log_entry["return_values"])
|
|
if(isweakref(thing_to_debug))
|
|
var/datum/weakref/ref = thing_to_debug
|
|
thing_to_debug = ref.resolve()
|
|
INVOKE_ASYNC(usr.client, TYPE_PROC_REF(/client, debug_variables), thing_to_debug)
|
|
return FALSE
|
|
if("vvGlobal")
|
|
var/thing_to_debug = traverse_list(params["indices"], current_state.globals["values"])
|
|
if(isweakref(thing_to_debug))
|
|
var/datum/weakref/ref = thing_to_debug
|
|
thing_to_debug = ref.resolve()
|
|
INVOKE_ASYNC(usr.client, TYPE_PROC_REF(/client, debug_variables), thing_to_debug)
|
|
return FALSE
|
|
if("clearArgs")
|
|
arguments.Cut()
|
|
return TRUE
|
|
if("toggleShowGlobalTable")
|
|
show_global_table = !show_global_table
|
|
return TRUE
|
|
if("toggleSupressRuntimes")
|
|
current_state.supress_runtimes = !current_state.supress_runtimes
|
|
return TRUE
|
|
if("nextPage")
|
|
page = min(page+1, CEILING(current_state.log.len/50, 1)-1)
|
|
return TRUE
|
|
if("previousPage")
|
|
page = max(page-1, 0)
|
|
return TRUE
|
|
if("nukeLog")
|
|
current_state.log.Cut()
|
|
return TRUE
|
|
|
|
/datum/lua_editor/ui_close(mob/user)
|
|
. = ..()
|
|
qdel(src)
|
|
|
|
ADMIN_VERB(lua_editor, R_DEBUG, "Open Lua Editor", "Its codin' time.", ADMIN_CATEGORY_DEBUG)
|
|
var/datum/lua_editor/editor = new
|
|
editor.ui_interact(user.mob)
|