From e69b81ad94daf12401ad3d50bdabe440a4fd9466 Mon Sep 17 00:00:00 2001 From: Tomasz N Date: Wed, 17 Sep 2025 18:07:45 +0200 Subject: [PATCH] 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. --- runtime/lua/vim/lsp/completion.lua | 5 +++- .../functional/plugin/lsp/completion_spec.lua | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/runtime/lua/vim/lsp/completion.lua b/runtime/lua/vim/lsp/completion.lua index eea35c878f..42fe470f14 100644 --- 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 index ec9760a068..6570bbb95c 100644 --- 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,