feat(lsp): overwrite omnifunc/tagfunc set by ftplugin #22267

Problem:
Some built-in ftplugins set omnifunc/tagfunc/formatexpr which causes
lsp.lua:set_defaults() to skip setup of defaults for those filetypes.
For example the C++ ftplugin has:
    omnifunc=ccomplete#Complete
          Last set from /usr/share/nvim/runtime/ftplugin/c.vim line 30
so the changes done in #95c65a6b221fe6e1cf91e8322e7d7571dc511a71
will always be skipped for C++ files.

Solution:
Overwrite omnifunc/tagfunc/formatexpr options that were set by stock
ftplugin.

Fixes #21001
This commit is contained in:
Michal Liszcz 2023-03-09 15:12:56 +01:00 committed by GitHub
parent ce0fddf5ae
commit 9ef7297ef1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 153 additions and 6 deletions

View File

@ -51,8 +51,11 @@ Starting a LSP client will automatically report diagnostics via
|vim.diagnostic|. Read |vim.diagnostic.config()| to learn how to customize the
display.
It also sets some buffer options if the options are otherwise empty and if the
language server supports the functionality.
It also sets some buffer options if the language server supports the
functionality and if the options are otherwise empty or have the default
values set by Nvim runtime files (e.g. a ftplugin). In the latter case,
the default values are not restored when the LSP client is detached from
the buffer.
- 'omnifunc' is set to |vim.lsp.omnifunc()|. This allows to trigger completion
using |i_CTRL-X_CTRL-O|

View File

@ -1112,19 +1112,43 @@ function lsp.start_client(config)
end
end
---@private
-- Determines whether the given option can be set by `set_defaults`.
local function is_empty_or_default(bufnr, option)
if vim.bo[bufnr][option] == '' then
return true
end
local old_bufnr = vim.fn.bufnr('')
local last_set_from = vim.fn.gettext('\n\tLast set from ')
local line = vim.fn.gettext(' line ')
vim.cmd.buffer(bufnr)
local scriptname = vim.fn
.execute('verbose set ' .. option .. '?')
:match(last_set_from .. '(.*)' .. line .. '%d+')
vim.cmd.buffer(old_bufnr)
if not scriptname then
return false
end
local vimruntime = vim.fn.getenv('VIMRUNTIME')
return vim.startswith(vim.fn.expand(scriptname), vim.fn.expand(vimruntime))
end
---@private
local function set_defaults(client, bufnr)
local capabilities = client.server_capabilities
if capabilities.definitionProvider and vim.bo[bufnr].tagfunc == '' then
if capabilities.definitionProvider and is_empty_or_default(bufnr, 'tagfunc') then
vim.bo[bufnr].tagfunc = 'v:lua.vim.lsp.tagfunc'
end
if capabilities.completionProvider and vim.bo[bufnr].omnifunc == '' then
if capabilities.completionProvider and is_empty_or_default(bufnr, 'omnifunc') then
vim.bo[bufnr].omnifunc = 'v:lua.vim.lsp.omnifunc'
end
if
capabilities.documentRangeFormattingProvider
and vim.bo[bufnr].formatprg == ''
and vim.bo[bufnr].formatexpr == ''
and is_empty_or_default(bufnr, 'formatprg')
and is_empty_or_default(bufnr, 'formatexpr')
then
vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr()'
end

View File

@ -932,6 +932,23 @@ function tests.basic_formatting()
}
end
function tests.set_defaults_all_capabilities()
skeleton {
on_init = function(_)
return {
capabilities = {
definitionProvider = true,
completionProvider = true,
documentRangeFormattingProvider = true,
}
}
end;
body = function()
notify('test')
end;
}
end
-- Tests will be indexed by test_name
local test_name = arg[1]
local timeout = arg[2]

View File

