Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

neovim/lua/bugabinga/pivi/guide.lua

Raw
local transcript = require 'bugabinga.pivi.transcript'
local glyph = require 'std.icon'

local MODES = { 'n', 'i', 'x', }

--- What every published intent is for, by its name.
local intents = function( bufnr )
  local known = {}
  for _, mode in ipairs( MODES ) do
    for _, mapping in ipairs( vim.api.nvim_buf_get_keymap( bufnr, mode ) ) do
      local plug = mapping.lhs:match '^<Plug>%((pivi%-[%w%-]+)%)$'
      if plug then known[plug] = ( mapping.desc or plug ):gsub( '^pivi ', '' ) end
    end
  end
  return known
end

--- What the keys of a pivi buffer are for.
--- pivi chooses no keys, so the guide reports the ones the configuration bound
--- to pivi's intents. It describes what is actually reachable rather than what
--- anyone intended, and cannot drift from either.
local describe = function( bufnr )
  local known = intents( bufnr )
  local found = {}
  local seen = {}

  local collect = function( mappings )
    for _, mapping in ipairs( mappings ) do
      if not mapping.lhs:find( '<Plug>', 1, true ) then
        local target = ( mapping.rhs or '' ):match '<Plug>%((pivi%-[%w%-]+)%)'
        local what = target and known[target] or nil

        -- A key pivi binds itself, such as slash-command entry, is described
        -- by its own mapping rather than by an intent.
        if not what and ( mapping.desc or '' ):find( 'pivi', 1, true ) then
          what = mapping.desc:gsub( '^pivi ', '' )
        end

        if what and not seen[mapping.lhs] then
          seen[mapping.lhs] = true
          table.insert( found, { keys = mapping.lhs, what = what, } )
        end
      end
    end
  end

  for _, mode in ipairs( MODES ) do
    collect( vim.api.nvim_buf_get_keymap( bufnr, mode ) )
    collect( vim.api.nvim_get_keymap( mode ) )
  end

  table.sort( found, function( left, right ) return left.keys < right.keys end )
  return found
end

local commands = function()
  local ok, command = pcall( require, 'bugabinga.pivi.command' )
  if not ok then return {} end
  return command.names()
end

local lines = function( bufnr )
  local written = { '# ' .. glyph.help .. ' pivi', '', }

  local keys = describe( bufnr )
  if #keys > 0 then
    table.insert( written, '## ' .. glyph.keyword .. ' keys here' )
    table.insert( written, '' )
    for _, mapping in ipairs( keys ) do
      table.insert( written, ( '- `%s` %s' ):format( mapping.keys, mapping.what ) )
    end
    table.insert( written, '' )
  end

  local names = commands()
  if #names > 0 then
    table.insert( written, '## ' .. glyph.command .. ' commands' )
    table.insert( written, '' )
    for _, name in ipairs( names ) do
      table.insert( written, '- `:Pi ' .. name .. '`' )
    end
    table.insert( written, '' )
    table.insert( written, 'Bare `:Pi` opens the prompt.' )
    table.insert( written, '' )
  end

  if vim.bo[bufnr].filetype == 'pivi-input' then
    table.insert( written, '## ' .. glyph.insert .. ' while writing' )
    table.insert( written, '' )
    table.insert( written, 'The prompt opens in insert mode, so keys behind a leader need `<Esc>` first.' )
    table.insert( written, 'Set `vim.g.pivi_start_insert = false` to open in normal mode instead.' )
    table.insert( written, '' )
  end

  table.insert( written, '## ' .. glyph.list .. ' ordinary neovim' )
  table.insert( written, '' )
  table.insert( written, '- `za` `zo` `zc` collapse or expand the entry under the cursor' )
  table.insert( written, '- `zR` / `zM` expand or collapse every entry' )
  table.insert( written, '- `]c` / `[c` move between changes inside a comparison' )
  table.insert( written, '' )
  table.insert( written, 'Entries are real folds, so no key here replaces one you already know.' )

  return written
end

--- Shows the guide without leaving the buffer it describes.
--- @return number? bufnr
--- @return number? winid
local show = function( bufnr )
  bufnr = bufnr or vim.api.nvim_get_current_buf()
  if not vim.api.nvim_buf_is_valid( bufnr ) then return nil end

  local ok, float, window = pcall( vim.lsp.util.open_floating_preview, lines( bufnr ), 'markdown', {
    border = 'rounded',
    focus = false,
    focusable = true,
    max_width = 80,
    max_height = 30,
    title = ' pivi ',
  } )
  if not ok then return nil end

  if float and vim.api.nvim_buf_is_valid( float ) then
    pcall( vim.api.nvim_buf_set_name, float, 'pivi://guide' )
  end

  return float, window
end

local bind = function( bufnr )
  vim.keymap.set( 'n', '<Plug>(pivi-guide)', function() show( bufnr ) end,
    { buffer = bufnr, desc = 'pivi show this guide', } )
end

transcript.on_buffer( bind )

return {
  describe = describe,
  lines = lines,
  show = show,
  bind = bind,
}