Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

neovim/lua/std/dbg/file.lua

Raw
local encode = require 'std.dbg.encode'
local decode = require 'std.dbg.encode'

local DBG_FILE = vim.fn.stdpath 'state' .. '/bugabinga_config_debug.jsonl'

---Returns the path to the debug log file.
---@return string
local function get_path()
  return vim.g.bugabinga_debug_file_override or DBG_FILE
end

---Returns the filename, function name, and line number of the caller.
---Skips all frames inside std/dbg/* modules.
---@return string filename
---@return string function_name
---@return integer line_number
local function get_context()
  local level = 2
  while true do
    local info = debug.getinfo(level, 'Sln')
    if not info then break end
    local src = info.short_src or info.source
    if src and not src:match 'std/dbg' and not src:match '@std/dbg' then
      return src, info.name or '', info.currentline or 0
    end
    level = level + 1
  end
  return 'unknown', '', 0
end

---Appends a debug event as a JSON line to the debug file.
---@param event table debug event
---@param path string? override path
local function write(event, path)
  local file_path = path or DBG_FILE
  local dir = vim.fs.dirname(file_path)
  if dir and dir ~= '' then
    vim.fn.mkdir(dir, 'p')
  end
  local file, errmsg = io.open(file_path, 'a')
  if not file then
    error('Unable to open debug log file `' .. file_path .. '`: ' .. tostring(errmsg))
  end
  local json_line = encode.encode(event)
  file:write(json_line .. '\n')
  file:flush()
  file:close()
end

---Closes the open file descriptor. No-op with io.open.
local function close_fd()
end

---Clears the debug log file by truncating it.
local function clear_log()
  local dir = vim.fs.dirname(DBG_FILE)
  if dir and dir ~= '' then
    vim.fn.mkdir(dir, 'p')
  end
  local file, errmsg = io.open(DBG_FILE, 'w')
  if file then
    file:close()
  end
end

---Reads all debug events from a JSONL file.
---@param path string? override path (defaults to DBG_FILE)
---@return table[] list of decoded events
local function read_all(path)
  local file_path = path or DBG_FILE
  local file = io.open(file_path, 'r')
  if not file then
    return {}
  end
  local events = {}
  for line in file:lines() do
    if line and line ~= '' then
      local ok, event = pcall(decode.decode, line)
      if ok and event then
        table.insert(events, event)
      end
    end
  end
  file:close()
  return events
end

---Reads only new events from the JSONL file, skipping the first `last_count` lines.
---Use for incremental updates when tailing a growing file.
---@param last_count number skip this many lines before reading new ones
---@param path string? override path (defaults to DBG_FILE)
---@return table[] new_events list of newly decoded events
---@return number new_total total count of events in file after reading
local function read_new(last_count, path)
  local file_path = path or DBG_FILE
  local file = io.open(file_path, 'r')
  if not file then
    return {}, 0
  end
  local new_events = {}
  local current_count = 0
  for line in file:lines() do
    if line and line ~= '' then
      current_count = current_count + 1
      if current_count > last_count then
        local ok, event = pcall(decode.decode, line)
        if ok and event then
          table.insert(new_events, event)
        end
      end
    end
  end
  file:close()
  return new_events, current_count
end

return {
  get_path = get_path,
  get_context = get_context,
  write = write,
  read_all = read_all,
  read_new = read_new,
  close_fd = close_fd,
  clear_log = clear_log,
}