Skip to content

Commit

Permalink
Merge pull request #872 'Add support for client-side RPC'
Browse files Browse the repository at this point in the history
  • Loading branch information
tarruda committed Jun 24, 2014
2 parents e126441 + 296da85 commit 9f1b972
Show file tree
Hide file tree
Showing 16 changed files with 1,126 additions and 670 deletions.
50 changes: 20 additions & 30 deletions scripts/msgpack-gen.lua
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ output:write([[
#include <msgpack.h>
#include "nvim/os/msgpack_rpc.h"
#include "nvim/os/msgpack_rpc_helpers.h"
#include "nvim/api/private/helpers.h"
]])

for i = 1, #headers do
Expand Down Expand Up @@ -120,20 +122,13 @@ output:write([[
};
const unsigned int msgpack_metadata_size = sizeof(msgpack_metadata);
void msgpack_rpc_dispatch(uint64_t channel_id, msgpack_object *req, msgpack_packer *res)
Object msgpack_rpc_dispatch(uint64_t channel_id,
uint64_t method_id,
msgpack_object *req,
Error *error)
{
Error error = { .set = false };
uint64_t method_id = (uint32_t)req->via.array.ptr[2].via.u64;
Object ret = NIL;
switch (method_id) {
case 0:
msgpack_pack_nil(res);
// The result is the [channel_id, metadata] array
msgpack_pack_array(res, 2);
msgpack_pack_uint64(res, channel_id);
msgpack_pack_raw(res, sizeof(msgpack_metadata));
msgpack_pack_raw_body(res, msgpack_metadata, sizeof(msgpack_metadata));
return;
]])

-- Visit each function metadata to build the case label with code generated
Expand All @@ -145,8 +140,7 @@ for i = 1, #api.functions do
output:write('\n case '..fn.id..': {')

output:write('\n if (req->via.array.ptr[3].via.array.size != '..#fn.parameters..') {')
output:write('\n snprintf(error.msg, sizeof(error.msg), "Wrong number of arguments: expecting '..#fn.parameters..' but got %u", req->via.array.ptr[3].via.array.size);')
output:write('\n msgpack_rpc_error(error.msg, res);')
output:write('\n snprintf(error->msg, sizeof(error->msg), "Wrong number of arguments: expecting '..#fn.parameters..' but got %u", req->via.array.ptr[3].via.array.size);')
output:write('\n goto '..cleanup_label..';')
output:write('\n }\n')
-- Declare/initialize variables that will hold converted arguments
Expand All @@ -164,7 +158,9 @@ for i = 1, #api.functions do
converted = 'arg_'..j
convert_arg = 'msgpack_rpc_to_'..string.lower(param[1])
output:write('\n if (!'..convert_arg..'('..arg..', &'..converted..')) {')
output:write('\n msgpack_rpc_error("Wrong type for argument '..j..', expecting '..param[1]..'", res);')
output:write('\n snprintf(error->msg, sizeof(error->msg), "Wrong type for argument '..j..', expecting '..param[1]..'");')

output:write('\n error->set = true;')
output:write('\n goto '..cleanup_label..';')
output:write('\n }\n')
args[#args + 1] = converted
Expand Down Expand Up @@ -195,28 +191,20 @@ for i = 1, #api.functions do
if fn.can_fail then
-- if the function can fail, also pass a pointer to the local error object
if #args > 0 then
output:write(', &error);\n')
output:write(', error);\n')
else
output:write('&error);\n')
output:write('error);\n')
end
-- and check for the error
output:write('\n if (error.set) {')
output:write('\n msgpack_rpc_error(error.msg, res);')
output:write('\n if (error->set) {')
output:write('\n goto '..cleanup_label..';')
output:write('\n }\n')
else
output:write(');\n')
end

-- nil error
output:write('\n msgpack_pack_nil(res);');

if fn.return_type == 'void' then
output:write('\n msgpack_pack_nil(res);');
else
output:write('\n msgpack_rpc_from_'..string.lower(fn.return_type)..'(rv, res);')
-- free the return value
output:write('\n msgpack_rpc_free_'..string.lower(fn.return_type)..'(rv);')
if fn.return_type ~= 'void' then
output:write('\n ret = '..string.upper(fn.return_type)..'_OBJ(rv);')
end
-- Now generate the cleanup label for freeing memory allocated for the
-- arguments
Expand All @@ -226,7 +214,7 @@ for i = 1, #api.functions do
local param = fn.parameters[j]
output:write('\n msgpack_rpc_free_'..string.lower(param[1])..'(arg_'..j..');')
end
output:write('\n return;');
output:write('\n break;');
output:write('\n };\n');

end
Expand All @@ -235,8 +223,10 @@ output:write([[
default:
msgpack_rpc_error("Invalid function id", res);
snprintf(error->msg, sizeof(error->msg), "Invalid function id");
error->set = true;
}
return ret;
}
]])
output:close()
18 changes: 17 additions & 1 deletion src/nvim/api/private/defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,16 @@ typedef enum {
kObjectTypeInteger,
kObjectTypeFloat,
kObjectTypeString,
kObjectTypeBuffer,
kObjectTypeWindow,
kObjectTypeTabpage,
kObjectTypeArray,
kObjectTypeDictionary
kObjectTypeDictionary,
kObjectTypePosition,
kObjectTypeStringArray,
kObjectTypeBufferArray,
kObjectTypeWindowArray,
kObjectTypeTabpageArray,
} ObjectType;

struct object {
Expand All @@ -76,8 +84,16 @@ struct object {
Integer integer;
Float floating;
String string;
Buffer buffer;
Window window;
Tabpage tabpage;
Array array;
Dictionary dictionary;
Position position;
StringArray stringarray;
BufferArray bufferarray;
WindowArray windowarray;
TabpageArray tabpagearray;
} data;
};

Expand Down
4 changes: 3 additions & 1 deletion src/nvim/api/private/helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ String cstr_to_string(const char *str)
};
}

static bool object_to_vim(Object obj, typval_T *tv, Error *err)
bool object_to_vim(Object obj, typval_T *tv, Error *err)
{
tv->v_type = VAR_UNKNOWN;
tv->v_lock = 0;
Expand Down Expand Up @@ -426,6 +426,8 @@ static bool object_to_vim(Object obj, typval_T *tv, Error *err)
}
tv->vval.v_dict->dv_refcount++;
break;
default:
abort();
}

