diff options
34 files changed, 751 insertions, 746 deletions
diff --git a/.ci/api-python.sh b/.ci/api-python.sh index a3e9b652a6..acdbcc2666 100644 --- a/.ci/api-python.sh +++ b/.ci/api-python.sh @@ -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 diff --git a/.ci/common.sh b/.ci/common.sh index 09a6e24b9d..ba00011222 100644 --- a/.ci/common.sh +++ b/.ci/common.sh @@ -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 diff --git a/cmake/FindMsgpack.cmake b/cmake/FindMsgpack.cmake index b2c89b13d1..6b3c8bb977 100644 --- a/cmake/FindMsgpack.cmake +++ b/cmake/FindMsgpack.cmake @@ -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} diff --git a/runtime/plugin/python_setup.vim b/runtime/plugin/python_setup.vim new file mode 100644 index 0000000000..7b2d800f74 --- /dev/null +++ b/runtime/plugin/python_setup.vim @@ -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 diff --git a/scripts/gendeclarations.lua b/scripts/gendeclarations.lua index 76711cc214..bc55b48a0a 100755 --- a/scripts/gendeclarations.lua +++ b/scripts/gendeclarations.lua @@ -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') diff --git a/scripts/msgpack-gen.lua b/scripts/msgpack-gen.lua index 8940cc72f6..68faa18c31 100644 --- a/scripts/msgpack-gen.lua +++ b/scripts/msgpack-gen.lua @@ -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; - - 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; - } + rpc_method_handler_fn handler = NULL; + + 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() diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index a268e04559..8355bfe868 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -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; } diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index b049412014..2dd229ec5f 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -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; }; diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index f6fb46e1d1..14a820aa1b 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -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 /// diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index f1b9dc3bc8..f3ecdaacc4 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -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 diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index 535722c087..91020d6850 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -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; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index a2c50b4c81..43f2aafdc8 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -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. diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 1ab441bed3..dd256f2b6d 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -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 (!win) { + 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 (pos.row <= 0 || pos.row > win->w_buffer->b_ml.ml_line_count) { - set_api_error("cursor position outside buffer", err); + if (!win) { return; } - if (pos.row > LONG_MAX || pos.row < LONG_MIN) { - set_api_error("Row value outside range", err); + 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.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; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 7c0d1b3d32..d8c2e73150 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -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"); diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index f1524ffce9..17905c3046 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -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); } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 3b9a9ae64b..49a4a2f604 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -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_( diff --git a/src/nvim/main.c b/src/nvim/main.c index ebd9f7f3ff..fc1826975a 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -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")); diff --git a/src/nvim/map.c b/src/nvim/map.c index 2e47e8b249..24aa38d67d 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -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) diff --git a/src/nvim/map.h b/src/nvim/map.h index 73698cba22..616516c3e1 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -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 diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 50dcca3c84..1c3d6e372c 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -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(); } } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 317d773748..9b98c84be4 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -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); } diff --git a/src/nvim/option.c b/src/nvim/option.c index b9becebbf4..b26b6ed4cc 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -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}, diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 555e9166d6..cd61b6427c 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -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. diff --git a/src/nvim/os/channel.c b/src/nvim/os/channel.c index bad82bc272..ad8f378dfc 100644 --- a/src/nvim/os/channel.c +++ b/src/nvim/os/channel.c @@ -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) diff --git a/src/nvim/os/job.c b/src/nvim/os/job.c index 9deca9de74..9fb2a49e50 100644 --- a/src/nvim/os/job.c +++ b/src/nvim/os/job.c @@ -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 diff --git a/src/nvim/os/msgpack_rpc.c b/src/nvim/os/msgpack_rpc.c index c6e2af2f1c..d7e3d33c4b 100644 --- a/src/nvim/os/msgpack_rpc.c +++ b/src/nvim/os/msgpack_rpc.c @@ -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) { diff --git a/src/nvim/os/msgpack_rpc.h b/src/nvim/os/msgpack_rpc.h index 35f175d2a0..3476d791ea 100644 --- a/src/nvim/os/msgpack_rpc.h +++ b/src/nvim/os/msgpack_rpc.h @@ -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 diff --git a/src/nvim/os/msgpack_rpc_helpers.c b/src/nvim/os/msgpack_rpc_helpers.c index e2c277abe4..b14de8245c 100644 --- a/src/nvim/os/msgpack_rpc_helpers.c +++ b/src/nvim/os/msgpack_rpc_helpers.c @@ -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) - diff --git a/src/nvim/os/msgpack_rpc_helpers.h b/src/nvim/os/msgpack_rpc_helpers.h index e3d1e756ef..aede6b1587 100644 --- a/src/nvim/os/msgpack_rpc_helpers.h +++ b/src/nvim/os/msgpack_rpc_helpers.h @@ -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 diff --git a/src/nvim/os/provider.c b/src/nvim/os/provider.c index 07e757fe0e..2e7a677793 100644 --- a/src/nvim/os/provider.c +++ b/src/nvim/os/provider.c @@ -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); + } + + 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); } - map_put(cstr_t, uint64_t)(registered_providers, xstrdup(method), channel_id); - ILOG("Registered channel %" PRIu64 " as the provider for \"%s\"", + 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; - } - - // 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 (size_t i = 0; i < FEATURE_COUNT; i++) { + Array methods = ARRAY_DICT_INIT; + Feature *f = &features[i]; - 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; } } diff --git a/src/nvim/os/wstream.c b/src/nvim/os/wstream.c index 44463c7c88..00a53d1628 100644 --- a/src/nvim/os/wstream.c +++ b/src/nvim/os/wstream.c @@ -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); } diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index 3fa8b803b2..6c79fbd479 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -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(); } diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 6479aeaafb..3ef291ef7c 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -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... diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index ff161a7cc7..2f7b4c9256 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -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() |