aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/lsp
diff options
context:
space:
mode:
authorJames McCoy <jamessan@jamessan.com>2020-08-08 08:57:35 -0400
committerJames McCoy <jamessan@jamessan.com>2020-08-08 08:57:35 -0400
commit840c12c10741d8f70e1787534fb6ea6d2b70edee (patch)
treef89ad27acbbf0b36db7ac08eeae0b8362da1fabb /runtime/lua/vim/lsp
parente813ec79c201c85c5af3b10c051ae92ab5cb8606 (diff)
parentf26df8bb66158baacb79c79822babaf137607cd6 (diff)
downloadrneovim-840c12c10741d8f70e1787534fb6ea6d2b70edee.tar.gz
rneovim-840c12c10741d8f70e1787534fb6ea6d2b70edee.tar.bz2
rneovim-840c12c10741d8f70e1787534fb6ea6d2b70edee.zip
Merge remote-tracking branch 'upstream/master' into libcallnr
Diffstat (limited to 'runtime/lua/vim/lsp')
-rw-r--r--runtime/lua/vim/lsp/buf.lua251
-rw-r--r--runtime/lua/vim/lsp/callbacks.lua293
-rw-r--r--runtime/lua/vim/lsp/log.lua94
-rw-r--r--runtime/lua/vim/lsp/protocol.lua988
-rw-r--r--runtime/lua/vim/lsp/rpc.lua472
-rw-r--r--runtime/lua/vim/lsp/util.lua1360
6 files changed, 3458 insertions, 0 deletions
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
new file mode 100644
index 0000000000..2e27617997
--- /dev/null
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -0,0 +1,251 @@
+local vim = vim
+local validate = vim.validate
+local api = vim.api
+local vfn = vim.fn
+local util = require 'vim.lsp.util'
+local list_extend = vim.list_extend
+
+local M = {}
+
+local function ok_or_nil(status, ...)
+ if not status then return end
+ return ...
+end
+local function npcall(fn, ...)
+ return ok_or_nil(pcall(fn, ...))
+end
+
+local function request(method, params, callback)
+ validate {
+ method = {method, 's'};
+ callback = {callback, 'f', true};
+ }
+ return vim.lsp.buf_request(0, method, params, callback)
+end
+
+--- Sends a notification through all clients associated with current buffer.
+--
+--@return `true` if server responds.
+function M.server_ready()
+ return not not vim.lsp.buf_notify(0, "window/progress", {})
+end
+
+--- Displays hover information about the symbol under the cursor in a floating
+--- window. Calling the function twice will jump into the floating window.
+function M.hover()
+ local params = util.make_position_params()
+ request('textDocument/hover', params)
+end
+
+--- Jumps to the declaration of the symbol under the cursor.
+---
+function M.declaration()
+ local params = util.make_position_params()
+ request('textDocument/declaration', params)
+end
+
+--- Jumps to the definition of the symbol under the cursor.
+---
+function M.definition()
+ local params = util.make_position_params()
+ request('textDocument/definition', params)
+end
+
+--- Jumps to the definition of the type of the symbol under the cursor.
+---
+function M.type_definition()
+ local params = util.make_position_params()
+ request('textDocument/typeDefinition', params)
+end
+
+--- Lists all the implementations for the symbol under the cursor in the
+--- quickfix window.
+function M.implementation()
+ local params = util.make_position_params()
+ request('textDocument/implementation', params)
+end
+
+--- Displays signature information about the symbol under the cursor in a
+--- floating window.
+function M.signature_help()
+ local params = util.make_position_params()
+ request('textDocument/signatureHelp', params)
+end
+
+--- Retrieves the completion items at the current cursor position. Can only be
+--- called in Insert mode.
+function M.completion(context)
+ local params = util.make_position_params()
+ params.context = context
+ return request('textDocument/completion', params)
+end
+
+--- Formats the current buffer.
+---
+--- The optional {options} table can be used to specify FormattingOptions, a
+--- list of which is available at
+--- https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting.
+--- Some unspecified options will be automatically derived from the current
+--- Neovim options.
+function M.formatting(options)
+ local params = util.make_formatting_params(options)
+ return request('textDocument/formatting', params)
+end
+
+--- Perform |vim.lsp.buf.formatting()| synchronously.
+---
+--- Useful for running on save, to make sure buffer is formatted prior to being
+--- saved. {timeout_ms} is passed on to |vim.lsp.buf_request_sync()|.
+function M.formatting_sync(options, timeout_ms)
+ local params = util.make_formatting_params(options)
+ local result = vim.lsp.buf_request_sync(0, "textDocument/formatting", params, timeout_ms)
+ if not result then return end
+ result = result[1].result
+ vim.lsp.util.apply_text_edits(result)
+end
+
+function M.range_formatting(options, start_pos, end_pos)
+ validate {
+ options = {options, 't', true};
+ start_pos = {start_pos, 't', true};
+ end_pos = {end_pos, 't', true};
+ }
+ local sts = vim.bo.softtabstop;
+ options = vim.tbl_extend('keep', options or {}, {
+ tabSize = (sts > 0 and sts) or (sts < 0 and vim.bo.shiftwidth) or vim.bo.tabstop;
+ insertSpaces = vim.bo.expandtab;
+ })
+ local A = list_extend({}, start_pos or api.nvim_buf_get_mark(0, '<'))
+ local B = list_extend({}, end_pos or api.nvim_buf_get_mark(0, '>'))
+ -- convert to 0-index
+ A[1] = A[1] - 1
+ B[1] = B[1] - 1
+ -- account for encoding.
+ if A[2] > 0 then
+ A = {A[1], util.character_offset(0, A[1], A[2])}
+ end
+ if B[2] > 0 then
+ B = {B[1], util.character_offset(0, B[1], B[2])}
+ end
+ local params = {
+ textDocument = { uri = vim.uri_from_bufnr(0) };
+ range = {
+ start = { line = A[1]; character = A[2]; };
+ ["end"] = { line = B[1]; character = B[2]; };
+ };
+ options = options;
+ }
+ return request('textDocument/rangeFormatting', params)
+end
+
+--- Renames all references to the symbol under the cursor. If {new_name} is not
+--- provided, the user will be prompted for a new name using |input()|.
+function M.rename(new_name)
+ -- TODO(ashkan) use prepareRename
+ -- * result: [`Range`](#range) \| `{ range: Range, placeholder: string }` \| `null` describing the range of the string to rename and optionally a placeholder text of the string content to be renamed. If `null` is returned then it is deemed that a 'textDocument/rename' request is not valid at the given position.
+ local params = util.make_position_params()
+ new_name = new_name or npcall(vfn.input, "New Name: ", vfn.expand('<cword>'))
+ if not (new_name and #new_name > 0) then return end
+ params.newName = new_name
+ request('textDocument/rename', params)
+end
+
+--- Lists all the references to the symbol under the cursor in the quickfix window.
+---
+function M.references(context)
+ validate { context = { context, 't', true } }
+ local params = util.make_position_params()
+ params.context = context or {
+ includeDeclaration = true;
+ }
+ params[vim.type_idx] = vim.types.dictionary
+ request('textDocument/references', params)
+end
+
+--- Lists all symbols in the current buffer in the quickfix window.
+---
+function M.document_symbol()
+ local params = { textDocument = util.make_text_document_params() }
+ request('textDocument/documentSymbol', params)
+end
+
+local function pick_call_hierarchy_item(call_hierarchy_items)
+ if not call_hierarchy_items then return end
+ if #call_hierarchy_items == 1 then
+ return call_hierarchy_items[1]
+ end
+ local items = {}
+ for i, item in ipairs(call_hierarchy_items) do
+ local entry = item.detail or item.name
+ table.insert(items, string.format("%d. %s", i, entry))
+ end
+ local choice = vim.fn.inputlist(items)
+ if choice < 1 or choice > #items then
+ return
+ end
+ return choice
+end
+
+function M.incoming_calls()
+ local params = util.make_position_params()
+ request('textDocument/prepareCallHierarchy', params, function(_, _, result)
+ local call_hierarchy_item = pick_call_hierarchy_item(result)
+ vim.lsp.buf_request(0, 'callHierarchy/incomingCalls', { item = call_hierarchy_item })
+ end)
+end
+
+function M.outgoing_calls()
+ local params = util.make_position_params()
+ request('textDocument/prepareCallHierarchy', params, function(_, _, result)
+ local call_hierarchy_item = pick_call_hierarchy_item(result)
+ vim.lsp.buf_request(0, 'callHierarchy/outgoingCalls', { item = call_hierarchy_item })
+ end)
+end
+
+--- Lists all symbols in the current workspace in the quickfix window.
+---
+--- The list is filtered against the optional argument {query};
+--- if the argument is omitted from the call, the user is prompted to enter a string on the command line.
+--- An empty string means no filtering is done.
+function M.workspace_symbol(query)
+ query = query or npcall(vfn.input, "Query: ")
+ local params = {query = query}
+ request('workspace/symbol', params)
+end
+
+--- Send request to server to resolve document highlights for the
+--- current text document position. This request can be associated
+--- to key mapping or to events such as `CursorHold`, eg:
+---
+--- <pre>
+--- vim.api.nvim_command [[autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()]]
+--- vim.api.nvim_command [[autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()]]
+--- vim.api.nvim_command [[autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()]]
+--- </pre>
+function M.document_highlight()
+ local params = util.make_position_params()
+ request('textDocument/documentHighlight', params)
+end
+
+function M.clear_references()
+ util.buf_clear_references()
+end
+
+function M.code_action(context)
+ validate { context = { context, 't', true } }
+ context = context or { diagnostics = util.get_line_diagnostics() }
+ local params = util.make_range_params()
+ params.context = context
+ request('textDocument/codeAction', params)
+end
+
+function M.execute_command(command)
+ validate {
+ command = { command.command, 's' },
+ arguments = { command.arguments, 't', true }
+ }
+ request('workspace/executeCommand', command)
+end
+
+return M
+-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua
new file mode 100644
index 0000000000..1ed58995d0
--- /dev/null
+++ b/runtime/lua/vim/lsp/callbacks.lua
@@ -0,0 +1,293 @@
+local log = require 'vim.lsp.log'
+local protocol = require 'vim.lsp.protocol'
+local util = require 'vim.lsp.util'
+local vim = vim
+local api = vim.api
+local buf = require 'vim.lsp.buf'
+
+local M = {}
+
+local function err_message(...)
+ api.nvim_err_writeln(table.concat(vim.tbl_flatten{...}))
+ api.nvim_command("redraw")
+end
+
+M['workspace/executeCommand'] = function(err, _)
+ if err then
+ error("Could not execute code action: "..err.message)
+ end
+end
+
+M['textDocument/codeAction'] = function(_, _, actions)
+ if actions == nil or vim.tbl_isempty(actions) then
+ print("No code actions available")
+ return
+ end
+
+ local option_strings = {"Code Actions:"}
+ for i, action in ipairs(actions) do
+ local title = action.title:gsub('\r\n', '\\r\\n')
+ title = title:gsub('\n', '\\n')
+ table.insert(option_strings, string.format("%d. %s", i, title))
+ end
+
+ local choice = vim.fn.inputlist(option_strings)
+ if choice < 1 or choice > #actions then
+ return
+ end
+ local action_chosen = actions[choice]
+ -- textDocument/codeAction can return either Command[] or CodeAction[].
+ -- If it is a CodeAction, it can have either an edit, a command or both.
+ -- Edits should be executed first
+ if action_chosen.edit or type(action_chosen.command) == "table" then
+ if action_chosen.edit then
+ util.apply_workspace_edit(action_chosen.edit)
+ end
+ if type(action_chosen.command) == "table" then
+ buf.execute_command(action_chosen.command)
+ end
+ else
+ buf.execute_command(action_chosen)
+ end
+end
+
+M['workspace/applyEdit'] = function(_, _, workspace_edit)
+ if not workspace_edit then return end
+ -- TODO(ashkan) Do something more with label?
+ if workspace_edit.label then
+ print("Workspace edit", workspace_edit.label)
+ end
+ local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit)
+ return {
+ applied = status;
+ failureReason = result;
+ }
+end
+
+M['textDocument/publishDiagnostics'] = function(_, _, result)
+ if not result then return end
+ local uri = result.uri
+ local bufnr = vim.uri_to_bufnr(uri)
+ if not bufnr then
+ err_message("LSP.publishDiagnostics: Couldn't find buffer for ", uri)
+ return
+ end
+
+ -- Unloaded buffers should not handle diagnostics.
+ -- When the buffer is loaded, we'll call on_attach, which sends textDocument/didOpen.
+ -- This should trigger another publish of the diagnostics.
+ --
+ -- In particular, this stops a ton of spam when first starting a server for current
+ -- unloaded buffers.
+ if not api.nvim_buf_is_loaded(bufnr) then
+ return
+ end
+
+ util.buf_clear_diagnostics(bufnr)
+
+ -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic
+ -- The diagnostic's severity. Can be omitted. If omitted it is up to the
+ -- client to interpret diagnostics as error, warning, info or hint.
+ -- TODO: Replace this with server-specific heuristics to infer severity.
+ for _, diagnostic in ipairs(result.diagnostics) do
+ if diagnostic.severity == nil then
+ diagnostic.severity = protocol.DiagnosticSeverity.Error
+ end
+ end
+
+ util.buf_diagnostics_save_positions(bufnr, result.diagnostics)
+ util.buf_diagnostics_underline(bufnr, result.diagnostics)
+ util.buf_diagnostics_virtual_text(bufnr, result.diagnostics)
+ util.buf_diagnostics_signs(bufnr, result.diagnostics)
+ vim.api.nvim_command("doautocmd User LspDiagnosticsChanged")
+end
+
+M['textDocument/references'] = function(_, _, result)
+ if not result then return end
+ util.set_qflist(util.locations_to_items(result))
+ api.nvim_command("copen")
+ api.nvim_command("wincmd p")
+end
+
+local symbol_callback = function(_, _, result, _, bufnr)
+ if not result or vim.tbl_isempty(result) then return end
+
+ util.set_qflist(util.symbols_to_items(result, bufnr))
+ api.nvim_command("copen")
+ api.nvim_command("wincmd p")
+end
+M['textDocument/documentSymbol'] = symbol_callback
+M['workspace/symbol'] = symbol_callback
+
+M['textDocument/rename'] = function(_, _, result)
+ if not result then return end
+ util.apply_workspace_edit(result)
+end
+
+M['textDocument/rangeFormatting'] = function(_, _, result)
+ if not result then return end
+ util.apply_text_edits(result)
+end
+
+M['textDocument/formatting'] = function(_, _, result)
+ if not result then return end
+ util.apply_text_edits(result)
+end
+
+M['textDocument/completion'] = function(_, _, result)
+ if vim.tbl_isempty(result or {}) then return end
+ local row, col = unpack(api.nvim_win_get_cursor(0))
+ local line = assert(api.nvim_buf_get_lines(0, row-1, row, false)[1])
+ local line_to_cursor = line:sub(col+1)
+ local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
+ local prefix = line_to_cursor:sub(textMatch+1)
+
+ local matches = util.text_document_completion_list_to_complete_items(result, prefix)
+ vim.fn.complete(textMatch+1, matches)
+end
+
+M['textDocument/hover'] = function(_, method, result)
+ util.focusable_float(method, function()
+ if not (result and result.contents) then
+ -- return { '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' }
+ return
+ end
+ local bufnr, winnr = util.fancy_floating_markdown(markdown_lines, {
+ pad_left = 1; pad_right = 1;
+ })
+ util.close_preview_autocmd({"CursorMoved", "BufHidden", "InsertCharPre"}, winnr)
+ return bufnr, winnr
+ end)
+end
+
+local function location_callback(_, method, result)
+ if result == nil or vim.tbl_isempty(result) then
+ local _ = log.info() and log.info(method, 'No location found')
+ return nil
+ end
+
+ -- textDocument/definition can return Location or Location[]
+ -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
+
+ if vim.tbl_islist(result) then
+ util.jump_to_location(result[1])
+
+ if #result > 1 then
+ util.set_qflist(util.locations_to_items(result))
+ api.nvim_command("copen")
+ api.nvim_command("wincmd p")
+ end
+ else
+ util.jump_to_location(result)
+ end
+end
+
+M['textDocument/declaration'] = location_callback
+M['textDocument/definition'] = location_callback
+M['textDocument/typeDefinition'] = location_callback
+M['textDocument/implementation'] = location_callback
+
+M['textDocument/signatureHelp'] = function(_, method, result)
+ util.focusable_preview(method, function()
+ if not (result and result.signatures and result.signatures[1]) then
+ return { 'No signature available' }
+ end
+ -- TODO show popup when signatures is empty?
+ local lines = util.convert_signature_help_to_markdown_lines(result)
+ lines = util.trim_empty_lines(lines)
+ if vim.tbl_isempty(lines) then
+ return { 'No signature available' }
+ end
+ return lines, util.try_trim_markdown_code_blocks(lines)
+ end)
+end
+
+M['textDocument/documentHighlight'] = function(_, _, result, _)
+ if not result then return end
+ local bufnr = api.nvim_get_current_buf()
+ util.buf_highlight_references(bufnr, result)
+end
+
+-- direction is "from" for incoming calls and "to" for outgoing calls
+local make_call_hierarchy_callback = function(direction)
+ -- result is a CallHierarchy{Incoming,Outgoing}Call[]
+ return function(_, _, result)
+ if not result then return end
+ local items = {}
+ for _, call_hierarchy_call in pairs(result) do
+ local call_hierarchy_item = call_hierarchy_call[direction]
+ for _, range in pairs(call_hierarchy_call.fromRanges) do
+ table.insert(items, {
+ filename = assert(vim.uri_to_fname(call_hierarchy_item.uri)),
+ text = call_hierarchy_item.name,
+ lnum = range.start.line + 1,
+ col = range.start.character + 1,
+ })
+ end
+ end
+ util.set_qflist(items)
+ api.nvim_command("copen")
+ api.nvim_command("wincmd p")
+ end
+end
+
+M['callHierarchy/incomingCalls'] = make_call_hierarchy_callback('from')
+
+M['callHierarchy/outgoingCalls'] = make_call_hierarchy_callback('to')
+
+M['window/logMessage'] = function(_, _, result, client_id)
+ local message_type = result.type
+ local message = result.message
+ 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")
+ end
+ if message_type == protocol.MessageType.Error then
+ log.error(message)
+ elseif message_type == protocol.MessageType.Warning then
+ log.warn(message)
+ elseif message_type == protocol.MessageType.Info then
+ log.info(message)
+ else
+ log.debug(message)
+ end
+ return result
+end
+
+M['window/showMessage'] = function(_, _, result, client_id)
+ local message_type = result.type
+ local message = result.message
+ 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")
+ end
+ if message_type == protocol.MessageType.Error then
+ err_message("LSP[", client_name, "] ", message)
+ else
+ local message_type_name = protocol.MessageType[message_type]
+ api.nvim_out_write(string.format("LSP[%s][%s] %s\n", client_name, message_type_name, message))
+ end
+ return result
+end
+
+-- Add boilerplate error validation and logging for all of these.
+for k, fn in pairs(M) do
+ M[k] = function(err, method, params, client_id, bufnr)
+ log.debug('default_callback', method, { params = params, client_id = client_id, err = err, bufnr = bufnr })
+ if err then
+ error(tostring(err))
+ end
+ return fn(err, method, params, client_id, bufnr)
+ end
+end
+
+return M
+-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua
new file mode 100644
index 0000000000..696ce43a59
--- /dev/null
+++ b/runtime/lua/vim/lsp/log.lua
@@ -0,0 +1,94 @@
+-- Logger for language client plugin.
+
+local log = {}
+
+-- Log level dictionary with reverse lookup as well.
+--
+-- Can be used to lookup the number from the name or the name from the number.
+-- Levels by name: 'trace', 'debug', 'info', 'warn', 'error'
+-- Level numbers begin with 'trace' at 0
+log.levels = {
+ TRACE = 0;
+ DEBUG = 1;
+ INFO = 2;
+ WARN = 3;
+ ERROR = 4;
+}
+
+-- Default log level is warn.
+local current_log_level = log.levels.WARN
+local log_date_format = "%FT%H:%M:%S%z"
+
+do
+ local path_sep = vim.loop.os_uname().sysname == "Windows" and "\\" or "/"
+ local function path_join(...)
+ return table.concat(vim.tbl_flatten{...}, path_sep)
+ end
+ local logfilename = path_join(vim.fn.stdpath('data'), 'lsp.log')
+
+ --- Return the log filename.
+ function log.get_filename()
+ return logfilename
+ end
+
+ vim.fn.mkdir(vim.fn.stdpath('data'), "p")
+ local logfile = assert(io.open(logfilename, "a+"))
+ for level, levelnr in pairs(log.levels) do
+ -- Also export the log level on the root object.
+ log[level] = levelnr
+ -- Set the lowercase name as the main use function.
+ -- If called without arguments, it will check whether the log level is
+ -- greater than or equal to this one. When called with arguments, it will
+ -- log at that level (if applicable, it is checked either way).
+ --
+ -- Recommended usage:
+ -- ```
+ -- local _ = log.warn() and log.warn("123")
+ -- ```
+ --
+ -- This way you can avoid string allocations if the log level isn't high enough.
+ log[level:lower()] = function(...)
+ local argc = select("#", ...)
+ if levelnr < current_log_level then return false end
+ if argc == 0 then return true end
+ local info = debug.getinfo(2, "Sl")
+ local fileinfo = string.format("%s:%s", info.short_src, info.currentline)
+ local parts = { table.concat({"[", level, "]", os.date(log_date_format), "]", fileinfo, "]"}, " ") }
+ for i = 1, argc do
+ local arg = select(i, ...)
+ if arg == nil then
+ table.insert(parts, "nil")
+ else
+ table.insert(parts, vim.inspect(arg, {newline=''}))
+ end
+ end
+ logfile:write(table.concat(parts, '\t'), "\n")
+ logfile:flush()
+ end
+ end
+ -- Add some space to make it easier to distinguish different neovim runs.
+ logfile:write("\n")
+end
+
+-- This is put here on purpose after the loop above so that it doesn't
+-- interfere with iterating the levels
+vim.tbl_add_reverse_lookup(log.levels)
+
+function log.set_level(level)
+ if type(level) == 'string' then
+ current_log_level = assert(log.levels[level:upper()], string.format("Invalid log level: %q", level))
+ else
+ assert(type(level) == 'number', "level must be a number or string")
+ assert(log.levels[level], string.format("Invalid log level: %d", level))
+ current_log_level = level
+ end
+end
+
+-- Return whether the level is sufficient for logging.
+-- @param level number log level
+function log.should_log(level)
+ return level >= current_log_level
+end
+
+return log
+-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
new file mode 100644
index 0000000000..ef5e08680e
--- /dev/null
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -0,0 +1,988 @@
+-- Protocol for the Microsoft Language Server Protocol (mslsp)
+
+local protocol = {}
+
+local function ifnil(a, b)
+ if a == nil then return b end
+ return a
+end
+
+
+--[=[
+-- 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
+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;
+ };
+
+ 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
+ vim.tbl_add_reverse_lookup(v)
+ protocol[k] = v
+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.