Merge pull request #18487 from clason/stylua

CI: format and lint runtime with Stylua
This commit is contained in:
Christian Clason
2022-05-11 08:54:24 +02:00
committed by GitHub
41 changed files with 4223 additions and 3571 deletions

View File

@@ -41,7 +41,7 @@ P.take_until = function(targets, specials)
parsed = true,
value = {
raw = table.concat(raw, ''),
esc = table.concat(esc, '')
esc = table.concat(esc, ''),
},
pos = new_pos,
}
@@ -248,48 +248,66 @@ S.format = P.any(
capture_index = values[3],
}, Node)
end),
P.map(P.seq(S.dollar, S.open, S.int, S.colon, S.slash, P.any(
P.token('upcase'),
P.token('downcase'),
P.token('capitalize'),
P.token('camelcase'),
P.token('pascalcase')
), S.close), function(values)
return setmetatable({
type = Node.Type.FORMAT,
capture_index = values[3],
modifier = values[6],
}, Node)
end),
P.map(P.seq(S.dollar, S.open, S.int, S.colon, P.any(
P.seq(S.question, P.take_until({ ':' }, { '\\' }), S.colon, P.take_until({ '}' }, { '\\' })),
P.seq(S.plus, P.take_until({ '}' }, { '\\' })),
P.seq(S.minus, P.take_until({ '}' }, { '\\' }))
), S.close), function(values)
return setmetatable({
type = Node.Type.FORMAT,
capture_index = values[3],
if_text = values[5][2].esc,
else_text = (values[5][4] or {}).esc,
}, Node)
end)
P.map(
P.seq(
S.dollar,
S.open,
S.int,
S.colon,
S.slash,
P.any(P.token('upcase'), P.token('downcase'), P.token('capitalize'), P.token('camelcase'), P.token('pascalcase')),
S.close
),
function(values)
return setmetatable({
type = Node.Type.FORMAT,
capture_index = values[3],
modifier = values[6],
}, Node)
end
),
P.map(
P.seq(
S.dollar,
S.open,
S.int,
S.colon,
P.any(
P.seq(S.question, P.take_until({ ':' }, { '\\' }), S.colon, P.take_until({ '}' }, { '\\' })),
P.seq(S.plus, P.take_until({ '}' }, { '\\' })),
P.seq(S.minus, P.take_until({ '}' }, { '\\' }))
),
S.close
),
function(values)
return setmetatable({
type = Node.Type.FORMAT,
capture_index = values[3],
if_text = values[5][2].esc,
else_text = (values[5][4] or {}).esc,
}, Node)
end
)
)
S.transform = P.map(P.seq(
S.slash,
P.take_until({ '/' }, { '\\' }),
S.slash,
P.many(P.any(S.format, S.text({ '$', '/' }, { '\\' }))),
S.slash,
P.opt(P.pattern('[ig]+'))
), function(values)
return setmetatable({
type = Node.Type.TRANSFORM,
pattern = values[2].raw,
format = values[4],
option = values[6],
}, Node)
end)
S.transform = P.map(
P.seq(
S.slash,
P.take_until({ '/' }, { '\\' }),
S.slash,
P.many(P.any(S.format, S.text({ '$', '/' }, { '\\' }))),
S.slash,
P.opt(P.pattern('[ig]+'))
),
function(values)
return setmetatable({
type = Node.Type.TRANSFORM,
pattern = values[2].raw,
format = values[4],
option = values[6],
}, Node)
end
)
S.tabstop = P.any(
P.map(P.seq(S.dollar, S.int), function(values)
@@ -314,34 +332,38 @@ S.tabstop = P.any(
)
S.placeholder = P.any(
P.map(P.seq(S.dollar, S.open, S.int, S.colon, P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' }))), S.close), function(values)
return setmetatable({
type = Node.Type.PLACEHOLDER,
tabstop = values[3],
children = values[5],
}, Node)
end)
P.map(
P.seq(S.dollar, S.open, S.int, S.colon, P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' }))), S.close),
function(values)
return setmetatable({
type = Node.Type.PLACEHOLDER,
tabstop = values[3],
children = values[5],
}, Node)
end
)
)
S.choice = P.map(P.seq(
S.dollar,
S.open,
S.int,
S.pipe,
P.many(
P.map(P.seq(S.text({ ',', '|' }), P.opt(S.comma)), function(values)
S.choice = P.map(
P.seq(
S.dollar,
S.open,
S.int,
S.pipe,
P.many(P.map(P.seq(S.text({ ',', '|' }), P.opt(S.comma)), function(values)
return values[1].esc
end)
end)),
S.pipe,
S.close
),
S.pipe,
S.close
), function(values)
return setmetatable({
type = Node.Type.CHOICE,
tabstop = values[3],
items = values[5],
}, Node)
end)
function(values)
return setmetatable({
type = Node.Type.CHOICE,
tabstop = values[3],
items = values[5],
}, Node)
end
)
S.variable = P.any(
P.map(P.seq(S.dollar, S.var), function(values)
@@ -363,13 +385,16 @@ S.variable = P.any(
transform = values[4],
}, Node)
end),
P.map(P.seq(S.dollar, S.open, S.var, S.colon, P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' }))), S.close), function(values)
return setmetatable({
type = Node.Type.VARIABLE,
name = values[3],
children = values[5],
}, Node)
end)
P.map(
P.seq(S.dollar, S.open, S.var, S.colon, P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' }))), S.close),
function(values)
return setmetatable({
type = Node.Type.VARIABLE,
name = values[3],
children = values[5],
}, Node)
end
)
)
S.snippet = P.map(P.many(P.any(S.toplevel, S.text({ '$' }, { '}', '\\' }))), function(values)

View File

@@ -1,7 +1,7 @@
local vim = vim
local validate = vim.validate
local vfn = vim.fn
local util = require 'vim.lsp.util'
local util = require('vim.lsp.util')
local M = {}
@@ -9,7 +9,9 @@ local M = {}
--- Returns nil if {status} is false or nil, otherwise returns the rest of the
--- arguments.
local function ok_or_nil(status, ...)
if not status then return end
if not status then
return
end
return ...
end
@@ -39,10 +41,10 @@ end
---
---@see |vim.lsp.buf_request()|
local function request(method, params, handler)
validate {
method = {method, 's'};
handler = {handler, 'f', true};
}
validate({
method = { method, 's' },
handler = { handler, 'f', true },
})
return vim.lsp.buf_request(0, method, params, handler)
end
@@ -51,7 +53,7 @@ end
---
---@returns `true` if server responds.
function M.server_ready()
return not not vim.lsp.buf_notify(0, "window/progress", {})
return not not vim.lsp.buf_notify(0, 'window/progress', {})
end
--- Displays hover information about the symbol under the cursor in a floating
@@ -117,9 +119,9 @@ end
--
---@returns The client that the user selected or nil
local function select_client(method, on_choice)
validate {
validate({
on_choice = { on_choice, 'function', false },
}
})
local clients = vim.tbl_values(vim.lsp.buf_get_clients())
clients = vim.tbl_filter(function(client)
return client.supports_method(method)
@@ -191,24 +193,21 @@ function M.format(options)
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
)
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
)
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
)
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.")
vim.notify('[LSP] Format request failed, no matching language servers.')
end
if options.async then
@@ -218,7 +217,7 @@ function M.format(options)
return
end
local params = util.make_formatting_params(options.formatting_options)
client.request("textDocument/formatting", params, function(...)
client.request('textDocument/formatting', params, function(...)
local handler = client.handlers['textDocument/formatting'] or vim.lsp.handlers['textDocument/formatting']
handler(...)
do_format(next(clients, idx))
@@ -229,11 +228,11 @@ function M.format(options)
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)
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)
vim.notify(string.format('[LSP][%s] %s', client.name, err), vim.log.levels.WARN)
end
end
end
@@ -310,7 +309,7 @@ end
---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 clients = vim.tbl_values(vim.lsp.buf_get_clients())
local bufnr = vim.api.nvim_get_current_buf()
-- sort the clients according to `order`
@@ -326,13 +325,18 @@ 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 vim.tbl_get(client.server_capabilities, "documentFormattingProvider") 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())
local result, err = client.request_sync(
'textDocument/formatting',
params,
timeout_ms,
vim.api.nvim_get_current_buf()
)
if result and result.result then
util.apply_text_edits(result.result, bufnr, client.offset_encoding)
elseif err then
vim.notify(string.format("vim.lsp.buf.formatting_seq_sync: (%s) %s", client.name, err), vim.log.levels.WARN)
vim.notify(string.format('vim.lsp.buf.formatting_seq_sync: (%s) %s', client.name, err), vim.log.levels.WARN)
end
end
end
@@ -377,20 +381,18 @@ function M.rename(new_name, options)
if options.filter then
clients = options.filter(clients)
elseif options.name then
clients = vim.tbl_filter(
function(client) return client.name == options.name end,
clients
)
clients = vim.tbl_filter(function(client)
return client.name == options.name
end, clients)
end
-- Clients must at least support rename, prepareRename is optional
clients = vim.tbl_filter(
function(client) return client.supports_method("textDocument/rename") end,
clients
)
clients = vim.tbl_filter(function(client)
return client.supports_method('textDocument/rename')
end, clients)
if #clients == 0 then
vim.notify("[LSP] Rename, no matching language servers with rename capability.")
vim.notify('[LSP] Rename, no matching language servers with rename capability.')
end
local win = vim.api.nvim_get_current_win()
@@ -427,7 +429,7 @@ function M.rename(new_name, options)
end, bufnr)
end
if client.supports_method("textDocument/prepareRename") then
if client.supports_method('textDocument/prepareRename') then
local params = util.make_position_params(win, client.offset_encoding)
client.request('textDocument/prepareRename', params, function(err, result)
if err or result == nil then
@@ -446,7 +448,7 @@ function M.rename(new_name, options)
end
local prompt_opts = {
prompt = "New Name: "
prompt = 'New Name: ',
}
-- result: Range | { range: Range, placeholder: string }
if result.placeholder then
@@ -466,15 +468,15 @@ function M.rename(new_name, options)
end)
end, bufnr)
else
assert(client.supports_method("textDocument/rename"), 'Client must support textDocument/rename')
assert(client.supports_method('textDocument/rename'), 'Client must support textDocument/rename')
if new_name then
rename(new_name)
return
end
local prompt_opts = {
prompt = "New Name: ",
default = cword
prompt = 'New Name: ',
default = cword,
}
vim.ui.input(prompt_opts, function(input)
if not input or #input == 0 then
@@ -493,10 +495,10 @@ end
---@param context (table) Context for the request
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
function M.references(context)
validate { context = { context, 't', true } }
validate({ context = { context, 't', true } })
local params = util.make_position_params()
params.context = context or {
includeDeclaration = true;
includeDeclaration = true,
}
request('textDocument/references', params)
end
@@ -510,14 +512,16 @@ end
---@private
local function pick_call_hierarchy_item(call_hierarchy_items)
if not call_hierarchy_items then return end
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 pairs(call_hierarchy_items) do
local entry = item.detail or item.name
table.insert(items, string.format("%d. %s", i, entry))
table.insert(items, string.format('%d. %s', i, entry))
end
local choice = vim.fn.inputlist(items)
if choice < 1 or choice > #items then
@@ -539,8 +543,8 @@ local function call_hierarchy(method)
if client then
client.request(method, { item = call_hierarchy_item }, nil, ctx.bufnr)
else
vim.notify(string.format(
'Client with id=%d disappeared during call hierarchy request', ctx.client_id),
vim.notify(
string.format('Client with id=%d disappeared during call hierarchy request', ctx.client_id),
vim.log.levels.WARN
)
end
@@ -576,20 +580,25 @@ end
--- Add the folder at path to the workspace folders. If {path} is
--- not provided, the user will be prompted for a path using |input()|.
function M.add_workspace_folder(workspace_folder)
workspace_folder = workspace_folder or npcall(vfn.input, "Workspace Folder: ", vfn.expand('%:p:h'), 'dir')
vim.api.nvim_command("redraw")
if not (workspace_folder and #workspace_folder > 0) then return end
if vim.fn.isdirectory(workspace_folder) == 0 then
print(workspace_folder, " is not a valid directory")
workspace_folder = workspace_folder or npcall(vfn.input, 'Workspace Folder: ', vfn.expand('%:p:h'), 'dir')
vim.api.nvim_command('redraw')
if not (workspace_folder and #workspace_folder > 0) then
return
end
local params = util.make_workspace_params({{uri = vim.uri_from_fname(workspace_folder); name = workspace_folder}}, {{}})
if vim.fn.isdirectory(workspace_folder) == 0 then
print(workspace_folder, ' is not a valid directory')
return
end
local params = util.make_workspace_params(
{ { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } },
{ {} }
)
for _, client in pairs(vim.lsp.buf_get_clients()) do
local found = false
for _, folder in pairs(client.workspace_folders or {}) do
if folder.name == workspace_folder then
found = true
print(workspace_folder, "is already part of this workspace")
print(workspace_folder, 'is already part of this workspace')
break
end
end
@@ -607,10 +616,15 @@ end
--- {path} is not provided, the user will be prompted for
--- a path using |input()|.
function M.remove_workspace_folder(workspace_folder)
workspace_folder = workspace_folder or npcall(vfn.input, "Workspace Folder: ", vfn.expand('%:p:h'))
vim.api.nvim_command("redraw")
if not (workspace_folder and #workspace_folder > 0) then return end
local params = util.make_workspace_params({{}}, {{uri = vim.uri_from_fname(workspace_folder); name = workspace_folder}})
workspace_folder = workspace_folder or npcall(vfn.input, 'Workspace Folder: ', vfn.expand('%:p:h'))
vim.api.nvim_command('redraw')
if not (workspace_folder and #workspace_folder > 0) then
return
end
local params = util.make_workspace_params(
{ {} },
{ { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } }
)
for _, client in pairs(vim.lsp.buf_get_clients()) do
for idx, folder in pairs(client.workspace_folders) do
if folder.name == workspace_folder then
@@ -620,7 +634,7 @@ function M.remove_workspace_folder(workspace_folder)
end
end
end
print(workspace_folder, "is not currently part of the workspace")
print(workspace_folder, 'is not currently part of the workspace')
end
--- Lists all symbols in the current workspace in the quickfix window.
@@ -631,11 +645,11 @@ end
---
---@param query (string, optional)
function M.workspace_symbol(query)
query = query or npcall(vfn.input, "Query: ")
query = query or npcall(vfn.input, 'Query: ')
if query == nil then
return
end
local params = {query = query}
local params = { query = query }
request('workspace/symbol', params)
end
@@ -665,7 +679,6 @@ function M.clear_references()
util.buf_clear_references()
end
---@private
--
--- This is not public because the main extension point is
@@ -728,10 +741,11 @@ local function on_code_action_results(results, ctx, options)
--
local client = vim.lsp.get_client_by_id(action_tuple[1])
local action = action_tuple[2]
if not action.edit
and client
and vim.tbl_get(client.server_capabilities, "codeActionProvider", "resolveProvider") then
if
not action.edit
and client
and vim.tbl_get(client.server_capabilities, 'codeActionProvider', 'resolveProvider')
then
client.request('codeAction/resolve', action, function(err, resolved_action)
if err then
vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR)
@@ -761,7 +775,6 @@ local function on_code_action_results(results, ctx, options)
}, on_user_choice)
end
--- Requests code actions from all clients and calls the handler exactly once
--- with all aggregated results
---@private
@@ -769,7 +782,7 @@ 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)
local ctx = { bufnr = bufnr, method = method, params = params}
local ctx = { bufnr = bufnr, method = method, params = params }
on_code_action_results(results, ctx, options)
end)
end
@@ -794,7 +807,7 @@ end
--- (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(options)
validate { options = { options, 't', true } }
validate({ options = { options, 't', true } })
options = options or {}
-- Detect old API call code_action(context) which should now be
-- code_action({ context = context} )
@@ -826,7 +839,7 @@ end
---@param end_pos ({number, number}, optional) mark-indexed position.
---Defaults to the end of the last visual selection.
function M.range_code_action(context, start_pos, end_pos)
validate { context = { context, 't', true } }
validate({ context = { context, 't', true } })
context = context or {}
if not context.diagnostics then
context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics()
@@ -841,16 +854,16 @@ end
---@param command_params table A valid `ExecuteCommandParams` object
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
function M.execute_command(command_params)
validate {
validate({
command = { command_params.command, 's' },
arguments = { command_params.arguments, 't', true }
}
arguments = { command_params.arguments, 't', true },
})
command_params = {
command=command_params.command,
arguments=command_params.arguments,
workDoneToken=command_params.workDoneToken,
command = command_params.command,
arguments = command_params.arguments,
workDoneToken = command_params.workDoneToken,
}
request('workspace/executeCommand', command_params )
request('workspace/executeCommand', command_params)
end
return M

View File

@@ -12,7 +12,7 @@ local lens_cache_by_buf = setmetatable({}, {
__index = function(t, b)
local key = b > 0 and b or api.nvim_get_current_buf()
return rawget(t, key)
end
end,
})
local namespaces = setmetatable({}, {
@@ -20,13 +20,12 @@ local namespaces = setmetatable({}, {
local value = api.nvim_create_namespace('vim_lsp_codelens:' .. key)
rawset(t, key, value)
return value
end;
end,
})
---@private
M.__namespaces = namespaces
---@private
local function execute_lens(lens, bufnr, client_id)
local line = lens.range.start.line
@@ -44,10 +43,14 @@ local function execute_lens(lens, bufnr, client_id)
local command_provider = client.server_capabilities.executeCommandProvider
local commands = type(command_provider) == 'table' and command_provider.commands or {}
if not vim.tbl_contains(commands, command.command) then
vim.notify(string.format(
"Language server does not support command `%s`. This command may require a client extension.", command.command),
vim.log.levels.WARN)
return
vim.notify(
string.format(
'Language server does not support command `%s`. This command may require a client extension.',
command.command
),
vim.log.levels.WARN
)
return
end
client.request('workspace/executeCommand', command, function(...)
local result = vim.lsp.handlers['workspace/executeCommand'](...)
@@ -56,14 +59,15 @@ local function execute_lens(lens, bufnr, client_id)
end, bufnr)
end
--- Return all lenses for the given buffer
---
---@param bufnr number Buffer number. 0 can be used for the current buffer.
---@return table (`CodeLens[]`)
function M.get(bufnr)
local lenses_by_client = lens_cache_by_buf[bufnr or 0]
if not lenses_by_client then return {} end
if not lenses_by_client then
return {}
end
local lenses = {}
for _, client_lenses in pairs(lenses_by_client) do
vim.list_extend(lenses, client_lenses)
@@ -71,7 +75,6 @@ function M.get(bufnr)
return lenses
end
--- Run the code lens in the current line
---
function M.run()
@@ -82,7 +85,7 @@ function M.run()
for client, lenses in pairs(lenses_by_client) do
for _, lens in pairs(lenses) do
if lens.range.start.line == (line - 1) then
table.insert(options, {client=client, lens=lens})
table.insert(options, { client = client, lens = lens })
end
end
end
@@ -105,7 +108,6 @@ function M.run()
end
end
--- Display the lenses using virtual text
---
---@param lenses table of lenses to display (`CodeLens[] | null`)
@@ -133,19 +135,20 @@ function M.display(lenses, bufnr, client_id)
local num_line_lenses = #line_lenses
for j, lens in ipairs(line_lenses) do
local text = lens.command and lens.command.title or 'Unresolved lens ...'
table.insert(chunks, {text, 'LspCodeLens' })
table.insert(chunks, { text, 'LspCodeLens' })
if j < num_line_lenses then
table.insert(chunks, {' | ', 'LspCodeLensSeparator' })
table.insert(chunks, { ' | ', 'LspCodeLensSeparator' })
end
end
if #chunks > 0 then
api.nvim_buf_set_extmark(bufnr, ns, i, 0, { virt_text = chunks,
hl_mode="combine" })
api.nvim_buf_set_extmark(bufnr, ns, i, 0, {
virt_text = chunks,
hl_mode = 'combine',
})
end
end
end
--- Store lenses for a specific buffer and client
---
---@param lenses table of lenses to store (`CodeLens[] | null`)
@@ -158,16 +161,17 @@ function M.save(lenses, bufnr, client_id)
lens_cache_by_buf[bufnr] = lenses_by_client
local ns = namespaces[client_id]
api.nvim_buf_attach(bufnr, false, {
on_detach = function(b) lens_cache_by_buf[b] = nil end,
on_detach = function(b)
lens_cache_by_buf[b] = nil
end,
on_lines = function(_, b, _, first_lnum, last_lnum)
api.nvim_buf_clear_namespace(b, ns, first_lnum, last_lnum)
end
end,
})
end
lenses_by_client[client_id] = lenses
end
---@private
local function resolve_lenses(lenses, bufnr, client_id, callback)
lenses = lenses or {}
@@ -201,8 +205,7 @@ local function resolve_lenses(lenses, bufnr, client_id, callback)
ns,
lens.range.start.line,
0,
{ virt_text = {{ lens.command.title, 'LspCodeLens' }},
hl_mode="combine" }
{ virt_text = { { lens.command.title, 'LspCodeLens' } }, hl_mode = 'combine' }
)
end
countdown()
@@ -211,13 +214,12 @@ local function resolve_lenses(lenses, bufnr, client_id, callback)
end
end
--- |lsp-handler| for the method `textDocument/codeLens`
---
function M.on_codelens(err, result, ctx, _)
if err then
active_refreshes[ctx.bufnr] = nil
local _ = log.error() and log.error("codelens", err)
local _ = log.error() and log.error('codelens', err)
return
end
@@ -232,7 +234,6 @@ function M.on_codelens(err, result, ctx, _)
end)
end
--- Refresh the codelens for the current buffer
---
--- It is recommended to trigger this using an autocmd or via keymap.
@@ -243,7 +244,7 @@ end
---
function M.refresh()
local params = {
textDocument = util.make_text_document_params()
textDocument = util.make_text_document_params(),
}
local bufnr = api.nvim_get_current_buf()
if active_refreshes[bufnr] then
@@ -253,5 +254,4 @@ function M.refresh()
vim.lsp.buf_request(0, 'textDocument/codeLens', params, M.on_codelens)
end
return M

View File

@@ -50,12 +50,12 @@ end
---@private
local function line_byte_from_position(lines, lnum, col, offset_encoding)
if not lines or offset_encoding == "utf-8" then
if not lines or offset_encoding == 'utf-8' then
return col
end
local line = lines[lnum + 1]
local ok, result = pcall(vim.str_byteindex, line, col, offset_encoding == "utf-16")
local ok, result = pcall(vim.str_byteindex, line, col, offset_encoding == 'utf-16')
if ok then
return result
end
@@ -75,7 +75,7 @@ local function get_buf_lines(bufnr)
return
end
local content = f:read("*a")
local content = f:read('*a')
if not content then
-- Some LSP servers report diagnostics at a directory level, in which case
-- io.read() returns nil
@@ -83,7 +83,7 @@ local function get_buf_lines(bufnr)
return
end
local lines = vim.split(content, "\n")
local lines = vim.split(content, '\n')
f:close()
return lines
end
@@ -92,10 +92,10 @@ end
local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)
local buf_lines = get_buf_lines(bufnr)
local client = vim.lsp.get_client_by_id(client_id)
local offset_encoding = client and client.offset_encoding or "utf-16"
local offset_encoding = client and client.offset_encoding or 'utf-16'
return vim.tbl_map(function(diagnostic)
local start = diagnostic.range.start
local _end = diagnostic.range["end"]
local _end = diagnostic.range['end']
return {
lnum = start.line,
col = line_byte_from_position(buf_lines, start.line, start.character, offset_encoding),
@@ -122,14 +122,14 @@ end
---@private
local function diagnostic_vim_to_lsp(diagnostics)
return vim.tbl_map(function(diagnostic)
return vim.tbl_extend("keep", {
return vim.tbl_extend('keep', {
-- "keep" the below fields over any duplicate fields in diagnostic.user_data.lsp
range = {
start = {
line = diagnostic.lnum,
character = diagnostic.col,
},
["end"] = {
['end'] = {
line = diagnostic.end_lnum,
character = diagnostic.end_col,
},
@@ -148,10 +148,10 @@ local _client_namespaces = {}
---
---@param client_id number The id of the LSP client
function M.get_namespace(client_id)
vim.validate { client_id = { client_id, 'n' } }
vim.validate({ client_id = { client_id, 'n' } })
if not _client_namespaces[client_id] then
local client = vim.lsp.get_client_by_id(client_id)
local name = string.format("vim.lsp.%s.%d", client and client.name or "unknown", client_id)
local name = string.format('vim.lsp.%s.%d', client and client.name or 'unknown', client_id)
_client_namespaces[client_id] = vim.api.nvim_create_namespace(name)
end
return _client_namespaces[client_id]
@@ -203,7 +203,7 @@ function M.on_publish_diagnostics(_, result, ctx, config)
for _, opt in pairs(config) do
if type(opt) == 'table' then
if not opt.severity and opt.severity_limit then
opt.severity = {min=severity_lsp_to_vim(opt.severity_limit)}
opt.severity = { min = severity_lsp_to_vim(opt.severity_limit) }
end
end
end
@@ -240,7 +240,6 @@ end
-- Deprecated Functions {{{
--- Save diagnostics to the current buffer.
---
---@deprecated Prefer |vim.diagnostic.set()|
@@ -251,7 +250,7 @@ end
---@param client_id number
---@private
function M.save(diagnostics, bufnr, client_id)
vim.deprecate('vim.lsp.diagnostic.save', 'vim.diagnostic.set', '0.8' )
vim.deprecate('vim.lsp.diagnostic.save', 'vim.diagnostic.set', '0.8')
local namespace = M.get_namespace(client_id)
vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id))
end
@@ -265,14 +264,14 @@ end
--- If nil, diagnostics of all clients are included.
---@return table with diagnostics grouped by bufnr (bufnr: Diagnostic[])
function M.get_all(client_id)
vim.deprecate('vim.lsp.diagnostic.get_all', 'vim.diagnostic.get', '0.8' )
vim.deprecate('vim.lsp.diagnostic.get_all', 'vim.diagnostic.get', '0.8')
local result = {}
local namespace
if client_id then
namespace = M.get_namespace(client_id)
end
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
local diagnostics = diagnostic_vim_to_lsp(vim.diagnostic.get(bufnr, {namespace = namespace}))
local diagnostics = diagnostic_vim_to_lsp(vim.diagnostic.get(bufnr, { namespace = namespace }))
result[bufnr] = diagnostics
end
return result
@@ -287,8 +286,10 @@ end
--- Else, return just the diagnostics associated with the client_id.
---@param predicate function|nil Optional function for filtering diagnostics
function M.get(bufnr, client_id, predicate)
vim.deprecate('vim.lsp.diagnostic.get', 'vim.diagnostic.get', '0.8' )
predicate = predicate or function() return true end
vim.deprecate('vim.lsp.diagnostic.get', 'vim.diagnostic.get', '0.8')
predicate = predicate or function()
return true
end
if client_id == nil then
local all_diagnostics = {}
vim.lsp.for_each_buffer_client(bufnr, function(_, iter_client_id, _)
@@ -301,7 +302,7 @@ function M.get(bufnr, client_id, predicate)
end
local namespace = M.get_namespace(client_id)
return diagnostic_vim_to_lsp(vim.tbl_filter(predicate, vim.diagnostic.get(bufnr, {namespace=namespace})))
return diagnostic_vim_to_lsp(vim.tbl_filter(predicate, vim.diagnostic.get(bufnr, { namespace = namespace })))
end
--- Get the diagnostics by line
@@ -325,7 +326,7 @@ function M.get_line_diagnostics(bufnr, line_nr, opts, client_id)
if opts.severity then
opts.severity = severity_lsp_to_vim(opts.severity)
elseif opts.severity_limit then
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
end
if client_id then
@@ -349,7 +350,7 @@ end
---@param severity DiagnosticSeverity
---@param client_id number the client id
function M.get_count(bufnr, severity, client_id)
vim.deprecate('vim.lsp.diagnostic.get_count', 'vim.diagnostic.get', '0.8' )
vim.deprecate('vim.lsp.diagnostic.get_count', 'vim.diagnostic.get', '0.8')
severity = severity_lsp_to_vim(severity)
local opts = { severity = severity }
if client_id ~= nil then
@@ -366,15 +367,15 @@ end
---@param opts table See |vim.lsp.diagnostic.goto_next()|
---@return table Previous diagnostic
function M.get_prev(opts)
vim.deprecate('vim.lsp.diagnostic.get_prev', 'vim.diagnostic.get_prev', '0.8' )
vim.deprecate('vim.lsp.diagnostic.get_prev', 'vim.diagnostic.get_prev', '0.8')
if opts then
if opts.severity then
opts.severity = severity_lsp_to_vim(opts.severity)
elseif opts.severity_limit then
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
end
end
return diagnostic_vim_to_lsp({vim.diagnostic.get_prev(opts)})[1]
return diagnostic_vim_to_lsp({ vim.diagnostic.get_prev(opts) })[1]
end
--- Return the pos, {row, col}, for the prev diagnostic in the current buffer.
@@ -384,12 +385,12 @@ end
---@param opts table See |vim.lsp.diagnostic.goto_next()|
---@return table Previous diagnostic position
function M.get_prev_pos(opts)
vim.deprecate('vim.lsp.diagnostic.get_prev_pos', 'vim.diagnostic.get_prev_pos', '0.8' )
vim.deprecate('vim.lsp.diagnostic.get_prev_pos', 'vim.diagnostic.get_prev_pos', '0.8')
if opts then
if opts.severity then
opts.severity = severity_lsp_to_vim(opts.severity)
elseif opts.severity_limit then
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
end
end
return vim.diagnostic.get_prev_pos(opts)
@@ -401,12 +402,12 @@ end
---
---@param opts table See |vim.lsp.diagnostic.goto_next()|
function M.goto_prev(opts)
vim.deprecate('vim.lsp.diagnostic.goto_prev', 'vim.diagnostic.goto_prev', '0.8' )
vim.deprecate('vim.lsp.diagnostic.goto_prev', 'vim.diagnostic.goto_prev', '0.8')
if opts then
if opts.severity then
opts.severity = severity_lsp_to_vim(opts.severity)
elseif opts.severity_limit then
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
end
end
return vim.diagnostic.goto_prev(opts)
@@ -419,15 +420,15 @@ end
---@param opts table See |vim.lsp.diagnostic.goto_next()|
---@return table Next diagnostic
function M.get_next(opts)
vim.deprecate('vim.lsp.diagnostic.get_next', 'vim.diagnostic.get_next', '0.8' )
vim.deprecate('vim.lsp.diagnostic.get_next', 'vim.diagnostic.get_next', '0.8')
if opts then
if opts.severity then
opts.severity = severity_lsp_to_vim(opts.severity)
elseif opts.severity_limit then
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
end
end
return diagnostic_vim_to_lsp({vim.diagnostic.get_next(opts)})[1]
return diagnostic_vim_to_lsp({ vim.diagnostic.get_next(opts) })[1]
end
--- Return the pos, {row, col}, for the next diagnostic in the current buffer.
@@ -437,12 +438,12 @@ end
---@param opts table See |vim.lsp.diagnostic.goto_next()|
---@return table Next diagnostic position
function M.get_next_pos(opts)
vim.deprecate('vim.lsp.diagnostic.get_next_pos', 'vim.diagnostic.get_next_pos', '0.8' )
vim.deprecate('vim.lsp.diagnostic.get_next_pos', 'vim.diagnostic.get_next_pos', '0.8')
if opts then
if opts.severity then
opts.severity = severity_lsp_to_vim(opts.severity)
elseif opts.severity_limit then
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
end
end
return vim.diagnostic.get_next_pos(opts)
@@ -452,12 +453,12 @@ end
---
---@deprecated Prefer |vim.diagnostic.goto_next()|
function M.goto_next(opts)
vim.deprecate('vim.lsp.diagnostic.goto_next', 'vim.diagnostic.goto_next', '0.8' )
vim.deprecate('vim.lsp.diagnostic.goto_next', 'vim.diagnostic.goto_next', '0.8')
if opts then
if opts.severity then
opts.severity = severity_lsp_to_vim(opts.severity)
elseif opts.severity_limit then
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
end
end
return vim.diagnostic.goto_next(opts)
@@ -476,10 +477,10 @@ end
--- - severity_limit (DiagnosticSeverity):
--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
function M.set_signs(diagnostics, bufnr, client_id, _, opts)
vim.deprecate('vim.lsp.diagnostic.set_signs', nil , '0.8' )
vim.deprecate('vim.lsp.diagnostic.set_signs', nil, '0.8')
local namespace = M.get_namespace(client_id)
if opts and not opts.severity and opts.severity_limit then
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
end
vim.diagnostic._set_signs(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id), opts)
@@ -497,10 +498,10 @@ end
--- - severity_limit (DiagnosticSeverity):
--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
function M.set_underline(diagnostics, bufnr, client_id, _, opts)
vim.deprecate('vim.lsp.diagnostic.set_underline', nil , '0.8' )
vim.deprecate('vim.lsp.diagnostic.set_underline', nil, '0.8')
local namespace = M.get_namespace(client_id)
if opts and not opts.severity and opts.severity_limit then
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
end
return vim.diagnostic._set_underline(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id), opts)
end
@@ -519,10 +520,10 @@ end
--- - severity_limit (DiagnosticSeverity):
--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
function M.set_virtual_text(diagnostics, bufnr, client_id, _, opts)
vim.deprecate('vim.lsp.diagnostic.set_virtual_text', nil , '0.8' )
vim.deprecate('vim.lsp.diagnostic.set_virtual_text', nil, '0.8')
local namespace = M.get_namespace(client_id)
if opts and not opts.severity and opts.severity_limit then
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
end
return vim.diagnostic._set_virtual_text(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id), opts)
end
@@ -538,7 +539,7 @@ end
---@return an array of [text, hl_group] arrays. This can be passed directly to
--- the {virt_text} option of |nvim_buf_set_extmark()|.
function M.get_virtual_text_chunks_for_line(bufnr, _, line_diags, opts)
vim.deprecate('vim.lsp.diagnostic.get_virtual_text_chunks_for_line', nil, '0.8' )
vim.deprecate('vim.lsp.diagnostic.get_virtual_text_chunks_for_line', nil, '0.8')
return vim.diagnostic._get_virt_text_chunks(diagnostic_lsp_to_vim(line_diags, bufnr), opts)
end
@@ -556,14 +557,14 @@ end
---@param position table|nil The (0,0)-indexed position
---@return table {popup_bufnr, win_id}
function M.show_position_diagnostics(opts, buf_nr, position)
vim.deprecate('vim.lsp.diagnostic.show_position_diagnostics', 'vim.diagnostic.open_float', '0.8' )
vim.deprecate('vim.lsp.diagnostic.show_position_diagnostics', 'vim.diagnostic.open_float', '0.8')
opts = opts or {}
opts.scope = "cursor"
opts.scope = 'cursor'
opts.pos = position
if opts.severity then
opts.severity = severity_lsp_to_vim(opts.severity)
elseif opts.severity_limit then
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
end
return vim.diagnostic.open_float(buf_nr, opts)
end
@@ -580,9 +581,9 @@ end
---@param client_id number|nil the client id
---@return table {popup_bufnr, win_id}
function M.show_line_diagnostics(opts, buf_nr, line_nr, client_id)
vim.deprecate('vim.lsp.diagnostic.show_line_diagnostics', 'vim.diagnostic.open_float', '0.8' )
vim.deprecate('vim.lsp.diagnostic.show_line_diagnostics', 'vim.diagnostic.open_float', '0.8')
opts = opts or {}
opts.scope = "line"
opts.scope = 'line'
opts.pos = line_nr
if client_id then
opts.namespace = M.get_namespace(client_id)
@@ -604,7 +605,7 @@ end
--- client. The default is to redraw diagnostics for all attached
--- clients.
function M.redraw(bufnr, client_id)
vim.deprecate('vim.lsp.diagnostic.redraw', 'vim.diagnostic.show', '0.8' )
vim.deprecate('vim.lsp.diagnostic.redraw', 'vim.diagnostic.show', '0.8')
bufnr = get_bufnr(bufnr)
if not client_id then
return vim.lsp.for_each_buffer_client(bufnr, function(client)
@@ -632,12 +633,12 @@ end
--- - {workspace}: (boolean, default true)
--- - Set the list with workspace diagnostics
function M.set_qflist(opts)
vim.deprecate('vim.lsp.diagnostic.set_qflist', 'vim.diagnostic.setqflist', '0.8' )
vim.deprecate('vim.lsp.diagnostic.set_qflist', 'vim.diagnostic.setqflist', '0.8')
opts = opts or {}
if opts.severity then
opts.severity = severity_lsp_to_vim(opts.severity)
elseif opts.severity_limit then
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
end
if opts.client_id then
opts.client_id = nil
@@ -664,12 +665,12 @@ end
--- - {workspace}: (boolean, default false)
--- - Set the list with workspace diagnostics
function M.set_loclist(opts)
vim.deprecate('vim.lsp.diagnostic.set_loclist', 'vim.diagnostic.setloclist', '0.8' )
vim.deprecate('vim.lsp.diagnostic.set_loclist', 'vim.diagnostic.setloclist', '0.8')
opts = opts or {}
if opts.severity then
opts.severity = severity_lsp_to_vim(opts.severity)
elseif opts.severity_limit then
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
end
if opts.client_id then
opts.client_id = nil
@@ -692,7 +693,7 @@ end
-- send diagnostic information and the client will still process it. The
-- diagnostics are simply not displayed to the user.
function M.disable(bufnr, client_id)
vim.deprecate('vim.lsp.diagnostic.disable', 'vim.diagnostic.disable', '0.8' )
vim.deprecate('vim.lsp.diagnostic.disable', 'vim.diagnostic.disable', '0.8')
if not client_id then
return vim.lsp.for_each_buffer_client(bufnr, function(client)
M.disable(bufnr, client.id)
@@ -713,7 +714,7 @@ end
--- client. The default is to enable diagnostics for all attached
--- clients.
function M.enable(bufnr, client_id)
vim.deprecate('vim.lsp.diagnostic.enable', 'vim.diagnostic.enable', '0.8' )
vim.deprecate('vim.lsp.diagnostic.enable', 'vim.diagnostic.enable', '0.8')
if not client_id then
return vim.lsp.for_each_buffer_client(bufnr, function(client)
M.enable(bufnr, client.id)

View File

@@ -1,6 +1,6 @@
local log = require 'vim.lsp.log'
local protocol = require 'vim.lsp.protocol'
local util = require 'vim.lsp.util'
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
@@ -12,8 +12,8 @@ local M = {}
--- Writes to error buffer.
---@param ... (table of strings) Will be concatenated before being written
local function err_message(...)
vim.notify(table.concat(vim.tbl_flatten{...}), vim.log.levels.ERROR)
api.nvim_command("redraw")
vim.notify(table.concat(vim.tbl_flatten({ ... })), vim.log.levels.ERROR)
api.nvim_command('redraw')
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
@@ -25,15 +25,17 @@ end
local function progress_handler(_, result, ctx, _)
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
local client_name = client and client.name or string.format("id=%d", 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 during progress update")
err_message('LSP[', client_name, '] client has shut down during progress update')
return vim.NIL
end
local val = result.value -- unspecified yet
local token = result.token -- string or number
local val = result.value -- unspecified yet
local token = result.token -- string or number
if type(val) ~= 'table' then val = { content=val } end
if type(val) ~= 'table' then
val = { content = val }
end
if val.kind then
if val.kind == 'begin' then
client.messages.progress[token] = {
@@ -42,11 +44,11 @@ local function progress_handler(_, result, ctx, _)
percentage = val.percentage,
}
elseif val.kind == 'report' then
client.messages.progress[token].message = val.message;
client.messages.progress[token].percentage = val.percentage;
client.messages.progress[token].message = val.message
client.messages.progress[token].percentage = val.percentage
elseif val.kind == 'end' then
if client.messages.progress[token] == nil then
err_message("LSP[", client_name, "] received `end` message with no corresponding `begin`")
err_message('LSP[', client_name, '] received `end` message with no corresponding `begin`')
else
client.messages.progress[token].message = val.message
client.messages.progress[token].done = true
@@ -57,20 +59,20 @@ local function progress_handler(_, result, ctx, _)
client.messages.progress[token].done = true
end
vim.api.nvim_command("doautocmd <nomodeline> User LspProgressUpdate")
vim.api.nvim_command('doautocmd <nomodeline> User LspProgressUpdate')
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
M['$/progress'] = progress_handler
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_workDoneProgress_create
M['window/workDoneProgress/create'] = function(_, result, ctx)
M['window/workDoneProgress/create'] = function(_, result, ctx)
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
local token = result.token -- string or number
local client_name = client and client.name or string.format("id=%d", client_id)
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 while creating progress report")
err_message('LSP[', client_name, '] client has shut down while creating progress report')
return vim.NIL
end
client.messages.progress[token] = {}
@@ -79,20 +81,19 @@ end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest
M['window/showMessageRequest'] = function(_, result)
local actions = result.actions
print(result.message)
local option_strings = {result.message, "\nRequest Actions:"}
local option_strings = { result.message, '\nRequest 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))
table.insert(option_strings, string.format('%d. %s', i, title))
end
-- window/showMessageRequest can return either MessageActionItem[] or null.
local choice = vim.fn.inputlist(option_strings)
if choice < 1 or choice > #actions then
return vim.NIL
return vim.NIL
else
return actions[choice]
end
@@ -101,11 +102,11 @@ end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability
M['client/registerCapability'] = function(_, _, ctx)
local client_id = ctx.client_id
local warning_tpl = "The language server %s triggers a registerCapability "..
"handler despite dynamicRegistration set to false. "..
"Report upstream, this warning is harmless"
local warning_tpl = 'The language server %s triggers a registerCapability '
.. 'handler despite dynamicRegistration set to false. '
.. 'Report upstream, this warning is harmless'
local client = vim.lsp.get_client_by_id(client_id)
local client_name = client and client.name or string.format("id=%d", client_id)
local client_name = client and client.name or string.format('id=%d', client_id)
local warning = string.format(warning_tpl, client_name)
log.warn(warning)
return vim.NIL
@@ -113,17 +114,19 @@ end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
M['workspace/applyEdit'] = function(_, workspace_edit, ctx)
if not workspace_edit then return end
if not workspace_edit then
return
end
-- TODO(ashkan) Do something more with label?
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
if workspace_edit.label then
print("Workspace edit", workspace_edit.label)
print('Workspace edit', workspace_edit.label)
end
local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit, client.offset_encoding)
return {
applied = status;
failureReason = result;
applied = status,
failureReason = result,
}
end
@@ -132,7 +135,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[", client_id, "] client has shut down after sending a workspace/configuration request")
err_message('LSP[', client_id, '] client has shut down after sending a workspace/configuration request')
return
end
if not result.items then
@@ -158,7 +161,7 @@ M['workspace/workspaceFolders'] = function(_, _, 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[id=', client_id, '] client has shut down after sending the message')
return
end
return client.workspace_folders or vim.NIL
@@ -172,7 +175,6 @@ M['textDocument/codeLens'] = function(...)
return require('vim.lsp.codelens').on_codelens(...)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
M['textDocument/references'] = function(_, result, ctx, config)
if not result or vim.tbl_isempty(result) then
@@ -182,23 +184,22 @@ M['textDocument/references'] = function(_, result, ctx, config)
config = config or {}
if config.loclist then
vim.fn.setloclist(0, {}, ' ', {
title = 'References';
items = util.locations_to_items(result, client.offset_encoding);
context = ctx;
title = 'References',
items = util.locations_to_items(result, client.offset_encoding),
context = ctx,
})
api.nvim_command("lopen")
api.nvim_command('lopen')
else
vim.fn.setqflist({}, ' ', {
title = 'References';
items = util.locations_to_items(result, client.offset_encoding);
context = ctx;
title = 'References',
items = util.locations_to_items(result, client.offset_encoding),
context = ctx,
})
api.nvim_command("botright copen")
api.nvim_command('botright copen')
end
end
end
---@private
--- Return a function that converts LSP responses to list items and opens the list
---
@@ -218,27 +219,26 @@ local function response_to_list(map_result, entity, title_fn)
config = config or {}
if config.loclist then
vim.fn.setloclist(0, {}, ' ', {
title = title_fn(ctx);
items = map_result(result, ctx.bufnr);
context = ctx;
title = title_fn(ctx),
items = map_result(result, ctx.bufnr),
context = ctx,
})
api.nvim_command("lopen")
api.nvim_command('lopen')
else
vim.fn.setqflist({}, ' ', {
title = title_fn(ctx);
items = map_result(result, ctx.bufnr);
context = ctx;
title = title_fn(ctx),
items = map_result(result, ctx.bufnr),
context = ctx,
})
api.nvim_command("botright copen")
api.nvim_command('botright copen')
end
end
end
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
M['textDocument/documentSymbol'] = response_to_list(util.symbols_to_items, 'document symbols', function(ctx)
local fname = vim.fn.fnamemodify(vim.uri_to_fname(ctx.params.textDocument.uri), ":.")
local fname = vim.fn.fnamemodify(vim.uri_to_fname(ctx.params.textDocument.uri), ':.')
return string.format('Symbols in %s', fname)
end)
@@ -249,36 +249,44 @@ end)
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename
M['textDocument/rename'] = function(_, result, ctx, _)
if not result then return end
if not result then
return
end
local client = vim.lsp.get_client_by_id(ctx.client_id)
util.apply_workspace_edit(result, client.offset_encoding)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting
M['textDocument/rangeFormatting'] = function(_, result, ctx, _)
if not result then return end
if not result then
return
end
local client = vim.lsp.get_client_by_id(ctx.client_id)
util.apply_text_edits(result, ctx.bufnr, client.offset_encoding)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
M['textDocument/formatting'] = function(_, result, ctx, _)
if not result then return end
if not result then
return
end
local client = vim.lsp.get_client_by_id(ctx.client_id)
util.apply_text_edits(result, ctx.bufnr, client.offset_encoding)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
M['textDocument/completion'] = function(_, result, _, _)
if vim.tbl_isempty(result or {}) then return end
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 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 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)
vim.fn.complete(textMatch + 1, matches)
end
--- |lsp-handler| for the method "textDocument/hover"
@@ -307,7 +315,7 @@ function M.hover(_, result, ctx, config)
vim.notify('No information available')
return
end
return util.open_floating_preview(markdown_lines, "markdown", config)
return util.open_floating_preview(markdown_lines, 'markdown', config)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
@@ -335,9 +343,9 @@ local function location_handler(_, result, ctx, _)
if #result > 1 then
vim.fn.setqflist({}, ' ', {
title = 'LSP locations',
items = util.locations_to_items(result, client.offset_encoding)
items = util.locations_to_items(result, client.offset_encoding),
})
api.nvim_command("botright copen")
api.nvim_command('botright copen')
end
else
util.jump_to_location(result, client.offset_encoding)
@@ -379,7 +387,7 @@ function M.signature_help(_, result, ctx, config)
return
end
local client = vim.lsp.get_client_by_id(ctx.client_id)
local triggers = vim.tbl_get(client.server_capabilities, "signatureHelpProvider", "triggerCharacters")
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)
@@ -389,9 +397,9 @@ function M.signature_help(_, result, ctx, config)
end
return
end
local fbuf, fwin = util.open_floating_preview(lines, "markdown", config)
local fbuf, fwin = util.open_floating_preview(lines, 'markdown', config)
if hl then
api.nvim_buf_add_highlight(fbuf, -1, "LspSignatureActiveParameter", 0, unpack(hl))
api.nvim_buf_add_highlight(fbuf, -1, 'LspSignatureActiveParameter', 0, unpack(hl))
end
return fbuf, fwin
end
@@ -401,10 +409,14 @@ M['textDocument/signatureHelp'] = M.signature_help
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight
M['textDocument/documentHighlight'] = function(_, result, ctx, _)
if not result then return end
if not result then
return
end
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
if not client then return end
if not client then
return
end
util.buf_highlight_references(ctx.bufnr, result, client.offset_encoding)
end
@@ -417,7 +429,9 @@ end
---@returns `CallHierarchyOutgoingCall[]` if {direction} is `"to"`,
local make_call_hierarchy_handler = function(direction)
return function(_, result)
if not result then return end
if not result then
return
end
local items = {}
for _, call_hierarchy_call in pairs(result) do
local call_hierarchy_item = call_hierarchy_call[direction]
@@ -430,8 +444,8 @@ local make_call_hierarchy_handler = function(direction)
})
end
end
vim.fn.setqflist({}, ' ', {title = 'LSP call hierarchy', items = items})
api.nvim_command("botright copen")
vim.fn.setqflist({}, ' ', { title = 'LSP call hierarchy', items = items })
api.nvim_command('botright copen')
end
end
@@ -447,15 +461,15 @@ M['window/logMessage'] = function(_, result, ctx, _)
local message = result.message
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
local client_name = client and client.name or string.format("id=%d", 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 ", message)
err_message('LSP[', client_name, '] client has shut down after sending ', 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 or message_type == protocol.MessageType.Log then
elseif message_type == protocol.MessageType.Info or message_type == protocol.MessageType.Log then
log.info(message)
else
log.debug(message)
@@ -469,15 +483,15 @@ M['window/showMessage'] = function(_, result, ctx, _)
local message = result.message
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
local client_name = client and client.name or string.format("id=%d", 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 ", 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)
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))
api.nvim_out_write(string.format('LSP[%s][%s] %s\n', client_name, message_type_name, message))
end
return result
end
@@ -485,9 +499,13 @@ end
-- Add boilerplate error validation and logging for all of these.
for k, fn in pairs(M) do
M[k] = function(err, result, ctx, config)
local _ = log.trace() and log.trace('default_handler', ctx.method, {
err = err, result = result, ctx=vim.inspect(ctx), config = config
})
local _ = log.trace()
and log.trace('default_handler', ctx.method, {
err = err,
result = result,
ctx = vim.inspect(ctx),
config = config,
})
if err then
-- LSP spec:
@@ -499,7 +517,7 @@ for k, fn in pairs(M) do
-- Per LSP, don't show ContentModified error to the user.
if err.code ~= protocol.ErrorCodes.ContentModified then
local client = vim.lsp.get_client_by_id(ctx.client_id)
local client_name = client and client.name or string.format("client_id=%d", ctx.client_id)
local client_name = client and client.name or string.format('client_id=%d', ctx.client_id)
err_message(client_name .. ': ' .. tostring(err.code) .. ': ' .. err.message)
end

View File

@@ -8,20 +8,19 @@ function M.check()
local log = require('vim.lsp.log')
local current_log_level = log.get_level()
local log_level_string = log.levels[current_log_level]
report_info(string.format("LSP log level : %s", log_level_string))
report_info(string.format('LSP log level : %s', log_level_string))
if current_log_level < log.levels.WARN then
report_warn(string.format("Log level %s will cause degraded performance and high disk usage", log_level_string))
report_warn(string.format('Log level %s will cause degraded performance and high disk usage', log_level_string))
end
local log_path = vim.lsp.get_log_path()
report_info(string.format("Log path: %s", log_path))
report_info(string.format('Log path: %s', log_path))
local log_size = vim.loop.fs_stat(log_path).size
local report_fn = (log_size / 1000000 > 100 and report_warn or report_info)
report_fn(string.format("Log size: %d KB", log_size / 1000 ))
report_fn(string.format('Log size: %d KB', log_size / 1000))
end
return M

View File

@@ -14,21 +14,23 @@ log.levels = vim.deepcopy(vim.log.levels)
-- Default log level is warn.
local current_log_level = log.levels.WARN
local log_date_format = "%F %H:%M:%S"
local format_func = function(arg) return vim.inspect(arg, {newline=''}) end
local log_date_format = '%F %H:%M:%S'
local format_func = function(arg)
return vim.inspect(arg, { newline = '' })
end
do
local path_sep = vim.loop.os_uname().version:match("Windows") and "\\" or "/"
local path_sep = vim.loop.os_uname().version:match('Windows') and '\\' or '/'
---@private
local function path_join(...)
return table.concat(vim.tbl_flatten{...}, path_sep)
return table.concat(vim.tbl_flatten({ ... }), path_sep)
end
local logfilename = path_join(vim.fn.stdpath('cache'), 'lsp.log')
-- TODO: Ideally the directory should be created in open_logfile(), right
-- before opening the log file, but open_logfile() can be called from libuv
-- callbacks, where using fn.mkdir() is not allowed.
vim.fn.mkdir(vim.fn.stdpath('cache'), "p")
vim.fn.mkdir(vim.fn.stdpath('cache'), 'p')
--- Returns the log filename.
---@returns (string) log filename
@@ -41,28 +43,28 @@ do
--- Opens log file. Returns true if file is open, false on error
local function open_logfile()
-- Try to open file only once
if logfile then return true end
if openerr then return false end
if logfile then
return true
end
if openerr then
return false
end
logfile, openerr = io.open(logfilename, "a+")
logfile, openerr = io.open(logfilename, 'a+')
if not logfile then
local err_msg = string.format("Failed to open LSP client log file: %s", openerr)
local err_msg = string.format('Failed to open LSP client log file: %s', openerr)
vim.notify(err_msg, vim.log.levels.ERROR)
return false
end
local log_info = vim.loop.fs_stat(logfilename)
if log_info and log_info.size > 1e9 then
local warn_msg = string.format(
"LSP client log is large (%d MB): %s",
log_info.size / (1000 * 1000),
logfilename
)
local warn_msg = string.format('LSP client log is large (%d MB): %s', log_info.size / (1000 * 1000), logfilename)
vim.notify(warn_msg)
end
-- Start message for logging
logfile:write(string.format("[START][%s] LSP logging initiated\n", os.date(log_date_format)))
logfile:write(string.format('[START][%s] LSP logging initiated\n', os.date(log_date_format)))
return true
end
@@ -83,24 +85,36 @@ do
-- ```
--
-- This way you can avoid string allocations if the log level isn't high enough.
if level ~= "OFF" then
if level ~= 'OFF' then
log[level:lower()] = function(...)
local argc = select("#", ...)
if levelnr < current_log_level then return false end
if argc == 0 then return true end
if not open_logfile() then return false end
local info = debug.getinfo(2, "Sl")
local header = string.format("[%s][%s] ...%s:%s", level, os.date(log_date_format), string.sub(info.short_src, #info.short_src - 15), info.currentline)
local argc = select('#', ...)
if levelnr < current_log_level then
return false
end
if argc == 0 then
return true
end
if not open_logfile() then
return false
end
local info = debug.getinfo(2, 'Sl')
local header = string.format(
'[%s][%s] ...%s:%s',
level,
os.date(log_date_format),
string.sub(info.short_src, #info.short_src - 15),
info.currentline
)
local parts = { header }
for i = 1, argc do
local arg = select(i, ...)
if arg == nil then
table.insert(parts, "nil")
table.insert(parts, 'nil')
else
table.insert(parts, format_func(arg))
end
end
logfile:write(table.concat(parts, '\t'), "\n")
logfile:write(table.concat(parts, '\t'), '\n')
logfile:flush()
end
end
@@ -115,10 +129,10 @@ vim.tbl_add_reverse_lookup(log.levels)
---@param level (string or number) One of `vim.lsp.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))
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))
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
@@ -132,7 +146,7 @@ end
--- Sets formatting function used to format logs
---@param handle function function to apply to logging arguments, pass vim.inspect for multi-line formatting
function log.set_format_func(handle)
assert(handle == vim.inspect or type(handle) == 'function', "handle must be a function")
assert(handle == vim.inspect or type(handle) == 'function', 'handle must be a function')
format_func = handle
end

View File

@@ -23,150 +23,150 @@ end
local constants = {
DiagnosticSeverity = {
-- Reports an error.
Error = 1;
Error = 1,
-- Reports a warning.
Warning = 2;
Warning = 2,
-- Reports an information.
Information = 3;
Information = 3,
-- Reports a hint.
Hint = 4;
};
Hint = 4,
},
DiagnosticTag = {
-- Unused or unnecessary code
Unnecessary = 1;
Unnecessary = 1,
-- Deprecated or obsolete code
Deprecated = 2;
};
Deprecated = 2,
},
MessageType = {
-- An error message.
Error = 1;
Error = 1,
-- A warning message.
Warning = 2;
Warning = 2,
-- An information message.
Info = 3;
Info = 3,
-- A log message.
Log = 4;
};
Log = 4,
},
-- The file event type.
FileChangeType = {
-- The file got created.
Created = 1;
Created = 1,
-- The file got changed.
Changed = 2;
Changed = 2,
-- The file got deleted.
Deleted = 3;
};
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;
};
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;
Invoked = 1,
-- Completion was triggered by a trigger character specified by
-- the `triggerCharacters` properties of the `CompletionRegistrationOptions`.
TriggerCharacter = 2;
TriggerCharacter = 2,
-- Completion was re-triggered as the current completion list is incomplete.
TriggerForIncompleteCompletions = 3;
};
TriggerForIncompleteCompletions = 3,
},
-- A document highlight kind.
DocumentHighlightKind = {
-- A textual occurrence.
Text = 1;
Text = 1,
-- Read-access of a symbol, like reading a variable.
Read = 2;
Read = 2,
-- Write-access of a symbol, like writing to a variable.
Write = 3;
};
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;
};
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;
Manual = 1,
-- Automatic after a delay.
AfterDelay = 2;
AfterDelay = 2,
-- When the editor lost focus.
FocusOut = 3;
};
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;
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;
};
RequestCancelled = -32800,
ContentModified = -32801,
},
-- Describes the content type that a client supports in various
-- result literals like `Hover`, `ParameterInfo` or `CompletionItem`.
@@ -175,88 +175,88 @@ local constants = {
-- are reserved for internal usage.
MarkupKind = {
-- Plain text is supported as a content format
PlainText = 'plaintext';
PlainText = 'plaintext',
-- Markdown is supported as a content format
Markdown = 'markdown';
};
Markdown = 'markdown',
},
ResourceOperationKind = {
-- Supports creating new files and folders.
Create = 'create';
Create = 'create',
-- Supports renaming existing files and folders.
Rename = 'rename';
Rename = 'rename',
-- Supports deleting existing files and folders.
Delete = 'delete';
};
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';
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';
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';
TextOnlyTransactional = 'textOnlyTransactional',
-- The client tries to undo the operations already executed. But there is no
-- guarantee that this succeeds.
Undo = 'undo';
};
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;
};
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;
None = 0,
-- Documents are synced by always sending the full content
-- of the document.
Full = 1;
Full = 1,
-- Documents are synced by sending the full content on open.
-- After that only incremental updates to the document are
-- send.
Incremental = 2;
};
Incremental = 2,
},
WatchKind = {
-- Interested in create events.
Create = 1;
Create = 1,
-- Interested in change events
Change = 2;
Change = 2,
-- Interested in delete events
Delete = 4;
};
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;
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;
};
Snippet = 2,
},
-- A set of predefined code action kinds
CodeActionKind = {
-- Empty kind.
Empty = '';
Empty = '',
-- Base kind for quickfix actions
QuickFix = 'quickfix';
QuickFix = 'quickfix',
-- Base kind for refactoring actions
Refactor = 'refactor';
Refactor = 'refactor',
-- Base kind for refactoring extraction actions
--
-- Example extract actions:
@@ -266,7 +266,7 @@ local constants = {
-- - Extract variable
-- - Extract interface from class
-- - ...
RefactorExtract = 'refactor.extract';
RefactorExtract = 'refactor.extract',
-- Base kind for refactoring inline actions
--
-- Example inline actions:
@@ -275,7 +275,7 @@ local constants = {
-- - Inline variable
-- - Inline constant
-- - ...
RefactorInline = 'refactor.inline';
RefactorInline = 'refactor.inline',
-- Base kind for refactoring rewrite actions
--
-- Example rewrite actions:
@@ -286,14 +286,14 @@ local constants = {
-- - Make method static
-- - Move method to base class
-- - ...
RefactorRewrite = 'refactor.rewrite';
RefactorRewrite = 'refactor.rewrite',
-- Base kind for source actions
--
-- Source code actions apply to the entire file.
Source = 'source';
Source = 'source',
-- Base kind for an organize imports source action
SourceOrganizeImports = 'source.organizeImports';
};
SourceOrganizeImports = 'source.organizeImports',
},
}
for k, v in pairs(constants) do
@@ -620,19 +620,19 @@ function protocol.make_client_capabilities()
return {
textDocument = {
synchronization = {
dynamicRegistration = false;
dynamicRegistration = false,
-- TODO(ashkan) Send textDocument/willSave before saving (BufWritePre)
willSave = false;
willSave = false,
-- TODO(ashkan) Implement textDocument/willSaveWaitUntil
willSaveWaitUntil = false;
willSaveWaitUntil = false,
-- Send textDocument/didSave after saving (BufWritePost)
didSave = true;
};
didSave = true,
},
codeAction = {
dynamicRegistration = false;
dynamicRegistration = false,
codeActionLiteralSupport = {
codeActionKind = {
@@ -640,138 +640,146 @@ function protocol.make_client_capabilities()
local res = vim.tbl_values(protocol.CodeActionKind)
table.sort(res)
return res
end)();
};
};
isPreferredSupport = true;
dataSupport = true;
end)(),
},
},
isPreferredSupport = true,
dataSupport = true,
resolveSupport = {
properties = { 'edit', }
};
};
properties = { 'edit' },
},
},
completion = {
dynamicRegistration = false;
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;
snippetSupport = false,
commitCharactersSupport = false;
preselectSupport = false;
deprecatedSupport = false;
documentationFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText };
};
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
if type(k) == 'number' then
table.insert(res, k)
end
end
return res
end)();
};
end)(),
},
-- TODO(tjdevries): Implement this
contextSupport = false;
};
contextSupport = false,
},
declaration = {
linkSupport = true;
};
linkSupport = true,
},
definition = {
linkSupport = true;
};
linkSupport = true,
},
implementation = {
linkSupport = true;
};
linkSupport = true,
},
typeDefinition = {
linkSupport = true;
};
linkSupport = true,
},
hover = {
dynamicRegistration = false;
contentFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText };
};
dynamicRegistration = false,
contentFormat = { protocol.MarkupKind.Markdown, protocol.MarkupKind.PlainText },
},
signatureHelp = {
dynamicRegistration = false;
dynamicRegistration = false,
signatureInformation = {
activeParameterSupport = true;
documentationFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText };
activeParameterSupport = true,
documentationFormat = { protocol.MarkupKind.Markdown, protocol.MarkupKind.PlainText },
parameterInformation = {
labelOffsetSupport = true;
};
};
};
labelOffsetSupport = true,
},
},
},
references = {
dynamicRegistration = false;
};
dynamicRegistration = false,
},
documentHighlight = {
dynamicRegistration = false
};
dynamicRegistration = false,
},
documentSymbol = {
dynamicRegistration = false;
dynamicRegistration = false,
symbolKind = {
valueSet = (function()
local res = {}
for k in ipairs(protocol.SymbolKind) do
if type(k) == 'number' then table.insert(res, k) end
if type(k) == 'number' then
table.insert(res, k)
end
end
return res
end)();
};
hierarchicalDocumentSymbolSupport = true;
};
end)(),
},
hierarchicalDocumentSymbolSupport = true,
},
rename = {
dynamicRegistration = false;
prepareSupport = true;
};
dynamicRegistration = false,
prepareSupport = true,
},
publishDiagnostics = {
relatedInformation = true;
relatedInformation = true,
tagSupport = {
valueSet = (function()
local res = {}
for k in ipairs(protocol.DiagnosticTag) do
if type(k) == 'number' then table.insert(res, k) end
if type(k) == 'number' then
table.insert(res, k)
end
end
return res
end)();
};
};
};
end)(),
},
},
},
workspace = {
symbol = {
dynamicRegistration = false;
dynamicRegistration = false,
symbolKind = {
valueSet = (function()
local res = {}
for k in ipairs(protocol.SymbolKind) do
if type(k) == 'number' then table.insert(res, k) end
if type(k) == 'number' then
table.insert(res, k)
end
end
return res
end)();
};
hierarchicalWorkspaceSymbolSupport = true;
};
workspaceFolders = true;
applyEdit = true;
end)(),
},
hierarchicalWorkspaceSymbolSupport = true,
},
workspaceFolders = true,
applyEdit = true,
workspaceEdit = {
resourceOperations = {'rename', 'create', 'delete',},
};
};
resourceOperations = { 'rename', 'create', 'delete' },
},
},
callHierarchy = {
dynamicRegistration = false;
};
experimental = nil;
dynamicRegistration = false,
},
experimental = nil,
window = {
workDoneProgress = true;
workDoneProgress = true,
showMessage = {
messageActionItem = {
additionalPropertiesSupport = false;
};
};
additionalPropertiesSupport = false,
},
},
showDocument = {
support = false;
};
};
support = false,
},
},
}
end
@@ -791,12 +799,12 @@ function protocol.resolve_capabilities(server_capabilities)
willSaveWaitUntil = false,
save = {
includeText = false,
}
},
}
elseif type(textDocumentSync) == 'number' then
-- Backwards compatibility
if not TextDocumentSyncKind[textDocumentSync] then
return nil, "Invalid server TextDocumentSyncKind for textDocumentSync"
return nil, 'Invalid server TextDocumentSyncKind for textDocumentSync'
end
server_capabilities.textDocumentSync = {
openClose = true,
@@ -805,10 +813,10 @@ function protocol.resolve_capabilities(server_capabilities)
willSaveWaitUntil = false,
save = {
includeText = false,
}
},
}
elseif type(textDocumentSync) ~= 'table' then
return nil, string.format("Invalid type for textDocumentSync: %q", type(textDocumentSync))
return nil, string.format('Invalid type for textDocumentSync: %q', type(textDocumentSync))
end
return server_capabilities
end
@@ -827,39 +835,41 @@ function protocol._resolve_capabilities_compat(server_capabilities)
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;
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"
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;
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);
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))
return nil, string.format('Invalid type for textDocumentSync: %q', type(textDocumentSync))
end
end
general_properties.completion = server_capabilities.completionProvider ~= nil
@@ -889,16 +899,18 @@ function protocol._resolve_capabilities_compat(server_capabilities)
general_properties.code_lens = true
general_properties.code_lens_resolve = server_capabilities.codeLensProvider.resolveProvider or false
else
error("The server sent invalid codeLensProvider")
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
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")
error('The server sent invalid codeActionProvider')
end
if server_capabilities.declarationProvider == nil then
@@ -908,7 +920,7 @@ function protocol._resolve_capabilities_compat(server_capabilities)
elseif type(server_capabilities.declarationProvider) == 'table' then
general_properties.declaration = server_capabilities.declarationProvider
else
error("The server sent invalid declarationProvider")
error('The server sent invalid declarationProvider')
end
if server_capabilities.typeDefinitionProvider == nil then
@@ -918,7 +930,7 @@ function protocol._resolve_capabilities_compat(server_capabilities)
elseif type(server_capabilities.typeDefinitionProvider) == 'table' then
general_properties.type_definition = server_capabilities.typeDefinitionProvider
else
error("The server sent invalid typeDefinitionProvider")
error('The server sent invalid typeDefinitionProvider')
end
if server_capabilities.implementationProvider == nil then
@@ -928,7 +940,7 @@ function protocol._resolve_capabilities_compat(server_capabilities)
elseif type(server_capabilities.implementationProvider) == 'table' then
general_properties.implementation = server_capabilities.implementationProvider
else
error("The server sent invalid implementationProvider")
error('The server sent invalid implementationProvider')
end
local workspace = server_capabilities.workspace
@@ -936,45 +948,45 @@ function protocol._resolve_capabilities_compat(server_capabilities)
if workspace == nil or workspace.workspaceFolders == nil then
-- Defaults if omitted.
workspace_properties = {
workspace_folder_properties = {
supported = false;
changeNotifications=false;
}
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);
}
supported = if_nil(workspace.workspaceFolders.supported, false),
changeNotifications = if_nil(workspace.workspaceFolders.changeNotifications, false),
},
}
else
error("The server sent invalid workspace")
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 = {};
signature_help = false,
signature_help_trigger_characters = {},
}
elseif type(server_capabilities.signatureHelpProvider) == 'table' then
signature_help_properties = {
signature_help = true;
signature_help = true,
-- The characters that trigger signature help automatically.
signature_help_trigger_characters = server_capabilities.signatureHelpProvider.triggerCharacters or {};
signature_help_trigger_characters = server_capabilities.signatureHelpProvider.triggerCharacters or {},
}
else
error("The server sent invalid signatureHelpProvider")
error('The server sent invalid signatureHelpProvider')
end
local capabilities = vim.tbl_extend("error"
, text_document_sync_properties
, signature_help_properties
, workspace_properties
, general_properties
)
local capabilities = vim.tbl_extend(
'error',
text_document_sync_properties,
signature_help_properties,
workspace_properties,
general_properties
)
return capabilities
end

