repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
neovim/lua/bugabinga/pivi/follow.lua
Rawlocal MODES = { window = true, tab = true, current = true, }
local state = {
mode = nil,
window = nil,
tabpage = nil,
origin = nil,
}
local mode = function() return state.mode end
local set = function( value )
if value == nil or value == 'off' then
state.mode = nil
return nil
end
if not MODES[value] then return state.mode end
state.mode = value
return state.mode
end
local toggle = function( value )
if state.mode then return set( nil ) end
return set( value or 'window' )
end
local remember_origin = function()
local win = vim.api.nvim_get_current_win()
state.origin = {
window = win,
buffer = vim.api.nvim_win_get_buf( win ),
cursor = vim.api.nvim_win_get_cursor( win ),
}
end
--- @return integer? window
local ensure_window = function()
if state.window and vim.api.nvim_win_is_valid( state.window ) then return state.window end
local current = vim.api.nvim_get_current_win()
if not pcall( vim.cmd.vsplit ) then return current end
state.window = vim.api.nvim_get_current_win()
vim.api.nvim_set_current_win( current )
return state.window
end
--- @return integer? window
local ensure_tab = function()
if state.tabpage and vim.api.nvim_tabpage_is_valid( state.tabpage ) then
local wins = vim.api.nvim_tabpage_list_wins( state.tabpage )
if #wins > 0 then return wins[1] end
end
local current = vim.api.nvim_get_current_win()
if not pcall( vim.cmd.tabnew ) then return current end
state.tabpage = vim.api.nvim_get_current_tabpage()
local win = vim.api.nvim_get_current_win()
vim.api.nvim_set_current_win( current )
return win
end
local resolve_buffer = function( location )
if location.buffer and vim.api.nvim_buf_is_valid( location.buffer ) then return location.buffer end
if not location.path or location.path == '' then return nil end
return vim.fn.bufadd( location.path )
end
--- Follows a located agent event.
--- Following is passive: it never steals the user's working window and
--- never pollutes the jumplist.
local to = function( location )
if not state.mode then return nil end
if type( location ) ~= 'table' then return nil end
local bufnr = resolve_buffer( location )
if not bufnr then return nil end
vim.fn.bufload( bufnr )
if not state.origin then remember_origin() end
local target
if state.mode == 'current' then
target = vim.api.nvim_get_current_win()
elseif state.mode == 'tab' then
target = ensure_tab()
else
target = ensure_window()
end
if target == nil or not vim.api.nvim_win_is_valid( target ) then return nil end
local previous = vim.api.nvim_get_current_win()
vim.api.nvim_win_set_buf( target, bufnr )
local count = vim.api.nvim_buf_line_count( bufnr )
local line = math.min( math.max( 1, location.line or 1 ), count )
pcall( vim.api.nvim_win_set_cursor, target, { line, location.column or 0, } )
if state.mode ~= 'current' and vim.api.nvim_win_is_valid( previous ) then
vim.api.nvim_set_current_win( previous )
end
return target
end
--- Navigates to a location because the user asked, not because follow mode is on.
--- The same surfaces are reused, so there is only ever one window policy.
local visit = function( location )
local chosen = state.mode
if not chosen then state.mode = 'window' end
local target = to( location )
state.mode = chosen
return target
end
--- Returns to the location held before following began.
local back = function()
local origin = state.origin
state.origin = nil
if not origin then return false end
if not vim.api.nvim_win_is_valid( origin.window ) then return false end
vim.api.nvim_set_current_win( origin.window )
if vim.api.nvim_buf_is_valid( origin.buffer ) then
vim.api.nvim_win_set_buf( origin.window, origin.buffer )
pcall( vim.api.nvim_win_set_cursor, origin.window, origin.cursor )
end
return true
end
local reset = function()
if state.window and vim.api.nvim_win_is_valid( state.window ) then
pcall( vim.api.nvim_win_close, state.window, true )
end
if state.tabpage and vim.api.nvim_tabpage_is_valid( state.tabpage ) then
for _, win in ipairs( vim.api.nvim_tabpage_list_wins( state.tabpage ) ) do
pcall( vim.api.nvim_win_close, win, true )
end
end
state.mode = nil
state.window = nil
state.tabpage = nil
state.origin = nil
end
local window = function() return state.window end
return {
modes = MODES,
mode = mode,
set = set,
toggle = toggle,
to = to,
visit = visit,
back = back,
reset = reset,
window = window,
}