mirror of
https://github.com/neovim/neovim.git
synced 2024-12-20 03:05:11 -07:00
Merge pull request #4568 from bfredl/multirequest
atomic multi request for async remote plugins
This commit is contained in:
commit
31df051ed9
@ -209,7 +209,7 @@ for i = 1, #functions do
|
||||
if fn.impl_name == nil then
|
||||
local args = {}
|
||||
|
||||
output:write('Object handle_'..fn.name..'(uint64_t channel_id, uint64_t request_id, Array args, Error *error)')
|
||||
output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Error *error)')
|
||||
output:write('\n{')
|
||||
output:write('\n Object ret = NIL;')
|
||||
-- Declare/initialize variables that will hold converted arguments
|
||||
|
@ -191,7 +191,7 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id,
|
||||
Object str = STRING_OBJ(cstr_to_string(bufstr));
|
||||
|
||||
// Vim represents NULs as NLs, but this may confuse clients.
|
||||
if (channel_id != INVALID_CHANNEL) {
|
||||
if (channel_id != INTERNAL_CALL) {
|
||||
strchrsub(str.data.string.data, '\n', '\0');
|
||||
}
|
||||
|
||||
@ -312,7 +312,7 @@ void nvim_buf_set_lines(uint64_t channel_id,
|
||||
// line and convert NULs to newlines to avoid truncation.
|
||||
lines[i] = xmallocz(l.size);
|
||||
for (size_t j = 0; j < l.size; j++) {
|
||||
if (l.data[j] == '\n' && channel_id != INVALID_CHANNEL) {
|
||||
if (l.data[j] == '\n' && channel_id != INTERNAL_CALL) {
|
||||
api_set_error(err, Exception, _("string cannot contain newlines"));
|
||||
new_len = i + 1;
|
||||
goto end;
|
||||
|
@ -33,8 +33,8 @@ typedef enum {
|
||||
/// Used as the message ID of notifications.
|
||||
#define NO_RESPONSE UINT64_MAX
|
||||
|
||||
/// Used as channel_id when the call is local
|
||||
#define INVALID_CHANNEL UINT64_MAX
|
||||
/// Used as channel_id when the call is local.
|
||||
#define INTERNAL_CALL UINT64_MAX
|
||||
|
||||
typedef struct {
|
||||
ErrorType type;
|
||||
|
@ -4,7 +4,6 @@
|
||||
#include "nvim/api/private/defs.h"
|
||||
|
||||
typedef Object (*ApiDispatchWrapper)(uint64_t channel_id,
|
||||
uint64_t request_id,
|
||||
Array args,
|
||||
Error *error);
|
||||
|
||||
|
@ -663,6 +663,95 @@ Array nvim_get_api_info(uint64_t channel_id)
|
||||
return rv;
|
||||
}
|
||||
|
||||
/// Call many api methods atomically
|
||||
///
|
||||
/// This has two main usages: Firstly, to perform several requests from an
|
||||
/// async context atomically, i.e. without processing requests from other rpc
|
||||
/// clients or redrawing or allowing user interaction in between. Note that api
|
||||
/// methods that could fire autocommands or do event processing still might do
|
||||
/// so. For instance invoking the :sleep command might call timer callbacks.
|
||||
/// Secondly, it can be used to reduce rpc overhead (roundtrips) when doing
|
||||
/// many requests in sequence.
|
||||
///
|
||||
/// @param calls an array of calls, where each call is described by an array
|
||||
/// with two elements: the request name, and an array of arguments.
|
||||
/// @param[out] err Details of a validation error of the nvim_multi_request call
|
||||
/// itself, i e malformatted `calls` parameter. Errors from called methods will
|
||||
/// be indicated in the return value, see below.
|
||||
///
|
||||
/// @return an array with two elements. The first is an array of return
|
||||
/// values. The second is NIL if all calls succeeded. If a call resulted in
|
||||
/// an error, it is a three-element array with the zero-based index of the call
|
||||
/// which resulted in an error, the error type and the error message. If an
|
||||
/// error ocurred, the values from all preceding calls will still be returned.
|
||||
Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err)
|
||||
FUNC_API_NOEVAL
|
||||
{
|
||||
Array rv = ARRAY_DICT_INIT;
|
||||
Array results = ARRAY_DICT_INIT;
|
||||
Error nested_error = ERROR_INIT;
|
||||
|
||||
size_t i; // also used for freeing the variables
|
||||
for (i = 0; i < calls.size; i++) {
|
||||
if (calls.items[i].type != kObjectTypeArray) {
|
||||
api_set_error(err,
|
||||
Validation,
|
||||
_("All items in calls array must be arrays"));
|
||||
goto validation_error;
|
||||
}
|
||||
Array call = calls.items[i].data.array;
|
||||
if (call.size != 2) {
|
||||
api_set_error(err,
|
||||
Validation,
|
||||
_("All items in calls array must be arrays of size 2"));
|
||||
goto validation_error;
|
||||
}
|
||||
|
||||
if (call.items[0].type != kObjectTypeString) {
|
||||
api_set_error(err,
|
||||
Validation,
|
||||
_("name must be String"));
|
||||
goto validation_error;
|
||||
}
|
||||
String name = call.items[0].data.string;
|
||||
|
||||
if (call.items[1].type != kObjectTypeArray) {
|
||||
api_set_error(err,
|
||||
Validation,
|
||||
_("args must be Array"));
|
||||
goto validation_error;
|
||||
}
|
||||
Array args = call.items[1].data.array;
|
||||
|
||||
MsgpackRpcRequestHandler handler = msgpack_rpc_get_handler_for(name.data,
|
||||
name.size);
|
||||
Object result = handler.fn(channel_id, args, &nested_error);
|
||||
if (nested_error.set) {
|
||||
// error handled after loop
|
||||
break;
|
||||
}
|
||||
|
||||
ADD(results, result);
|
||||
}
|
||||
|
||||
ADD(rv, ARRAY_OBJ(results));
|
||||
if (nested_error.set) {
|
||||
Array errval = ARRAY_DICT_INIT;
|
||||
ADD(errval, INTEGER_OBJ((Integer)i));
|
||||
ADD(errval, INTEGER_OBJ(nested_error.type));
|
||||
ADD(errval, STRING_OBJ(cstr_to_string(nested_error.msg)));
|
||||
ADD(rv, ARRAY_OBJ(errval));
|
||||
} else {
|
||||
ADD(rv, NIL);
|
||||
}
|
||||
return rv;
|
||||
|
||||
validation_error:
|
||||
api_free_array(results);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
/// Writes a message to vim output or error buffer. The string is split
|
||||
/// and flushed after each newline. Incomplete lines are kept for writing
|
||||
/// later.
|
||||
|
@ -7133,7 +7133,7 @@ static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
}
|
||||
|
||||
Error err = ERROR_INIT;
|
||||
Object result = fn(INVALID_CHANNEL, NO_RESPONSE, args, &err);
|
||||
Object result = fn(INTERNAL_CALL, args, &err);
|
||||
|
||||
if (err.set) {
|
||||
nvim_err_writeln(cstr_as_string(err.msg));
|
||||
|
@ -452,7 +452,7 @@ static void on_request_event(void **argv)
|
||||
Array args = e->args;
|
||||
uint64_t request_id = e->request_id;
|
||||
Error error = ERROR_INIT;
|
||||
Object result = handler.fn(channel->id, request_id, args, &error);
|
||||
Object result = handler.fn(channel->id, args, &error);
|
||||
if (request_id != NO_RESPONSE) {
|
||||
// send the response
|
||||
msgpack_packer response;
|
||||
|
@ -455,7 +455,6 @@ void msgpack_rpc_from_dictionary(Dictionary result, msgpack_packer *res)
|
||||
|
||||
/// Handler executed when an invalid method name is passed
|
||||
Object msgpack_rpc_handle_missing_method(uint64_t channel_id,
|
||||
uint64_t request_id,
|
||||
Array args,
|
||||
Error *error)
|
||||
{
|
||||
@ -466,7 +465,6 @@ Object msgpack_rpc_handle_missing_method(uint64_t channel_id,
|
||||
|
||||
/// Handler executed when malformated arguments are passed
|
||||
Object msgpack_rpc_handle_invalid_arguments(uint64_t channel_id,
|
||||
uint64_t request_id,
|
||||
Array args,
|
||||
Error *error)
|
||||
{
|
||||
|
@ -298,6 +298,94 @@ describe('vim_* functions', function()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('call_atomic', function()
|
||||
it('works', function()
|
||||
meths.buf_set_lines(0, 0, -1, true, {'first'})
|
||||
local req = {
|
||||
{'nvim_get_current_line', {}},
|
||||
{'nvim_set_current_line', {'second'}},
|
||||
}
|
||||
eq({{'first', NIL}, NIL}, meths.call_atomic(req))
|
||||
eq({'second'}, meths.buf_get_lines(0, 0, -1, true))
|
||||
end)
|
||||
|
||||
it('allows multiple return values', function()
|
||||
local req = {
|
||||
{'nvim_set_var', {'avar', true}},
|
||||
{'nvim_set_var', {'bvar', 'string'}},
|
||||
{'nvim_get_var', {'avar'}},
|
||||
{'nvim_get_var', {'bvar'}},
|
||||
}
|
||||
eq({{NIL, NIL, true, 'string'}, NIL}, meths.call_atomic(req))
|
||||
end)
|
||||
|
||||
it('is aborted by errors in call', function()
|
||||
local error_types = meths.get_api_info()[2].error_types
|
||||
local req = {
|
||||
{'nvim_set_var', {'one', 1}},
|
||||
{'nvim_buf_set_lines', {}},
|
||||
{'nvim_set_var', {'two', 2}},
|
||||
}
|
||||
eq({{NIL}, {1, error_types.Exception.id,
|
||||
'Wrong number of arguments: expecting 5 but got 0'}},
|
||||
meths.call_atomic(req))
|
||||
eq(1, meths.get_var('one'))
|
||||
eq(false, pcall(meths.get_var, 'two'))
|
||||
|
||||
-- still returns all previous successful calls
|
||||
req = {
|
||||
{'nvim_set_var', {'avar', 5}},
|
||||
{'nvim_set_var', {'bvar', 'string'}},
|
||||
{'nvim_get_var', {'avar'}},
|
||||
{'nvim_buf_get_lines', {0, 10, 20, true}},
|
||||
{'nvim_get_var', {'bvar'}},
|
||||
}
|
||||
eq({{NIL, NIL, 5}, {3, error_types.Validation.id, 'Index out of bounds'}},
|
||||
meths.call_atomic(req))
|
||||
|
||||
req = {
|
||||
{'i_am_not_a_method', {'xx'}},
|
||||
{'nvim_set_var', {'avar', 10}},
|
||||
}
|
||||
eq({{}, {0, error_types.Exception.id, 'Invalid method name'}},
|
||||
meths.call_atomic(req))
|
||||
eq(5, meths.get_var('avar'))
|
||||
end)
|
||||
|
||||
it('throws error on malformated arguments', function()
|
||||
local req = {
|
||||
{'nvim_set_var', {'avar', 1}},
|
||||
{'nvim_set_var'},
|
||||
{'nvim_set_var', {'avar', 2}},
|
||||
}
|
||||
local status, err = pcall(meths.call_atomic, req)
|
||||
eq(false, status)
|
||||
ok(err:match(' All items in calls array must be arrays of size 2') ~= nil)
|
||||
-- call before was done, but not after
|
||||
eq(1, meths.get_var('avar'))
|
||||
|
||||
req = {
|
||||
{'nvim_set_var', {'bvar', {2,3}}},
|
||||
12,
|
||||
}
|
||||
status, err = pcall(meths.call_atomic, req)
|
||||
eq(false, status)
|
||||
ok(err:match('All items in calls array must be arrays') ~= nil)
|
||||
eq({2,3}, meths.get_var('bvar'))
|
||||
|
||||
req = {
|
||||
{'nvim_set_current_line', 'little line'},
|
||||
{'nvim_set_var', {'avar', 3}},
|
||||
}
|
||||
status, err = pcall(meths.call_atomic, req)
|
||||
eq(false, status)
|
||||
ok(err:match('args must be Array') ~= nil)
|
||||
-- call before was done, but not after
|
||||
eq(1, meths.get_var('avar'))
|
||||
eq({''}, meths.buf_get_lines(0, 0, -1, true))
|
||||
end)
|
||||
end)
|
||||
|
||||
it('can throw exceptions', function()
|
||||
local status, err = pcall(nvim, 'get_option', 'invalid-option')
|
||||
eq(false, status)
|
||||
|
Loading…
Reference in New Issue
Block a user