feat: automated setup works finally

This commit is contained in:
Tomas Mirchev 2025-10-26 02:53:13 +03:00
parent 2692b95109
commit cd5025859c
7 changed files with 135 additions and 168 deletions

View File

@ -1,5 +1,4 @@
if #vim.api.nvim_list_uis() == 0 then if #vim.api.nvim_list_uis() == 0 then
print('--> Running neovim in headless mode')
require('setup') require('setup')
return return
end end

View File

@ -94,7 +94,7 @@ au('InsertEnter', {
end, end,
}) })
require('modules.theme') require('modules.theme')
au('UIEnter', { au('UIEnter', {
group = group, group = group,
once = true, once = true,
@ -102,6 +102,7 @@ au('UIEnter', {
vim.schedule(function() vim.schedule(function()
require('modules.navigation') require('modules.navigation')
require('plugins.language-manager').setup() require('plugins.language-manager').setup()
require('modules.diagnostics')
end) end)
end, end,
}) })

View File

@ -1,6 +1,7 @@
local utils = require('utils')
local M = { local M = {
lsp = {},
ts = {}, ts = {},
lsp = {},
lint = {}, lint = {},
format = {}, format = {},
} }
@ -138,14 +139,20 @@ end
-- ======== Spec Generation ======== -- ======== Spec Generation ========
local function get_mason_registry()
vim.cmd.packadd('mason.nvim')
require('mason').setup()
local registry = require('mason-registry')
registry.refresh()
return registry
end
function M.generate_specs(specs_raw) function M.generate_specs(specs_raw)
local install_spec = create_spec() local install_spec = create_spec()
local specs = create_spec() local specs = create_spec()
local registry = get_mason_registry()
-- ensure Mason is available
vim.cmd.packadd('mason.nvim')
require('mason').setup()
local registry = require('mason-registry')
local lsp_map = {} local lsp_map = {}
@ -157,17 +164,21 @@ function M.generate_specs(specs_raw)
spec.ts = spec.ts or spec.ft spec.ts = spec.ts or spec.ft
specs.add(spec.ts, 'ts_parsers') specs.add(spec.ts, 'ts_parsers')
-- resolve LSP name using Mason registry install_spec.add(spec.lsp, 'code_tools')
install_spec.add(spec.lsp, 'language_servers')
local resolved_lsps = {} local resolved_lsps = {}
for _, lsp in ipairs(wrap(spec.lsp)) do for _, lsp_name in ipairs(wrap(spec.lsp)) do
if registry.has_package(lsp) then if registry and registry.has_package(lsp_name) then
local pkg = registry.get_package(lsp) local pkg = registry.get_package(lsp_name)
local lspconfig = pkg.spec and pkg.spec.neovim and pkg.spec.neovim.lspconfig or lsp if pkg.spec and pkg.spec.neovim and pkg.spec.neovim.lspconfig then
table.insert(resolved_lsps, lspconfig) local lspconfig = pkg.spec and pkg.spec.neovim and pkg.spec.neovim.lspconfig
lsp_map[lspconfig] = lsp lsp_name = lspconfig
lsp_map[lspconfig] = lsp_name
table.insert(resolved_lsps, lsp_name)
else else
vim.notify('Unknown LSP: ' .. lsp, vim.log.levels.WARN) print('Package found but not lsp name: ' .. lsp_name)
end
else
print('Package not found: ' .. lsp_name)
end end
end end
specs.add(resolved_lsps, 'language_servers') specs.add(resolved_lsps, 'language_servers')
@ -185,19 +196,7 @@ function M.generate_specs(specs_raw)
return result, install_spec.get() return result, install_spec.get()
end end
-- ======== Cache Interface ======== -- ======== Cache ========
function M.load_or_generate(specs_raw)
local hash = hash_spec(specs_raw)
local cache = load_cache()
if cache and cache.hash == hash then
M.general = cache.spec
else
M.general, M.mason = M.generate_specs(specs_raw)
save_cache({ hash = hash, spec = M.general })
end
return M.general, M.mason
end
function M.load_specs() function M.load_specs()
local cache = load_cache() local cache = load_cache()
@ -205,9 +204,8 @@ function M.load_specs()
M.general = cache.spec M.general = cache.spec
else else
local specs_raw = require('modules.language-specs').get() local specs_raw = require('modules.language-specs').get()
local hash = hash_spec(specs_raw)
M.general, M.mason = M.generate_specs(specs_raw) M.general, M.mason = M.generate_specs(specs_raw)
save_cache({ hash = hash, spec = M.general }) save_cache({ hash = hash_spec(specs_raw), spec = M.general })
end end
return M.general, M.mason return M.general, M.mason
end end
@ -223,103 +221,53 @@ function M.invalidate_cache()
M.mason = nil M.mason = nil
end end
-- ======== Setup Functions ========
local function on_ts_installed(parsers, cb)
local info = require('nvim-treesitter.info')
local timer = vim.loop.new_timer()
timer:start(0, 500, vim.schedule_wrap(function()
for _, lang in ipairs(parsers) do
if not vim.tbl_contains(info.installed_parsers(), lang) then
return
end
end
timer:stop()
timer:close()
cb()
end))
end
function M.ts.install() function M.ts.install()
local install = require('nvim-treesitter.install') vim.cmd.packadd('nvim-treesitter')
local parsers = M.general.ts_parsers or {} local ts_install = require('nvim-treesitter.install').ensure_installed_sync
install.ensure_installed(parsers) -- async ts_install(M.general.ts_parsers)
return parsers
end
-- ======== Orchestration ========
function M.install()
local parsers = M.ts.install()
on_ts_installed(parsers, function()
M.mason_install()
end)
end
local function ensure_registry_ready()
local registry = require('mason-registry')
if not registry.is_installed() then
registry.update()
vim.wait(10000, function()
return registry.is_installed()
end, 200)
end
return registry
end end
function M.mason_install() function M.mason_install()
vim.cmd.packadd('mason.nvim') local packages = M.mason.code_tools
require('mason').setup() local registry = get_mason_registry()
local registry = ensure_registry_ready()
local list = vim.list_extend(M.mason.language_servers, M.mason.code_tools)
print(vim.inspect(list))
local function install_and_wait(name) local pending = #packages
local result = utils.await(function(resolve)
for _, name in ipairs(packages) do
print('Mason package installing: ' .. name)
local pkg = registry.get_package(name) local pkg = registry.get_package(name)
if pkg:is_installed() then pkg:install({}, function(success, error)
vim.notify('Already installed ' .. name, vim.log.levels.INFO) if success then
return print('Mason package installed: ' .. name)
else
print('Mason package failed: ' .. name)
print(' > Error: ' .. vim.inspect(error))
end end
vim.notify('Installing ' .. name, vim.log.levels.INFO) pending = pending - 1
local done = false if pending == 0 then
pkg:install():once('closed', function() resolve(true)
done = true end
end) end)
local ok = vim.wait(60 * 60 * 1000, function()
return done or pkg:is_installed()
end, 200)
if not ok or not pkg:is_installed() then
vim.notify('Install failed: ' .. name, vim.log.levels.ERROR)
else
vim.notify('Installed ' .. name, vim.log.levels.INFO)
end
end end
end, 5 * 60 * 1000, 200)
for _, name in ipairs(list) do if not result.ok or pending ~= 0 then
if registry.has_package(name) then print('\n!! >> Exited timeout, possible clean up needed!')
install_and_wait(name) print(' > status: ' .. result.ok)
else print(' > pending: ' .. pending)
vim.notify('Package not found in registry: ' .. name, vim.log.levels.WARN)
end end
end
-- force event loop to process any pending Mason async teardown
local settled = false
vim.defer_fn(function()
settled = true
end, 3000)
vim.wait(10 * 1000, function()
return settled
end, 100)
end end
function M.lsp.setup() -- ======== Public API ========
for _, lsp_name in ipairs(M.general.language_servers or {}) do
vim.lsp.enable(lsp_name) function M.install()
end print('\n> Starting ts parsers install')
M.ts.install()
print('\n> Starting mason install: lsp, lint, format')
M.mason_install()
end end
function M.ts.setup() function M.ts.setup()
@ -329,12 +277,18 @@ function M.ts.setup()
}) })
end end
function M.lsp.setup()
for _, lsp_name in ipairs((M.general and M.general.language_servers) or {}) do
vim.lsp.enable(lsp_name)
end
end
function M.lint.setup() function M.lint.setup()
vim.api.nvim_create_autocmd({ 'BufReadPre', 'BufNewFile' }, { vim.api.nvim_create_autocmd({ 'BufReadPre', 'BufNewFile' }, {
group = M.group, group = M.group,
callback = function() callback = function()
local lint = require('lint') local lint = require('lint')
lint.linters_by_ft = M.general.linters_by_ft or {} lint.linters_by_ft = (M.general and M.general.linters_by_ft) or {}
vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWritePost', 'InsertLeave' }, { vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWritePost', 'InsertLeave' }, {
group = vim.api.nvim_create_augroup('language-manager.lint', { clear = true }), group = vim.api.nvim_create_augroup('language-manager.lint', { clear = true }),
callback = function() callback = function()
@ -353,7 +307,7 @@ function M.format.setup()
require('conform').setup({ require('conform').setup({
format_on_save = { timeout_ms = 500, lsp_format = 'fallback' }, format_on_save = { timeout_ms = 500, lsp_format = 'fallback' },
default_format_opts = { stop_after_first = true }, default_format_opts = { stop_after_first = true },
formatters_by_ft = M.general.formatters_by_ft or {}, formatters_by_ft = (M.general and M.general.formatters_by_ft) or {},
}) })
end, end,
}) })