return true;
Expand Down
51 changes: 43 additions & 8 deletions src/nvim/api/private/helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
err->set = true; \
} while (0)

#define BOOL_OBJ(b) ((Object) { \
#define OBJECT_OBJ(o) o

#define BOOLEAN_OBJ(b) ((Object) { \
.type = kObjectTypeBoolean, \
.data.boolean = b \
})
Expand All @@ -26,26 +28,59 @@

#define STRING_OBJ(s) ((Object) { \
.type = kObjectTypeString, \
.data.string = cstr_to_string(s) \
.data.string = s \
})

#define STRINGL_OBJ(d, s) ((Object) { \
.type = kObjectTypeString, \
.data.string = (String) { \
.size = s, \
.data = xmemdup(d, s) \
}})
#define BUFFER_OBJ(s) ((Object) { \
.type = kObjectTypeBuffer, \
.data.buffer = s \
})

#define WINDOW_OBJ(s) ((Object) { \
.type = kObjectTypeWindow, \
.data.window = s \
})

#define TABPAGE_OBJ(s) ((Object) { \
.type = kObjectTypeTabpage, \
.data.tabpage = s \
})

#define ARRAY_OBJ(a) ((Object) { \
.type = kObjectTypeArray, \
.data.array = a \
})

#define STRINGARRAY_OBJ(a) ((Object) { \
.type = kObjectTypeStringArray, \
.data.stringarray = a \
})

#define BUFFERARRAY_OBJ(a) ((Object) { \
.type = kObjectTypeBufferArray, \
.data.bufferarray = a \
})

#define WINDOWARRAY_OBJ(a) ((Object) { \
.type = kObjectTypeWindowArray, \
.data.windowarray = a \
})

#define TABPAGEARRAY_OBJ(a) ((Object) { \
.type = kObjectTypeTabpageArray, \
.data.tabpagearray = a \
})

#define DICTIONARY_OBJ(d) ((Object) { \
.type = kObjectTypeDictionary, \
.data.dictionary = d \
})

