diff --git a/lua/custom/plugins/cmp.lua b/lua/custom/plugins/cmp.lua index 0b1c417..3f80291 100755 --- a/lua/custom/plugins/cmp.lua +++ b/lua/custom/plugins/cmp.lua @@ -2,111 +2,94 @@ return { 'hrsh7th/nvim-cmp', event = { 'InsertEnter', 'CmdlineEnter' }, dependencies = { - -- Core snippet engine and integration - { 'L3MON4D3/LuaSnip' }, - { 'saadparwaiz1/cmp_luasnip' }, - - -- LSP support - { 'hrsh7th/cmp-nvim-lsp' }, - - -- Predefined snippets, lazy-loaded - { 'rafamadriz/friendly-snippets' }, - - -- Additional sources - { 'hrsh7th/cmp-path' }, - { 'hrsh7th/cmp-buffer' }, -- Will be filtered later - - -- Cmdline & git - { 'hrsh7th/cmp-cmdline' }, - { 'petertriho/cmp-git', ft = { 'gitcommit' } }, - - -- UI enhancement - { 'onsails/lspkind-nvim' }, + 'hrsh7th/cmp-nvim-lsp', + 'hrsh7th/cmp-buffer', + 'hrsh7th/cmp-path', + 'hrsh7th/cmp-cmdline', + 'petertriho/cmp-git', + 'onsails/lspkind-nvim', + 'L3MON4D3/LuaSnip', + 'saadparwaiz1/cmp_luasnip', }, + config = function() local cmp = require('cmp') - local luasnip = require('luasnip') local lspkind = require('lspkind') - - -- Load only selected snippets - require('luasnip.loaders.from_vscode').lazy_load({}) - - vim.opt.completeopt = { 'menu', 'menuone', 'noselect' } - - luasnip.config.setup({ - history = true, - updateevents = 'TextChanged,TextChangedI', - }) - - -- Precompute mappings for insert and command mode - local insert_mappings = { - [''] = cmp.mapping.select_next_item(), - [''] = cmp.mapping.select_prev_item(), - [''] = cmp.mapping.scroll_docs(-4), - [''] = cmp.mapping.scroll_docs(4), - [''] = cmp.mapping(function(fallback) - if cmp.visible() then - cmp.select_next_item() - elseif luasnip.expand_or_jumpable() then - luasnip.expand_or_jump() - else - fallback() - end - end, { 'i', 's', 'c' }), - [''] = cmp.mapping(function(fallback) - if cmp.visible() then - cmp.select_prev_item() - elseif luasnip.jumpable(-1) then - luasnip.jump(-1) - else - fallback() - end - end, { 'i', 's', 'c' }), - [''] = cmp.mapping.confirm({ select = false }), - } - - local insert_sources = { - { name = 'nvim_lsp', priority = 1000 }, - { name = 'luasnip', priority = 900 }, - { name = 'path', priority = 750 }, - { name = 'buffer', priority = 500, keyword_length = 5, max_item_count = 50 }, - } + -- NO luasnip require here cmp.setup({ snippet = { expand = function(args) - luasnip.lsp_expand(args.body) + require('luasnip').lsp_expand(args.body) -- lazy, only on snippet expand end, }, - mapping = insert_mappings, - sources = insert_sources, + mapping = cmp.mapping.preset.insert({ + [''] = cmp.mapping.select_next_item(), + [''] = cmp.mapping.select_prev_item(), + [''] = cmp.mapping.scroll_docs(-4), + [''] = cmp.mapping.scroll_docs(4), + [''] = cmp.mapping.confirm({ select = false }), + [''] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_next_item() + else + local luasnip = require('luasnip') -- lazy, only on keypress + if luasnip.expand_or_jumpable() then + luasnip.expand_or_jump() + else + fallback() + end + end + end, { 'i', 's' }), + [''] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_prev_item() + else + local luasnip = require('luasnip') -- lazy, only on keypress + if luasnip.jumpable(-1) then + luasnip.jump(-1) + else + fallback() + end + end + end, { 'i', 's' }), + }), + sources = { + { name = 'nvim_lsp', priority = 1000 }, + { name = 'luasnip', priority = 900 }, + { name = 'path', priority = 750 }, + { + name = 'buffer', + priority = 500, + keyword_length = 5, + max_item_count = 50, + option = { + get_bufnrs = function() + return { vim.api.nvim_get_current_buf() } -- only index current buffer, not all open buffers + end, + }, + }, + }, formatting = { fields = { 'abbr', 'kind', 'menu' }, expandable_indicator = false, - format = function(entry, vim_item) - if entry.source.name == 'nvim_lsp' then - return lspkind.cmp_format({ - mode = 'symbol_text', - maxwidth = 50, - ellipsis_char = '...', - menu = { - nvim_lsp = '[LSP]', - luasnip = '[Snip]', - buffer = '[Buffer]', - path = '[Path]', - }, - })(entry, vim_item) - end - return vim_item - end, + format = lspkind.cmp_format({ + mode = 'symbol_text', + maxwidth = 50, + ellipsis_char = '...', + menu = { + nvim_lsp = '[LSP]', + luasnip = '[Snip]', + buffer = '[Buffer]', + path = '[Path]', + }, + }), }, window = { completion = cmp.config.window.bordered(), documentation = cmp.config.window.bordered(), }, performance = { - filtering_context_budget = 5, - async_budget = 5, confirm_resolve_timeout = 100, max_view_entries = 20, debounce = 60, @@ -115,15 +98,29 @@ return { }, }) - -- Cmdline setup - cmp.setup.cmdline({ '/', '?' }, { - mapping = insert_mappings, - sources = { { name = 'buffer', keyword_length = 5 } }, + cmp.setup.filetype('gitcommit', { -- only once + sources = { + { name = 'git', priority = 900 }, + { name = 'buffer', keyword_length = 5 }, + }, }) + local cmdline_mapping = cmp.mapping.preset.cmdline({ + [''] = { c = cmp.mapping.select_next_item() }, + [''] = { c = cmp.mapping.select_prev_item() }, + [''] = { c = cmp.mapping.select_next_item() }, + [''] = { c = cmp.mapping.select_prev_item() }, + }) + cmp.setup.cmdline({ '/', '?' }, { + mapping = cmdline_mapping, + sources = { { name = 'buffer', keyword_length = 5 } }, + }) cmp.setup.cmdline(':', { - mapping = insert_mappings, - sources = { { name = 'path' }, { name = 'cmdline' } }, + mapping = cmdline_mapping, + sources = { + { name = 'path' }, + { name = 'cmdline' }, + }, window = { documentation = cmp.config.window.bordered({ winhighlight = 'Normal:Normal,FloatBorder:FloatBorder,CursorLine:Visual,Search:None', @@ -131,13 +128,5 @@ return { }), }, }) - - -- Git commit - cmp.setup.filetype('gitcommit', { - sources = { - { name = 'git', priority = 900 }, - { name = 'buffer', keyword_length = 5 }, - }, - }) end, } diff --git a/lua/custom/plugins/lsp-config.lua b/lua/custom/plugins/lsp-config.lua index 4745b49..8b94af8 100755 --- a/lua/custom/plugins/lsp-config.lua +++ b/lua/custom/plugins/lsp-config.lua @@ -2,45 +2,75 @@ return { 'neovim/nvim-lspconfig', event = { 'BufReadPre', 'BufNewFile' }, dependencies = { - -- Mason core - { 'williamboman/mason.nvim', build = ':MasonUpdate', cmd = 'Mason', config = true }, + { + 'williamboman/mason.nvim', + build = ':MasonUpdate', + cmd = 'Mason', + config = true, + }, 'williamboman/mason-lspconfig.nvim', 'WhoIsSethDaniel/mason-tool-installer.nvim', - -- Lua dev - { 'folke/neodev.nvim', ft = 'lua', config = true }, - - -- LSP UI & UX - { 'j-hui/fidget.nvim', opts = {}, event = 'LspAttach' }, - { 'ray-x/lsp_signature.nvim', event = 'LspAttach' }, - - -- Python venv selector + -- lazydev replaces neodev { - 'linux-cultist/venv-selector.nvim', - ft = 'python', - opts = { auto_activate = true }, - config = true, + 'folke/lazydev.nvim', + ft = 'lua', + opts = { library = { { path = '${3rd}/luv/library', words = { 'vim%.uv' } } } }, }, - -- JSON Schema support + { + 'j-hui/fidget.nvim', + event = 'LspAttach', + opts = { + notification = { + override_vim_notify = true, + }, + }, + config = function(_, opts) + require('fidget').setup(opts) + + local filters = { + 'ExperimentalWarning: SQLite', + 'DeprecationWarning.*punycode', + 'Cannot find request.*whilst attempting to cancel', + 'AbortError: The operation was aborted', + 'rate limit exceeded', + 'Rate limited by server', + 'Error while parsing file://', + 'Error while formatting.*Shfmt', + } + + vim.schedule(function() + local fidget_notify = vim.notify + ---@diagnostic disable-next-line: duplicate-set-field + vim.notify = function(msg, level, notify_opts) + if type(msg) == 'string' then + for _, pattern in ipairs(filters) do + if msg:match(pattern) then + return + end + end + end + fidget_notify(msg, level, notify_opts) + end + end) + end, + }, + { 'ray-x/lsp_signature.nvim', event = 'LspAttach' }, + { 'b0o/schemastore.nvim', ft = { 'json', 'yaml' } }, }, config = function() - require('neodev').setup() - - -- Enhanced capabilities for LSP servers local capabilities = vim.lsp.protocol.make_client_capabilities() - local has_cmp, cmp_nvim_lsp = pcall(require, 'cmp_nvim_lsp') + local has_cmp, cmp_lsp = pcall(require, 'cmp_nvim_lsp') if has_cmp then - capabilities = cmp_nvim_lsp.default_capabilities(capabilities) + capabilities = cmp_lsp.default_capabilities(capabilities) end - capabilities.offsetEncoding = { 'utf-8' } - - -- Disable dynamicRegistration to silence yamlls warning - capabilities.workspace = capabilities.workspace or {} - capabilities.workspace.didChangeConfiguration = capabilities.workspace.didChangeConfiguration or {} - capabilities.workspace.didChangeConfiguration.dynamicRegistration = false + -- Silence yamlls dynamicRegistration warning + capabilities.workspace = vim.tbl_deep_extend('force', capabilities.workspace or {}, { + didChangeConfiguration = { dynamicRegistration = false }, + }) local function path_exists(p) return p and p ~= '' and vim.uv.fs_stat(p) ~= nil @@ -48,21 +78,17 @@ return { local function mason_bin(name) local p = vim.fs.joinpath(vim.fn.stdpath('data'), 'mason', 'bin', name) - if path_exists(p) then - return p - end - return name + return path_exists(p) and p or name end local function julia_bin() local home = vim.env.HOME or '' - local candidates = { + for _, p in ipairs({ vim.fn.exepath('julia'), vim.fs.joinpath(home, '.juliaup', 'bin', 'julia'), '/opt/homebrew/bin/julia', '/usr/local/bin/julia', - } - for _, p in ipairs(candidates) do + }) do if path_exists(p) then return p end @@ -70,8 +96,128 @@ return { return 'julia' end - -- Servers that should provide formatting - local format_enabled_servers = { + -- Augroup created once; buffer-scoped autocmds clean themselves up + local lsp_aug = vim.api.nvim_create_augroup('lsp_attach', { clear = true }) + + vim.api.nvim_create_autocmd('LspAttach', { + group = lsp_aug, + callback = function(args) + local bufnr = args.buf + local client = vim.lsp.get_client_by_id(args.data.client_id) + if not client then + return + end + + -- LSP signature + require('lsp_signature').on_attach({ + hint_enable = false, + handler_opts = { border = 'rounded' }, + }, bufnr) + + vim.bo[bufnr].omnifunc = 'v:lua.vim.lsp.omnifunc' + + -- Keymap helper + local function map(keys, func, desc, modes) + vim.keymap.set(modes or 'n', keys, func, { + buffer = bufnr, + desc = 'LSP: ' .. (desc or keys), + }) + end + + -- Lazy-load Telescope wrapper + local function tb(name) + return function(...) + require('lazy').load({ plugins = { 'telescope.nvim' } }) + return require('telescope.builtin')[name](...) + end + end + + -- Navigation + map('gd', tb('lsp_definitions'), 'Goto Definition') + map('gD', vim.lsp.buf.declaration, 'Goto Declaration') + map('gR', tb('lsp_references'), 'Goto References') + map('gI', tb('lsp_implementations'), 'Goto Implementation') + map('gy', tb('lsp_type_definitions'), 'Goto Type Definition') + map('ls', tb('lsp_document_symbols'), 'Document Symbols') + map('lS', tb('lsp_dynamic_workspace_symbols'), 'Workspace Symbols') + + -- Diagnostics + map('ld', vim.diagnostic.open_float, 'Diagnostic float') + map(']e', function() + vim.diagnostic.jump({ count = 1, float = true }) + vim.cmd('normal! zz') + end, 'Next diagnostic') + map('[e', function() + vim.diagnostic.jump({ count = -1, float = true }) + vim.cmd('normal! zz') + end, 'Prev diagnostic') + map(']E', function() + vim.diagnostic.jump({ count = 1, severity = vim.diagnostic.severity.ERROR, float = true }) + vim.cmd('normal! zz') + end, 'Next error') + map('[E', function() + vim.diagnostic.jump({ count = -1, severity = vim.diagnostic.severity.ERROR, float = true }) + vim.cmd('normal! zz') + end, 'Prev error') + + -- Code actions & refactoring + map('lr', vim.lsp.buf.rename, 'Rename') + map('la', vim.lsp.buf.code_action, 'Code Action', { 'n', 'v' }) + + -- Documentation + map('K', vim.lsp.buf.hover, 'Hover documentation') + map('lk', vim.lsp.buf.signature_help, 'Signature help') + vim.keymap.set('i', '', vim.lsp.buf.signature_help, { + buffer = bufnr, + desc = 'LSP: Signature help (insert)', + }) + + -- Document highlight — guarded per client, per buffer + if client:supports_method('textDocument/documentHighlight', bufnr) then + local visual_bg = vim.fn.synIDattr(vim.fn.hlID('Visual'), 'bg') or '#3e4452' + vim.api.nvim_set_hl(0, 'LspReferenceText', { bg = visual_bg }) + vim.api.nvim_set_hl(0, 'LspReferenceRead', { bg = visual_bg }) + vim.api.nvim_set_hl(0, 'LspReferenceWrite', { bg = visual_bg }) + + -- Unique group per buffer so multiple clients don't clobber each other + local hl_aug = vim.api.nvim_create_augroup('lsp_highlight_' .. bufnr, { clear = true }) + + vim.api.nvim_create_autocmd('CursorHold', { + group = hl_aug, + buffer = bufnr, + callback = vim.lsp.buf.document_highlight, + }) + vim.api.nvim_create_autocmd('CursorMoved', { + group = hl_aug, + buffer = bufnr, + callback = vim.lsp.buf.clear_references, + }) + + vim.api.nvim_buf_create_user_command(bufnr, 'LspToggleHighlight', function() + local enabled = vim.b.lsp_highlight_enabled + if enabled then + vim.api.nvim_clear_autocmds({ group = hl_aug, buffer = bufnr }) + vim.lsp.buf.clear_references() + else + vim.api.nvim_create_autocmd( + 'CursorHold', + { group = hl_aug, buffer = bufnr, callback = vim.lsp.buf.document_highlight } + ) + vim.api.nvim_create_autocmd( + 'CursorMoved', + { group = hl_aug, buffer = bufnr, callback = vim.lsp.buf.clear_references } + ) + end + vim.b.lsp_highlight_enabled = not enabled + vim.notify('LSP highlights ' .. (enabled and 'disabled' or 'enabled')) + end, {}) + + vim.b[bufnr].lsp_highlight_enabled = true + end + end, + }) + + local format_enabled = { bashls = true, clangd = true, gopls = true, @@ -89,136 +235,39 @@ return { zls = true, } - -- Default on_attach function - local on_attach = function(client, bufnr) - -- Enable formatting only for supported servers - if not format_enabled_servers[client.name] then - client.server_capabilities.documentFormattingProvider = false - client.server_capabilities.documentRangeFormattingProvider = false - end - - vim.bo[bufnr].omnifunc = 'v:lua.vim.lsp.omnifunc' - - -- LSP signature help - require('lsp_signature').on_attach({ - hint_enable = false, - handler_opts = { border = 'rounded' }, - }, bufnr) - - -- Keymap helper - local function map(keys, func, desc, modes) - modes = modes or 'n' - vim.keymap.set(modes, keys, func, { - buffer = bufnr, - desc = desc and 'LSP: ' .. desc or nil, - }) - end - - -- Lazy-load Telescope for LSP - local function telescope_builtin(name) - return function(...) - require('lazy').load({ plugins = { 'telescope.nvim' } }) - return require('telescope.builtin')[name](...) + vim.api.nvim_create_autocmd('LspAttach', { + group = lsp_aug, + callback = function(args) + local client = vim.lsp.get_client_by_id(args.data.client_id) + if not client then + return end - end - - -- Navigation - map('gd', telescope_builtin('lsp_definitions'), '[G]oto [D]efinition') - map('gD', vim.lsp.buf.declaration, '[G]oto [D]eclaration') - map('gr', telescope_builtin('lsp_references'), '[G]oto [R]eferences') - map('gI', telescope_builtin('lsp_implementations'), '[G]oto [I]mplementation') - map('lT', telescope_builtin('lsp_type_definitions'), '[T]ype Definition') - map('ls', telescope_builtin('lsp_document_symbols'), '[D]ocument [S]ymbols') - map('lS', telescope_builtin('lsp_dynamic_workspace_symbols'), '[W]orkspace [S]ymbols') - - -- Diagnostics - map('ld', vim.diagnostic.open_float, 'Show line [E]rrors') - map('[d', vim.diagnostic.get_prev, 'Previous Diagnostic') - map(']d', vim.diagnostic.get_next, 'Next Diagnostic') - - -- Code Actions & Refactoring - map('lr', vim.lsp.buf.rename, 'Rename') - map('la', vim.lsp.buf.code_action, 'Code Action', { 'n', 'v' }) - - -- Documentation - map('K', vim.lsp.buf.hover, 'Hover Documentation') - map('', vim.lsp.buf.signature_help, 'Signature Documentation') - - -- Document highlight - if client.server_capabilities.documentHighlightProvider then - local highlight_group = vim.api.nvim_create_augroup('lsp_document_highlight', { clear = true }) - local visual_bg = vim.fn.synIDattr(vim.fn.hlID('Visual'), 'bg') or '#3e4452' - - vim.api.nvim_set_hl(0, 'LspReferenceText', { bg = visual_bg }) - vim.api.nvim_set_hl(0, 'LspReferenceRead', { bg = visual_bg }) - vim.api.nvim_set_hl(0, 'LspReferenceWrite', { bg = visual_bg }) - vim.o.updatetime = math.max(vim.o.updatetime, 500) - - local function toggle_lsp_highlight(enable) - if enable then - vim.api.nvim_create_autocmd('CursorHold', { - group = highlight_group, - buffer = bufnr, - callback = function() - if client and client.server_capabilities.documentHighlightProvider then - vim.lsp.buf.document_highlight() - end - end, - }) - vim.api.nvim_create_autocmd('CursorMoved', { - group = highlight_group, - buffer = bufnr, - callback = vim.lsp.buf.clear_references, - }) - else - vim.api.nvim_clear_autocmds({ group = highlight_group, buffer = bufnr }) - vim.lsp.buf.clear_references() - end + if not format_enabled[client.name] then + client.server_capabilities.documentFormattingProvider = false + client.server_capabilities.documentRangeFormattingProvider = false end - - vim.api.nvim_buf_create_user_command(bufnr, 'LspToggleHighlight', function() - local enabled = vim.b.lsp_highlight_enabled or false - toggle_lsp_highlight(not enabled) - vim.b.lsp_highlight_enabled = not enabled - print('LSP document highlights ' .. (enabled and 'disabled' or 'enabled')) - end, {}) - - toggle_lsp_highlight(true) - end - end - - -- Default LSP configuration - local default_config = { - capabilities = capabilities, - on_attach = on_attach, - autostart = true, - } + end, + }) local julia_cmd = julia_bin() - local julia_ls_project = vim.fn.expand('~/.julia/environments/nvim-lsp') - -- Server-specific configurations local servers = { bashls = { filetypes = { 'sh', 'bash', 'zsh' }, + -- suppress all diagnostics from bashls (too noisy) + handlers = { ['textDocument/publishDiagnostics'] = function() end }, }, - html = { filetypes = { 'html', 'htmldjango' }, init_options = { configurationSection = { 'html', 'css', 'javascript' }, - embeddedLanguages = { - css = true, - javascript = true, - }, + embeddedLanguages = { css = true, javascript = true }, }, }, - htmx = { cmd = { 'htmx-lsp' }, filetypes = { 'html', 'htmx' }, }, - gopls = { settings = { gopls = { @@ -230,7 +279,6 @@ return { }, }, }, - clangd = { cmd = { 'clangd', @@ -242,87 +290,55 @@ return { '--query-driver=/usr/bin/clang,/usr/bin/clang++', '--enable-config', }, + -- offsetEncoding is clangd-specific — set here, not on global capabilities + capabilities = vim.tbl_deep_extend('force', capabilities, { + offsetEncoding = { 'utf-8' }, + }), + filetypes = { 'c', 'cpp', 'objc', 'objcpp', 'h', 'hpp', 'hxx' }, settings = { formatting = true, - inlayHints = { - designators = true, - enabled = true, - parameterNames = true, - deducedTypes = true, - }, + inlayHints = { designators = true, enabled = true, parameterNames = true, deducedTypes = true }, }, - filetypes = { 'c', 'cpp', 'objc', 'objcpp', 'h', 'hpp', 'hxx' }, }, - marksman = { filetypes = { 'markdown' }, - settings = { - marksman = { - extensions = { 'mdx' }, - }, - }, + settings = { marksman = { extensions = { 'mdx' } } }, }, - jsonls = { cmd = { 'vscode-json-language-server', '--stdio' }, - settings = { - json = { - validate = { enable = true }, - }, - }, + settings = { json = { validate = { enable = true } } }, + on_attach = function(client, _) + -- attach schemastore schemas after server starts + local ok, schema = pcall(require, 'schemastore') + if ok then + client.config.settings.json.schemas = schema.json.schemas() + client.notify('workspace/didChangeConfiguration', { settings = client.config.settings }) + end + end, }, - julials = { filetypes = { 'julia' }, cmd = { julia_cmd, - '--project=' .. julia_ls_project, + '--project=' .. vim.fn.expand('~/.julia/environments/nvim-lsp'), '--startup-file=no', '--history-file=no', '-e', [[ - using Logging - using LanguageServer - using SymbolServer - - global_logger(ConsoleLogger(stderr, Logging.Warn)) - - function project_path() - try - return LanguageServer.find_project_path(pwd()) - catch - return pwd() - end - end - - depot_path = get(ENV, "JULIA_DEPOT_PATH", "") - - server = LanguageServer.LanguageServerInstance( - stdin, - stdout, - something(project_path(), pwd()), - depot_path, - ) - - server.runlinter = true - run(server) - ]], + using Logging, LanguageServer, SymbolServer + global_logger(ConsoleLogger(stderr, Logging.Warn)) + depot = get(ENV, "JULIA_DEPOT_PATH", "") + server = LanguageServer.LanguageServerInstance( + stdin, stdout, + something(LanguageServer.find_project_path(pwd()), pwd()), + depot, + ) + server.runlinter = true + run(server) + ]], }, - settings = { - julia = { - lint = { - run = true, - }, - }, - }, - on_attach = function(client, bufnr) - on_attach(client, bufnr) - -- Julia must never format via LSP - client.server_capabilities.documentFormattingProvider = false - client.server_capabilities.documentRangeFormattingProvider = false - end, + settings = { julia = { lint = { run = true } } }, }, - pyright = { settings = { python = { @@ -331,29 +347,16 @@ return { diagnosticMode = 'workspace', useLibraryCodeForTypes = true, typeCheckingMode = 'none', - reportGeneralTypeIssues = false, }, }, }, }, - ruff = { filetypes = { 'python' }, - on_init = function() - vim.api.nvim_create_autocmd('LspAttach', { - group = vim.api.nvim_create_augroup('lsp_attach_disable_ruff_hover', { clear = true }), - callback = function(args) - local client = vim.lsp.get_client_by_id(args.data.client_id) - if client and client.name == 'ruff' then - -- Disable Ruff hover - client.server_capabilities.hoverProvider = false - end - end, - desc = 'LSP: Disable hover capability from Ruff', - }) + on_attach = function(client) + client.server_capabilities.hoverProvider = false end, }, - rust_analyzer = { settings = { ['rust-analyzer'] = { @@ -364,27 +367,18 @@ return { }, }, }, - - taplo = { - filetypes = { 'toml' }, - }, - + taplo = { filetypes = { 'toml' } }, yamlls = { settings = { - yaml = { - schemaStore = { enable = true }, - validate = true, - }, + yaml = { schemaStore = { enable = true }, validate = true }, }, }, - texlab = { filetypes = { 'tex', 'plaintex', 'bib', 'cls', 'sty' }, settings = { texlab = { - build = { - onSave = false, - }, + build = { onSave = false }, + auxDirectory = 'output', diagnostics = { ignoredPatterns = { '^Overfull \\\\hbox', @@ -392,11 +386,9 @@ return { '^Package.*Warning', }, }, - auxDirectory = 'output', }, }, }, - lua_ls = { cmd = { mason_bin('lua-language-server') }, settings = { @@ -407,97 +399,43 @@ return { }, }, }, - sqls = { filetypes = { 'sql', 'mysql', 'plsql', 'postgresql' }, settings = { - sql = { - connections = { - { - driver = 'sqlite3', - dataSourceName = 'file::memory:?cache=shared', - }, - }, - }, + sql = { connections = { { driver = 'sqlite3', dataSourceName = 'file::memory:?cache=shared' } } }, }, on_init = function(client) - local root_dir = client.config.root_dir or vim.fn.getcwd() - local db_files = vim.fn.globpath(root_dir, '*.db', false, true) - vim.list_extend(db_files, vim.fn.globpath(root_dir, '*.sqlite', false, true)) - - if #db_files > 0 then - local connections = {} - for _, path in ipairs(db_files) do - table.insert(connections, { - driver = 'sqlite3', - dataSourceName = vim.fn.fnamemodify(path, ':p'), - }) - end - client.config.settings.sql.connections = connections + local root = client.config.root_dir or vim.fn.getcwd() + local dbs = + vim.list_extend(vim.fn.globpath(root, '*.db', false, true), vim.fn.globpath(root, '*.sqlite', false, true)) + if #dbs > 0 then + client.config.settings.sql.connections = vim.tbl_map(function(p) + return { driver = 'sqlite3', dataSourceName = vim.fn.fnamemodify(p, ':p') } + end, dbs) client.notify('workspace/didChangeConfiguration', { settings = client.config.settings }) end end, }, - - zls = { - filetypes = { 'zig' }, - }, + zls = { filetypes = { 'zig' } }, } - -- Use Mason to ensure servers are installed - local ensure_installed = vim.tbl_keys(servers) - ensure_installed = vim.tbl_filter(function(name) - return name ~= 'julials' - end, ensure_installed) - require('mason-lspconfig').setup({ - ensure_installed = ensure_installed, + ensure_installed = vim.tbl_filter(function(n) + return n ~= 'julials' + end, vim.tbl_keys(servers)), automatic_enable = false, }) - local function get_lspconfig_defaults(server_name) - local ok, cfg = pcall(require, 'lspconfig.configs.' .. server_name) - if ok and cfg and cfg.default_config then - local defaults = vim.deepcopy(cfg.default_config) - if type(defaults.root_dir) == 'function' then - local orig_root_dir = defaults.root_dir - defaults.root_dir = function(bufnr, on_dir) - local fname = bufnr - if type(bufnr) == 'number' then - fname = vim.api.nvim_buf_get_name(bufnr) - if fname == '' or type(fname) ~= 'string' or fname:match('^%w+://') then - fname = vim.fn.getcwd() - end - end - - local root = orig_root_dir(fname) - if type(on_dir) == 'function' then - on_dir(root) - return - end - return root - end - end - return defaults - end - return {} + for name, config in pairs(servers) do + vim.lsp.config( + name, + vim.tbl_deep_extend('force', { + capabilities = capabilities, + }, config) + ) + vim.lsp.enable(name) end - -- Setup and enable LSP servers - for server_name, config in pairs(servers) do - if config then - local lspconfig_defaults = get_lspconfig_defaults(server_name) - - -- Merge default config with server-specific config - local merged_config = vim.tbl_deep_extend('force', lspconfig_defaults, default_config, config) - - -- Configure and enable the server - vim.lsp.config(server_name, merged_config) - vim.lsp.enable(server_name) - end - end - - -- Diagnostics configuration vim.diagnostic.config({ underline = true, severity_sort = true, @@ -508,15 +446,40 @@ return { [vim.diagnostic.severity.HINT] = 'H', [vim.diagnostic.severity.INFO] = 'I', }, - linehl = { - [vim.diagnostic.severity.ERROR] = 'ErrorMsg', - }, - numhl = { - [vim.diagnostic.severity.WARN] = 'WarningMsg', - }, + numhl = { [vim.diagnostic.severity.WARN] = 'WarningMsg' }, }, virtual_text = { spacing = 2, prefix = '●' }, float = { source = 'if_many', border = 'rounded' }, }) + + require('mason-tool-installer').setup({ + ensure_installed = { + -- LSP servers (formatters/linters installed via mason-lspconfig separately) + 'lua-language-server', + 'bash-language-server', + 'pyright', + 'ruff', + + -- Formatters (from formatting.lua) + 'stylua', + 'yamlfmt', + 'shfmt', + 'sqlfluff', + 'jq', + 'prettierd', + 'prettier', + + -- Linters (from linting.lua) + 'mypy', + 'golangci-lint', + 'yamllint', + 'markdownlint', + 'shellcheck', + 'luacheck', + }, + auto_update = false, + run_on_start = true, + start_delay = 3000, -- defer 3s after startup so it doesn't block anything + }) end, }