mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +00:00 
			
		
		
		
	backport: feat(lsp): pass resolved config to cmd() #34560
Problem:
In LSP configs, the function form of `cmd()` cannot easily get the
resolved root dir (workspace). One of the main use-cases of a dynamic
`cmd()` is to be able to start a new server  whose binary may be located
*in the workspace* ([example](https://github.com/neovim/nvim-lspconfig/pull/3912)).
Compare `reuse_client()`, which also receives the resolved config.
Solution:
Pass the resolved config to `cmd()`.
(cherry picked from commit 32f30c4874)
Co-authored-by: Julian Visser <12615757+justmejulian@users.noreply.github.com>
			
			
This commit is contained in:
		@@ -1309,16 +1309,16 @@ Lua module: vim.lsp.client                                        *lsp-client*
 | 
			
		||||
                               • Note: To send an empty dictionary use
 | 
			
		||||
                                 |vim.empty_dict()|, else it will be encoded
 | 
			
		||||
                                 as an array.
 | 
			
		||||
      • {cmd}                  (`string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient`)
 | 
			
		||||
      • {cmd}                  (`string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers, config: vim.lsp.ClientConfig): vim.lsp.rpc.PublicClient`)
 | 
			
		||||
                               Command `string[]` that launches the language
 | 
			
		||||
                               server (treated as in |jobstart()|, must be
 | 
			
		||||
                               absolute or on `$PATH`, shell constructs like
 | 
			
		||||
                               "~" are not expanded), or function that creates
 | 
			
		||||
                               an RPC client. Function receives a
 | 
			
		||||
                               `dispatchers` table and returns a table with
 | 
			
		||||
                               member functions `request`, `notify`,
 | 
			
		||||
                               `is_closing` and `terminate`. See
 | 
			
		||||
                               |vim.lsp.rpc.request()|,
 | 
			
		||||
                               `dispatchers` table and the resolved `config`,
 | 
			
		||||
                               and must return a table with member functions
 | 
			
		||||
                               `request`, `notify`, `is_closing` and
 | 
			
		||||
                               `terminate`. See |vim.lsp.rpc.request()|,
 | 
			
		||||
                               |vim.lsp.rpc.notify()|. For TCP there is a
 | 
			
		||||
                               builtin RPC client factory:
 | 
			
		||||
                               |vim.lsp.rpc.connect()|
 | 
			
		||||
 
 | 
			
		||||
@@ -283,6 +283,8 @@ LSP
 | 
			
		||||
  its parameters.
 | 
			
		||||
• |vim.lsp.Config| gained `workspace_required`.
 | 
			
		||||
• `root_markers` in |vim.lsp.Config| can now be ordered by priority.
 | 
			
		||||
• The function form of `cmd` in a vim.lsp.Config or vim.lsp.ClientConfig
 | 
			
		||||
  receives the resolved config as the second arg: `cmd(dispatchers, config)`.
 | 
			
		||||
 | 
			
		||||
LUA
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -45,11 +45,11 @@ local validate = vim.validate
 | 
			
		||||
---
 | 
			
		||||
--- Command `string[]` that launches the language server (treated as in |jobstart()|, must be
 | 
			
		||||
--- absolute or on `$PATH`, shell constructs like "~" are not expanded), or function that creates an
 | 
			
		||||
--- RPC client. Function receives a `dispatchers` table and returns a table with member functions
 | 
			
		||||
--- `request`, `notify`, `is_closing` and `terminate`.
 | 
			
		||||
--- RPC client. Function receives a `dispatchers` table and the resolved `config`, and must return
 | 
			
		||||
--- a table with member functions `request`, `notify`, `is_closing` and `terminate`.
 | 
			
		||||
--- See |vim.lsp.rpc.request()|, |vim.lsp.rpc.notify()|.
 | 
			
		||||
--- For TCP there is a builtin RPC client factory: |vim.lsp.rpc.connect()|
 | 
			
		||||
--- @field cmd string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient
 | 
			
		||||
--- @field cmd string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers, config: vim.lsp.ClientConfig): vim.lsp.rpc.PublicClient
 | 
			
		||||
---
 | 
			
		||||
--- Directory to launch the `cmd` process. Not related to `root_dir`.
 | 
			
		||||
--- (default: cwd)
 | 
			
		||||
@@ -455,7 +455,7 @@ function Client.create(config)
 | 
			
		||||
  -- Start the RPC client.
 | 
			
		||||
  local config_cmd = config.cmd
 | 
			
		||||
  if type(config_cmd) == 'function' then
 | 
			
		||||
    self.rpc = config_cmd(dispatchers)
 | 
			
		||||
    self.rpc = config_cmd(dispatchers, config)
 | 
			
		||||
  else
 | 
			
		||||
    self.rpc = lsp.rpc.start(config_cmd, dispatchers, {
 | 
			
		||||
      cwd = config.cmd_cwd,
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@ M.create_server_definition = function()
 | 
			
		||||
    local server = {}
 | 
			
		||||
    server.messages = {}
 | 
			
		||||
 | 
			
		||||
    function server.cmd(dispatchers)
 | 
			
		||||
    function server.cmd(dispatchers, _config)
 | 
			
		||||
      local closing = false
 | 
			
		||||
      local handlers = opts.handlers or {}
 | 
			
		||||
      local srv = {}
 | 
			
		||||
 
 | 
			
		||||
@@ -6490,7 +6490,7 @@ describe('LSP', function()
 | 
			
		||||
      )
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it('supports async function for root_dir', function()
 | 
			
		||||
    it('async root_dir, cmd(…,config) gets resolved config', function()
 | 
			
		||||
      exec_lua(create_server_definition)
 | 
			
		||||
 | 
			
		||||
      local tmp1 = t.tmpname(true)
 | 
			
		||||
@@ -6504,7 +6504,10 @@ describe('LSP', function()
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        vim.lsp.config('foo', {
 | 
			
		||||
          cmd = server.cmd,
 | 
			
		||||
          cmd = function(dispatchers, config)
 | 
			
		||||
            _G.test_resolved_root = config.root_dir --[[@type string]]
 | 
			
		||||
            return server.cmd(dispatchers, config)
 | 
			
		||||
          end,
 | 
			
		||||
          filetypes = { 'foo' },
 | 
			
		||||
          root_dir = function(bufnr, cb)
 | 
			
		||||
            assert(tmp1 == vim.api.nvim_buf_get_name(bufnr))
 | 
			
		||||
@@ -6527,6 +6530,12 @@ describe('LSP', function()
 | 
			
		||||
          end)
 | 
			
		||||
        )
 | 
			
		||||
      end)
 | 
			
		||||
      eq(
 | 
			
		||||
        'some_dir',
 | 
			
		||||
        exec_lua(function()
 | 
			
		||||
          return _G.test_resolved_root
 | 
			
		||||
        end)
 | 
			
		||||
      )
 | 
			
		||||
    end)
 | 
			
		||||
 | 
			
		||||
    it('starts correct LSP and stops incorrect LSP when filetype changes', function()
 | 
			
		||||
@@ -6784,10 +6793,8 @@ describe('LSP', function()
 | 
			
		||||
      markers_resolve_to({ 'marker_a', { 'marker_b', 'marker_d' } }, tmp_root)
 | 
			
		||||
      markers_resolve_to({ 'foo', { 'bar', 'baz' }, 'marker_d' }, dir_b)
 | 
			
		||||
    end)
 | 
			
		||||
  end)
 | 
			
		||||
 | 
			
		||||
  describe('vim.lsp.is_enabled()', function()
 | 
			
		||||
    it('works', function()
 | 
			
		||||
    it('vim.lsp.is_enabled()', function()
 | 
			
		||||
      exec_lua(function()
 | 
			
		||||
        vim.lsp.config('foo', {
 | 
			
		||||
          cmd = { 'foo' },
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user