320 lines
8.6 KiB
Lua
320 lines
8.6 KiB
Lua
-- mod-version:3 -- lite-xl 2.1.8
|
|
local core = require "core"
|
|
local style = require "core.style"
|
|
local command = require "core.command"
|
|
local View = require "core.view"
|
|
local process = require "process"
|
|
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
|
|
core.jpdebug.runners = core.jpdebug.runners or {}
|
|
|
|
-- Currently used view
|
|
local active_view = nil
|
|
|
|
-- The selected target
|
|
local selected_target = nil
|
|
|
|
-- 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
|
|
local function dump(o, force)
|
|
force = force or false
|
|
if type(o) == 'table' or force then
|
|
local s = '{ '
|
|
for k,v in pairs(o) do
|
|
if type(k) ~= 'number' then k = '"'..k..'"' end
|
|
s = s .. '['..k..'] = ' .. dump(v) .. ','
|
|
end
|
|
return s .. '} '
|
|
else
|
|
return type(o)..": "..tostring(o)
|
|
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 ----------
|
|
---@class JPDebugView : core.view
|
|
local JPDebugView = View:extend()
|
|
|
|
function JPDebugView:new(title)
|
|
JPDebugView.super.new(self)
|
|
self.scrollable = true
|
|
self.context = "session"
|
|
self.caption = title or "JP Debug"
|
|
self.lines = { "ready.\n" }
|
|
self.max_lines = 5000 -- keep memory bounded
|
|
self.font = style.code_font
|
|
self.line_h = self.font:get_height()
|
|
end
|
|
|
|
function JPDebugView:get_name()
|
|
return self.caption
|
|
end
|
|
|
|
function JPDebugView:get_scrollable_size()
|
|
return math.max(#self.lines * self.line_h + style.padding.y * 2, self.size.y)
|
|
end
|
|
|
|
function JPDebugView:push(kind, s)
|
|
--TODO do some things with kind here
|
|
if not s or s == "" then return end
|
|
local lines = stringsplit(s, "\n")
|
|
for _,l in pairs(lines) do
|
|
self.lines[#self.lines + 1] = l
|
|
if #self.lines > self.max_lines then
|
|
local drop = #self.lines - self.max_lines
|
|
for _ = 1, drop do table.remove(self.lines, 1) end
|
|
end
|
|
end
|
|
-- autoscroll to bottom
|
|
self.scroll.to.y = self:get_scrollable_size()
|
|
core.redraw = true
|
|
end
|
|
|
|
function JPDebugView:clear()
|
|
self.lines = {}
|
|
core.redraw = true
|
|
end
|
|
|
|
function JPDebugView:draw()
|
|
self:draw_background(style.background)
|
|
local ox, oy = self:get_content_offset()
|
|
local x = ox + style.padding.x
|
|
local y = oy + style.padding.y
|
|
renderer.draw_rect(self.position.x, self.position.y, self.size.x, self.size.y, style.background)
|
|
for i = 1, #self.lines do
|
|
renderer.draw_text(self.font, self.lines[i], x, y, style.text)
|
|
y = y + self.line_h
|
|
end
|
|
self:draw_scrollbar()
|
|
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 ----------
|
|
local function get_targets()
|
|
local t = (config.plugins and config.plugins.jpdebug and config.plugins.jpdebug.targets) or {}
|
|
return t
|
|
end
|
|
|
|
local function get_selected_target()
|
|
local t = selected_target or ((config.plugins and config.plugins.jdebug and config.plugins.jpdebug.default_target) or nil)
|
|
return t
|
|
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 = ensure_debug_view()
|
|
|
|
-- Check if we have a runner
|
|
for runner_name,runner in pairs(core.jpdebug.runners) do
|
|
if runner_name == target.type then
|
|
debugger.run(target, name, runner, view)
|
|
return view
|
|
end
|
|
end
|
|
|
|
-- No suitable runners found
|
|
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
|
|
|
|
---@diagnostic disable-next-line: param-type-mismatch
|
|
command.add(nil, {
|
|
|
|
-- The run command
|
|
["jpdebug:run"] = function()
|
|
local targets = get_targets()
|
|
local target = get_selected_target()
|
|
if target then
|
|
if targets[target] then
|
|
core.log('[jpdebug] Starting target %s', target)
|
|
run_target(targets[target], target)
|
|
else
|
|
core.error("[jpdebug] Selected target not existing in targets list")
|
|
return
|
|
end
|
|
else
|
|
core.error("[jpdebug] No target selected in project module")
|
|
return
|
|
end
|
|
end,
|
|
|
|
["jpdebug:stop"] = function()
|
|
debugger.stop()
|
|
end,
|
|
|
|
-- The set target command
|
|
["jpdebug:set-target"] = function()
|
|
core.command_view:enter("Select target", {
|
|
show_suggestions = true,
|
|
submit = function(selection)
|
|
if get_targets()[selection] then
|
|
selected_target = selection
|
|
else
|
|
core.error("[jpdebug] '%s' not a target", selection)
|
|
end
|
|
end,
|
|
suggest = function(_)
|
|
local l = {}
|
|
for name, _ in pairs(get_targets()) do
|
|
table.insert(l, name)
|
|
end
|
|
return l
|
|
end
|
|
})
|
|
end,
|
|
|
|
["jpdebug:reload-runners"] = function()
|
|
load_runners()
|
|
end,
|
|
|
|
})
|
|
|