diff --git a/debugger.lua b/debugger.lua index 8cf7e93..de84fe3 100644 --- a/debugger.lua +++ b/debugger.lua @@ -49,6 +49,7 @@ local debugger = { runner = runner, -- Set here as member to let runners extend this base class debugwindow = nil, ---@type JPDebugView debugrunner = nil, ---@type runner|nil + state = "idle", } function debugger.log(msg) @@ -73,9 +74,18 @@ function debugger.is_running() return debugger.debugrunner~=nil end +function debugger.on_state(state) + debugger.log(string.format("state %s -> %s", debugger.state, state)) + debugger.state = state +end + function debugger.run(target, name, r, view) if debugger.debugrunner then - debugger.error("Already an active session") + if debugger.state == "paused" then + debugger.debugrunner:continue() + else + debugger.error("Already an active session") + end return end debugger.debugwindow = view @@ -90,6 +100,11 @@ function debugger.run(target, name, r, view) on_stdout = debugger.on_stdout, on_stderr = debugger.on_stderr, on_exit = debugger.on_exit, + on_state = debugger.on_state, + + on_break = function(file, line, reason) + debugger.log(string.format("breakpoint hit: %s:%d", file, line)) + end, }) -- And run debugger.debugrunner:run(target, name) @@ -104,11 +119,13 @@ function debugger.stop() debugger.log(string.format("... Stoped with exit code %d", exitcode)) end debugger.debugrunner = nil + debugger.on_state('idle') end function debugger.on_exit(exitcode) debugger.log(string.format("exit: %d", exitcode)) debugger.debugrunner = nil + debugger.on_state('idle') end return debugger diff --git a/runners/luadebug.lua b/runners/luadebug/init.lua similarity index 74% rename from runners/luadebug.lua rename to runners/luadebug/init.lua index 8a11454..f9003b1 100644 --- a/runners/luadebug.lua +++ b/runners/luadebug/init.lua @@ -2,6 +2,10 @@ local core = require "core" local process = require "process" local debugger = require "plugins.jpdebug.debugger" +-- Enable hot reloading of mdb +package.loaded["plugins.jpdebug.runners.luadebug.mdb"] = nil +local mdb = require "plugins.jpdebug.runners.luadebug.mdb" + ---@class luadebug: runner local luadebug = debugger.runner:new({ name = "luadebug", @@ -60,41 +64,6 @@ local function spawn_lua_process(entry, lua, cwd, env) return proc end -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 - -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 self.error("target.entry is required for "..name) @@ -106,11 +75,22 @@ function luadebug:run(target, name) 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) + self.mdb = mdb:new({ + log = self.log, + error = self.error, + + on_connected = function() + self.on_state('running') + self.mdb:setb('test.lua', 6) + self.mdb:run() + end, + on_pause = function(file, line) + self.on_state('paused') + self.on_break(file, line, "breakpoint") + end, + + }) + self.mdb:spawn(lua, get_plugin_root()) -- spawn the main lua process local proc = spawn_lua_process(entry, lua, cwd, env) @@ -120,6 +100,8 @@ function luadebug:run(target, name) end self.proc = proc + self.on_state('connecting') + -- output pump core.add_thread(function() while true do @@ -138,6 +120,11 @@ function luadebug:run(target, name) end) end +function luadebug:continue() + if not self.proc then return end + self.mdb:run() +end + function luadebug:wait(time) if not self.proc then return end return self.proc:wait(time) @@ -156,11 +143,7 @@ function luadebug:read_stderr() end function luadebug:kill() - if self.mdb then - -- Execute ctrl-C twice to exit mobdebug - self.mdb:kill() - self.mdb:kill() - end + if self.mdb then self.mdb:kill() end if not self.proc then return end self.proc:kill() end diff --git a/runners/luadebug/mdb.lua b/runners/luadebug/mdb.lua new file mode 100644 index 0000000..7c02f40 --- /dev/null +++ b/runners/luadebug/mdb.lua @@ -0,0 +1,120 @@ +local core = require "core" +local process = require "process" + +-- tiny helpers +local function join(a, b) return (a:sub(-1) == "/" and a or (a .. "/")) .. b end + +---@class mdb +local mdb = { + proc = nil, ---@type process|nil + state = 'idle', +} + +---@meta +function mdb.log(msg) end +---@meta +function mdb.error(msg) end +---@meta +function mdb.on_connected() end +---@meta +function mdb.on_pause(file, line) end + +function mdb:new(o) + o = o or {} + setmetatable(o, self) + self.__index = self + return o +end + +function mdb:set_state(newstate) + self.state = newstate +end + +function mdb:parse_line(line) + self.log('mdb> '..line) + local words = {} + for w in line:gmatch("%w+") do table.insert(words, w) end + + if words[1] == 'Paused' then + -- Breakpoint hit + if #words ~= 7 then return end + local file = words[4] + local linenr = tonumber(words[7]) + self:set_state("paused") + core.add_thread(function() self.on_pause(file, linenr) end) + elseif words[1] == "Type" then + -- mobdebug started + self:set_state("paused") + core.add_thread(function() self.on_connected() end) + end +end + +function mdb:spawn(lua, root) + local vendor_glob = join(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)) + + self:set_state('spawning') + + local proc = process.start(cmd, { + stdout = process.REDIRECT_PIPE, + stderr = process.REDIRECT_PIPE, + }) + self.proc = proc + + self:set_state('connecting') + + 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 + -- mdb exited + break + end + if sout and sout~="" then + for l in string.gmatch(sout, "([^\n]+)") do + self:parse_line(l) + end + end + end + end) +end + +function mdb:cmd(c) + -- commands are only possible when mdb is paused + if self.state == "paused" then + self.log('>>>'..c) + self.proc:write(c..'\n') + else + self.error('mdb not paused') + end +end + +function mdb:run() + self:cmd("run") +end + +function mdb:setb(file, line) + self:cmd(string.format("setb %s %d", file, line)) +end + +function mdb:kill() + self.proc:kill() + self.proc:kill() +end + +return mdb + diff --git a/runners/shell.lua b/runners/shell.lua index 1be232f..12d9ab9 100644 --- a/runners/shell.lua +++ b/runners/shell.lua @@ -25,6 +25,8 @@ function shell:run(target, name) self.error(string.format("command not specified for target %s", name)) end + self.on_state('running') + -- output pump core.add_thread(function() while true do