#define POSITION_OBJ(p) ((Object) { \
.type = kObjectTypePosition, \
.data.position = p \
})

#define NIL ((Object) {.type = kObjectTypeNil})

#define PUT(dict, k, v) \
Expand Down
10 changes: 6 additions & 4 deletions src/nvim/api/vim.c
Original file line number Diff line number Diff line change
Expand Up @@ -424,8 +424,8 @@ void vim_set_current_tabpage(Tabpage tabpage, Error *err)
/// @param event The event type string
void vim_subscribe(uint64_t channel_id, String event)
{
size_t length = (event.size < EVENT_MAXLEN ? event.size : EVENT_MAXLEN);
char e[EVENT_MAXLEN + 1];
size_t length = (event.size < METHOD_MAXLEN ? event.size : METHOD_MAXLEN);
char e[METHOD_MAXLEN + 1];
memcpy(e, event.data, length);
e[length] = NUL;
channel_subscribe(channel_id, e);
Expand All @@ -437,8 +437,10 @@ void vim_subscribe(uint64_t channel_id, String event)
/// @param event The event type string
void vim_unsubscribe(uint64_t channel_id, String event)
{
size_t length = (event.size < EVENT_MAXLEN ? event.size : EVENT_MAXLEN);
char e[EVENT_MAXLEN + 1];
size_t length = (event.size < METHOD_MAXLEN ?
event.size :
METHOD_MAXLEN);
char e[METHOD_MAXLEN + 1];
memcpy(e, event.data, length);
e[length] = NUL;
channel_unsubscribe(channel_id, e);
Expand Down
45 changes: 45 additions & 0 deletions src/nvim/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
#include "nvim/os/time.h"
#include "nvim/os/channel.h"
#include "nvim/api/private/helpers.h"
#include "nvim/os/msgpack_rpc_helpers.h"

#define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */

Expand Down Expand Up @@ -6453,6 +6454,7 @@ static struct fst {
{"searchpair", 3, 7, f_searchpair},
{"searchpairpos", 3, 7, f_searchpairpos},
{"searchpos", 1, 4, f_searchpos},
{"send_call", 3, 3, f_send_call},
{"send_event", 3, 3, f_send_event},
{"setbufvar", 3, 3, f_setbufvar},
{"setcmdpos", 1, 1, f_setcmdpos},
Expand Down Expand Up @@ -10474,6 +10476,7 @@ static void f_job_start(typval_T *argvars, typval_T *rettv)
on_job_stderr,
on_job_exit,
true,
0,
&rettv->vval.v_number);

if (rettv->vval.v_number <= 0) {
Expand Down Expand Up @@ -10535,6 +10538,7 @@ static void f_job_write(typval_T *argvars, typval_T *rettv)
if (!job) {
// Invalid job id
EMSG(_(e_invjob));
return;
}

WBuffer *buf = wstream_new_buffer(xstrdup((char *)argvars[1].vval.v_string),
Expand Down Expand Up @@ -12523,6 +12527,47 @@ do_searchpair (
return retval;
}

// "send_call()" function
static void f_send_call(typval_T *argvars, typval_T *rettv)
{
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = 0;

if (check_restricted() || check_secure()) {
return;
}

if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number <= 0) {
EMSG2(_(e_invarg2), "Channel id must be a positive integer");
return;
}

if (argvars[1].v_type != VAR_STRING) {
EMSG2(_(e_invarg2), "Method name must be a string");
return;
}

bool errored;
Object result;
if (!channel_send_call((uint64_t)argvars[0].vval.v_number,
(char *)argvars[1].vval.v_string,
vim_to_object(&argvars[2]),
&result,
&errored)) {
EMSG2(_(e_invarg2), "Channel doesn't exist");
return;
}

Error conversion_error = {.set = false};
if (errored || !object_to_vim(result, rettv, &conversion_error)) {
EMSG(errored ?
result.data.string.data :
_("Error converting the call result"));
}

msgpack_rpc_free_object(result);
}

// "send_event()" function
static void f_send_event(typval_T *argvars, typval_T *rettv)
{
Expand Down
Loading

0 comments on commit 9f1b972

Please sign in to comment.