local M = {} local ns = nil local log_buf = nil local log_ns = nil function M.init(hobs_ns) ns = hobs_ns log_ns = vim.api.nvim_create_namespace("hobs.nvim.log") vim.api.nvim_set_hl(log_ns, "HobLogTimestamp", { fg = "grey", bold = true }) vim.api.nvim_set_hl(log_ns, "HobLogTagStarted", { fg = "#4ade80", bold = true }) vim.api.nvim_set_hl(log_ns, "HobLogTagFinished", { fg = "#60a5fa", bold = true }) vim.api.nvim_set_hl(log_ns, "HobLogTagFailed", { fg = "#f87171", bold = true }) vim.api.nvim_set_hl(log_ns, "HobLogTagError", { fg = "#fb923c", bold = true }) vim.api.nvim_set_hl(log_ns, "HobLogSed", { fg = "#a78bfa" }) vim.api.nvim_set_hl(log_ns, "HobLogSeparator", { fg = "#71717a" }) end local function get_log_buf() if log_buf and vim.api.nvim_buf_is_valid(log_buf) then return log_buf end log_buf = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(log_buf, "hobs://log") vim.bo[log_buf].buftype = "nofile" vim.bo[log_buf].bufhidden = "hide" vim.bo[log_buf].swapfile = false vim.bo[log_buf].filetype = "log" vim.bo[log_buf].modifiable = false return log_buf end local function append_to_log(lines, hl_group) local buf = get_log_buf() local base_row = vim.api.nvim_buf_line_count(buf) vim.bo[buf].modifiable = true vim.api.nvim_buf_set_lines(buf, -1, -1, false, lines) vim.bo[buf].modifiable = false if hl_group then local end_row = vim.api.nvim_buf_line_count(buf) - 1 for row = base_row, end_row do vim.api.nvim_buf_add_highlight(buf, log_ns, hl_group, row, 0, -1) end end end local function timestamp() return os.date("%Y-%m-%d %H:%M:%S") end local function log_entry(tag, msg, hl_group) local lines = vim.split(msg, "\n", { plain = true }) local prefix = string.format("[%s] %s:", timestamp(), tag) lines[1] = prefix .. " " .. lines[1] for i = 2, #lines do lines[i] = "" .. lines[i] end if hl_group and #lines == 1 then local buf = get_log_buf() local base_row = vim.api.nvim_buf_line_count(buf) vim.bo[buf].modifiable = true vim.api.nvim_buf_set_lines(buf, -1, -1, false, lines) local tag_end_col = #prefix vim.api.nvim_buf_add_highlight(buf, log_ns, "HobLogTimestamp", base_row, 0, tag_end_col) vim.api.nvim_buf_add_highlight(buf, log_ns, hl_group, base_row, tag_end_col, -1) vim.bo[buf].modifiable = false else append_to_log(lines) end end function M.log_info(msg, hl_group) append_to_log({ string.format("[%s] INFO: %s", timestamp(), msg) }, hl_group) end function M.log_error(msg) log_entry("ERROR", msg, "HobLogTagError") end function M.log_hob_started(id, filename, prompt) local filename_short = vim.fn.fnamemodify(filename, ":t") local msg = string.format("Hob %d started | %s | %s", id, filename_short, prompt) log_entry("HOB", msg, "HobLogTagStarted") end function M.log_hob_working(id, status) log_entry("HOB", string.format("Hob %d: %s", id, status)) end function M.log_hob_sed(id, script) local buf = get_log_buf() vim.bo[buf].modifiable = true local base_row = vim.api.nvim_buf_line_count(buf) local separator_line = string.format("===== Hob %d sed script =====", id) vim.api.nvim_buf_set_lines(buf, -1, -1, false, { separator_line }) vim.api.nvim_buf_add_highlight(buf, log_ns, "HobLogSeparator", base_row, 0, -1) local sed_lines = vim.split(script, "\n", { plain = true }) if #sed_lines > 0 then vim.api.nvim_buf_set_lines(buf, -1, -1, false, sed_lines) for i = 1, #sed_lines do vim.api.nvim_buf_add_highlight(buf, log_ns, "HobLogSed", base_row + i - 1, 0, -1) end end local end_sep_row = vim.api.nvim_buf_line_count(buf) - 1 vim.api.nvim_buf_set_lines(buf, -1, -1, false, { "===== end sed script =====" }) vim.api.nvim_buf_add_highlight(buf, log_ns, "HobLogSeparator", end_sep_row, 0, -1) vim.bo[buf].modifiable = false end function M.log_hob_finished(id) log_entry("HOB", string.format("Hob %d finished and applied edit", id), "HobLogTagFinished") end function M.log_hob_failed(id, reason) log_entry("HOB", string.format("Hob %d failed: %s", id, reason or "unknown error"), "HobLogTagFailed") end function M.log_hob_edit_failed(id, reason) log_entry("HOB", string.format("Hob %d edit failed: %s", id, reason or "unknown error"), "HobLogTagFailed") end function M.open() local buf = get_log_buf() if not buf or not vim.api.nvim_buf_is_valid(buf) then return end local win = vim.api.nvim_get_current_win() local win_height = vim.api.nvim_win_get_height(win) vim.cmd("botright split") vim.api.nvim_win_set_buf(0, buf) local log_height = math.min(15, math.ceil(win_height * 0.4)) vim.api.nvim_win_set_height(0, log_height) vim.api.nvim_win_set_hl_ns(0, log_ns) local lines_count = vim.api.nvim_buf_line_count(buf) if lines_count > 0 then local col = vim.fn.min({ vim.api.nvim_win_get_width(0) - 1, vim.fn.strwidth(vim.api.nvim_get_current_line()) }) vim.api.nvim_win_set_cursor(0, { lines_count, col }) end vim.cmd("startinsert") end function M.reset() if log_buf and vim.api.nvim_buf_is_valid(log_buf) then vim.api.nvim_buf_delete(log_buf, { force = true }) log_buf = nil end end return M