repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
neovim/lua/bugabinga/pivi/transcript.lua
Rawlocal FILETYPE = 'pivi'
local buffers = {}
local setups = {}
--- Registers a listener for a transcript buffer being created.
--- Buffer-local behavior belongs to the layers that define it, so no global
--- mapping is ever claimed on the user's behalf.
local on_buffer = function( listener )
table.insert( setups, listener )
end
local configure = function( bufnr, name )
vim.bo[bufnr].buftype = 'nofile'
vim.bo[bufnr].bufhidden = 'hide'
vim.bo[bufnr].swapfile = false
vim.bo[bufnr].filetype = FILETYPE
vim.bo[bufnr].modifiable = false
pcall( vim.api.nvim_buf_set_name, bufnr, 'pivi://' .. name )
for _, listener in ipairs( setups ) do
pcall( listener, bufnr )
end
end
--- Returns the transcript buffer for a name, creating it when missing.
local ensure = function( name )
vim.validate( 'name', name, 'string' )
local bufnr = buffers[name]
if bufnr and vim.api.nvim_buf_is_valid( bufnr ) then return bufnr end
bufnr = vim.api.nvim_create_buf( false, true )
configure( bufnr, name )
buffers[name] = bufnr
return bufnr
end
local resets = {}
--- Registers a listener for transcript content being discarded.
--- Presentation layers own state keyed by buffer, and that state is only
--- correct while the text it describes still exists.
local on_reset = function( listener )
table.insert( resets, listener )
end
local announce_reset = function( bufnr )
for _, listener in ipairs( resets ) do
pcall( listener, bufnr )
end
end
local windows = {}
--- Registers a listener for a window becoming a pivi window.
--- Window-local presentation belongs to the layers that define it, not to the
--- buffer that happens to be shown.
local on_window = function( listener )
table.insert( windows, listener )
end
local writable = function( bufnr, action )
vim.bo[bufnr].modifiable = true
local ok, failure = pcall( action )
vim.bo[bufnr].modifiable = false
if not ok then error( failure ) end
end
local follow_windows = function( bufnr )
local last = vim.api.nvim_buf_line_count( bufnr )
for _, win in ipairs( vim.api.nvim_list_wins() ) do
if vim.api.nvim_win_get_buf( win ) == bufnr then
pcall( vim.api.nvim_win_set_cursor, win, { last, 0, } )
end
end
end
--- Appends raw text, continuing the final line when it does not start with a newline.
local append = function( bufnr, text )
vim.validate( 'bufnr', bufnr, 'number' )
vim.validate( 'text', text, 'string' )
if text == '' then return end
if not vim.api.nvim_buf_is_valid( bufnr ) then return end
local chunks = vim.split( text, '\n', { plain = true, } )
writable( bufnr, function()
local count = vim.api.nvim_buf_line_count( bufnr )
local last = vim.api.nvim_buf_get_lines( bufnr, count - 1, count, false )[1] or ''
local lines = { last .. chunks[1], }
for offset = 2, #chunks do
table.insert( lines, chunks[offset] )
end
vim.api.nvim_buf_set_lines( bufnr, count - 1, count, false, lines )
end )
follow_windows( bufnr )
end
--- Appends complete lines.
local line = function( bufnr, text )
vim.validate( 'bufnr', bufnr, 'number' )
vim.validate( 'text', text, 'string' )
if not vim.api.nvim_buf_is_valid( bufnr ) then return end
append( bufnr, text .. '\n' )
end
--- Replaces a range of rows, leaving everything around it alone.
--- Rows are zero based and the end row is exclusive, as in the buffer API.
local replace = function( bufnr, first, last, text )
if not vim.api.nvim_buf_is_valid( bufnr ) then return end
writable( bufnr, function()
vim.api.nvim_buf_set_lines( bufnr, first, last, false, text )
end )
follow_windows( bufnr )
end
local lines = function( bufnr )
if not vim.api.nvim_buf_is_valid( bufnr ) then return {} end
return vim.api.nvim_buf_get_lines( bufnr, 0, -1, false )
end
local clear = function( bufnr )
if not vim.api.nvim_buf_is_valid( bufnr ) then return end
announce_reset( bufnr )
writable( bufnr, function()
vim.api.nvim_buf_set_lines( bufnr, 0, -1, false, { '', } )
end )
end
--- Window-local identity for a pivi window.
--- Only pivi's own windows are decorated; user-owned windows are untouched.
local decorate = function( win, name )
vim.wo[win].number = false
vim.wo[win].relativenumber = false
vim.wo[win].signcolumn = 'no'
vim.wo[win].foldcolumn = '0'
vim.wo[win].cursorline = false
vim.wo[win].wrap = true
vim.wo[win].linebreak = true
vim.wo[win].breakindent = true
vim.wo[win].list = false
vim.wo[win].spell = false
vim.wo[win].winbar = '%#Title#π ' .. name .. '%*'
for _, listener in ipairs( windows ) do
pcall( listener, win )
end
end
--- Shows what a pivi window is doing right now, in its own winbar.
local set_state = function( bufnr, name, state )
if not vim.api.nvim_buf_is_valid( bufnr ) then return end
local text = '%#Title#π ' .. name .. '%*'
if state and state ~= '' then text = text .. ' %#Comment#' .. state .. '%*' end
for _, win in ipairs( vim.api.nvim_list_wins() ) do
if vim.api.nvim_win_get_buf( win ) == bufnr then vim.wo[win].winbar = text end
end
end
local name_of = function( bufnr )
for name, candidate in pairs( buffers ) do
if candidate == bufnr then return name end
end
return 'pi'
end
--- Shows the transcript buffer without stealing the current window.
local show = function( bufnr )
if not vim.api.nvim_buf_is_valid( bufnr ) then return nil end
for _, win in ipairs( vim.api.nvim_list_wins() ) do
if vim.api.nvim_win_get_buf( win ) == bufnr then return win end
end
local win = require( 'bugabinga.pivi.layout' ).open( bufnr )
if not win then return nil end
decorate( win, name_of( bufnr ) )
return win
end
local forget = function( name )
local bufnr = buffers[name]
buffers[name] = nil
if bufnr then announce_reset( bufnr ) end
if bufnr and vim.api.nvim_buf_is_valid( bufnr ) then
pcall( vim.api.nvim_buf_delete, bufnr, { force = true, } )
end
end
local forget_all = function()
for name in pairs( buffers ) do
forget( name )
end
end
return {
filetype = FILETYPE,
ensure = ensure,
on_reset = on_reset,
on_window = on_window,
on_buffer = on_buffer,
decorate = decorate,
set_state = set_state,
append = append,
line = line,
replace = replace,
lines = lines,
clear = clear,
show = show,
forget = forget,
forget_all = forget_all,
}