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