Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

neovim/lua/bugabinga/pivi/navigate.lua

Raw
local entry = require 'bugabinga.pivi.entry'
local transcript = require 'bugabinga.pivi.transcript'
local follow = require 'bugabinga.pivi.follow'
local tool = require 'bugabinga.pivi.tool'

local matching = function( bufnr, kind )
  local found = {}
  for _, item in ipairs( entry.list( bufnr ) ) do
    if not kind or item.kind == kind then table.insert( found, item ) end
  end
  return found
end

--- The next entry strictly after a line, or the first one wrapping around.
local after = function( bufnr, line_number, kind )
  local items = matching( bufnr, kind )
  for _, item in ipairs( items ) do
    if item.first > line_number then return item end
  end
  return items[1]
end

--- The previous entry strictly before a line, or the last one wrapping around.
local before = function( bufnr, line_number, kind )
  local items = matching( bufnr, kind )
  local found
  for _, item in ipairs( items ) do
    if item.first < line_number then found = item end
  end
  return found or items[#items]
end

--- Moves the cursor to an entry, leaving the window and the buffer alone.
local go = function( item )
  if not item then return nil end
  pcall( vim.api.nvim_win_set_cursor, 0, { item.first, 0, } )
  return item
end

local next_entry = function( bufnr, kind )
  return go( after( bufnr, vim.api.nvim_win_get_cursor( 0 )[1], kind ) )
end

local previous_entry = function( bufnr, kind )
  return go( before( bufnr, vim.api.nvim_win_get_cursor( 0 )[1], kind ) )
end

--- The place an entry names, if it names one.
local location_of = function( item )
  if not item then return nil end
  local meta = item.meta or {}
  local args = type( meta.args ) == 'table' and meta.args or {}
  -- A result kept only in part still has somewhere it lives in full.
  local path = meta.path or args.path or args.file_path or args.filePath or tool.full_output( meta )
  if type( path ) ~= 'string' or path == '' then return nil end

  if meta.cwd and not vim.startswith( path, '/' ) and not path:match '^%a:' then
    path = vim.fs.normalize( meta.cwd .. '/' .. path )
  end

  -- An edit is worth reaching where it changed something, not at the top of a
  -- file the change may be nowhere near.
  local line = tool.first_changed_line( meta ) or meta.line or args.line or args.lnum
  return { path = path, line = line, }
end

--- Reaches the place the entry under the cursor names.
--- Navigation reuses follow mode's surfaces, so the working window is kept.
local locate = function( bufnr, line_number )
  local item = entry.at( bufnr, line_number or vim.api.nvim_win_get_cursor( 0 )[1] )
  local location = location_of( item )
  if not location then return nil end
  return follow.visit( location )
end

--- pivi publishes intent; the configuration chooses the keys.
local bind = function( bufnr )
  local motions = {
    { plug = '<Plug>(pivi-next-entry)', kind = nil, move = next_entry,
      description = 'pivi next entry', },
    { plug = '<Plug>(pivi-previous-entry)', kind = nil, move = previous_entry,
      description = 'pivi previous entry', },
    { plug = '<Plug>(pivi-next-tool)', kind = 'tool', move = next_entry,
      description = 'pivi next tool call', },
    { plug = '<Plug>(pivi-previous-tool)', kind = 'tool', move = previous_entry,
      description = 'pivi previous tool call', },
  }

  for _, motion in ipairs( motions ) do
    vim.keymap.set( 'n', motion.plug, function() motion.move( bufnr, motion.kind ) end,
      { buffer = bufnr, desc = motion.description, } )
  end

  vim.keymap.set( 'n', '<Plug>(pivi-goto-location)', function() locate( bufnr ) end,
    { buffer = bufnr, desc = 'pivi go to the location of the entry', } )
end

transcript.on_buffer( bind )

return {
  location_of = location_of,
  locate = locate,
  after = after,
  before = before,
  next_entry = next_entry,
  previous_entry = previous_entry,
  bind = bind,
}