Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

neovim/lua/bugabinga/pivi/follow.lua

Raw
local 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,
}