commit e69b81ad94daf12401ad3d50bdabe440a4fd9466
parent 63ece2b151e3d051d9366452441f64430ae5b45e
Author: Tomasz N <przepompownia@users.noreply.github.com>
Date: Wed, 17 Sep 2025 18:07:45 +0200
fix(lsp): treat 2-triggers-at-once as "last char wins" #35435
Problem:
If there are 2 language servers with different trigger chars (`-` and
`>`), and a keymap inputs both simultaneously (`->`), then `>` doesn't
trigger. We get completion items from server1 only.
This happens because the `completion_timer` for the `-` trigger is still
pending.
Solution:
If the next character arrived enough quickly (< 25 ms), replace the
existing deferred autotrigger with a new one that matches this later
character.
Diffstat:
2 files changed, 32 insertions(+), 1 deletion(-)
diff --git a/runtime/lua/vim/lsp/completion.lua b/runtime/lua/vim/lsp/completion.lua
@@ -571,7 +571,10 @@ local function on_insert_char_pre(handle)
local char = api.nvim_get_vvar('char')
local matched_clients = handle.triggers[char]
- if not completion_timer and matched_clients then
+ -- Discard pending trigger char, complete the "latest" one.
+ -- Can happen if a mapping inputs multiple trigger chars simultaneously.
+ reset_timer()
+ if matched_clients then
completion_timer = assert(vim.uv.new_timer())
completion_timer:start(25, 0, function()
reset_timer()
diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua
@@ -1037,6 +1037,34 @@ describe('vim.lsp.completion: protocol', function()
end)
end)
+ it('treats 2-triggers-at-once as "last char wins"', function()
+ local results1 = {
+ isIncomplete = false,
+ items = {
+ {
+ label = 'first',
+ },
+ },
+ }
+ create_server('dummy1', results1, { trigger_chars = { '-' } })
+ local results2 = {
+ isIncomplete = false,
+ items = {
+ {
+ label = 'second',
+ },
+ },
+ }
+ create_server('dummy2', results2, { trigger_chars = { '>' } })
+
+ feed('i->')
+
+ assert_matches(function(matches)
+ eq(1, #matches)
+ eq('second', matches[1].word)
+ end)
+ end)
+
it('executes commands', function()
local completion_list = {
isIncomplete = false,