diff --git a/debugger.lua b/debugger.lua index e54bc5c..8cf7e93 100644 --- a/debugger.lua +++ b/debugger.lua @@ -2,72 +2,113 @@ local core = require "core" ---@class runner local runner = { - new = function(self, o) end, ---@meta + new = function(self, o) + o = o or {} + setmetatable(o, self) + self.__index = self + return o + end, + + caps = { + can_pause = false, + can_continue = false, + can_step_in = false, + can_step_over = false, + can_step_out = false, + can_breakpoints = false, + has_stack = false, + has_locals = false, + can_eval = false, + }, + run = function(self, target, name) end, ---@meta - wait = function(sefl, time) end, ---@meta + pause = function(self) end, --@meta + continue = function(self) end, --@meta + step_in = function(self) end, --@meta + step_over = function(self) end, --@meta + step_out = function(self) end, --@meta + + wait = function(self, time) end, ---@meta kill = function(self) end, ---@meta - terminate = function(self) end, ---@meta + terminate = function(self) end, ---@meta + + -- Callbacks + log = function(msg) end, ---@meta + error = function(msg) end, ---@meta + on_stdout = function(msg) end, ---@meta + on_stderr = function(msg) end, ---@meta + on_exit = function(exitcode) end, ---@meta + on_state = function(state) end, ---@meta + on_break = function(file, line, reason) end, --@meta + on_stack = function(frames) end, ---@meta + on_locals = function(frame, vars) end, ---@meta + on_evaluated = function(expr, ok, value) end, ---@meta } -local debugger = {} -local debugwindow = nil ---@type JPDebugView -local debugrunner = nil ---@type runner|nil +local debugger = { + runner = runner, -- Set here as member to let runners extend this base class + debugwindow = nil, ---@type JPDebugView + debugrunner = nil, ---@type runner|nil +} function debugger.log(msg) core.log("[jpdebug][debugger] %s", msg) - if debugwindow then debugwindow:push("meta", "debugger] "..msg) end + if debugger.debugwindow then debugger.debugwindow:push("meta", "debugger] "..msg) end end function debugger.error(msg) core.error("[jpdebug][debugger]"..msg) - if debugwindow then debugwindow:push("meta", "debugger] ERROR: "..msg) end + if debugger.debugwindow then debugger.debugwindow:push("meta", "debugger] ERROR: "..msg) end end -function debugger.stdout(msg) - if debugwindow then debugwindow:push("stdout", msg) end +function debugger.on_stdout(msg) + if debugger.debugwindow then debugger.debugwindow:push("stdout", msg) end end -function debugger.stderr(msg) - if debugwindow then debugwindow:push("stderr", msg) end +function debugger.on_stderr(msg) + if debugger.debugwindow then debugger.debugwindow:push("stderr", msg) end end function debugger.is_running() - return debugrunner~=nil + return debugger.debugrunner~=nil end function debugger.run(target, name, r, view) - debugwindow = view - debugwindow:clear() + if debugger.debugrunner then + debugger.error("Already an active session") + return + end + debugger.debugwindow = view + debugger.debugwindow:clear() debugger.log(string.format("Running %s", name)) -- Create new runner object - debugrunner = r:new({ + debugger.debugrunner = r:new({ + -- Set callbacks log = debugger.log, error = debugger.error, - stdout = debugger.stdout, - stderr = debugger.stderr, - exited = debugger.exited, + on_stdout = debugger.on_stdout, + on_stderr = debugger.on_stderr, + on_exit = debugger.on_exit, }) -- And run - debugrunner:run(target, name) + debugger.debugrunner:run(target, name) end function debugger.stop() - if debugrunner then - debugrunner:kill() - local exitcode = debugrunner:wait(1000) + if debugger.debugrunner then + debugger.debugrunner.on_exit = function() end + debugger.debugrunner:kill() + local exitcode = debugger.debugrunner:wait(1000) -- TODO terminate if needed - debugger.log(string.format("... Stoped: %d", exitcode)) + debugger.log(string.format("... Stoped with exit code %d", exitcode)) end - debugrunner = nil + debugger.debugrunner = nil end -function debugger.exited() - if debugrunner then - local exitcode = debugrunner:wait(process.WAIT_INFINITE) - debugger.log(string.format("exit: %d", exitcode)) - end - debugrunner = nil +function debugger.on_exit(exitcode) + debugger.log(string.format("exit: %d", exitcode)) + debugger.debugrunner = nil end return debugger diff --git a/init.lua b/init.lua index abf669e..1e4d32c 100644 --- a/init.lua +++ b/init.lua @@ -233,6 +233,11 @@ if required_toolbar_plugins and ToolbarView then else table.insert(t, {symbol = "D", command = "jpdebug:stop"}) end + + if debugger.is_running() then + if debugger.debugrunner.caps.can_pause then table.insert(t, {symbol="C",command=""}) end + end + table.insert(t, {symbol = "E", command = "jpdebug:reload-runners"}) self.toolbar_commands = t end diff --git a/runners/luadebug.lua b/runners/luadebug.lua index c38dfb4..8a11454 100644 --- a/runners/luadebug.lua +++ b/runners/luadebug.lua @@ -1,5 +1,12 @@ local core = require "core" local process = require "process" +local debugger = require "plugins.jpdebug.debugger" + +---@class luadebug: runner +local luadebug = debugger.runner:new({ + name = "luadebug", + proc = nil ---@type process|nil +}) -- tiny helpers local function dirname(p) return p:match("^(.*)[/\\]") or "" end @@ -53,18 +60,44 @@ local function spawn_lua_process(entry, lua, cwd, env) return proc end ----@class LDB -local LDB = { - name = "luadebug" -} +local function spawn_mdb_process(lua) + local vendor_glob = join(get_plugin_root(), "vendor/?.lua") + local cmd = {} + if type(lua) == "table" then + for i=1, #lua do table.insert(cmd, lua[i]) end + else + table.insert(cmd, lua) + end + table.insert(cmd, "-e") + table.insert(cmd, string.format([[ + package.path = %q .. ";" .. package.path + require("mobdebug").listen() + ]], vendor_glob)) + local proc = process.start(cmd, { + stdout = process.REDIRECT_PIPE, + stderr = process.REDIRECT_PIPE, + }) + return proc +end ----@param target table Target table ----@param name string Name of the target to run ----@praam debuginfo table Debugging information ----@return process|nil -function LDB:run(target, name, debuginfo) +function luadebug:mdb_pump() + while true do + core.redraw = true + coroutine.yield(0.016) -- 60FPS + local sout = self.mdb:read_stdout() + local serr = self.mdb:read_stderr() + if sout == nil and serr == nil then + -- mdb exited + break + end + if sout and sout~="" then self.on_stdout("mdb>"..sout) end + if serr and serr~="" then self.on_stdout("mdb> ERROR: "..serr) end + end +end + +function luadebug:run(target, name) if target.entry == nil then - core.error("[jpdebug][luadebug] target.entry is required") + self.error("target.entry is required for "..name) return end local entry = target.entry @@ -72,44 +105,70 @@ function LDB:run(target, name, debuginfo) local cwd = target.cwd or "." local env = target.env or {} + -- spwawn the mobdebug process + self.mdb = spawn_mdb_process(lua) + local dbg = self + core.add_thread(function() + luadebug.mdb_pump(dbg) + end) + -- spawn the main lua process local proc = spawn_lua_process(entry, lua, cwd, env) - if proc == nil then - core.error("[jpdebug][luadebug] Failed to start "..entry) - return nil + self.error("Failed to start "..entry) + return end + self.proc = proc - return { - luaproc=proc - } + -- output pump + core.add_thread(function() + while true do + core.redraw = true + coroutine.yield(0.016) -- 60FPS + local sout = self.proc:read_stdout() + local serr = self.proc:read_stderr() + if sout == nil and serr == nil then + local exitcode = self.proc:wait(process.WAIT_INFINITE) + self.on_exit(exitcode) + break + end + if sout and sout~="" then self.on_stdout(sout) end + if serr and serr~="" then self.on_stderr(serr) end + end + end) end --- Wait untill it ends, possibly with timeout -function LDB:wait(proc, time) - return proc.luaproc:wait(time) +function luadebug:wait(time) + if not self.proc then return end + return self.proc:wait(time) end --- Read the stdout, returns nil if process has stopped -function LDB:read_stdout(proc) - local sl = proc.luaproc:read_stdout() +function luadebug:read_stdout() + if not self.proc then return end + local sl = self.proc:read_stdout() return sl end --- Read the stderr -function LDB:read_stderr(proc) - local sl = proc.luaproc:read_stderr() +function luadebug:read_stderr() + if not self.proc then return end + local sl = self.proc:read_stderr() return sl end --- Kill the process -function LDB:kill(proc) - proc.luaproc:kill() +function luadebug:kill() + if self.mdb then + -- Execute ctrl-C twice to exit mobdebug + self.mdb:kill() + self.mdb:kill() + end + if not self.proc then return end + self.proc:kill() end --- Terminate the process -function LDB:terminate(proc) - proc.luaproc:terminate() +function luadebug:terminate() + if not self.proc then return end + self.proc:terminate() end -return LDB +return luadebug + diff --git a/runners/shell.lua b/runners/shell.lua index 8661531..1be232f 100644 --- a/runners/shell.lua +++ b/runners/shell.lua @@ -1,30 +1,12 @@ local core = require "core" local process = require "process" +local debugger = require "plugins.jpdebug.debugger" ---@class shell: runner -local shell = { +local shell = debugger.runner:new({ name = "shell", proc = nil ---@type process|nil -} - -function shell:new(o) - o = o or {} - setmetatable(o, self) - self.__index = self - return o -end - ----@meta -function shell.log(msg) end ----@meta -function shell.error(msg) end ----@meta -function shell.stdout(msg) end ----@meta -function shell.stderr(msg) end ----@meta -function shell.exited() end - +}) function shell:run(target, name) local opts = { @@ -51,11 +33,12 @@ function shell:run(target, name) local sout = self.proc:read_stdout() local serr = self.proc:read_stderr() if sout == nil and serr == nil then - self.exited() + local exitcode = self.proc:wait(process.WAIT_INFINITE) + self.on_exit(exitcode) break end - if sout and sout~="" then self.stdout(sout) end - if serr and serr~="" then self.stderr(serr) end + if sout and sout~="" then self.on_stdout(sout) end + if serr and serr~="" then self.on_stderr(serr) end end end) end