local user_command = require 'std.user_command' local icon = require 'std.icon' local DBG_FILE = vim.fn.stdpath 'state' .. '/bugabinga_config_debug.jsonl' ---Debug kinds and their mapped highlight groups and icons. ---@type table local DBG_KINDS = { --TODO DEBUG = {}, TRACE = {}, ERROR = {}, } ---@alias dbg_kind ---| 'DEBUG' normal debug event -> use this, when logging debug information ---| 'TRACE' function trace event -> use this, when tracing function call flow ---| 'ERROR' error event -> use this, when logging unexpected events ---Get context information from `debug.traceback()` and other `debug` functions. ---Wwhen getting the stacktrace, we need to account for the function call graph inside this `dbg` module. ---Either its: `callsite -> metatable -> print_debug -> print_debug_kind -> get_context`. ---Or: `callsite -> print_debug_kind -> get_context`. ---In both cases we are only interested in callsite to determine file name, the function name and line number. --- ---@return string filename The filename, the calling function resides in. ---@return string function_name The name of the function, where `dbg` was called in. ---@return integer line_number Line number the calling functions is invoking the `dbg` function at. local function get_context() --TODO local filename = nil local function_name = nil local line_number = nil return filename, function_name, line_number end --- Encodes debug event to JSON --- @param event table debug event to log --- @return string json JSON-encoded debug event local function encode( event ) return vim.json.encode( event, { escape_slash = true, } ) end --- Decodes debug event from JSON --- @param line string JSON-encoded debug event --- @return table event debug event to log local function decode( line ) return vim.json.decode( line, { luanil = { object = true, array = true, }, } ) end --- Writes debug events to the debug file, one per line. --- --- @see encode --- @param event table debug event to log local function write_to_debug_file( event ) local file, errmsg = io.open( DBG_FILE, 'a+' ) if not file then error( 'Unable to open debug log file `' .. DBG_FILE .. '`: ' .. errmsg ) end local json_line = encode( event ) file:write( json_line ) file:flush() file:close() end ---Replaces expressions inside the given `format`, by indexing into `user_data`. --- ---Resolve `format`, by replacing `{}` inside format string. ---`` is used to index into `user_data`. ---e.g.: `user_data = {a = 1, b = 2, c = 3}` ---To get `b`: the format expression would be `{b}`. ---`{b}` would be replaced by `2` in format string. --- ---e.g.: `user_data = {{a=1},{b=2},{c=3}}` ---To index into nested structures, use numbers: `{2.b}`. ---`{2.b}` would be replaced by `2` in format string. ---Indexing is 1-based, as per usual in lua. ---Values are converted by `vim.inspect` to strings. --- ---@see vim.inspect --- ---@param format string Message with optional "{}"-style expressions inside ---@param data table Arbitrary data, that can be indexed into, to pick values for the `format` string ---@return string formatted_message Message with format expressions resolved local function format_message( format, data ) local formatted_message --TODO return formatted_message end if vim.g.bugabinga_debug_mode == nil then vim.g.bugabinga_debug_mode = false end local function is_debug_mode() return vim.g.bugabinga_debug_mode end ---Log debug message of particular kind. ---@param kind dbg_kind kind of event ---@param message string message format string ---@param ... any arbitrary user data, that can be referenced in message format local function print_debug_kind( kind, message, ... ) if not is_debug_mode() then return end local now = os.date '!*t' local user_data = { ... } local filename, function_name, line_number = get_context() write_to_debug_file { kind = kind, date = now, format = message, data = user_data, filename = filename, fn = function_name, line = line_number, } end ---Log a debug event. ---@param message string message format string ---@param ... any arbitrary user data, that can be referenced in message format local function print_debug( message, ... ) if not is_debug_mode() then return end print_debug_kind( 'DEBUG', message, ... ) end local function toggle_debug_mode() vim.g.bugabinga_debug_mode = not vim.g.bugabinga_debug_mode vim.notify( 'Toggled debug mode to ' .. tostring( is_debug_mode() ) ) end --- Opens the debug file in a special buffer, that is not modifiable. --- It renders the debug events in a table with special functions: --- - filter by type --- - jump to location --- - colors and icons for different kinds --- Filters are a label, e.g. "Filter by kind: " plus a special region after it on one line in the buffer above the table. --- Typing in that region activates the filtering in the table. --- --- Icons are shown as prefix to kind values in their cells: . --- The table rows are highlighted according to kinds in `DBG_KINDS`. --- Note: table row can be multiline because of formatted messages. Highlighting needs to account for that. --- --- Pressing enter while the cursor is on a table row, jumps to this events location. --- --- Debug events are read and decoded from `DBG_FILE` and converted to a table of: --- Kind, Date, Message, Location --- --- Kind -> debug event kind + icon --- Date -> human readable datetime in hosts timezone --- Message -> formatted message from format string and user data --- Locataion -> combined file name, function name and line number local function open_special_debug_buffer() --TODO end --- Opens the file `DBG_FILE` in a normal neovim buffer local function open_raw_debug_file() --TODO end user_command.Debug 'Toggle global vim config debug mode.' ( toggle_debug_mode ) user_command.DebugOpen 'Open special buffer with debug events' ( open_special_debug_buffer ) user_command.DebugOpenRaw 'Opens the JSON debug file in normal buffer' ( open_raw_debug_file ) return setmetatable( { kind = print_debug_kind, toggle = toggle_debug_mode, get = is_debug_mode, }, { --TODO: refactor usages of this function in other code to new name __call = print_debug, __newindex = function ( ... ) error( 'Assignments are not supported for std.dbg.\n' .. vim.inspect( ... ), 2 ) end, } )