diff --git a/README.md b/README.md index 73e70a2..a614e9a 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ If `prettierd` is stuck, run `pkill prettierd` and re-run install commands. - `:Setup` - bootstraps Paq if needed - - installs plugins + - removes unlisted Paq plugins and installs missing plugins - installs treesitter parsers + Mason tools (LSP/linters/formatters) - `:InstallPackages` (included in `:Setup`) diff --git a/lsp/ruff.lua b/lsp/ruff.lua new file mode 100644 index 0000000..7fb61ea --- /dev/null +++ b/lsp/ruff.lua @@ -0,0 +1,35 @@ +---@brief +--- +--- https://github.com/astral-sh/ruff +--- +--- A Language Server Protocol implementation for Ruff, an extremely fast Python linter and code formatter, written in Rust. It can be installed via `pip`. +--- +--- ```sh +--- pip install ruff +--- ``` +--- +--- **Available in Ruff `v0.4.5` in beta and stabilized in Ruff `v0.5.3`.** +--- +--- This is the new built-in language server written in Rust. It supports the same feature set as `ruff-lsp`, but with superior performance and no installation required. Note that the `ruff-lsp` server will continue to be maintained until further notice. +--- +--- Server settings can be provided via: +--- +--- ```lua +--- vim.lsp.config('ruff', { +--- init_options = { +--- settings = { +--- -- Server settings should go here +--- } +--- } +--- }) +--- ``` +--- +--- Refer to the [documentation](https://docs.astral.sh/ruff/editors/) for more details. + +---@type vim.lsp.Config +return { + cmd = { 'ruff', 'server' }, + filetypes = { 'python' }, + root_markers = { 'pyproject.toml', 'ruff.toml', '.ruff.toml', '.git' }, + settings = {}, +} diff --git a/lsp/tailwindcss.lua b/lsp/tailwindcss.lua index 1d523b6..191161b 100644 --- a/lsp/tailwindcss.lua +++ b/lsp/tailwindcss.lua @@ -152,7 +152,7 @@ return { 'theme/static_src/tailwind.config.ts', 'theme/static_src/postcss.config.js', -- Fallback for tailwind v4, where tailwind.config.* is not required anymore - '.git', + -- '.git', } local fname = vim.api.nvim_buf_get_name(bufnr) root_files = utils.insert_package_json(root_files, 'tailwindcss', fname) diff --git a/lsp/yamlls.lua b/lsp/yamlls.lua index 2fdd736..abca481 100644 --- a/lsp/yamlls.lua +++ b/lsp/yamlls.lua @@ -69,7 +69,7 @@ return { redhat = { telemetry = { enabled = false } }, -- formatting disabled by default in yaml-language-server; enable it yaml = { - format = { enable = true }, + format = { enable = true, printWidth = 100 }, validate = false, -- Disable all schema validation }, }, diff --git a/lua/core/events.lua b/lua/core/events.lua index 5f1314b..efd4274 100644 --- a/lua/core/events.lua +++ b/lua/core/events.lua @@ -29,38 +29,50 @@ vim.api.nvim_create_user_command('MessagesToBuffer', function() vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) end, { desc = 'Open :messages output in a scratch buffer' }) -vim.api.nvim_create_user_command('CmdToBuffer', function(opts) - local cmd = table.concat(opts.fargs, ' ') +vim.api.nvim_create_user_command( + 'CmdToBuffer', + function(opts) + local cmd = table.concat(opts.fargs, ' ') - vim.g.__cmd_to_buffer_cmd = cmd - vim.g.__cmd_to_buffer_out = nil + vim.g.__cmd_to_buffer_cmd = cmd + vim.g.__cmd_to_buffer_out = nil - local exec_line = opts.bang and 'silent execute' or 'execute' - local ok, err = pcall(vim.cmd, string.format( - [[ + local exec_line = opts.bang and 'silent execute' or 'execute' + local ok, err = pcall( + vim.cmd, + string.format( + [[ redir => g:__cmd_to_buffer_out %s g:__cmd_to_buffer_cmd redir END ]], - exec_line - )) - if not ok then - pcall(vim.cmd, 'redir END') - error(err) - end + exec_line + ) + ) + if not ok then + pcall(vim.cmd, 'redir END') + error(err) + end - local output = vim.g.__cmd_to_buffer_out or '' - vim.g.__cmd_to_buffer_cmd = nil - vim.g.__cmd_to_buffer_out = nil - local lines = vim.split(output, '\n', { plain = true, trimempty = false }) + local output = vim.g.__cmd_to_buffer_out or '' + vim.g.__cmd_to_buffer_cmd = nil + vim.g.__cmd_to_buffer_out = nil + local lines = vim.split(output, '\n', { plain = true, trimempty = false }) - vim.cmd('new') - local bufnr = vim.api.nvim_get_current_buf() - vim.bo[bufnr].buftype = 'nofile' - vim.bo[bufnr].bufhidden = 'wipe' - vim.bo[bufnr].swapfile = false - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) -end, { nargs = '+', bang = true, complete = 'command', desc = 'Run an Ex command and open its output in a scratch buffer' }) + vim.cmd('new') + local bufnr = vim.api.nvim_get_current_buf() + vim.bo[bufnr].buftype = 'nofile' + vim.bo[bufnr].bufhidden = 'wipe' + vim.bo[bufnr].swapfile = false + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + end, + { + nargs = '+', + bang = true, + complete = 'command', + desc = 'Run an Ex command and open its output in a scratch buffer', + } +) -- Automatically create a scratch buffer if Neovim starts with no files au('VimEnter', { @@ -166,3 +178,9 @@ au('UIEnter', { end) end, }) + +vim.api.nvim_create_autocmd('VimResized', { + callback = function() + vim.opt.scroll = 20 + end, +}) diff --git a/lua/modules/language-specs.lua b/lua/modules/language-specs.lua index 428d916..d94f225 100644 --- a/lua/modules/language-specs.lua +++ b/lua/modules/language-specs.lua @@ -21,11 +21,17 @@ function M.get() -- ts = 'helm', -- lsp = 'helm-ls', -- }, + { + ft = 'python', + ts = 'python', + lsp = 'ruff', + format = { name = 'ruff_format', install = false }, + }, { ft = 'yaml', ts = 'yaml', lsp = 'yaml-language-server', - format = { 'prettierd', 'prettier' }, + -- format = { 'prettierd', 'prettier' }, }, { ts = { 'yaml', 'toml', 'sql', 'diff', 'dockerfile', 'gitcommit', 'gitignore' } }, { ts = { 'c', 'cpp', 'go', 'rust', 'python' } }, diff --git a/lua/plugins/language-manager.lua b/lua/plugins/language-manager.lua index c9ab9d9..10feecd 100644 --- a/lua/plugins/language-manager.lua +++ b/lua/plugins/language-manager.lua @@ -181,10 +181,20 @@ function M.generate_specs(specs_raw) specs.add(resolved_lsps, 'language_servers') install_spec.add(spec.lint, 'code_tools') - install_spec.add(spec.format, 'code_tools') for _, ft in ipairs(wrap(spec.ft)) do specs.add(spec.lint, 'linters_by_ft', ft) - specs.add(spec.format, 'formatters_by_ft', ft) + end + + for _, raw in ipairs(wrap(spec.format)) do + local f = type(raw) == 'table' and { name = raw.name, install = raw.install } + or { name = raw, install = raw } + + if f.install then + install_spec.add(f.install, 'code_tools') + end + for _, ft in ipairs(wrap(spec.ft)) do + specs.add(f.name, 'formatters_by_ft', ft) + end end end diff --git a/lua/setup/init.lua b/lua/setup/init.lua index 9f97198..87950e4 100644 --- a/lua/setup/init.lua +++ b/lua/setup/init.lua @@ -1,4 +1,5 @@ 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' @@ -19,32 +20,102 @@ local function load_paq() return paq end -local function install_packages() - clone_paq() - +local function run_paq(event, operation) local done = false vim.api.nvim_create_autocmd('User', { - pattern = 'PaqDoneInstall', + pattern = event, once = true, callback = function() done = true end, }) - local paq = load_paq() - paq.install() + operation() vim.wait(60000, function() return done end, 200) - if not done then + 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() @@ -58,7 +129,13 @@ end local function sync_packages() clone_paq() local paq = load_paq() - paq:sync() + 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() @@ -94,8 +171,8 @@ local function create_command(name, callback, desc) end create_command('Setup', function() - print('> Installing packages...') - install_packages() + print('> Reconciling packages...') + reconcile_packages() print('\n> Installing languages: treesitter parsers, language servers, linters, formatters...') install_languages() print('\n=== Setup Finished ===\n') diff --git a/paq-lock.json b/paq-lock.json index 85ab70c..0223ba0 100644 --- a/paq-lock.json +++ b/paq-lock.json @@ -1 +1 @@ -{"nvim-autopairs":{"name":"nvim-autopairs","dir":"/home/tomas/.local/share/nvim/site/pack/paqs/start/nvim-autopairs","status":0,"url":"https://github.com/windwp/nvim-autopairs","hash":"59bce2eef357189c3305e25bc6dd2d138c1683f5"},"nvim-tree.lua":{"name":"nvim-tree.lua","dir":"/home/tomas/.local/share/nvim/site/pack/paqs/start/nvim-tree.lua","status":0,"url":"https://gitea.tomastm.com/tomas.mirchev/nvim-tree.lua","hash":"7c0f7e906ab6f11b61eec52171eaf7dc06726ef1"},"nvim-treesitter":{"name":"nvim-treesitter","dir":"/home/tomas/.local/share/nvim/site/pack/paqs/start/nvim-treesitter","status":0,"url":"https://github.com/nvim-treesitter/nvim-treesitter","hash":"42fc28ba918343ebfd5565147a42a26580579482"},"nvim-lint":{"name":"nvim-lint","dir":"/home/tomas/.local/share/nvim/site/pack/paqs/start/nvim-lint","status":0,"url":"https://github.com/mfussenegger/nvim-lint","hash":"bcd1a44edbea8cd473af7e7582d3f7ffc60d8e81"},"paq-nvim":{"name":"paq-nvim","dir":"/home/tomas/.local/share/nvim/site/pack/paqs/opt/paq-nvim","status":0,"url":"https://github.com/savq/paq-nvim.git","hash":"971344d1fe1fd93580961815e7b7c8853c3605e4"},"invero.nvim":{"name":"invero.nvim","dir":"/home/tomas/.local/share/nvim/site/pack/paqs/start/invero.nvim","status":0,"url":"https://github.com/triimd/invero.nvim","hash":"1a1761f4f4444d3b9b9e87308a33c0b043e209d3"},"mason.nvim":{"name":"mason.nvim","dir":"/home/tomas/.local/share/nvim/site/pack/paqs/opt/mason.nvim","status":0,"url":"https://github.com/mason-org/mason.nvim","hash":"44d1e90e1f66e077268191e3ee9d2ac97cc18e65"},"vim-fugitive":{"name":"vim-fugitive","dir":"/home/tomas/.local/share/nvim/site/pack/paqs/start/vim-fugitive","status":1,"url":"https://github.com/tpope/vim-fugitive","hash":""},"nvim-ts-autotag":{"name":"nvim-ts-autotag","dir":"/home/tomas/.local/share/nvim/site/pack/paqs/start/nvim-ts-autotag","status":0,"url":"https://github.com/windwp/nvim-ts-autotag","hash":"8e1c0a389f20bf7f5b0dd0e00306c1247bda2595"},"conform.nvim":{"name":"conform.nvim","dir":"/home/tomas/.local/share/nvim/site/pack/paqs/start/conform.nvim","status":0,"url":"https://github.com/stevearc/conform.nvim","hash":"c2526f1cde528a66e086ab1668e996d162c75f4f"}} \ No newline at end of file +{"invero.nvim":{"status":0,"dir":"/home/tomas/.local/share/nvim/site/pack/paqs/start/invero.nvim","url":"https://github.com/triimd/invero.nvim","name":"invero.nvim","hash":"1a1761f4f4444d3b9b9e87308a33c0b043e209d3"},"nvim-autopairs":{"status":0,"dir":"/home/tomas/.local/share/nvim/site/pack/paqs/start/nvim-autopairs","url":"https://github.com/windwp/nvim-autopairs","name":"nvim-autopairs","hash":"59bce2eef357189c3305e25bc6dd2d138c1683f5"},"nvim-tree.lua":{"status":0,"dir":"/home/tomas/.local/share/nvim/site/pack/paqs/start/nvim-tree.lua","url":"https://gitea.tomastm.com/tomas.mirchev/nvim-tree.lua","name":"nvim-tree.lua","hash":"7c0f7e906ab6f11b61eec52171eaf7dc06726ef1"},"paq-nvim":{"status":0,"dir":"/home/tomas/.local/share/nvim/site/pack/paqs/opt/paq-nvim","url":"https://github.com/savq/paq-nvim.git","name":"paq-nvim","hash":"971344d1fe1fd93580961815e7b7c8853c3605e4"},"nvim-treesitter":{"status":0,"dir":"/home/tomas/.local/share/nvim/site/pack/paqs/start/nvim-treesitter","url":"https://github.com/nvim-treesitter/nvim-treesitter","name":"nvim-treesitter","hash":"42fc28ba918343ebfd5565147a42a26580579482"},"mason.nvim":{"status":0,"dir":"/home/tomas/.local/share/nvim/site/pack/paqs/opt/mason.nvim","url":"https://github.com/mason-org/mason.nvim","name":"mason.nvim","hash":"44d1e90e1f66e077268191e3ee9d2ac97cc18e65"},"vim-fugitive":{"status":0,"dir":"/home/tomas/.local/share/nvim/site/pack/paqs/start/vim-fugitive","url":"https://github.com/tpope/vim-fugitive","name":"vim-fugitive","hash":"61b51c09b7c9ce04e821f6cf76ea4f6f903e3cf4"},"conform.nvim":{"status":0,"dir":"/home/tomas/.local/share/nvim/site/pack/paqs/start/conform.nvim","url":"https://github.com/stevearc/conform.nvim","name":"conform.nvim","hash":"c2526f1cde528a66e086ab1668e996d162c75f4f"},"nvim-lint":{"status":0,"dir":"/home/tomas/.local/share/nvim/site/pack/paqs/start/nvim-lint","url":"https://github.com/mfussenegger/nvim-lint","name":"nvim-lint","hash":"bcd1a44edbea8cd473af7e7582d3f7ffc60d8e81"},"nvim-ts-autotag":{"status":0,"dir":"/home/tomas/.local/share/nvim/site/pack/paqs/start/nvim-ts-autotag","url":"https://github.com/windwp/nvim-ts-autotag","name":"nvim-ts-autotag","hash":"8e1c0a389f20bf7f5b0dd0e00306c1247bda2595"}} diff --git a/skills-lock.json b/skills-lock.json new file mode 100644 index 0000000..49270d3 --- /dev/null +++ b/skills-lock.json @@ -0,0 +1,47 @@ +{ + "version": 1, + "skills": { + "cavecrew": { + "source": "JuliusBrussee/caveman", + "sourceType": "github", + "skillPath": "skills/cavecrew/SKILL.md", + "computedHash": "505d836228d1c5e14834ff5d62aad72390c7d27f79c6aa7f9a7a55ed6606d6a2" + }, + "caveman": { + "source": "JuliusBrussee/caveman", + "sourceType": "github", + "skillPath": "skills/caveman/SKILL.md", + "computedHash": "dfbf85749fd474feeb0bbe60c779795ecd5dbec0083299b56e68916bc3ddd8c9" + }, + "caveman-commit": { + "source": "JuliusBrussee/caveman", + "sourceType": "github", + "skillPath": "skills/caveman-commit/SKILL.md", + "computedHash": "f456ea0564875e46858890ac39ee701ecb9c601c72a2da1e6ce6bd5f9fc5d817" + }, + "caveman-compress": { + "source": "JuliusBrussee/caveman", + "sourceType": "github", + "skillPath": "skills/caveman-compress/SKILL.md", + "computedHash": "f61268bc137909b25053b018de45d90afc3a856569088a9ef9e1d0fdc8147b35" + }, + "caveman-help": { + "source": "JuliusBrussee/caveman", + "sourceType": "github", + "skillPath": "skills/caveman-help/SKILL.md", + "computedHash": "2c21437427e98df1eacabaa0b055b5256a308b2191feea3ca2df6a9418e76cb1" + }, + "caveman-review": { + "source": "JuliusBrussee/caveman", + "sourceType": "github", + "skillPath": "skills/caveman-review/SKILL.md", + "computedHash": "fb7214a1c5793bae6ba8b1be4329e2e6f40dbec6dd911dfb335ad29f09c316a1" + }, + "caveman-stats": { + "source": "JuliusBrussee/caveman", + "sourceType": "github", + "skillPath": "skills/caveman-stats/SKILL.md", + "computedHash": "47ce2de3d6cb39a75047b5c962e4eb3da15594e7397c94103e9a104d42626553" + } + } +}