feat: lsp
This commit is contained in:
parent
bb12a11cc0
commit
173ff5e47a
34
lsp/cssls.lua
Normal file
34
lsp/cssls.lua
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
---@brief
|
||||||
|
---
|
||||||
|
--- https://github.com/hrsh7th/vscode-langservers-extracted
|
||||||
|
---
|
||||||
|
--- `css-languageserver` can be installed via `npm`:
|
||||||
|
---
|
||||||
|
--- ```sh
|
||||||
|
--- npm i -g vscode-langservers-extracted
|
||||||
|
--- ```
|
||||||
|
---
|
||||||
|
--- Neovim does not currently include built-in snippets. `vscode-css-language-server` only provides completions when snippet support is enabled. To enable completion, install a snippet plugin and add the following override to your language client capabilities during setup.
|
||||||
|
---
|
||||||
|
--- ```lua
|
||||||
|
--- --Enable (broadcasting) snippet capability for completion
|
||||||
|
--- local capabilities = vim.lsp.protocol.make_client_capabilities()
|
||||||
|
--- capabilities.textDocument.completion.completionItem.snippetSupport = true
|
||||||
|
---
|
||||||
|
--- vim.lsp.config('cssls', {
|
||||||
|
--- capabilities = capabilities,
|
||||||
|
--- })
|
||||||
|
--- ```
|
||||||
|
|
||||||
|
---@type vim.lsp.Config
|
||||||
|
return {
|
||||||
|
cmd = { 'vscode-css-language-server', '--stdio' },
|
||||||
|
filetypes = { 'css', 'scss', 'less' },
|
||||||
|
init_options = { provideFormatter = true }, -- needed to enable formatting capabilities
|
||||||
|
root_markers = { 'package.json', '.git' },
|
||||||
|
settings = {
|
||||||
|
css = { validate = true },
|
||||||
|
scss = { validate = true },
|
||||||
|
less = { validate = true },
|
||||||
|
},
|
||||||
|
}
|
||||||
226
lsp/eslint.lua
Normal file
226
lsp/eslint.lua
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
--- @brief
|
||||||
|
---
|
||||||
|
--- https://github.com/hrsh7th/vscode-langservers-extracted
|
||||||
|
---
|
||||||
|
--- `vscode-eslint-language-server` is a linting engine for JavaScript / Typescript.
|
||||||
|
--- It can be installed via `npm`:
|
||||||
|
---
|
||||||
|
--- ```sh
|
||||||
|
--- npm i -g vscode-langservers-extracted
|
||||||
|
--- ```
|
||||||
|
---
|
||||||
|
--- The default `on_attach` config provides the `LspEslintFixAll` command that can be used to format a document on save:
|
||||||
|
--- ```lua
|
||||||
|
--- local base_on_attach = vim.lsp.config.eslint.on_attach
|
||||||
|
--- vim.lsp.config("eslint", {
|
||||||
|
--- on_attach = function(client, bufnr)
|
||||||
|
--- if not base_on_attach then return end
|
||||||
|
---
|
||||||
|
--- base_on_attach(client, bufnr)
|
||||||
|
--- vim.api.nvim_create_autocmd("BufWritePre", {
|
||||||
|
--- buffer = bufnr,
|
||||||
|
--- command = "LspEslintFixAll",
|
||||||
|
--- })
|
||||||
|
--- end,
|
||||||
|
--- })
|
||||||
|
--- ```
|
||||||
|
---
|
||||||
|
--- See [vscode-eslint](https://github.com/microsoft/vscode-eslint/blob/55871979d7af184bf09af491b6ea35ebd56822cf/server/src/eslintServer.ts#L216-L229) for configuration options.
|
||||||
|
---
|
||||||
|
--- Messages handled in lspconfig: `eslint/openDoc`, `eslint/confirmESLintExecution`, `eslint/probeFailed`, `eslint/noLibrary`
|
||||||
|
---
|
||||||
|
--- Additional messages you can handle: `eslint/noConfig`
|
||||||
|
---
|
||||||
|
--- ### Monorepo support
|
||||||
|
---
|
||||||
|
--- `vscode-eslint-language-server` supports monorepos by default. It will automatically find the config file corresponding to the package you are working on. You can use different configs in different packages.
|
||||||
|
--- This works without the need of spawning multiple instances of `vscode-eslint-language-server`.
|
||||||
|
--- You can use a different version of ESLint in each package, but it is recommended to use the same version of ESLint in all packages. The location of the ESLint binary will be determined automatically.
|
||||||
|
---
|
||||||
|
--- /!\ When using flat config files, you need to use them across all your packages in your monorepo, as it's a global setting for the server.
|
||||||
|
|
||||||
|
local utils = require('utils')
|
||||||
|
local lsp = vim.lsp
|
||||||
|
|
||||||
|
local eslint_config_files = {
|
||||||
|
'.eslintrc',
|
||||||
|
'.eslintrc.js',
|
||||||
|
'.eslintrc.cjs',
|
||||||
|
'.eslintrc.yaml',
|
||||||
|
'.eslintrc.yml',
|
||||||
|
'.eslintrc.json',
|
||||||
|
'eslint.config.js',
|
||||||
|
'eslint.config.mjs',
|
||||||
|
'eslint.config.cjs',
|
||||||
|
'eslint.config.ts',
|
||||||
|
'eslint.config.mts',
|
||||||
|
'eslint.config.cts',
|
||||||
|
}
|
||||||
|
|
||||||
|
---@type vim.lsp.Config
|
||||||
|
return {
|
||||||
|
cmd = { 'vscode-eslint-language-server', '--stdio' },
|
||||||
|
filetypes = {
|
||||||
|
'javascript',
|
||||||
|
'javascriptreact',
|
||||||
|
'javascript.jsx',
|
||||||
|
'typescript',
|
||||||
|
'typescriptreact',
|
||||||
|
'typescript.tsx',
|
||||||
|
'vue',
|
||||||
|
'svelte',
|
||||||
|
'astro',
|
||||||
|
'htmlangular',
|
||||||
|
},
|
||||||
|
workspace_required = true,
|
||||||
|
on_attach = function(client, bufnr)
|
||||||
|
vim.api.nvim_buf_create_user_command(0, 'LspEslintFixAll', function()
|
||||||
|
client:request_sync('workspace/executeCommand', {
|
||||||
|
command = 'eslint.applyAllFixes',
|
||||||
|
arguments = {
|
||||||
|
{
|
||||||
|
uri = vim.uri_from_bufnr(bufnr),
|
||||||
|
version = lsp.util.buf_versions[bufnr],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil, bufnr)
|
||||||
|
end, {})
|
||||||
|
end,
|
||||||
|
root_dir = function(bufnr, on_dir)
|
||||||
|
-- The project root is where the LSP can be started from
|
||||||
|
-- As stated in the documentation above, this LSP supports monorepos and simple projects.
|
||||||
|
-- We select then from the project root, which is identified by the presence of a package
|
||||||
|
-- manager lock file.
|
||||||
|
local root_markers =
|
||||||
|
{ 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb', 'bun.lock' }
|
||||||
|
-- Give the root markers equal priority by wrapping them in a table
|
||||||
|
root_markers = vim.fn.has('nvim-0.11.3') == 1 and { root_markers, { '.git' } }
|
||||||
|
or vim.list_extend(root_markers, { '.git' })
|
||||||
|
-- We fallback to the current working directory if no project root is found
|
||||||
|
local project_root = vim.fs.root(bufnr, root_markers) or vim.fn.getcwd()
|
||||||
|
|
||||||
|
-- We know that the buffer is using ESLint if it has a config file
|
||||||
|
-- in its directory tree.
|
||||||
|
--
|
||||||
|
-- Eslint used to support package.json files as config files, but it doesn't anymore.
|
||||||
|
-- We keep this for backward compatibility.
|
||||||
|
local filename = vim.api.nvim_buf_get_name(bufnr)
|
||||||
|
local eslint_config_files_with_package_json =
|
||||||
|
utils.insert_package_json(eslint_config_files, 'eslintConfig', filename)
|
||||||
|
local is_buffer_using_eslint = vim.fs.find(eslint_config_files_with_package_json, {
|
||||||
|
path = filename,
|
||||||
|
type = 'file',
|
||||||
|
limit = 1,
|
||||||
|
upward = true,
|
||||||
|
stop = vim.fs.dirname(project_root),
|
||||||
|
})[1]
|
||||||
|
if not is_buffer_using_eslint then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
on_dir(project_root)
|
||||||
|
end,
|
||||||
|
-- Refer to https://github.com/Microsoft/vscode-eslint#settings-options for documentation.
|
||||||
|
settings = {
|
||||||
|
validate = 'on',
|
||||||
|
packageManager = nil,
|
||||||
|
useESLintClass = false,
|
||||||
|
experimental = {
|
||||||
|
useFlatConfig = false,
|
||||||
|
},
|
||||||
|
codeActionOnSave = {
|
||||||
|
enable = false,
|
||||||
|
mode = 'all',
|
||||||
|
},
|
||||||
|
format = true,
|
||||||
|
quiet = false,
|
||||||
|
onIgnoredFiles = 'off',
|
||||||
|
rulesCustomizations = {},
|
||||||
|
run = 'onType',
|
||||||
|
problems = {
|
||||||
|
shortenToSingleLine = false,
|
||||||
|
},
|
||||||
|
-- nodePath configures the directory in which the eslint server should start its node_modules resolution.
|
||||||
|
-- This path is relative to the workspace folder (root dir) of the server instance.
|
||||||
|
nodePath = '',
|
||||||
|
-- use the workspace folder location or the file location (if no workspace folder is open) as the working directory
|
||||||
|
workingDirectory = { mode = 'auto' },
|
||||||
|
codeAction = {
|
||||||
|
disableRuleComment = {
|
||||||
|
enable = true,
|
||||||
|
location = 'separateLine',
|
||||||
|
},
|
||||||
|
showDocumentation = {
|
||||||
|
enable = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
before_init = function(_, config)
|
||||||
|
-- The "workspaceFolder" is a VSCode concept. It limits how far the
|
||||||
|
-- server will traverse the file system when locating the ESLint config
|
||||||
|
-- file (e.g., .eslintrc).
|
||||||
|
local root_dir = config.root_dir
|
||||||
|
|
||||||
|
if root_dir then
|
||||||
|
config.settings = config.settings or {}
|
||||||
|
config.settings.workspaceFolder = {
|
||||||
|
uri = root_dir,
|
||||||
|
name = vim.fn.fnamemodify(root_dir, ':t'),
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Support flat config files
|
||||||
|
-- They contain 'config' in the file name
|
||||||
|
local flat_config_files = vim.tbl_filter(function(file)
|
||||||
|
return file:match('config')
|
||||||
|
end, eslint_config_files)
|
||||||
|
|
||||||
|
for _, file in ipairs(flat_config_files) do
|
||||||
|
local found_files = vim.fn.globpath(root_dir, file, true, true)
|
||||||
|
|
||||||
|
-- Filter out files inside node_modules
|
||||||
|
local filtered_files = {}
|
||||||
|
for _, found_file in ipairs(found_files) do
|
||||||
|
if string.find(found_file, '[/\\]node_modules[/\\]') == nil then
|
||||||
|
table.insert(filtered_files, found_file)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #filtered_files > 0 then
|
||||||
|
config.settings.experimental = config.settings.experimental or {}
|
||||||
|
config.settings.experimental.useFlatConfig = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Support Yarn2 (PnP) projects
|
||||||
|
local pnp_cjs = root_dir .. '/.pnp.cjs'
|
||||||
|
local pnp_js = root_dir .. '/.pnp.js'
|
||||||
|
if vim.uv.fs_stat(pnp_cjs) or vim.uv.fs_stat(pnp_js) then
|
||||||
|
local cmd = config.cmd
|
||||||
|
config.cmd = vim.list_extend({ 'yarn', 'exec' }, cmd)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
handlers = {
|
||||||
|
['eslint/openDoc'] = function(_, result)
|
||||||
|
if result then
|
||||||
|
vim.ui.open(result.url)
|
||||||
|
end
|
||||||
|
return {}
|
||||||
|
end,
|
||||||
|
['eslint/confirmESLintExecution'] = function(_, result)
|
||||||
|
if not result then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
return 4 -- approved
|
||||||
|
end,
|
||||||
|
['eslint/probeFailed'] = function()
|
||||||
|
vim.notify('[lspconfig] ESLint probe failed.', vim.log.levels.WARN)
|
||||||
|
return {}
|
||||||
|
end,
|
||||||
|
['eslint/noLibrary'] = function()
|
||||||
|
vim.notify('[lspconfig] Unable to find ESLint library.', vim.log.levels.WARN)
|
||||||
|
return {}
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
36
lsp/html.lua
Normal file
36
lsp/html.lua
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
---@brief
|
||||||
|
---
|
||||||
|
--- https://github.com/hrsh7th/vscode-langservers-extracted
|
||||||
|
---
|
||||||
|
--- `vscode-html-language-server` can be installed via `npm`:
|
||||||
|
--- ```sh
|
||||||
|
--- npm i -g vscode-langservers-extracted
|
||||||
|
--- ```
|
||||||
|
---
|
||||||
|
--- Neovim does not currently include built-in snippets. `vscode-html-language-server` only provides completions when snippet support is enabled.
|
||||||
|
--- To enable completion, install a snippet plugin and add the following override to your language client capabilities during setup.
|
||||||
|
---
|
||||||
|
--- The code-formatting feature of the lsp can be controlled with the `provideFormatter` option.
|
||||||
|
---
|
||||||
|
--- ```lua
|
||||||
|
--- --Enable (broadcasting) snippet capability for completion
|
||||||
|
--- local capabilities = vim.lsp.protocol.make_client_capabilities()
|
||||||
|
--- capabilities.textDocument.completion.completionItem.snippetSupport = true
|
||||||
|
---
|
||||||
|
--- vim.lsp.config('html', {
|
||||||
|
--- capabilities = capabilities,
|
||||||
|
--- })
|
||||||
|
--- ```
|
||||||
|
|
||||||
|
---@type vim.lsp.Config
|
||||||
|
return {
|
||||||
|
cmd = { 'vscode-html-language-server', '--stdio' },
|
||||||
|
filetypes = { 'html', 'templ' },
|
||||||
|
root_markers = { 'package.json', '.git' },
|
||||||
|
settings = {},
|
||||||
|
init_options = {
|
||||||
|
provideFormatter = true,
|
||||||
|
embeddedLanguages = { css = true, javascript = true },
|
||||||
|
configurationSection = { 'html', 'css', 'javascript' },
|
||||||
|
},
|
||||||
|
}
|
||||||
@ -75,3 +75,5 @@ map('n', '[d', function()
|
|||||||
vim.diagnostic.jump({ count = -1, float = true })
|
vim.diagnostic.jump({ count = -1, float = true })
|
||||||
end)
|
end)
|
||||||
map('n', '<leader>q', vim.diagnostic.setloclist)
|
map('n', '<leader>q', vim.diagnostic.setloclist)
|
||||||
|
map('n', '<leader>d', vim.diagnostic.open_float)
|
||||||
|
map('n', '<leader>s', vim.lsp.buf.signature_help)
|
||||||
|
|||||||
@ -71,7 +71,7 @@ vim.opt.mouse = 'a' -- Enable mouse support
|
|||||||
vim.opt.ignorecase = true
|
vim.opt.ignorecase = true
|
||||||
vim.opt.smartcase = true -- Override ignorecase if search contains upper case chars
|
vim.opt.smartcase = true -- Override ignorecase if search contains upper case chars
|
||||||
vim.opt.inccommand = 'split' -- Live substitution preview
|
vim.opt.inccommand = 'split' -- Live substitution preview
|
||||||
vim.opt.completeopt = { 'menu,menuone,noselect' }
|
vim.opt.completeopt = { 'fuzzy,menuone,popup,preview,noselect' }
|
||||||
|
|
||||||
-- Splits
|
-- Splits
|
||||||
vim.opt.splitright = true
|
vim.opt.splitright = true
|
||||||
@ -87,22 +87,3 @@ vim.opt.timeout = true
|
|||||||
vim.opt.ttimeout = true
|
vim.opt.ttimeout = true
|
||||||
vim.opt.timeoutlen = 500
|
vim.opt.timeoutlen = 500
|
||||||
vim.opt.ttimeoutlen = 10
|
vim.opt.ttimeoutlen = 10
|
||||||
|
|
||||||
vim.keymap.set('n', '<leader>d', vim.diagnostic.open_float, { noremap = true, silent = true })
|
|
||||||
vim.diagnostic.config({
|
|
||||||
update_in_insert = false,
|
|
||||||
virtual_text = {
|
|
||||||
prefix = '', -- remove annoying ▎ etc
|
|
||||||
format = function(diagnostic)
|
|
||||||
if diagnostic.source then
|
|
||||||
return string.format('[%s] %s', diagnostic.source, diagnostic.message)
|
|
||||||
end
|
|
||||||
return diagnostic.message
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
float = {
|
|
||||||
border = 'rounded',
|
|
||||||
source = true, -- show source in floating window too
|
|
||||||
},
|
|
||||||
severity_sort = true,
|
|
||||||
})
|
|
||||||
|
|||||||
@ -19,6 +19,13 @@ return {
|
|||||||
TabLine = { fg = c.muted, bg = c.none },
|
TabLine = { fg = c.muted, bg = c.none },
|
||||||
TabLineSel = { fg = c.text, bg = c.none, bold = true },
|
TabLineSel = { fg = c.text, bg = c.none, bold = true },
|
||||||
TabLineFill = { fg = c.outline_light, bg = c.none },
|
TabLineFill = { fg = c.outline_light, bg = c.none },
|
||||||
|
|
||||||
|
Pmenu = { fg = c.text, bg = c.surface },
|
||||||
|
PmenuSel = { fg = c.text, bg = c.accent_light },
|
||||||
|
QuickFixLine = { fg = c.accent, bg = c.none, bold = true },
|
||||||
|
-- PmenuSbar = { bg = c.surface },
|
||||||
|
-- PmenuThumb = { bg = c.outline },
|
||||||
|
-- PmenuBorder = { fg = c.outline },
|
||||||
}
|
}
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -13,8 +13,8 @@ local language_specs = {
|
|||||||
'gitignore',
|
'gitignore',
|
||||||
'dockerfile',
|
'dockerfile',
|
||||||
'diff',
|
'diff',
|
||||||
{ 'json', nil, nil, 'prettier' },
|
{ 'json', 'json-lsp' },
|
||||||
{ 'jsonc', nil, nil, 'prettier' },
|
{ 'jsonc', 'json-lsp' },
|
||||||
|
|
||||||
-- Shell / scripting
|
-- Shell / scripting
|
||||||
{ 'bash', 'bash-language-server', 'shellcheck', 'shfmt' },
|
{ 'bash', 'bash-language-server', 'shellcheck', 'shfmt' },
|
||||||
@ -30,12 +30,12 @@ local language_specs = {
|
|||||||
-- { 'python', 'pyright', 'ruff', 'ruff' }, -- install ensurepip
|
-- { 'python', 'pyright', 'ruff', 'ruff' }, -- install ensurepip
|
||||||
|
|
||||||
-- Web stack
|
-- Web stack
|
||||||
{ 'html', nil, nil, 'prettier' },
|
{ 'html', 'html-lsp' },
|
||||||
{ 'css', nil, nil, 'prettier' },
|
{ 'css', 'css-lsp' },
|
||||||
{ 'javascript', 'vtsls', 'eslint_d', 'prettier' },
|
{ 'javascript', { 'vtsls', 'eslint-lsp' }, nil, 'prettier' },
|
||||||
{ 'typescript', 'vtsls', 'eslint_d', 'prettier' },
|
{ 'typescript', { 'vtsls', 'eslint-lsp' }, nil, 'prettier' },
|
||||||
{ nil, 'vtsls', 'eslint_d', 'prettier', filetype = 'javascriptreact' },
|
{ nil, { 'vtsls', 'eslint-lsp' }, nil, 'prettier', filetype = 'javascriptreact' },
|
||||||
{ 'tsx', 'vtsls', 'eslint_d', 'prettier', filetype = 'typescriptreact' },
|
{ 'tsx', { 'vtsls', 'eslint-lsp' }, nil, 'prettier', filetype = 'typescriptreact' },
|
||||||
}
|
}
|
||||||
|
|
||||||
local function normalize(spec)
|
local function normalize(spec)
|
||||||
@ -71,7 +71,13 @@ local function collect(specs)
|
|||||||
table.insert(ts, s.treesitter)
|
table.insert(ts, s.treesitter)
|
||||||
end
|
end
|
||||||
if s.lsp then
|
if s.lsp then
|
||||||
table.insert(lsps, s.lsp)
|
if type(s.lsp) == 'table' then
|
||||||
|
for _, v in ipairs(s.lsp) do
|
||||||
|
table.insert(lsps, v)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
table.insert(lsps, s.lsp)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
if s.linter then
|
if s.linter then
|
||||||
linters[s.filetype] = { s.linter }
|
linters[s.filetype] = { s.linter }
|
||||||
@ -187,9 +193,124 @@ vim.api.nvim_create_user_command('TreesitterInstallAll', function()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end, { desc = 'Install all Treesitter parsers defined in ensure_installed' })
|
end, { desc = 'Install all Treesitter parsers defined in ensure_installed' })
|
||||||
|
|
||||||
|
vim.api.nvim_create_user_command('General', function()
|
||||||
|
print(vim.inspect(general))
|
||||||
|
end, {})
|
||||||
|
|
||||||
|
local special_sources = {
|
||||||
|
lua_ls = 'lua',
|
||||||
|
eslint = 'eslint',
|
||||||
|
}
|
||||||
|
|
||||||
|
vim.diagnostic.config({
|
||||||
|
underline = true,
|
||||||
|
severity_sort = true,
|
||||||
|
update_in_insert = true,
|
||||||
|
|
||||||
|
virtual_text = {
|
||||||
|
format = function(diagnostic)
|
||||||
|
local src = diagnostic.source and (special_sources[diagnostic.source] or diagnostic.source)
|
||||||
|
if src then
|
||||||
|
return string.format('%s: %s', src, diagnostic.message)
|
||||||
|
end
|
||||||
|
return diagnostic.message
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
float = {
|
||||||
|
border = 'rounded',
|
||||||
|
header = '',
|
||||||
|
format = function(diagnostic)
|
||||||
|
local src = diagnostic.source and (special_sources[diagnostic.source] or diagnostic.source)
|
||||||
|
if src then
|
||||||
|
return string.format('%s: %s', src, diagnostic.message)
|
||||||
|
end
|
||||||
|
return diagnostic.message
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Override the virtual text diagnostic handler so that the most severe diagnostic is shown first.
|
||||||
|
local show_handler = vim.diagnostic.handlers.virtual_text.show
|
||||||
|
assert(show_handler)
|
||||||
|
local hide_handler = vim.diagnostic.handlers.virtual_text.hide
|
||||||
|
vim.diagnostic.handlers.virtual_text = {
|
||||||
|
show = function(ns, bufnr, diagnostics, opts)
|
||||||
|
table.sort(diagnostics, function(diag1, diag2)
|
||||||
|
return diag1.severity > diag2.severity
|
||||||
|
end)
|
||||||
|
return show_handler(ns, bufnr, diagnostics, opts)
|
||||||
|
end,
|
||||||
|
hide = hide_handler,
|
||||||
|
}
|
||||||
|
|
||||||
|
vim.api.nvim_create_autocmd('LspAttach', {
|
||||||
|
group = vim.api.nvim_create_augroup('minimal_lsp', { clear = true }),
|
||||||
|
callback = function(ev)
|
||||||
|
local client = vim.lsp.get_client_by_id(ev.data.client_id)
|
||||||
|
if not client then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Enable native completion
|
||||||
|
if client:supports_method('textDocument/completion') then
|
||||||
|
vim.lsp.completion.enable(true, client.id, ev.buf, { autotrigger = true })
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
-- Optional: annotate completion items with their kind
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
vim.lsp.handlers['textDocument/completion'] = function(err, result, ctx, config)
|
||||||
|
if err or not result then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
for _, item in ipairs(result.items or result) do
|
||||||
|
if item.kind then
|
||||||
|
local kind = vim.lsp.protocol.CompletionItemKind[item.kind] or ''
|
||||||
|
item.menu = '[' .. kind .. ']'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return vim.lsp.completion._on_completion_result(err, result, ctx, config)
|
||||||
|
end
|
||||||
|
|
||||||
---------------------------------------------------------------------
|
---------------------------------------------------------------------
|
||||||
-- Plugins
|
-- Plugins
|
||||||
---------------------------------------------------------------------
|
---------------------------------------------------------------------
|
||||||
|
vim.api.nvim_create_autocmd('LspAttach', {
|
||||||
|
group = vim.api.nvim_create_augroup('lsp_attach_timing', { clear = true }),
|
||||||
|
callback = function(ev)
|
||||||
|
local client = vim.lsp.get_client_by_id(ev.data.client_id)
|
||||||
|
if client then
|
||||||
|
vim.notify(
|
||||||
|
string.format('LSP attached: %s (buf: %d)', client.name, ev.buf),
|
||||||
|
vim.log.levels.INFO
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
local function enable_lsp_with_timing(lsp_name)
|
||||||
|
local start_time = vim.uv.hrtime()
|
||||||
|
|
||||||
|
vim.lsp.enable(lsp_name)
|
||||||
|
|
||||||
|
-- Track when the server actually attaches
|
||||||
|
local group = vim.api.nvim_create_augroup('lsp_timing_' .. lsp_name, { clear = true })
|
||||||
|
vim.api.nvim_create_autocmd('LspAttach', {
|
||||||
|
group = group,
|
||||||
|
callback = function(ev)
|
||||||
|
local client = vim.lsp.get_client_by_id(ev.data.client_id)
|
||||||
|
if client and client.name == lsp_name then
|
||||||
|
local elapsed = (vim.uv.hrtime() - start_time) / 1e6 -- Convert to milliseconds
|
||||||
|
vim.notify(string.format('%s attached in %.2f ms', lsp_name, elapsed), vim.log.levels.INFO)
|
||||||
|
vim.api.nvim_del_augroup_by_id(group)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
return {
|
return {
|
||||||
{ 'windwp/nvim-ts-autotag', config = true },
|
{ 'windwp/nvim-ts-autotag', config = true },
|
||||||
{ 'windwp/nvim-autopairs', event = 'InsertEnter', config = true },
|
{ 'windwp/nvim-autopairs', event = 'InsertEnter', config = true },
|
||||||
@ -210,7 +331,8 @@ return {
|
|||||||
table.insert(lsp_configs, lsp_name)
|
table.insert(lsp_configs, lsp_name)
|
||||||
|
|
||||||
-- Native enable call (Neovim ≥ 0.11)
|
-- Native enable call (Neovim ≥ 0.11)
|
||||||
vim.lsp.enable(lsp_name)
|
-- vim.lsp.enable(lsp_name)
|
||||||
|
enable_lsp_with_timing(lsp_name)
|
||||||
else
|
else
|
||||||
vim.notify('Unknown LSP: ' .. lsp, vim.log.levels.WARN)
|
vim.notify('Unknown LSP: ' .. lsp, vim.log.levels.WARN)
|
||||||
end
|
end
|
||||||
|
|||||||
33
lua/utils.lua
Normal file
33
lua/utils.lua
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
--- Appends `new_names` to `root_files` if `field` is found in any such file in any ancestor of `fname`.
|
||||||
|
---
|
||||||
|
--- NOTE: this does a "breadth-first" search, so is broken for multi-project workspaces:
|
||||||
|
--- https://github.com/neovim/nvim-lspconfig/issues/3818#issuecomment-2848836794
|
||||||
|
---
|
||||||
|
--- @param root_files string[] List of root-marker files to append to.
|
||||||
|
--- @param new_names string[] Potential root-marker filenames (e.g. `{ 'package.json', 'package.json5' }`) to inspect for the given `field`.
|
||||||
|
--- @param field string Field to search for in the given `new_names` files.
|
||||||
|
--- @param fname string Full path of the current buffer name to start searching upwards from.
|
||||||
|
function M.root_markers_with_field(root_files, new_names, field, fname)
|
||||||
|
local path = vim.fn.fnamemodify(fname, ':h')
|
||||||
|
local found = vim.fs.find(new_names, { path = path, upward = true })
|
||||||
|
|
||||||
|
for _, f in ipairs(found or {}) do
|
||||||
|
-- Match the given `field`.
|
||||||
|
for line in io.lines(f) do
|
||||||
|
if line:find(field) then
|
||||||
|
root_files[#root_files + 1] = vim.fs.basename(f)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return root_files
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.insert_package_json(root_files, field, fname)
|
||||||
|
return M.root_markers_with_field(root_files, { 'package.json', 'package.json5' }, field, fname)
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
Loading…
Reference in New Issue
Block a user