@ -31,6 +31,11 @@ local fake_lsp_code = lsp_helpers.fake_lsp_code
local fake_lsp_logfile = lsp_helpers.fake_lsp_logfile
local test_rpc_server = lsp_helpers.test_rpc_server
local function get_buf_option(name, bufnr)
bufnr = bufnr or "BUFFER"
return exec_lua(string.format("return vim.api.nvim_buf_get_option(%s, '%s')", bufnr, name))
end
-- TODO(justinmk): hangs on Windows https://github.com/neovim/neovim/pull/11837
if skip(is_os('win')) then return end
@ -313,6 +318,104 @@ describe('LSP', function()
}
end)
it('should set default options on attach', function()
local client
test_rpc_server {
test_name = "set_defaults_all_capabilities";
on_setup = function()
exec_lua [[
BUFFER = vim.api.nvim_create_buf(false, true)
]]
end;
on_init = function(_client)
client = _client
exec_lua("lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)")
end;
on_handler = function(_, _, ctx)
if ctx.method == 'test' then
eq('v:lua.vim.lsp.tagfunc', get_buf_option("tagfunc"))
eq('v:lua.vim.lsp.omnifunc', get_buf_option("omnifunc"))
eq('v:lua.vim.lsp.formatexpr()', get_buf_option("formatexpr"))
client.stop()
end
end;
on_exit = function(_, _)
eq('', get_buf_option("tagfunc"))
eq('', get_buf_option("omnifunc"))
eq('', get_buf_option("formatexpr"))
end;
}
end)
it('should overwrite options set by ftplugins', function()
local client
test_rpc_server {
test_name = "set_defaults_all_capabilities";
on_setup = function()
exec_lua [[
vim.api.nvim_command('filetype plugin on')
BUFFER_1 = vim.api.nvim_create_buf(false, true)
BUFFER_2 = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_option(BUFFER_1, 'filetype', 'man')
vim.api.nvim_buf_set_option(BUFFER_2, 'filetype', 'xml')
]]
eq('v:lua.require\'man\'.goto_tag', get_buf_option("tagfunc", "BUFFER_1"))
eq('xmlcomplete#CompleteTags', get_buf_option("omnifunc", "BUFFER_2"))
eq('xmlformat#Format()', get_buf_option("formatexpr", "BUFFER_2"))
end;
on_init = function(_client)
client = _client
exec_lua("lsp.buf_attach_client(BUFFER_1, TEST_RPC_CLIENT_ID)")
exec_lua("lsp.buf_attach_client(BUFFER_2, TEST_RPC_CLIENT_ID)")
end;
on_handler = function(_, _, ctx)
if ctx.method == 'test' then
eq('v:lua.vim.lsp.tagfunc', get_buf_option("tagfunc", "BUFFER_1"))
eq('v:lua.vim.lsp.omnifunc', get_buf_option("omnifunc", "BUFFER_2"))
eq('v:lua.vim.lsp.formatexpr()', get_buf_option("formatexpr", "BUFFER_2"))
client.stop()
end
end;
on_exit = function(_, _)
eq('', get_buf_option("tagfunc", "BUFFER_1"))
eq('', get_buf_option("omnifunc", "BUFFER_2"))
eq('', get_buf_option("formatexpr", "BUFFER_2"))
end;
}
end)
it('should not overwrite user-defined options', function()
local client
test_rpc_server {
test_name = "set_defaults_all_capabilities";
on_setup = function()
exec_lua [[
BUFFER = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_option(BUFFER, 'tagfunc', 'tfu')
vim.api.nvim_buf_set_option(BUFFER, 'omnifunc', 'ofu')
vim.api.nvim_buf_set_option(BUFFER, 'formatexpr', 'fex')
]]
end;
on_init = function(_client)
client = _client
exec_lua("lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)")
end;
on_handler = function(_, _, ctx)
if ctx.method == 'test' then
eq('tfu', get_buf_option("tagfunc"))
eq('ofu', get_buf_option("omnifunc"))
eq('fex', get_buf_option("formatexpr"))
client.stop()
end
end;
on_exit = function(_, _)
eq('tfu', get_buf_option("tagfunc"))
eq('ofu', get_buf_option("omnifunc"))
eq('fex', get_buf_option("formatexpr"))
end;
}
end)
it('should detach buffer on bufwipe', function()
clear()
exec_lua(create_server_definition)