Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

neovim/lua/bugabinga/pivi/runtime.lua

Raw
--- Locates the assets pivi ships and the address a child needs to reach back.
--- Everything here is additive: pivi adds its companion extension and its skill
--- to the user's own Pi, and never removes what the user configured.

local runtime_file = function( relative )
  local found = vim.api.nvim_get_runtime_file( relative, false )[1]
  if found then return vim.fs.normalize( found ) end
  return nil
end

local extension_path = function()
  return runtime_file 'lua/bugabinga/pivi/extension/index.ts'
end

local client_path = function()
  return runtime_file 'lua/bugabinga/pivi/extension/client.lua'
end

local skill_path = function()
  return runtime_file 'lua/bugabinga/pivi/skills/pivi/SKILL.md'
end

--- Directories a global npm install commonly uses.
--- A Neovim started from a desktop launcher inherits a smaller PATH than a
--- shell does, so PATH alone is not a reliable way to find Pi.
local install_directories = function()
  local home = vim.fs.normalize( vim.uv.os_homedir() or '' )
  local directories = {}

  -- Built without holes: a nil entry would end iteration early.
  local add = function( directory )
    if directory and directory ~= '' then
      table.insert( directories, vim.fs.normalize( directory ) )
    end
  end

  add( vim.env.PIVI_PI_DIR )
  add( vim.env.NPM_CONFIG_PREFIX )
  if vim.env.NPM_CONFIG_PREFIX then add( vim.env.NPM_CONFIG_PREFIX .. '/bin' ) end
  add( home .. '/.local/share/npm' )
  add( home .. '/.local/bin' )
  add( home .. '/AppData/Roaming/npm' )
  add( home .. '/.npm-global/bin' )
  add( home .. '/.bun/bin' )
  add '/usr/local/bin'
  add '/opt/homebrew/bin'

  return directories
end

local names = function()
  if vim.fn.has 'win32' == 1 then return { 'pi.cmd', 'pi.exe', 'pi.bat', 'pi', } end
  return { 'pi', }
end

local probe = function( candidate )
  if candidate == nil or candidate == '' then return nil end
  if vim.fn.executable( candidate ) == 1 then
    local resolved = vim.fn.exepath( candidate )
    return vim.fs.normalize( resolved ~= '' and resolved or candidate )
  end
  local stat = vim.uv.fs_stat( candidate )
  if stat and stat.type == 'file' then return vim.fs.normalize( candidate ) end
  return nil
end

--- Resolves the Pi executable to a real path.
--- A bare name is not enough: on Windows the launcher is a `.CMD` shim, and an
--- unresolved name reaches the process spawner as a missing file.
--- @return string? path
--- @return string? failure
local executable = function()
  local configured = vim.g.pivi_executable
  if configured then
    local found = probe( configured )
    if found then return found end
    return nil, 'pi executable not found: ' .. tostring( configured )
  end

  for _, name in ipairs( names() ) do
    local found = probe( name )
    if found then return found end
  end

  for _, directory in ipairs( install_directories() ) do
    if directory and directory ~= '' then
      for _, name in ipairs( names() ) do
        local found = probe( vim.fs.normalize( directory .. '/' .. name ) )
        if found then return found end
      end
    end
  end

  return nil, 'pi not found on PATH or in the usual install directories; set vim.g.pivi_executable'
end

--- Environment the companion extension needs to reach this Neovim instance.
local bridge_environment = function()
  local address = vim.v.servername
  if address == nil or address == '' then return nil end
  return {
    PIVI_NVIM_ADDRESS = address,
    PIVI_NVIM_EXE = vim.v.progpath,
    PIVI_NVIM_CLIENT = client_path(),
  }
end

return {
  file = runtime_file,
  install_directories = install_directories,
  executable = executable,
  extension_path = extension_path,
  client_path = client_path,
  skill_path = skill_path,
  bridge_environment = bridge_environment,
}