Files
jpdebug/runners/luadebug.lua
2025-11-20 12:36:55 +01:00

234 lines
5.9 KiB
Lua

local core = require "core"
local process = require "process"
local debugger = require "plugins.jpdebug.debugger"
-- tiny helpers
local function dirname(p) return p:match("^(.*)[/\\]") or "" end
local function join(a, b) return (a:sub(-1) == "/" and a or (a .. "/")) .. b end
-- returns absolute path to the plugin root (…/jpdebug)
local function get_plugin_root()
-- debug.getinfo(1, "S").source gives "@/full/path/to/this/file.lua"
local src = debug.getinfo(1, "S").source
if src:sub(1, 1) == "@" then src = src:sub(2) end
local here = dirname(src) -- …/jpdebug/runners
return dirname(here) -- …/jpdebug
end
-- Check if string starts with a substring
local function starts_with(str, word)
return str:sub(1, #word) == word
end
-- ------------------- Runner API --------------------------------
---@class luadebug: runner
local luadebug = debugger.runner:new({
name = "luadebug",
proc = nil, ---@type process|nil
caps = {
can_pause = false,
can_continue = true,
can_step_in = true,
can_step_over = true,
can_step_out = true,
can_breakpoints = true,
has_stack = true,
has_locals = true,
can_eval = false,
},
})
function luadebug:run(target, name)
if target.entry == nil then
self.error("target.entry is required for "..name)
return
end
local entry = target.entry
local lua = target.lua or "lua"
local cwd = target.cwd or "."
local env = target.env or {}
-- spawn the debugger
self:spawn_mdb(lua)
-- TODO error checking
-- spawn the main lua process
local proc = self:spawn_lua_process(entry, lua, cwd, env)
if proc == nil then
self.error("Failed to start "..entry)
-- TODO kill mdb
return
end
self.proc = proc
self.on_state('connecting')
-- Start output pump
self:pumps()
end
function luadebug:continue()
self.mdb:write('run\n')
self.on_state('running')
end
function luadebug:wait(time)
if not self.proc then return end
return self.proc:wait(time)
end
function luadebug:read_stdout()
if not self.proc then return end
local sl = self.proc:read_stdout()
return sl
end
function luadebug:read_stderr()
if not self.proc then return end
local sl = self.proc:read_stderr()
return sl
end
function luadebug:kill()
if not self.proc then return end
self.proc:kill()
end
function luadebug:terminate()
if not self.proc then return end
self.proc:terminate()
end
-- --------------- Lua Process -----------------------------------
-- Main lua process spawner
function luadebug:spawn_lua_process(entry, lua, cwd, env)
local host = "localhost"
local port = 8172
-- Resolve vendor/?.lua so "require('mobdebug')" finds the bundled file
local vendor_glob = join(get_plugin_root(), "vendor/?.lua")
-- Build the inline lua launcher
local dbg_call = string.format([[
local ok, m = pcall(require, "mobdebug"); if ok then
print("Connecting to "..%q..":"..tostring(%d))
m.connecttimeout = 0.1
local connected = m.start(%q, %d)
if not connecten then m.off() end
end
print("Test123")
]], host, port, host, port)
local launcher = string.format([[
package.path = %q .. ";" .. package.path
%s
dofile(%q)
os.exit()
]], vendor_glob, dbg_call, entry)
-- Spawn the process
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, launcher)
local proc = process.start(cmd, {
cwd = cwd,
env = env,
stdout = process.REDIRECT_PIPE,
stderr = process.REDIRECT_PIPE,
})
return proc
end
-- ---------------- Mobdebug -------------------------------------
function luadebug:spawn_mdb(lua)
local host = "localhost"
local port = 8172
-- Resolve vendor/?.lua so "require('mobdebug')" finds the bundled file
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(%s, %d)
os.exit()
]], vendor_glob, host, port))
local proc = process.start(cmd, {
stdout = process.REDIRECT_PIPE,
stderr = process.REDIRECT_PIPE,
})
if proc == nil then
self.error("Failed to start debugger")
return
end
self.mdb = proc
end
-- ---------------- Pumps ---------------------------------------
function luadebug:pumps()
core.add_thread(function()
while true do
core.redraw = true
coroutine.yield(0.016) -- 60FPS
-- LUA PROGRAM OUTPUT
if true then
local sout = self.proc:read_stdout()
local serr = self.proc:read_stderr()
if sout == nil or serr == nil then
-- Make sure to read stderr for the last time
if serr and serr~="" then self.on_stderr(serr) end
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
-- MDB OUTPUT
if true then
local sout = self.mdb:read_stdout()
local serr = self.mdb:read_stderr()
if sout == nil or serr == nil then
-- Make sure to read stderr for the last time
if serr and serr~="" then self.on_stderr(serr) end
local exitcode = self.proc:wait(process.WAIT_INFINITE)
self.on_exit(exitcode)
break
end
if sout and sout~="" then self.on_stdout("mdb> "..sout) end
if serr and serr~="" then self.on_stderr("mdb> "..serr) end
if sout and sout~="" then
-- Check output
if starts_with(sout, "Paused") then
self.on_state('paused')
self.on_break('?', -1, 'paused')
end
end
end
end
end)
end
return luadebug