diff --git a/debugger.lua b/debugger.lua new file mode 100644 index 0000000..e54bc5c --- /dev/null +++ b/debugger.lua @@ -0,0 +1,73 @@ +local core = require "core" + +---@class runner +local runner = { + new = function(self, o) end, ---@meta + run = function(self, target, name) end, ---@meta + wait = function(sefl, time) end, ---@meta + kill = function(self) end, ---@meta + terminate = function(self) end, ---@meta +} + +local debugger = {} +local debugwindow = nil ---@type JPDebugView +local debugrunner = nil ---@type runner|nil + +function debugger.log(msg) + core.log("[jpdebug][debugger] %s", msg) + if debugwindow then 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 +end + +function debugger.stdout(msg) + if debugwindow then debugwindow:push("stdout", msg) end +end + +function debugger.stderr(msg) + if debugwindow then debugwindow:push("stderr", msg) end +end + +function debugger.is_running() + return debugrunner~=nil +end + +function debugger.run(target, name, r, view) + debugwindow = view + debugwindow:clear() + debugger.log(string.format("Running %s", name)) + + -- Create new runner object + debugrunner = r:new({ + log = debugger.log, + error = debugger.error, + stdout = debugger.stdout, + stderr = debugger.stderr, + exited = debugger.exited, + }) + -- And run + debugrunner:run(target, name) +end + +function debugger.stop() + if debugrunner then + debugrunner:kill() + local exitcode = debugrunner:wait(1000) + -- TODO terminate if needed + debugger.log(string.format("... Stoped: %d", exitcode)) + end + 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 +end + +return debugger diff --git a/init.lua b/init.lua index aa081d5..abf669e 100644 --- a/init.lua +++ b/init.lua @@ -8,6 +8,8 @@ local config = require "core.config" local TreeView = require "plugins.treeview" local ToolbarView = require "plugins.toolbarview" +local debugger = require "plugins.jpdebug.debugger" + core.jpdebug = core.jpdebug or {} -- Global list of all the runners @@ -19,10 +21,6 @@ local active_view = nil -- The selected target local selected_target = nil --- The running system -local running_proc = nil -local running_runner = nil - -- Table containing all debug information (like breakpoints) local debug_info = {} @@ -156,12 +154,7 @@ end -- ---------- run target & pipe stdout/stderr into the view ---------- local function run_target(target, name) - -- Check if something is alredy running - if running_proc then - core.error("[jpdebug] Already a runner active") - return - end - + -- Create/get view to push text to -- TODO fix this, it throws a node is locked error once in a while local view = nil if active_view then @@ -177,53 +170,16 @@ local function run_target(target, name) -- Check if we have a runner for runner_name,runner in pairs(core.jpdebug.runners) do if runner_name == target.type then - -- Found a runner - running_proc = runner:run(target, name, debug_info) - running_runner = runner - - if running_proc == nil then - core.error("[jpdebug] Could not run the target") - view:push("stderr", "Could not run the target") - return - end - - view:clear() - - -- background pump (non-blocking I/O) - core.add_thread(function() - while true do - core.redraw = true - coroutine.yield(0.016) -- ~60fps - if running_proc == nil then - return - end - local out = runner:read_stdout(running_proc) - if out == nil then - -- stdout pipe closed: try drain stderr and break when both closed - local err = runner:read_stderr(running_proc) - if err ~= nil and err ~= "" then view:push("stderr", err) end - break - end - if out ~= "" then view:push("stdout", out) end - local err = runner:read_stderr(running_proc) - if err ~= nil and err ~= "" then view:push("stderr", err) end - end - if running_proc then - local code = runner:wait(running_proc, process.WAIT_INFINITE) - view:push("stdout", ("\n[exit] code=%s\n"):format(tostring(code))) - end - running_proc = nil - running_runner = nil - end) - + debugger.run(target, name, runner, view) return view end end + -- No suitable runners found - core.error("[jpdebug] No suitable runners found for target %s", name) - view:push("stderr", "No suitable runners found for target "..name) + debugger.error(string.format("No suitable runners found for target %s", name)) end +-- ---------- Reload a module on the go ------------------------------ local function hot_require(name, into) -- blow away the cached module package.loaded[name] = nil @@ -243,7 +199,11 @@ local function hot_require(name, into) end end +-- Load the runners table on the go local function load_runners() + -- TODO Remove: reload debugger as well here + debugger = hot_require("plugins.jpdebug.debugger", core.jpdebug.debugger) + local runner_shell = hot_require("plugins.jpdebug.runners.shell", core.jpdebug.runners.shell) local runner_luadebug = hot_require("plugins.jpdebug.runners.luadebug", core.jpdebug.runners.luadebug) core.jpdebug.runners[runner_shell.name] = runner_shell @@ -268,7 +228,7 @@ if required_toolbar_plugins and ToolbarView then local t = { {symbol = "A", command = "jpdebug:set-target"}, } - if running_proc == nil then + if not debugger.is_running() then table.insert(t, {symbol = "B", command = "jpdebug:run"}) else table.insert(t, {symbol = "D", command = "jpdebug:stop"}) @@ -311,16 +271,7 @@ command.add(nil, { end, ["jpdebug:stop"] = function() - core.log(dump(running_proc)) - if running_runner then - running_runner:kill(running_proc) - end - core.log("[jpdebug] Stopped runner") - if active_view then - active_view:push("stdout", " ... Stopped") - end - running_proc = nil - running_runner = nil + debugger.stop() end, -- The set target command diff --git a/runners/luadebug.lua b/runners/luadebug.lua index fd3ced3..c38dfb4 100644 --- a/runners/luadebug.lua +++ b/runners/luadebug.lua @@ -18,7 +18,6 @@ end local function spawn_lua_process(entry, lua, cwd, env) local host = "localhost" local port = 8172 - local nowait = false -- Resolve vendor/?.lua so "require('mobdebug')" finds the bundled file local vendor_glob = join(get_plugin_root(), "vendor/?.lua") @@ -28,9 +27,8 @@ local function spawn_lua_process(entry, lua, cwd, env) local ok, m = pcall(require, "mobdebug"); if ok then print("Connecting to "..%q..":"..tostring(%d)) local connected = m.start(%q, %d) - if not connected and %s then m.off() end end - ]], host, port, host, port, nowait and "true" or "false") + ]], host, port, host, port) local launcher = string.format([[ package.path = %q .. ";" .. package.path %s @@ -74,8 +72,6 @@ function LDB:run(target, name, debuginfo) local cwd = target.cwd or "." local env = target.env or {} - -- TODO spawn the mobdebugger - -- spawn the main lua process local proc = spawn_lua_process(entry, lua, cwd, env) @@ -85,7 +81,7 @@ function LDB:run(target, name, debuginfo) end return { - luaproc=proc, + luaproc=proc } end @@ -96,12 +92,14 @@ end -- Read the stdout, returns nil if process has stopped function LDB:read_stdout(proc) - return proc.luaproc:read_stdout() + local sl = proc.luaproc:read_stdout() + return sl end -- Read the stderr function LDB:read_stderr(proc) - return proc.luaproc:read_stderr() + local sl = proc.luaproc:read_stderr() + return sl end -- Kill the process diff --git a/runners/shell.lua b/runners/shell.lua index 8fb0c3f..8661531 100644 --- a/runners/shell.lua +++ b/runners/shell.lua @@ -1,19 +1,32 @@ local core = require "core" local process = require "process" ----@class M -local M = { - name = "shell" +---@class shell: runner +local shell = { + name = "shell", + proc = nil ---@type process|nil } --- Run a shell command ----@param target table Target table ----@param name string Name of the target to run ----@praam debuginfo table Debugging information ----@return process|nil ----@diagnostic disable-next-line: unused-local -function M:run(target, name, debuginfo) - core.log("[jpdebug] Running shell command") +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 = { cwd = target.cwd or ".", env = target.env or {}, @@ -21,43 +34,46 @@ function M:run(target, name, debuginfo) stderr = process.REDIRECT_PIPE } if target.cmd then - local proc = process.start(target.cmd, opts) - return proc + self.proc = process.start(target.cmd, opts) + if not self.proc then + self.error("Could not start process...") + return + end else - core.error("[jpdebug] command not specified for target %s", name) + self.error(string.format("command not specified for target %s", name)) end + + -- 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 + self.exited() + break + end + if sout and sout~="" then self.stdout(sout) end + if serr and serr~="" then self.stderr(serr) end + end + end) end --- Wait untill it ends, possibly with timeout ----@param proc process Process object ----@param time any Time field, process.WAIT_INFINITE -function M:wait(proc, time) - return proc:wait(time) +function shell:wait(time) + if not self.proc then return end + return self.proc:wait(time) end --- Read the stdout, returns nil if process has stopped ----@param proc process Process object -function M:read_stdout(proc) - return proc:read_stdout() +function shell:kill() + if not self.proc then return end + self.proc:kill() end --- Read the stderr ----@param proc process Process object -function M:read_stderr(proc) - return proc:read_stderr() +function shell:terminate() + if not self.proc then return end + self.proc:terminate() end --- Kill the process ----@param proc process Process object -function M:kill(proc) - proc:kill() -end - --- Terminate the process ----@param proc process Process object -function M:terminate(proc) - proc:terminate() -end - -return M +return shell