Neovim为每个缓冲区重复启动LSP客户端问题求助
解决Neovim LSP重复启动实例的问题
问题根源
你遇到的tsserver、tailwindcss等LSP重复启动的问题,核心原因是不同文件被识别为独立工作区(root_dir),Neovim会为每个独立工作区启动新的LSP客户端。
具体来看:
- 你手动为
tsserver指定了root_dir = require("lspconfig.util").find_git_ancestor,仅以Git仓库根目录作为工作区根路径 - 其他LSP(如
tailwindcss)使用默认检测逻辑,优先识别package.json、tailwind.config.js等配置文件所在目录 - 当文件所在目录与Git根目录层级不同,或存在独立配置文件时,会被判定为不同工作区,触发新的LSP实例启动
修复方案
统一前端相关LSP的root_dir检测逻辑,确保同一项目下的所有文件被识别为同一个工作区:
步骤1:调整LSP配置结构
将需要统一root_dir的LSP从循环列表中移除,单独配置以覆盖默认逻辑:
-- Mappings. -- See `:help vim.diagnostic.*` for documentation on any of the below functions local opts = { noremap = true, silent = true } vim.api.nvim_set_keymap("n", "<space>e", "<cmd>lua vim.diagnostic.open_float()<CR>", opts) vim.api.nvim_set_keymap("n", "[d", "<cmd>lua vim.diagnostic.goto_prev()<CR>", opts) vim.api.nvim_set_keymap("n", "]d", "<cmd>lua vim.diagnostic.goto_next()<CR>", opts) vim.api.nvim_set_keymap("n", "<space>q", "<cmd>lua vim.diagnostic.setloclist()<CR>", opts) -- Use an on_attach function to only map the following keys -- after the language server attaches to the current buffer local on_attach = function(client, bufnr) vim.api.nvim_buf_set_option(bufnr, "omnifunc", "v:lua.vim.lsp.omnifunc") -- Mappings. -- See `:help vim.lsp.*` for documentation on any of the below functions vim.api.nvim_buf_set_keymap(bufnr, "n", "gd", "<cmd>lua vim.lsp.buf.definition()<CR>", opts) end local capabilities = require("cmp_nvim_lsp").default_capabilities(vim.lsp.protocol.make_client_capabilities()) local rawCapabilitiesWithoutFormatting = vim.lsp.protocol.make_client_capabilities() rawCapabilitiesWithoutFormatting.textDocument.formatting = false rawCapabilitiesWithoutFormatting.textDocument.rangeFormatting = false local capabilitiesWithoutFormatting = require("cmp_nvim_lsp").default_capabilities(rawCapabilitiesWithoutFormatting) -- 保留无需特殊配置的LSP local basic_servers = { "bashls", "clangd", "cssls", "eslint", "gopls", "html", "jsonls", "rust_analyzer", "svelte", "vimls", "volar", "prismals", "marksman", } for _, lsp in pairs(basic_servers) do require("lspconfig")[lsp].setup({ on_attach = on_attach, flags = { debounce_text_changes = 300, }, capabilities = capabilities, }) end -- 统一配置前端相关LSP的root_dir local shared_root_dir = require("lspconfig.util").find_git_ancestor -- setup tsserver require("lspconfig").tsserver.setup({ on_attach = function(client, bufnr) client.server_capabilities.document_formatting = false client.server_capabilities.document_range_formatting = false on_attach(client, bufnr) end, flags = { debounce_text_changes = 300, }, capabilities = capabilitiesWithoutFormatting, settings = { documentFormatting = false, }, root_dir = shared_root_dir, }) -- 单独配置tailwindcss,统一root_dir require("lspconfig").tailwindcss.setup({ on_attach = on_attach, flags = { debounce_text_changes = 300, }, capabilities = capabilities, root_dir = shared_root_dir, }) vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { underline = true, -- This sets the spacing and the prefix, obviously. virtual_text = { spacing = 4, }, signs = true, update_in_insert = true, })
步骤2:验证修复效果
重启Neovim后打开多个项目文件,执行:LspInfo命令查看:
- 同一项目下的文件应关联同一个LSP客户端实例
- 不再出现重复启动的
tsserver或tailwindcss进程
可选:自定义root_dir检测逻辑
如果你更倾向于以项目配置文件(如package.json)作为工作区根目录,可替换shared_root_dir为:
local shared_root_dir = require("lspconfig.util").root_pattern("package.json", "tsconfig.json", "tailwind.config.js")
该逻辑会优先识别包含上述配置文件的最近父目录作为工作区根,更符合前端项目常规结构。
额外注意事项
- 避免对同一个LSP多次调用
setup,否则可能导致配置冲突 - 若为Monorepo项目,可根据需求调整
root_dir逻辑,比如使用root_pattern("package.json")让每个子包作为独立工作区(此场景下多LSP实例属于正常情况)
内容的提问来源于stack exchange,提问作者Andrew
相关产品推荐
相关产品推荐

