mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +00:00 
			
		
		
		
	feat(lsp): support completion context #32793
Problem: vim.lsp.completion with "autotrigger" enabled, does not send completion context, even though it has all the necessary info. Solution: Include the context for "autotrigger". trigger() also optionally accepts context when manually invoked.
This commit is contained in:
		
				
					committed by
					
						
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							67c39f5eca
						
					
				
				
					commit
					3b0fe2659e
				
			@@ -1869,9 +1869,14 @@ enable({enable}, {client_id}, {bufnr}, {opts})
 | 
				
			|||||||
      • {opts}       (`vim.lsp.completion.BufferOpts?`) See
 | 
					      • {opts}       (`vim.lsp.completion.BufferOpts?`) See
 | 
				
			||||||
                     |vim.lsp.completion.BufferOpts|.
 | 
					                     |vim.lsp.completion.BufferOpts|.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
trigger()                                       *vim.lsp.completion.trigger()*
 | 
					trigger({opts})                                 *vim.lsp.completion.trigger()*
 | 
				
			||||||
    Triggers LSP completion once in the current buffer.
 | 
					    Triggers LSP completion once in the current buffer.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Parameters: ~
 | 
				
			||||||
 | 
					      • {opts}  (`table?`) A table with the following fields:
 | 
				
			||||||
 | 
					                • {ctx}? (`lsp.CompletionContext`) Completion context.
 | 
				
			||||||
 | 
					                  Defaults to a trigger kind of `invoked`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
==============================================================================
 | 
					==============================================================================
 | 
				
			||||||
Lua module: vim.lsp.inlay_hint                                *lsp-inlay_hint*
 | 
					Lua module: vim.lsp.inlay_hint                                *lsp-inlay_hint*
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -313,6 +313,8 @@ LSP
 | 
				
			|||||||
• |vim.lsp.enable()| has been added to enable servers.
 | 
					• |vim.lsp.enable()| has been added to enable servers.
 | 
				
			||||||
• |vim.lsp.buf.code_action()| resolves the `command` property during the
 | 
					• |vim.lsp.buf.code_action()| resolves the `command` property during the
 | 
				
			||||||
  `codeAction/resolve` request.
 | 
					  `codeAction/resolve` request.
 | 
				
			||||||
 | 
					• The `textDocument/completion` request now includes the completion context in
 | 
				
			||||||
 | 
					  its parameters.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
LUA
 | 
					LUA
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -424,9 +424,10 @@ end
 | 
				
			|||||||
--- @param clients table<integer, vim.lsp.Client> # keys != client_id
 | 
					--- @param clients table<integer, vim.lsp.Client> # keys != client_id
 | 
				
			||||||
--- @param bufnr integer
 | 
					--- @param bufnr integer
 | 
				
			||||||
--- @param win integer
 | 
					--- @param win integer
 | 
				
			||||||
 | 
					--- @param ctx? lsp.CompletionContext
 | 
				
			||||||
--- @param callback fun(responses: table<integer, { err: lsp.ResponseError, result: vim.lsp.CompletionResult }>)
 | 
					--- @param callback fun(responses: table<integer, { err: lsp.ResponseError, result: vim.lsp.CompletionResult }>)
 | 
				
			||||||
--- @return function # Cancellation function
 | 
					--- @return function # Cancellation function
 | 
				
			||||||
local function request(clients, bufnr, win, callback)
 | 
					local function request(clients, bufnr, win, ctx, callback)
 | 
				
			||||||
  local responses = {} --- @type table<integer, { err: lsp.ResponseError, result: any }>
 | 
					  local responses = {} --- @type table<integer, { err: lsp.ResponseError, result: any }>
 | 
				
			||||||
  local request_ids = {} --- @type table<integer, integer>
 | 
					  local request_ids = {} --- @type table<integer, integer>
 | 
				
			||||||
  local remaining_requests = vim.tbl_count(clients)
 | 
					  local remaining_requests = vim.tbl_count(clients)
 | 
				
			||||||
