aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/lsp/handlers.lua
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-11-29 22:40:31 +0000
committerJosh Rahm <joshuarahm@gmail.com>2023-11-29 22:40:31 +0000
commit339e2d15cc26fe86988ea06468d912a46c8d6f29 (patch)
treea6167fc8fcfc6ae2dc102f57b2473858eac34063 /runtime/lua/vim/lsp/handlers.lua
parent067dc73729267c0262438a6fdd66e586f8496946 (diff)
parent4a8bf24ac690004aedf5540fa440e788459e5e34 (diff)
downloadrneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.tar.gz
rneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.tar.bz2
rneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.zip
Merge remote-tracking branch 'upstream/master' into fix_repeatcmdline
Diffstat (limited to 'runtime/lua/vim/lsp/handlers.lua')
-rw-r--r--runtime/lua/vim/lsp/handlers.lua321
1 files changed, 180 insertions, 141 deletions
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 5096100a60..6fde55cf04 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -1,5 +1,6 @@
local log = require('vim.lsp.log')
local protocol = require('vim.lsp.protocol')
+local ms = protocol.Methods
local util = require('vim.lsp.util')
local api = vim.api
@@ -7,82 +8,70 @@ local M = {}
-- FIXME: DOC: Expose in vimdocs
----@private
--- Writes to error buffer.
----@param ... (table of strings) Will be concatenated before being written
+---@param ... string 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')
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
-M['workspace/executeCommand'] = function(_, _, _, _)
+M[ms.workspace_executeCommand] = function(_, _, _, _)
-- Error handling is done implicitly by wrapping all handlers; see end of this file
end
----@private
-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)
+--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
+---@param result lsp.ProgressParams
+---@param ctx lsp.HandlerContext
+M[ms.dollar_progress] = function(_, result, ctx)
+ local client = vim.lsp.get_client_by_id(ctx.client_id)
if not client then
- err_message('LSP[', client_name, '] client has shut down during progress update')
+ err_message('LSP[id=', tostring(ctx.client_id), '] 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 kind = nil
+ local value = result.value
- if type(val) ~= 'table' then
- val = { content = val }
- end
- if val.kind then
- if val.kind == 'begin' then
- client.messages.progress[token] = {
- title = val.title,
- cancellable = val.cancellable,
- message = val.message,
- percentage = val.percentage,
- }
- elseif val.kind == 'report' then
- client.messages.progress[token].cancellable = val.cancellable
- 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`')
- else
- client.messages.progress[token].message = val.message
- client.messages.progress[token].done = true
+ if type(value) == 'table' then
+ kind = value.kind
+ -- Carry over title of `begin` messages to `report` and `end` messages
+ -- So that consumers always have it available, even if they consume a
+ -- subset of the full sequence
+ if kind == 'begin' then
+ client.progress.pending[result.token] = value.title
+ else
+ value.title = client.progress.pending[result.token]
+ if kind == 'end' then
+ client.progress.pending[result.token] = nil
end
end
- else
- client.messages.progress[token] = val
- client.messages.progress[token].done = true
end
- api.nvim_exec_autocmds('User', { pattern = 'LspProgressUpdate', modeline = false })
-end
+ client.progress:push(result)
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
-M['$/progress'] = progress_handler
+ api.nvim_exec_autocmds('LspProgress', {
+ pattern = kind,
+ modeline = false,
+ data = { client_id = ctx.client_id, result = result },
+ })
+end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_workDoneProgress_create
-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)
+---@param result lsp.WorkDoneProgressCreateParams
+---@param ctx lsp.HandlerContext
+M[ms.window_workDoneProgress_create] = function(_, result, ctx)
+ local client = vim.lsp.get_client_by_id(ctx.client_id)
if not client then
- err_message('LSP[', client_name, '] client has shut down while creating progress report')
+ err_message('LSP[id=', tostring(ctx.client_id), '] client has shut down during progress update')
return vim.NIL
end
- client.messages.progress[token] = {}
+ client.progress:push(result)
return vim.NIL
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest
---@param result lsp.ShowMessageRequestParams
-M['window/showMessageRequest'] = function(_, result)
+M[ms.window_showMessageRequest] = function(_, result)
local actions = result.actions or {}
local co, is_main = coroutine.running()
if co and not is_main then
@@ -117,20 +106,52 @@ M['window/showMessageRequest'] = function(_, result)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability
-M['client/registerCapability'] = function(_, _, ctx)
+M[ms.client_registerCapability] = function(_, result, 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'
+ ---@type lsp.Client
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 warning = string.format(warning_tpl, client_name)
- log.warn(warning)
+
+ client.dynamic_capabilities:register(result.registrations)
+ for bufnr, _ in pairs(client.attached_buffers) do
+ vim.lsp._set_defaults(client, bufnr)
+ end
+
+ ---@type string[]
+ local unsupported = {}
+ for _, reg in ipairs(result.registrations) do
+ if reg.method == ms.workspace_didChangeWatchedFiles then
+ require('vim.lsp._watchfiles').register(reg, ctx)
+ elseif not client.dynamic_capabilities:supports_registration(reg.method) then
+ unsupported[#unsupported + 1] = reg.method
+ end
+ end
+ if #unsupported > 0 then
+ local warning_tpl = 'The language server %s triggers a registerCapability '
+ .. 'handler for %s despite dynamicRegistration set to false. '
+ .. 'Report upstream, this warning is harmless'
+ local client_name = client and client.name or string.format('id=%d', client_id)
+ local warning = string.format(warning_tpl, client_name, table.concat(unsupported, ', '))
+ log.warn(warning)
+ end
+ return vim.NIL
+end
+
+--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_unregisterCapability
+M[ms.client_unregisterCapability] = function(_, result, ctx)
+ local client_id = ctx.client_id
+ local client = vim.lsp.get_client_by_id(client_id)
+ client.dynamic_capabilities:unregister(result.unregisterations)
+
+ for _, unreg in ipairs(result.unregisterations) do
+ if unreg.method == ms.workspace_didChangeWatchedFiles then
+ require('vim.lsp._watchfiles').unregister(unreg, ctx)
+ end
+ end
return vim.NIL
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
-M['workspace/applyEdit'] = function(_, workspace_edit, ctx)
+M[ms.workspace_applyEdit] = function(_, workspace_edit, ctx)
assert(
workspace_edit,
'workspace/applyEdit must be called with `ApplyWorkspaceEditParams`. Server is violating the specification'
@@ -150,7 +171,7 @@ M['workspace/applyEdit'] = function(_, workspace_edit, ctx)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_configuration
-M['workspace/configuration'] = function(_, result, ctx)
+M[ms.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
@@ -180,7 +201,7 @@ M['workspace/configuration'] = function(_, result, ctx)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_workspaceFolders
-M['workspace/workspaceFolders'] = function(_, _, ctx)
+M[ms.workspace_workspaceFolders] = function(_, _, ctx)
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
if not client then
@@ -190,16 +211,24 @@ M['workspace/workspaceFolders'] = function(_, _, ctx)
return client.workspace_folders or vim.NIL
end
-M['textDocument/publishDiagnostics'] = function(...)
+M[ms.textDocument_publishDiagnostics] = function(...)
return require('vim.lsp.diagnostic').on_publish_diagnostics(...)
end
-M['textDocument/codeLens'] = function(...)
+M[ms.textDocument_diagnostic] = function(...)
+ return require('vim.lsp.diagnostic').on_diagnostic(...)
+end
+
+M[ms.textDocument_codeLens] = function(...)
return require('vim.lsp.codelens').on_codelens(...)
end
+M[ms.textDocument_inlayHint] = function(...)
+ return require('vim.lsp.inlay_hint').on_inlayhint(...)
+end
+
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
-M['textDocument/references'] = function(_, result, ctx, config)
+M[ms.textDocument_references] = function(_, result, ctx, config)
if not result or vim.tbl_isempty(result) then
vim.notify('No references found')
else
@@ -221,7 +250,6 @@ M['textDocument/references'] = function(_, result, ctx, config)
end
end
----@private
--- Return a function that converts LSP responses to list items and opens the list
---
--- The returned function has an optional {config} parameter that accepts a table
@@ -256,7 +284,7 @@ local function response_to_list(map_result, entity, title_fn)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
-M['textDocument/documentSymbol'] = response_to_list(
+M[ms.textDocument_documentSymbol] = response_to_list(
util.symbols_to_items,
'document symbols',
function(ctx)
@@ -266,12 +294,12 @@ M['textDocument/documentSymbol'] = response_to_list(
)
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol
-M['workspace/symbol'] = response_to_list(util.symbols_to_items, 'symbols', function(ctx)
+M[ms.workspace_symbol] = response_to_list(util.symbols_to_items, 'symbols', function(ctx)
return string.format("Symbols matching '%s'", ctx.params.query)
end)
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename
-M['textDocument/rename'] = function(_, result, ctx, _)
+M[ms.textDocument_rename] = function(_, result, ctx, _)
if not result then
vim.notify("Language server couldn't provide rename result", vim.log.levels.INFO)
return
@@ -281,7 +309,7 @@ M['textDocument/rename'] = function(_, result, ctx, _)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting
-M['textDocument/rangeFormatting'] = function(_, result, ctx, _)
+M[ms.textDocument_rangeFormatting] = function(_, result, ctx, _)
if not result then
return
end
@@ -290,7 +318,7 @@ M['textDocument/rangeFormatting'] = function(_, result, ctx, _)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
-M['textDocument/formatting'] = function(_, result, ctx, _)
+M[ms.textDocument_formatting] = function(_, result, ctx, _)
if not result then
return
end
@@ -299,7 +327,7 @@ M['textDocument/formatting'] = function(_, result, ctx, _)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
-M['textDocument/completion'] = function(_, result, _, _)
+M[ms.textDocument_completion] = function(_, result, _, _)
if vim.tbl_isempty(result or {}) then
return
end
@@ -314,20 +342,22 @@ M['textDocument/completion'] = function(_, result, _, _)
end
--- |lsp-handler| for the method "textDocument/hover"
---- <pre>lua
---- vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(
---- vim.lsp.handlers.hover, {
---- -- Use a sharp border with `FloatBorder` highlights
---- border = "single",
---- -- add the title in hover float window
---- title = "hover"
---- }
---- )
---- </pre>
+---
+--- ```lua
+--- vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(
+--- vim.lsp.handlers.hover, {
+--- -- Use a sharp border with `FloatBorder` highlights
+--- border = "single",
+--- -- add the title in hover float window
+--- title = "hover"
+--- }
+--- )
+--- ```
+---
---@param config table Configuration table.
--- - border: (default=nil)
--- - Add borders to the floating window
---- - See |nvim_open_win()|
+--- - See |vim.lsp.util.open_floating_preview()| for more options.
function M.hover(_, result, ctx, config)
config = config or {}
config.focus_id = ctx.method
@@ -341,21 +371,28 @@ function M.hover(_, result, ctx, config)
end
return
end
- local markdown_lines = util.convert_input_to_markdown_lines(result.contents)
- markdown_lines = util.trim_empty_lines(markdown_lines)
- if vim.tbl_isempty(markdown_lines) then
- vim.notify('No information available')
+ local format = 'markdown'
+ local contents ---@type string[]
+ if type(result.contents) == 'table' and result.contents.kind == 'plaintext' then
+ format = 'plaintext'
+ contents = vim.split(result.contents.value or '', '\n', { trimempty = true })
+ else
+ contents = util.convert_input_to_markdown_lines(result.contents)
+ end
+ if vim.tbl_isempty(contents) then
+ if config.silent ~= true then
+ vim.notify('No information available')
+ end
return
end
- return util.open_floating_preview(markdown_lines, 'markdown', config)
+ return util.open_floating_preview(contents, format, config)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
-M['textDocument/hover'] = M.hover
+M[ms.textDocument_hover] = M.hover
----@private
--- Jumps to a location. Used as a handler for multiple LSP methods.
----@param _ (not used)
+---@param _ nil not used
---@param result (table) result of LSP method; a location or a list of locations.
---@param ctx (table) table containing the context of the request, including the method
---(`textDocument/definition` can return `Location` or `Location[]`
@@ -370,50 +407,54 @@ local function location_handler(_, result, ctx, config)
-- textDocument/definition can return Location or Location[]
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
+ if not vim.tbl_islist(result) then
+ result = { result }
+ end
- if vim.tbl_islist(result) then
- local title = 'LSP locations'
- local items = util.locations_to_items(result, client.offset_encoding)
+ local title = 'LSP locations'
+ local items = util.locations_to_items(result, client.offset_encoding)
- if config.on_list then
- assert(type(config.on_list) == 'function', 'on_list is not a function')
- config.on_list({ title = title, items = items })
- else
- if #result == 1 then
- util.jump_to_location(result[1], client.offset_encoding, config.reuse_win)
- return
- end
- vim.fn.setqflist({}, ' ', { title = title, items = items })
- api.nvim_command('botright copen')
- end
- else
- util.jump_to_location(result, client.offset_encoding, config.reuse_win)
+ if config.on_list then
+ assert(type(config.on_list) == 'function', 'on_list is not a function')
+ config.on_list({ title = title, items = items })
+ return
+ end
+ if #result == 1 then
+ util.jump_to_location(result[1], client.offset_encoding, config.reuse_win)
+ return
end
+ vim.fn.setqflist({}, ' ', { title = title, items = items })
+ api.nvim_command('botright copen')
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_declaration
-M['textDocument/declaration'] = location_handler
+M[ms.textDocument_declaration] = location_handler
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
-M['textDocument/definition'] = location_handler
+M[ms.textDocument_definition] = location_handler
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_typeDefinition
-M['textDocument/typeDefinition'] = location_handler
+M[ms.textDocument_typeDefinition] = location_handler
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_implementation
-M['textDocument/implementation'] = location_handler
+M[ms.textDocument_implementation] = location_handler
--- |lsp-handler| for the method "textDocument/signatureHelp".
+---
--- The active parameter is highlighted with |hl-LspSignatureActiveParameter|.
---- <pre>lua
---- vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(
---- vim.lsp.handlers.signature_help, {
---- -- Use a sharp border with `FloatBorder` highlights
---- border = "single"
---- }
---- )
---- </pre>
+---
+--- ```lua
+--- vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(
+--- vim.lsp.handlers.signature_help, {
+--- -- Use a sharp border with `FloatBorder` highlights
+--- border = "single"
+--- }
+--- )
+--- ```
+---
+---@param result table Response from the language server
+---@param ctx table Client context
---@param config table Configuration table.
--- - border: (default=nil)
--- - Add borders to the floating window
---- - See |nvim_open_win()|
+--- - See |vim.lsp.util.open_floating_preview()| for more options
function M.signature_help(_, result, ctx, config)
config = config or {}
config.focus_id = ctx.method
@@ -432,10 +473,9 @@ function M.signature_help(_, result, ctx, config)
local client = vim.lsp.get_client_by_id(ctx.client_id)
local triggers =
vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters')
- local ft = api.nvim_buf_get_option(ctx.bufnr, 'filetype')
+ local ft = vim.bo[ctx.bufnr].filetype
local lines, hl = util.convert_signature_help_to_markdown_lines(result, ft, triggers)
- lines = util.trim_empty_lines(lines)
- if vim.tbl_isempty(lines) then
+ if not lines or vim.tbl_isempty(lines) then
if config.silent ~= true then
print('No signature help available')
end
@@ -443,16 +483,18 @@ function M.signature_help(_, result, ctx, config)
end
local fbuf, fwin = util.open_floating_preview(lines, 'markdown', config)
if hl then
- api.nvim_buf_add_highlight(fbuf, -1, 'LspSignatureActiveParameter', 0, unpack(hl))
+ -- Highlight the second line if the signature is wrapped in a Markdown code block.
+ local line = vim.startswith(lines[1], '```') and 1 or 0
+ api.nvim_buf_add_highlight(fbuf, -1, 'LspSignatureActiveParameter', line, unpack(hl))
end
return fbuf, fwin
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
-M['textDocument/signatureHelp'] = M.signature_help
+M[ms.textDocument_signatureHelp] = M.signature_help
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight
-M['textDocument/documentHighlight'] = function(_, result, ctx, _)
+M[ms.textDocument_documentHighlight] = function(_, result, ctx, _)
if not result then
return
end
@@ -469,8 +511,9 @@ end
--- Displays call hierarchy in the quickfix window.
---
---@param direction `"from"` for incoming calls and `"to"` for outgoing calls
----@returns `CallHierarchyIncomingCall[]` if {direction} is `"from"`,
----@returns `CallHierarchyOutgoingCall[]` if {direction} is `"to"`,
+---@return function
+--- `CallHierarchyIncomingCall[]` if {direction} is `"from"`,
+--- `CallHierarchyOutgoingCall[]` if {direction} is `"to"`,
local make_call_hierarchy_handler = function(direction)
return function(_, result)
if not result then
@@ -494,13 +537,13 @@ local make_call_hierarchy_handler = function(direction)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_incomingCalls
-M['callHierarchy/incomingCalls'] = make_call_hierarchy_handler('from')
+M[ms.callHierarchy_incomingCalls] = make_call_hierarchy_handler('from')
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_outgoingCalls
-M['callHierarchy/outgoingCalls'] = make_call_hierarchy_handler('to')
+M[ms.callHierarchy_outgoingCalls] = make_call_hierarchy_handler('to')
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_logMessage
-M['window/logMessage'] = function(_, result, ctx, _)
+M[ms.window_logMessage] = function(_, result, ctx, _)
local message_type = result.type
local message = result.message
local client_id = ctx.client_id
@@ -522,7 +565,7 @@ M['window/logMessage'] = function(_, result, ctx, _)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessage
-M['window/showMessage'] = function(_, result, ctx, _)
+M[ms.window_showMessage] = function(_, result, ctx, _)
local message_type = result.type
local message = result.message
local client_id = ctx.client_id
@@ -541,27 +584,19 @@ M['window/showMessage'] = function(_, result, ctx, _)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showDocument
-M['window/showDocument'] = function(_, result, ctx, _)
+M[ms.window_showDocument] = function(_, result, ctx, _)
local uri = result.uri
if result.external then
-- TODO(lvimuser): ask the user for confirmation
- local cmd
- if vim.fn.has('win32') == 1 then
- cmd = { 'cmd.exe', '/c', 'start', '""', uri }
- elseif vim.fn.has('macunix') == 1 then
- cmd = { 'open', uri }
- else
- cmd = { 'xdg-open', uri }
- end
+ local ret, err = vim.ui.open(uri)
- local ret = vim.fn.system(cmd)
- if vim.v.shell_error ~= 0 then
+ if ret == nil or ret.code ~= 0 then
return {
success = false,
error = {
code = protocol.ErrorCodes.UnknownErrorCode,
- message = ret,
+ message = ret and ret.stderr or err,
},
}
end
@@ -573,7 +608,7 @@ M['window/showDocument'] = function(_, result, ctx, _)
local client = vim.lsp.get_client_by_id(client_id)
local client_name = client and client.name or string.format('id=%d', client_id)
if not client then
- err_message({ 'LSP[', client_name, '] client has shut down after sending ', ctx.method })
+ err_message('LSP[', client_name, '] client has shut down after sending ', ctx.method)
return vim.NIL
end
@@ -589,6 +624,11 @@ M['window/showDocument'] = function(_, result, ctx, _)
return { success = success or false }
end
+---@see https://microsoft.github.io/language-server-protocol/specification/#workspace_inlayHint_refresh
+M[ms.workspace_inlayHint_refresh] = function(err, result, ctx, config)
+ return require('vim.lsp.inlay_hint').on_refresh(err, result, ctx, config)
+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)
@@ -622,4 +662,3 @@ for k, fn in pairs(M) do
end
return M
--- vim:sw=2 ts=2 et