local lock_path = vim.fs.joinpath(vim.fn.stdpath('config'), 'paq-lock.json') local paq_removed_status = 3 local function clone_paq() local path = vim.fn.stdpath('data') .. '/site/pack/paqs/opt/paq-nvim' if not vim.uv.fs_stat(path) then local repo = 'https://github.com/savq/paq-nvim.git' local result = vim.system({ 'git', 'clone', '--depth=1', repo, path }):wait() if result.code == 0 then print('Package manager installed correctly') end end end local function load_paq() vim.cmd.packadd('paq-nvim') local paq = require('paq') local packages = require('setup.packages').get() paq:setup({ lock = lock_path })(packages) return paq end local function run_paq(event, operation) local done = false vim.api.nvim_create_autocmd('User', { pattern = event, once = true, callback = function() done = true end, }) operation() vim.wait(60000, function() return done end, 200) return done end local function install_packages() clone_paq() local paq = load_paq() if not run_paq('PaqDoneInstall', paq.install) then print('Paq installation timeout or failed') else print('Paq installation completed') end end local function assert_no_unlisted_package_dirs(paq) local expected_dirs = {} for _, filter in ipairs({ 'installed', 'to_install' }) do for _, pkg in ipairs(paq.query(filter)) do expected_dirs[pkg.dir] = true end end local stale_dirs = {} local paq_path = vim.fs.joinpath(vim.fn.stdpath('data'), 'site', 'pack', 'paqs') for _, packdir in ipairs({ 'start', 'opt' }) do local path = vim.fs.joinpath(paq_path, packdir) if vim.uv.fs_stat(path) then for name, type in vim.fs.dir(path) do local dir = vim.fs.joinpath(path, name) if type == 'directory' and not expected_dirs[dir] then table.insert(stale_dirs, dir) end end end end if #stale_dirs > 0 then error('Paq cleanup left unlisted package directories:\n' .. table.concat(stale_dirs, '\n')) end end local function prune_removed_lock_entries() if vim.fn.filereadable(lock_path) == 0 then return end local raw = table.concat(vim.fn.readfile(lock_path), '\n') local lock = vim.json.decode(raw) local changed = false for name, pkg in pairs(lock) do if pkg.status == paq_removed_status then lock[name] = nil changed = true end end if changed then vim.fn.writefile({ vim.json.encode(lock) }, lock_path) end end local function reconcile_packages() clone_paq() local paq = load_paq() if not run_paq('PaqDoneRemove', paq.clean) then print('Paq cleanup timeout or failed') return end assert_no_unlisted_package_dirs(paq) prune_removed_lock_entries() if not run_paq('PaqDoneInstall', paq.install) then print('Paq installation timeout or failed') else print('Paq packages reconciled') end end local function install_languages() vim.cmd.packadd('mason.nvim') require('mason').setup() local lm = require('plugins.language-manager') lm.invalidate_cache() lm.load_specs() lm.install() end local function sync_packages() clone_paq() local paq = load_paq() if not run_paq('PaqDoneSync', function() paq:sync() end) then print('Paq sync timeout or failed') else print('Paq sync completed') end end local function fetch_lsp_configs() local base_url = 'https://raw.githubusercontent.com/neovim/nvim-lspconfig/refs/heads/master/lsp/' local lm = require('plugins.language-manager') lm.invalidate_cache() local general = lm.load_specs() local lsp_dir = vim.fs.joinpath(vim.fn.stdpath('config'), 'lsp') vim.fn.mkdir(lsp_dir, 'p') for _, name in ipairs(general.language_servers or {}) do local file = vim.fs.joinpath(lsp_dir, name .. '.lua') if vim.fn.filereadable(file) == 0 then local url = base_url .. name .. '.lua' local cmd = string.format('curl -fsSL -o %q %q', file, url) vim.fn.system(cmd) if vim.v.shell_error ~= 0 then vim.notify('Failed to fetch ' .. name .. '.lua', vim.log.levels.ERROR) vim.fn.delete(file) else vim.notify('Fetched ' .. name .. '.lua', vim.log.levels.INFO) end else vim.notify('Skipped existing ' .. name .. '.lua', vim.log.levels.INFO) end end end local function create_command(name, callback, desc) vim.api.nvim_create_user_command(name, callback, { desc = desc }) end create_command('Setup', function() print('> Reconciling packages...') reconcile_packages() print('\n> Installing languages: treesitter parsers, language servers, linters, formatters...') install_languages() print('\n=== Setup Finished ===\n') end, 'Install packages and language tooling (first-time setup)') create_command('InstallPackages', function() print('> Installing packages...') install_packages() print('\n=== Package Install Finished ===\n') end, 'Install packages via Paq') create_command('InstallLanguages', function() print('> Installing languages: treesitter parsers, language servers, linters, formatters...') install_languages() print('\n=== Language Install Finished ===\n') end, 'Install treesitter, LSP, linter, and formatter tooling') create_command('Sync', sync_packages, 'Sync packages with Paq') create_command('FetchLspConfigs', fetch_lsp_configs, 'Fetch default LSP configs into lsp/')