-- 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" local active_views = {} -- ---------- 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 local opts = { cwd = target.cwd, stdout = process.REDIRECT_PIPE, stderr = process.REDIRECT_PIPE } view:clear() local proc = process.start({"lua", target.file}, opts) -- 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 command.add(false, { ["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, })