View File

@ -1,53 +1,52 @@
local M = {} local function clone_package_manager()
function M.install_pm()
local path = vim.fn.stdpath('data') .. '/site/pack/paqs/opt/paq-nvim' local path = vim.fn.stdpath('data') .. '/site/pack/paqs/opt/paq-nvim'
if not vim.uv.fs_stat(path) then if not vim.uv.fs_stat(path) then
local repo = 'https://github.com/savq/paq-nvim.git' local repo = 'https://github.com/savq/paq-nvim.git'
local cmd = { 'git', 'clone', '--depth=1', repo, path } local cmd = { 'git', 'clone', '--depth=1', repo, path }
vim.system(cmd):wait() local result = vim.system(cmd):wait()
print("Installed: paq") if result.code == 0 then
print('Package manager installed correctly')
end
end end
end end
function M.install_packages() local function install_packages()
print("Installing packages...")
vim.cmd.packadd('paq-nvim')
local packages = require('setup.packages').get()
local paq = require('paq')
paq(packages)
-- capture list of jobs before starting
local install_list = paq.install()
-- if nothing to install, skip wait
if not install_list or vim.tbl_isempty(install_list) then
vim.cmd('packloadall')
vim.cmd('silent! helptags ALL')
return
end
local done = false local done = false
vim.api.nvim_create_autocmd('User', { vim.api.nvim_create_autocmd('User', {
pattern = 'PaqDoneInstall', pattern = 'PaqDoneInstall',
once = true, once = true,
callback = function() callback = function()
vim.cmd('packloadall')
vim.cmd('silent! helptags ALL')
done = true done = true
end, end,
}) })
-- wait until done or timeout vim.cmd.packadd('paq-nvim')
vim.wait(300000, function() local paq = require('paq')
local packages = require('setup.packages').get()
paq(packages)
paq.install()
local to_install = paq.query('to_install')
if #to_install == 0 then
return
end
vim.wait(60000, function()
return done return done
end, 200) end, 200)
if not done then
print('Paq installation timeout or failed')
else
print('Paq installation completed')
end
end end
local function install_languages()
vim.cmd.packadd('mason.nvim')
require('mason').setup()
function M.install_languages()
local lm = require('plugins.language-manager') local lm = require('plugins.language-manager')
lm.invalidate_cache() lm.invalidate_cache()
lm.load_specs() lm.load_specs()
@ -55,9 +54,13 @@ function M.install_languages()
end end
vim.api.nvim_create_user_command('InstallAll', function() vim.api.nvim_create_user_command('InstallAll', function()
M.install_pm() print('> Starting clone package manager...')
M.install_packages() clone_package_manager()
M.install_languages() print('\n> Starting installing packages...')
install_packages()
print('\n> Starting installing languages: ts parsers, language servers, linters, formatters...')
install_languages()
print('\n=== Install Finished ===\n\n')
end, {}) end, {})
vim.api.nvim_create_user_command('FetchLspConfigs', function() vim.api.nvim_create_user_command('FetchLspConfigs', function()
@ -70,7 +73,7 @@ vim.api.nvim_create_user_command('FetchLspConfigs', function()
local lsp_dir = vim.fs.joinpath(vim.fn.getcwd(), 'lsp') local lsp_dir = vim.fs.joinpath(vim.fn.getcwd(), 'lsp')
vim.fn.mkdir(lsp_dir, 'p') vim.fn.mkdir(lsp_dir, 'p')
for _, name in ipairs(general.language_servers) do for _, name in ipairs(general.language_servers or {}) do
local file = vim.fs.joinpath(lsp_dir, name .. '.lua') local file = vim.fs.joinpath(lsp_dir, name .. '.lua')
if vim.fn.filereadable(file) == 0 then if vim.fn.filereadable(file) == 0 then
local url = base_url .. name .. '.lua' local url = base_url .. name .. '.lua'
@ -87,5 +90,3 @@ vim.api.nvim_create_user_command('FetchLspConfigs', function()
end end
end end
end, { desc = 'Fetch default LSP configs into ./lsp in cwd' }) end, { desc = 'Fetch default LSP configs into ./lsp in cwd' })
return M

24
lua/utils.lua Normal file
View File

@ -0,0 +1,24 @@
-- utils.lua
local M = {}
function M.await(fn, timeout, interval)
local done = false
local ok, data
-- Wrap resolve in vim.schedule so it runs on main loop
fn(function(success, result)
vim.schedule(function()
done = true
ok = success
data = result
end)
end)
vim.wait(timeout, function()
return done
end, interval)
return { ok = ok or false, data = data }
end
return M

View File

@ -1,7 +0,0 @@
{
"plugins": {
"nvim-tree.lua": {
"version": "'master'"
}
}
}

View File

@ -1,5 +0,0 @@
#!/usr/bin/env bash
nvim --headless '+lua =require("setup").install_pm()' +qa
nvim --headless '+lua =require("setup").install_packages()' +qa
nvim --headless '+lua =require("setup").install_languages()' +qa