Merge PR #1130 'Update to the experimental msgpack v5 branch'

This commit is contained in:
Thiago de Arruda 2014-09-12 14:01:35 -03:00
commit 6a8932aa58
34 changed files with 752 additions and 747 deletions

View File

@ -23,7 +23,7 @@ fi
valgrind_check "$tmpdir" valgrind_check "$tmpdir"
export NVIM_SPAWN_ARGV="[\"valgrind\", \"-q\", \"--track-origins=yes\", \"--leak-check=yes\", \"--suppressions=$suppressions\", \"--log-file=$tmpdir/valgrind-%p.log\", \"../build/bin/nvim\", \"-u\", \"NONE\", \"--embedded-mode\"]" export NVIM_SPAWN_ARGV="[\"valgrind\", \"-q\", \"--track-origins=yes\", \"--leak-check=yes\", \"--suppressions=$suppressions\", \"--log-file=$tmpdir/valgrind-%p.log\", \"../build/bin/nvim\", \"-u\", \"NONE\", \"--embed\"]"
if ! nosetests --verbosity=2 --nologcapture; then if ! nosetests --verbosity=2 --nologcapture; then
valgrind_check "$tmpdir" valgrind_check "$tmpdir"
exit 1 exit 1

View File

@ -67,7 +67,7 @@ install_prebuilt_deps() {
install_vroom() { install_vroom() {
( (
sudo pip install neovim sudo pip install git+https://github.com/neovim/python-client.git
git clone git://github.com/google/vroom git clone git://github.com/google/vroom
cd vroom cd vroom
python setup.py build python setup.py build

View File

@ -24,13 +24,13 @@ find_path(MSGPACK_INCLUDE_DIR msgpack.h
HINTS ${PC_MSGPACK_INCLUDEDIR} ${PC_MSGPACK_INCLUDE_DIRS} HINTS ${PC_MSGPACK_INCLUDEDIR} ${PC_MSGPACK_INCLUDE_DIRS}
${LIMIT_SEARCH}) ${LIMIT_SEARCH})
# If we're asked to use static linkage, add libmsgpackc.a as a preferred library name. # If we're asked to use static linkage, add libmsgpack.a as a preferred library name.
if(MSGPACK_USE_STATIC) if(MSGPACK_USE_STATIC)
list(APPEND MSGPACK_NAMES list(APPEND MSGPACK_NAMES
"${CMAKE_STATIC_LIBRARY_PREFIX}msgpackc${CMAKE_STATIC_LIBRARY_SUFFIX}") "${CMAKE_STATIC_LIBRARY_PREFIX}msgpack${CMAKE_STATIC_LIBRARY_SUFFIX}")
endif() endif()
list(APPEND MSGPACK_NAMES msgpackc) list(APPEND MSGPACK_NAMES msgpack)
find_library(MSGPACK_LIBRARY NAMES ${MSGPACK_NAMES} find_library(MSGPACK_LIBRARY NAMES ${MSGPACK_NAMES}
HINTS ${PC_MSGPACK_LIBDIR} ${PC_MSGPACK_LIBRARY_DIRS} HINTS ${PC_MSGPACK_LIBDIR} ${PC_MSGPACK_LIBRARY_DIRS}

View File

@ -0,0 +1,44 @@
" Nvim plugin for loading python extensions via an external interpreter
if exists("did_python_setup") || &cp
finish
endif
let did_python_setup = 1
let s:get_version =
\ ' -c "import sys; sys.stdout.write(str(sys.version_info.major))"'
" To load the python host a python 2 executable must be available
if exists('python_interpreter')
\ && executable(g:python_interpreter)
\ && system(g:python_interpreter.s:get_version) == "2"
let s:python_interpreter = g:python_interpreter
elseif executable('python') &&
\ system('python'.s:get_version) == "2"
let s:python_interpreter = 'python'
elseif executable('python2') &&
\ system('python2'.s:get_version) == "2"
" In some distros, python3 is the default python
let s:python_interpreter = 'python2'
else
finish
endif
" Execute python, import neovim and print a string. If import_result matches
" the printed string, we can probably start the host
let s:import_result = substitute(system(
\ s:python_interpreter .' -c "import neovim; print \"ok\""'),
\ '^[\s\n]*\(ok\)[\s\n]*$', '\1', '')
if s:import_result != 'ok'
finish
endif
let s:pyhost_id = api_spawn(s:python_interpreter,
\ ['-c', 'import neovim; neovim.start_host()'])
" Evaluate an expression in the script host as an additional sanity check, and
" to block until all providers have been registered(or else some plugins loaded
" by the user's vimrc would not get has('python') == 1
if send_call(s:pyhost_id, 'python_eval', '"o" + "k"') != 'ok' || !has('python')
" Something went wrong
api_close(s:pyhost_id)
endif

View File

@ -59,9 +59,16 @@ local right_word = concat(
raw_word, raw_word,
neg_look_ahead(aw) neg_look_ahead(aw)
) )
local word = concat( local word = branch(
neg_look_behind(aw), concat(
right_word branch(lit('ArrayOf('), lit('DictionaryOf(')), -- typed container macro
one_or_more(any_character - lit(')')),
lit(')')
),
concat(
neg_look_behind(aw),
right_word
)
) )
local spaces = any_amount(branch( local spaces = any_amount(branch(
s, s,
@ -204,7 +211,7 @@ while init ~= nil do
declaration = declaration:gsub('\n', ' ') declaration = declaration:gsub('\n', ' ')
declaration = declaration:gsub('%s+', ' ') declaration = declaration:gsub('%s+', ' ')
declaration = declaration:gsub(' ?%( ?', '(') declaration = declaration:gsub(' ?%( ?', '(')
declaration = declaration:gsub(' ?%) ?', ')') -- declaration = declaration:gsub(' ?%) ?', ')')
declaration = declaration:gsub(' ?, ?', ', ') declaration = declaration:gsub(' ?, ?', ', ')
declaration = declaration:gsub(' ?(%*+) ?', ' %1') declaration = declaration:gsub(' ?(%*+) ?', ' %1')
declaration = declaration:gsub(' ?(FUNC_ATTR_)', ' %1') declaration = declaration:gsub(' ?(FUNC_ATTR_)', ' %1')

View File

@ -16,7 +16,12 @@ ws = S(' \t') + nl
fill = ws ^ 0 fill = ws ^ 0
c_comment = P('//') * (not_nl ^ 0) c_comment = P('//') * (not_nl ^ 0)
c_preproc = P('#') * (not_nl ^ 0) c_preproc = P('#') * (not_nl ^ 0)
c_id = letter * (alpha ^ 0) typed_container =
(P('ArrayOf(') + P('DictionaryOf(')) * ((any - P(')')) ^ 1) * P(')')
c_id = (
typed_container +
(letter * (alpha ^ 0))
)
c_void = P('void') c_void = P('void')
c_param_type = ( c_param_type = (
((P('Error') * fill * P('*') * fill) * Cc('error')) + ((P('Error') * fill * P('*') * fill) * Cc('error')) +
@ -35,12 +40,8 @@ grammar = Ct((c_proto + c_comment + c_preproc + ws) ^ 1)
-- we need at least 2 arguments since the last one is the output file -- we need at least 2 arguments since the last one is the output file
assert(#arg >= 1) assert(#arg >= 1)
-- api metadata functions = {}
api = {
functions = {},
-- Helpers for object-oriented languages
classes = {'Buffer', 'Window', 'Tabpage'}
}
-- names of all headers relative to the source root(for inclusion in the -- names of all headers relative to the source root(for inclusion in the
-- generated file) -- generated file)
headers = {} headers = {}
@ -59,9 +60,8 @@ for i = 1, #arg - 1 do
local input = io.open(full_path, 'rb') local input = io.open(full_path, 'rb')
local tmp = grammar:match(input:read('*all')) local tmp = grammar:match(input:read('*all'))
for i = 1, #tmp do for i = 1, #tmp do
api.functions[#api.functions + 1] = tmp[i] functions[#functions + 1] = tmp[i]
local fn_id = #api.functions local fn = tmp[i]
local fn = api.functions[fn_id]
if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then
-- this function should receive the channel id -- this function should receive the channel id
fn.receives_channel_id = true fn.receives_channel_id = true
@ -75,8 +75,6 @@ for i = 1, #arg - 1 do
-- for specifying errors -- for specifying errors
fn.parameters[#fn.parameters] = nil fn.parameters[#fn.parameters] = nil
end end
-- assign a unique integer id for each api function
fn.id = fn_id
end end
input:close() input:close()
end end
@ -93,9 +91,11 @@ output:write([[
#include "nvim/map.h" #include "nvim/map.h"
#include "nvim/log.h" #include "nvim/log.h"
#include "nvim/vim.h"
#include "nvim/os/msgpack_rpc.h" #include "nvim/os/msgpack_rpc.h"
#include "nvim/os/msgpack_rpc_helpers.h" #include "nvim/os/msgpack_rpc_helpers.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/private/defs.h"
]]) ]])
for i = 1, #headers do for i = 1, #headers do
@ -107,12 +107,12 @@ end
output:write([[ output:write([[
const uint8_t msgpack_metadata[] = { static const uint8_t msgpack_metadata[] = {
]]) ]])
-- serialize the API metadata using msgpack and embed into the resulting -- serialize the API metadata using msgpack and embed into the resulting
-- binary for easy querying by clients -- binary for easy querying by clients
packed = msgpack.pack(api) packed = msgpack.pack(functions)
for i = 1, #packed do for i = 1, #packed do
output:write(string.byte(packed, i)..', ') output:write(string.byte(packed, i)..', ')
if i % 10 == 0 then if i % 10 == 0 then
@ -121,16 +121,40 @@ for i = 1, #packed do
end end
output:write([[ output:write([[
}; };
const unsigned int msgpack_metadata_size = sizeof(msgpack_metadata);
void msgpack_rpc_init_function_metadata(Dictionary *metadata)
{
msgpack_unpacked unpacked;
msgpack_unpacked_init(&unpacked);
assert(msgpack_unpack_next(&unpacked,
(const char *)msgpack_metadata,
sizeof(msgpack_metadata),
NULL) == MSGPACK_UNPACK_SUCCESS);
Object functions;
msgpack_rpc_to_object(&unpacked.data, &functions);
msgpack_unpacked_destroy(&unpacked);
PUT(*metadata, "functions", functions);
}
]]) ]])
-- start the handler functions. First handler (method_id=0) is reserved for local function real_type(type)
-- querying the metadata, usually it is the first function called by clients. local rv = type
-- Visit each function metadata to build the handler function with code if typed_container:match(rv) then
-- generated for validating arguments and calling to the real API. if rv:match('Array') then
for i = 1, #api.functions do rv = 'Array'
local fn = api.functions[i] else
rv = 'Dictionary'
end
end
return rv
end
-- start the handler functions. Visit each function metadata to build the
-- handler function with code generated for validating arguments and calling to
-- the real API.
for i = 1, #functions do
local fn = functions[i]
local args = {} local args = {}
output:write('static Object handle_'..fn.name..'(uint64_t channel_id, msgpack_object *req, Error *error)') output:write('static Object handle_'..fn.name..'(uint64_t channel_id, msgpack_object *req, Error *error)')
@ -140,7 +164,7 @@ for i = 1, #api.functions do
for j = 1, #fn.parameters do for j = 1, #fn.parameters do
local param = fn.parameters[j] local param = fn.parameters[j]
local converted = 'arg_'..j local converted = 'arg_'..j
output:write('\n '..param[1]..' '..converted..' msgpack_rpc_init_'..string.lower(param[1])..';') output:write('\n '..param[1]..' '..converted..' api_init_'..string.lower(real_type(param[1]))..';')
end end
output:write('\n') output:write('\n')
output:write('\n if (req->via.array.ptr[3].via.array.size != '..#fn.parameters..') {') output:write('\n if (req->via.array.ptr[3].via.array.size != '..#fn.parameters..') {')
@ -155,7 +179,7 @@ for i = 1, #api.functions do
param = fn.parameters[j] param = fn.parameters[j]
arg = '(req->via.array.ptr[3].via.array.ptr + '..(j - 1)..')' arg = '(req->via.array.ptr[3].via.array.ptr + '..(j - 1)..')'
converted = 'arg_'..j converted = 'arg_'..j
convert_arg = 'msgpack_rpc_to_'..string.lower(param[1]) convert_arg = 'msgpack_rpc_to_'..real_type(param[1]):lower()
output:write('\n if (!'..convert_arg..'('..arg..', &'..converted..')) {') output:write('\n if (!'..convert_arg..'('..arg..', &'..converted..')) {')
output:write('\n snprintf(error->msg, sizeof(error->msg), "Wrong type for argument '..j..', expecting '..param[1]..'");') 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 error->set = true;')
@ -202,7 +226,7 @@ for i = 1, #api.functions do
end end
if fn.return_type ~= 'void' then if fn.return_type ~= 'void' then
output:write('\n Object ret = '..string.upper(fn.return_type)..'_OBJ(rv);') output:write('\n Object ret = '..string.upper(real_type(fn.return_type))..'_OBJ(rv);')
end end
-- Now generate the cleanup label for freeing memory allocated for the -- Now generate the cleanup label for freeing memory allocated for the
-- arguments -- arguments
@ -210,7 +234,7 @@ for i = 1, #api.functions do
for j = 1, #fn.parameters do for j = 1, #fn.parameters do
local param = fn.parameters[j] local param = fn.parameters[j]
output:write('\n msgpack_rpc_free_'..string.lower(param[1])..'(arg_'..j..');') output:write('\n api_free_'..string.lower(real_type(param[1]))..'(arg_'..j..');')
end end
if fn.return_type ~= 'void' then if fn.return_type ~= 'void' then
output:write('\n return ret;\n}\n\n'); output:write('\n return ret;\n}\n\n');
@ -219,47 +243,26 @@ for i = 1, #api.functions do
end end
end end
output:write([[
static Object handle_missing_method(uint64_t channel_id,
msgpack_object *req,
Error *error)
{
snprintf(error->msg, sizeof(error->msg), "Invalid function id");
error->set = true;
return NIL;
}
]])
-- Generate the table of handler functions indexed by method id
output:write([[
static const rpc_method_handler_fn rpc_method_handlers[] = {
[0] = (rpc_method_handler_fn)NULL]])
for i = 1, #api.functions do
local fn = api.functions[i]
output:write(',\n ['..i..'] = handle_'..fn.name..'')
end
output:write('\n};\n\n')
-- Generate a function that initializes method names with handler functions -- Generate a function that initializes method names with handler functions
output:write([[ output:write([[
static Map(cstr_t, uint64_t) *rpc_method_ids = NULL; static Map(String, rpc_method_handler_fn) *methods = NULL;
void msgpack_rpc_init(void) void msgpack_rpc_init(void)
{ {
rpc_method_ids = map_new(cstr_t, uint64_t)(); methods = map_new(String, rpc_method_handler_fn)();
]]) ]])
-- Msgpack strings must be copied to a 0-terminated temporary buffer before -- Keep track of the maximum method name length in order to avoid walking
-- searching in the map, so we keep track of the maximum method name length in -- strings longer than that when searching for a method handler
-- order to create the smallest possible buffer for xstrlcpy
local max_fname_len = 0 local max_fname_len = 0
for i = 1, #api.functions do for i = 1, #functions do
local fn = api.functions[i] local fn = functions[i]
output:write(' map_put(cstr_t, uint64_t)(rpc_method_ids, "' output:write(' map_put(String, rpc_method_handler_fn)(methods, '..
..fn.name..'", '..i..');\n') '(String) {.data = "'..fn.name..'", '..
'.size = sizeof("'..fn.name..'") - 1}, handle_'..
fn.name..');\n')
if #fn.name > max_fname_len then if #fn.name > max_fname_len then
max_fname_len = #fn.name max_fname_len = #fn.name
end end
@ -268,30 +271,27 @@ end
output:write('\n}\n\n') output:write('\n}\n\n')
output:write([[ output:write([[
#define min(X, Y) (X < Y ? X : Y)
Object msgpack_rpc_dispatch(uint64_t channel_id, Object msgpack_rpc_dispatch(uint64_t channel_id,
msgpack_object *req, msgpack_object *req,
Error *error) Error *error)
{ {
msgpack_object method = req->via.array.ptr[2]; msgpack_object method = req->via.array.ptr[2];
uint64_t method_id = method.via.u64; rpc_method_handler_fn handler = NULL;
if (method.type == MSGPACK_OBJECT_RAW) { if (method.type == MSGPACK_OBJECT_BIN || method.type == MSGPACK_OBJECT_STR) {
char method_name[]]..(max_fname_len + 1)..[[]; ]])
xstrlcpy(method_name, method.via.raw.ptr, min(method.via.raw.size, ]] ..(max_fname_len)..[[) + 1); output:write(' handler = map_get(String, rpc_method_handler_fn)')
method_id = map_get(cstr_t, uint64_t)(rpc_method_ids, method_name); output:write('(methods, (String){.data=(char *)method.via.bin.ptr,')
if (!method_id) { output:write('.size=min(method.via.bin.size, '..max_fname_len..')});\n')
method_id = UINT64_MAX; output:write([[
} }
}
if (!handler) {
handler = msgpack_rpc_handle_missing_method;
}
return handler(channel_id, req, error);
}
]]) ]])
output:write('\n // method_id=0 is specially handled')
output:write('\n assert(method_id > 0);')
output:write('\n');
output:write('\n rpc_method_handler_fn handler = (method_id <= '..#api.functions..') ?')
output:write('\n rpc_method_handlers[method_id] : handle_missing_method;')
output:write('\n return handler(channel_id, req, error);')
output:write('\n}\n')
output:close() output:close()

View File

@ -51,10 +51,10 @@ Integer buffer_get_length(Buffer buffer, Error *err)
String buffer_get_line(Buffer buffer, Integer index, Error *err) String buffer_get_line(Buffer buffer, Integer index, Error *err)
{ {
String rv = {.size = 0}; String rv = {.size = 0};
StringArray slice = buffer_get_slice(buffer, index, index, true, true, err); Array slice = buffer_get_slice(buffer, index, index, true, true, err);
if (!err->set && slice.size) { if (!err->set && slice.size) {
rv = slice.items[0]; rv = slice.items[0].data.string;
} }
free(slice.items); free(slice.items);
@ -70,7 +70,8 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void buffer_set_line(Buffer buffer, Integer index, String line, Error *err) void buffer_set_line(Buffer buffer, Integer index, String line, Error *err)
{ {
StringArray array = {.items = &line, .size = 1}; Object l = STRING_OBJ(line);
Array array = {.items = &l, .size = 1};
buffer_set_slice(buffer, index, index, true, true, array, err); buffer_set_slice(buffer, index, index, true, true, array, err);
} }
@ -81,7 +82,7 @@ void buffer_set_line(Buffer buffer, Integer index, String line, Error *err)
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void buffer_del_line(Buffer buffer, Integer index, Error *err) void buffer_del_line(Buffer buffer, Integer index, Error *err)
{ {
StringArray array = ARRAY_DICT_INIT; Array array = ARRAY_DICT_INIT;
buffer_set_slice(buffer, index, index, true, true, array, err); buffer_set_slice(buffer, index, index, true, true, array, err);
} }
@ -94,14 +95,14 @@ void buffer_del_line(Buffer buffer, Integer index, Error *err)
/// @param include_end True if the slice includes the `end` parameter /// @param include_end True if the slice includes the `end` parameter
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
/// @return An array of lines /// @return An array of lines
StringArray buffer_get_slice(Buffer buffer, ArrayOf(String) buffer_get_slice(Buffer buffer,
Integer start, Integer start,
Integer end, Integer end,
Boolean include_start, Boolean include_start,
Boolean include_end, Boolean include_end,
Error *err) Error *err)
{ {
StringArray rv = ARRAY_DICT_INIT; Array rv = ARRAY_DICT_INIT;
buf_T *buf = find_buffer_by_handle(buffer, err); buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) { if (!buf) {
@ -118,7 +119,7 @@ StringArray buffer_get_slice(Buffer buffer,
} }
rv.size = (size_t)(end - start); rv.size = (size_t)(end - start);
rv.items = xcalloc(sizeof(String), rv.size); rv.items = xcalloc(sizeof(Object), rv.size);
for (size_t i = 0; i < rv.size; i++) { for (size_t i = 0; i < rv.size; i++) {
int64_t lnum = start + (int64_t)i; int64_t lnum = start + (int64_t)i;
@ -129,13 +130,13 @@ StringArray buffer_get_slice(Buffer buffer,
} }
const char *bufstr = (char *) ml_get_buf(buf, (linenr_T) lnum, false); const char *bufstr = (char *) ml_get_buf(buf, (linenr_T) lnum, false);
rv.items[i] = cstr_to_string(bufstr); rv.items[i] = STRING_OBJ(cstr_to_string(bufstr));
} }
end: end:
if (err->set) { if (err->set) {
for (size_t i = 0; i < rv.size; i++) { for (size_t i = 0; i < rv.size; i++) {
free(rv.items[i].data); free(rv.items[i].data.string.data);
} }
free(rv.items); free(rv.items);
@ -152,15 +153,15 @@ end:
/// @param end The last line index /// @param end The last line index
/// @param include_start True if the slice includes the `start` parameter /// @param include_start True if the slice includes the `start` parameter
/// @param include_end True if the slice includes the `end` parameter /// @param include_end True if the slice includes the `end` parameter
/// @param lines An array of lines to use as replacement(A 0-length array /// @param replacement An array of lines to use as replacement(A 0-length
/// will simply delete the line range) // array will simply delete the line range)
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void buffer_set_slice(Buffer buffer, void buffer_set_slice(Buffer buffer,
Integer start, Integer start,
Integer end, Integer end,
Boolean include_start, Boolean include_start,
Boolean include_end, Boolean include_end,
StringArray replacement, ArrayOf(String) replacement,
Error *err) Error *err)
{ {
buf_T *buf = find_buffer_by_handle(buffer, err); buf_T *buf = find_buffer_by_handle(buffer, err);
@ -184,10 +185,15 @@ void buffer_set_slice(Buffer buffer,
size_t new_len = replacement.size; size_t new_len = replacement.size;
size_t old_len = (size_t)(end - start); size_t old_len = (size_t)(end - start);
ssize_t extra = 0; // lines added to text, can be negative ssize_t extra = 0; // lines added to text, can be negative
char **lines = (new_len != 0) ? xmalloc(new_len * sizeof(char *)) : NULL; char **lines = (new_len != 0) ? xcalloc(new_len, sizeof(char *)) : NULL;
for (size_t i = 0; i < new_len; i++) { for (size_t i = 0; i < new_len; i++) {
String l = replacement.items[i]; if (replacement.items[i].type != kObjectTypeString) {
set_api_error("all items in the replacement array must be strings", err);
goto end;
}
String l = replacement.items[i].data.string;
lines[i] = xmemdupz(l.data, l.size); lines[i] = xmemdupz(l.data, l.size);
} }
@ -430,7 +436,10 @@ Boolean buffer_is_valid(Buffer buffer)
/// to the end of the buffer. /// to the end of the buffer.
/// @param lines An array of lines /// @param lines An array of lines
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void buffer_insert(Buffer buffer, Integer lnum, StringArray lines, Error *err) void buffer_insert(Buffer buffer,
Integer lnum,
ArrayOf(String) lines,
Error *err)
{ {
buffer_set_slice(buffer, lnum, lnum, false, true, lines, err); buffer_set_slice(buffer, lnum, lnum, false, true, lines, err);
} }
@ -441,9 +450,9 @@ void buffer_insert(Buffer buffer, Integer lnum, StringArray lines, Error *err)
/// @param name The mark's name /// @param name The mark's name
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
/// @return The (row, col) tuple /// @return The (row, col) tuple
Position buffer_get_mark(Buffer buffer, String name, Error *err) ArrayOf(Integer, 2) buffer_get_mark(Buffer buffer, String name, Error *err)
{ {
Position rv = POSITION_INIT; Array rv = ARRAY_DICT_INIT;
buf_T *buf = find_buffer_by_handle(buffer, err); buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) { if (!buf) {
@ -473,8 +482,9 @@ Position buffer_get_mark(Buffer buffer, String name, Error *err)
return rv; return rv;
} }
rv.row = posp->lnum; ADD(rv, INTEGER_OBJ(posp->lnum));
rv.col = posp->col; ADD(rv, INTEGER_OBJ(posp->col));
return rv; return rv;
} }

View File

@ -5,17 +5,15 @@
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
#define ARRAY_DICT_INIT {.size = 0, .items = NULL} #define ARRAY_DICT_INIT {.size = 0, .capacity = 0, .items = NULL}
#define STRING_INIT {.data = NULL, .size = 0} #define STRING_INIT {.data = NULL, .size = 0}
#define OBJECT_INIT { .type = kObjectTypeNil } #define OBJECT_INIT { .type = kObjectTypeNil }
#define POSITION_INIT { .row = 0, .col = 0 }
#define REMOTE_TYPE(type) typedef uint64_t type #define REMOTE_TYPE(type) typedef uint64_t type
#define TYPED_ARRAY_OF(type) \ #ifdef INCLUDE_GENERATED_DECLARATIONS
typedef struct { \ #define ArrayOf(...) Array
type *items; \ #define DictionaryOf(...) Dictionary
size_t size; \ #endif
} type##Array
// Basic types // Basic types
typedef struct { typedef struct {
@ -38,15 +36,6 @@ REMOTE_TYPE(Tabpage);
typedef struct object Object; typedef struct object Object;
TYPED_ARRAY_OF(Buffer);
TYPED_ARRAY_OF(Window);
TYPED_ARRAY_OF(Tabpage);
TYPED_ARRAY_OF(String);
typedef struct {
Integer row, col;
} Position;
typedef struct { typedef struct {
Object *items; Object *items;
size_t size, capacity; size_t size, capacity;
@ -60,40 +49,30 @@ typedef struct {
} Dictionary; } Dictionary;
typedef enum { typedef enum {
kObjectTypeBuffer,
kObjectTypeWindow,
kObjectTypeTabpage,
kObjectTypeNil, kObjectTypeNil,
kObjectTypeBoolean, kObjectTypeBoolean,
kObjectTypeInteger, kObjectTypeInteger,
kObjectTypeFloat, kObjectTypeFloat,
kObjectTypeString, kObjectTypeString,
kObjectTypeBuffer,
kObjectTypeWindow,
kObjectTypeTabpage,
kObjectTypeArray, kObjectTypeArray,
kObjectTypeDictionary, kObjectTypeDictionary,
kObjectTypePosition,
kObjectTypeStringArray,
kObjectTypeBufferArray,
kObjectTypeWindowArray,
kObjectTypeTabpageArray,
} ObjectType; } ObjectType;
struct object { struct object {
ObjectType type; ObjectType type;
union { union {
Buffer buffer;
Window window;
Tabpage tabpage;
Boolean boolean; Boolean boolean;
Integer integer; Integer integer;
Float floating; Float floating;
String string; String string;
Buffer buffer;
Window window;
Tabpage tabpage;
Array array; Array array;
Dictionary dictionary; Dictionary dictionary;
Position position;
StringArray stringarray;
BufferArray bufferarray;
WindowArray windowarray;
TabpageArray tabpagearray;
} data; } data;
}; };

View File

@ -6,6 +6,7 @@
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/api/private/handle.h" #include "nvim/api/private/handle.h"
#include "nvim/os/provider.h"
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/vim.h" #include "nvim/vim.h"
#include "nvim/buffer.h" #include "nvim/buffer.h"
@ -449,6 +450,130 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
return true; return true;
} }
void api_free_string(String value)
{
if (!value.data) {
return;
}
free(value.data);
}
void api_free_object(Object value)
{
switch (value.type) {
case kObjectTypeNil:
case kObjectTypeBoolean:
case kObjectTypeInteger:
case kObjectTypeFloat:
case kObjectTypeBuffer:
case kObjectTypeWindow:
case kObjectTypeTabpage:
break;
case kObjectTypeString:
api_free_string(value.data.string);
break;
case kObjectTypeArray:
api_free_array(value.data.array);
break;
case kObjectTypeDictionary:
api_free_dictionary(value.data.dictionary);
break;
default:
abort();
}
}
void api_free_array(Array value)
{
for (size_t i = 0; i < value.size; i++) {
api_free_object(value.items[i]);
}
free(value.items);
}
void api_free_dictionary(Dictionary value)
{
for (size_t i = 0; i < value.size; i++) {
api_free_string(value.items[i].key);
api_free_object(value.items[i].value);
}
free(value.items);
}
Dictionary api_metadata(void)
{
static Dictionary metadata = ARRAY_DICT_INIT;
if (!metadata.size) {
msgpack_rpc_init_function_metadata(&metadata);
init_type_metadata(&metadata);
provider_init_feature_metadata(&metadata);
}
return copy_object(DICTIONARY_OBJ(metadata)).data.dictionary;
}
static void init_type_metadata(Dictionary *metadata)
{
Dictionary types = ARRAY_DICT_INIT;
Dictionary buffer_metadata = ARRAY_DICT_INIT;
PUT(buffer_metadata, "id", INTEGER_OBJ(kObjectTypeBuffer));
Dictionary window_metadata = ARRAY_DICT_INIT;
PUT(window_metadata, "id", INTEGER_OBJ(kObjectTypeWindow));
Dictionary tabpage_metadata = ARRAY_DICT_INIT;
PUT(tabpage_metadata, "id", INTEGER_OBJ(kObjectTypeTabpage));
PUT(types, "Buffer", DICTIONARY_OBJ(buffer_metadata));
PUT(types, "Window", DICTIONARY_OBJ(window_metadata));
PUT(types, "Tabpage", DICTIONARY_OBJ(tabpage_metadata));
PUT(*metadata, "types", DICTIONARY_OBJ(types));
}
/// Creates a deep clone of an object
static Object copy_object(Object obj)
{
switch (obj.type) {
case kObjectTypeNil:
case kObjectTypeBoolean:
case kObjectTypeInteger:
case kObjectTypeFloat:
return obj;
case kObjectTypeString:
return STRING_OBJ(cstr_to_string(obj.data.string.data));
case kObjectTypeArray: {
Array rv = ARRAY_DICT_INIT;
for (size_t i = 0; i < obj.data.array.size; i++) {
ADD(rv, copy_object(obj.data.array.items[i]));
}
return ARRAY_OBJ(rv);
}
case kObjectTypeDictionary: {
Dictionary rv = ARRAY_DICT_INIT;
for (size_t i = 0; i < obj.data.dictionary.size; i++) {
KeyValuePair item = obj.data.dictionary.items[i];
PUT(rv, item.key.data, copy_object(item.value));
}
return DICTIONARY_OBJ(rv);
}
default:
abort();
}
}
/// Recursion helper for the `vim_to_object`. This uses a pointer table /// Recursion helper for the `vim_to_object`. This uses a pointer table
/// to avoid infinite recursion due to cyclic references /// to avoid infinite recursion due to cyclic references
/// ///

View File

@ -51,36 +51,11 @@
.data.array = a \ .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) { \ #define DICTIONARY_OBJ(d) ((Object) { \
.type = kObjectTypeDictionary, \ .type = kObjectTypeDictionary, \
.data.dictionary = d \ .data.dictionary = d \
}) })
#define POSITION_OBJ(p) ((Object) { \
.type = kObjectTypePosition, \
.data.position = p \
})
#define NIL ((Object) {.type = kObjectTypeNil}) #define NIL ((Object) {.type = kObjectTypeNil})
#define PUT(dict, k, v) \ #define PUT(dict, k, v) \
@ -91,6 +66,25 @@
#define ADD(array, item) \ #define ADD(array, item) \
kv_push(Object, array, item) kv_push(Object, array, item)
// Helpers used by the generated msgpack-rpc api wrappers
#define api_init_boolean
#define api_init_integer
#define api_init_float
#define api_init_string = STRING_INIT
#define api_init_buffer
#define api_init_window
#define api_init_tabpage
#define api_init_object = NIL
#define api_init_array = ARRAY_DICT_INIT
#define api_init_dictionary = ARRAY_DICT_INIT
#define api_free_boolean(value)
#define api_free_integer(value)
#define api_free_float(value)
#define api_free_buffer(value)
#define api_free_window(value)
#define api_free_tabpage(value)
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/private/helpers.h.generated.h" # include "api/private/helpers.h.generated.h"
#endif #endif

View File

@ -13,9 +13,9 @@
/// @param tabpage The tabpage /// @param tabpage The tabpage
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
/// @return The number of windows in `tabpage` /// @return The number of windows in `tabpage`
WindowArray tabpage_get_windows(Tabpage tabpage, Error *err) ArrayOf(Window) tabpage_get_windows(Tabpage tabpage, Error *err)
{ {
WindowArray rv = ARRAY_DICT_INIT; Array rv = ARRAY_DICT_INIT;
tabpage_T *tab = find_tab_by_handle(tabpage, err); tabpage_T *tab = find_tab_by_handle(tabpage, err);
if (!tab) { if (!tab) {
@ -32,14 +32,14 @@ WindowArray tabpage_get_windows(Tabpage tabpage, Error *err)
rv.size++; rv.size++;
} }
rv.items = xmalloc(sizeof(Window) * rv.size); rv.items = xmalloc(sizeof(Object) * rv.size);
size_t i = 0; size_t i = 0;
FOR_ALL_TAB_WINDOWS(tp, wp) { FOR_ALL_TAB_WINDOWS(tp, wp) {
if (tp != tab) { if (tp != tab) {
break; break;
} }
rv.items[i++] = wp->handle; rv.items[i++] = WINDOW_OBJ(wp->handle);
} }
return rv; return rv;

View File

@ -149,9 +149,9 @@ Integer vim_strwidth(String str, Error *err)
/// Returns a list of paths contained in 'runtimepath' /// Returns a list of paths contained in 'runtimepath'
/// ///
/// @return The list of paths /// @return The list of paths
StringArray vim_list_runtime_paths(void) ArrayOf(String) vim_list_runtime_paths(void)
{ {
StringArray rv = ARRAY_DICT_INIT; Array rv = ARRAY_DICT_INIT;
uint8_t *rtp = p_rtp; uint8_t *rtp = p_rtp;
if (*rtp == NUL) { if (*rtp == NUL) {
@ -168,19 +168,20 @@ StringArray vim_list_runtime_paths(void)
} }
// Allocate memory for the copies // Allocate memory for the copies
rv.items = xmalloc(sizeof(String) * rv.size); rv.items = xmalloc(sizeof(Object) * rv.size);
// reset the position // reset the position
rtp = p_rtp; rtp = p_rtp;
// Start copying // Start copying
for (size_t i = 0; i < rv.size && *rtp != NUL; i++) { for (size_t i = 0; i < rv.size && *rtp != NUL; i++) {
rv.items[i].data = xmalloc(MAXPATHL); rv.items[i].type = kObjectTypeString;
rv.items[i].data.string.data = xmalloc(MAXPATHL);
// Copy the path from 'runtimepath' to rv.items[i] // Copy the path from 'runtimepath' to rv.items[i]
int length = copy_option_part(&rtp, int length = copy_option_part(&rtp,
(char_u *)rv.items[i].data, (char_u *)rv.items[i].data.string.data,
MAXPATHL, MAXPATHL,
","); ",");
assert(length >= 0); assert(length >= 0);
rv.items[i].size = (size_t)length; rv.items[i].data.string.size = (size_t)length;
} }
return rv; return rv;
@ -307,12 +308,22 @@ void vim_err_write(String str)
write_msg(str, true); write_msg(str, true);
} }
/// Higher level error reporting function that ensures all str contents
/// are written by sending a trailing linefeed to `vim_wrr_write`
///
/// @param str The message
void vim_report_error(String str)
{
vim_err_write(str);
vim_err_write((String) {.data = "\n", .size = 1});
}
/// Gets the current list of buffer handles /// Gets the current list of buffer handles
/// ///
/// @return The number of buffers /// @return The number of buffers
BufferArray vim_get_buffers(void) ArrayOf(Buffer) vim_get_buffers(void)
{ {
BufferArray rv = ARRAY_DICT_INIT; Array rv = ARRAY_DICT_INIT;
buf_T *b = firstbuf; buf_T *b = firstbuf;
while (b) { while (b) {
@ -320,12 +331,12 @@ BufferArray vim_get_buffers(void)
b = b->b_next; b = b->b_next;
} }
rv.items = xmalloc(sizeof(Buffer) * rv.size); rv.items = xmalloc(sizeof(Object) * rv.size);
size_t i = 0; size_t i = 0;
b = firstbuf; b = firstbuf;
while (b) { while (b) {
rv.items[i++] = b->handle; rv.items[i++] = BUFFER_OBJ(b->handle);
b = b->b_next; b = b->b_next;
} }
@ -370,9 +381,9 @@ void vim_set_current_buffer(Buffer buffer, Error *err)
/// Gets the current list of window handles /// Gets the current list of window handles
/// ///
/// @return The number of windows /// @return The number of windows
WindowArray vim_get_windows(void) ArrayOf(Window) vim_get_windows(void)
{ {
WindowArray rv = ARRAY_DICT_INIT; Array rv = ARRAY_DICT_INIT;
tabpage_T *tp; tabpage_T *tp;
win_T *wp; win_T *wp;
@ -380,11 +391,11 @@ WindowArray vim_get_windows(void)
rv.size++; rv.size++;
} }
rv.items = xmalloc(sizeof(Window) * rv.size); rv.items = xmalloc(sizeof(Object) * rv.size);
size_t i = 0; size_t i = 0;
FOR_ALL_TAB_WINDOWS(tp, wp) { FOR_ALL_TAB_WINDOWS(tp, wp) {
rv.items[i++] = wp->handle; rv.items[i++] = WINDOW_OBJ(wp->handle);
} }
return rv; return rv;
@ -426,9 +437,9 @@ void vim_set_current_window(Window window, Error *err)
/// Gets the current list of tabpage handles /// Gets the current list of tabpage handles
/// ///
/// @return The number of tab pages /// @return The number of tab pages
TabpageArray vim_get_tabpages(void) ArrayOf(Tabpage) vim_get_tabpages(void)
{ {
TabpageArray rv = ARRAY_DICT_INIT; Array rv = ARRAY_DICT_INIT;
tabpage_T *tp = first_tabpage; tabpage_T *tp = first_tabpage;
while (tp) { while (tp) {
@ -436,12 +447,12 @@ TabpageArray vim_get_tabpages(void)
tp = tp->tp_next; tp = tp->tp_next;
} }
rv.items = xmalloc(sizeof(Tabpage) * rv.size); rv.items = xmalloc(sizeof(Object) * rv.size);
size_t i = 0; size_t i = 0;
tp = first_tabpage; tp = first_tabpage;
while (tp) { while (tp) {
rv.items[i++] = tp->handle; rv.items[i++] = TABPAGE_OBJ(tp->handle);
tp = tp->tp_next; tp = tp->tp_next;
} }
@ -501,22 +512,33 @@ void vim_unsubscribe(uint64_t channel_id, String event)
channel_unsubscribe(channel_id, e); channel_unsubscribe(channel_id, e);
} }
/// Registers the channel as the provider for `method`. This fails if /// Registers the channel as the provider for `feature`. This fails if
/// a provider for `method` is already registered. /// a provider for `feature` is already provided by another channel.
/// ///
/// @param channel_id The channel id /// @param channel_id The channel id
/// @param method The method name /// @param feature The feature name
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void vim_register_provider(uint64_t channel_id, String method, Error *err) void vim_register_provider(uint64_t channel_id, String feature, Error *err)
{ {
char buf[METHOD_MAXLEN]; char buf[METHOD_MAXLEN];
xstrlcpy(buf, method.data, sizeof(buf)); xstrlcpy(buf, feature.data, sizeof(buf));
if (!provider_register(buf, channel_id)) { if (!provider_register(buf, channel_id)) {
set_api_error("Provider already registered", err); set_api_error("Feature doesn't exist", err);
} }
} }
Array vim_get_api_info(uint64_t channel_id)
{
Array rv = ARRAY_DICT_INIT;
assert(channel_id <= INT64_MAX);
ADD(rv, INTEGER_OBJ((int64_t)channel_id));
ADD(rv, DICTIONARY_OBJ(api_metadata()));
return rv;
}
/// Writes a message to vim output or error buffer. The string is split /// Writes a message to vim output or error buffer. The string is split
/// and flushed after each newline. Incomplete lines are kept for writing /// and flushed after each newline. Incomplete lines are kept for writing
/// later. /// later.

View File

@ -33,14 +33,14 @@ Buffer window_get_buffer(Window window, Error *err)
/// @param window The window handle /// @param window The window handle
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
/// @return the (row, col) tuple /// @return the (row, col) tuple
Position window_get_cursor(Window window, Error *err) ArrayOf(Integer, 2) window_get_cursor(Window window, Error *err)
{ {
Position rv = POSITION_INIT; Array rv = ARRAY_DICT_INIT;
win_T *win = find_window_by_handle(window, err); win_T *win = find_window_by_handle(window, err);
if (win) { if (win) {
rv.row = win->w_cursor.lnum; ADD(rv, INTEGER_OBJ(win->w_cursor.lnum));
rv.col = win->w_cursor.col; ADD(rv, INTEGER_OBJ(win->w_cursor.col));
} }
return rv; return rv;
@ -51,31 +51,35 @@ Position window_get_cursor(Window window, Error *err)
/// @param window The window handle /// @param window The window handle
/// @param pos the (row, col) tuple representing the new position /// @param pos the (row, col) tuple representing the new position
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void window_set_cursor(Window window, Position pos, Error *err) void window_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err)
{ {
win_T *win = find_window_by_handle(window, err); win_T *win = find_window_by_handle(window, err);
if (pos.size != 2 || pos.items[0].type != kObjectTypeInteger ||
pos.items[1].type != kObjectTypeInteger) {
set_api_error("\"pos\" argument must be a [row, col] array", err);
return;
}
if (!win) { if (!win) {
return; return;
} }
if (pos.row <= 0 || pos.row > win->w_buffer->b_ml.ml_line_count) { int64_t row = pos.items[0].data.integer;
int64_t col = pos.items[1].data.integer;
if (row <= 0 || row > win->w_buffer->b_ml.ml_line_count) {
set_api_error("cursor position outside buffer", err); set_api_error("cursor position outside buffer", err);
return; return;
} }
if (pos.row > LONG_MAX || pos.row < LONG_MIN) { if (col > MAXCOL || col < 0) {
set_api_error("Row value outside range", err);
return;
}
if (pos.col > INT_MAX || pos.col < INT_MIN) {
set_api_error("Column value outside range", err); set_api_error("Column value outside range", err);
return; return;
} }
win->w_cursor.lnum = (linenr_T)pos.row; win->w_cursor.lnum = (linenr_T)row;
win->w_cursor.col = (colnr_T)pos.col; win->w_cursor.col = (colnr_T)col;
win->w_cursor.coladd = 0; win->w_cursor.coladd = 0;
// When column is out of range silently correct it. // When column is out of range silently correct it.
check_cursor_col_win(win); check_cursor_col_win(win);
@ -243,14 +247,14 @@ void window_set_option(Window window, String name, Object value, Error *err)
/// @param window The window handle /// @param window The window handle
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
/// @return The (row, col) tuple with the window position /// @return The (row, col) tuple with the window position
Position window_get_position(Window window, Error *err) ArrayOf(Integer, 2) window_get_position(Window window, Error *err)
{ {
Position rv = POSITION_INIT; Array rv = ARRAY_DICT_INIT;
win_T *win = find_window_by_handle(window, err); win_T *win = find_window_by_handle(window, err);
if (win) { if (win) {
rv.col = win->w_wincol; ADD(rv, INTEGER_OBJ(win->w_winrow));
rv.row = win->w_winrow; ADD(rv, INTEGER_OBJ(win->w_wincol));
} }
return rv; return rv;

View File

@ -84,7 +84,6 @@
#include "nvim/os/channel.h" #include "nvim/os/channel.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h" #include "nvim/api/vim.h"
#include "nvim/os/msgpack_rpc_helpers.h"
#include "nvim/os/dl.h" #include "nvim/os/dl.h"
#include "nvim/os/provider.h" #include "nvim/os/provider.h"
@ -6307,6 +6306,8 @@ static struct fst {
{"acos", 1, 1, f_acos}, /* WJMc */ {"acos", 1, 1, f_acos}, /* WJMc */
{"add", 2, 2, f_add}, {"add", 2, 2, f_add},
{"and", 2, 2, f_and}, {"and", 2, 2, f_and},
{"api_close", 1, 1, f_api_close},
{"api_spawn", 1, 2, f_api_spawn},
{"append", 2, 2, f_append}, {"append", 2, 2, f_append},
{"argc", 0, 0, f_argc}, {"argc", 0, 0, f_argc},
{"argidx", 0, 0, f_argidx}, {"argidx", 0, 0, f_argidx},
@ -7054,6 +7055,83 @@ static void f_and(typval_T *argvars, typval_T *rettv)
& get_tv_number_chk(&argvars[1], NULL); & get_tv_number_chk(&argvars[1], NULL);
} }
// "api_close(prog, argv)" function
static void f_api_close(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) {
// Wrong argument types
EMSG(_(e_invarg));
return;
}
rettv->vval.v_number = channel_close(argvars[0].vval.v_number);
}
// "api_spawn(prog, argv)" function
static void f_api_spawn(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_STRING
|| (argvars[1].v_type != VAR_LIST && argvars[1].v_type != VAR_UNKNOWN)) {
// Wrong argument types
EMSG(_(e_invarg));
return;
}
list_T *args = NULL;
int argsl = 0;
if (argvars[1].v_type == VAR_LIST) {
args = argvars[1].vval.v_list;
argsl = args->lv_len;
// Assert that all list items are strings
for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) {
if (arg->li_tv.v_type != VAR_STRING) {
EMSG(_(e_invarg));
return;
}
}
}
// Allocate extra memory for the argument vector and the NULL pointer
int argvl = argsl + 2;
char **argv = xmalloc(sizeof(char_u *) * argvl);
// Copy program name
argv[0] = xstrdup((char *)argvars[0].vval.v_string);
int i = 1;
// Copy arguments to the vector
if (argsl > 0) {
for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) {
argv[i++] = xstrdup((char *)arg->li_tv.vval.v_string);
}
}
// The last item of argv must be NULL
argv[i] = NULL;
uint64_t channel_id = channel_from_job(argv);
if (!channel_id) {
EMSG(_(e_api_spawn_failed));
}
rettv->vval.v_number = (varnumber_T)channel_id;
}
/* /*
* "append(lnum, string/list)" function * "append(lnum, string/list)" function
*/ */
@ -12718,14 +12796,18 @@ static void f_send_call(typval_T *argvars, typval_T *rettv)
return; return;
} }
if (errored) {
vim_report_error(result.data.string);
goto end;
}
Error conversion_error = {.set = false}; Error conversion_error = {.set = false};
if (errored || !object_to_vim(result, rettv, &conversion_error)) { if (!object_to_vim(result, rettv, &conversion_error)) {
EMSG(errored ? EMSG(_("Error converting the call result"));
result.data.string.data :
_("Error converting the call result"));
} }
msgpack_rpc_free_object(result); end:
api_free_object(result);
} }
// "send_event()" function // "send_event()" function
@ -19239,7 +19321,7 @@ static void script_host_eval(char *method, typval_T *argvars, typval_T *rettv)
Error err = {.set = false}; Error err = {.set = false};
object_to_vim(result, rettv, &err); object_to_vim(result, rettv, &err);
msgpack_rpc_free_object(result); api_free_object(result);
if (err.set) { if (err.set) {
EMSG("Error converting value back to vim"); EMSG("Error converting value back to vim");

View File

@ -55,7 +55,6 @@
#include "nvim/os/shell.h" #include "nvim/os/shell.h"
#include "nvim/os/fs_defs.h" #include "nvim/os/fs_defs.h"
#include "nvim/os/provider.h" #include "nvim/os/provider.h"
#include "nvim/os/msgpack_rpc_helpers.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
@ -3264,7 +3263,7 @@ static void script_host_execute(char *method, exarg_T *eap)
Object result = provider_call(method, args); Object result = provider_call(method, args);
// We don't care about the result, so free it just in case a bad provider // We don't care about the result, so free it just in case a bad provider
// returned something // returned something
msgpack_rpc_free_object(result); api_free_object(result);
} }
free(script); free(script);
@ -3278,7 +3277,7 @@ static void script_host_execute_file(char *method, exarg_T *eap)
Array args = ARRAY_DICT_INIT; Array args = ARRAY_DICT_INIT;
ADD(args, STRING_OBJ(cstr_to_string(buffer))); ADD(args, STRING_OBJ(cstr_to_string(buffer)));
Object result = provider_call(method, args); Object result = provider_call(method, args);
msgpack_rpc_free_object(result); api_free_object(result);
} }
static void script_host_do_range(char *method, exarg_T *eap) static void script_host_do_range(char *method, exarg_T *eap)
@ -3288,6 +3287,6 @@ static void script_host_do_range(char *method, exarg_T *eap)
ADD(args, INTEGER_OBJ(eap->line2)); ADD(args, INTEGER_OBJ(eap->line2));
ADD(args, STRING_OBJ(cstr_to_string((char *)eap->arg))); ADD(args, STRING_OBJ(cstr_to_string((char *)eap->arg)));
Object result = provider_call(method, args); Object result = provider_call(method, args);
msgpack_rpc_free_object(result); api_free_object(result);
} }

View File

@ -1098,6 +1098,7 @@ EXTERN garray_T error_ga
* Excluded are errors that are only used once and debugging messages. * Excluded are errors that are only used once and debugging messages.
*/ */
EXTERN char_u e_abort[] INIT(= N_("E470: Command aborted")); EXTERN char_u e_abort[] INIT(= N_("E470: Command aborted"));
EXTERN char_u e_api_spawn_failed[] INIT(= N_("E903: Could not spawn API job"));
EXTERN char_u e_argreq[] INIT(= N_("E471: Argument required")); EXTERN char_u e_argreq[] INIT(= N_("E471: Argument required"));
EXTERN char_u e_backslash[] INIT(= N_("E10: \\ should be followed by /, ? or &")); EXTERN char_u e_backslash[] INIT(= N_("E10: \\ should be followed by /, ? or &"));
EXTERN char_u e_cmdwin[] INIT(= N_( EXTERN char_u e_cmdwin[] INIT(= N_(

View File

@ -12,6 +12,8 @@
#include <string.h> #include <string.h>
#include <stdbool.h> #include <stdbool.h>
#include <msgpack.h>
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/vim.h" #include "nvim/vim.h"
#include "nvim/main.h" #include "nvim/main.h"
@ -57,6 +59,9 @@
#include "nvim/os/input.h" #include "nvim/os/input.h"
#include "nvim/os/os.h" #include "nvim/os/os.h"
#include "nvim/os/signal.h" #include "nvim/os/signal.h"
#include "nvim/os/msgpack_rpc_helpers.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
/* Maximum number of commands from + or -c arguments. */ /* Maximum number of commands from + or -c arguments. */
#define MAX_ARG_CMDS 10 #define MAX_ARG_CMDS 10
@ -116,9 +121,6 @@ static void init_locale(void);
# endif # endif
#endif /* NO_VIM_MAIN */ #endif /* NO_VIM_MAIN */
extern const uint8_t msgpack_metadata[];
extern const unsigned int msgpack_metadata_size;
/* /*
* Different types of error messages. * Different types of error messages.
*/ */
@ -1026,12 +1028,18 @@ static void command_line_scan(mparm_T *parmp)
msg_putchar('\n'); msg_putchar('\n');
msg_didout = FALSE; msg_didout = FALSE;
mch_exit(0); mch_exit(0);
} else if (STRICMP(argv[0] + argv_idx, "api-msgpack-metadata") == 0) { } else if (STRICMP(argv[0] + argv_idx, "api-info") == 0) {
for (unsigned int i = 0; i<msgpack_metadata_size; i++) { msgpack_sbuffer* b = msgpack_sbuffer_new();
putchar(msgpack_metadata[i]); msgpack_packer* p = msgpack_packer_new(b, msgpack_sbuffer_write);
Object md = DICTIONARY_OBJ(api_metadata());
msgpack_rpc_from_object(md, p);
for (size_t i = 0; i < b->size; i++) {
putchar(b->data[i]);
} }
mch_exit(0); mch_exit(0);
} else if (STRICMP(argv[0] + argv_idx, "embedded-mode") == 0) { } else if (STRICMP(argv[0] + argv_idx, "embed") == 0) {
embedded_mode = true; embedded_mode = true;
} else if (STRNICMP(argv[0] + argv_idx, "literal", 7) == 0) { } else if (STRNICMP(argv[0] + argv_idx, "literal", 7) == 0) {
#if !defined(UNIX) #if !defined(UNIX)
@ -2212,8 +2220,8 @@ static void usage(void)
main_msg(_("-W <scriptout>\tWrite all typed commands to file <scriptout>")); main_msg(_("-W <scriptout>\tWrite all typed commands to file <scriptout>"));
main_msg(_("--startuptime <file>\tWrite startup timing messages to <file>")); main_msg(_("--startuptime <file>\tWrite startup timing messages to <file>"));
main_msg(_("-i <viminfo>\t\tUse <viminfo> instead of .viminfo")); main_msg(_("-i <viminfo>\t\tUse <viminfo> instead of .viminfo"));
main_msg(_("--api-msgpack-metadata\tDump API metadata information and exit")); main_msg(_("--api-info\t\tDump API metadata serialized to msgpack and exit"));
main_msg(_("--embedded-mode\tUse stdin/stdout as a msgpack-rpc channel. " main_msg(_("--embed\t\tUse stdin/stdout as a msgpack-rpc channel. "
"This can be used for embedding Neovim into other programs")); "This can be used for embedding Neovim into other programs"));
main_msg(_("-h or --help\tPrint Help (this message) and exit")); main_msg(_("-h or --help\tPrint Help (this message) and exit"));
main_msg(_("--version\t\tPrint version information and exit")); main_msg(_("--version\t\tPrint version information and exit"));

View File

@ -1,10 +1,12 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h> #include <stdbool.h>
#include <string.h>
#include "nvim/map.h" #include "nvim/map.h"
#include "nvim/map_defs.h" #include "nvim/map_defs.h"
#include "nvim/vim.h" #include "nvim/vim.h"
#include "nvim/memory.h" #include "nvim/memory.h"
#include "nvim/os/msgpack_rpc.h"
#include "nvim/lib/khash.h" #include "nvim/lib/khash.h"
@ -87,7 +89,23 @@
return rv; \ return rv; \
} }
static inline khint_t String_hash(String s)
{
khint_t h = 0;
for (size_t i = 0; i < s.size && s.data[i]; i++) {
h = (h << 5) - h + (uint8_t)s.data[i];
}
return h;
}
static inline bool String_eq(String a, String b)
{
return strncmp(a.data, b.data, min(a.size, b.size)) == 0;
}
MAP_IMPL(cstr_t, uint64_t, DEFAULT_INITIALIZER) MAP_IMPL(cstr_t, uint64_t, DEFAULT_INITIALIZER)
MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(String, rpc_method_handler_fn, DEFAULT_INITIALIZER)

View File

@ -4,6 +4,8 @@
#include <stdbool.h> #include <stdbool.h>
#include "nvim/map_defs.h" #include "nvim/map_defs.h"
#include "nvim/api/private/defs.h"
#include "nvim/os/msgpack_rpc.h"
#define MAP_DECLS(T, U) \ #define MAP_DECLS(T, U) \
KHASH_DECLARE(T##_##U##_map, T, U) \ KHASH_DECLARE(T##_##U##_map, T, U) \
@ -23,6 +25,7 @@ MAP_DECLS(cstr_t, uint64_t)
MAP_DECLS(cstr_t, ptr_t) MAP_DECLS(cstr_t, ptr_t)
MAP_DECLS(ptr_t, ptr_t) MAP_DECLS(ptr_t, ptr_t)
MAP_DECLS(uint64_t, ptr_t) MAP_DECLS(uint64_t, ptr_t)
MAP_DECLS(String, rpc_method_handler_fn)
#define map_new(T, U) map_##T##_##U##_new #define map_new(T, U) map_##T##_##U##_new
#define map_free(T, U) map_##T##_##U##_free #define map_free(T, U) map_##T##_##U##_free

View File

@ -121,7 +121,8 @@ void *xmalloc(size_t size)
void *ret = try_malloc(size); void *ret = try_malloc(size);
if (!ret) { if (!ret) {
OUT_STR("Vim: Error: Out of memory.\n"); OUT_STR(e_outofmem);
out_char('\n');
preserve_exit(); preserve_exit();
} }
return ret; return ret;
@ -147,7 +148,8 @@ void *xcalloc(size_t count, size_t size)
if (!ret && (!count || !size)) if (!ret && (!count || !size))
ret = calloc(1, 1); ret = calloc(1, 1);
if (!ret) { if (!ret) {
OUT_STR("Vim: Error: Out of memory.\n"); OUT_STR(e_outofmem);
out_char('\n');
preserve_exit(); preserve_exit();
} }
} }
@ -174,7 +176,8 @@ void *xrealloc(void *ptr, size_t size)
if (!ret && !size) if (!ret && !size)
ret = realloc(ptr, 1); ret = realloc(ptr, 1);
if (!ret) { if (!ret) {
OUT_STR("Vim: Error: Out of memory.\n"); OUT_STR(e_outofmem);
out_char('\n');
preserve_exit(); preserve_exit();
} }
} }
@ -194,7 +197,7 @@ void *xmallocz(size_t size)
void *ret; void *ret;
if (total_size < size) { if (total_size < size) {
OUT_STR("Vim: Data too large to fit into virtual memory space\n"); OUT_STR(_("Vim: Data too large to fit into virtual memory space\n"));
preserve_exit(); preserve_exit();
} }
@ -316,7 +319,8 @@ char *xstrdup(const char *str)
try_to_free_memory(); try_to_free_memory();
ret = strdup(str); ret = strdup(str);
if (!ret) { if (!ret) {
OUT_STR("Vim: Error: Out of memory.\n"); OUT_STR(e_outofmem);
out_char('\n');
preserve_exit(); preserve_exit();
} }
} }

View File

@ -48,7 +48,6 @@
#include "nvim/undo.h" #include "nvim/undo.h"
#include "nvim/window.h" #include "nvim/window.h"
#include "nvim/os/provider.h" #include "nvim/os/provider.h"
#include "nvim/os/msgpack_rpc_helpers.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
/* /*
@ -5256,7 +5255,7 @@ static void get_clipboard(int name)
return; return;
err: err:
msgpack_rpc_free_object(result); api_free_object(result);
free(reg->y_array); free(reg->y_array);
reg->y_array = NULL; reg->y_array = NULL;
reg->y_size = 0; reg->y_size = 0;
@ -5287,5 +5286,5 @@ static void set_clipboard(int name)
ADD(args, ARRAY_OBJ(lines)); ADD(args, ARRAY_OBJ(lines));
Object result = provider_call("clipboard_set", args); Object result = provider_call("clipboard_set", args);
msgpack_rpc_free_object(result); api_free_object(result);
} }

View File

@ -974,12 +974,6 @@ static struct vimoption
{"infercase", "inf", P_BOOL|P_VI_DEF, {"infercase", "inf", P_BOOL|P_VI_DEF,
(char_u *)&p_inf, PV_INF, (char_u *)&p_inf, PV_INF,
{(char_u *)FALSE, (char_u *)0L} SCRIPTID_INIT}, {(char_u *)FALSE, (char_u *)0L} SCRIPTID_INIT},
{"initclipboard","icpb",P_STRING|P_VI_DEF|P_SECURE,
(char_u *)&p_icpb, PV_NONE,
{(char_u *)"", (char_u *)0L} SCRIPTID_INIT},
{"initpython","ipy",P_STRING|P_VI_DEF|P_SECURE,
(char_u *)&p_ipy, PV_NONE,
{(char_u *)"", (char_u *)0L} SCRIPTID_INIT},
{"insertmode", "im", P_BOOL|P_VI_DEF|P_VIM, {"insertmode", "im", P_BOOL|P_VI_DEF|P_VIM,
(char_u *)&p_im, PV_NONE, (char_u *)&p_im, PV_NONE,
{(char_u *)FALSE, (char_u *)0L} SCRIPTID_INIT}, {(char_u *)FALSE, (char_u *)0L} SCRIPTID_INIT},

View File

@ -631,8 +631,6 @@ EXTERN int p_write; /* 'write' */
EXTERN int p_wa; /* 'writeany' */ EXTERN int p_wa; /* 'writeany' */
EXTERN int p_wb; /* 'writebackup' */ EXTERN int p_wb; /* 'writebackup' */
EXTERN long p_wd; /* 'writedelay' */ EXTERN long p_wd; /* 'writedelay' */
EXTERN char *p_ipy; // 'initpython'
EXTERN char *p_icpb; // 'initclipboard'
/* /*
* "indir" values for buffer-local opions. * "indir" values for buffer-local opions.

View File

@ -22,8 +22,10 @@
#include "nvim/memory.h" #include "nvim/memory.h"
#include "nvim/os_unix.h" #include "nvim/os_unix.h"
#include "nvim/message.h" #include "nvim/message.h"
#include "nvim/term.h"
#include "nvim/map.h" #include "nvim/map.h"
#include "nvim/log.h" #include "nvim/log.h"
#include "nvim/misc1.h"
#include "nvim/lib/kvec.h" #include "nvim/lib/kvec.h"
#define CHANNEL_BUFFER_SIZE 0xffff #define CHANNEL_BUFFER_SIZE 0xffff
@ -156,7 +158,7 @@ bool channel_send_event(uint64_t id, char *name, Array args)
if (id > 0) { if (id > 0) {
if (!(channel = pmap_get(uint64_t)(channels, id)) || !channel->enabled) { if (!(channel = pmap_get(uint64_t)(channels, id)) || !channel->enabled) {
msgpack_rpc_free_array(args); api_free_array(args);
return false; return false;
} }
send_event(channel, name, args); send_event(channel, name, args);
@ -184,7 +186,7 @@ bool channel_send_call(uint64_t id,
Channel *channel = NULL; Channel *channel = NULL;
if (!(channel = pmap_get(uint64_t)(channels, id)) || !channel->enabled) { if (!(channel = pmap_get(uint64_t)(channels, id)) || !channel->enabled) {
msgpack_rpc_free_array(args); api_free_array(args);
return false; return false;
} }
@ -197,7 +199,7 @@ bool channel_send_call(uint64_t id,
"Channel %" PRIu64 " crossed maximum stack depth", "Channel %" PRIu64 " crossed maximum stack depth",
channel->id); channel->id);
*result = STRING_OBJ(cstr_to_string(buf)); *result = STRING_OBJ(cstr_to_string(buf));
msgpack_rpc_free_array(args); api_free_array(args);
return false; return false;
} }
@ -265,6 +267,23 @@ void channel_unsubscribe(uint64_t id, char *event)
unsubscribe(channel, event); unsubscribe(channel, event);
} }
/// Closes a channel
///
/// @param id The channel id
/// @return true if successful, false otherwise
bool channel_close(uint64_t id)
{
Channel *channel;
if (!(channel = pmap_get(uint64_t)(channels, id)) || !channel->enabled) {
return false;
}
channel_kill(channel);
channel->enabled = false;
return true;
}
/// Creates an API channel from stdin/stdout. This is used when embedding /// Creates an API channel from stdin/stdout. This is used when embedding
/// Neovim /// Neovim
static void channel_from_stdio(void) static void channel_from_stdio(void)
@ -331,11 +350,10 @@ static void parse_msgpack(RStream *rstream, void *data, bool eof)
msgpack_unpacked unpacked; msgpack_unpacked unpacked;
msgpack_unpacked_init(&unpacked); msgpack_unpacked_init(&unpacked);
UnpackResult result; msgpack_unpack_return result;
// Deserialize everything we can. // Deserialize everything we can.
while ((result = msgpack_rpc_unpack(channel->unpacker, &unpacked)) while ((result = msgpack_unpacker_next(channel->unpacker, &unpacked))) {
== kUnpackResultOk) {
if (kv_size(channel->call_stack) && is_rpc_response(&unpacked.data)) { if (kv_size(channel->call_stack) && is_rpc_response(&unpacked.data)) {
if (is_valid_rpc_response(&unpacked.data, channel)) { if (is_valid_rpc_response(&unpacked.data, channel)) {
call_stack_pop(&unpacked.data, channel); call_stack_pop(&unpacked.data, channel);
@ -362,7 +380,13 @@ static void parse_msgpack(RStream *rstream, void *data, bool eof)
} }
} }
if (result == kUnpackResultFail) { if (result == MSGPACK_UNPACK_NOMEM_ERROR) {
OUT_STR(e_outofmem);
out_char('\n');
preserve_exit();
}
if (result == MSGPACK_UNPACK_PARSE_ERROR) {
// See src/msgpack/unpack_template.h in msgpack source tree for // See src/msgpack/unpack_template.h in msgpack source tree for
// causes for this error(search for 'goto _failed') // causes for this error(search for 'goto _failed')
// //
@ -441,7 +465,7 @@ static void broadcast_event(char *name, Array args)
}); });
if (!kv_size(subscribed)) { if (!kv_size(subscribed)) {
msgpack_rpc_free_array(args); api_free_array(args);
goto end; goto end;
} }
@ -489,7 +513,13 @@ static void close_channel(Channel *channel)
pmap_free(cstr_t)(channel->subscribed_events); pmap_free(cstr_t)(channel->subscribed_events);
kv_destroy(channel->call_stack); kv_destroy(channel->call_stack);
channel_kill(channel);
free(channel);
}
static void channel_kill(Channel *channel)
{
if (channel->is_job) { if (channel->is_job) {
if (channel->data.job) { if (channel->data.job) {
job_stop(channel->data.job); job_stop(channel->data.job);
@ -504,8 +534,6 @@ static void close_channel(Channel *channel)
mch_exit(0); mch_exit(0);
} }
} }
free(channel);
} }
static void close_cb(uv_handle_t *handle) static void close_cb(uv_handle_t *handle)

View File

@ -197,6 +197,12 @@ Job *job_start(char **argv,
job->stdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; job->stdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
job->stdio[2].data.stream = (uv_stream_t *)&job->proc_stderr; job->stdio[2].data.stream = (uv_stream_t *)&job->proc_stderr;
// Give all handles a reference to the job
handle_set_job((uv_handle_t *)&job->proc, job);
handle_set_job((uv_handle_t *)&job->proc_stdin, job);
handle_set_job((uv_handle_t *)&job->proc_stdout, job);
handle_set_job((uv_handle_t *)&job->proc_stderr, job);
// Spawn the job // Spawn the job
if (uv_spawn(uv_default_loop(), &job->proc, &job->proc_opts) != 0) { if (uv_spawn(uv_default_loop(), &job->proc, &job->proc_opts) != 0) {
free_job(job); free_job(job);
@ -204,12 +210,6 @@ Job *job_start(char **argv,
return NULL; return NULL;
} }
// Give all handles a reference to the job
handle_set_job((uv_handle_t *)&job->proc, job);
handle_set_job((uv_handle_t *)&job->proc_stdin, job);
handle_set_job((uv_handle_t *)&job->proc_stdout, job);
handle_set_job((uv_handle_t *)&job->proc_stderr, job);
job->in = wstream_new(maxmem); job->in = wstream_new(maxmem);
wstream_set_stream(job->in, (uv_stream_t *)&job->proc_stdin); wstream_set_stream(job->in, (uv_stream_t *)&job->proc_stdin);
// Start the readable streams // Start the readable streams

View File

@ -17,9 +17,6 @@
# include "os/msgpack_rpc.c.generated.h" # include "os/msgpack_rpc.c.generated.h"
#endif #endif
extern const uint8_t msgpack_metadata[];
extern const unsigned int msgpack_metadata_size;
/// Validates the basic structure of the msgpack-rpc call and fills `res` /// Validates the basic structure of the msgpack-rpc call and fills `res`
/// with the basic response structure. /// with the basic response structure.
/// ///
@ -39,11 +36,6 @@ WBuffer *msgpack_rpc_call(uint64_t channel_id,
return serialize_response(response_id, err, NIL, sbuffer); return serialize_response(response_id, err, NIL, sbuffer);
} }
if (req->via.array.ptr[2].type == MSGPACK_OBJECT_POSITIVE_INTEGER
&& req->via.array.ptr[2].via.u64 == 0) {
return serialize_metadata(response_id, channel_id, sbuffer);
}
// dispatch the call // dispatch the call
Error error = { .set = false }; Error error = { .set = false };
Object rv = msgpack_rpc_dispatch(channel_id, req, &error); Object rv = msgpack_rpc_dispatch(channel_id, req, &error);
@ -63,42 +55,6 @@ WBuffer *msgpack_rpc_call(uint64_t channel_id,
return serialize_response(response_id, NULL, rv, sbuffer); return serialize_response(response_id, NULL, rv, sbuffer);
} }
/// Try to unpack a msgpack document from the data in the unpacker buffer. This
/// function is a replacement to msgpack.h `msgpack_unpack_next` that lets
/// the called know if the unpacking failed due to bad input or due to missing
/// data.
///
/// @param unpacker The unpacker containing the parse buffer
/// @param result The result which will contain the parsed object
/// @return kUnpackResultOk : An object was parsed
/// kUnpackResultFail : Got bad input
/// kUnpackResultNeedMore: Need more data
UnpackResult msgpack_rpc_unpack(msgpack_unpacker* unpacker,
msgpack_unpacked* result)
FUNC_ATTR_NONNULL_ALL
{
if (result->zone != NULL) {
msgpack_zone_free(result->zone);
}
int res = msgpack_unpacker_execute(unpacker);
if (res > 0) {
result->zone = msgpack_unpacker_release_zone(unpacker);
result->data = msgpack_unpacker_data(unpacker);
msgpack_unpacker_reset(unpacker);
return kUnpackResultOk;
}
if (res < 0) {
// Since we couldn't parse it, destroy the data consumed so far
msgpack_unpacker_reset(unpacker);
return kUnpackResultFail;
}
return kUnpackResultNeedMore;
}
/// Finishes the msgpack-rpc call with an error message. /// Finishes the msgpack-rpc call with an error message.
/// ///
/// @param msg The error message /// @param msg The error message
@ -109,12 +65,22 @@ void msgpack_rpc_error(char *msg, msgpack_packer *res)
size_t len = strlen(msg); size_t len = strlen(msg);
// error message // error message
msgpack_pack_raw(res, len); msgpack_pack_bin(res, len);
msgpack_pack_raw_body(res, msg, len); msgpack_pack_bin_body(res, msg, len);
// Nil result // Nil result
msgpack_pack_nil(res); msgpack_pack_nil(res);
} }
/// Handler executed when an invalid method name is passed
Object msgpack_rpc_handle_missing_method(uint64_t channel_id,
msgpack_object *req,
Error *error)
{
snprintf(error->msg, sizeof(error->msg), "Invalid method name");
error->set = true;
return NIL;
}
/// Serializes a msgpack-rpc request or notification(id == 0) /// Serializes a msgpack-rpc request or notification(id == 0)
WBuffer *serialize_request(uint64_t request_id, WBuffer *serialize_request(uint64_t request_id,
String method, String method,
@ -132,14 +98,14 @@ WBuffer *serialize_request(uint64_t request_id,
msgpack_pack_uint64(&pac, request_id); msgpack_pack_uint64(&pac, request_id);
} }
msgpack_pack_raw(&pac, method.size); msgpack_pack_bin(&pac, method.size);
msgpack_pack_raw_body(&pac, method.data, method.size); msgpack_pack_bin_body(&pac, method.data, method.size);
msgpack_rpc_from_array(args, &pac); msgpack_rpc_from_array(args, &pac);
WBuffer *rv = wstream_new_buffer(xmemdup(sbuffer->data, sbuffer->size), WBuffer *rv = wstream_new_buffer(xmemdup(sbuffer->data, sbuffer->size),
sbuffer->size, sbuffer->size,
refcount, refcount,
free); free);
msgpack_rpc_free_array(args); api_free_array(args);
msgpack_sbuffer_clear(sbuffer); msgpack_sbuffer_clear(sbuffer);
return rv; return rv;
} }
@ -160,8 +126,8 @@ WBuffer *serialize_response(uint64_t response_id,
if (err_msg) { if (err_msg) {
String err = {.size = strlen(err_msg), .data = err_msg}; String err = {.size = strlen(err_msg), .data = err_msg};
// error message // error message
msgpack_pack_raw(&pac, err.size); msgpack_pack_bin(&pac, err.size);
msgpack_pack_raw_body(&pac, err.data, err.size); msgpack_pack_bin_body(&pac, err.data, err.size);
// Nil result // Nil result
msgpack_pack_nil(&pac); msgpack_pack_nil(&pac);
} else { } else {
@ -175,32 +141,7 @@ WBuffer *serialize_response(uint64_t response_id,
sbuffer->size, sbuffer->size,
1, // responses only go though 1 channel 1, // responses only go though 1 channel
free); free);
msgpack_rpc_free_object(arg); api_free_object(arg);
msgpack_sbuffer_clear(sbuffer);
return rv;
}
WBuffer *serialize_metadata(uint64_t id,
uint64_t channel_id,
msgpack_sbuffer *sbuffer)
FUNC_ATTR_NONNULL_ALL
{
msgpack_packer pac;
msgpack_packer_init(&pac, sbuffer, msgpack_sbuffer_write);
msgpack_pack_array(&pac, 4);
msgpack_pack_int(&pac, 1);
msgpack_pack_uint64(&pac, id);
// Nil error
msgpack_pack_nil(&pac);
// The result is the [channel_id, metadata] array
msgpack_pack_array(&pac, 2);
msgpack_pack_uint64(&pac, channel_id);
msgpack_pack_raw(&pac, msgpack_metadata_size);
msgpack_pack_raw_body(&pac, msgpack_metadata, msgpack_metadata_size);
WBuffer *rv = wstream_new_buffer(xmemdup(sbuffer->data, sbuffer->size),
sbuffer->size,
1,
free);
msgpack_sbuffer_clear(sbuffer); msgpack_sbuffer_clear(sbuffer);
return rv; return rv;
} }
@ -234,9 +175,9 @@ static char *msgpack_rpc_validate(uint64_t *response_id, msgpack_object *req)
return "Message type must be 0"; return "Message type must be 0";
} }
if (req->via.array.ptr[2].type != MSGPACK_OBJECT_POSITIVE_INTEGER if (req->via.array.ptr[2].type != MSGPACK_OBJECT_BIN
&& req->via.array.ptr[2].type != MSGPACK_OBJECT_RAW) { && req->via.array.ptr[2].type != MSGPACK_OBJECT_STR) {
return "Method must be a positive integer or a string"; return "Method must be a string";
} }
if (req->via.array.ptr[3].type != MSGPACK_OBJECT_ARRAY) { if (req->via.array.ptr[3].type != MSGPACK_OBJECT_ARRAY) {

View File

@ -25,6 +25,7 @@ typedef Object (*rpc_method_handler_fn)(uint64_t channel_id,
/// Initializes the msgpack-rpc method table /// Initializes the msgpack-rpc method table
void msgpack_rpc_init(void); void msgpack_rpc_init(void);
void msgpack_rpc_init_function_metadata(Dictionary *metadata);
/// Dispatches to the actual API function after basic payload validation by /// Dispatches to the actual API function after basic payload validation by
/// `msgpack_rpc_call`. It is responsible for validating/converting arguments /// `msgpack_rpc_call`. It is responsible for validating/converting arguments

View File

@ -7,61 +7,67 @@
#include "nvim/vim.h" #include "nvim/vim.h"
#include "nvim/memory.h" #include "nvim/memory.h"
#define REMOTE_FUNCS_IMPL(t, lt) \ #ifdef INCLUDE_GENERATED_DECLARATIONS
bool msgpack_rpc_to_##lt(msgpack_object *obj, t *arg) \ # include "os/msgpack_rpc_helpers.c.generated.h"
{ \ #endif
*arg = obj->via.u64; \
return obj->type == MSGPACK_OBJECT_POSITIVE_INTEGER; \
} \
\
void msgpack_rpc_from_##lt(t result, msgpack_packer *res) \
{ \
msgpack_pack_uint64(res, result); \
}
#define TYPED_ARRAY_IMPL(t, lt) \ static msgpack_zone zone;
bool msgpack_rpc_to_##lt##array(msgpack_object *obj, t##Array *arg) \ static msgpack_sbuffer sbuffer;
#define HANDLE_TYPE_CONVERSION_IMPL(t, lt) \
bool msgpack_rpc_to_##lt(msgpack_object *obj, t *arg) \
FUNC_ATTR_NONNULL_ALL \
{ \ { \
if (obj->type != MSGPACK_OBJECT_ARRAY) { \ if (obj->type != MSGPACK_OBJECT_EXT \
|| obj->via.ext.type != kObjectType##t) { \
return false; \ return false; \
} \ } \
\ \
arg->size = obj->via.array.size; \ msgpack_object data; \
arg->items = xcalloc(obj->via.array.size, sizeof(t)); \ msgpack_unpack_return ret = msgpack_unpack(obj->via.ext.ptr, \
obj->via.ext.size, \
NULL, \
&zone, \
&data); \
\ \
for (size_t i = 0; i < obj->via.array.size; i++) { \ if (ret != MSGPACK_UNPACK_SUCCESS) { \
if (!msgpack_rpc_to_##lt(obj->via.array.ptr + i, &arg->items[i])) { \ return false; \
return false; \
} \
} \ } \
\ \
*arg = data.via.u64; \
return true; \ return true; \
} \ } \
\ \
void msgpack_rpc_from_##lt##array(t##Array result, msgpack_packer *res) \ void msgpack_rpc_from_##lt(t o, msgpack_packer *res) \
FUNC_ATTR_NONNULL_ARG(2) \
{ \ { \
msgpack_pack_array(res, result.size); \ msgpack_packer pac; \
\ msgpack_packer_init(&pac, &sbuffer, msgpack_sbuffer_write); \
for (size_t i = 0; i < result.size; i++) { \ msgpack_pack_uint64(&pac, o); \
msgpack_rpc_from_##lt(result.items[i], res); \ msgpack_pack_ext(res, sbuffer.size, kObjectType##t); \
} \ msgpack_pack_ext_body(res, sbuffer.data, sbuffer.size); \
} \ msgpack_sbuffer_clear(&sbuffer); \
\
void msgpack_rpc_free_##lt##array(t##Array value) { \
for (size_t i = 0; i < value.size; i++) { \
msgpack_rpc_free_##lt(value.items[i]); \
} \
\
free(value.items); \
} }
void msgpack_rpc_helpers_init(void)
{
msgpack_zone_init(&zone, 0xfff);
msgpack_sbuffer_init(&sbuffer);
}
HANDLE_TYPE_CONVERSION_IMPL(Buffer, buffer)
HANDLE_TYPE_CONVERSION_IMPL(Window, window)
HANDLE_TYPE_CONVERSION_IMPL(Tabpage, tabpage)
bool msgpack_rpc_to_boolean(msgpack_object *obj, Boolean *arg) bool msgpack_rpc_to_boolean(msgpack_object *obj, Boolean *arg)
FUNC_ATTR_NONNULL_ALL
{ {
*arg = obj->via.boolean; *arg = obj->via.boolean;
return obj->type == MSGPACK_OBJECT_BOOLEAN; return obj->type == MSGPACK_OBJECT_BOOLEAN;
} }
bool msgpack_rpc_to_integer(msgpack_object *obj, Integer *arg) bool msgpack_rpc_to_integer(msgpack_object *obj, Integer *arg)
FUNC_ATTR_NONNULL_ALL
{ {
if (obj->type == MSGPACK_OBJECT_POSITIVE_INTEGER if (obj->type == MSGPACK_OBJECT_POSITIVE_INTEGER
&& obj->via.u64 <= INT64_MAX) { && obj->via.u64 <= INT64_MAX) {
@ -74,23 +80,27 @@ bool msgpack_rpc_to_integer(msgpack_object *obj, Integer *arg)
} }
bool msgpack_rpc_to_float(msgpack_object *obj, Float *arg) bool msgpack_rpc_to_float(msgpack_object *obj, Float *arg)
FUNC_ATTR_NONNULL_ALL
{ {
*arg = obj->via.dec; *arg = obj->via.dec;
return obj->type == MSGPACK_OBJECT_DOUBLE; return obj->type == MSGPACK_OBJECT_DOUBLE;
} }
bool msgpack_rpc_to_string(msgpack_object *obj, String *arg) bool msgpack_rpc_to_string(msgpack_object *obj, String *arg)
FUNC_ATTR_NONNULL_ALL
{ {
if (obj->type != MSGPACK_OBJECT_RAW) { if (obj->type == MSGPACK_OBJECT_BIN || obj->type == MSGPACK_OBJECT_STR) {
arg->data = xmemdupz(obj->via.bin.ptr, obj->via.bin.size);
arg->size = obj->via.bin.size;
} else {
return false; return false;
} }
arg->data = xmemdupz(obj->via.raw.ptr, obj->via.raw.size);
arg->size = obj->via.raw.size;
return true; return true;
} }
bool msgpack_rpc_to_object(msgpack_object *obj, Object *arg) bool msgpack_rpc_to_object(msgpack_object *obj, Object *arg)
FUNC_ATTR_NONNULL_ALL
{ {
switch (obj->type) { switch (obj->type) {
case MSGPACK_OBJECT_NIL: case MSGPACK_OBJECT_NIL:
@ -110,7 +120,8 @@ bool msgpack_rpc_to_object(msgpack_object *obj, Object *arg)
arg->type = kObjectTypeFloat; arg->type = kObjectTypeFloat;
return msgpack_rpc_to_float(obj, &arg->data.floating); return msgpack_rpc_to_float(obj, &arg->data.floating);
case MSGPACK_OBJECT_RAW: case MSGPACK_OBJECT_BIN:
case MSGPACK_OBJECT_STR:
arg->type = kObjectTypeString; arg->type = kObjectTypeString;
return msgpack_rpc_to_string(obj, &arg->data.string); return msgpack_rpc_to_string(obj, &arg->data.string);
@ -122,21 +133,22 @@ bool msgpack_rpc_to_object(msgpack_object *obj, Object *arg)
arg->type = kObjectTypeDictionary; arg->type = kObjectTypeDictionary;
return msgpack_rpc_to_dictionary(obj, &arg->data.dictionary); return msgpack_rpc_to_dictionary(obj, &arg->data.dictionary);
case MSGPACK_OBJECT_EXT:
switch (obj->via.ext.type) {
case kObjectTypeBuffer:
return msgpack_rpc_to_buffer(obj, &arg->data.buffer);
case kObjectTypeWindow:
return msgpack_rpc_to_window(obj, &arg->data.window);
case kObjectTypeTabpage:
return msgpack_rpc_to_tabpage(obj, &arg->data.tabpage);
}
default: default:
return false; return false;
} }
} }
bool msgpack_rpc_to_position(msgpack_object *obj, Position *arg)
{
return obj->type == MSGPACK_OBJECT_ARRAY
&& obj->via.array.size == 2
&& msgpack_rpc_to_integer(obj->via.array.ptr, &arg->row)
&& msgpack_rpc_to_integer(obj->via.array.ptr + 1, &arg->col);
}
bool msgpack_rpc_to_array(msgpack_object *obj, Array *arg) bool msgpack_rpc_to_array(msgpack_object *obj, Array *arg)
FUNC_ATTR_NONNULL_ALL
{ {
if (obj->type != MSGPACK_OBJECT_ARRAY) { if (obj->type != MSGPACK_OBJECT_ARRAY) {
return false; return false;
@ -155,6 +167,7 @@ bool msgpack_rpc_to_array(msgpack_object *obj, Array *arg)
} }
bool msgpack_rpc_to_dictionary(msgpack_object *obj, Dictionary *arg) bool msgpack_rpc_to_dictionary(msgpack_object *obj, Dictionary *arg)
FUNC_ATTR_NONNULL_ALL
{ {
if (obj->type != MSGPACK_OBJECT_MAP) { if (obj->type != MSGPACK_OBJECT_MAP) {
return false; return false;
@ -180,6 +193,7 @@ bool msgpack_rpc_to_dictionary(msgpack_object *obj, Dictionary *arg)
} }
void msgpack_rpc_from_boolean(Boolean result, msgpack_packer *res) void msgpack_rpc_from_boolean(Boolean result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2)
{ {
if (result) { if (result) {
msgpack_pack_true(res); msgpack_pack_true(res);
@ -189,22 +203,26 @@ void msgpack_rpc_from_boolean(Boolean result, msgpack_packer *res)
} }
void msgpack_rpc_from_integer(Integer result, msgpack_packer *res) void msgpack_rpc_from_integer(Integer result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2)
{ {
msgpack_pack_int64(res, result); msgpack_pack_int64(res, result);
} }
void msgpack_rpc_from_float(Float result, msgpack_packer *res) void msgpack_rpc_from_float(Float result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2)
{ {
msgpack_pack_double(res, result); msgpack_pack_double(res, result);
} }
void msgpack_rpc_from_string(String result, msgpack_packer *res) void msgpack_rpc_from_string(String result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2)
{ {
msgpack_pack_raw(res, result.size); msgpack_pack_bin(res, result.size);
msgpack_pack_raw_body(res, result.data, result.size); msgpack_pack_bin_body(res, result.data, result.size);
} }
void msgpack_rpc_from_object(Object result, msgpack_packer *res) void msgpack_rpc_from_object(Object result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2)
{ {
switch (result.type) { switch (result.type) {
case kObjectTypeNil: case kObjectTypeNil:
@ -231,10 +249,6 @@ void msgpack_rpc_from_object(Object result, msgpack_packer *res)
msgpack_rpc_from_array(result.data.array, res); msgpack_rpc_from_array(result.data.array, res);
break; break;
case kObjectTypePosition:
msgpack_rpc_from_position(result.data.position, res);
break;
case kObjectTypeBuffer: case kObjectTypeBuffer:
msgpack_rpc_from_buffer(result.data.buffer, res); msgpack_rpc_from_buffer(result.data.buffer, res);
break; break;
@ -247,36 +261,14 @@ void msgpack_rpc_from_object(Object result, msgpack_packer *res)
msgpack_rpc_from_tabpage(result.data.tabpage, res); msgpack_rpc_from_tabpage(result.data.tabpage, res);
break; break;
case kObjectTypeStringArray:
msgpack_rpc_from_stringarray(result.data.stringarray, res);
break;
case kObjectTypeBufferArray:
msgpack_rpc_from_bufferarray(result.data.bufferarray, res);
break;
case kObjectTypeWindowArray:
msgpack_rpc_from_windowarray(result.data.windowarray, res);
break;
case kObjectTypeTabpageArray:
msgpack_rpc_from_tabpagearray(result.data.tabpagearray, res);
break;
case kObjectTypeDictionary: case kObjectTypeDictionary:
msgpack_rpc_from_dictionary(result.data.dictionary, res); msgpack_rpc_from_dictionary(result.data.dictionary, res);
break; break;
} }
} }
void msgpack_rpc_from_position(Position result, msgpack_packer *res)
{
msgpack_pack_array(res, 2);;
msgpack_pack_int64(res, result.row);
msgpack_pack_int64(res, result.col);
}
void msgpack_rpc_from_array(Array result, msgpack_packer *res) void msgpack_rpc_from_array(Array result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2)
{ {
msgpack_pack_array(res, result.size); msgpack_pack_array(res, result.size);
@ -286,6 +278,7 @@ void msgpack_rpc_from_array(Array result, msgpack_packer *res)
} }
void msgpack_rpc_from_dictionary(Dictionary result, msgpack_packer *res) void msgpack_rpc_from_dictionary(Dictionary result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2)
{ {
msgpack_pack_map(res, result.size); msgpack_pack_map(res, result.size);
@ -294,87 +287,3 @@ void msgpack_rpc_from_dictionary(Dictionary result, msgpack_packer *res)
msgpack_rpc_from_object(result.items[i].value, res); msgpack_rpc_from_object(result.items[i].value, res);
} }
} }
void msgpack_rpc_free_string(String value)
{
if (!value.data) {
return;
}
free(value.data);
}
void msgpack_rpc_free_object(Object value)
{
switch (value.type) {
case kObjectTypeNil:
case kObjectTypeBoolean:
case kObjectTypeInteger:
case kObjectTypeFloat:
case kObjectTypePosition:
case kObjectTypeBuffer:
case kObjectTypeWindow:
case kObjectTypeTabpage:
break;
case kObjectTypeString:
msgpack_rpc_free_string(value.data.string);
break;
case kObjectTypeArray:
msgpack_rpc_free_array(value.data.array);
break;
case kObjectTypeStringArray:
msgpack_rpc_free_stringarray(value.data.stringarray);
break;
case kObjectTypeBufferArray:
msgpack_rpc_free_bufferarray(value.data.bufferarray);
break;
case kObjectTypeWindowArray:
msgpack_rpc_free_windowarray(value.data.windowarray);
break;
case kObjectTypeTabpageArray:
msgpack_rpc_free_tabpagearray(value.data.tabpagearray);
break;
case kObjectTypeDictionary:
msgpack_rpc_free_dictionary(value.data.dictionary);
break;
default:
abort();
}
}
void msgpack_rpc_free_array(Array value)
{
for (uint32_t i = 0; i < value.size; i++) {
msgpack_rpc_free_object(value.items[i]);
}
free(value.items);
}
void msgpack_rpc_free_dictionary(Dictionary value)
{
for (uint32_t i = 0; i < value.size; i++) {
msgpack_rpc_free_string(value.items[i].key);
msgpack_rpc_free_object(value.items[i].value);
}
free(value.items);
}
REMOTE_FUNCS_IMPL(Buffer, buffer)
REMOTE_FUNCS_IMPL(Window, window)
REMOTE_FUNCS_IMPL(Tabpage, tabpage)
TYPED_ARRAY_IMPL(Buffer, buffer)
TYPED_ARRAY_IMPL(Window, window)
TYPED_ARRAY_IMPL(Tabpage, tabpage)
TYPED_ARRAY_IMPL(String, string)

View File

@ -6,119 +6,11 @@
#include <msgpack.h> #include <msgpack.h>
#include "nvim/func_attr.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
/// Functions for validating and converting from msgpack types to C types. #ifdef INCLUDE_GENERATED_DECLARATIONS
/// These are used by `msgpack_rpc_dispatch` to validate and convert each # include "os/msgpack_rpc_helpers.h.generated.h"
/// argument. #endif
///
/// @param obj The object to convert
/// @param[out] arg A pointer to the avalue
/// @return true if the conversion succeeded, false otherwise
bool msgpack_rpc_to_boolean(msgpack_object *obj, Boolean *arg)
FUNC_ATTR_NONNULL_ALL;
bool msgpack_rpc_to_integer(msgpack_object *obj, Integer *arg)
FUNC_ATTR_NONNULL_ALL;
bool msgpack_rpc_to_float(msgpack_object *obj, Float *arg)
FUNC_ATTR_NONNULL_ALL;
bool msgpack_rpc_to_position(msgpack_object *obj, Position *arg)
FUNC_ATTR_NONNULL_ALL;
bool msgpack_rpc_to_string(msgpack_object *obj, String *arg)
FUNC_ATTR_NONNULL_ALL;
bool msgpack_rpc_to_buffer(msgpack_object *obj, Buffer *arg)
FUNC_ATTR_NONNULL_ALL;
bool msgpack_rpc_to_window(msgpack_object *obj, Window *arg)
FUNC_ATTR_NONNULL_ALL;
bool msgpack_rpc_to_tabpage(msgpack_object *obj, Tabpage *arg)
FUNC_ATTR_NONNULL_ALL;
bool msgpack_rpc_to_object(msgpack_object *obj, Object *arg)
FUNC_ATTR_NONNULL_ALL;
bool msgpack_rpc_to_stringarray(msgpack_object *obj, StringArray *arg)
FUNC_ATTR_NONNULL_ALL;
bool msgpack_rpc_to_bufferarray(msgpack_object *obj, BufferArray *arg)
FUNC_ATTR_NONNULL_ALL;
bool msgpack_rpc_to_windowarray(msgpack_object *obj, WindowArray *arg)
FUNC_ATTR_NONNULL_ALL;
bool msgpack_rpc_to_tabpagearray(msgpack_object *obj, TabpageArray *arg)
FUNC_ATTR_NONNULL_ALL;
bool msgpack_rpc_to_array(msgpack_object *obj, Array *arg)
FUNC_ATTR_NONNULL_ALL;
bool msgpack_rpc_to_dictionary(msgpack_object *obj, Dictionary *arg)
FUNC_ATTR_NONNULL_ALL;
/// Functions for converting from C types to msgpack types.
/// These are used by `msgpack_rpc_dispatch` to convert return values
/// from the API
///
/// @param result A pointer to the result
/// @param res A packer that contains the response
void msgpack_rpc_from_boolean(Boolean result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2);
void msgpack_rpc_from_integer(Integer result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2);
void msgpack_rpc_from_float(Float result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2);
void msgpack_rpc_from_position(Position result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2);
void msgpack_rpc_from_string(String result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2);
void msgpack_rpc_from_buffer(Buffer result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2);
void msgpack_rpc_from_window(Window result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2);
void msgpack_rpc_from_tabpage(Tabpage result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2);
void msgpack_rpc_from_object(Object result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2);
void msgpack_rpc_from_stringarray(StringArray result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2);
void msgpack_rpc_from_bufferarray(BufferArray result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2);
void msgpack_rpc_from_windowarray(WindowArray result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2);
void msgpack_rpc_from_tabpagearray(TabpageArray result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2);
void msgpack_rpc_from_array(Array result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2);
void msgpack_rpc_from_dictionary(Dictionary result, msgpack_packer *res)
FUNC_ATTR_NONNULL_ARG(2);
/// Helpers for initializing types that may be freed later
#define msgpack_rpc_init_boolean
#define msgpack_rpc_init_integer
#define msgpack_rpc_init_float
#define msgpack_rpc_init_position
#define msgpack_rpc_init_string = STRING_INIT
#define msgpack_rpc_init_buffer
#define msgpack_rpc_init_window
#define msgpack_rpc_init_tabpage
#define msgpack_rpc_init_object = {.type = kObjectTypeNil}
#define msgpack_rpc_init_stringarray = ARRAY_DICT_INIT
#define msgpack_rpc_init_bufferarray = ARRAY_DICT_INIT
#define msgpack_rpc_init_windowarray = ARRAY_DICT_INIT
#define msgpack_rpc_init_tabpagearray = ARRAY_DICT_INIT
#define msgpack_rpc_init_array = ARRAY_DICT_INIT
#define msgpack_rpc_init_dictionary = ARRAY_DICT_INIT
/// Helpers for freeing arguments/return value
///
/// @param value The value to be freed
#define msgpack_rpc_free_boolean(value)
#define msgpack_rpc_free_integer(value)
#define msgpack_rpc_free_float(value)
#define msgpack_rpc_free_position(value)
void msgpack_rpc_free_string(String value);
#define msgpack_rpc_free_buffer(value)
#define msgpack_rpc_free_window(value)
#define msgpack_rpc_free_tabpage(value)
void msgpack_rpc_free_object(Object value);
void msgpack_rpc_free_stringarray(StringArray value);
void msgpack_rpc_free_bufferarray(BufferArray value);
void msgpack_rpc_free_windowarray(WindowArray value);
void msgpack_rpc_free_tabpagearray(TabpageArray value);
void msgpack_rpc_free_array(Array value);
void msgpack_rpc_free_dictionary(Dictionary value);
#endif // NVIM_OS_MSGPACK_RPC_HELPERS_H #endif // NVIM_OS_MSGPACK_RPC_HELPERS_H

View File

@ -14,37 +14,34 @@
#include "nvim/log.h" #include "nvim/log.h"
#include "nvim/map.h" #include "nvim/map.h"
#include "nvim/message.h" #include "nvim/message.h"
#include "nvim/os/msgpack_rpc_helpers.h"
#define FEATURE_COUNT (sizeof(features) / sizeof(features[0])) #define FEATURE_COUNT (sizeof(features) / sizeof(features[0]))
#define FEATURE(feature_name, provider_bootstrap_command, ...) { \ #define FEATURE(feature_name, ...) { \
.name = feature_name, \ .name = feature_name, \
.bootstrap_command = provider_bootstrap_command, \
.argv = NULL, \
.channel_id = 0, \ .channel_id = 0, \
.methods = (char *[]){__VA_ARGS__, NULL} \ .methods = (char *[]){__VA_ARGS__, NULL} \
} }
static struct feature { typedef struct {
char *name, **bootstrap_command, **argv, **methods; char *name, **methods;
size_t name_length; size_t name_length;
uint64_t channel_id; uint64_t channel_id;
} features[] = { } Feature;
static Feature features[] = {
FEATURE("python", FEATURE("python",
&p_ipy,
"python_execute", "python_execute",
"python_execute_file", "python_execute_file",
"python_do_range", "python_do_range",
"python_eval"), "python_eval"),
FEATURE("clipboard", FEATURE("clipboard",
&p_icpb,
"clipboard_get", "clipboard_get",
"clipboard_set") "clipboard_set")
}; };
static Map(cstr_t, uint64_t) *registered_providers = NULL; static PMap(cstr_t) *registered_providers = NULL;
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/provider.c.generated.h" # include "os/provider.c.generated.h"
@ -53,163 +50,101 @@ static Map(cstr_t, uint64_t) *registered_providers = NULL;
void provider_init(void) void provider_init(void)
{ {
registered_providers = map_new(cstr_t, uint64_t)(); registered_providers = pmap_new(cstr_t)();
} }
bool provider_has_feature(char *name) bool provider_has_feature(char *name)
{ {
for (size_t i = 0; i < FEATURE_COUNT; i++) { Feature *f = find_feature(name);
struct feature *f = &features[i]; return f != NULL && channel_exists(f->channel_id);
if (!STRICMP(name, f->name)) {
return f->channel_id || can_execute(f);
}
}
return false;
} }
bool provider_available(char *method) bool provider_register(char *name, uint64_t channel_id)
{ {
return map_has(cstr_t, uint64_t)(registered_providers, method); Feature *f = find_feature(name);
}
bool provider_register(char *method, uint64_t channel_id) if (!f) {
{
if (map_has(cstr_t, uint64_t)(registered_providers, method)) {
return false; return false;
} }
// First check if this method is part of a feature, and if so, update if (f->channel_id && channel_exists(f->channel_id)) {
// the feature structure with the channel id ILOG("Feature \"%s\" is already provided by another channel"
struct feature *f = get_feature_for(method); "(will be replaced)", name);
if (f) {
DLOG("Registering provider for \"%s\" "
"which is part of the \"%s\" feature",
method,
f->name);
f->channel_id = channel_id;
} }
map_put(cstr_t, uint64_t)(registered_providers, xstrdup(method), channel_id); DLOG("Registering provider for \"%s\"", name);
ILOG("Registered channel %" PRIu64 " as the provider for \"%s\"", f->channel_id = channel_id;
// Associate all method names with the feature struct
size_t i;
char *method;
for (method = f->methods[i = 0]; method; method = f->methods[++i]) {
pmap_put(cstr_t)(registered_providers, method, f);
DLOG("Channel \"%" PRIu64 "\" will be sent requests for \"%s\"",
channel_id,
method);
}
ILOG("Registered channel %" PRIu64 " as the provider for the \"%s\" feature",
channel_id, channel_id,
method); name);
return true; return true;
} }
Object provider_call(char *method, Array args) Object provider_call(char *method, Array args)
{ {
uint64_t channel_id = get_provider_for(method); Feature *f = pmap_get(cstr_t)(registered_providers, method);
if (!channel_id) { if (!f || !channel_exists(f->channel_id)) {
char buf[256]; char buf[256];
snprintf(buf, snprintf(buf,
sizeof(buf), sizeof(buf),
"Provider for \"%s\" is not available", "Provider for method \"%s\" is not available",
method); method);
report_error(buf); vim_report_error(cstr_as_string(buf));
msgpack_rpc_free_array(args); api_free_array(args);
return NIL; return NIL;
} }
bool error = false; bool error = false;
Object result = NIL; Object result = NIL;
channel_send_call(channel_id, method, args, &result, &error); channel_send_call(f->channel_id, method, args, &result, &error);
if (error) { if (error) {
report_error(result.data.string.data); vim_report_error(result.data.string);
msgpack_rpc_free_object(result); api_free_object(result);
return NIL; return NIL;
} }
return result; return result;
} }
static uint64_t get_provider_for(char *method) void provider_init_feature_metadata(Dictionary *metadata)
{ {
uint64_t channel_id = map_get(cstr_t, uint64_t)(registered_providers, method); Dictionary md = ARRAY_DICT_INIT;
if (channel_id) { for (size_t i = 0; i < FEATURE_COUNT; i++) {
return channel_id; Array methods = ARRAY_DICT_INIT;
} Feature *f = &features[i];
// Try to bootstrap if the method is part of a feature size_t j;
struct feature *f = get_feature_for(method); char *method;
for (method = f->methods[j = 0]; method; method = f->methods[++j]) {
if (!f || !can_execute(f)) { ADD(methods, STRING_OBJ(cstr_to_string(method)));
ELOG("Cannot bootstrap provider for \"%s\"", method);
goto err;
}
if (f->channel_id) {
ELOG("Already bootstrapped provider for \"%s\"", f->name);
goto err;
}
f->channel_id = channel_from_job(f->argv);
if (!f->channel_id) {
ELOG("The provider for \"%s\" failed to bootstrap", f->name);
goto err;
}
return f->channel_id;
err:
// Ensure we won't try to restart the provider
if (f) {
f->bootstrap_command = NULL;
f->channel_id = 0;
}
return 0;
}
static bool can_execute(struct feature *f)
{
if (!f->bootstrap_command) {
return false;
}
char *cmd = *f->bootstrap_command;
if (!cmd || !strlen(cmd)) {
return false;
}
if (!f->argv) {
f->argv = shell_build_argv((uint8_t *)cmd, NULL);
}
return os_can_exe((uint8_t *)f->argv[0]);
}
static void report_error(char *str)
{
vim_err_write((String) {.data = str, .size = strlen(str)});
vim_err_write((String) {.data = "\n", .size = 1});
}
static bool feature_has_method(struct feature *f, char *method)
{
size_t i;
char *m;
for (m = f->methods[i = 0]; m; m = f->methods[++i]) {
if (!STRCMP(method, m)) {
return true;
} }
PUT(md, f->name, ARRAY_OBJ(methods));
} }
return false; PUT(*metadata, "features", DICTIONARY_OBJ(md));
} }
static Feature * find_feature(char *name)
static struct feature *get_feature_for(char *method)
{ {
for (size_t i = 0; i < FEATURE_COUNT; i++) { for (size_t i = 0; i < FEATURE_COUNT; i++) {
struct feature *f = &features[i]; Feature *f = &features[i];
if (feature_has_method(f, method)) { if (!STRICMP(name, f->name)) {
return f; return f;
} }
} }

View File

@ -72,12 +72,12 @@ WStream * wstream_new(size_t maxmem)
/// @param wstream The `WStream` instance /// @param wstream The `WStream` instance
void wstream_free(WStream *wstream) { void wstream_free(WStream *wstream) {
if (!wstream->pending_reqs) { if (!wstream->pending_reqs) {
handle_set_wstream((uv_handle_t *)wstream->stream, NULL);
if (wstream->free_handle) { if (wstream->free_handle) {
uv_close((uv_handle_t *)wstream->stream, close_cb); uv_close((uv_handle_t *)wstream->stream, close_cb);
} else {
handle_set_wstream((uv_handle_t *)wstream->stream, NULL);
free(wstream);
} }
free(wstream);
} else { } else {
wstream->freed = true; wstream->freed = true;
} }
@ -238,12 +238,7 @@ static void release_wbuffer(WBuffer *buffer)
static void close_cb(uv_handle_t *handle) static void close_cb(uv_handle_t *handle)
{ {
WStream *wstream = handle_get_wstream(handle); free(handle_get_wstream(handle));
if (wstream) {
free(wstream);
}
free(handle->data); free(handle->data);
free(handle); free(handle);
} }

View File

@ -55,6 +55,7 @@
#include "nvim/os/signal.h" #include "nvim/os/signal.h"
#include "nvim/os/job.h" #include "nvim/os/job.h"
#include "nvim/os/msgpack_rpc.h" #include "nvim/os/msgpack_rpc.h"
#include "nvim/os/msgpack_rpc_helpers.h"
#if defined(HAVE_SYS_IOCTL_H) #if defined(HAVE_SYS_IOCTL_H)
# include <sys/ioctl.h> # include <sys/ioctl.h>
@ -166,6 +167,7 @@ void mch_init(void)
#endif #endif
msgpack_rpc_init(); msgpack_rpc_init();
msgpack_rpc_helpers_init();
event_init(); event_init();
} }

View File

@ -8,6 +8,8 @@
#ifndef NVIM_VIM_H #ifndef NVIM_VIM_H
# define NVIM_VIM_H # define NVIM_VIM_H
#define min(X, Y) (X < Y ? X : Y)
#include "nvim/types.h" #include "nvim/types.h"
#include "nvim/pos.h" // for linenr_T, MAXCOL, etc... #include "nvim/pos.h" // for linenr_T, MAXCOL, etc...

View File

@ -50,8 +50,8 @@ include(ExternalProject)
set(LIBUV_URL https://github.com/joyent/libuv/archive/v0.11.28.tar.gz) set(LIBUV_URL https://github.com/joyent/libuv/archive/v0.11.28.tar.gz)
set(LIBUV_MD5 1a849ba4fc571d531482ed74bc7aabc4) set(LIBUV_MD5 1a849ba4fc571d531482ed74bc7aabc4)
set(MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/cpp-0.5.8/msgpack-0.5.8.tar.gz) set(MSGPACK_URL https://github.com/msgpack/msgpack-c/archive/0335df55e1a408c0d56d43e46253c952fb8a7f04.tar.gz)
set(MSGPACK_MD5 ea0bee0939d2980c0df91f0e4843ccc4) set(MSGPACK_MD5 4c18a1625b586c0d69a0e955ce9a187f)
set(LUAJIT_URL http://luajit.org/download/LuaJIT-2.0.3.tar.gz) set(LUAJIT_URL http://luajit.org/download/LuaJIT-2.0.3.tar.gz)
set(LUAJIT_MD5 f14e9104be513913810cd59c8c658dc0) set(LUAJIT_MD5 f14e9104be513913810cd59c8c658dc0)
@ -92,9 +92,18 @@ if(USE_BUNDLED_MSGPACK)
-DEXPECTED_MD5=${MSGPACK_MD5} -DEXPECTED_MD5=${MSGPACK_MD5}
-DTARGET=msgpack -DTARGET=msgpack
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake
CONFIGURE_COMMAND ${DEPS_BUILD_DIR}/src/msgpack/configure --disable-shared CONFIGURE_COMMAND cmake ${DEPS_BUILD_DIR}/src/msgpack
--with-pic --prefix=${DEPS_INSTALL_DIR} CC=${DEPS_C_COMPILER} -DMSGPACK_ENABLE_CXX=OFF
INSTALL_COMMAND ${MAKE_PRG} install) -DMSGPACK_BUILD_TESTS=OFF
-DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR}
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
"-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} -fPIC"
BUILD_COMMAND ${MAKE_PRG}
INSTALL_COMMAND ${MAKE_PRG} install &&
rm ${DEPS_INSTALL_DIR}/lib/libmsgpack.so &&
rm ${DEPS_INSTALL_DIR}/lib/libmsgpack.so.3 &&
rm ${DEPS_INSTALL_DIR}/lib/libmsgpack.so.4.0.0
)
list(APPEND THIRD_PARTY_DEPS msgpack) list(APPEND THIRD_PARTY_DEPS msgpack)
endif() endif()