- Major refactor of lsp-config.lua with simplified configuration - Streamline cmp.lua for better performance - Remove unused LSP configurations
485 lines
15 KiB
Lua
Executable file
485 lines
15 KiB
Lua
Executable file
return {
|
|
'neovim/nvim-lspconfig',
|
|
event = { 'BufReadPre', 'BufNewFile' },
|
|
dependencies = {
|
|
{
|
|
'williamboman/mason.nvim',
|
|
build = ':MasonUpdate',
|
|
cmd = 'Mason',
|
|
config = true,
|
|
},
|
|
'williamboman/mason-lspconfig.nvim',
|
|
'WhoIsSethDaniel/mason-tool-installer.nvim',
|
|
|
|
-- lazydev replaces neodev
|
|
{
|
|
'folke/lazydev.nvim',
|
|
ft = 'lua',
|
|
opts = { library = { { path = '${3rd}/luv/library', words = { 'vim%.uv' } } } },
|
|
},
|
|
|
|
{
|
|
'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()
|
|
local capabilities = vim.lsp.protocol.make_client_capabilities()
|
|
local has_cmp, cmp_lsp = pcall(require, 'cmp_nvim_lsp')
|
|
if has_cmp then
|
|
capabilities = cmp_lsp.default_capabilities(capabilities)
|
|
end
|
|
-- 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
|
|
end
|
|
|
|
local function mason_bin(name)
|
|
local p = vim.fs.joinpath(vim.fn.stdpath('data'), 'mason', 'bin', name)
|
|
return path_exists(p) and p or name
|
|
end
|
|
|
|
local function julia_bin()
|
|
local home = vim.env.HOME or ''
|
|
for _, p in ipairs({
|
|
vim.fn.exepath('julia'),
|
|
vim.fs.joinpath(home, '.juliaup', 'bin', 'julia'),
|
|
'/opt/homebrew/bin/julia',
|
|
'/usr/local/bin/julia',
|
|
}) do
|
|
if path_exists(p) then
|
|
return p
|
|
end
|
|
end
|
|
return 'julia'
|
|
end
|
|
|
|
-- 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('<leader>ls', tb('lsp_document_symbols'), 'Document Symbols')
|
|
map('<leader>lS', tb('lsp_dynamic_workspace_symbols'), 'Workspace Symbols')
|
|
|
|
-- Diagnostics
|
|
map('<leader>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('<leader>lr', vim.lsp.buf.rename, 'Rename')
|
|
map('<leader>la', vim.lsp.buf.code_action, 'Code Action', { 'n', 'v' })
|
|
|
|
-- Documentation
|
|
map('K', vim.lsp.buf.hover, 'Hover documentation')
|
|
map('<leader>lk', vim.lsp.buf.signature_help, 'Signature help')
|
|
vim.keymap.set('i', '<C-s>', 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,
|
|
html = true,
|
|
htmx = true,
|
|
jsonls = true,
|
|
lua_ls = true,
|
|
marksman = true,
|
|
pyright = true,
|
|
ruff = true,
|
|
rust_analyzer = true,
|
|
taplo = true,
|
|
texlab = true,
|
|
yamlls = true,
|
|
zls = true,
|
|
}
|
|
|
|
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
|
|
if not format_enabled[client.name] then
|
|
client.server_capabilities.documentFormattingProvider = false
|
|
client.server_capabilities.documentRangeFormattingProvider = false
|
|
end
|
|
end,
|
|
})
|
|
|
|
local julia_cmd = julia_bin()
|
|
|
|
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 },
|
|
},
|
|
},
|
|
htmx = {
|
|
cmd = { 'htmx-lsp' },
|
|
filetypes = { 'html', 'htmx' },
|
|
},
|
|
gopls = {
|
|
settings = {
|
|
gopls = {
|
|
gofumpt = true,
|
|
staticcheck = true,
|
|
completeUnimported = true,
|
|
usePlaceholders = true,
|
|
analyses = { unusedparams = true },
|
|
},
|
|
},
|
|
},
|
|
clangd = {
|
|
cmd = {
|
|
'clangd',
|
|
'--background-index',
|
|
'--clang-tidy',
|
|
'--header-insertion=iwyu',
|
|
'--completion-style=detailed',
|
|
'--header-insertion-decorators',
|
|
'--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 },
|
|
},
|
|
},
|
|
marksman = {
|
|
filetypes = { 'markdown' },
|
|
settings = { marksman = { extensions = { 'mdx' } } },
|
|
},
|
|
jsonls = {
|
|
cmd = { 'vscode-json-language-server', '--stdio' },
|
|
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=' .. vim.fn.expand('~/.julia/environments/nvim-lsp'),
|
|
'--startup-file=no',
|
|
'--history-file=no',
|
|
'-e',
|
|
[[
|
|
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 } } },
|
|
},
|
|
pyright = {
|
|
settings = {
|
|
python = {
|
|
analysis = {
|
|
autoSearchPaths = true,
|
|
diagnosticMode = 'workspace',
|
|
useLibraryCodeForTypes = true,
|
|
typeCheckingMode = 'none',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
ruff = {
|
|
filetypes = { 'python' },
|
|
on_attach = function(client)
|
|
client.server_capabilities.hoverProvider = false
|
|
end,
|
|
},
|
|
rust_analyzer = {
|
|
settings = {
|
|
['rust-analyzer'] = {
|
|
imports = { granularity = { group = 'module' }, prefix = 'self' },
|
|
cargo = { buildScripts = { enable = true } },
|
|
procMacro = { enable = true },
|
|
checkOnSave = { command = 'clippy' },
|
|
},
|
|
},
|
|
},
|
|
taplo = { filetypes = { 'toml' } },
|
|
yamlls = {
|
|
settings = {
|
|
yaml = { schemaStore = { enable = true }, validate = true },
|
|
},
|
|
},
|
|
texlab = {
|
|
filetypes = { 'tex', 'plaintex', 'bib', 'cls', 'sty' },
|
|
settings = {
|
|
texlab = {
|
|
build = { onSave = false },
|
|
auxDirectory = 'output',
|
|
diagnostics = {
|
|
ignoredPatterns = {
|
|
'^Overfull \\\\hbox',
|
|
'^Underfull \\\\hbox',
|
|
'^Package.*Warning',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
lua_ls = {
|
|
cmd = { mason_bin('lua-language-server') },
|
|
settings = {
|
|
Lua = {
|
|
workspace = { checkThirdParty = false },
|
|
telemetry = { enable = false },
|
|
diagnostics = { globals = { 'vim' } },
|
|
},
|
|
},
|
|
},
|
|
sqls = {
|
|
filetypes = { 'sql', 'mysql', 'plsql', 'postgresql' },
|
|
settings = {
|
|
sql = { connections = { { driver = 'sqlite3', dataSourceName = 'file::memory:?cache=shared' } } },
|
|
},
|
|
on_init = function(client)
|
|
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' } },
|
|
}
|
|
|
|
require('mason-lspconfig').setup({
|
|
ensure_installed = vim.tbl_filter(function(n)
|
|
return n ~= 'julials'
|
|
end, vim.tbl_keys(servers)),
|
|
automatic_enable = false,
|
|
})
|
|
|
|
for name, config in pairs(servers) do
|
|
vim.lsp.config(
|
|
name,
|
|
vim.tbl_deep_extend('force', {
|
|
capabilities = capabilities,
|
|
}, config)
|
|
)
|
|
vim.lsp.enable(name)
|
|
end
|
|
|
|
vim.diagnostic.config({
|
|
underline = true,
|
|
severity_sort = true,
|
|
signs = {
|
|
text = {
|
|
[vim.diagnostic.severity.ERROR] = 'E',
|
|
[vim.diagnostic.severity.WARN] = 'W',
|
|
[vim.diagnostic.severity.HINT] = 'H',
|
|
[vim.diagnostic.severity.INFO] = 'I',
|
|
},
|
|
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,
|
|
}
|