@@ -434,6 +435,8 @@ local function request(clients, bufnr, win, callback)
 | 
				
			|||||||
  for _, client in pairs(clients) do
 | 
					  for _, client in pairs(clients) do
 | 
				
			||||||
    local client_id = client.id
 | 
					    local client_id = client.id
 | 
				
			||||||
    local params = lsp.util.make_position_params(win, client.offset_encoding)
 | 
					    local params = lsp.util.make_position_params(win, client.offset_encoding)
 | 
				
			||||||
 | 
					    --- @cast params lsp.CompletionParams
 | 
				
			||||||
 | 
					    params.context = ctx
 | 
				
			||||||
    local ok, request_id = client:request(ms.textDocument_completion, params, function(err, result)
 | 
					    local ok, request_id = client:request(ms.textDocument_completion, params, function(err, result)
 | 
				
			||||||
      responses[client_id] = { err = err, result = result }
 | 
					      responses[client_id] = { err = err, result = result }
 | 
				
			||||||
      remaining_requests = remaining_requests - 1
 | 
					      remaining_requests = remaining_requests - 1
 | 
				
			||||||
@@ -457,7 +460,10 @@ local function request(clients, bufnr, win, callback)
 | 
				
			|||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function trigger(bufnr, clients)
 | 
					--- @param bufnr integer
 | 
				
			||||||
 | 
					--- @param clients vim.lsp.Client[]
 | 
				
			||||||
 | 
					--- @param ctx? lsp.CompletionContext
 | 
				
			||||||
 | 
					local function trigger(bufnr, clients, ctx)
 | 
				
			||||||
  reset_timer()
 | 
					  reset_timer()
 | 
				
			||||||
  Context:cancel_pending()
 | 
					  Context:cancel_pending()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -473,7 +479,7 @@ local function trigger(bufnr, clients)
 | 
				
			|||||||
  local start_time = vim.uv.hrtime()
 | 
					  local start_time = vim.uv.hrtime()
 | 
				
			||||||
  Context.last_request_time = start_time
 | 
					  Context.last_request_time = start_time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  local cancel_request = request(clients, bufnr, win, function(responses)
 | 
					  local cancel_request = request(clients, bufnr, win, ctx, function(responses)
 | 
				
			||||||
    local end_time = vim.uv.hrtime()
 | 
					    local end_time = vim.uv.hrtime()
 | 
				
			||||||
    rtt_ms = compute_new_average((end_time - start_time) * ns_to_ms)
 | 
					    rtt_ms = compute_new_average((end_time - start_time) * ns_to_ms)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -527,11 +533,20 @@ local function on_insert_char_pre(handle)
 | 
				
			|||||||
      reset_timer()
 | 
					      reset_timer()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      local debounce_ms = next_debounce()
 | 
					      local debounce_ms = next_debounce()
 | 
				
			||||||
 | 
					      local ctx = { triggerKind = protocol.CompletionTriggerKind.TriggerForIncompleteCompletions }
 | 
				
			||||||
      if debounce_ms == 0 then
 | 
					      if debounce_ms == 0 then
 | 
				
			||||||
        vim.schedule(M.trigger)
 | 
					        vim.schedule(function()
 | 
				
			||||||
 | 
					          M.trigger(ctx)
 | 
				
			||||||
 | 
					        end)
 | 
				
			||||||
      else
 | 
					      else
 | 
				
			||||||
        completion_timer = new_timer()
 | 
					        completion_timer = new_timer()
 | 
				
			||||||
        completion_timer:start(debounce_ms, 0, vim.schedule_wrap(M.trigger))
 | 
					        completion_timer:start(
 | 
				
			||||||
 | 
					          debounce_ms,
 | 
				
			||||||
 | 
					          0,
 | 
				
			||||||
 | 
					          vim.schedule_wrap(function()
 | 
				
			||||||
 | 
					            M.trigger(ctx)
 | 
				
			||||||
 | 
					          end)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -545,7 +560,11 @@ local function on_insert_char_pre(handle)
 | 
				
			|||||||
    completion_timer:start(25, 0, function()
 | 
					    completion_timer:start(25, 0, function()
 | 
				
			||||||
      reset_timer()
 | 
					      reset_timer()
 | 
				
			||||||
      vim.schedule(function()
 | 
					      vim.schedule(function()
 | 
				
			||||||
        trigger(api.nvim_get_current_buf(), matched_clients)
 | 
					        trigger(
 | 
				
			||||||
 | 
					          api.nvim_get_current_buf(),
 | 
				
			||||||
 | 
					          matched_clients,
 | 
				
			||||||
 | 
					          { triggerKind = protocol.CompletionTriggerKind.TriggerCharacter, triggerCharacter = char }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
      end)
 | 
					      end)
 | 
				
			||||||
    end)
 | 
					    end)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
@@ -771,11 +790,20 @@ function M.enable(enable, client_id, bufnr, opts)
 | 
				
			|||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--- @inlinedoc
 | 
				
			||||||
 | 
					--- @class vim.lsp.completion.trigger.Opts
 | 
				
			||||||
 | 
					--- @field ctx? lsp.CompletionContext Completion context. Defaults to a trigger kind of `invoked`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
--- Triggers LSP completion once in the current buffer.
 | 
					--- Triggers LSP completion once in the current buffer.
 | 
				
			||||||
function M.trigger()
 | 
					---
 | 
				
			||||||
 | 
					--- @param opts? vim.lsp.completion.trigger.Opts
 | 
				
			||||||
 | 
					function M.trigger(opts)
 | 
				
			||||||
 | 
					  opts = opts or {}
 | 
				
			||||||
 | 
					  local ctx = opts.ctx or { triggerKind = protocol.CompletionTriggerKind.Invoked }
 | 
				
			||||||
  local bufnr = api.nvim_get_current_buf()
 | 
					  local bufnr = api.nvim_get_current_buf()
 | 
				
			||||||
  local clients = (buf_handles[bufnr] or {}).clients or {}
 | 
					  local clients = (buf_handles[bufnr] or {}).clients or {}
 | 
				
			||||||
  trigger(bufnr, clients)
 | 
					
 | 
				
			||||||
 | 
					  trigger(bufnr, clients, ctx)
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
--- Implements 'omnifunc' compatible LSP completion.
 | 
					--- Implements 'omnifunc' compatible LSP completion.
 | 
				
			||||||
@@ -800,7 +828,7 @@ function M._omnifunc(findstart, base)
 | 
				
			|||||||
    return findstart == 1 and -1 or {}
 | 
					    return findstart == 1 and -1 or {}
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  trigger(bufnr, clients)
 | 
					  trigger(bufnr, clients, { triggerKind = protocol.CompletionTriggerKind.Invoked })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  -- Return -2 to signal that we should continue completion so that we can
 | 
					  -- Return -2 to signal that we should continue completion so that we can
 | 
				
			||||||
  -- async complete.
 | 
					  -- async complete.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -476,9 +476,7 @@ function protocol.make_client_capabilities()
 | 
				
			|||||||
            'data',
 | 
					            'data',
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        contextSupport = true,
 | 
				
			||||||
        -- TODO(tjdevries): Implement this
 | 
					 | 
				
			||||||
        contextSupport = false,
 | 
					 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      declaration = {
 | 
					      declaration = {
 | 
				
			||||||
        linkSupport = true,
 | 
					        linkSupport = true,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1127,6 +1127,73 @@ describe('vim.lsp.completion: protocol', function()
 | 
				
			|||||||
      eq('foo', matches[1].abbr)
 | 
					      eq('foo', matches[1].abbr)
 | 
				
			||||||
    end)
 | 
					    end)
 | 
				
			||||||
  end)
 | 
					  end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('sends completion context when invoked', function()
 | 
				
			||||||
 | 
					    local params = exec_lua(function()
 | 
				
			||||||
 | 
					      local params
 | 
				
			||||||
 | 
					      local server = _G._create_server({
 | 
				
			||||||
 | 
					        capabilities = {
 | 
				
			||||||
 | 
					          completionProvider = true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        handlers = {
 | 
				
			||||||
 | 
					          ['textDocument/completion'] = function(_, params0, callback)
 | 
				
			||||||
 | 
					            params = params0
 | 
				
			||||||
 | 
					            callback(nil, nil)
 | 
				
			||||||
 | 
					          end,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      local bufnr = vim.api.nvim_get_current_buf()
 | 
				
			||||||
 | 
					      vim.api.nvim_win_set_buf(0, bufnr)
 | 
				
			||||||
 | 
					      vim.lsp.start({
 | 
				
			||||||
 | 
					        name = 'dummy',
 | 
				
			||||||
 | 
					        cmd = server.cmd,
 | 
				
			||||||
 | 
					        on_attach = function(client, bufnr0)
 | 
				
			||||||
 | 
					          vim.lsp.completion.enable(true, client.id, bufnr0)
 | 
				
			||||||
 | 
					        end,
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      vim.lsp.completion.trigger()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return params
 | 
				
			||||||
 | 
					    end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    eq({ triggerKind = 1 }, params.context)
 | 
				
			||||||
 | 
					  end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('sends completion context with trigger characters', function()
 | 
				
			||||||
 | 
					    exec_lua(function()
 | 
				
			||||||
 | 
					      local server = _G._create_server({
 | 
				
			||||||
 | 
					        capabilities = {
 | 
				
			||||||
 | 
					          completionProvider = {
 | 
				
			||||||
 | 
					            triggerCharacters = { 'h' },
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        handlers = {
 | 
				
			||||||
 | 
					          ['textDocument/completion'] = function(_, params, callback)
 | 
				
			||||||
 | 
					            _G.params = params
 | 
				
			||||||
 | 
					            callback(nil, { isIncomplete = false, items = { label = 'hello' } })
 | 
				
			||||||
 | 
					          end,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      local bufnr = vim.api.nvim_get_current_buf()
 | 
				
			||||||
 | 
					      vim.api.nvim_win_set_buf(0, bufnr)
 | 
				
			||||||
 | 
					      vim.lsp.start({
 | 
				
			||||||
 | 
					        name = 'dummy',
 | 
				
			||||||
 | 
					        cmd = server.cmd,
 | 
				
			||||||
 | 
					        on_attach = function(client, bufnr0)
 | 
				
			||||||
 | 
					          vim.lsp.completion.enable(true, client.id, bufnr0, { autotrigger = true })
 | 
				
			||||||
 | 
					        end,
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    feed('ih')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    retry(100, nil, function()
 | 
				
			||||||
 | 
					      eq({ triggerKind = 2, triggerCharacter = 'h' }, exec_lua('return _G.params.context'))
 | 
				
			||||||
 | 
					    end)
 | 
				
			||||||
 | 
					  end)
 | 
				
			||||||
end)
 | 
					end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('vim.lsp.completion: integration', function()
 | 
					describe('vim.lsp.completion: integration', function()
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user