diff options
Diffstat (limited to 'runtime/lua/vim')
| -rw-r--r-- | runtime/lua/vim/filetype.lua | 1 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp.lua | 99 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp/buf.lua | 173 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp/handlers.lua | 16 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp/protocol.lua | 47 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp/rpc.lua | 2 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp/util.lua | 4 | ||||
| -rw-r--r-- | runtime/lua/vim/shared.lua | 5 | 
8 files changed, 268 insertions, 79 deletions
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 1297ef6241..2a34fec7f2 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -241,6 +241,7 @@ local extension = {    gmi = "gemtext",    gemini = "gemtext",    gift = "gift", +  gleam = "gleam",    glsl = "glsl",    gpi = "gnuplot",    gnuplot = "gnuplot", diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 7bbe3637db..19ee75a1b6 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -31,28 +31,28 @@ local lsp = {    rpc_response_error = lsp_rpc.rpc_response_error;  } --- maps request name to the required resolved_capability in the client. +-- maps request name to the required server_capability in the client.  lsp._request_name_to_capability = { -  ['textDocument/hover'] = 'hover'; -  ['textDocument/signatureHelp'] = 'signature_help'; -  ['textDocument/definition'] = 'goto_definition'; -  ['textDocument/implementation'] = 'implementation'; -  ['textDocument/declaration'] = 'declaration'; -  ['textDocument/typeDefinition'] = 'type_definition'; -  ['textDocument/documentSymbol'] = 'document_symbol'; -  ['textDocument/prepareCallHierarchy'] = 'call_hierarchy'; -  ['textDocument/rename'] = 'rename'; -  ['textDocument/prepareRename'] = 'rename'; -  ['textDocument/codeAction'] = 'code_action'; -  ['textDocument/codeLens'] = 'code_lens'; -  ['codeLens/resolve'] = 'code_lens_resolve'; -  ['workspace/executeCommand'] = 'execute_command'; -  ['workspace/symbol'] = 'workspace_symbol'; -  ['textDocument/references'] = 'find_references'; -  ['textDocument/rangeFormatting'] = 'document_range_formatting'; -  ['textDocument/formatting'] = 'document_formatting'; -  ['textDocument/completion'] = 'completion'; -  ['textDocument/documentHighlight'] = 'document_highlight'; +  ['textDocument/hover'] = { 'hoverProvider' }; +  ['textDocument/signatureHelp'] = { 'signatureHelpProvider' }; +  ['textDocument/definition'] = { 'definitionProvider' }; +  ['textDocument/implementation'] = { 'implementationProvider' }; +  ['textDocument/declaration'] = { 'declarationProvider' }; +  ['textDocument/typeDefinition'] = { 'typeDefinitionProvider' }; +  ['textDocument/documentSymbol'] = { 'documentSymbolProvider' }; +  ['textDocument/prepareCallHierarchy'] = { 'callHierarchyProvider' }; +  ['textDocument/rename'] = { 'renameProvider' }; +  ['textDocument/prepareRename'] = { 'renameProvider', 'prepareProvider'} ; +  ['textDocument/codeAction'] = { 'codeActionProvider' }; +  ['textDocument/codeLens'] = { 'codeLensProvider' }; +  ['codeLens/resolve'] = { 'codeLensProvider', 'resolveProvider' }; +  ['workspace/executeCommand'] = { 'executeCommandProvider' }; +  ['workspace/symbol'] = { 'workspaceSymbolProvider' }; +  ['textDocument/references'] = { 'referencesProvider' }; +  ['textDocument/rangeFormatting'] = { 'documentRangeFormattingProvider' }; +  ['textDocument/formatting'] = { 'documentFormattingProvider' }; +  ['textDocument/completion'] = { 'completionProvider' }; +  ['textDocument/documentHighlight'] = { 'documentHighlightProvider' };  }  -- TODO improve handling of scratch buffers with LSP attached. @@ -328,7 +328,7 @@ do    function changetracking.init(client, bufnr)      local use_incremental_sync = (        if_nil(client.config.flags.allow_incremental_sync, true) -      and client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.Incremental +      and vim.tbl_get(client.server_capabilities, "textDocumentSync", "change") == protocol.TextDocumentSyncKind.Incremental      )      local state = state_by_client[client.id]      if not state then @@ -447,7 +447,7 @@ do      end)      local uri = vim.uri_from_bufnr(bufnr)      return function(client) -      if client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.None then +      if vim.tbl_get(client.server_capabilities, "textDocumentSync", "change")  == protocol.TextDocumentSyncKind.None then          return        end        local state = state_by_client[client.id] @@ -526,7 +526,7 @@ end  ---@param client Client object  local function text_document_did_open_handler(bufnr, client)    changetracking.init(client, bufnr) -  if not client.resolved_capabilities.text_document_open_close then +  if not vim.tbl_get(client.server_capabilities, "textDocumentSync", "openClose") then      return    end    if not vim.api.nvim_buf_is_loaded(bufnr) then @@ -632,10 +632,6 @@ end  ---  ---  - {server_capabilities} (table): Response from the server sent on  ---    `initialize` describing the server's capabilities. ---- ----  - {resolved_capabilities} (table): Normalized table of ----    capabilities that we have detected based on the initialize ----    response from the server in `server_capabilities`.  function lsp.client()    error()  end @@ -884,6 +880,7 @@ function lsp.start_client(config)      messages = { name = name, messages = {}, progress = {}, status = {} };    } +    -- Store the uninitialized_clients for cleanup in case we exit before initialize finishes.    uninitialized_clients[client_id] = client; @@ -960,27 +957,48 @@ function lsp.start_client(config)        client.workspace_folders = workspace_folders        -- TODO(mjlbach): Backwards compatibility, to be removed in 0.7        client.workspaceFolders = client.workspace_folders -      client.server_capabilities = assert(result.capabilities, "initialize result doesn't contain capabilities") +        -- These are the cleaned up capabilities we use for dynamically deciding        -- when to send certain events to clients. -      client.resolved_capabilities = protocol.resolve_capabilities(client.server_capabilities) +      client.server_capabilities = assert(result.capabilities, "initialize result doesn't contain capabilities") +      client.server_capabilities = protocol.resolve_capabilities(client.server_capabilities) + +      -- Deprecation wrapper: this will be removed in 0.8 +      local mt = {} +      mt.__index = function(table, key) +        if key == 'resolved_capabilities' then +          vim.notify_once("[LSP] Accessing client.resolved_capabilities is deprecated, " .. +          "update your plugins or configuration to access client.server_capabilities instead." .. +          "The new key/value pairs in server_capabilities directly match those " .. +          "defined in the language server protocol", vim.log.levels.WARN) +          rawset(table, key, protocol._resolve_capabilities_compat(client.server_capabilities)) +          return rawget(table, key) +        else +          return rawget(table, key) +        end +      end +      setmetatable(client, mt) +        client.supports_method = function(method)          local required_capability = lsp._request_name_to_capability[method]          -- if we don't know about the method, assume that the client supports it.          if not required_capability then            return true          end - -        return client.resolved_capabilities[required_capability] +        if vim.tbl_get(client.server_capabilities, unpack(required_capability)) then +          return true +        else +          return false +        end        end +        if config.on_init then          local status, err = pcall(config.on_init, client, result)          if not status then            pcall(handlers.on_error, lsp.client_errors.ON_INIT_CALLBACK_ERROR, err)          end        end -      local _ = log.debug() and log.debug(log_prefix, "server_capabilities", client.server_capabilities) -      local _ = log.info() and log.info(log_prefix, "initialized", { resolved_capabilities = client.resolved_capabilities }) +      local _ = log.info() and log.info(log_prefix, "server_capabilities", { server_capabilities = client.server_capabilities })        -- Only assign after initialized.        active_clients[client_id] = client @@ -1190,10 +1208,11 @@ function lsp._text_document_did_save_handler(bufnr)    bufnr = resolve_bufnr(bufnr)    local uri = vim.uri_from_bufnr(bufnr)    local text = once(buf_get_full_text) -  for_each_buffer_client(bufnr, function(client, _client_id) -    if client.resolved_capabilities.text_document_save then +  for_each_buffer_client(bufnr, function(client) +    local save_capability = vim.tbl_get(client.server_capabilities, "textDocumentSync", "save") +    if save_capability then        local included_text -      if client.resolved_capabilities.text_document_save_include_text then +      if type(save_capability) == "table" and save_capability.includeText then          included_text = text(bufnr)        end        client.notify('textDocument/didSave', { @@ -1246,7 +1265,7 @@ function lsp.buf_attach_client(bufnr, client_id)          local params = { textDocument = { uri = uri; } }          for_each_buffer_client(bufnr, function(client, _)            changetracking.reset_buf(client, bufnr) -          if client.resolved_capabilities.text_document_open_close then +          if vim.tbl_get(client.server_capabilities, "textDocumentSync", "openClose") then              client.notify('textDocument/didClose', params)            end            text_document_did_open_handler(bufnr, client) @@ -1256,7 +1275,7 @@ function lsp.buf_attach_client(bufnr, client_id)          local params = { textDocument = { uri = uri; } }          for_each_buffer_client(bufnr, function(client, _)            changetracking.reset_buf(client, bufnr) -          if client.resolved_capabilities.text_document_open_close then +          if vim.tbl_get(client.server_capabilities, "textDocumentSync", "openClose") then              client.notify('textDocument/didClose', params)            end          end) @@ -1306,7 +1325,7 @@ function lsp.buf_detach_client(bufnr, client_id)    changetracking.reset_buf(client, bufnr) -  if client.resolved_capabilities.text_document_open_close then +  if vim.tbl_get(client.server_capabilities, "textDocumentSync", "openClose") then      local uri = vim.uri_from_bufnr(bufnr)      local params = { textDocument = { uri = uri; } }      client.notify('textDocument/didClose', params) diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index eb7ec579f1..aabafc422f 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -143,14 +143,114 @@ local function select_client(method, on_choice)    end  end +--- Formats a buffer using the attached (and optionally filtered) language +--- server clients. +--- +--- @param options table|nil Optional table which holds the following optional fields: +---     - formatting_options (table|nil): +---         Can be used to specify FormattingOptions. Some unspecified options will be +---         automatically derived from the current Neovim options. +---         @see https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting +---     - timeout_ms (integer|nil, default 1000): +---         Time in milliseconds to block for formatting requests. No effect if async=true +---     - bufnr (number|nil): +---         Restrict formatting to the clients attached to the given buffer, defaults to the current +---         buffer (0). +---     - filter (function|nil): +---         Predicate to filter clients used for formatting. Receives the list of clients attached +---         to bufnr as the argument and must return the list of clients on which to request +---         formatting. Example: +--- +---         <pre> +---         -- Never request typescript-language-server for formatting +---         vim.lsp.buf.format { +---           filter = function(clients) +---             return vim.tbl_filter( +---               function(client) return client.name ~= "tsserver" end, +---               clients +---             ) +---           end +---         } +---         </pre> +--- +---     - async boolean|nil +---         If true the method won't block. Defaults to false. +---         Editing the buffer while formatting asynchronous can lead to unexpected +---         changes. +--- +---     - id (number|nil): +---         Restrict formatting to the client with ID (client.id) matching this field. +---     - name (string|nil): +---         Restrict formatting to the client with name (client.name) matching this field. + +function M.format(options) +  options = options or {} +  local bufnr = options.bufnr or vim.api.nvim_get_current_buf() +  local clients = vim.lsp.buf_get_clients(bufnr) + +  if options.filter then +    clients = options.filter(clients) +  elseif options.id then +    clients = vim.tbl_filter( +      function(client) return client.id == options.id end, +      clients +    ) +  elseif options.name then +    clients = vim.tbl_filter( +      function(client) return client.name == options.name end, +      clients +    ) +  end + +  clients = vim.tbl_filter( +    function(client) return client.supports_method("textDocument/formatting") end, +    clients +  ) + +  if #clients == 0 then +    vim.notify("[LSP] Format request failed, no matching language servers.") +  end + +  if options.async then +    local do_format +    do_format = function(idx, client) +      if not client then +        return +      end +      local params = util.make_formatting_params(options.formatting_options) +      client.request("textDocument/formatting", params, function(...) +        local handler = client.handlers['textDocument/formatting'] or vim.lsp.handlers['textDocument/formatting'] +        handler(...) +        do_format(next(clients, idx)) +      end, bufnr) +    end +    do_format(next(clients)) +  else +    local timeout_ms = options.timeout_ms or 1000 +    for _, client in pairs(clients) do +      local params = util.make_formatting_params(options.formatting_options) +      local result, err = client.request_sync("textDocument/formatting", params, timeout_ms, bufnr) +      if result and result.result then +        util.apply_text_edits(result.result, bufnr, client.offset_encoding) +      elseif err then +        vim.notify(string.format("[LSP][%s] %s", client.name, err), vim.log.levels.WARN) +      end +    end +  end +end +  --- Formats the current buffer.  --- ----@param options (optional, table) Can be used to specify FormattingOptions. +---@param options (table|nil) Can be used to specify FormattingOptions.  --- Some unspecified options will be automatically derived from the current  --- Neovim options.  --  ---@see https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting  function M.formatting(options) +  vim.notify_once( +    'vim.lsp.buf.formatting is deprecated. Use vim.lsp.buf.format { async = true } instead', +    vim.log.levels.WARN +  )    local params = util.make_formatting_params(options)    local bufnr = vim.api.nvim_get_current_buf()    select_client('textDocument/formatting', function(client) @@ -171,10 +271,11 @@ end  --- autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_sync()  --- </pre>  --- ----@param options Table with valid `FormattingOptions` entries +---@param options table|nil with valid `FormattingOptions` entries  ---@param timeout_ms (number) Request timeout  ---@see |vim.lsp.buf.formatting_seq_sync|  function M.formatting_sync(options, timeout_ms) +  vim.notify_once('vim.lsp.buf.formatting_sync is deprecated. Use vim.lsp.buf.format instead', vim.log.levels.WARN)    local params = util.make_formatting_params(options)    local bufnr = vim.api.nvim_get_current_buf()    select_client('textDocument/formatting', function(client) @@ -202,12 +303,13 @@ end  --- vim.api.nvim_command[[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_seq_sync()]]  --- </pre>  --- ----@param options (optional, table) `FormattingOptions` entries ----@param timeout_ms (optional, number) Request timeout ----@param order (optional, table) List of client names. Formatting is requested from clients +---@param options (table|nil) `FormattingOptions` entries +---@param timeout_ms (number|nil) Request timeout +---@param order (table|nil) List of client names. Formatting is requested from clients  ---in the following order: first all clients that are not in the `order` list, then  ---the remaining clients in the order as they occur in the `order` list.  function M.formatting_seq_sync(options, timeout_ms, order) +  vim.notify_once('vim.lsp.buf.formatting_seq_sync is deprecated. Use vim.lsp.buf.format instead', vim.log.levels.WARN)    local clients = vim.tbl_values(vim.lsp.buf_get_clients());    local bufnr = vim.api.nvim_get_current_buf() @@ -224,7 +326,7 @@ function M.formatting_seq_sync(options, timeout_ms, order)    -- loop through the clients and make synchronous formatting requests    for _, client in pairs(clients) do -    if client.resolved_capabilities.document_formatting then +    if vim.tbl_get(client.server_capabilities, "documentFormattingProvider") then        local params = util.make_formatting_params(options)        local result, err = client.request_sync("textDocument/formatting", params, timeout_ms, vim.api.nvim_get_current_buf())        if result and result.result then @@ -491,11 +593,14 @@ end  --- from multiple clients to have 1 single UI prompt for the user, yet we still  --- need to be able to link a `CodeAction|Command` to the right client for  --- `codeAction/resolve` -local function on_code_action_results(results, ctx) +local function on_code_action_results(results, ctx, options)    local action_tuples = {} +  local filter = options and options.filter    for client_id, result in pairs(results) do      for _, action in pairs(result.result or {}) do -      table.insert(action_tuples, { client_id, action }) +      if not filter or filter(action) then +        table.insert(action_tuples, { client_id, action }) +      end      end    end    if #action_tuples == 0 then @@ -542,8 +647,7 @@ local function on_code_action_results(results, ctx)      local action = action_tuple[2]      if not action.edit          and client -        and type(client.resolved_capabilities.code_action) == 'table' -        and client.resolved_capabilities.code_action.resolveProvider then +        and vim.tbl_get(client.server_capabilities, "codeActionProvider", "resolveProvider") then        client.request('codeAction/resolve', action, function(err, resolved_action)          if err then @@ -557,6 +661,13 @@ local function on_code_action_results(results, ctx)      end    end +  -- If options.apply is given, and there are just one remaining code action, +  -- apply it directly without querying the user. +  if options and options.apply and #action_tuples == 1 then +    on_user_choice(action_tuples[1]) +    return +  end +    vim.ui.select(action_tuples, {      prompt = 'Code actions:',      kind = 'codeaction', @@ -571,35 +682,49 @@ end  --- Requests code actions from all clients and calls the handler exactly once  --- with all aggregated results  ---@private -local function code_action_request(params) +local function code_action_request(params, options)    local bufnr = vim.api.nvim_get_current_buf()    local method = 'textDocument/codeAction'    vim.lsp.buf_request_all(bufnr, method, params, function(results) -    on_code_action_results(results, { bufnr = bufnr, method = method, params = params }) +    local ctx = { bufnr = bufnr, method = method, params = params} +    on_code_action_results(results, ctx, options)    end)  end  --- Selects a code action available at the current  --- cursor position.  --- ----@param context table|nil `CodeActionContext` of the LSP specification: ----               - diagnostics: (table|nil) ----                             LSP `Diagnostic[]`. Inferred from the current ----                             position if not provided. ----               - only: (string|nil) ----                      LSP `CodeActionKind` used to filter the code actions. ----                      Most language servers support values like `refactor` ----                      or `quickfix`. +---@param options table|nil Optional table which holds the following optional fields: +---    - context (table|nil): +---        Corresponds to `CodeActionContext` of the LSP specification: +---          - diagnostics (table|nil): +---                        LSP `Diagnostic[]`. Inferred from the current +---                        position if not provided. +---          - only (string|nil): +---                 LSP `CodeActionKind` used to filter the code actions. +---                 Most language servers support values like `refactor` +---                 or `quickfix`. +---    - filter (function|nil): +---             Predicate function taking an `CodeAction` and returning a boolean. +---    - apply (boolean|nil): +---             When set to `true`, and there is just one remaining action +---            (after filtering), the action is applied without user query.  ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction -function M.code_action(context) -  validate { context = { context, 't', true } } -  context = context or {} +function M.code_action(options) +  validate { options = { options, 't', true } } +  options = options or {} +  -- Detect old API call code_action(context) which should now be +  -- code_action({ context = context} ) +  if options.diagnostics or options.only then +    options = { options = options } +  end +  local context = options.context or {}    if not context.diagnostics then      context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics()    end    local params = util.make_range_params()    params.context = context -  code_action_request(params) +  code_action_request(params, options)  end  --- Performs |vim.lsp.buf.code_action()| for a given range. diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 71b4d33ec0..5c80ed0d10 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -27,7 +27,7 @@ local function progress_handler(_, result, ctx, _)    local client = vim.lsp.get_client_by_id(client_id)    local client_name = client and client.name or string.format("id=%d", client_id)    if not client then -    err_message("LSP[", client_name, "] client has shut down after sending the message") +    err_message("LSP[", client_name, "] client has shut down during progress update")      return vim.NIL    end    local val = result.value    -- unspecified yet @@ -70,7 +70,7 @@ M['window/workDoneProgress/create'] =  function(_, result, ctx)    local token = result.token  -- string or number    local client_name = client and client.name or string.format("id=%d", client_id)    if not client then -    err_message("LSP[", client_name, "] client has shut down after sending the message") +    err_message("LSP[", client_name, "] client has shut down while creating progress report")      return vim.NIL    end    client.messages.progress[token] = {} @@ -132,7 +132,7 @@ M['workspace/configuration'] = function(_, result, ctx)    local client_id = ctx.client_id    local client = vim.lsp.get_client_by_id(client_id)    if not client then -    err_message("LSP[id=", client_id, "] client has shut down after sending the message") +    err_message("LSP[", client_id, "] client has shut down after sending a workspace/configuration request")      return    end    if not result.items then @@ -298,13 +298,13 @@ function M.hover(_, result, ctx, config)    config = config or {}    config.focus_id = ctx.method    if not (result and result.contents) then -    -- return { 'No information available' } +    vim.notify('No information available')      return    end    local markdown_lines = util.convert_input_to_markdown_lines(result.contents)    markdown_lines = util.trim_empty_lines(markdown_lines)    if vim.tbl_isempty(markdown_lines) then -    -- return { 'No information available' } +    vim.notify('No information available')      return    end    return util.open_floating_preview(markdown_lines, "markdown", config) @@ -379,7 +379,7 @@ function M.signature_help(_, result, ctx, config)      return    end    local client = vim.lsp.get_client_by_id(ctx.client_id) -  local triggers = client.resolved_capabilities.signature_help_trigger_characters +  local triggers = vim.tbl_get(client.server_capabilities, "signatureHelpProvider", "triggerCharacters")    local ft = api.nvim_buf_get_option(ctx.bufnr, 'filetype')    local lines, hl = util.convert_signature_help_to_markdown_lines(result, ft, triggers)    lines = util.trim_empty_lines(lines) @@ -449,7 +449,7 @@ M['window/logMessage'] = function(_, result, ctx, _)    local client = vim.lsp.get_client_by_id(client_id)    local client_name = client and client.name or string.format("id=%d", client_id)    if not client then -    err_message("LSP[", client_name, "] client has shut down after sending the message") +    err_message("LSP[", client_name, "] client has shut down after sending ", message)    end    if message_type == protocol.MessageType.Error then      log.error(message) @@ -471,7 +471,7 @@ M['window/showMessage'] = function(_, result, ctx, _)    local client = vim.lsp.get_client_by_id(client_id)    local client_name = client and client.name or string.format("id=%d", client_id)    if not client then -    err_message("LSP[", client_name, "] client has shut down after sending the message") +    err_message("LSP[", client_name, "] client has shut down after sending ", message)    end    if message_type == protocol.MessageType.Error then      err_message("LSP[", client_name, "] ", message) diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 86c9e2fd58..8f50863360 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -1,7 +1,5 @@  -- Protocol for the Microsoft Language Server Protocol (mslsp) -local if_nil = vim.F.if_nil -  local protocol = {}  --[=[ @@ -645,6 +643,7 @@ function protocol.make_client_capabilities()              end)();            };          }; +        isPreferredSupport = true;          dataSupport = true;          resolveSupport = {            properties = { 'edit', } @@ -776,10 +775,50 @@ function protocol.make_client_capabilities()    }  end +local if_nil = vim.F.if_nil  --- Creates a normalized object describing LSP server capabilities.  ---@param server_capabilities table Table of capabilities supported by the server  ---@return table Normalized table of capabilities  function protocol.resolve_capabilities(server_capabilities) +  local TextDocumentSyncKind = protocol.TextDocumentSyncKind +  local textDocumentSync = server_capabilities.textDocumentSync +  if textDocumentSync == nil then +    -- Defaults if omitted. +    server_capabilities.textDocumentSync = { +      openClose = false, +      change = TextDocumentSyncKind.None, +      willSave = false, +      willSaveWaitUntil = false, +      save = { +        includeText = false, +      } +    } +  elseif type(textDocumentSync) == 'number' then +    -- Backwards compatibility +    if not TextDocumentSyncKind[textDocumentSync] then +      return nil, "Invalid server TextDocumentSyncKind for textDocumentSync" +    end +    server_capabilities.textDocumentSync = { +      openClose = true, +      change = textDocumentSync, +      willSave = false, +      willSaveWaitUntil = false, +      save = { +        includeText = false, +      } +    } +  elseif type(textDocumentSync) ~= 'table' then +    return nil, string.format("Invalid type for textDocumentSync: %q", type(textDocumentSync)) +  end +  return server_capabilities +end + +---@private +--- Creates a normalized object describing LSP server capabilities. +-- @deprecated access resolved_capabilities instead +---@param server_capabilities table Table of capabilities supported by the server +---@return table Normalized table of capabilities +function protocol._resolve_capabilities_compat(server_capabilities)    local general_properties = {}    local text_document_sync_properties    do @@ -930,12 +969,14 @@ function protocol.resolve_capabilities(server_capabilities)      error("The server sent invalid signatureHelpProvider")    end -  return vim.tbl_extend("error" +  local capabilities = vim.tbl_extend("error"        , text_document_sync_properties        , signature_help_properties        , workspace_properties        , general_properties        ) + +  return capabilities  end  return protocol diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 1ecac50df4..6d0a78fba8 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -385,7 +385,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)    ---@param method (string) The invoked LSP method    ---@param params (table) Parameters for the invoked LSP method    ---@param callback (function) Callback to invoke -  ---@param notify_reply_callback (function) Callback to invoke as soon as a request is no longer pending +  ---@param notify_reply_callback (function|nil) Callback to invoke as soon as a request is no longer pending    ---@returns (bool, number) `(true, message_id)` if request could be sent, `false` if not    local function request(method, params, callback, notify_reply_callback)      validate { diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 1f1a34b04a..77ab1d4224 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1873,7 +1873,7 @@ end  --- Returns indentation size.  ---  ---@see |shiftwidth| ----@param bufnr (optional, number): Buffer handle, defaults to current +---@param bufnr (number|nil): Buffer handle, defaults to current  ---@returns (number) indentation size  function M.get_effective_tabstop(bufnr)    validate { bufnr = {bufnr, 'n', true} } @@ -1884,7 +1884,7 @@ end  --- Creates a `DocumentFormattingParams` object for the current buffer and cursor position.  --- ----@param options Table with valid `FormattingOptions` entries +---@param options table|nil with valid `FormattingOptions` entries  ---@returns `DocumentFormattingParams` object  ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting  function M.make_formatting_params(options) diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index f0dc34608c..172fac3a88 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -365,7 +365,10 @@ function vim.tbl_get(o, ...)    if #keys == 0 then      return    end -  for _, k in ipairs(keys) do +  for i, k in ipairs(keys) do +    if type(o[k]) ~= 'table' and next(keys, i) then +      return nil +    end      o = o[k]      if o == nil then        return  | 
