mirror of
https://github.com/neovim/neovim.git
synced 2024-12-19 18:55:14 -07:00
feat(lsp): run handler in coroutine to support async response (#21026)
To illustrate a use-case this also changes `window/showMessageRequest` to use `vim.ui.select`
This commit is contained in:
parent
0958dccc6d
commit
af204dd0f1
@ -81,22 +81,38 @@ M['window/workDoneProgress/create'] = function(_, result, ctx)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest
|
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest
|
||||||
|
---@param result lsp.ShowMessageRequestParams
|
||||||
M['window/showMessageRequest'] = function(_, result)
|
M['window/showMessageRequest'] = function(_, result)
|
||||||
local actions = result.actions
|
local actions = result.actions or {}
|
||||||
print(result.message)
|
local co, is_main = coroutine.running()
|
||||||
local option_strings = { result.message, '\nRequest Actions:' }
|
if co and not is_main then
|
||||||
for i, action in ipairs(actions) do
|
local opts = {
|
||||||
local title = action.title:gsub('\r\n', '\\r\\n')
|
prompt = result.message .. ': ',
|
||||||
title = title:gsub('\n', '\\n')
|
format_item = function(action)
|
||||||
table.insert(option_strings, string.format('%d. %s', i, title))
|
return (action.title:gsub('\r\n', '\\r\\n')):gsub('\n', '\\n')
|
||||||
end
|
end,
|
||||||
|
}
|
||||||
-- window/showMessageRequest can return either MessageActionItem[] or null.
|
vim.ui.select(actions, opts, function(choice)
|
||||||
local choice = vim.fn.inputlist(option_strings)
|
-- schedule to ensure resume doesn't happen _before_ yield with
|
||||||
if choice < 1 or choice > #actions then
|
-- default synchronous vim.ui.select
|
||||||
return vim.NIL
|
vim.schedule(function()
|
||||||
|
coroutine.resume(co, choice or vim.NIL)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
return coroutine.yield()
|
||||||
else
|
else
|
||||||
return actions[choice]
|
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))
|
||||||
|
end
|
||||||
|
local choice = vim.fn.inputlist(option_strings)
|
||||||
|
if choice < 1 or choice > #actions then
|
||||||
|
return vim.NIL
|
||||||
|
else
|
||||||
|
return actions[choice]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -20,6 +20,14 @@ function transform_schema_to_table()
|
|||||||
end
|
end
|
||||||
--]=]
|
--]=]
|
||||||
|
|
||||||
|
---@class lsp.ShowMessageRequestParams
|
||||||
|
---@field type lsp.MessageType
|
||||||
|
---@field message string
|
||||||
|
---@field actions nil|lsp.MessageActionItem[]
|
||||||
|
|
||||||
|
---@class lsp.MessageActionItem
|
||||||
|
---@field title string
|
||||||
|
|
||||||
local constants = {
|
local constants = {
|
||||||
DiagnosticSeverity = {
|
DiagnosticSeverity = {
|
||||||
-- Reports an error.
|
-- Reports an error.
|
||||||
@ -39,6 +47,7 @@ local constants = {
|
|||||||
Deprecated = 2,
|
Deprecated = 2,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
---@enum lsp.MessageType
|
||||||
MessageType = {
|
MessageType = {
|
||||||
-- An error message.
|
-- An error message.
|
||||||
Error = 1,
|
Error = 1,
|
||||||
|
@ -391,44 +391,46 @@ function Client:handle_body(body)
|
|||||||
-- Schedule here so that the users functions don't trigger an error and
|
-- Schedule here so that the users functions don't trigger an error and
|
||||||
-- we can still use the result.
|
-- we can still use the result.
|
||||||
schedule(function()
|
schedule(function()
|
||||||
local status, result
|
coroutine.wrap(function()
|
||||||
status, result, err = self:try_call(
|
local status, result
|
||||||
client_errors.SERVER_REQUEST_HANDLER_ERROR,
|
status, result, err = self:try_call(
|
||||||
self.dispatchers.server_request,
|
client_errors.SERVER_REQUEST_HANDLER_ERROR,
|
||||||
decoded.method,
|
self.dispatchers.server_request,
|
||||||
decoded.params
|
decoded.method,
|
||||||
)
|
decoded.params
|
||||||
local _ = log.debug()
|
|
||||||
and log.debug(
|
|
||||||
'server_request: callback result',
|
|
||||||
{ status = status, result = result, err = err }
|
|
||||||
)
|
)
|
||||||
if status then
|
local _ = log.debug()
|
||||||
if result == nil and err == nil then
|
and log.debug(
|
||||||
error(
|
'server_request: callback result',
|
||||||
string.format(
|
{ status = status, result = result, err = err }
|
||||||
'method %q: either a result or an error must be sent to the server in response',
|
)
|
||||||
decoded.method
|
if status then
|
||||||
|
if result == nil and err == nil then
|
||||||
|
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.'
|
||||||
|
)
|
||||||
|
err.message = err.message or code_name
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- On an exception, result will contain the error message.
|
||||||
|
err = rpc_response_error(protocol.ErrorCodes.InternalError, result)
|
||||||
|
result = nil
|
||||||
end
|
end
|
||||||
if err then
|
self:send_response(decoded.id, err, result)
|
||||||
assert(
|
end)()
|
||||||
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
|
|
||||||
-- On an exception, result will contain the error message.
|
|
||||||
err = rpc_response_error(protocol.ErrorCodes.InternalError, result)
|
|
||||||
result = nil
|
|
||||||
end
|
|
||||||
self:send_response(decoded.id, err, result)
|
|
||||||
end)
|
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
|
elseif decoded.id and (decoded.result ~= vim.NIL or decoded.error ~= vim.NIL) then
|
||||||
|
Loading…
Reference in New Issue
Block a user