Compare commits
10 Commits
232d05d244
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d654e1776a | ||
|
|
fa0c8d5a2c | ||
|
|
349cbc8175 | ||
|
|
92fd3cabf8 | ||
|
|
abedca2394 | ||
|
|
f6c031849f | ||
|
|
8f5d71a6b9 | ||
|
|
1cd7b378b6 | ||
|
|
8cde56a4e8 | ||
|
|
7b822746d3 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
logs.txt
|
||||||
@@ -4,12 +4,19 @@ config.plugins.jpdebug = {
|
|||||||
targets = {
|
targets = {
|
||||||
["test - msys"] = {
|
["test - msys"] = {
|
||||||
type = "shell",
|
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"] = {
|
["test"] = {
|
||||||
type = "shell",
|
type = "shell",
|
||||||
cmd = {"lua", "test.lua"}
|
cmd = {"lua", "test.lua"}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
default_target = "test"
|
["luadebug"] = {
|
||||||
|
type = "luadebug",
|
||||||
|
entry = "test.lua",
|
||||||
|
cwd = ".",
|
||||||
|
-- lua = {"lua"},
|
||||||
|
lua = {"C:\\msys64\\ucrt64\\bin\\lua.exe"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default_target = "luadebug"
|
||||||
}
|
}
|
||||||
|
|||||||
138
debugger.lua
Normal file
138
debugger.lua
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
local core = require "core"
|
||||||
|
|
||||||
|
---@class runner
|
||||||
|
local runner = {
|
||||||
|
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
|
||||||
|
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 = {
|
||||||
|
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 debugger.debugwindow then debugger.debugwindow:push("meta", "debugger] "..msg) end
|
||||||
|
end
|
||||||
|
|
||||||
|
function debugger.error(msg)
|
||||||
|
core.error("[jpdebug][debugger]"..msg)
|
||||||
|
if debugger.debugwindow then debugger.debugwindow:push("meta", "debugger] ERROR: "..msg) end
|
||||||
|
end
|
||||||
|
|
||||||
|
function debugger.on_stdout(msg)
|
||||||
|
if debugger.debugwindow then debugger.debugwindow:push("stdout", msg) end
|
||||||
|
end
|
||||||
|
|
||||||
|
function debugger.on_stderr(msg)
|
||||||
|
if debugger.debugwindow then debugger.debugwindow:push("stderr", msg) end
|
||||||
|
end
|
||||||
|
|
||||||
|
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.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)
|
||||||
|
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
|
||||||
|
debugger.debugrunner = r:new({
|
||||||
|
-- Set callbacks
|
||||||
|
log = debugger.log,
|
||||||
|
error = debugger.error,
|
||||||
|
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
|
||||||
|
debugger.debugrunner:run(target, name)
|
||||||
|
end
|
||||||
|
|
||||||
|
function debugger.stop()
|
||||||
|
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 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
|
||||||
237
init.lua
237
init.lua
@@ -5,24 +5,41 @@ local command = require "core.command"
|
|||||||
local View = require "core.view"
|
local View = require "core.view"
|
||||||
local process = require "process"
|
local process = require "process"
|
||||||
local config = require "core.config"
|
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 {}
|
core.jpdebug = core.jpdebug or {}
|
||||||
|
|
||||||
-- Global list of all the runners
|
-- Global list of all the runners
|
||||||
core.jpdebug.runners = core.jpdebug.runners or {}
|
core.jpdebug.runners = core.jpdebug.runners or {}
|
||||||
local runner_shell = require("plugins.jpdebug.runners.shell")
|
|
||||||
core.jpdebug.runners[runner_shell.name] = runner_shell
|
|
||||||
|
|
||||||
-- A list of created views
|
-- Currently used view
|
||||||
local active_views = {}
|
local active_view = nil
|
||||||
|
|
||||||
-- The selected target
|
-- The selected target
|
||||||
local selected_target = nil
|
local selected_target = nil
|
||||||
|
|
||||||
-- Local helper functions for debugging --------------------------
|
-- Table containing all debug information (like breakpoints)
|
||||||
|
local debug_info = {}
|
||||||
|
|
||||||
|
-- Check if required plugins are installed
|
||||||
|
local required_toolbar_plugins = true
|
||||||
|
if TreeView == nil or ToolbarView == nil then
|
||||||
|
required_toolbar_plugins = false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Local helper functions ----------------------------------------
|
||||||
|
|
||||||
|
-- Dump any table to a string
|
||||||
|
---@param o any
|
||||||
|
---@param force bool|nil
|
||||||
|
---@return nil
|
||||||
---@diagnostic disable-next-line: unused-function
|
---@diagnostic disable-next-line: unused-function
|
||||||
local function dump(o)
|
local function dump(o, force)
|
||||||
if type(o) == 'table' then
|
force = force or false
|
||||||
|
if type(o) == 'table' or force then
|
||||||
local s = '{ '
|
local s = '{ '
|
||||||
for k,v in pairs(o) do
|
for k,v in pairs(o) do
|
||||||
if type(k) ~= 'number' then k = '"'..k..'"' end
|
if type(k) ~= 'number' then k = '"'..k..'"' end
|
||||||
@@ -30,10 +47,37 @@ local function dump(o)
|
|||||||
end
|
end
|
||||||
return s .. '} '
|
return s .. '} '
|
||||||
else
|
else
|
||||||
return tostring(o)
|
return type(o)..": "..tostring(o)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Get the plugin's path
|
||||||
|
---@return string|nil
|
||||||
|
local function get_plugin_directory()
|
||||||
|
local paths = {
|
||||||
|
USERDIR .. PATHSEP .. "plugins" .. PATHSEP .. "jpdebug",
|
||||||
|
DATADIR .. PATHSEP .. "plugins" .. PATHSEP .. "jpdebug"
|
||||||
|
}
|
||||||
|
for _, v in ipairs(paths) do
|
||||||
|
if system.get_file_info(v) then
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Simple function splitting strings
|
||||||
|
local function stringsplit(inputstr, sep)
|
||||||
|
if sep == nil then
|
||||||
|
sep = "%s"
|
||||||
|
end
|
||||||
|
local t = {}
|
||||||
|
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
|
||||||
|
table.insert(t, str)
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
-- ---------- JPDebugView: a simple scrollable log view ----------
|
-- ---------- JPDebugView: a simple scrollable log view ----------
|
||||||
---@class JPDebugView : core.view
|
---@class JPDebugView : core.view
|
||||||
local JPDebugView = View:extend()
|
local JPDebugView = View:extend()
|
||||||
@@ -41,6 +85,7 @@ local JPDebugView = View:extend()
|
|||||||
function JPDebugView:new(title)
|
function JPDebugView:new(title)
|
||||||
JPDebugView.super.new(self)
|
JPDebugView.super.new(self)
|
||||||
self.scrollable = true
|
self.scrollable = true
|
||||||
|
self.context = "session"
|
||||||
self.caption = title or "JP Debug"
|
self.caption = title or "JP Debug"
|
||||||
self.lines = { "ready.\n" }
|
self.lines = { "ready.\n" }
|
||||||
self.max_lines = 5000 -- keep memory bounded
|
self.max_lines = 5000 -- keep memory bounded
|
||||||
@@ -57,15 +102,11 @@ function JPDebugView:get_scrollable_size()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function JPDebugView:push(kind, s)
|
function JPDebugView:push(kind, s)
|
||||||
|
--TODO do some things with kind here
|
||||||
if not s or s == "" then return end
|
if not s or s == "" then return end
|
||||||
-- split on newlines; prefix stderr
|
local lines = stringsplit(s, "\n")
|
||||||
for line in (s .. "\n"):gmatch("(.-)\n") do
|
for _,l in pairs(lines) do
|
||||||
if kind == "stderr" then
|
self.lines[#self.lines + 1] = l
|
||||||
line = "[stderr] " .. line
|
|
||||||
else
|
|
||||||
line = "[stdout] " .. line
|
|
||||||
end
|
|
||||||
self.lines[#self.lines + 1] = line
|
|
||||||
if #self.lines > self.max_lines then
|
if #self.lines > self.max_lines then
|
||||||
local drop = #self.lines - self.max_lines
|
local drop = #self.lines - self.max_lines
|
||||||
for _ = 1, drop do table.remove(self.lines, 1) end
|
for _ = 1, drop do table.remove(self.lines, 1) end
|
||||||
@@ -94,6 +135,12 @@ function JPDebugView:draw()
|
|||||||
self:draw_scrollbar()
|
self:draw_scrollbar()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function JPDebugView:try_close(do_close)
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
JPDebugView.super.try_close(self, do_close)
|
||||||
|
active_view = nil
|
||||||
|
end
|
||||||
|
|
||||||
-- ---------- helper: pick a target from project module ----------
|
-- ---------- helper: pick a target from project module ----------
|
||||||
local function get_targets()
|
local function get_targets()
|
||||||
local t = (config.plugins and config.plugins.jpdebug and config.plugins.jpdebug.targets) or {}
|
local t = (config.plugins and config.plugins.jpdebug and config.plugins.jpdebug.targets) or {}
|
||||||
@@ -106,61 +153,116 @@ local function get_selected_target()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- ---------- run target & pipe stdout/stderr into the view ----------
|
-- ---------- run target & pipe stdout/stderr into the view ----------
|
||||||
local function run_target(target, name)
|
local function ensure_debug_view()
|
||||||
local title = ("JP Debug: %s"):format(name)
|
if active_view then
|
||||||
|
return active_view
|
||||||
local view = nil
|
|
||||||
if active_views[title] then
|
|
||||||
-- If there is already a view use that one
|
|
||||||
view = active_views[title]
|
|
||||||
else
|
|
||||||
-- Otherwhise lets make one
|
|
||||||
view = JPDebugView(title)
|
|
||||||
core.root_view:get_active_node():add_view(view)
|
|
||||||
active_views[title] = view
|
|
||||||
end
|
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 = ensure_debug_view()
|
||||||
|
|
||||||
-- Check if we have a runner
|
-- Check if we have a runner
|
||||||
for runner_name,runner in pairs(core.jpdebug.runners) do
|
for runner_name,runner in pairs(core.jpdebug.runners) do
|
||||||
if runner_name == target.type then
|
if runner_name == target.type then
|
||||||
-- Found a runner
|
debugger.run(target, name, runner, view)
|
||||||
local proc = runner:run(target.cmd, {
|
|
||||||
cwd = target.cwd or ".",
|
|
||||||
env = target.env or {},
|
|
||||||
stdout = process.REDIRECT_PIPE,
|
|
||||||
stderr = process.REDIRECT_PIPE
|
|
||||||
}, name)
|
|
||||||
|
|
||||||
if proc == nil then
|
|
||||||
core.error("[jpdebug] Could not run the target")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
view:clear()
|
|
||||||
-- background pump (non-blocking I/O)
|
|
||||||
core.add_thread(function()
|
|
||||||
while true do
|
|
||||||
coroutine.yield(0.016) -- ~60fps
|
|
||||||
local out = proc:read_stdout()
|
|
||||||
if out == nil then
|
|
||||||
-- stdout pipe closed: try drain stderr and break when both closed
|
|
||||||
local err = proc:read_stderr()
|
|
||||||
if err ~= nil and err ~= "" then view:push("stderr", err) end
|
|
||||||
break
|
|
||||||
end
|
|
||||||
if out ~= "" then view:push("stdout", out) end
|
|
||||||
local err = proc:read_stderr()
|
|
||||||
if err ~= nil and err ~= "" then view:push("stderr", err) end
|
|
||||||
end
|
|
||||||
local code = proc:wait(process.WAIT_INFINITE)
|
|
||||||
view:push("stdout", ("\n[exit] code=%s\n"):format(tostring(code)))
|
|
||||||
end)
|
|
||||||
|
|
||||||
return view
|
return view
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- No suitable runners found
|
-- No suitable runners found
|
||||||
core.error("[jpdebug] No suitable runners found for target %s", 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
|
||||||
|
local fresh = require(name)
|
||||||
|
|
||||||
|
if into then
|
||||||
|
-- wipe old table (keep the identity)
|
||||||
|
for k in pairs(into) do into[k] = nil end
|
||||||
|
-- copy new fields in
|
||||||
|
for k, v in pairs(fresh) do into[k] = v end
|
||||||
|
-- copy metatable too, if any
|
||||||
|
local mt = getmetatable(fresh)
|
||||||
|
if mt then setmetatable(into, mt) end
|
||||||
|
return into
|
||||||
|
else
|
||||||
|
return fresh
|
||||||
|
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
|
||||||
|
core.jpdebug.runners[runner_luadebug.name] = runner_luadebug
|
||||||
|
core.log("[jpdebug] Runners loaded")
|
||||||
|
end
|
||||||
|
-- And call it while loading the plugin
|
||||||
|
load_runners()
|
||||||
|
|
||||||
|
-- ---------- Add toolbar to treeview if plugins are installed ------
|
||||||
|
if required_toolbar_plugins and ToolbarView then
|
||||||
|
|
||||||
|
---@class Toolbar: core.view
|
||||||
|
local Toolbar = ToolbarView:extend()
|
||||||
|
function Toolbar:new()
|
||||||
|
Toolbar.super.new(self)
|
||||||
|
self.toolbar_font = renderer.font.load(get_plugin_directory() .. PATHSEP .. "toolbar_commands.ttf", style.icon_big_font:get_size())
|
||||||
|
self.toolbar_commands = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function Toolbar:_rebuild()
|
||||||
|
local t = {
|
||||||
|
{symbol = "A", command = "jpdebug:set-target"},
|
||||||
|
}
|
||||||
|
if not debugger.is_running() then
|
||||||
|
table.insert(t, {symbol = "B", command = "jpdebug:run"})
|
||||||
|
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
|
||||||
|
|
||||||
|
function Toolbar:update()
|
||||||
|
---@diagnostic disable-next-line: undefined-field
|
||||||
|
Toolbar.super.update(self)
|
||||||
|
self:_rebuild()
|
||||||
|
end
|
||||||
|
|
||||||
|
local toolbar_view = Toolbar()
|
||||||
|
---@diagnostic disable-next-line: unused-local
|
||||||
|
local toolbar_node = TreeView.node.b:split("up", toolbar_view, {y = true})
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@diagnostic disable-next-line: param-type-mismatch
|
---@diagnostic disable-next-line: param-type-mismatch
|
||||||
@@ -184,8 +286,12 @@ command.add(nil, {
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
["jpdebug:stop"] = function()
|
||||||
|
debugger.stop()
|
||||||
|
end,
|
||||||
|
|
||||||
-- The set target command
|
-- The set target command
|
||||||
["jpdebug:settarget"] = function()
|
["jpdebug:set-target"] = function()
|
||||||
core.command_view:enter("Select target", {
|
core.command_view:enter("Select target", {
|
||||||
show_suggestions = true,
|
show_suggestions = true,
|
||||||
submit = function(selection)
|
submit = function(selection)
|
||||||
@@ -204,5 +310,10 @@ command.add(nil, {
|
|||||||
end
|
end
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
["jpdebug:reload-runners"] = function()
|
||||||
|
load_runners()
|
||||||
|
end,
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
233
runners/luadebug.lua
Normal file
233
runners/luadebug.lua
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
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
|
||||||
@@ -1,20 +1,66 @@
|
|||||||
local core = require "core"
|
local core = require "core"
|
||||||
local process = require "process"
|
local process = require "process"
|
||||||
|
local debugger = require "plugins.jpdebug.debugger"
|
||||||
|
|
||||||
---@class M
|
---@class shell: runner
|
||||||
local M = {
|
local shell = debugger.runner:new({
|
||||||
name = "shell"
|
name = "shell",
|
||||||
}
|
proc = nil ---@type process|nil
|
||||||
|
})
|
||||||
|
|
||||||
function M:run(cmd, opts, name)
|
function shell:run(target, name)
|
||||||
core.log("[jpdebug] Running shell command")
|
local opts = {
|
||||||
if cmd then
|
cwd = target.cwd or ".",
|
||||||
local proc = process.start(cmd, opts)
|
env = target.env or {},
|
||||||
return proc
|
stdout = process.REDIRECT_PIPE,
|
||||||
else
|
stderr = process.REDIRECT_PIPE
|
||||||
core.error("[jpdebug] command not specified for target %s", name)
|
}
|
||||||
|
if target.cmd then
|
||||||
|
self.proc = process.start(target.cmd, opts)
|
||||||
|
if not self.proc then
|
||||||
|
self.error("Could not start process...")
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
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
|
||||||
|
core.redraw = true
|
||||||
|
coroutine.yield(0.016) -- 60FPS
|
||||||
|
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
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
function shell:wait(time)
|
||||||
|
if not self.proc then return end
|
||||||
|
return self.proc:wait(time)
|
||||||
|
end
|
||||||
|
|
||||||
|
function shell:kill()
|
||||||
|
if not self.proc then return end
|
||||||
|
self.proc:kill()
|
||||||
|
end
|
||||||
|
|
||||||
|
function shell:terminate()
|
||||||
|
if not self.proc then return end
|
||||||
|
self.proc:terminate()
|
||||||
|
end
|
||||||
|
|
||||||
|
return shell
|
||||||
|
|
||||||
|
|||||||
5
test.lua
5
test.lua
@@ -1,5 +1,6 @@
|
|||||||
print("Starting loop: test 123")
|
print("Starting loop: test 123")
|
||||||
for i = 1, 4 do
|
for i = 1,5 do
|
||||||
print("i =", i) -- we'll stop here
|
print("i = ", i) -- we'll stop here
|
||||||
|
require("socket").sleep(1)
|
||||||
end
|
end
|
||||||
print("Yaaayyyy it works perfectly fine :)")
|
print("Yaaayyyy it works perfectly fine :)")
|
||||||
|
|||||||
BIN
toolbar_commands.ttf
Normal file
BIN
toolbar_commands.ttf
Normal file
Binary file not shown.
Reference in New Issue
Block a user