mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Merge PR #1130 'Update to the experimental msgpack v5 branch'
This commit is contained in:
commit
6a8932aa58
@ -23,7 +23,7 @@ fi
|
||||
|
||||
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
|
||||
valgrind_check "$tmpdir"
|
||||
exit 1
|
||||
|
@ -67,7 +67,7 @@ install_prebuilt_deps() {
|
||||
|
||||
install_vroom() {
|
||||
(
|
||||
sudo pip install neovim
|
||||
sudo pip install git+https://github.com/neovim/python-client.git
|
||||
git clone git://github.com/google/vroom
|
||||
cd vroom
|
||||
python setup.py build
|
||||
|
@ -24,13 +24,13 @@ find_path(MSGPACK_INCLUDE_DIR msgpack.h
|
||||
HINTS ${PC_MSGPACK_INCLUDEDIR} ${PC_MSGPACK_INCLUDE_DIRS}
|
||||
${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)
|
||||
list(APPEND MSGPACK_NAMES
|
||||
"${CMAKE_STATIC_LIBRARY_PREFIX}msgpackc${CMAKE_STATIC_LIBRARY_SUFFIX}")
|
||||
"${CMAKE_STATIC_LIBRARY_PREFIX}msgpack${CMAKE_STATIC_LIBRARY_SUFFIX}")
|
||||
endif()
|
||||
|
||||
list(APPEND MSGPACK_NAMES msgpackc)
|
||||
list(APPEND MSGPACK_NAMES msgpack)
|
||||
|
||||
find_library(MSGPACK_LIBRARY NAMES ${MSGPACK_NAMES}
|
||||
HINTS ${PC_MSGPACK_LIBDIR} ${PC_MSGPACK_LIBRARY_DIRS}
|
||||
|
44
runtime/plugin/python_setup.vim
Normal file
44
runtime/plugin/python_setup.vim
Normal 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
|
@ -59,9 +59,16 @@ local right_word = concat(
|
||||
raw_word,
|
||||
neg_look_ahead(aw)
|
||||
)
|
||||
local word = concat(
|
||||
neg_look_behind(aw),
|
||||
right_word
|
||||
local word = branch(
|
||||
concat(
|
||||
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(
|
||||
s,
|
||||
@ -204,7 +211,7 @@ while init ~= nil do
|
||||
declaration = declaration:gsub('\n', ' ')
|
||||
declaration = declaration:gsub('%s+', ' ')
|
||||
declaration = declaration:gsub(' ?%( ?', '(')
|
||||
declaration = declaration:gsub(' ?%) ?', ')')
|
||||
-- declaration = declaration:gsub(' ?%) ?', ')')
|
||||
declaration = declaration:gsub(' ?, ?', ', ')
|
||||
declaration = declaration:gsub(' ?(%*+) ?', ' %1')
|
||||
declaration = declaration:gsub(' ?(FUNC_ATTR_)', ' %1')
|
||||
|
@ -16,7 +16,12 @@ ws = S(' \t') + nl
|
||||
fill = ws ^ 0
|
||||
c_comment = 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_param_type = (
|
||||
((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
|
||||
assert(#arg >= 1)
|
||||
-- api metadata
|
||||
api = {
|
||||
functions = {},
|
||||
-- Helpers for object-oriented languages
|
||||
classes = {'Buffer', 'Window', 'Tabpage'}
|
||||
}
|
||||
functions = {}
|
||||
|
||||
-- names of all headers relative to the source root(for inclusion in the
|
||||
-- generated file)
|
||||
headers = {}
|
||||
@ -59,9 +60,8 @@ for i = 1, #arg - 1 do
|
||||
local input = io.open(full_path, 'rb')
|
||||
local tmp = grammar:match(input:read('*all'))
|
||||
for i = 1, #tmp do
|
||||
api.functions[#api.functions + 1] = tmp[i]
|
||||
local fn_id = #api.functions
|
||||
local fn = api.functions[fn_id]
|
||||
functions[#functions + 1] = tmp[i]
|
||||
local fn = tmp[i]
|
||||
if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then
|
||||
-- this function should receive the channel id
|
||||
fn.receives_channel_id = true
|
||||
@ -75,8 +75,6 @@ for i = 1, #arg - 1 do
|
||||
-- for specifying errors
|
||||
fn.parameters[#fn.parameters] = nil
|
||||
end
|
||||
-- assign a unique integer id for each api function
|
||||
fn.id = fn_id
|
||||
end
|
||||
input:close()
|
||||
end
|
||||
@ -93,9 +91,11 @@ output:write([[
|
||||
|
||||
#include "nvim/map.h"
|
||||
#include "nvim/log.h"
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/os/msgpack_rpc.h"
|
||||
#include "nvim/os/msgpack_rpc_helpers.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
]])
|
||||
|
||||
for i = 1, #headers do
|
||||
@ -107,12 +107,12 @@ end
|
||||
output:write([[
|
||||
|
||||
|
||||
const uint8_t msgpack_metadata[] = {
|
||||
static const uint8_t msgpack_metadata[] = {
|
||||
|
||||
]])
|
||||
-- serialize the API metadata using msgpack and embed into the resulting
|
||||
-- binary for easy querying by clients
|
||||
packed = msgpack.pack(api)
|
||||
packed = msgpack.pack(functions)
|
||||
for i = 1, #packed do
|
||||
output:write(string.byte(packed, i)..', ')
|
||||
if i % 10 == 0 then
|
||||
@ -121,16 +121,40 @@ for i = 1, #packed do
|
||||
end
|
||||
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
|
||||
-- querying the metadata, usually it is the first function called by clients.
|
||||
-- Visit each function metadata to build the handler function with code
|
||||
-- generated for validating arguments and calling to the real API.
|
||||
for i = 1, #api.functions do
|
||||
local fn = api.functions[i]
|
||||
local function real_type(type)
|
||||
local rv = type
|
||||
if typed_container:match(rv) then
|
||||
if rv:match('Array') then
|
||||
rv = 'Array'
|
||||
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 = {}
|
||||
|
||||
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
|
||||
local param = fn.parameters[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
|
||||
output:write('\n')
|
||||
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]
|
||||
arg = '(req->via.array.ptr[3].via.array.ptr + '..(j - 1)..')'
|
||||
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 snprintf(error->msg, sizeof(error->msg), "Wrong type for argument '..j..', expecting '..param[1]..'");')
|
||||
output:write('\n error->set = true;')
|
||||
@ -202,7 +226,7 @@ for i = 1, #api.functions do
|
||||
end
|
||||
|
||||
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
|
||||
-- Now generate the cleanup label for freeing memory allocated for the
|
||||
-- arguments
|
||||
@ -210,7 +234,7 @@ for i = 1, #api.functions do
|
||||
|
||||
for j = 1, #fn.parameters do
|
||||
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
|
||||
if fn.return_type ~= 'void' then
|
||||
output:write('\n return ret;\n}\n\n');
|
||||
@ -219,47 +243,26 @@ for i = 1, #api.functions do
|
||||
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
|
||||
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)
|
||||
{
|
||||
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
|
||||
-- searching in the map, so we keep track of the maximum method name length in
|
||||
-- order to create the smallest possible buffer for xstrlcpy
|
||||
-- Keep track of the maximum method name length in order to avoid walking
|
||||
-- strings longer than that when searching for a method handler
|
||||
local max_fname_len = 0
|
||||
for i = 1, #api.functions do
|
||||
local fn = api.functions[i]
|
||||
output:write(' map_put(cstr_t, uint64_t)(rpc_method_ids, "'
|
||||
..fn.name..'", '..i..');\n')
|
||||
for i = 1, #functions do
|
||||
local fn = functions[i]
|
||||
output:write(' map_put(String, rpc_method_handler_fn)(methods, '..
|
||||
'(String) {.data = "'..fn.name..'", '..
|
||||
'.size = sizeof("'..fn.name..'") - 1}, handle_'..
|
||||
fn.name..');\n')
|
||||
|
||||
if #fn.name > max_fname_len then
|
||||
max_fname_len = #fn.name
|
||||
end
|
||||
@ -268,30 +271,27 @@ end
|
||||
output:write('\n}\n\n')
|
||||
|
||||
output:write([[
|
||||
#define min(X, Y) (X < Y ? X : Y)
|
||||
|
||||
Object msgpack_rpc_dispatch(uint64_t channel_id,
|
||||
msgpack_object *req,
|
||||
Error *error)
|
||||
{
|
||||
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) {
|
||||
char method_name[]]..(max_fname_len + 1)..[[];
|
||||
xstrlcpy(method_name, method.via.raw.ptr, min(method.via.raw.size, ]] ..(max_fname_len)..[[) + 1);
|
||||
method_id = map_get(cstr_t, uint64_t)(rpc_method_ids, method_name);
|
||||
if (!method_id) {
|
||||
method_id = UINT64_MAX;
|
||||
}
|
||||
}
|
||||
if (method.type == MSGPACK_OBJECT_BIN || method.type == MSGPACK_OBJECT_STR) {
|
||||
]])
|
||||
output:write(' handler = map_get(String, rpc_method_handler_fn)')
|
||||
output:write('(methods, (String){.data=(char *)method.via.bin.ptr,')
|
||||
output:write('.size=min(method.via.bin.size, '..max_fname_len..')});\n')
|
||||
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()
|
||||
|
@ -51,10 +51,10 @@ Integer buffer_get_length(Buffer buffer, Error *err)
|
||||
String buffer_get_line(Buffer buffer, Integer index, Error *err)
|
||||
{
|
||||
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) {
|
||||
rv = slice.items[0];
|
||||
rv = slice.items[0].data.string;
|
||||
}
|
||||
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
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);
|
||||
}
|
||||
|
||||
@ -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[out] err Details of an error that may have occurred
|
||||
/// @return An array of lines
|
||||
StringArray buffer_get_slice(Buffer buffer,
|
||||
Integer start,
|
||||
Integer end,
|
||||
Boolean include_start,
|
||||
Boolean include_end,
|
||||
Error *err)
|
||||
ArrayOf(String) buffer_get_slice(Buffer buffer,
|
||||
Integer start,
|
||||
Integer end,
|
||||
Boolean include_start,
|
||||
Boolean include_end,
|
||||
Error *err)
|
||||
{
|
||||
StringArray rv = ARRAY_DICT_INIT;
|
||||
Array rv = ARRAY_DICT_INIT;
|
||||
buf_T *buf = find_buffer_by_handle(buffer, err);
|
||||
|
||||
if (!buf) {
|
||||
@ -118,7 +119,7 @@ StringArray buffer_get_slice(Buffer buffer,
|
||||
}
|
||||
|
||||
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++) {
|
||||
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);
|
||||
rv.items[i] = cstr_to_string(bufstr);
|
||||
rv.items[i] = STRING_OBJ(cstr_to_string(bufstr));
|
||||
}
|
||||
|
||||
end:
|
||||
if (err->set) {
|
||||
for (size_t i = 0; i < rv.size; i++) {
|
||||
free(rv.items[i].data);
|
||||
free(rv.items[i].data.string.data);
|
||||
}
|
||||
|
||||
free(rv.items);
|
||||
@ -152,15 +153,15 @@ end:
|
||||
/// @param end The last line index
|
||||
/// @param include_start True if the slice includes the `start` 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
|
||||
/// will simply delete the line range)
|
||||
/// @param replacement An array of lines to use as replacement(A 0-length
|
||||
// array will simply delete the line range)
|
||||
/// @param[out] err Details of an error that may have occurred
|
||||
void buffer_set_slice(Buffer buffer,
|
||||
Integer start,
|
||||
Integer end,
|
||||
Boolean include_start,
|
||||
Boolean include_end,
|
||||
StringArray replacement,
|
||||
ArrayOf(String) replacement,
|
||||
Error *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 old_len = (size_t)(end - start);
|
||||
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++) {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -430,7 +436,10 @@ Boolean buffer_is_valid(Buffer buffer)
|
||||
/// to the end of the buffer.
|
||||
/// @param lines An array of lines
|
||||
/// @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);
|
||||
}
|
||||
@ -441,9 +450,9 @@ void buffer_insert(Buffer buffer, Integer lnum, StringArray lines, Error *err)
|
||||
/// @param name The mark's name
|
||||
/// @param[out] err Details of an error that may have occurred
|
||||
/// @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);
|
||||
|
||||
if (!buf) {
|
||||
@ -473,8 +482,9 @@ Position buffer_get_mark(Buffer buffer, String name, Error *err)
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv.row = posp->lnum;
|
||||
rv.col = posp->col;
|
||||
ADD(rv, INTEGER_OBJ(posp->lnum));
|
||||
ADD(rv, INTEGER_OBJ(posp->col));
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
@ -5,17 +5,15 @@
|
||||
#include <stdbool.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 OBJECT_INIT { .type = kObjectTypeNil }
|
||||
#define POSITION_INIT { .row = 0, .col = 0 }
|
||||
#define REMOTE_TYPE(type) typedef uint64_t type
|
||||
|
||||
#define TYPED_ARRAY_OF(type) \
|
||||
typedef struct { \
|
||||
type *items; \
|
||||
size_t size; \
|
||||
} type##Array
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
#define ArrayOf(...) Array
|
||||
#define DictionaryOf(...) Dictionary
|
||||
#endif
|
||||
|
||||
// Basic types
|
||||
typedef struct {
|
||||
@ -38,15 +36,6 @@ REMOTE_TYPE(Tabpage);
|
||||
|
||||
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 {
|
||||
Object *items;
|
||||
size_t size, capacity;
|
||||
@ -60,40 +49,30 @@ typedef struct {
|
||||
} Dictionary;
|
||||
|
||||
typedef enum {
|
||||
kObjectTypeBuffer,
|
||||
kObjectTypeWindow,
|
||||
kObjectTypeTabpage,
|
||||
kObjectTypeNil,
|
||||
kObjectTypeBoolean,
|
||||
kObjectTypeInteger,
|
||||
kObjectTypeFloat,
|
||||
kObjectTypeString,
|
||||
kObjectTypeBuffer,
|
||||
kObjectTypeWindow,
|
||||
kObjectTypeTabpage,
|
||||
kObjectTypeArray,
|
||||
kObjectTypeDictionary,
|
||||
kObjectTypePosition,
|
||||
kObjectTypeStringArray,
|
||||
kObjectTypeBufferArray,
|
||||
kObjectTypeWindowArray,
|
||||
kObjectTypeTabpageArray,
|
||||
} ObjectType;
|
||||
|
||||
struct object {
|
||||
ObjectType type;
|
||||
union {
|
||||
Buffer buffer;
|
||||
Window window;
|
||||
Tabpage tabpage;
|
||||
Boolean boolean;
|
||||
Integer integer;
|
||||
Float floating;
|
||||
String string;
|
||||
Buffer buffer;
|
||||
Window window;
|
||||
Tabpage tabpage;
|
||||
Array array;
|
||||
Dictionary dictionary;
|
||||
Position position;
|
||||
StringArray stringarray;
|
||||
BufferArray bufferarray;
|
||||
WindowArray windowarray;
|
||||
TabpageArray tabpagearray;
|
||||
} data;
|
||||
};
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/api/private/handle.h"
|
||||
#include "nvim/os/provider.h"
|
||||
#include "nvim/ascii.h"
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/buffer.h"
|
||||
@ -449,6 +450,130 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
|
||||
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
|
||||
/// to avoid infinite recursion due to cyclic references
|
||||
///
|
||||
|
@ -51,36 +51,11 @@
|
||||
.data.array = a \
|
||||
})
|
||||
|
||||
#define STRINGARRAY_OBJ(a) ((Object) { \
|
||||
.type = kObjectTypeStringArray, \
|
||||
.data.stringarray = a \
|
||||
})
|
||||
|
||||
#define BUFFERARRAY_OBJ(a) ((Object) { \
|
||||
.type = kObjectTypeBufferArray, \
|
||||
.data.bufferarray = a \
|
||||
})
|
||||
|
||||
#define WINDOWARRAY_OBJ(a) ((Object) { \
|
||||
.type = kObjectTypeWindowArray, \
|
||||
.data.windowarray = a \
|
||||
})
|
||||
|
||||
#define TABPAGEARRAY_OBJ(a) ((Object) { \
|
||||
.type = kObjectTypeTabpageArray, \
|
||||
.data.tabpagearray = a \
|
||||
})
|
||||
|
||||
#define DICTIONARY_OBJ(d) ((Object) { \
|
||||
.type = kObjectTypeDictionary, \
|
||||
.data.dictionary = d \
|
||||
})
|
||||
|
||||
#define POSITION_OBJ(p) ((Object) { \
|
||||
.type = kObjectTypePosition, \
|
||||
.data.position = p \
|
||||
})
|
||||
|
||||
#define NIL ((Object) {.type = kObjectTypeNil})
|
||||
|
||||
#define PUT(dict, k, v) \
|
||||
@ -91,6 +66,25 @@
|
||||
#define ADD(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
|
||||
# include "api/private/helpers.h.generated.h"
|
||||
#endif
|
||||
|
@ -13,9 +13,9 @@
|
||||
/// @param tabpage The tabpage
|
||||
/// @param[out] err Details of an error that may have occurred
|
||||
/// @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);
|
||||
|
||||
if (!tab) {
|
||||
@ -32,14 +32,14 @@ WindowArray tabpage_get_windows(Tabpage tabpage, Error *err)
|
||||
rv.size++;
|
||||
}
|
||||
|
||||
rv.items = xmalloc(sizeof(Window) * rv.size);
|
||||
rv.items = xmalloc(sizeof(Object) * rv.size);
|
||||
size_t i = 0;
|
||||
|
||||
FOR_ALL_TAB_WINDOWS(tp, wp) {
|
||||
if (tp != tab) {
|
||||
break;
|
||||
}
|
||||
rv.items[i++] = wp->handle;
|
||||
rv.items[i++] = WINDOW_OBJ(wp->handle);
|
||||
}
|
||||
|
||||
return rv;
|
||||
|
@ -149,9 +149,9 @@ Integer vim_strwidth(String str, Error *err)
|
||||
/// Returns a list of paths contained in 'runtimepath'
|
||||
///
|
||||
/// @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;
|
||||
|
||||
if (*rtp == NUL) {
|
||||
@ -168,19 +168,20 @@ StringArray vim_list_runtime_paths(void)
|
||||
}
|
||||
|
||||
// Allocate memory for the copies
|
||||
rv.items = xmalloc(sizeof(String) * rv.size);
|
||||
rv.items = xmalloc(sizeof(Object) * rv.size);
|
||||
// reset the position
|
||||
rtp = p_rtp;
|
||||
// Start copying
|
||||
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]
|
||||
int length = copy_option_part(&rtp,
|
||||
(char_u *)rv.items[i].data,
|
||||
(char_u *)rv.items[i].data.string.data,
|
||||
MAXPATHL,
|
||||
",");
|
||||
assert(length >= 0);
|
||||
rv.items[i].size = (size_t)length;
|
||||
rv.items[i].data.string.size = (size_t)length;
|
||||
}
|
||||
|
||||
return rv;
|
||||
@ -307,12 +308,22 @@ void vim_err_write(String str)
|
||||
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
|
||||
///
|
||||
/// @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;
|
||||
|
||||
while (b) {
|
||||
@ -320,12 +331,12 @@ BufferArray vim_get_buffers(void)
|
||||
b = b->b_next;
|
||||
}
|
||||
|
||||
rv.items = xmalloc(sizeof(Buffer) * rv.size);
|
||||
rv.items = xmalloc(sizeof(Object) * rv.size);
|
||||
size_t i = 0;
|
||||
b = firstbuf;
|
||||
|
||||
while (b) {
|
||||
rv.items[i++] = b->handle;
|
||||
rv.items[i++] = BUFFER_OBJ(b->handle);
|
||||
b = b->b_next;
|
||||
}
|
||||
|
||||
@ -370,9 +381,9 @@ void vim_set_current_buffer(Buffer buffer, Error *err)
|
||||
/// Gets the current list of window handles
|
||||
///
|
||||
/// @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;
|
||||
win_T *wp;
|
||||
|
||||
@ -380,11 +391,11 @@ WindowArray vim_get_windows(void)
|
||||
rv.size++;
|
||||
}
|
||||
|
||||
rv.items = xmalloc(sizeof(Window) * rv.size);
|
||||
rv.items = xmalloc(sizeof(Object) * rv.size);
|
||||
size_t i = 0;
|
||||
|
||||
FOR_ALL_TAB_WINDOWS(tp, wp) {
|
||||
rv.items[i++] = wp->handle;
|
||||
rv.items[i++] = WINDOW_OBJ(wp->handle);
|
||||
}
|
||||
|
||||
return rv;
|
||||
@ -426,9 +437,9 @@ void vim_set_current_window(Window window, Error *err)
|
||||
/// Gets the current list of tabpage handles
|
||||
///
|
||||
/// @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;
|
||||
|
||||
while (tp) {
|
||||
@ -436,12 +447,12 @@ TabpageArray vim_get_tabpages(void)
|
||||
tp = tp->tp_next;
|
||||
}
|
||||
|
||||
rv.items = xmalloc(sizeof(Tabpage) * rv.size);
|
||||
rv.items = xmalloc(sizeof(Object) * rv.size);
|
||||
size_t i = 0;
|
||||
tp = first_tabpage;
|
||||
|
||||
while (tp) {
|
||||
rv.items[i++] = tp->handle;
|
||||
rv.items[i++] = TABPAGE_OBJ(tp->handle);
|
||||
tp = tp->tp_next;
|
||||
}
|
||||
|
||||
@ -501,22 +512,33 @@ void vim_unsubscribe(uint64_t channel_id, String event)
|
||||
channel_unsubscribe(channel_id, e);
|
||||
}
|
||||
|
||||
/// Registers the channel as the provider for `method`. This fails if
|
||||
/// a provider for `method` is already registered.
|
||||
/// Registers the channel as the provider for `feature`. This fails if
|
||||
/// a provider for `feature` is already provided by another channel.
|
||||
///
|
||||
/// @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
|
||||
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];
|
||||
xstrlcpy(buf, method.data, sizeof(buf));
|
||||
xstrlcpy(buf, feature.data, sizeof(buf));
|
||||
|
||||
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
|
||||
/// and flushed after each newline. Incomplete lines are kept for writing
|
||||
/// later.
|
||||
|
@ -33,14 +33,14 @@ Buffer window_get_buffer(Window window, Error *err)
|
||||
/// @param window The window handle
|
||||
/// @param[out] err Details of an error that may have occurred
|
||||
/// @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);
|
||||
|
||||
if (win) {
|
||||
rv.row = win->w_cursor.lnum;
|
||||
rv.col = win->w_cursor.col;
|
||||
ADD(rv, INTEGER_OBJ(win->w_cursor.lnum));
|
||||
ADD(rv, INTEGER_OBJ(win->w_cursor.col));
|
||||
}
|
||||
|
||||
return rv;
|
||||
@ -51,31 +51,35 @@ Position window_get_cursor(Window window, Error *err)
|
||||
/// @param window The window handle
|
||||
/// @param pos the (row, col) tuple representing the new position
|
||||
/// @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);
|
||||
|
||||
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) {
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pos.row > LONG_MAX || pos.row < LONG_MIN) {
|
||||
set_api_error("Row value outside range", err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pos.col > INT_MAX || pos.col < INT_MIN) {
|
||||
if (col > MAXCOL || col < 0) {
|
||||
set_api_error("Column value outside range", err);
|
||||
return;
|
||||
}
|
||||
|
||||
win->w_cursor.lnum = (linenr_T)pos.row;
|
||||
win->w_cursor.col = (colnr_T)pos.col;
|
||||
win->w_cursor.lnum = (linenr_T)row;
|
||||
win->w_cursor.col = (colnr_T)col;
|
||||
win->w_cursor.coladd = 0;
|
||||
// When column is out of range silently correct it.
|
||||
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[out] err Details of an error that may have occurred
|
||||
/// @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);
|
||||
|
||||
if (win) {
|
||||
rv.col = win->w_wincol;
|
||||
rv.row = win->w_winrow;
|
||||
ADD(rv, INTEGER_OBJ(win->w_winrow));
|
||||
ADD(rv, INTEGER_OBJ(win->w_wincol));
|
||||
}
|
||||
|
||||
return rv;
|
||||
|
@ -84,7 +84,6 @@
|
||||
#include "nvim/os/channel.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/api/vim.h"
|
||||
#include "nvim/os/msgpack_rpc_helpers.h"
|
||||
#include "nvim/os/dl.h"
|
||||
#include "nvim/os/provider.h"
|
||||
|
||||
@ -6307,6 +6306,8 @@ static struct fst {
|
||||
{"acos", 1, 1, f_acos}, /* WJMc */
|
||||
{"add", 2, 2, f_add},
|
||||
{"and", 2, 2, f_and},
|
||||
{"api_close", 1, 1, f_api_close},
|
||||
{"api_spawn", 1, 2, f_api_spawn},
|
||||
{"append", 2, 2, f_append},
|
||||
{"argc", 0, 0, f_argc},
|
||||
{"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);
|
||||
}
|
||||
|
||||
// "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
|
||||
*/
|
||||
@ -12718,14 +12796,18 @@ static void f_send_call(typval_T *argvars, typval_T *rettv)
|
||||
return;
|
||||
}
|
||||
|
||||
if (errored) {
|
||||
vim_report_error(result.data.string);
|
||||
goto end;
|
||||
}
|
||||
|
||||
Error conversion_error = {.set = false};
|
||||
if (errored || !object_to_vim(result, rettv, &conversion_error)) {
|
||||
EMSG(errored ?
|
||||
result.data.string.data :
|
||||
_("Error converting the call result"));
|
||||
if (!object_to_vim(result, rettv, &conversion_error)) {
|
||||
EMSG(_("Error converting the call result"));
|
||||
}
|
||||
|
||||
msgpack_rpc_free_object(result);
|
||||
end:
|
||||
api_free_object(result);
|
||||
}
|
||||
|
||||
// "send_event()" function
|
||||
@ -19239,7 +19321,7 @@ static void script_host_eval(char *method, typval_T *argvars, typval_T *rettv)
|
||||
|
||||
Error err = {.set = false};
|
||||
object_to_vim(result, rettv, &err);
|
||||
msgpack_rpc_free_object(result);
|
||||
api_free_object(result);
|
||||
|
||||
if (err.set) {
|
||||
EMSG("Error converting value back to vim");
|
||||
|
@ -55,7 +55,6 @@
|
||||
#include "nvim/os/shell.h"
|
||||
#include "nvim/os/fs_defs.h"
|
||||
#include "nvim/os/provider.h"
|
||||
#include "nvim/os/msgpack_rpc_helpers.h"
|
||||
#include "nvim/api/private/helpers.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);
|
||||
// We don't care about the result, so free it just in case a bad provider
|
||||
// returned something
|
||||
msgpack_rpc_free_object(result);
|
||||
api_free_object(result);
|
||||
}
|
||||
|
||||
free(script);
|
||||
@ -3278,7 +3277,7 @@ static void script_host_execute_file(char *method, exarg_T *eap)
|
||||
Array args = ARRAY_DICT_INIT;
|
||||
ADD(args, STRING_OBJ(cstr_to_string(buffer)));
|
||||
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)
|
||||
@ -3288,6 +3287,6 @@ static void script_host_do_range(char *method, exarg_T *eap)
|
||||
ADD(args, INTEGER_OBJ(eap->line2));
|
||||
ADD(args, STRING_OBJ(cstr_to_string((char *)eap->arg)));
|
||||
Object result = provider_call(method, args);
|
||||
msgpack_rpc_free_object(result);
|
||||
api_free_object(result);
|
||||
}
|
||||
|
||||
|
@ -1098,6 +1098,7 @@ EXTERN garray_T error_ga
|
||||
* 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_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_backslash[] INIT(= N_("E10: \\ should be followed by /, ? or &"));
|
||||
EXTERN char_u e_cmdwin[] INIT(= N_(
|
||||
|
@ -12,6 +12,8 @@
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <msgpack.h>
|
||||
|
||||
#include "nvim/ascii.h"
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/main.h"
|
||||
@ -57,6 +59,9 @@
|
||||
#include "nvim/os/input.h"
|
||||
#include "nvim/os/os.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. */
|
||||
#define MAX_ARG_CMDS 10
|
||||
@ -116,9 +121,6 @@ static void init_locale(void);
|
||||
# endif
|
||||
#endif /* NO_VIM_MAIN */
|
||||
|
||||
extern const uint8_t msgpack_metadata[];
|
||||
extern const unsigned int msgpack_metadata_size;
|
||||
|
||||
/*
|
||||
* Different types of error messages.
|
||||
*/
|
||||
@ -1026,12 +1028,18 @@ static void command_line_scan(mparm_T *parmp)
|
||||
msg_putchar('\n');
|
||||
msg_didout = FALSE;
|
||||
mch_exit(0);
|
||||
} else if (STRICMP(argv[0] + argv_idx, "api-msgpack-metadata") == 0) {
|
||||
for (unsigned int i = 0; i<msgpack_metadata_size; i++) {
|
||||
putchar(msgpack_metadata[i]);
|
||||
} else if (STRICMP(argv[0] + argv_idx, "api-info") == 0) {
|
||||
msgpack_sbuffer* b = msgpack_sbuffer_new();
|
||||
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);
|
||||
} else if (STRICMP(argv[0] + argv_idx, "embedded-mode") == 0) {
|
||||
} else if (STRICMP(argv[0] + argv_idx, "embed") == 0) {
|
||||
embedded_mode = true;
|
||||
} else if (STRNICMP(argv[0] + argv_idx, "literal", 7) == 0) {
|
||||
#if !defined(UNIX)
|
||||
@ -2212,8 +2220,8 @@ static void usage(void)
|
||||
main_msg(_("-W <scriptout>\tWrite all typed commands to file <scriptout>"));
|
||||
main_msg(_("--startuptime <file>\tWrite startup timing messages to <file>"));
|
||||
main_msg(_("-i <viminfo>\t\tUse <viminfo> instead of .viminfo"));
|
||||
main_msg(_("--api-msgpack-metadata\tDump API metadata information and exit"));
|
||||
main_msg(_("--embedded-mode\tUse stdin/stdout as a msgpack-rpc channel. "
|
||||
main_msg(_("--api-info\t\tDump API metadata serialized to msgpack and exit"));
|
||||
main_msg(_("--embed\t\tUse stdin/stdout as a msgpack-rpc channel. "
|
||||
"This can be used for embedding Neovim into other programs"));
|
||||
main_msg(_("-h or --help\tPrint Help (this message) and exit"));
|
||||
main_msg(_("--version\t\tPrint version information and exit"));
|
||||
|
@ -1,10 +1,12 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "nvim/map.h"
|
||||
#include "nvim/map_defs.h"
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/memory.h"
|
||||
#include "nvim/os/msgpack_rpc.h"
|
||||
|
||||
#include "nvim/lib/khash.h"
|
||||
|
||||
@ -87,7 +89,23 @@
|
||||
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, ptr_t, DEFAULT_INITIALIZER)
|
||||
MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER)
|
||||
MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER)
|
||||
MAP_IMPL(String, rpc_method_handler_fn, DEFAULT_INITIALIZER)
|
||||
|
@ -4,6 +4,8 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "nvim/map_defs.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/os/msgpack_rpc.h"
|
||||
|
||||
#define MAP_DECLS(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(ptr_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_free(T, U) map_##T##_##U##_free
|
||||
|
@ -121,7 +121,8 @@ void *xmalloc(size_t size)
|
||||
void *ret = try_malloc(size);
|
||||
|
||||
if (!ret) {
|
||||
OUT_STR("Vim: Error: Out of memory.\n");
|
||||
OUT_STR(e_outofmem);
|
||||
out_char('\n');
|
||||
preserve_exit();
|
||||
}
|
||||
return ret;
|
||||
@ -147,7 +148,8 @@ void *xcalloc(size_t count, size_t size)
|
||||
if (!ret && (!count || !size))
|
||||
ret = calloc(1, 1);
|
||||
if (!ret) {
|
||||
OUT_STR("Vim: Error: Out of memory.\n");
|
||||
OUT_STR(e_outofmem);
|
||||
out_char('\n');
|
||||
preserve_exit();
|
||||
}
|
||||
}
|
||||
@ -174,7 +176,8 @@ void *xrealloc(void *ptr, size_t size)
|
||||
if (!ret && !size)
|
||||
ret = realloc(ptr, 1);
|
||||
if (!ret) {
|
||||
OUT_STR("Vim: Error: Out of memory.\n");
|
||||
OUT_STR(e_outofmem);
|
||||
out_char('\n');
|
||||
preserve_exit();
|
||||
}
|
||||
}
|
||||
@ -194,7 +197,7 @@ void *xmallocz(size_t size)
|
||||
void *ret;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@ -316,7 +319,8 @@ char *xstrdup(const char *str)
|
||||
try_to_free_memory();
|
||||
ret = strdup(str);
|
||||
if (!ret) {
|
||||
OUT_STR("Vim: Error: Out of memory.\n");
|
||||
OUT_STR(e_outofmem);
|
||||
out_char('\n');
|
||||
preserve_exit();
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,6 @@
|
||||
#include "nvim/undo.h"
|
||||
#include "nvim/window.h"
|
||||
#include "nvim/os/provider.h"
|
||||
#include "nvim/os/msgpack_rpc_helpers.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
|
||||
/*
|
||||
@ -5256,7 +5255,7 @@ static void get_clipboard(int name)
|
||||
return;
|
||||
|
||||
err:
|
||||
msgpack_rpc_free_object(result);
|
||||
api_free_object(result);
|
||||
free(reg->y_array);
|
||||
reg->y_array = NULL;
|
||||
reg->y_size = 0;
|
||||
@ -5287,5 +5286,5 @@ static void set_clipboard(int name)
|
||||
ADD(args, ARRAY_OBJ(lines));
|
||||
|
||||
Object result = provider_call("clipboard_set", args);
|
||||
msgpack_rpc_free_object(result);
|
||||
api_free_object(result);
|
||||
}
|
||||
|
@ -974,12 +974,6 @@ static struct vimoption
|
||||
{"infercase", "inf", P_BOOL|P_VI_DEF,
|
||||
(char_u *)&p_inf, PV_INF,
|
||||
{(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,
|
||||
(char_u *)&p_im, PV_NONE,
|
||||
{(char_u *)FALSE, (char_u *)0L} SCRIPTID_INIT},
|
||||
|
@ -631,8 +631,6 @@ EXTERN int p_write; /* 'write' */
|
||||
EXTERN int p_wa; /* 'writeany' */
|
||||
EXTERN int p_wb; /* 'writebackup' */
|
||||
EXTERN long p_wd; /* 'writedelay' */
|
||||
EXTERN char *p_ipy; // 'initpython'
|
||||
EXTERN char *p_icpb; // 'initclipboard'
|
||||
|
||||
/*
|
||||
* "indir" values for buffer-local opions.
|
||||
|
@ -22,8 +22,10 @@
|
||||
#include "nvim/memory.h"
|
||||
#include "nvim/os_unix.h"
|
||||
#include "nvim/message.h"
|
||||
#include "nvim/term.h"
|
||||
#include "nvim/map.h"
|
||||
#include "nvim/log.h"
|
||||
#include "nvim/misc1.h"
|
||||
#include "nvim/lib/kvec.h"
|
||||
|
||||
#define CHANNEL_BUFFER_SIZE 0xffff
|
||||
@ -156,7 +158,7 @@ bool channel_send_event(uint64_t id, char *name, Array args)
|
||||
|
||||
if (id > 0) {
|
||||
if (!(channel = pmap_get(uint64_t)(channels, id)) || !channel->enabled) {
|
||||
msgpack_rpc_free_array(args);
|
||||
api_free_array(args);
|
||||
return false;
|
||||
}
|
||||
send_event(channel, name, args);
|
||||
@ -184,7 +186,7 @@ bool channel_send_call(uint64_t id,
|
||||
Channel *channel = NULL;
|
||||
|
||||
if (!(channel = pmap_get(uint64_t)(channels, id)) || !channel->enabled) {
|
||||
msgpack_rpc_free_array(args);
|
||||
api_free_array(args);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -197,7 +199,7 @@ bool channel_send_call(uint64_t id,
|
||||
"Channel %" PRIu64 " crossed maximum stack depth",
|
||||
channel->id);
|
||||
*result = STRING_OBJ(cstr_to_string(buf));
|
||||
msgpack_rpc_free_array(args);
|
||||
api_free_array(args);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -265,6 +267,23 @@ void channel_unsubscribe(uint64_t id, char *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
|
||||
/// Neovim
|
||||
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_init(&unpacked);
|
||||
UnpackResult result;
|
||||
msgpack_unpack_return result;
|
||||
|
||||
// Deserialize everything we can.
|
||||
while ((result = msgpack_rpc_unpack(channel->unpacker, &unpacked))
|
||||
== kUnpackResultOk) {
|
||||
while ((result = msgpack_unpacker_next(channel->unpacker, &unpacked))) {
|
||||
if (kv_size(channel->call_stack) && is_rpc_response(&unpacked.data)) {
|
||||
if (is_valid_rpc_response(&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
|
||||
// causes for this error(search for 'goto _failed')
|
||||
//
|
||||
@ -441,7 +465,7 @@ static void broadcast_event(char *name, Array args)
|
||||
});
|
||||
|
||||
if (!kv_size(subscribed)) {
|
||||
msgpack_rpc_free_array(args);
|
||||
api_free_array(args);
|
||||
goto end;
|
||||
}
|
||||
|
||||
@ -489,7 +513,13 @@ static void close_channel(Channel *channel)
|
||||
|
||||
pmap_free(cstr_t)(channel->subscribed_events);
|
||||
kv_destroy(channel->call_stack);
|
||||
channel_kill(channel);
|
||||
|
||||
free(channel);
|
||||
}
|
||||
|
||||
static void channel_kill(Channel *channel)
|
||||
{
|
||||
if (channel->is_job) {
|
||||
if (channel->data.job) {
|
||||
job_stop(channel->data.job);
|
||||
@ -504,8 +534,6 @@ static void close_channel(Channel *channel)
|
||||
mch_exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
free(channel);
|
||||
}
|
||||
|
||||
static void close_cb(uv_handle_t *handle)
|
||||
|
@ -197,6 +197,12 @@ Job *job_start(char **argv,
|
||||
job->stdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
|
||||
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
|
||||
if (uv_spawn(uv_default_loop(), &job->proc, &job->proc_opts) != 0) {
|
||||
free_job(job);
|
||||
@ -204,12 +210,6 @@ Job *job_start(char **argv,
|
||||
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);
|
||||
wstream_set_stream(job->in, (uv_stream_t *)&job->proc_stdin);
|
||||
// Start the readable streams
|
||||
|
@ -17,9 +17,6 @@
|
||||
# include "os/msgpack_rpc.c.generated.h"
|
||||
#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`
|
||||
/// 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);
|
||||
}
|
||||
|
||||
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
|
||||
Error error = { .set = false };
|
||||
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);
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// @param msg The error message
|
||||
@ -109,12 +65,22 @@ void msgpack_rpc_error(char *msg, msgpack_packer *res)
|
||||
size_t len = strlen(msg);
|
||||
|
||||
// error message
|
||||
msgpack_pack_raw(res, len);
|
||||
msgpack_pack_raw_body(res, msg, len);
|
||||
msgpack_pack_bin(res, len);
|
||||
msgpack_pack_bin_body(res, msg, len);
|
||||
// Nil result
|
||||
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)
|
||||
WBuffer *serialize_request(uint64_t request_id,
|
||||
String method,
|
||||
@ -132,14 +98,14 @@ WBuffer *serialize_request(uint64_t request_id,
|
||||
msgpack_pack_uint64(&pac, request_id);
|
||||
}
|
||||
|
||||
msgpack_pack_raw(&pac, method.size);
|
||||
msgpack_pack_raw_body(&pac, method.data, method.size);
|
||||
msgpack_pack_bin(&pac, method.size);
|
||||
msgpack_pack_bin_body(&pac, method.data, method.size);
|
||||
msgpack_rpc_from_array(args, &pac);
|
||||
WBuffer *rv = wstream_new_buffer(xmemdup(sbuffer->data, sbuffer->size),
|
||||
sbuffer->size,
|
||||
refcount,
|
||||
free);
|
||||
msgpack_rpc_free_array(args);
|
||||
api_free_array(args);
|
||||
msgpack_sbuffer_clear(sbuffer);
|
||||
return rv;
|
||||
}
|
||||
@ -160,8 +126,8 @@ WBuffer *serialize_response(uint64_t response_id,
|
||||
if (err_msg) {
|
||||
String err = {.size = strlen(err_msg), .data = err_msg};
|
||||
// error message
|
||||
msgpack_pack_raw(&pac, err.size);
|
||||
msgpack_pack_raw_body(&pac, err.data, err.size);
|
||||
msgpack_pack_bin(&pac, err.size);
|
||||
msgpack_pack_bin_body(&pac, err.data, err.size);
|
||||
// Nil result
|
||||
msgpack_pack_nil(&pac);
|
||||
} else {
|
||||
@ -175,32 +141,7 @@ WBuffer *serialize_response(uint64_t response_id,
|
||||
sbuffer->size,
|
||||
1, // responses only go though 1 channel
|
||||
free);
|
||||
msgpack_rpc_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);
|
||||
api_free_object(arg);
|
||||
msgpack_sbuffer_clear(sbuffer);
|
||||
return rv;
|
||||
}
|
||||
@ -234,9 +175,9 @@ static char *msgpack_rpc_validate(uint64_t *response_id, msgpack_object *req)
|
||||
return "Message type must be 0";
|
||||
}
|
||||
|
||||
if (req->via.array.ptr[2].type != MSGPACK_OBJECT_POSITIVE_INTEGER
|
||||
&& req->via.array.ptr[2].type != MSGPACK_OBJECT_RAW) {
|
||||
return "Method must be a positive integer or a string";
|
||||
if (req->via.array.ptr[2].type != MSGPACK_OBJECT_BIN
|
||||
&& req->via.array.ptr[2].type != MSGPACK_OBJECT_STR) {
|
||||
return "Method must be a string";
|
||||
}
|
||||
|
||||
if (req->via.array.ptr[3].type != MSGPACK_OBJECT_ARRAY) {
|
||||
|
@ -25,6 +25,7 @@ typedef Object (*rpc_method_handler_fn)(uint64_t channel_id,
|
||||
/// Initializes the msgpack-rpc method table
|
||||
void msgpack_rpc_init(void);
|
||||
|
||||
void msgpack_rpc_init_function_metadata(Dictionary *metadata);
|
||||
|
||||
/// Dispatches to the actual API function after basic payload validation by
|
||||
/// `msgpack_rpc_call`. It is responsible for validating/converting arguments
|
||||
|
@ -7,61 +7,67 @@
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/memory.h"
|
||||
|
||||
#define REMOTE_FUNCS_IMPL(t, lt) \
|
||||
bool msgpack_rpc_to_##lt(msgpack_object *obj, t *arg) \
|
||||
{ \
|
||||
*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); \
|
||||
}
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "os/msgpack_rpc_helpers.c.generated.h"
|
||||
#endif
|
||||
|
||||
#define TYPED_ARRAY_IMPL(t, lt) \
|
||||
bool msgpack_rpc_to_##lt##array(msgpack_object *obj, t##Array *arg) \
|
||||
static msgpack_zone zone;
|
||||
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; \
|
||||
} \
|
||||
\
|
||||
arg->size = obj->via.array.size; \
|
||||
arg->items = xcalloc(obj->via.array.size, sizeof(t)); \
|
||||
msgpack_object data; \
|
||||
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 (!msgpack_rpc_to_##lt(obj->via.array.ptr + i, &arg->items[i])) { \
|
||||
return false; \
|
||||
} \
|
||||
if (ret != MSGPACK_UNPACK_SUCCESS) { \
|
||||
return false; \
|
||||
} \
|
||||
\
|
||||
*arg = data.via.u64; \
|
||||
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); \
|
||||
\
|
||||
for (size_t i = 0; i < result.size; i++) { \
|
||||
msgpack_rpc_from_##lt(result.items[i], res); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
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); \
|
||||
msgpack_packer pac; \
|
||||
msgpack_packer_init(&pac, &sbuffer, msgpack_sbuffer_write); \
|
||||
msgpack_pack_uint64(&pac, o); \
|
||||
msgpack_pack_ext(res, sbuffer.size, kObjectType##t); \
|
||||
msgpack_pack_ext_body(res, sbuffer.data, sbuffer.size); \
|
||||
msgpack_sbuffer_clear(&sbuffer); \
|
||||
}
|
||||
|
||||
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)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
*arg = obj->via.boolean;
|
||||
return obj->type == MSGPACK_OBJECT_BOOLEAN;
|
||||
}
|
||||
|
||||
bool msgpack_rpc_to_integer(msgpack_object *obj, Integer *arg)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
if (obj->type == MSGPACK_OBJECT_POSITIVE_INTEGER
|
||||
&& 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)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
*arg = obj->via.dec;
|
||||
return obj->type == MSGPACK_OBJECT_DOUBLE;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
arg->data = xmemdupz(obj->via.raw.ptr, obj->via.raw.size);
|
||||
arg->size = obj->via.raw.size;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool msgpack_rpc_to_object(msgpack_object *obj, Object *arg)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
switch (obj->type) {
|
||||
case MSGPACK_OBJECT_NIL:
|
||||
@ -110,7 +120,8 @@ bool msgpack_rpc_to_object(msgpack_object *obj, Object *arg)
|
||||
arg->type = kObjectTypeFloat;
|
||||
return msgpack_rpc_to_float(obj, &arg->data.floating);
|
||||
|
||||
case MSGPACK_OBJECT_RAW:
|
||||
case MSGPACK_OBJECT_BIN:
|
||||
case MSGPACK_OBJECT_STR:
|
||||
arg->type = kObjectTypeString;
|
||||
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;
|
||||
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:
|
||||
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)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
if (obj->type != MSGPACK_OBJECT_ARRAY) {
|
||||
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)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
if (obj->type != MSGPACK_OBJECT_MAP) {
|
||||
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)
|
||||
FUNC_ATTR_NONNULL_ARG(2)
|
||||
{
|
||||
if (result) {
|
||||
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)
|
||||
FUNC_ATTR_NONNULL_ARG(2)
|
||||
{
|
||||
msgpack_pack_int64(res, result);
|
||||
}
|
||||
|
||||
void msgpack_rpc_from_float(Float result, msgpack_packer *res)
|
||||
FUNC_ATTR_NONNULL_ARG(2)
|
||||
{
|
||||
msgpack_pack_double(res, result);
|
||||
}
|
||||
|
||||
void msgpack_rpc_from_string(String result, msgpack_packer *res)
|
||||
FUNC_ATTR_NONNULL_ARG(2)
|
||||
{
|
||||
msgpack_pack_raw(res, result.size);
|
||||
msgpack_pack_raw_body(res, result.data, result.size);
|
||||
msgpack_pack_bin(res, result.size);
|
||||
msgpack_pack_bin_body(res, result.data, result.size);
|
||||
}
|
||||
|
||||
void msgpack_rpc_from_object(Object result, msgpack_packer *res)
|
||||
FUNC_ATTR_NONNULL_ARG(2)
|
||||
{
|
||||
switch (result.type) {
|
||||
case kObjectTypeNil:
|
||||
@ -231,10 +249,6 @@ void msgpack_rpc_from_object(Object result, msgpack_packer *res)
|
||||
msgpack_rpc_from_array(result.data.array, res);
|
||||
break;
|
||||
|
||||
case kObjectTypePosition:
|
||||
msgpack_rpc_from_position(result.data.position, res);
|
||||
break;
|
||||
|
||||
case kObjectTypeBuffer:
|
||||
msgpack_rpc_from_buffer(result.data.buffer, res);
|
||||
break;
|
||||
@ -247,36 +261,14 @@ void msgpack_rpc_from_object(Object result, msgpack_packer *res)
|
||||
msgpack_rpc_from_tabpage(result.data.tabpage, res);
|
||||
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:
|
||||
msgpack_rpc_from_dictionary(result.data.dictionary, res);
|
||||
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)
|
||||
FUNC_ATTR_NONNULL_ARG(2)
|
||||
{
|
||||
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)
|
||||
FUNC_ATTR_NONNULL_ARG(2)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
|
@ -6,119 +6,11 @@
|
||||
|
||||
#include <msgpack.h>
|
||||
|
||||
#include "nvim/func_attr.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
|
||||
/// Functions for validating and converting from msgpack types to C types.
|
||||
/// These are used by `msgpack_rpc_dispatch` to validate and convert each
|
||||
/// argument.
|
||||
///
|
||||
/// @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);
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "os/msgpack_rpc_helpers.h.generated.h"
|
||||
#endif
|
||||
|
||||
#endif // NVIM_OS_MSGPACK_RPC_HELPERS_H
|
||||
|
||||
|
@ -14,37 +14,34 @@
|
||||
#include "nvim/log.h"
|
||||
#include "nvim/map.h"
|
||||
#include "nvim/message.h"
|
||||
#include "nvim/os/msgpack_rpc_helpers.h"
|
||||
|
||||
#define FEATURE_COUNT (sizeof(features) / sizeof(features[0]))
|
||||
|
||||
#define FEATURE(feature_name, provider_bootstrap_command, ...) { \
|
||||
#define FEATURE(feature_name, ...) { \
|
||||
.name = feature_name, \
|
||||
.bootstrap_command = provider_bootstrap_command, \
|
||||
.argv = NULL, \
|
||||
.channel_id = 0, \
|
||||
.methods = (char *[]){__VA_ARGS__, NULL} \
|
||||
}
|
||||
|
||||
static struct feature {
|
||||
char *name, **bootstrap_command, **argv, **methods;
|
||||
typedef struct {
|
||||
char *name, **methods;
|
||||
size_t name_length;
|
||||
uint64_t channel_id;
|
||||
} features[] = {
|
||||
} Feature;
|
||||
|
||||
static Feature features[] = {
|
||||
FEATURE("python",
|
||||
&p_ipy,
|
||||
"python_execute",
|
||||
"python_execute_file",
|
||||
"python_do_range",
|
||||
"python_eval"),
|
||||
|
||||
FEATURE("clipboard",
|
||||
&p_icpb,
|
||||
"clipboard_get",
|
||||
"clipboard_set")
|
||||
};
|
||||
|
||||
static Map(cstr_t, uint64_t) *registered_providers = NULL;
|
||||
static PMap(cstr_t) *registered_providers = NULL;
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "os/provider.c.generated.h"
|
||||
@ -53,163 +50,101 @@ static Map(cstr_t, uint64_t) *registered_providers = NULL;
|
||||
|
||||
void provider_init(void)
|
||||
{
|
||||
registered_providers = map_new(cstr_t, uint64_t)();
|
||||
registered_providers = pmap_new(cstr_t)();
|
||||
}
|
||||
|
||||
bool provider_has_feature(char *name)
|
||||
{
|
||||
for (size_t i = 0; i < FEATURE_COUNT; i++) {
|
||||
struct feature *f = &features[i];
|
||||
if (!STRICMP(name, f->name)) {
|
||||
return f->channel_id || can_execute(f);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
Feature *f = find_feature(name);
|
||||
return f != NULL && channel_exists(f->channel_id);
|
||||
}
|
||||
|
||||
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 (map_has(cstr_t, uint64_t)(registered_providers, method)) {
|
||||
if (!f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// First check if this method is part of a feature, and if so, update
|
||||
// the feature structure with the channel id
|
||||
struct feature *f = get_feature_for(method);
|
||||
if (f) {
|
||||
DLOG("Registering provider for \"%s\" "
|
||||
"which is part of the \"%s\" feature",
|
||||
method,
|
||||
f->name);
|
||||
f->channel_id = channel_id;
|
||||
if (f->channel_id && channel_exists(f->channel_id)) {
|
||||
ILOG("Feature \"%s\" is already provided by another channel"
|
||||
"(will be replaced)", name);
|
||||
}
|
||||
|
||||
map_put(cstr_t, uint64_t)(registered_providers, xstrdup(method), channel_id);
|
||||
ILOG("Registered channel %" PRIu64 " as the provider for \"%s\"",
|
||||
DLOG("Registering provider for \"%s\"", name);
|
||||
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,
|
||||
method);
|
||||
name);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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];
|
||||
snprintf(buf,
|
||||
sizeof(buf),
|
||||
"Provider for \"%s\" is not available",
|
||||
"Provider for method \"%s\" is not available",
|
||||
method);
|
||||
report_error(buf);
|
||||
msgpack_rpc_free_array(args);
|
||||
vim_report_error(cstr_as_string(buf));
|
||||
api_free_array(args);
|
||||
return NIL;
|
||||
}
|
||||
|
||||
bool error = false;
|
||||
Object result = NIL;
|
||||
channel_send_call(channel_id, method, args, &result, &error);
|
||||
channel_send_call(f->channel_id, method, args, &result, &error);
|
||||
|
||||
if (error) {
|
||||
report_error(result.data.string.data);
|
||||
msgpack_rpc_free_object(result);
|
||||
vim_report_error(result.data.string);
|
||||
api_free_object(result);
|
||||
return NIL;
|
||||
}
|
||||
|
||||
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) {
|
||||
return channel_id;
|
||||
}
|
||||
for (size_t i = 0; i < FEATURE_COUNT; i++) {
|
||||
Array methods = ARRAY_DICT_INIT;
|
||||
Feature *f = &features[i];
|
||||
|
||||
// Try to bootstrap if the method is part of a feature
|
||||
struct feature *f = get_feature_for(method);
|
||||
|
||||
if (!f || !can_execute(f)) {
|
||||
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;
|
||||
size_t j;
|
||||
char *method;
|
||||
for (method = f->methods[j = 0]; method; method = f->methods[++j]) {
|
||||
ADD(methods, STRING_OBJ(cstr_to_string(method)));
|
||||
}
|
||||
|
||||
PUT(md, f->name, ARRAY_OBJ(methods));
|
||||
}
|
||||
|
||||
return false;
|
||||
PUT(*metadata, "features", DICTIONARY_OBJ(md));
|
||||
}
|
||||
|
||||
|
||||
static struct feature *get_feature_for(char *method)
|
||||
static Feature * find_feature(char *name)
|
||||
{
|
||||
for (size_t i = 0; i < FEATURE_COUNT; i++) {
|
||||
struct feature *f = &features[i];
|
||||
if (feature_has_method(f, method)) {
|
||||
Feature *f = &features[i];
|
||||
if (!STRICMP(name, f->name)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
@ -72,12 +72,12 @@ WStream * wstream_new(size_t maxmem)
|
||||
/// @param wstream The `WStream` instance
|
||||
void wstream_free(WStream *wstream) {
|
||||
if (!wstream->pending_reqs) {
|
||||
handle_set_wstream((uv_handle_t *)wstream->stream, NULL);
|
||||
if (wstream->free_handle) {
|
||||
uv_close((uv_handle_t *)wstream->stream, close_cb);
|
||||
} else {
|
||||
handle_set_wstream((uv_handle_t *)wstream->stream, NULL);
|
||||
free(wstream);
|
||||
}
|
||||
|
||||
free(wstream);
|
||||
} else {
|
||||
wstream->freed = true;
|
||||
}
|
||||
@ -238,12 +238,7 @@ static void release_wbuffer(WBuffer *buffer)
|
||||
|
||||
static void close_cb(uv_handle_t *handle)
|
||||
{
|
||||
WStream *wstream = handle_get_wstream(handle);
|
||||
|
||||
if (wstream) {
|
||||
free(wstream);
|
||||
}
|
||||
|
||||
free(handle_get_wstream(handle));
|
||||
free(handle->data);
|
||||
free(handle);
|
||||
}
|
||||
|
@ -55,6 +55,7 @@
|
||||
#include "nvim/os/signal.h"
|
||||
#include "nvim/os/job.h"
|
||||
#include "nvim/os/msgpack_rpc.h"
|
||||
#include "nvim/os/msgpack_rpc_helpers.h"
|
||||
|
||||
#if defined(HAVE_SYS_IOCTL_H)
|
||||
# include <sys/ioctl.h>
|
||||
@ -166,6 +167,7 @@ void mch_init(void)
|
||||
#endif
|
||||
|
||||
msgpack_rpc_init();
|
||||
msgpack_rpc_helpers_init();
|
||||
event_init();
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,8 @@
|
||||
#ifndef NVIM_VIM_H
|
||||
# define NVIM_VIM_H
|
||||
|
||||
#define min(X, Y) (X < Y ? X : Y)
|
||||
|
||||
#include "nvim/types.h"
|
||||
#include "nvim/pos.h" // for linenr_T, MAXCOL, etc...
|
||||
|
||||
|
19
third-party/CMakeLists.txt
vendored
19
third-party/CMakeLists.txt
vendored
@ -50,8 +50,8 @@ include(ExternalProject)
|
||||
set(LIBUV_URL https://github.com/joyent/libuv/archive/v0.11.28.tar.gz)
|
||||
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_MD5 ea0bee0939d2980c0df91f0e4843ccc4)
|
||||
set(MSGPACK_URL https://github.com/msgpack/msgpack-c/archive/0335df55e1a408c0d56d43e46253c952fb8a7f04.tar.gz)
|
||||
set(MSGPACK_MD5 4c18a1625b586c0d69a0e955ce9a187f)
|
||||
|
||||
set(LUAJIT_URL http://luajit.org/download/LuaJIT-2.0.3.tar.gz)
|
||||
set(LUAJIT_MD5 f14e9104be513913810cd59c8c658dc0)
|
||||
@ -92,9 +92,18 @@ if(USE_BUNDLED_MSGPACK)
|
||||
-DEXPECTED_MD5=${MSGPACK_MD5}
|
||||
-DTARGET=msgpack
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake
|
||||
CONFIGURE_COMMAND ${DEPS_BUILD_DIR}/src/msgpack/configure --disable-shared
|
||||
--with-pic --prefix=${DEPS_INSTALL_DIR} CC=${DEPS_C_COMPILER}
|
||||
INSTALL_COMMAND ${MAKE_PRG} install)
|
||||
CONFIGURE_COMMAND cmake ${DEPS_BUILD_DIR}/src/msgpack
|
||||
-DMSGPACK_ENABLE_CXX=OFF
|
||||
-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)
|
||||
endif()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user