View File

@@ -32,9 +32,9 @@ local function env_merge(env)
-- Merge.
env = vim.tbl_extend('force', vim.fn.environ(), env)
local final_env = {}
for k,v in pairs(env) do
for k, v in pairs(env) do
assert(type(k) == 'string', 'env must be a dict')
table.insert(final_env, k..'='..tostring(v))
table.insert(final_env, k .. '=' .. tostring(v))
end
return final_env
end
@@ -45,10 +45,12 @@ end
---@param encoded_message (string)
---@returns (table) table containing encoded message and `Content-Length` attribute
local function format_message_with_content_length(encoded_message)
return table.concat {
'Content-Length: '; tostring(#encoded_message); '\r\n\r\n';
encoded_message;
}
return table.concat({
'Content-Length: ',
tostring(#encoded_message),
'\r\n\r\n',
encoded_message,
})
end
---@private
@@ -65,23 +67,25 @@ local function parse_headers(header)
if line == '' then
break
end
local key, value = line:match("^%s*(%S+)%s*:%s*(.+)%s*$")
local key, value = line:match('^%s*(%S+)%s*:%s*(.+)%s*$')
if key then
key = key:lower():gsub('%-', '_')
headers[key] = value
else
local _ = log.error() and log.error("invalid header line %q", line)
error(string.format("invalid header line %q", line))
local _ = log.error() and log.error('invalid header line %q', line)
error(string.format('invalid header line %q', line))
end
end
headers.content_length = tonumber(headers.content_length)
or error(string.format("Content-Length not found in headers. %q", header))
or error(string.format('Content-Length not found in headers. %q', header))
return headers
end
-- This is the start of any possible header patterns. The gsub converts it to a
-- case insensitive pattern.
local header_start_pattern = ("content"):gsub("%w", function(c) return "["..c..c:upper().."]" end)
local header_start_pattern = ('content'):gsub('%w', function(c)
return '[' .. c .. c:upper() .. ']'
end)
---@private
--- The actual workhorse.
@@ -100,17 +104,16 @@ local function request_parser_loop()
-- be searching for.
-- TODO(ashkan) I'd like to remove this, but it seems permanent :(
local buffer_start = buffer:find(header_start_pattern)
local headers = parse_headers(buffer:sub(buffer_start, start-1))
local headers = parse_headers(buffer:sub(buffer_start, start - 1))
local content_length = headers.content_length
-- Use table instead of just string to buffer the message. It prevents
-- a ton of strings allocating.
-- ref. http://www.lua.org/pil/11.6.html
local body_chunks = {buffer:sub(finish+1)}
local body_chunks = { buffer:sub(finish + 1) }
local body_length = #body_chunks[1]
-- Keep waiting for data until we have enough.
while body_length < content_length do
local chunk = coroutine.yield()
or error("Expected more data for the body. The server may have died.") -- TODO hmm.
local chunk = coroutine.yield() or error('Expected more data for the body. The server may have died.') -- TODO hmm.
table.insert(body_chunks, chunk)
body_length = body_length + #chunk
end
@@ -123,25 +126,24 @@ local function request_parser_loop()
end
local body = table.concat(body_chunks)
-- Yield our data.
buffer = rest..(coroutine.yield(headers, body)
or error("Expected more data for the body. The server may have died.")) -- TODO hmm.
buffer = rest
.. (coroutine.yield(headers, body) or error('Expected more data for the body. The server may have died.')) -- TODO hmm.
else
-- Get more data since we don't have enough.
buffer = buffer..(coroutine.yield()
or error("Expected more data for the header. The server may have died.")) -- TODO hmm.
buffer = buffer .. (coroutine.yield() or error('Expected more data for the header. The server may have died.')) -- TODO hmm.
end
end
end
--- Mapping of error codes used by the client
local client_errors = {
INVALID_SERVER_MESSAGE = 1;
INVALID_SERVER_JSON = 2;
NO_RESULT_CALLBACK_FOUND = 3;
READ_ERROR = 4;
NOTIFICATION_HANDLER_ERROR = 5;
SERVER_REQUEST_HANDLER_ERROR = 6;
SERVER_RESULT_CALLBACK_ERROR = 7;
INVALID_SERVER_MESSAGE = 1,
INVALID_SERVER_JSON = 2,
NO_RESULT_CALLBACK_FOUND = 3,
READ_ERROR = 4,
NOTIFICATION_HANDLER_ERROR = 5,
SERVER_REQUEST_HANDLER_ERROR = 6,
SERVER_RESULT_CALLBACK_ERROR = 7,
}
client_errors = vim.tbl_add_reverse_lookup(client_errors)
@@ -151,26 +153,26 @@ client_errors = vim.tbl_add_reverse_lookup(client_errors)
---@param err (table) The error object
---@returns (string) The formatted error message
local function format_rpc_error(err)
validate {
err = { err, 't' };
}
validate({
err = { err, 't' },
})
-- There is ErrorCodes in the LSP specification,
-- but in ResponseError.code it is not used and the actual type is number.
local code
if protocol.ErrorCodes[err.code] then
code = string.format("code_name = %s,", protocol.ErrorCodes[err.code])
code = string.format('code_name = %s,', protocol.ErrorCodes[err.code])
else
code = string.format("code_name = unknown, code = %s,", err.code)
code = string.format('code_name = unknown, code = %s,', err.code)
end
local message_parts = {"RPC[Error]", code}
local message_parts = { 'RPC[Error]', code }
if err.message then
table.insert(message_parts, "message =")
table.insert(message_parts, string.format("%q", err.message))
table.insert(message_parts, 'message =')
table.insert(message_parts, string.format('%q', err.message))
end
if err.data then
table.insert(message_parts, "data =")
table.insert(message_parts, 'data =')
table.insert(message_parts, vim.inspect(err.data))
end
return table.concat(message_parts, ' ')
@@ -185,11 +187,11 @@ local function rpc_response_error(code, message, data)
-- TODO should this error or just pick a sane error (like InternalError)?
local code_name = assert(protocol.ErrorCodes[code], 'Invalid RPC error code')
return setmetatable({
code = code;
message = message or code_name;
data = data;
code = code,
message = message or code_name,
data = data,
}, {
__tostring = format_rpc_error;
__tostring = format_rpc_error,
})
end
@@ -220,7 +222,7 @@ end
---@param signal (number): Number describing the signal used to terminate (if
---any)
function default_dispatchers.on_exit(code, signal)
local _ = log.info() and log.info("client_exit", { code = code, signal = signal })
local _ = log.info() and log.info('client_exit', { code = code, signal = signal })
end
---@private
--- Default dispatcher for client errors.
@@ -258,15 +260,15 @@ end
--- - {handle} A handle for low-level interaction with the LSP server process
--- |vim.loop|.
local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
local _ = log.info() and log.info("Starting RPC client", {cmd = cmd, args = cmd_args, extra = extra_spawn_params})
validate {
cmd = { cmd, 's' };
cmd_args = { cmd_args, 't' };
dispatchers = { dispatchers, 't', true };
}
local _ = log.info() and log.info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params })
validate({
cmd = { cmd, 's' },
cmd_args = { cmd_args, 't' },
dispatchers = { dispatchers, 't', true },
})
if extra_spawn_params and extra_spawn_params.cwd then
assert(is_dir(extra_spawn_params.cwd), "cwd must be a directory")
assert(is_dir(extra_spawn_params.cwd), 'cwd must be a directory')
end
if dispatchers then
local user_dispatchers = dispatchers
@@ -275,11 +277,11 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
local user_dispatcher = user_dispatchers[dispatch_name]
if user_dispatcher then
if type(user_dispatcher) ~= 'function' then
error(string.format("dispatcher.%s must be a function", dispatch_name))
error(string.format('dispatcher.%s must be a function', dispatch_name))
end
-- server_request is wrapped elsewhere.
if not (dispatch_name == 'server_request'
or dispatch_name == 'on_exit') -- TODO this blocks the loop exiting for some reason.
if
not (dispatch_name == 'server_request' or dispatch_name == 'on_exit') -- TODO this blocks the loop exiting for some reason.
then
user_dispatcher = schedule_wrap(user_dispatcher)
end
@@ -317,9 +319,9 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
dispatchers.on_exit(code, signal)
end
local spawn_params = {
args = cmd_args;
stdio = {stdin, stdout, stderr};
detached = true;
args = cmd_args,
stdio = { stdin, stdout, stderr },
detached = true,
}
if extra_spawn_params then
spawn_params.cwd = extra_spawn_params.cwd
@@ -330,11 +332,11 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
end
handle, pid = uv.spawn(cmd, spawn_params, onexit)
if handle == nil then
local msg = string.format("Spawning language server with cmd: `%s` failed", cmd)
if string.match(pid, "ENOENT") then
msg = msg .. ". The language server is either not installed, missing from PATH, or not executable."
local msg = string.format('Spawning language server with cmd: `%s` failed', cmd)
if string.match(pid, 'ENOENT') then
msg = msg .. '. The language server is either not installed, missing from PATH, or not executable.'
else
msg = msg .. string.format(" with error message: %s", pid)
msg = msg .. string.format(' with error message: %s', pid)
end
vim.notify(msg, vim.log.levels.WARN)
return
@@ -348,8 +350,10 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
---@param payload table
---@returns true if the payload could be scheduled, false if the main event-loop is in the process of closing.
local function encode_and_send(payload)
local _ = log.debug() and log.debug("rpc.send", payload)
if handle == nil or handle:is_closing() then return false end
local _ = log.debug() and log.debug('rpc.send', payload)
if handle == nil or handle:is_closing() then
return false
end
local encoded = vim.json.encode(payload)
stdin:write(format_message_with_content_length(encoded))
return true
@@ -363,22 +367,22 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
---@param params (table): Parameters for the invoked LSP method
---@returns (bool) `true` if notification could be sent, `false` if not
local function notify(method, params)
return encode_and_send {
jsonrpc = "2.0";
method = method;
params = params;
}
return encode_and_send({
jsonrpc = '2.0',
method = method,
params = params,
})
end
---@private
--- sends an error object to the remote LSP process.
local function send_response(request_id, err, result)
return encode_and_send {
id = request_id;
jsonrpc = "2.0";
error = err;
result = result;
}
return encode_and_send({
id = request_id,
jsonrpc = '2.0',
error = err,
result = result,
})
end
-- FIXME: DOC: Should be placed on the RPC client object returned by
@@ -392,18 +396,18 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
---@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 {
callback = { callback, 'f' };
notify_reply_callback = { notify_reply_callback, 'f', true };
}
validate({
callback = { callback, 'f' },
notify_reply_callback = { notify_reply_callback, 'f', true },
})
message_index = message_index + 1
local message_id = message_index
local result = encode_and_send {
id = message_id;
jsonrpc = "2.0";
method = method;
params = params;
}
local result = encode_and_send({
id = message_id,
jsonrpc = '2.0',
method = method,
params = params,
})
if result then
if message_callbacks then
message_callbacks[message_id] = schedule_wrap(callback)
@@ -421,7 +425,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
stderr:read_start(function(_err, chunk)
if chunk then
local _ = log.error() and log.error("rpc", cmd, "stderr", chunk)
local _ = log.error() and log.error('rpc', cmd, 'stderr', chunk)
end
end)
@@ -455,7 +459,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
on_error(client_errors.INVALID_SERVER_JSON, decoded)
return
end
local _ = log.debug() and log.debug("rpc.receive", decoded)
local _ = log.debug() and log.debug('rpc.receive', decoded)
if type(decoded.method) == 'string' and decoded.id then
local err
@@ -463,17 +467,30 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
-- we can still use the result.
schedule(function()
local status, result
status, result, err = try_call(client_errors.SERVER_REQUEST_HANDLER_ERROR,
dispatchers.server_request, decoded.method, decoded.params)
local _ = log.debug() and log.debug("server_request: callback result", { status = status, result = result, err = err })
status, result, err = try_call(
client_errors.SERVER_REQUEST_HANDLER_ERROR,
dispatchers.server_request,
decoded.method,
decoded.params
)
local _ = log.debug()
and log.debug('server_request: callback result', { status = status, result = result, err = err })
if status then
if not (result or err) then
-- TODO this can be a problem if `null` is sent for result. needs vim.NIL
error(string.format("method %q: either a result or an error must be sent to the server in response", decoded.method))
error(
string.format(
'method %q: either a result or an error must be sent to the server in response',
decoded.method
)
)
end
if err then
assert(type(err) == 'table', "err must be a table. Use rpc_response_error to help format errors.")
local code_name = assert(protocol.ErrorCodes[err.code], "Errors must use protocol.ErrorCodes. Use rpc_response_error to help format errors.")
assert(type(err) == 'table', 'err must be a table. Use rpc_response_error to help format errors.')
local code_name = assert(
protocol.ErrorCodes[err.code],
'Errors must use protocol.ErrorCodes. Use rpc_response_error to help format errors.'
)
err.message = err.message or code_name
end
else
@@ -483,18 +500,17 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
end
send_response(decoded.id, err, result)
end)
-- This works because we are expecting vim.NIL here
-- This works because we are expecting vim.NIL here
elseif decoded.id and (decoded.result ~= vim.NIL or decoded.error ~= vim.NIL) then
-- We sent a number, so we expect a number.
local result_id = tonumber(decoded.id)
-- Notify the user that a response was received for the request
local notify_reply_callback = notify_reply_callbacks and notify_reply_callbacks[result_id]
if notify_reply_callback then
validate {
notify_reply_callback = { notify_reply_callback, 'f' };
}
validate({
notify_reply_callback = { notify_reply_callback, 'f' },
})
notify_reply_callback(result_id)
notify_reply_callbacks[result_id] = nil
end
@@ -503,7 +519,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
if decoded.error then
local mute_error = false
if decoded.error.code == protocol.ErrorCodes.RequestCancelled then
local _ = log.debug() and log.debug("Received cancellation ack", decoded)
local _ = log.debug() and log.debug('Received cancellation ack', decoded)
mute_error = true
end
@@ -523,24 +539,22 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
local callback = message_callbacks and message_callbacks[result_id]
if callback then
message_callbacks[result_id] = nil
validate {
callback = { callback, 'f' };
}
validate({
callback = { callback, 'f' },
})
if decoded.error then
decoded.error = setmetatable(decoded.error, {
__tostring = format_rpc_error;
__tostring = format_rpc_error,
})
end
try_call(client_errors.SERVER_RESULT_CALLBACK_ERROR,
callback, decoded.error, decoded.result)
try_call(client_errors.SERVER_RESULT_CALLBACK_ERROR, callback, decoded.error, decoded.result)
else
on_error(client_errors.NO_RESULT_CALLBACK_FOUND, decoded)
local _ = log.error() and log.error("No callback found for server response id "..result_id)
local _ = log.error() and log.error('No callback found for server response id ' .. result_id)
end
elseif type(decoded.method) == 'string' then
-- Notification
try_call(client_errors.NOTIFICATION_HANDLER_ERROR,
dispatchers.notification, decoded.method, decoded.params)
try_call(client_errors.NOTIFICATION_HANDLER_ERROR, dispatchers.notification, decoded.method, decoded.params)
else
-- Invalid server message
on_error(client_errors.INVALID_SERVER_MESSAGE, decoded)
@@ -556,7 +570,9 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
return
end
-- This should signal that we are done reading from the client.
if not chunk then return end
if not chunk then
return
end
-- Flush anything in the parser by looping until we don't get a result
-- anymore.
while true do
@@ -574,17 +590,17 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
end)
return {
pid = pid;
handle = handle;
request = request;
notify = notify
pid = pid,
handle = handle,
request = request,
notify = notify,
}
end
return {
start = start;
rpc_response_error = rpc_response_error;
format_rpc_error = format_rpc_error;
client_errors = client_errors;
start = start,
rpc_response_error = rpc_response_error,
format_rpc_error = format_rpc_error,
client_errors = client_errors,
}
-- vim:sw=2 ts=2 et

