-- 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" -- Child Processes API local config = require "core.config" core.jpdebug = {} -- Global list of all the runners local runner_shell = require("plugins.jpdebug.runners.shell") core.jpdebug.runners = { runner_shell } -- A list of created views local active_views = {} -- Local helper functions for debugging -------------------------- ---@diagnostic disable-next-line: unused-function local function dump(o) if type(o) == 'table' 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 tostring(o) end 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.caption = title or "JP Debug" self.lines = { "[jpdebug] 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) if not s or s == "" then return end -- split on newlines; prefix stderr for line in (s .. "\n"):gmatch("(.-)\n") do if kind == "stderr" then line = "[stderr] " .. line else line = "[stdout] " .. line end self.lines[#self.lines + 1] = line 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 -- ---------- 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 = (config.plugins and config.plugins.jpdebug and config.plugins.jpdebug.target) or {} return t end -- ---------- run target & pipe stdout/stderr into the view ---------- local function run_target(target, name) local title = ("JP Debug: %s"):format(name) 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 -- Check if we have a runner ---@diagnostic disable-next-line: unused-local for i,runner in ipairs(core.jpdebug.runners) do if runner.name and runner.name == target.type then -- Found a runner 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 end end -- No suitable runners found core.error("[jpdebug] No suitable runners found for target %s", name) end ---@diagnostic disable-next-line: param-type-mismatch command.add(nil, { ["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, })