local entry = require 'bugabinga.pivi.entry' local transcript = require 'bugabinga.pivi.transcript' local navigate = require 'bugabinga.pivi.navigate' local MAX_RESULT_LINES = 60 local TITLES = { prompt = 'submission', answer = 'answer', tool = 'tool call', error = 'error', } --- One readable value. --- A JSON fence would be concealed by the markdown rendering of the float, so --- arguments are listed rather than encoded. local readable = function( value ) if type( value ) == 'string' then return value end if type( value ) ~= 'table' then return tostring( value ) end return ( vim.inspect( value, { newline = ' ', indent = '', } ) ) end local result_text = function( result ) if type( result ) ~= 'table' then return nil end local pieces = {} for _, block in ipairs( result.content or {} ) do if block.type == 'text' and type( block.text ) == 'string' then table.insert( pieces, block.text ) end end local joined = vim.trim( table.concat( pieces, '\n' ) ) if joined == '' then return nil end return joined end --- Everything known about one entry, as readable text. local describe = function( bufnr, item ) if not item then return {} end local meta = item.meta or {} local lines = { '# ' .. ( TITLES[item.kind] or item.kind ), '', } if meta.tool then table.insert( lines, '- tool: `' .. tostring( meta.tool ) .. '`' ) end if meta.failed ~= nil then table.insert( lines, '- outcome: ' .. ( meta.failed and 'failed' or 'succeeded' ) ) end local location = navigate.location_of( item ) if location then local place = location.path if location.line then place = place .. ':' .. tostring( location.line ) end table.insert( lines, '- location: `' .. place .. '`' ) end table.insert( lines, '- lines: ' .. tostring( item.first ) .. '-' .. tostring( item.last ) ) if type( meta.args ) == 'table' and next( meta.args ) ~= nil then table.insert( lines, '' ) table.insert( lines, '## arguments' ) table.insert( lines, '' ) local names = vim.tbl_keys( meta.args ) table.sort( names, function( left, right ) return tostring( left ) < tostring( right ) end ) for _, name in ipairs( names ) do table.insert( lines, '- ' .. tostring( name ) .. ': ' .. readable( meta.args[name] ) ) end end if meta.error then vim.list_extend( lines, { '', '## error', '', tostring( meta.error ), } ) end local produced = result_text( meta.result ) if produced then local shown = vim.split( produced, '\n', { plain = true, } ) local truncated = #shown > MAX_RESULT_LINES if truncated then shown = vim.list_slice( shown, 1, MAX_RESULT_LINES ) end vim.list_extend( lines, { '', '## result', '', } ) vim.list_extend( lines, shown ) if truncated then table.insert( lines, '' ) table.insert( lines, '_the entry itself holds the rest_' ) end end if item.kind ~= 'tool' and not meta.error then local content = vim.api.nvim_buf_get_lines( bufnr, item.first - 1, item.last, false ) vim.list_extend( lines, { '', '## text', '', } ) vim.list_extend( lines, content ) end return lines end --- Inspects the entry under the cursor without leaving the transcript. --- @return number? bufnr the floating buffer --- @return number? winid the floating window local inspect = function( bufnr, line_number ) local item = entry.at( bufnr, line_number or vim.api.nvim_win_get_cursor( 0 )[1] ) if not item then return nil end local lines = describe( bufnr, item ) if #lines == 0 then return nil end -- A nameless buffer makes a statusline fall back to the working directory, -- so even a transient float says what it is. local ok, float, window = pcall( vim.lsp.util.open_floating_preview, lines, 'markdown', { border = 'rounded', focus = false, focusable = true, max_width = 100, max_height = 30, } ) if not ok then return nil end if float and vim.api.nvim_buf_is_valid( float ) then pcall( vim.api.nvim_buf_set_name, float, 'pivi://' .. ( item.meta.tool or item.kind ) .. '/detail' ) end return float, window end local bind = function( bufnr ) vim.keymap.set( 'n', '(pivi-inspect-entry)', function() inspect( bufnr ) end, { buffer = bufnr, desc = 'pivi inspect the entry', } ) end transcript.on_buffer( bind ) return { describe = describe, inspect = inspect, bind = bind, }