refactor: overhaul LSP and completion setup
- Major refactor of lsp-config.lua with simplified configuration - Streamline cmp.lua for better performance - Remove unused LSP configurations
This commit is contained in:
parent
e8979088e6
commit
bfb1a63776
2 changed files with 370 additions and 418 deletions
|
|
@ -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 = {
|
||||
['<C-n>'] = cmp.mapping.select_next_item(),
|
||||
['<C-p>'] = cmp.mapping.select_prev_item(),
|
||||
['<C-b>'] = cmp.mapping.scroll_docs(-4),
|
||||
['<C-f>'] = cmp.mapping.scroll_docs(4),
|
||||
['<Tab>'] = 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' }),
|
||||
['<S-Tab>'] = 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' }),
|
||||
['<CR>'] = 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({
|
||||
['<C-n>'] = cmp.mapping.select_next_item(),
|
||||
['<C-p>'] = cmp.mapping.select_prev_item(),
|
||||
['<C-b>'] = cmp.mapping.scroll_docs(-4),
|
||||
['<C-f>'] = cmp.mapping.scroll_docs(4),
|
||||
['<CR>'] = cmp.mapping.confirm({ select = false }),
|
||||
['<Tab>'] = 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' }),
|
||||
['<S-Tab>'] = 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-n>'] = { c = cmp.mapping.select_next_item() },
|
||||
['<C-p>'] = { c = cmp.mapping.select_prev_item() },
|
||||
['<Tab>'] = { c = cmp.mapping.select_next_item() },
|
||||
['<S-Tab>'] = { 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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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('<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,
|
||||
|
|
@ -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('<leader>lT', telescope_builtin('lsp_type_definitions'), '[T]ype Definition')
|
||||
map('<leader>ls', telescope_builtin('lsp_document_symbols'), '[D]ocument [S]ymbols')
|
||||
map('<leader>lS', telescope_builtin('lsp_dynamic_workspace_symbols'), '[W]orkspace [S]ymbols')
|
||||
|
||||
-- Diagnostics
|
||||
map('<leader>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('<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('<C-k>', 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,
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue