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

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

View File

@ -23,7 +23,7 @@ fi
valgrind_check "$tmpdir"
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

View File

@ -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

View File

@ -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}

View File

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

View File

@ -59,9 +59,16 @@ local right_word = concat(
raw_word,
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')

View File

@ -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()

View File

@ -51,10 +51,10 @@ Integer buffer_get_length(Buffer buffer, Error *err)
String buffer_get_line(Buffer buffer, Integer index, Error *err)
{
String 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;
}

View File

@ -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;
};

View File

@ -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
///

View File

@ -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

View File

@ -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;

View File

@ -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.

View File

@ -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;

View File

@ -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");

View File

@ -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);
}

View File

@ -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_(

View File

@ -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"));

View File

@ -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)

View File

@ -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

View File

@ -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();
}
}

View File

@ -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);
}

View File

@ -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},

View File

@ -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.

View File

@ -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)

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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...

View File

@ -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()