repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
neovim/lua/bugabinga/pivi/capture.lua
Rawlocal encode = function( width, height, cell )
local rows, styles = {}, {}
for row = 1, height do
local text, runs = {}, {}
for col = 1, width do
local char, id, attributes = cell( row, col )
text[#text + 1] = char
local key = tostring( id )
styles[key] = styles[key] or attributes
local previous = runs[#runs]
if previous and previous.style == key then
previous.width = previous.width + 1
else
runs[#runs + 1] = { col = col, width = 1, style = key, }
end
end
rows[row] = { text = table.concat( text ), runs = runs, }
end
return { width = width, height = height, rows = rows, styles = styles, }
end
local consume = function( state, events )
for _, event in ipairs( events ) do
local name = event[1]
if name == 'flush' then state.ready = true end
for i = 2, #event do
local item = event[i]
if name == 'hl_attr_define' then
local attributes = item[2]
attributes.cterm = item[3]
state.styles[item[1]] = attributes
elseif name == 'default_colors_set' then
state.defaults = {
foreground = item[1], background = item[2], special = item[3],
ctermfg = item[4], ctermbg = item[5],
}
elseif name == 'grid_resize' and item[1] == 1 then
state.width, state.height = item[2], item[3]
state.cells = {}
for row = 1, state.height do
state.cells[row] = {}
for col = 1, state.width do state.cells[row][col] = { ' ', 0, } end
end
elseif name == 'grid_clear' and item[1] == 1 then
for row = 1, state.height do
for col = 1, state.width do state.cells[row][col] = { ' ', 0, } end
end
elseif name == 'grid_line' and item[1] == 1 then
local row, col, id = item[2] + 1, item[3] + 1, 0
for _, cell in ipairs( item[4] ) do
id = cell[2] or id
for _ = 1, cell[3] or 1 do
state.cells[row][col] = { cell[1], id, }
col = col + 1
end
end
elseif name == 'grid_scroll' and item[1] == 1 then
local before = vim.deepcopy( state.cells )
for row = item[2] + 1, item[3] do
for col = item[4] + 1, item[5] do
local source_row, source_col = row + item[6], col + item[7]
state.cells[row][col] = source_row > item[2] and source_row <= item[3]
and source_col > item[4] and source_col <= item[5]
and before[source_row][source_col] or { ' ', 0, }
end
end
elseif name == 'grid_cursor_goto' and item[1] == 1 then
state.cursor = { row = item[2] + 1, col = item[3] + 1, }
end
end
end
end
local decode = function( state, unpacker, chunk )
local offset = 1
while offset <= #chunk do
local message
message, offset = unpacker( chunk, offset )
if message then
if message[1] == 2 and message[2] == 'redraw' then
consume( state, message[3] )
elseif message[1] == 1 and message[2] == 1 and message[3] ~= vim.NIL then
state.error = vim.inspect( message[3] )
end
end
end
end
local info = function()
local uis = vim.api.nvim_list_uis()
if #uis == 0 then error( 'nvim_capture requires an attached UI; headless buffer text is not a rendered grid' ) end
local grid = { width = vim.o.columns, height = vim.o.lines, }
grid.coordinate_base = 1
grid.mode = vim.api.nvim_get_mode().mode
grid.uis = uis
grid.windows = {}
for _, window in ipairs( vim.api.nvim_tabpage_list_wins( 0 ) ) do
local position = vim.api.nvim_win_get_position( window )
grid.windows[#grid.windows + 1] = {
window = window, buffer = vim.api.nvim_win_get_buf( window ),
row = position[1] + 1, col = position[2] + 1,
width = vim.api.nvim_win_get_width( window ), height = vim.api.nvim_win_get_height( window ),
config = vim.api.nvim_win_get_config( window ),
}
end
grid.external = {}
for _, name in ipairs( { 'ext_cmdline', 'ext_messages', 'ext_popupmenu', 'ext_tabline', 'ext_wildmenu', } ) do
for _, ui in ipairs( uis ) do
if ui[name] then grid.external[#grid.external + 1] = name; break end
end
end
return grid
end
local capture = function( address, metadata )
local state = { cells = {}, styles = { [0] = { cterm = {}, }, }, }
local unpacker = vim.mpack.Unpacker()
local host, port = address:match( '^([%w%.%-]+):(%d+)$' )
local channel = host and vim.uv.new_tcp() or vim.uv.new_pipe( false )
local read = function( problem, chunk )
if problem then state.error = tostring( problem ); return end
if not chunk then
if not state.ready then state.error = 'UI capture connection closed before flush' end
return
end
local ok, failure = pcall( decode, state, unpacker, chunk )
if not ok then state.error = tostring( failure ) end
end
local connected = function( problem )
if problem then state.error = tostring( problem ); return end
channel:read_start( read )
channel:write( vim.mpack.encode( { 0, 1, 'nvim_ui_attach', {
metadata.width, metadata.height,
{ rgb = true, ext_linegrid = true, ext_multigrid = false, },
}, } ) )
end
local ok, result = pcall( function()
if host then channel:connect( host, tonumber( port ), connected ) else channel:connect( address, connected ) end
local ready = vim.wait( 5000, function() return state.ready or state.error ~= nil end, 10 )
if state.error then error( state.error ) end
if not ready then error( 'timed out waiting for a rendered UI flush' ) end
local grid = encode( state.width, state.height, function( row, col )
local cell = state.cells[row][col]
return cell[1], cell[2], state.styles[cell[2]]
end )
grid.coordinate_base = metadata.coordinate_base
grid.defaults = state.defaults
grid.cursor = state.cursor
grid.cursor.mode = metadata.mode
grid.uis = metadata.uis
grid.windows = metadata.windows
grid.external = metadata.external
return grid
end )
channel:read_stop()
channel:close()
if not ok then error( result ) end
return result
end
return { info = info, capture = capture, encode = encode, consume = consume, decode = decode, }