mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-03 17:24:29 +00:00 
			
		
		
		
	fix(lsp): do not assume client capability exists in watchfiles check (#24550)
PR #23689 assumes `client.config.capabilities.workspace.didChangeWatchedFiles` exists when checking `dynamicRegistration`, but thats's true only if it was passed to `vim.lsp.start{_client}`. This caused #23806 (still an issue in v0.9.1; needs manual backport), but #23681 fixed it by defaulting `config.capabilities` to `make_client_capabilities` if not passed to `vim.lsp.start{_client}`. However, the bug resurfaces on HEAD if you provide a non-nil `capabilities` to `vim.lsp.start{_client}` with missing fields (e.g: not made via `make_client_capabilities`). From what I see, the spec says such missing fields should be interpreted as an absence of the capability (including those indicated by missing sub-fields): https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#clientCapabilities Also, suggest `vim.empty_dict()` for an empty dict in `:h vim.lsp.start_client()` (`{[vim.type_idx]=vim.types.dictionary}` no longer works anyway, probably since the cjson switch).
This commit is contained in:
		@@ -4402,58 +4402,79 @@ describe('LSP', function()
 | 
			
		||||
 | 
			
		||||
    it("ignores registrations by servers when the client doesn't advertise support", function()
 | 
			
		||||
      exec_lua(create_server_definition)
 | 
			
		||||
      local result = exec_lua([[
 | 
			
		||||
        local server = _create_server()
 | 
			
		||||
        local client_id = vim.lsp.start({
 | 
			
		||||
          name = 'watchfiles-test',
 | 
			
		||||
          cmd = server.cmd,
 | 
			
		||||
          root_dir = 'some_dir',
 | 
			
		||||
          capabilities = {
 | 
			
		||||
            workspace = {
 | 
			
		||||
              didChangeWatchedFiles = {
 | 
			
		||||
                dynamicRegistration = false,
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        local watching = false
 | 
			
		||||
      exec_lua([[
 | 
			
		||||
        server = _create_server()
 | 
			
		||||
        require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback)
 | 
			
		||||
          -- Since the registration is ignored, this should not execute and `watching` should stay false
 | 
			
		||||
          watching = true
 | 
			
		||||
          return function() end
 | 
			
		||||
        end
 | 
			
		||||
      ]])
 | 
			
		||||
 | 
			
		||||
        vim.lsp.handlers['client/registerCapability'](nil, {
 | 
			
		||||
          registrations = {
 | 
			
		||||
            {
 | 
			
		||||
              id = 'watchfiles-test-kind',
 | 
			
		||||
              method = 'workspace/didChangeWatchedFiles',
 | 
			
		||||
              registerOptions = {
 | 
			
		||||
                watchers = {
 | 
			
		||||
                  {
 | 
			
		||||
                    globPattern = '**/*',
 | 
			
		||||
      local function check_registered(capabilities)
 | 
			
		||||
        return exec_lua([[
 | 
			
		||||
          watching = false
 | 
			
		||||
          local client_id = vim.lsp.start({
 | 
			
		||||
            name = 'watchfiles-test',
 | 
			
		||||
            cmd = server.cmd,
 | 
			
		||||
            root_dir = 'some_dir',
 | 
			
		||||
            capabilities = ...,
 | 
			
		||||
          }, {
 | 
			
		||||
            reuse_client = function() return false end,
 | 
			
		||||
          })
 | 
			
		||||
 | 
			
		||||
          vim.lsp.handlers['client/registerCapability'](nil, {
 | 
			
		||||
            registrations = {
 | 
			
		||||
              {
 | 
			
		||||
                id = 'watchfiles-test-kind',
 | 
			
		||||
                method = 'workspace/didChangeWatchedFiles',
 | 
			
		||||
                registerOptions = {
 | 
			
		||||
                  watchers = {
 | 
			
		||||
                    {
 | 
			
		||||
                      globPattern = '**/*',
 | 
			
		||||
                    },
 | 
			
		||||
                  },
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        }, { client_id = client_id })
 | 
			
		||||
          }, { client_id = client_id })
 | 
			
		||||
 | 
			
		||||
        -- Ensure no errors occur when unregistering something that was never really registered.
 | 
			
		||||
        vim.lsp.handlers['client/unregisterCapability'](nil, {
 | 
			
		||||
          unregisterations = {
 | 
			
		||||
            {
 | 
			
		||||
              id = 'watchfiles-test-kind',
 | 
			
		||||
              method = 'workspace/didChangeWatchedFiles',
 | 
			
		||||
          -- Ensure no errors occur when unregistering something that was never really registered.
 | 
			
		||||
          vim.lsp.handlers['client/unregisterCapability'](nil, {
 | 
			
		||||
            unregisterations = {
 | 
			
		||||
              {
 | 
			
		||||
                id = 'watchfiles-test-kind',
 | 
			
		||||
                method = 'workspace/didChangeWatchedFiles',
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          }, { client_id = client_id })
 | 
			
		||||
 | 
			
		||||
          vim.lsp.stop_client(client_id, true)
 | 
			
		||||
          return watching
 | 
			
		||||
        ]], capabilities)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      eq(true, check_registered(nil))  -- start{_client}() defaults to make_client_capabilities().
 | 
			
		||||
      eq(false, check_registered(vim.empty_dict()))
 | 
			
		||||
      eq(false, check_registered({
 | 
			
		||||
          workspace = {
 | 
			
		||||
            ignoreMe = true,
 | 
			
		||||
          },
 | 
			
		||||
        }))
 | 
			
		||||
      eq(false, check_registered({
 | 
			
		||||
          workspace = {
 | 
			
		||||
            didChangeWatchedFiles = {
 | 
			
		||||
              dynamicRegistration = false,
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        }, { client_id = client_id })
 | 
			
		||||
 | 
			
		||||
        return watching
 | 
			
		||||
      ]])
 | 
			
		||||
 | 
			
		||||
      eq(false, result)
 | 
			
		||||
        }))
 | 
			
		||||
      eq(true, check_registered({
 | 
			
		||||
          workspace = {
 | 
			
		||||
            didChangeWatchedFiles = {
 | 
			
		||||
              dynamicRegistration = true,
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        }))
 | 
			
		||||
    end)
 | 
			
		||||
  end)
 | 
			
		||||
end)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user