--- Statusline consumer for pivi. --- It reads the published cache only and never requires pivi, so the --- statusline keeps working when pivi is absent, unloaded, or broken. local statusline = require 'bugabinga.statusline' local auto = require 'std.auto' local glyph = require 'std.icon' -- The progress frames the rest of the configuration already animates with. local SPINNER = { glyph.progress_0, glyph.progress_1, glyph.progress_2, glyph.progress_3, glyph.progress_4, glyph.progress_5, glyph.progress_6, glyph.progress_7, glyph.progress_8, glyph.progress_9, } local INTERVAL = 90 local state = { frame = 1, timer = nil, } local sessions = function() local value = vim.g.pivi if type( value ) ~= 'table' then return nil, {} end return value.active, value.sessions or {} end local busy = function( entry ) return entry ~= nil and entry.connection ~= 'stopped' and ( entry.run == 'running' or entry.run == 'retrying' or entry.compacting == true ) end local pending = function( entry ) if not entry or type( entry.queue ) ~= 'table' then return 0 end return #( entry.queue.steering or {} ) + #( entry.queue.follow_up or {} ) end local label = function( entry ) if entry.error then return '%#ErrorMsg#' .. glyph.error .. ' ' .. ( entry.tool and entry.tool.name or 'error' ) .. '%*' end if entry.compacting then return '%#WarningMsg#π ' .. glyph.lazy .. ' compacting%*' end if entry.run == 'retrying' then return '%#WarningMsg#π ' .. glyph.warn .. ' retry%*' end if entry.tool then return '%#DiffChange#π ' .. tostring( entry.tool.name ) .. '%*' end if entry.run == 'running' then return '%#DiffChange#π ' .. glyph.star .. ' thinking%*' end return 'π' end local stop_spinner = function() if not state.timer then return end state.timer:stop() state.timer:close() state.timer = nil end local start_spinner = function() if state.timer then return end state.timer = vim.uv.new_timer() state.timer:start( INTERVAL, INTERVAL, vim.schedule_wrap( function() state.frame = state.frame % #SPINNER + 1 vim.cmd.redrawstatus() end ) ) end --- Cached only: evaluating the statusline never talks to Pi. local component = function() local active, all = sessions() if not active then return '' end local entry = all[active] if not entry then return '' end local pieces = { label( entry ), } if busy( entry ) then table.insert( pieces, 1, SPINNER[state.frame] ) end local waiting = pending( entry ) if waiting > 0 then table.insert( pieces, '+' .. tostring( waiting ) ) end return table.concat( pieces, ' ' ) end statusline.register( component ) auto 'pivi_statusline' { events = 'User', pattern = 'PiviStatus', description = 'Animate the pivi statusline while a run is active.', command = function() local active, all = sessions() local entry = active and all[active] or nil if busy( entry ) then start_spinner() else stop_spinner() end vim.cmd.redrawstatus() end, } auto 'pivi_activity' { events = 'User', pattern = 'PiviActivity', description = 'Announce agent findings that landed in a native list.', command = function( event ) local data = event.data if type( data ) ~= 'table' or data.kind ~= 'findings' then return end local count = tonumber( data.count ) or 0 if count == 0 then return end vim.notify( ( 'π %d finding%s in the quickfix list' ):format( count, count == 1 and '' or 's' ), vim.log.levels.INFO ) end, } _G.BugabingaPiviStatus = { component = component, busy = busy, pending = pending, label = label, } return _G.BugabingaPiviStatus