repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
neovim/lua/bugabinga/pivi/tool.lua
Rawlocal glyph = require 'std.icon'
local MAX_SUBJECT = 60
--- What each tool does, at a glance.
--- A reader scans a transcript for the shape of the work, so the icon says
--- what kind of work it was rather than which tool performed it.
local ICONS = {
read = glyph.file,
write = glyph.edit,
edit = glyph.edit,
bash = glyph.shell,
powershell = glyph.shell,
grep = glyph.search,
find = glyph.search,
ls = glyph.folder,
nvim_help = glyph.help,
nvim_state = glyph.vim,
nvim_lua = glyph.vim,
nvim_findings = glyph.list,
}
local DEFAULT_ICON = glyph.command
--- The icon for a tool entry.
local icon = function( meta )
local name = meta and meta.tool
return ICONS[name] or DEFAULT_ICON
end
--- What a tool reports about its own work.
--- Everything here is derived from that report; a tool that reports nothing
--- gets no outcome rather than an invented one.
local details_of = function( meta )
local result = meta and meta.result
if type( result ) ~= 'table' then return nil end
if type( result.details ) ~= 'table' then return nil end
return result.details
end
local relative = function( path, cwd )
if type( path ) ~= 'string' or path == '' then return nil end
local root = cwd and vim.fs.normalize( cwd ) or nil
local full = vim.fs.normalize( path )
if root and full:sub( 1, #root + 1 ) == root .. '/' then return full:sub( #root + 2 ) end
return vim.fs.basename( full )
end
--- What the tool acted on, short enough to scan.
local subject = function( meta )
local args = meta and type( meta.args ) == 'table' and meta.args or {}
local found = args.path or args.file_path or args.filePath
or args.command or args.pattern or args.tag or args.title
if type( found ) ~= 'string' or found == '' then return nil end
if found:find( '[/\\]' ) then found = relative( found, meta.cwd ) or found end
found = found:gsub( '%s+', ' ' )
if #found > MAX_SUBJECT then found = found:sub( 1, MAX_SUBJECT - 1 ) .. '\u{2026}' end
return found
end
local counts = function( diff )
local added, removed = 0, 0
for _, line in ipairs( vim.split( diff or '', '\n', { plain = true, } ) ) do
local mark = line:sub( 1, 1 )
if mark == '+' then
added = added + 1
elseif mark == '-' then
removed = removed + 1
end
end
return added, removed
end
local LIMITS = {
{ field = 'matchLimitReached', label = 'match limit', },
{ field = 'resultLimitReached', label = 'result limit', },
{ field = 'entryLimitReached', label = 'entry limit', },
}
--- How the tool ended, or nothing when it reported nothing.
local outcome = function( meta )
if not meta then return nil end
local details = details_of( meta )
local parts = {}
if details and type( details.diff ) == 'string' then
local added, removed = counts( details.diff )
table.insert( parts, ( '+%d -%d' ):format( added, removed ) )
end
if details and type( details.truncation ) == 'table' and details.truncation.truncated then
table.insert( parts, 'truncated' )
end
for _, limit in ipairs( LIMITS ) do
if details and details[limit.field] then table.insert( parts, limit.label ) end
end
if meta.failed then table.insert( parts, 'failed' ) end
if #parts == 0 then return nil end
return table.concat( parts, ' ' )
end
--- The change a completed edit reported, as the tool formatted it.
local diff = function( meta )
local details = details_of( meta )
if not details or type( details.diff ) ~= 'string' then return nil end
local text = details.diff:gsub( '\n+$', '' )
if text == '' then return nil end
return text
end
--- The line a reported change starts at, when the tool reported one.
local first_changed_line = function( meta )
local details = details_of( meta )
local line = details and details.firstChangedLine
if type( line ) ~= 'number' or line < 1 then return nil end
return math.floor( line )
end
--- How one line of a reported change should read.
local diff_highlight = function( text )
local mark = ( text or '' ):sub( 1, 1 )
if mark == '+' then return 'DiffAdd' end
if mark == '-' then return 'DiffDelete' end
return nil
end
--- The text a tool produced, as the tool produced it.
local produced = function( meta )
local result = meta and meta.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
--- What a tool entry shows below its headline.
--- A reported change reads better than a sentence about it, so an edit shows
--- its change and every other tool shows what it produced.
local body = function( meta )
local change = diff( meta )
if change then return vim.split( change, '\n', { plain = true, } ) end
local output = produced( meta )
if output then return vim.split( output, '\n', { plain = true, } ) end
if meta and meta.failed then return { 'failed', } end
return {}
end
--- Tools whose output is the content of the file they name.
local CONTENT_TOOLS = { read = true, }
--- The language a tool's output came from, when that is known.
--- Only a tool that returns file content can claim one; anything else would
--- be a guess dressed up as syntax.
local language = function( meta )
if not meta or not CONTENT_TOOLS[meta.tool] then return nil end
if diff( meta ) then return nil end
local args = type( meta.args ) == 'table' and meta.args or {}
local path = args.path or args.file_path or args.filePath
if type( path ) ~= 'string' or path == '' then return nil end
local filetype = vim.filetype.match { filename = path, }
if not filetype then return nil end
local resolved = vim.treesitter.language.get_lang( filetype )
if not resolved then return nil end
if not pcall( vim.treesitter.language.add, resolved ) then return nil end
return resolved
end
--- Whether a tool kept less than it produced.
local truncated = function( meta )
local details = details_of( meta )
local truncation = details and details.truncation
return type( truncation ) == 'table' and truncation.truncated == true
end
--- Where everything a tool produced still lives, when it reported that.
local full_output = function( meta )
local details = details_of( meta )
local path = details and details.fullOutputPath
if type( path ) ~= 'string' or path == '' then return nil end
return path
end
--- The line a tool entry opens with: the tool and what it acted on.
local headline = function( meta )
local name = tostring( meta and meta.tool or 'tool' )
local acted_on = subject( meta )
if not acted_on then return name end
return name .. ' ' .. acted_on
end
--- Tool, subject and outcome in one line, for a collapsed entry.
local summary = function( meta )
local ended = outcome( meta )
if not ended then return headline( meta ) end
return headline( meta ) .. ' ' .. ended
end
return {
max_subject = MAX_SUBJECT,
icons = ICONS,
icon = icon,
details = details_of,
relative = relative,
subject = subject,
counts = counts,
diff = diff,
first_changed_line = first_changed_line,
diff_highlight = diff_highlight,
produced = produced,
body = body,
language = language,
outcome = outcome,
truncated = truncated,
full_output = full_output,
headline = headline,
summary = summary,
}