From cfcdbcf638b5957213b3c1a55d5d1b5659a2fb3e Mon Sep 17 00:00:00 2001 From: Barrett Ruth <62671086+barrettruth@users.noreply.github.com> Date: Sat, 21 Mar 2026 20:09:01 -0400 Subject: [PATCH] feat(lsp): add `buftypes` field to `vim.lsp.Config` (#38380) Problem: `vim.lsp.enable()` skips buffers with `buftype` set, even when `filetype` matches. Solution: Add `buftypes` field to `vim.lsp.Config`. --- runtime/doc/lsp.txt | 8 ++++++++ runtime/doc/news.txt | 2 ++ runtime/lua/vim/lsp.lua | 22 +++++++++++++++----- test/functional/plugin/lsp_spec.lua | 32 +++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 39be03c5e4..692af17dd4 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -827,6 +827,14 @@ Lua module: vim.lsp *lsp-core* ".git": > root_markers = { { 'stylua.toml', '.luarc.json' }, '.git' } < + • {buftypes}? (`string[]`) *lsp-buftypes* Buffer types the client + will attach to, or `nil` for normal (empty `buftype`) + buffers only (default `{ '' }`). Set this to restrict + attachment to specific buffer types such as `help`: >lua + vim.lsp.config('vimdoc_ls', { + buftypes = { 'help' }, + }) +< buf_attach_client({bufnr}, {client_id}) *vim.lsp.buf_attach_client()* diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index ef9f0eb5dc..8bb7a8063b 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -268,6 +268,8 @@ LSP • |:lsp| can be used to interactively manage LSP clients. • |vim.lsp.ClientConfig| gained `workspace_required`. +• |vim.lsp.Config| gained |lsp-buftypes| to control which 'buftype' values a + config will attach to (default: empty buftype only). • You can control the priority of |vim.lsp.Config| `root_markers`. • Support for `textDocument/documentColor`: |lsp-document_color| https://microsoft.github.io/language-server-protocol/specification/#textDocument_documentColor diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 184a784faa..2be7e6add7 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -219,6 +219,16 @@ end --- ``` --- --- @field root_markers? (string|string[])[] +--- +--- [lsp-buftypes]() +--- Buffer types the client will attach to, or `nil` for normal (empty `buftype`) buffers only (default `{ '' }`). +--- Set this to restrict attachment to specific buffer types such as `help`: +--- ```lua +--- vim.lsp.config('vimdoc_ls', { +--- buftypes = { 'help' }, +--- }) +--- ``` +--- @field buftypes? string[] --- Sets the default configuration for an LSP client (or _all_ clients if the special name "*" is --- used). @@ -478,6 +488,7 @@ local function validate_config(config) validate('cmd', config.cmd, validate_cmd, 'expected function or table with executable command') validate('reuse_client', config.reuse_client, 'function', true) validate('filetypes', config.filetypes, 'table', true) + validate('buftypes', config.buftypes, 'table', true) end --- Returns true if: @@ -490,6 +501,12 @@ end --- @param logging boolean local function can_start(bufnr, config, logging) assert(config) + + local allowed_buftypes = config.buftypes or { '' } + if not vim.tbl_contains(allowed_buftypes, vim.bo[bufnr].buftype) then + return false + end + if type(config.filetypes) == 'table' and not vim.tbl_contains(config.filetypes, vim.bo[bufnr].filetype) @@ -520,11 +537,6 @@ end --- @param bufnr integer local function lsp_enable_callback(bufnr) - -- Only ever attach to buffers that represent an actual file. - if vim.bo[bufnr].buftype ~= '' then - return - end - -- Stop any clients that no longer apply to this buffer. local clients = lsp.get_clients({ bufnr = bufnr, _uninitialized = true }) for _, client in ipairs(clients) do diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 7249ac2d47..84edf20a8c 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -6998,6 +6998,38 @@ describe('LSP', function() ) end) + it('attaches to buftype=help when buftypes includes help', function() + exec_lua(create_server_definition) + + local tmp1 = t.tmpname(true) + + eq( + { 1, 0 }, + exec_lua(function() + local server = _G._create_server() + + vim.lsp.config('with_help', { + cmd = server.cmd, + buftypes = { '', 'help' }, + }) + vim.lsp.config('without_help', { + cmd = server.cmd, + }) + + vim.lsp.enable('with_help') + vim.lsp.enable('without_help') + + vim.cmd.edit(assert(tmp1)) + vim.bo.buftype = 'help' + vim.bo.filetype = 'help' + + local with = vim.lsp.get_clients({ name = 'with_help', bufnr = 0 }) + local without = vim.lsp.get_clients({ name = 'without_help', bufnr = 0 }) + return { #with, #without } + end) + ) + end) + it('does not allow wildcards in config name', function() local err = '.../lsp.lua:0: name: expected non%-wildcard string, got foo%*%. Info: LSP config name cannot contain wildcard %("%*"%)'