View File

@@ -79,7 +79,7 @@ local function compute_line_length(line, offset_encoding)
local length
local _
if offset_encoding == 'utf-16' then
_, length = str_utfindex(line)
_, length = str_utfindex(line)
elseif offset_encoding == 'utf-32' then
length, _ = str_utfindex(line)
else
@@ -100,7 +100,7 @@ local function align_end_position(line, byte, offset_encoding)
-- If on the first byte, or an empty string: the trivial case
if byte == 1 or #line == 0 then
char = byte
-- Called in the case of extending an empty line "" -> "a"
-- Called in the case of extending an empty line "" -> "a"
elseif byte == #line + 1 then
char = compute_line_length(line, offset_encoding) + 1
else
@@ -175,12 +175,12 @@ local function compute_start_range(prev_lines, curr_lines, firstline, lastline,
end
-- Convert byte to codepoint if applicable
if start_byte_idx == 1 or (#prev_line == 0 and start_byte_idx == 1)then
if start_byte_idx == 1 or (#prev_line == 0 and start_byte_idx == 1) then
byte_idx = start_byte_idx
char_idx = 1
elseif start_byte_idx == #prev_line + 1 then
byte_idx = start_byte_idx
char_idx = compute_line_length(prev_line, offset_encoding) + 1
char_idx = compute_line_length(prev_line, offset_encoding) + 1
else
byte_idx = start_byte_idx + str_utf_start(prev_line, start_byte_idx)
char_idx = byte_to_utf(prev_line, byte_idx, offset_encoding)
@@ -203,14 +203,30 @@ end
---@param new_lastline integer
---@param offset_encoding string
---@returns (int, int) end_line_idx and end_col_idx of range
local function compute_end_range(prev_lines, curr_lines, start_range, firstline, lastline, new_lastline, offset_encoding)
local function compute_end_range(
prev_lines,
curr_lines,
start_range,
firstline,
lastline,
new_lastline,
offset_encoding
)
-- If firstline == new_lastline, the first change occurred on a line that was deleted.
-- In this case, the last_byte...
if firstline == new_lastline then
return { line_idx = (lastline - new_lastline + firstline), byte_idx = 1, char_idx = 1 }, { line_idx = firstline, byte_idx = 1, char_idx = 1 }
return { line_idx = (lastline - new_lastline + firstline), byte_idx = 1, char_idx = 1 }, {
line_idx = firstline,
byte_idx = 1,
char_idx = 1,
}
end
if firstline == lastline then
return { line_idx = firstline, byte_idx = 1, char_idx = 1 }, { line_idx = new_lastline - lastline + firstline, byte_idx = 1, char_idx = 1 }
return { line_idx = firstline, byte_idx = 1, char_idx = 1 }, {
line_idx = new_lastline - lastline + firstline,
byte_idx = 1,
char_idx = 1,
}
end
-- Compare on last line, at minimum will be the start range
local start_line_idx = start_range.line_idx
@@ -239,9 +255,7 @@ local function compute_end_range(prev_lines, curr_lines, start_range, firstline,
end
for idx = 0, max_length do
byte_offset = idx
if
str_byte(prev_line, prev_line_length - byte_offset) ~= str_byte(curr_line, curr_line_length - byte_offset)
then
if str_byte(prev_line, prev_line_length - byte_offset) ~= str_byte(curr_line, curr_line_length - byte_offset) then
break
end
end
@@ -281,14 +295,13 @@ end
---@param end_range table new_end_range returned by last_difference
---@returns string text extracted from defined region
local function extract_text(lines, start_range, end_range, line_ending)
if not lines[start_range.line_idx] then
return ""
end
if not lines[start_range.line_idx] then
return ''
end
-- Trivial case: start and end range are the same line, directly grab changed text
if start_range.line_idx == end_range.line_idx then
-- string.sub is inclusive, end_range is not
return string.sub(lines[start_range.line_idx], start_range.byte_idx, end_range.byte_idx - 1)
else
-- Handle deletion case
-- Collect the changed portion of the first changed line
@@ -303,7 +316,7 @@ local function extract_text(lines, start_range, end_range, line_ending)
-- Collect the changed portion of the last changed line.
table.insert(result, string.sub(lines[end_range.line_idx], 1, end_range.byte_idx - 1))
else
table.insert(result, "")
table.insert(result, '')
end
-- Add line ending between all lines

File diff suppressed because it is too large Load Diff