-- Protocol for the Microsoft Language Server Protocol (mslsp)

local if_nil = vim.F.if_nil

local protocol = {}

--[=[
---@private
--- Useful for interfacing with:
--- https://github.com/microsoft/language-server-protocol/raw/gh-pages/_specifications/specification-3-14.md
function transform_schema_comments()
  nvim.command [[silent! '<,'>g/\/\*\*\|\*\/\|^$/d]]
  nvim.command [[silent! '<,'>s/^\(\s*\) \* \=\(.*\)/\1--\2/]]
end
---@private
function transform_schema_to_table()
  transform_schema_comments()
  nvim.command [[silent! '<,'>s/: \S\+//]]
  nvim.command [[silent! '<,'>s/export const //]]
  nvim.command [[silent! '<,'>s/export namespace \(\S*\)\s*{/protocol.\1 = {/]]
  nvim.command [[silent! '<,'>s/namespace \(\S*\)\s*{/protocol.\1 = {/]]
end
--]=]

local constants = {
  DiagnosticSeverity = {
    -- Reports an error.
    Error = 1;
    -- Reports a warning.
    Warning = 2;
    -- Reports an information.
    Information = 3;
    -- Reports a hint.
    Hint = 4;
  };

  DiagnosticTag = {
    -- Unused or unnecessary code
    Unnecessary = 1;
    -- Deprecated or obsolete code
    Deprecated = 2;
  };

  MessageType = {
    -- An error message.
    Error = 1;
    -- A warning message.
    Warning = 2;
    -- An information message.
    Info = 3;
    -- A log message.
    Log = 4;
  };

  -- The file event type.
  FileChangeType = {
    -- The file got created.
    Created = 1;
    -- The file got changed.
    Changed = 2;
    -- The file got deleted.
    Deleted = 3;
  };

  -- The kind of a completion entry.
  CompletionItemKind = {
    Text = 1;
    Method = 2;
    Function = 3;
    Constructor = 4;
    Field = 5;
    Variable = 6;
    Class = 7;
    Interface = 8;
    Module = 9;
    Property = 10;
    Unit = 11;
    Value = 12;
    Enum = 13;
    Keyword = 14;
    Snippet = 15;
    Color = 16;
    File = 17;
    Reference = 18;
    Folder = 19;
    EnumMember = 20;
    Constant = 21;
    Struct = 22;
    Event = 23;
    Operator = 24;
    TypeParameter = 25;
  };

  -- How a completion was triggered
  CompletionTriggerKind = {
    -- Completion was triggered by typing an identifier (24x7 code
    -- complete), manual invocation (e.g Ctrl+Space) or via API.
    Invoked = 1;
    -- Completion was triggered by a trigger character specified by
    -- the `triggerCharacters` properties of the `CompletionRegistrationOptions`.
    TriggerCharacter = 2;
    -- Completion was re-triggered as the current completion list is incomplete.
    TriggerForIncompleteCompletions = 3;
  };

  -- A document highlight kind.
  DocumentHighlightKind = {
    -- A textual occurrence.
    Text = 1;
    -- Read-access of a symbol, like reading a variable.
    Read = 2;
    -- Write-access of a symbol, like writing to a variable.
    Write = 3;
  };

  -- A symbol kind.
  SymbolKind = {
    File = 1;
    Module = 2;
    Namespace = 3;
    Package = 4;
    Class = 5;
    Method = 6;
    Property = 7;
    Field = 8;
    Constructor = 9;
    Enum = 10;
    Interface = 11;
    Function = 12;
    Variable = 13;
    Constant = 14;
    String = 15;
    Number = 16;
    Boolean = 17;
    Array = 18;
    Object = 19;
    Key = 20;
    Null = 21;
    EnumMember = 22;
    Struct = 23;
    Event = 24;
    Operator = 25;
    TypeParameter = 26;
  };

  -- Represents reasons why a text document is saved.
  TextDocumentSaveReason = {
    -- Manually triggered, e.g. by the user pressing save, by starting debugging,
    -- or by an API call.
    Manual = 1;
    -- Automatic after a delay.
    AfterDelay = 2;
    -- When the editor lost focus.
    FocusOut = 3;
  };

  ErrorCodes = {
    -- Defined by JSON RPC
    ParseError           = -32700;
    InvalidRequest       = -32600;
    MethodNotFound       = -32601;
    InvalidParams        = -32602;
    InternalError        = -32603;
    serverErrorStart     = -32099;
    serverErrorEnd       = -32000;
    ServerNotInitialized = -32002;
    UnknownErrorCode     = -32001;
    -- Defined by the protocol.
    RequestCancelled     = -32800;
    ContentModified      = -32801;
  };

  -- Describes the content type that a client supports in various
  -- result literals like `Hover`, `ParameterInfo` or `CompletionItem`.
  --
  -- Please note that `MarkupKinds` must not start with a `$`. This kinds
  -- are reserved for internal usage.
  MarkupKind = {
    -- Plain text is supported as a content format
    PlainText = 'plaintext';
    -- Markdown is supported as a content format
    Markdown = 'markdown';
  };

  ResourceOperationKind = {
    -- Supports creating new files and folders.
    Create = 'create';
    -- Supports renaming existing files and folders.
    Rename = 'rename';
    -- Supports deleting existing files and folders.
    Delete = 'delete';
  };

  FailureHandlingKind = {
    -- Applying the workspace change is simply aborted if one of the changes provided
    -- fails. All operations executed before the failing operation stay executed.
    Abort = 'abort';
    -- All operations are executed transactionally. That means they either all
    -- succeed or no changes at all are applied to the workspace.
    Transactional = 'transactional';
    -- If the workspace edit contains only textual file changes they are executed transactionally.
    -- If resource changes (create, rename or delete file) are part of the change the failure
    -- handling strategy is abort.
    TextOnlyTransactional = 'textOnlyTransactional';
    -- The client tries to undo the operations already executed. But there is no
    -- guarantee that this succeeds.
    Undo = 'undo';
  };

  -- Known error codes for an `InitializeError`;
  InitializeError = {
    -- If the protocol version provided by the client can't be handled by the server.
    -- @deprecated This initialize error got replaced by client capabilities. There is
    -- no version handshake in version 3.0x
    unknownProtocolVersion = 1;
  };

  -- Defines how the host (editor) should sync document changes to the language server.
  TextDocumentSyncKind = {
    -- Documents should not be synced at all.
    None = 0;
    -- Documents are synced by always sending the full content
    -- of the document.
    Full = 1;
    -- Documents are synced by sending the full content on open.
    -- After that only incremental updates to the document are
    -- send.
    Incremental = 2;
  };

  WatchKind = {
    -- Interested in create events.
    Create = 1;
    -- Interested in change events
    Change = 2;
    -- Interested in delete events
    Delete = 4;
  };

  -- Defines whether the insert text in a completion item should be interpreted as
  -- plain text or a snippet.
  InsertTextFormat = {
    -- The primary text to be inserted is treated as a plain string.
    PlainText = 1;
    -- The primary text to be inserted is treated as a snippet.
    --
    -- A snippet can define tab stops and placeholders with `$1`, `$2`
    -- and `${3:foo};`. `$0` defines the final tab stop, it defaults to
    -- the end of the snippet. Placeholders with equal identifiers are linked,
    -- that is typing in one will update others too.
    Snippet = 2;
  };

  -- A set of predefined code action kinds
  CodeActionKind = {
    -- Empty kind.
    Empty = '';
    -- Base kind for quickfix actions
    QuickFix = 'quickfix';
    -- Base kind for refactoring actions
    Refactor = 'refactor';
    -- Base kind for refactoring extraction actions
    --
    -- Example extract actions:
    --
    -- - Extract method
    -- - Extract function
    -- - Extract variable
    -- - Extract interface from class
    -- - ...
    RefactorExtract = 'refactor.extract';
    -- Base kind for refactoring inline actions
    --
    -- Example inline actions:
    --
    -- - Inline function
    -- - Inline variable
    -- - Inline constant
    -- - ...
    RefactorInline = 'refactor.inline';
    -- Base kind for refactoring rewrite actions
    --
    -- Example rewrite actions:
    --
    -- - Convert JavaScript function to class
    -- - Add or remove parameter
    -- - Encapsulate field
    -- - Make method static
    -- - Move method to base class
    -- - ...
    RefactorRewrite = 'refactor.rewrite';
    -- Base kind for source actions
    --
    -- Source code actions apply to the entire file.
    Source = 'source';
    -- Base kind for an organize imports source action
    SourceOrganizeImports = 'source.organizeImports';
  };
}

for k, v in pairs(constants) do
  local tbl = vim.deepcopy(v)
  vim.tbl_add_reverse_lookup(tbl)
  protocol[k] = tbl
end

--[=[
--Text document specific client capabilities.
export interface TextDocumentClientCapabilities {
  synchronization?: {
    --Whether text document synchronization supports dynamic registration.
    dynamicRegistration?: boolean;
    --The client supports sending will save notifications.
    willSave?: boolean;
    --The client supports sending a will save request and
    --waits for a response providing text edits which will
    --be applied to the document before it is saved.
    willSaveWaitUntil?: boolean;
    --The client supports did save notifications.
    didSave?: boolean;
  }
  --Capabilities specific to the `textDocument/completion`
  completion?: {
    --Whether completion supports dynamic registration.
    dynamicRegistration?: boolean;
    --The client supports the following `CompletionItem` specific
    --capabilities.
    completionItem?: {
      --The client supports snippets as insert text.
      --
      --A snippet can define tab stops and placeholders with `$1`, `$2`
      --and `${3:foo}`. `$0` defines the final tab stop, it defaults to
      --the end of the snippet. Placeholders with equal identifiers are linked,
      --that is typing in one will update others too.
      snippetSupport?: boolean;
      --The client supports commit characters on a completion item.
      commitCharactersSupport?: boolean
      --The client supports the following content formats for the documentation
      --property. The order describes the preferred format of the client.
      documentationFormat?: MarkupKind[];
      --The client supports the deprecated property on a completion item.
      deprecatedSupport?: boolean;
      --The client supports the preselect property on a completion item.
      preselectSupport?: boolean;
    }
    completionItemKind?: {
      --The completion item kind values the client supports. When this
      --property exists the client also guarantees that it will
      --handle values outside its set gracefully and falls back
      --to a default value when unknown.
      --
      --If this property is not present the client only supports
      --the completion items kinds from `Text` to `Reference` as defined in
      --the initial version of the protocol.
      valueSet?: CompletionItemKind[];
    },
    --The client supports to send additional context information for a
    --`textDocument/completion` request.
    contextSupport?: boolean;
  };
  --Capabilities specific to the `textDocument/hover`
  hover?: {
    --Whether hover supports dynamic registration.
    dynamicRegistration?: boolean;
    --The client supports the follow content formats for the content
    --property. The order describes the preferred format of the client.
    contentFormat?: MarkupKind[];
  };
  --Capabilities specific to the `textDocument/signatureHelp`
  signatureHelp?: {
    --Whether signature help supports dynamic registration.
    dynamicRegistration?: boolean;
    --The client supports the following `SignatureInformation`
    --specific properties.
    signatureInformation?: {
      --The client supports the follow content formats for the documentation
      --property. The order describes the preferred format of the client.
      documentationFormat?: MarkupKind[];
      --Client capabilities specific to parameter information.
      parameterInformation?: {
        --The client supports processing label offsets instead of a
        --simple label string.
        --
        --Since 3.14.0
        labelOffsetSupport?: boolean;
      }
    };
  };
  --Capabilities specific to the `textDocument/references`
  references?: {
    --Whether references supports dynamic registration.
    dynamicRegistration?: boolean;
  };
  --Capabilities specific to the `textDocument/documentHighlight`
  documentHighlight?: {
    --Whether document highlight supports dynamic registration.
    dynamicRegistration?: boolean;
  };
  --Capabilities specific to the `textDocument/documentSymbol`
  documentSymbol?: {
    --Whether document symbol supports dynamic registration.
    dynamicRegistration?: boolean;
    --Specific capabilities for the `SymbolKind`.
    symbolKind?: {
      --The symbol kind values the client supports. When this
      --property exists the client also guarantees that it will
      --handle values outside its set gracefully and falls back
      --to a default value when unknown.
      --
      --If this property is not present the client only supports
      --the symbol kinds from `File` to `Array` as defined in
      --the initial version of the protocol.
      valueSet?: SymbolKind[];
    }
    --The client supports hierarchical document symbols.
    hierarchicalDocumentSymbolSupport?: boolean;
  };
  --Capabilities specific to the `textDocument/formatting`
  formatting?: {
    --Whether formatting supports dynamic registration.
    dynamicRegistration?: boolean;
  };
  --Capabilities specific to the `textDocument/rangeFormatting`
  rangeFormatting?: {
    --Whether range formatting supports dynamic registration.
    dynamicRegistration?: boolean;
  };
  --Capabilities specific to the `textDocument/onTypeFormatting`
  onTypeFormatting?: {
    --Whether on type formatting supports dynamic registration.
    dynamicRegistration?: boolean;
  };
  --Capabilities specific to the `textDocument/declaration`
  declaration?: {
    --Whether declaration supports dynamic registration. If this is set to `true`
    --the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
    --return value for the corresponding server capability as well.
    dynamicRegistration?: boolean;
    --The client supports additional metadata in the form of declaration links.
    --
    --Since 3.14.0
    linkSupport?: boolean;
  };
  --Capabilities specific to the `textDocument/definition`.
  --
  --Since 3.14.0
  definition?: {
    --Whether definition supports dynamic registration.
    dynamicRegistration?: boolean;
    --The client supports additional metadata in the form of definition links.
    linkSupport?: boolean;
  };
  --Capabilities specific to the `textDocument/typeDefinition`
  --
  --Since 3.6.0
  typeDefinition?: {
    --Whether typeDefinition supports dynamic registration. If this is set to `true`
    --the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
    --return value for the corresponding server capability as well.
    dynamicRegistration?: boolean;
    --The client supports additional metadata in the form of definition links.
    --
    --Since 3.14.0
    linkSupport?: boolean;
  };
  --Capabilities specific to the `textDocument/implementation`.
  --
  --Since 3.6.0
  implementation?: {
    --Whether implementation supports dynamic registration. If this is set to `true`
    --the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
    --return value for the corresponding server capability as well.
    dynamicRegistration?: boolean;
    --The client supports additional metadata in the form of definition links.
    --
    --Since 3.14.0
    linkSupport?: boolean;
  };
  --Capabilities specific to the `textDocument/codeAction`
  codeAction?: {
    --Whether code action supports dynamic registration.
    dynamicRegistration?: boolean;
    --The client support code action literals as a valid
    --response of the `textDocument/codeAction` request.
    --
    --Since 3.8.0
    codeActionLiteralSupport?: {
      --The code action kind is support with the following value
      --set.
      codeActionKind: {
        --The code action kind values the client supports. When this
        --property exists the client also guarantees that it will
        --handle values outside its set gracefully and falls back
        --to a default value when unknown.
        valueSet: CodeActionKind[];
      };
    };
  };
  --Capabilities specific to the `textDocument/codeLens`
  codeLens?: {
    --Whether code lens supports dynamic registration.
    dynamicRegistration?: boolean;
  };
  --Capabilities specific to the `textDocument/documentLink`
  documentLink?: {
    --Whether document link supports dynamic registration.
    dynamicRegistration?: boolean;
  };
  --Capabilities specific to the `textDocument/documentColor` and the
  --`textDocument/colorPresentation` request.
  --
  --Since 3.6.0
  colorProvider?: {
    --Whether colorProvider supports dynamic registration. If this is set to `true`
    --the client supports the new `(ColorProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions)`
    --return value for the corresponding server capability as well.
    dynamicRegistration?: boolean;
  }
  --Capabilities specific to the `textDocument/rename`
  rename?: {
    --Whether rename supports dynamic registration.
    dynamicRegistration?: boolean;
    --The client supports testing for validity of rename operations
    --before execution.
    prepareSupport?: boolean;
  };
  --Capabilities specific to `textDocument/publishDiagnostics`.
  publishDiagnostics?: {
    --Whether the clients accepts diagnostics with related information.
    relatedInformation?: boolean;
    --Client supports the tag property to provide meta data about a diagnostic.
	  --Clients supporting tags have to handle unknown tags gracefully.
    --Since 3.15.0
    tagSupport?: {
      --The tags supported by this client
      valueSet: DiagnosticTag[];
    };
  };
  --Capabilities specific to `textDocument/foldingRange` requests.
  --
  --Since 3.10.0
  foldingRange?: {
    --Whether implementation supports dynamic registration for folding range providers. If this is set to `true`
    --the client supports the new `(FoldingRangeProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions)`
    --return value for the corresponding server capability as well.
    dynamicRegistration?: boolean;
    --The maximum number of folding ranges that the client prefers to receive per document. The value serves as a
    --hint, servers are free to follow the limit.
    rangeLimit?: number;
    --If set, the client signals that it only supports folding complete lines. If set, client will
    --ignore specified `startCharacter` and `endCharacter` properties in a FoldingRange.
    lineFoldingOnly?: boolean;
  };
}
--]=]

--[=[
--Workspace specific client capabilities.
export interface WorkspaceClientCapabilities {
  --The client supports applying batch edits to the workspace by supporting
  --the request 'workspace/applyEdit'
  applyEdit?: boolean;
  --Capabilities specific to `WorkspaceEdit`s
  workspaceEdit?: {
    --The client supports versioned document changes in `WorkspaceEdit`s
    documentChanges?: boolean;
    --The resource operations the client supports. Clients should at least
    --support 'create', 'rename' and 'delete' files and folders.
    resourceOperations?: ResourceOperationKind[];
    --The failure handling strategy of a client if applying the workspace edit
    --fails.
    failureHandling?: FailureHandlingKind;
  };
  --Capabilities specific to the `workspace/didChangeConfiguration` notification.
  didChangeConfiguration?: {
    --Did change configuration notification supports dynamic registration.
    dynamicRegistration?: boolean;
  };
  --Capabilities specific to the `workspace/didChangeWatchedFiles` notification.
  didChangeWatchedFiles?: {
    --Did change watched files notification supports dynamic registration. Please note
    --that the current protocol doesn't support static configuration for file changes
    --from the server side.
    dynamicRegistration?: boolean;
  };
  --Capabilities specific to the `workspace/symbol` request.
  symbol?: {
    --Symbol request supports dynamic registration.
    dynamicRegistration?: boolean;
    --Specific capabilities for the `SymbolKind` in the `workspace/symbol` request.
    symbolKind?: {
      --The symbol kind values the client supports. When this
      --property exists the client also guarantees that it will
      --handle values outside its set gracefully and falls back
      --to a default value when unknown.
      --
      --If this property is not present the client only supports
      --the symbol kinds from `File` to `Array` as defined in
      --the initial version of the protocol.
      valueSet?: SymbolKind[];
    }
  };
  --Capabilities specific to the `workspace/executeCommand` request.
  executeCommand?: {
    --Execute command supports dynamic registration.
    dynamicRegistration?: boolean;
  };
  --The client has support for workspace folders.
  --
  --Since 3.6.0
  workspaceFolders?: boolean;
  --The client supports `workspace/configuration` requests.
  --
  --Since 3.6.0
  configuration?: boolean;
}
--]=]

--- Gets a new ClientCapabilities object describing the LSP client
--- capabilities.
function protocol.make_client_capabilities()
  return {
    textDocument = {
      synchronization = {
        dynamicRegistration = false;

        -- TODO(ashkan) Send textDocument/willSave before saving (BufWritePre)
        willSave = false;

        -- TODO(ashkan) Implement textDocument/willSaveWaitUntil
        willSaveWaitUntil = false;

        -- Send textDocument/didSave after saving (BufWritePost)
        didSave = true;
      };
      codeAction = {
        dynamicRegistration = false;

        codeActionLiteralSupport = {
          codeActionKind = {
            valueSet = (function()
              local res = vim.tbl_values(protocol.CodeActionKind)
              table.sort(res)
              return res
            end)();
          };
        };
        dataSupport = true;
        resolveSupport = {
          properties = { 'edit', }
        };
      };
      completion = {
        dynamicRegistration = false;
        completionItem = {
          -- Until we can actually expand snippet, move cursor and allow for true snippet experience,
          -- this should be disabled out of the box.
          -- However, users can turn this back on if they have a snippet plugin.
          snippetSupport = false;

          commitCharactersSupport = false;
          preselectSupport = false;
          deprecatedSupport = false;
          documentationFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText };
        };
        completionItemKind = {
          valueSet = (function()
            local res = {}
            for k in ipairs(protocol.CompletionItemKind) do
              if type(k) == 'number' then table.insert(res, k) end
            end
            return res
          end)();
        };

        -- TODO(tjdevries): Implement this
        contextSupport = false;
      };
      declaration = {
        linkSupport = true;
      };
      definition = {
        linkSupport = true;
      };
      implementation = {
        linkSupport = true;
      };
      typeDefinition = {
        linkSupport = true;
      };
      hover = {
        dynamicRegistration = false;
        contentFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText };
      };
      signatureHelp = {
        dynamicRegistration = false;
        signatureInformation = {
          activeParameterSupport = true;
          documentationFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText };
          parameterInformation = {
            labelOffsetSupport = true;
          };
        };
      };
      references = {
        dynamicRegistration = false;
      };
      documentHighlight = {
        dynamicRegistration = false
      };
      documentSymbol = {
        dynamicRegistration = false;
        symbolKind = {
          valueSet = (function()
            local res = {}
            for k in ipairs(protocol.SymbolKind) do
              if type(k) == 'number' then table.insert(res, k) end
            end
            return res
          end)();
        };
        hierarchicalDocumentSymbolSupport = true;
      };
      rename = {
        dynamicRegistration = false;
        prepareSupport = true;
      };
      publishDiagnostics = {
        relatedInformation = true;
        tagSupport = {
          valueSet = (function()
            local res = {}
            for k in ipairs(protocol.DiagnosticTag) do
              if type(k) == 'number' then table.insert(res, k) end
            end
            return res
          end)();
        };
      };
    };
    workspace = {
      symbol = {
        dynamicRegistration = false;
        symbolKind = {
          valueSet = (function()
            local res = {}
            for k in ipairs(protocol.SymbolKind) do
              if type(k) == 'number' then table.insert(res, k) end
            end
            return res
          end)();
        };
        hierarchicalWorkspaceSymbolSupport = true;
      };
      workspaceFolders = true;
      applyEdit = true;
      workspaceEdit = {
        resourceOperations = {'rename', 'create', 'delete',},
      };
    };
    callHierarchy = {
      dynamicRegistration = false;
    };
    experimental = nil;
    window = {
      workDoneProgress = true;
      showMessage = {
        messageActionItem = {
          additionalPropertiesSupport = false;
        };
      };
      showDocument = {
        support = false;
      };
    };
  }
end

--- 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 general_properties = {}
  local text_document_sync_properties
  do
    local TextDocumentSyncKind = protocol.TextDocumentSyncKind
    local textDocumentSync = server_capabilities.textDocumentSync
    if textDocumentSync == nil then
      -- Defaults if omitted.
      text_document_sync_properties = {
        text_document_open_close = false;
        text_document_did_change = TextDocumentSyncKind.None;
--        text_document_did_change = false;
        text_document_will_save = false;
        text_document_will_save_wait_until = false;
        text_document_save = false;
        text_document_save_include_text = false;
      }
    elseif type(textDocumentSync) == 'number' then
      -- Backwards compatibility
      if not TextDocumentSyncKind[textDocumentSync] then
        return nil, "Invalid server TextDocumentSyncKind for textDocumentSync"
      end
      text_document_sync_properties = {
        text_document_open_close = true;
        text_document_did_change = textDocumentSync;
        text_document_will_save = false;
        text_document_will_save_wait_until = false;
        text_document_save = true;
        text_document_save_include_text = false;
      }
    elseif type(textDocumentSync) == 'table' then
      text_document_sync_properties = {
        text_document_open_close = if_nil(textDocumentSync.openClose, false);
        text_document_did_change = if_nil(textDocumentSync.change, TextDocumentSyncKind.None);
        text_document_will_save = if_nil(textDocumentSync.willSave, false);
        text_document_will_save_wait_until = if_nil(textDocumentSync.willSaveWaitUntil, false);
        text_document_save = if_nil(textDocumentSync.save, false);
        text_document_save_include_text = if_nil(type(textDocumentSync.save) == 'table'
                                                and textDocumentSync.save.includeText, false);
      }
    else
      return nil, string.format("Invalid type for textDocumentSync: %q", type(textDocumentSync))
    end
  end
  general_properties.completion = server_capabilities.completionProvider ~= nil
  general_properties.hover = server_capabilities.hoverProvider or false
  general_properties.goto_definition = server_capabilities.definitionProvider or false
  general_properties.find_references = server_capabilities.referencesProvider or false
  general_properties.document_highlight = server_capabilities.documentHighlightProvider or false
  general_properties.document_symbol = server_capabilities.documentSymbolProvider or false
  general_properties.workspace_symbol = server_capabilities.workspaceSymbolProvider or false
  general_properties.document_formatting = server_capabilities.documentFormattingProvider or false
  general_properties.document_range_formatting = server_capabilities.documentRangeFormattingProvider or false
  general_properties.call_hierarchy = server_capabilities.callHierarchyProvider or false
  general_properties.execute_command = server_capabilities.executeCommandProvider ~= nil

  if server_capabilities.renameProvider == nil then
    general_properties.rename = false
  elseif type(server_capabilities.renameProvider) == 'boolean' then
    general_properties.rename = server_capabilities.renameProvider
  else
    general_properties.rename = true
  end

  if server_capabilities.codeLensProvider == nil then
    general_properties.code_lens = false
    general_properties.code_lens_resolve = false
  elseif type(server_capabilities.codeLensProvider) == 'table' then
    general_properties.code_lens = true
    general_properties.code_lens_resolve = server_capabilities.codeLensProvider.resolveProvider or false
  else
    error("The server sent invalid codeLensProvider")
  end

  if server_capabilities.codeActionProvider == nil then
    general_properties.code_action = false
  elseif type(server_capabilities.codeActionProvider) == 'boolean'
    or type(server_capabilities.codeActionProvider) == 'table' then
    general_properties.code_action = server_capabilities.codeActionProvider
  else
    error("The server sent invalid codeActionProvider")
  end

  if server_capabilities.declarationProvider == nil then
    general_properties.declaration = false
  elseif type(server_capabilities.declarationProvider) == 'boolean' then
    general_properties.declaration = server_capabilities.declarationProvider
  elseif type(server_capabilities.declarationProvider) == 'table' then
    general_properties.declaration = server_capabilities.declarationProvider
  else
    error("The server sent invalid declarationProvider")
  end

  if server_capabilities.typeDefinitionProvider == nil then
    general_properties.type_definition = false
  elseif type(server_capabilities.typeDefinitionProvider) == 'boolean' then
    general_properties.type_definition = server_capabilities.typeDefinitionProvider
  elseif type(server_capabilities.typeDefinitionProvider) == 'table' then
    general_properties.type_definition = server_capabilities.typeDefinitionProvider
  else
    error("The server sent invalid typeDefinitionProvider")
  end

  if server_capabilities.implementationProvider == nil then
    general_properties.implementation = false
  elseif type(server_capabilities.implementationProvider) == 'boolean' then
    general_properties.implementation = server_capabilities.implementationProvider
  elseif type(server_capabilities.implementationProvider) == 'table' then
    general_properties.implementation = server_capabilities.implementationProvider
  else
    error("The server sent invalid implementationProvider")
  end

  local workspace = server_capabilities.workspace
  local workspace_properties = {}
  if workspace == nil or workspace.workspaceFolders == nil then
    -- Defaults if omitted.
    workspace_properties = {
      workspace_folder_properties =  {
        supported = false;
        changeNotifications=false;
      }
    }
  elseif type(workspace.workspaceFolders) == 'table' then
    workspace_properties = {
      workspace_folder_properties = {
        supported = if_nil(workspace.workspaceFolders.supported, false);
        changeNotifications = if_nil(workspace.workspaceFolders.changeNotifications, false);

      }
    }
  else
    error("The server sent invalid workspace")
  end

  local signature_help_properties
  if server_capabilities.signatureHelpProvider == nil then
    signature_help_properties = {
      signature_help = false;
      signature_help_trigger_characters = {};
    }
  elseif type(server_capabilities.signatureHelpProvider) == 'table' then
    signature_help_properties = {
      signature_help = true;
      -- The characters that trigger signature help automatically.
      signature_help_trigger_characters = server_capabilities.signatureHelpProvider.triggerCharacters or {};
    }
  else
    error("The server sent invalid signatureHelpProvider")
  end

  return vim.tbl_extend("error"
      , text_document_sync_properties
      , signature_help_properties
      , workspace_properties
      , general_properties
      )
end

return protocol
-- vim:sw=2 ts=2 et
