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 = '(pivi-next-entry)', kind = nil, move = next_entry, description = 'pivi next entry', }, { plug = '(pivi-previous-entry)', kind = nil, move = previous_entry, description = 'pivi previous entry', }, { plug = '(pivi-next-tool)', kind = 'tool', move = next_entry, description = 'pivi next tool call', }, { 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', '(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, }