Compare commits
4 Commits
abedca2394
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| d654e1776a | |||
| fa0c8d5a2c | |||
| 349cbc8175 | |||
| 92fd3cabf8 |
@ -4,7 +4,7 @@ config.plugins.jpdebug = {
|
||||
targets = {
|
||||
["test - msys"] = {
|
||||
type = "shell",
|
||||
cmd = {"C:\\msys64\\msys2_shell.cmd", "-defterm", "-here", "-no-start", "-ucrt64", "-shell", "bash", "-c", "lua test.lua"}
|
||||
cmd = {"C:\\msys64\\ucrt64\\bin\\lua.exe", "test.lua"}
|
||||
},
|
||||
["test"] = {
|
||||
type = "shell",
|
||||
@ -14,7 +14,8 @@ config.plugins.jpdebug = {
|
||||
type = "luadebug",
|
||||
entry = "test.lua",
|
||||
cwd = ".",
|
||||
lua = {"lua"},
|
||||
-- lua = {"lua"},
|
||||
lua = {"C:\\msys64\\ucrt64\\bin\\lua.exe"},
|
||||
},
|
||||
},
|
||||
default_target = "luadebug"
|
||||
|
||||
123
debugger.lua
123
debugger.lua
@ -2,72 +2,137 @@ 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
|
||||
|
||||
-- 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
|
||||
state = "idle",
|
||||
}
|
||||
|
||||
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.on_state(state)
|
||||
debugger.log(string.format("state %s -> %s", debugger.state, state))
|
||||
debugger.state = state
|
||||
end
|
||||
|
||||
function debugger.on_break(file, line, reason)
|
||||
debugger.log(string.format("breakpoint hit: %s:%d", file, line))
|
||||
-- Temporary continue at unknown breaks
|
||||
if file=='?' and line==-1 then
|
||||
if debugger.debugrunner.caps.can_continue then
|
||||
debugger.debugrunner:continue()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function debugger.run(target, name, r, view)
|
||||
debugwindow = view
|
||||
debugwindow:clear()
|
||||
if debugger.debugrunner then
|
||||
if debugger.state == "paused" then
|
||||
debugger.debugrunner:continue()
|
||||
else
|
||||
debugger.error("Already an active session")
|
||||
end
|
||||
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,
|
||||
on_state = debugger.on_state,
|
||||
on_break = debugger.on_break,
|
||||
})
|
||||
-- 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
|
||||
debugger.on_state('idle')
|
||||
end
|
||||
|
||||
function debugger.exited()
|
||||
if debugrunner then
|
||||
local exitcode = debugrunner:wait(process.WAIT_INFINITE)
|
||||
function debugger.on_exit(exitcode)
|
||||
debugger.log(string.format("exit: %d", exitcode))
|
||||
end
|
||||
debugrunner = nil
|
||||
debugger.debugrunner = nil
|
||||
debugger.on_state('idle')
|
||||
end
|
||||
|
||||
return debugger
|
||||
|
||||
36
init.lua
36
init.lua
@ -153,19 +153,30 @@ local function get_selected_target()
|
||||
end
|
||||
|
||||
-- ---------- run target & pipe stdout/stderr into the view ----------
|
||||
local function ensure_debug_view()
|
||||
if active_view then
|
||||
return active_view
|
||||
end
|
||||
|
||||
local node = core.root_view:get_active_node()
|
||||
local view = JPDebugView()
|
||||
|
||||
-- Defer the add until the node is unlocked (next tick).
|
||||
core.add_thread(function()
|
||||
-- Wait until the layout is safe to mutate
|
||||
while node.locked do coroutine.yield(0) end
|
||||
node:add_view(view)
|
||||
core.redraw = true
|
||||
end)
|
||||
|
||||
active_view = view
|
||||
return view
|
||||
end
|
||||
|
||||
local function run_target(target, name)
|
||||
-- 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
|
||||
-- If there is already a view use that one
|
||||
view = active_view
|
||||
else
|
||||
-- Otherwhise lets make one
|
||||
view = JPDebugView()
|
||||
core.root_view:get_active_node():add_view(view)
|
||||
active_view = view
|
||||
end
|
||||
local view = ensure_debug_view()
|
||||
|
||||
-- Check if we have a runner
|
||||
for runner_name,runner in pairs(core.jpdebug.runners) do
|
||||
@ -233,6 +244,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
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
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
|
||||
@ -14,8 +15,95 @@ local function get_plugin_root()
|
||||
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
|
||||
local function spawn_lua_process(entry, lua, cwd, env)
|
||||
function luadebug:spawn_lua_process(entry, lua, cwd, env)
|
||||
local host = "localhost"
|
||||
local port = 8172
|
||||
|
||||
@ -26,13 +114,17 @@ local function spawn_lua_process(entry, lua, cwd, env)
|
||||
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
|
||||
@ -50,66 +142,92 @@ local function spawn_lua_process(entry, lua, cwd, env)
|
||||
stdout = process.REDIRECT_PIPE,
|
||||
stderr = process.REDIRECT_PIPE,
|
||||
})
|
||||
|
||||
return proc
|
||||
end
|
||||
|
||||
---@class LDB
|
||||
local LDB = {
|
||||
name = "luadebug"
|
||||
}
|
||||
-- ---------------- Mobdebug -------------------------------------
|
||||
function luadebug:spawn_mdb(lua)
|
||||
local host = "localhost"
|
||||
local port = 8172
|
||||
|
||||
---@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)
|
||||
if target.entry == nil then
|
||||
core.error("[jpdebug][luadebug] target.entry is required")
|
||||
-- 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
|
||||
local entry = target.entry
|
||||
local lua = target.lua or "lua"
|
||||
local cwd = target.cwd or "."
|
||||
local env = target.env or {}
|
||||
|
||||
-- spawn the main lua process
|
||||
local proc = spawn_lua_process(entry, lua, cwd, env)
|
||||
self.mdb = proc
|
||||
end
|
||||
|
||||
if proc == nil then
|
||||
core.error("[jpdebug][luadebug] Failed to start "..entry)
|
||||
return nil
|
||||
-- ---------------- 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
|
||||
|
||||
return {
|
||||
luaproc=proc
|
||||
}
|
||||
-- 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
|
||||
|
||||
-- Wait untill it ends, possibly with timeout
|
||||
function LDB:wait(proc, time)
|
||||
return proc.luaproc:wait(time)
|
||||
end
|
||||
|
||||
-- Read the stdout, returns nil if process has stopped
|
||||
function LDB:read_stdout(proc)
|
||||
local sl = proc.luaproc:read_stdout()
|
||||
return sl
|
||||
end
|
||||
|
||||
-- Read the stderr
|
||||
function LDB:read_stderr(proc)
|
||||
local sl = proc.luaproc:read_stderr()
|
||||
return sl
|
||||
end
|
||||
|
||||
-- Kill the process
|
||||
function LDB:kill(proc)
|
||||
proc.luaproc:kill()
|
||||
end
|
||||
|
||||
-- Terminate the process
|
||||
function LDB:terminate(proc)
|
||||
proc.luaproc:terminate()
|
||||
end
|
||||
|
||||
return LDB
|
||||
return luadebug
|
||||
|
||||
@ -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 = {
|
||||
@ -43,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
|
||||
@ -50,12 +34,15 @@ function shell:run(target, name)
|
||||
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()
|
||||
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.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
|
||||
|
||||
Reference in New Issue
Block a user