Merge PR #1316 'Refactor event deferral'

This commit is contained in:
Thiago de Arruda 2014-10-22 07:30:32 -03:00
commit 5cd9b64742
40 changed files with 680 additions and 815 deletions

View File

@ -2,8 +2,6 @@
set_environment /opt/neovim-deps/64 set_environment /opt/neovim-deps/64
install_functional_test_deps
sudo pip install cpp-coveralls sudo pip install cpp-coveralls
clang_version=3.4 clang_version=3.4

View File

@ -47,12 +47,6 @@ install_prebuilt_deps() {
fi fi
} }
install_functional_test_deps() {
sudo pip install git+https://github.com/neovim/python-client.git
# Pass -E to let pip use PKG_CONFIG_PATH for luajit
sudo -E pip install lupa
}
tmpdir="$(pwd)/tmp" tmpdir="$(pwd)/tmp"
rm -rf "$tmpdir" rm -rf "$tmpdir"
mkdir -p "$tmpdir" mkdir -p "$tmpdir"

View File

@ -1,10 +1,5 @@
. "$CI_SCRIPTS/common.sh" . "$CI_SCRIPTS/common.sh"
# To install lupa, a temporarary functional test dependency, we require the
# 64-bit luajit since travis version of python is 64-bit.
export PKG_CONFIG_PATH="/opt/neovim-deps/64/usr/lib/pkgconfig"
install_functional_test_deps
set_environment /opt/neovim-deps/32 set_environment /opt/neovim-deps/32
# Need this to keep apt-get from removing gcc when installing libncurses # Need this to keep apt-get from removing gcc when installing libncurses

View File

@ -2,8 +2,6 @@
set_environment /opt/neovim-deps/64 set_environment /opt/neovim-deps/64
install_functional_test_deps
sudo pip install cpp-coveralls sudo pip install cpp-coveralls
sudo apt-get install valgrind sudo apt-get install valgrind

View File

@ -32,8 +32,6 @@ src/nvim/os/job.c
src/nvim/os/job.h src/nvim/os/job.h
src/nvim/os/job_defs.h src/nvim/os/job_defs.h
src/nvim/os/mem.c src/nvim/os/mem.c
src/nvim/os/msgpack_rpc.c
src/nvim/os/msgpack_rpc.h
src/nvim/os/os.h src/nvim/os/os.h
src/nvim/os/rstream.c src/nvim/os/rstream.c
src/nvim/os/rstream.h src/nvim/os/rstream.h
@ -44,10 +42,12 @@ src/nvim/os/signal.c
src/nvim/os/signal.h src/nvim/os/signal.h
src/nvim/os/time.c src/nvim/os/time.c
src/nvim/os/time.h src/nvim/os/time.h
src/nvim/os/server.c src/nvim/msgpack_rpc/server.c
src/nvim/os/server.h src/nvim/msgpack_rpc/server.h
src/nvim/os/channel.c src/nvim/msgpack_rpc/channel.c
src/nvim/os/channel.h src/nvim/msgpack_rpc/channel.h
src/nvim/msgpack_rpc/helpers.c
src/nvim/msgpack_rpc/helpers.h
src/nvim/tempfile.c src/nvim/tempfile.c
src/nvim/tempfile.h src/nvim/tempfile.h
src/nvim/profile.c src/nvim/profile.c

View File

@ -34,6 +34,8 @@ c_params = Ct(c_void + c_param_list)
c_proto = Ct( c_proto = Ct(
Cg(c_type, 'return_type') * Cg(c_id, 'name') * Cg(c_type, 'return_type') * Cg(c_id, 'name') *
fill * P('(') * fill * Cg(c_params, 'parameters') * fill * P(')') * fill * P('(') * fill * Cg(c_params, 'parameters') * fill * P(')') *
Cg(Cc(false), 'deferred') *
(fill * Cg((P('FUNC_ATTR_DEFERRED') * Cc(true)), 'deferred') ^ -1) *
fill * P(';') fill * P(';')
) )
grammar = Ct((c_proto + c_comment + c_preproc + ws) ^ 1) grammar = Ct((c_proto + c_comment + c_preproc + ws) ^ 1)
@ -92,8 +94,8 @@ output:write([[
#include "nvim/map.h" #include "nvim/map.h"
#include "nvim/log.h" #include "nvim/log.h"
#include "nvim/vim.h" #include "nvim/vim.h"
#include "nvim/os/msgpack_rpc.h" #include "nvim/msgpack_rpc/helpers.h"
#include "nvim/os/msgpack_rpc_helpers.h" #include "nvim/msgpack_rpc/defs.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
]]) ]])
@ -159,9 +161,10 @@ for i = 1, #functions do
local fn = functions[i] local fn = functions[i]
local args = {} local args = {}
output:write('static Object handle_'..fn.name..'(uint64_t channel_id, msgpack_object *req, Error *error)') output:write('static Object handle_'..fn.name..'(uint64_t channel_id, uint64_t request_id, Array args, Error *error)')
output:write('\n{') output:write('\n{')
output:write('\n DLOG("Received msgpack-rpc call to '..fn.name..'(request id: %" PRIu64 ")", req->via.array.ptr[1].via.u64);') output:write('\n DLOG("Handling msgpack-rpc call to '..fn.name..'(request id: %" PRIu64 ")", request_id);')
output:write('\n Object ret = NIL;')
-- Declare/initialize variables that will hold converted arguments -- Declare/initialize variables that will hold converted arguments
for j = 1, #fn.parameters do for j = 1, #fn.parameters do
local param = fn.parameters[j] local param = fn.parameters[j]
@ -169,8 +172,8 @@ for i = 1, #functions do
output:write('\n '..param[1]..' '..converted..' api_init_'..string.lower(real_type(param[1]))..';') output:write('\n '..param[1]..' '..converted..' api_init_'..string.lower(real_type(param[1]))..';')
end end
output:write('\n') output:write('\n')
output:write('\n if (req->via.array.ptr[3].via.array.size != '..#fn.parameters..') {') output:write('\n if (args.size != '..#fn.parameters..') {')
output:write('\n snprintf(error->msg, sizeof(error->msg), "Wrong number of arguments: expecting '..#fn.parameters..' but got %u", req->via.array.ptr[3].via.array.size);') output:write('\n snprintf(error->msg, sizeof(error->msg), "Wrong number of arguments: expecting '..#fn.parameters..' but got %zu", args.size);')
output:write('\n error->set = true;') output:write('\n error->set = true;')
output:write('\n goto cleanup;') output:write('\n goto cleanup;')
output:write('\n }\n') output:write('\n }\n')
@ -179,14 +182,18 @@ for i = 1, #functions do
for j = 1, #fn.parameters do for j = 1, #fn.parameters do
local converted, convert_arg, param, arg local converted, convert_arg, param, arg
param = fn.parameters[j] param = fn.parameters[j]
arg = '(req->via.array.ptr[3].via.array.ptr + '..(j - 1)..')'
converted = 'arg_'..j converted = 'arg_'..j
convert_arg = 'msgpack_rpc_to_'..real_type(param[1]):lower() if real_type(param[1]) ~= 'Object' then
output:write('\n if (!'..convert_arg..'('..arg..', &'..converted..')) {') output:write('\n if (args.items['..(j - 1)..'].type != kObjectType'..real_type(param[1])..') {')
output:write('\n snprintf(error->msg, sizeof(error->msg), "Wrong type for argument '..j..', expecting '..param[1]..'");') output:write('\n snprintf(error->msg, sizeof(error->msg), "Wrong type for argument '..j..', expecting '..param[1]..'");')
output:write('\n error->set = true;') output:write('\n error->set = true;')
output:write('\n goto cleanup;') output:write('\n goto cleanup;')
output:write('\n }\n') output:write('\n }')
output:write('\n '..converted..' = args.items['..(j - 1)..'].data.'..real_type(param[1]):lower()..';\n')
else
output:write('\n '..converted..' = args.items['..(j - 1)..'];\n')
end
args[#args + 1] = converted args[#args + 1] = converted
end end
@ -228,7 +235,7 @@ for i = 1, #functions do
end end
if fn.return_type ~= 'void' then if fn.return_type ~= 'void' then
output:write('\n Object ret = '..string.upper(real_type(fn.return_type))..'_OBJ(rv);') output:write('\n ret = '..string.upper(real_type(fn.return_type))..'_OBJ(rv);')
end end
-- Now generate the cleanup label for freeing memory allocated for the -- Now generate the cleanup label for freeing memory allocated for the
-- arguments -- arguments
@ -238,20 +245,16 @@ for i = 1, #functions do
local param = fn.parameters[j] local param = fn.parameters[j]
output:write('\n api_free_'..string.lower(real_type(param[1]))..'(arg_'..j..');') output:write('\n api_free_'..string.lower(real_type(param[1]))..'(arg_'..j..');')
end end
if fn.return_type ~= 'void' then output:write('\n return ret;\n}\n\n');
output:write('\n return ret;\n}\n\n');
else
output:write('\n return NIL;\n}\n\n');
end
end end
-- Generate a function that initializes method names with handler functions -- Generate a function that initializes method names with handler functions
output:write([[ output:write([[
static Map(String, rpc_method_handler_fn) *methods = NULL; static Map(String, MsgpackRpcRequestHandler) *methods = NULL;
void msgpack_rpc_init(void) void msgpack_rpc_init_method_table(void)
{ {
methods = map_new(String, rpc_method_handler_fn)(); methods = map_new(String, MsgpackRpcRequestHandler)();
]]) ]])
@ -260,10 +263,11 @@ void msgpack_rpc_init(void)
local max_fname_len = 0 local max_fname_len = 0
for i = 1, #functions do for i = 1, #functions do
local fn = functions[i] local fn = functions[i]
output:write(' map_put(String, rpc_method_handler_fn)(methods, '.. output:write(' map_put(String, MsgpackRpcRequestHandler)(methods, '..
'(String) {.data = "'..fn.name..'", '.. '(String) {.data = "'..fn.name..'", '..
'.size = sizeof("'..fn.name..'") - 1}, handle_'.. '.size = sizeof("'..fn.name..'") - 1}, '..
fn.name..');\n') '(MsgpackRpcRequestHandler) {.fn = handle_'.. fn.name..
', .defer = '..tostring(fn.deferred)..'});\n')
if #fn.name > max_fname_len then if #fn.name > max_fname_len then
max_fname_len = #fn.name max_fname_len = #fn.name
@ -273,26 +277,21 @@ end
output:write('\n}\n\n') output:write('\n}\n\n')
output:write([[ output:write([[
Object msgpack_rpc_dispatch(uint64_t channel_id, MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name,
msgpack_object *req, size_t name_len)
Error *error)
{ {
msgpack_object method = req->via.array.ptr[2]; String m = {
rpc_method_handler_fn handler = NULL; .data=(char *)name,
.size=min(name_len, ]]..max_fname_len..[[)
};
MsgpackRpcRequestHandler rv =
map_get(String, MsgpackRpcRequestHandler)(methods, m);
if (method.type == MSGPACK_OBJECT_BIN || method.type == MSGPACK_OBJECT_STR) { if (!rv.fn) {
]]) rv.fn = msgpack_rpc_handle_missing_method;
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) { return rv;
handler = msgpack_rpc_handle_missing_method;
}
return handler(channel_id, req, error);
} }
]]) ]])

View File

@ -3,7 +3,7 @@ include(CheckLibraryExists)
set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto) set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto)
set(DISPATCH_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/msgpack-gen.lua) set(DISPATCH_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/msgpack-gen.lua)
file(GLOB API_HEADERS api/*.h) file(GLOB API_HEADERS api/*.h)
set(MSGPACK_RPC_HEADER ${PROJECT_SOURCE_DIR}/src/nvim/os/msgpack_rpc.h) file(GLOB MSGPACK_RPC_HEADERS msgpack_rpc/*.h)
set(MSGPACK_DISPATCH ${GENERATED_DIR}/msgpack_dispatch.c) set(MSGPACK_DISPATCH ${GENERATED_DIR}/msgpack_dispatch.c)
set(HEADER_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/gendeclarations.lua) set(HEADER_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/gendeclarations.lua)
set(GENERATED_INCLUDES_DIR ${PROJECT_BINARY_DIR}/include) set(GENERATED_INCLUDES_DIR ${PROJECT_BINARY_DIR}/include)
@ -19,12 +19,14 @@ file(MAKE_DIRECTORY ${GENERATED_DIR})
file(MAKE_DIRECTORY ${GENERATED_DIR}/os) file(MAKE_DIRECTORY ${GENERATED_DIR}/os)
file(MAKE_DIRECTORY ${GENERATED_DIR}/api) file(MAKE_DIRECTORY ${GENERATED_DIR}/api)
file(MAKE_DIRECTORY ${GENERATED_DIR}/api/private) file(MAKE_DIRECTORY ${GENERATED_DIR}/api/private)
file(MAKE_DIRECTORY ${GENERATED_DIR}/msgpack_rpc)
file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}) file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR})
file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/os) file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/os)
file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/api) file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/api)
file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/api/private) file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/api/private)
file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/msgpack_rpc)
file(GLOB NEOVIM_SOURCES *.c os/*.c api/*.c api/private/*.c) file(GLOB NEOVIM_SOURCES *.c os/*.c api/*.c api/private/*.c msgpack_rpc/*.c)
file(GLOB_RECURSE NEOVIM_HEADERS *.h) file(GLOB_RECURSE NEOVIM_HEADERS *.h)
foreach(sfile ${NEOVIM_SOURCES}) foreach(sfile ${NEOVIM_SOURCES})
@ -36,8 +38,7 @@ endforeach()
list(REMOVE_ITEM NEOVIM_SOURCES ${to_remove}) list(REMOVE_ITEM NEOVIM_SOURCES ${to_remove})
set(CONV_SRCS set(CONV_SOURCES
api.c
arabic.c arabic.c
cursor.c cursor.c
garray.c garray.c
@ -46,31 +47,24 @@ set(CONV_SRCS
map.c map.c
memory.c memory.c
misc2.c misc2.c
map.c
profile.c profile.c
os/env.c
os/event.c
os/job.c
os/mem.c
os/rstream.c
os/signal.c
os/users.c
os/provider.c
os/uv_helpers.c
os/wstream.c
os/msgpack_rpc.c
tempfile.c tempfile.c
api/buffer.c
api/private/helpers.c
api/private/handle.c
api/tabpage.c
api/window.c
api/vim.h
api/vim.c
) )
foreach(sfile ${CONV_SOURCES})
if(NOT EXISTS "${PROJECT_SOURCE_DIR}/src/nvim/${sfile}")
message(FATAL_ERROR "${sfile} doesn't exist(it was added to CONV_SOURCES)")
endif()
endforeach()
file(GLOB_RECURSE EXTRA_CONV_SOURCES os/*.c api/*.c msgpack_rpc/*.c)
foreach(sfile ${EXTRA_CONV_SOURCES})
file(RELATIVE_PATH f "${PROJECT_SOURCE_DIR}/src/nvim" "${sfile}")
list(APPEND CONV_SOURCES ${f})
endforeach()
set_source_files_properties( set_source_files_properties(
${CONV_SRCS} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wconversion") ${CONV_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wconversion")
if(CMAKE_C_COMPILER_ID MATCHES "Clang") if(CMAKE_C_COMPILER_ID MATCHES "Clang")
if(DEFINED ENV{SANITIZE}) if(DEFINED ENV{SANITIZE})
@ -126,7 +120,7 @@ add_custom_command(OUTPUT ${MSGPACK_DISPATCH}
COMMAND ${LUA_PRG} ${DISPATCH_GENERATOR} ${API_HEADERS} ${MSGPACK_DISPATCH} COMMAND ${LUA_PRG} ${DISPATCH_GENERATOR} ${API_HEADERS} ${MSGPACK_DISPATCH}
DEPENDS DEPENDS
${API_HEADERS} ${API_HEADERS}
${MSGPACK_RPC_HEADER} ${MSGPACK_RPC_HEADERS}
${DISPATCH_GENERATOR} ${DISPATCH_GENERATOR}
) )

View File

@ -69,6 +69,7 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
/// @param line The new line. /// @param line The new line.
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void buffer_set_line(Buffer buffer, Integer index, String line, Error *err) void buffer_set_line(Buffer buffer, Integer index, String line, Error *err)
FUNC_ATTR_DEFERRED
{ {
Object l = STRING_OBJ(line); Object l = STRING_OBJ(line);
Array array = {.items = &l, .size = 1}; Array array = {.items = &l, .size = 1};
@ -81,6 +82,7 @@ void buffer_set_line(Buffer buffer, Integer index, String line, Error *err)
/// @param index The line index /// @param index The line index
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void buffer_del_line(Buffer buffer, Integer index, Error *err) void buffer_del_line(Buffer buffer, Integer index, Error *err)
FUNC_ATTR_DEFERRED
{ {
Array array = ARRAY_DICT_INIT; Array array = ARRAY_DICT_INIT;
buffer_set_line_slice(buffer, index, index, true, true, array, err); buffer_set_line_slice(buffer, index, index, true, true, array, err);
@ -163,6 +165,7 @@ void buffer_set_line_slice(Buffer buffer,
Boolean include_end, Boolean include_end,
ArrayOf(String) replacement, ArrayOf(String) replacement,
Error *err) Error *err)
FUNC_ATTR_DEFERRED
{ {
buf_T *buf = find_buffer_by_handle(buffer, err); buf_T *buf = find_buffer_by_handle(buffer, err);
@ -314,6 +317,7 @@ Object buffer_get_var(Buffer buffer, String name, Error *err)
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
/// @return The old value /// @return The old value
Object buffer_set_var(Buffer buffer, String name, Object value, Error *err) Object buffer_set_var(Buffer buffer, String name, Object value, Error *err)
FUNC_ATTR_DEFERRED
{ {
buf_T *buf = find_buffer_by_handle(buffer, err); buf_T *buf = find_buffer_by_handle(buffer, err);
@ -349,6 +353,7 @@ Object buffer_get_option(Buffer buffer, String name, Error *err)
/// @param value The option value /// @param value The option value
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void buffer_set_option(Buffer buffer, String name, Object value, Error *err) void buffer_set_option(Buffer buffer, String name, Object value, Error *err)
FUNC_ATTR_DEFERRED
{ {
buf_T *buf = find_buffer_by_handle(buffer, err); buf_T *buf = find_buffer_by_handle(buffer, err);
@ -399,6 +404,7 @@ String buffer_get_name(Buffer buffer, Error *err)
/// @param name The buffer name /// @param name The buffer name
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void buffer_set_name(Buffer buffer, String name, Error *err) void buffer_set_name(Buffer buffer, String name, Error *err)
FUNC_ATTR_DEFERRED
{ {
buf_T *buf = find_buffer_by_handle(buffer, err); buf_T *buf = find_buffer_by_handle(buffer, err);
@ -444,6 +450,7 @@ void buffer_insert(Buffer buffer,
Integer lnum, Integer lnum,
ArrayOf(String) lines, ArrayOf(String) lines,
Error *err) Error *err)
FUNC_ATTR_DEFERRED
{ {
buffer_set_line_slice(buffer, lnum, lnum, false, true, lines, err); buffer_set_line_slice(buffer, lnum, lnum, false, true, lines, err);
} }

View File

@ -62,6 +62,7 @@ Object tabpage_get_var(Tabpage tabpage, String name, Error *err)
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
/// @return The tab page handle /// @return The tab page handle
Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err) Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err)
FUNC_ATTR_DEFERRED
{ {
tabpage_T *tab = find_tab_by_handle(tabpage, err); tabpage_T *tab = find_tab_by_handle(tabpage, err);

View File

@ -10,7 +10,7 @@
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/api/buffer.h" #include "nvim/api/buffer.h"
#include "nvim/os/channel.h" #include "nvim/msgpack_rpc/channel.h"
#include "nvim/os/provider.h" #include "nvim/os/provider.h"
#include "nvim/vim.h" #include "nvim/vim.h"
#include "nvim/buffer.h" #include "nvim/buffer.h"
@ -31,19 +31,12 @@
# include "api/vim.c.generated.h" # include "api/vim.c.generated.h"
#endif #endif
/// Send keys to vim input buffer, simulating user input.
///
/// @param str The keys to send
void vim_push_keys(String str)
{
abort();
}
/// Executes an ex-mode command str /// Executes an ex-mode command str
/// ///
/// @param str The command str /// @param str The command str
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void vim_command(String str, Error *err) void vim_command(String str, Error *err)
FUNC_ATTR_DEFERRED
{ {
// Run the command // Run the command
try_start(); try_start();
@ -111,6 +104,7 @@ String vim_replace_termcodes(String str, Boolean from_part, Boolean do_lt,
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
/// @return The expanded object /// @return The expanded object
Object vim_eval(String str, Error *err) Object vim_eval(String str, Error *err)
FUNC_ATTR_DEFERRED
{ {
Object rv; Object rv;
// Evaluate the expression // Evaluate the expression
@ -230,6 +224,7 @@ String vim_get_current_line(Error *err)
/// @param line The line contents /// @param line The line contents
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void vim_set_current_line(String line, Error *err) void vim_set_current_line(String line, Error *err)
FUNC_ATTR_DEFERRED
{ {
buffer_set_line(curbuf->handle, curwin->w_cursor.lnum - 1, line, err); buffer_set_line(curbuf->handle, curwin->w_cursor.lnum - 1, line, err);
} }
@ -238,6 +233,7 @@ void vim_set_current_line(String line, Error *err)
/// ///
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void vim_del_current_line(Error *err) void vim_del_current_line(Error *err)
FUNC_ATTR_DEFERRED
{ {
buffer_del_line(curbuf->handle, curwin->w_cursor.lnum - 1, err); buffer_del_line(curbuf->handle, curwin->w_cursor.lnum - 1, err);
} }
@ -259,6 +255,7 @@ Object vim_get_var(String name, Error *err)
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
/// @return the old value if any /// @return the old value if any
Object vim_set_var(String name, Object value, Error *err) Object vim_set_var(String name, Object value, Error *err)
FUNC_ATTR_DEFERRED
{ {
return dict_set_value(&globvardict, name, value, err); return dict_set_value(&globvardict, name, value, err);
} }
@ -289,6 +286,7 @@ Object vim_get_option(String name, Error *err)
/// @param value The new option value /// @param value The new option value
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void vim_set_option(String name, Object value, Error *err) void vim_set_option(String name, Object value, Error *err)
FUNC_ATTR_DEFERRED
{ {
set_option_to(NULL, SREQ_GLOBAL, name, value, err); set_option_to(NULL, SREQ_GLOBAL, name, value, err);
} }
@ -297,6 +295,7 @@ void vim_set_option(String name, Object value, Error *err)
/// ///
/// @param str The message /// @param str The message
void vim_out_write(String str) void vim_out_write(String str)
FUNC_ATTR_DEFERRED
{ {
write_msg(str, false); write_msg(str, false);
} }
@ -305,6 +304,7 @@ void vim_out_write(String str)
/// ///
/// @param str The message /// @param str The message
void vim_err_write(String str) void vim_err_write(String str)
FUNC_ATTR_DEFERRED
{ {
write_msg(str, true); write_msg(str, true);
} }
@ -314,6 +314,7 @@ void vim_err_write(String str)
/// ///
/// @param str The message /// @param str The message
void vim_report_error(String str) void vim_report_error(String str)
FUNC_ATTR_DEFERRED
{ {
vim_err_write(str); vim_err_write(str);
vim_err_write((String) {.data = "\n", .size = 1}); vim_err_write((String) {.data = "\n", .size = 1});
@ -357,6 +358,7 @@ Buffer vim_get_current_buffer(void)
/// @param id The buffer handle /// @param id The buffer handle
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void vim_set_current_buffer(Buffer buffer, Error *err) void vim_set_current_buffer(Buffer buffer, Error *err)
FUNC_ATTR_DEFERRED
{ {
buf_T *buf = find_buffer_by_handle(buffer, err); buf_T *buf = find_buffer_by_handle(buffer, err);
@ -407,6 +409,7 @@ Window vim_get_current_window(void)
/// ///
/// @param handle The window handle /// @param handle The window handle
void vim_set_current_window(Window window, Error *err) void vim_set_current_window(Window window, Error *err)
FUNC_ATTR_DEFERRED
{ {
win_T *win = find_window_by_handle(window, err); win_T *win = find_window_by_handle(window, err);
@ -462,6 +465,7 @@ Tabpage vim_get_current_tabpage(void)
/// @param handle The tab page handle /// @param handle The tab page handle
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void vim_set_current_tabpage(Tabpage tabpage, Error *err) void vim_set_current_tabpage(Tabpage tabpage, Error *err)
FUNC_ATTR_DEFERRED
{ {
tabpage_T *tp = find_tab_by_handle(tabpage, err); tabpage_T *tp = find_tab_by_handle(tabpage, err);

View File

@ -52,6 +52,7 @@ ArrayOf(Integer, 2) window_get_cursor(Window window, Error *err)
/// @param pos the (row, col) tuple representing the new position /// @param pos the (row, col) tuple representing the new position
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void window_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) void window_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err)
FUNC_ATTR_DEFERRED
{ {
win_T *win = find_window_by_handle(window, err); win_T *win = find_window_by_handle(window, err);
@ -111,6 +112,7 @@ Integer window_get_height(Window window, Error *err)
/// @param height the new height in rows /// @param height the new height in rows
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void window_set_height(Window window, Integer height, Error *err) void window_set_height(Window window, Integer height, Error *err)
FUNC_ATTR_DEFERRED
{ {
win_T *win = find_window_by_handle(window, err); win_T *win = find_window_by_handle(window, err);
@ -154,6 +156,7 @@ Integer window_get_width(Window window, Error *err)
/// @param width the new width in columns /// @param width the new width in columns
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void window_set_width(Window window, Integer width, Error *err) void window_set_width(Window window, Integer width, Error *err)
FUNC_ATTR_DEFERRED
{ {
win_T *win = find_window_by_handle(window, err); win_T *win = find_window_by_handle(window, err);
@ -199,6 +202,7 @@ Object window_get_var(Window window, String name, Error *err)
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
/// @return The old value /// @return The old value
Object window_set_var(Window window, String name, Object value, Error *err) Object window_set_var(Window window, String name, Object value, Error *err)
FUNC_ATTR_DEFERRED
{ {
win_T *win = find_window_by_handle(window, err); win_T *win = find_window_by_handle(window, err);
@ -234,6 +238,7 @@ Object window_get_option(Window window, String name, Error *err)
/// @param value The option value /// @param value The option value
/// @param[out] err Details of an error that may have occurred /// @param[out] err Details of an error that may have occurred
void window_set_option(Window window, String name, Object value, Error *err) void window_set_option(Window window, String name, Object value, Error *err)
FUNC_ATTR_DEFERRED
{ {
win_T *win = find_window_by_handle(window, err); win_T *win = find_window_by_handle(window, err);

View File

@ -18,6 +18,8 @@
#include <stdbool.h> #include <stdbool.h>
#include <math.h> #include <math.h>
#include "nvim/lib/klist.h"
#include "nvim/assert.h" #include "nvim/assert.h"
#include "nvim/vim.h" #include "nvim/vim.h"
#include "nvim/ascii.h" #include "nvim/ascii.h"
@ -81,11 +83,12 @@
#include "nvim/os/rstream.h" #include "nvim/os/rstream.h"
#include "nvim/os/rstream_defs.h" #include "nvim/os/rstream_defs.h"
#include "nvim/os/time.h" #include "nvim/os/time.h"
#include "nvim/os/channel.h" #include "nvim/msgpack_rpc/channel.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h" #include "nvim/api/vim.h"
#include "nvim/os/dl.h" #include "nvim/os/dl.h"
#include "nvim/os/provider.h" #include "nvim/os/provider.h"
#include "nvim/os/event.h"
#define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */ #define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */
@ -443,6 +446,16 @@ static dictitem_T vimvars_var; /* variable used for v: */
#define FNE_CHECK_START 2 /* find_name_end(): check name starts with #define FNE_CHECK_START 2 /* find_name_end(): check name starts with
valid character */ valid character */
// Memory pool for reusing JobEvent structures
typedef struct {
Job *job;
RStream *rstream;
char *type;
} JobEvent;
#define JobEventFreer(x)
KMEMPOOL_INIT(JobEventPool, JobEvent, JobEventFreer)
kmempool_t(JobEventPool) *job_event_pool = NULL;
/* /*
* Initialize the global and v: variables. * Initialize the global and v: variables.
*/ */
@ -478,6 +491,7 @@ void eval_init(void)
set_vim_var_nr(VV_HLSEARCH, 1L); set_vim_var_nr(VV_HLSEARCH, 1L);
set_reg_var(0); /* default for v:register is not 0 but '"' */ set_reg_var(0); /* default for v:register is not 0 but '"' */
job_event_pool = kmp_init(JobEventPool);
} }
#if defined(EXITFREE) || defined(PROTO) #if defined(EXITFREE) || defined(PROTO)
@ -19508,35 +19522,55 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags)
return ret; return ret;
} }
// JobActivity autocommands will execute vimscript code, so it must be executed
// on Nvim main loop
#define push_job_event(j, r, t) \
do { \
JobEvent *event_data = kmp_alloc(JobEventPool, job_event_pool); \
event_data->job = j; \
event_data->rstream = r; \
event_data->type = t; \
event_push((Event) { \
.handler = on_job_event, \
.data = event_data \
}); \
} while(0)
static void on_job_stdout(RStream *rstream, void *data, bool eof) static void on_job_stdout(RStream *rstream, void *data, bool eof)
{ {
if (!eof) { if (!eof) {
on_job_data(rstream, data, eof, "stdout"); push_job_event(data, rstream, "stdout");
} }
} }
static void on_job_stderr(RStream *rstream, void *data, bool eof) static void on_job_stderr(RStream *rstream, void *data, bool eof)
{ {
if (!eof) { if (!eof) {
on_job_data(rstream, data, eof, "stderr"); push_job_event(data, rstream, "stderr");
} }
} }
static void on_job_exit(Job *job, void *data) static void on_job_exit(Job *job, void *data)
{ {
apply_job_autocmds(job, data, "exit", NULL); push_job_event(data, NULL, "exit");
free(data);
} }
static void on_job_data(RStream *rstream, void *data, bool eof, char *type) static void on_job_event(Event event)
{ {
Job *job = data; JobEvent *data = event.data;
uint32_t read_count = rstream_pending(rstream); Job *job = data->job;
char *str = xmalloc(read_count + 1); char *str = NULL;
rstream_read(rstream, str, read_count); if (data->rstream) {
str[read_count] = NUL; // Read event
apply_job_autocmds(job, job_data(job), type, str); size_t read_count = rstream_pending(data->rstream);
str = xmalloc(read_count + 1);
rstream_read(data->rstream, str, read_count);
str[read_count] = NUL;
}
apply_job_autocmds(job, job_data(job), data->type, str);
kmp_free(JobEventPool, job_event_pool, data);
} }
static void apply_job_autocmds(Job *job, char *name, char *type, char *str) static void apply_job_autocmds(Job *job, char *name, char *type, char *str)

View File

@ -179,6 +179,7 @@
#endif #endif
#ifdef DEFINE_FUNC_ATTRIBUTES #ifdef DEFINE_FUNC_ATTRIBUTES
#define FUNC_ATTR_DEFERRED
#define FUNC_ATTR_MALLOC REAL_FATTR_MALLOC #define FUNC_ATTR_MALLOC REAL_FATTR_MALLOC
#define FUNC_ATTR_ALLOC_SIZE(x) REAL_FATTR_ALLOC_SIZE(x) #define FUNC_ATTR_ALLOC_SIZE(x) REAL_FATTR_ALLOC_SIZE(x)
#define FUNC_ATTR_ALLOC_SIZE_PROD(x,y) REAL_FATTR_ALLOC_SIZE_PROD(x,y) #define FUNC_ATTR_ALLOC_SIZE_PROD(x,y) REAL_FATTR_ALLOC_SIZE_PROD(x,y)

View File

@ -39,6 +39,8 @@
static inline kmp_##name##_t *kmp_init_##name(void) { \ static inline kmp_##name##_t *kmp_init_##name(void) { \
return xcalloc(1, sizeof(kmp_##name##_t)); \ return xcalloc(1, sizeof(kmp_##name##_t)); \
} \ } \
static inline void kmp_destroy_##name(kmp_##name##_t *mp) \
REAL_FATTR_UNUSED; \
static inline void kmp_destroy_##name(kmp_##name##_t *mp) { \ static inline void kmp_destroy_##name(kmp_##name##_t *mp) { \
size_t k; \ size_t k; \
for (k = 0; k < mp->n; ++k) { \ for (k = 0; k < mp->n; ++k) { \

View File

@ -59,7 +59,7 @@
#include "nvim/os/input.h" #include "nvim/os/input.h"
#include "nvim/os/os.h" #include "nvim/os/os.h"
#include "nvim/os/signal.h" #include "nvim/os/signal.h"
#include "nvim/os/msgpack_rpc_helpers.h" #include "nvim/msgpack_rpc/helpers.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"

View File

@ -6,7 +6,7 @@
#include "nvim/map_defs.h" #include "nvim/map_defs.h"
#include "nvim/vim.h" #include "nvim/vim.h"
#include "nvim/memory.h" #include "nvim/memory.h"
#include "nvim/os/msgpack_rpc.h" #include "nvim/msgpack_rpc/defs.h"
#include "nvim/lib/khash.h" #include "nvim/lib/khash.h"
@ -108,4 +108,5 @@ MAP_IMPL(cstr_t, uint64_t, DEFAULT_INITIALIZER)
MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(String, rpc_method_handler_fn, DEFAULT_INITIALIZER) #define MSGPACK_HANDLER_INITIALIZER {.fn = NULL, .defer = false}
MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER)

View File

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

View File

@ -5,9 +5,11 @@
#include <uv.h> #include <uv.h>
#include <msgpack.h> #include <msgpack.h>
#include "nvim/lib/klist.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h" #include "nvim/api/vim.h"
#include "nvim/os/channel.h" #include "nvim/msgpack_rpc/channel.h"
#include "nvim/os/event.h" #include "nvim/os/event.h"
#include "nvim/os/rstream.h" #include "nvim/os/rstream.h"
#include "nvim/os/rstream_defs.h" #include "nvim/os/rstream_defs.h"
@ -15,8 +17,7 @@
#include "nvim/os/wstream_defs.h" #include "nvim/os/wstream_defs.h"
#include "nvim/os/job.h" #include "nvim/os/job.h"
#include "nvim/os/job_defs.h" #include "nvim/os/job_defs.h"
#include "nvim/os/msgpack_rpc.h" #include "nvim/msgpack_rpc/helpers.h"
#include "nvim/os/msgpack_rpc_helpers.h"
#include "nvim/vim.h" #include "nvim/vim.h"
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/memory.h" #include "nvim/memory.h"
@ -32,14 +33,14 @@
typedef struct { typedef struct {
uint64_t request_id; uint64_t request_id;
bool errored; bool returned, errored;
Object result; Object result;
} ChannelCallFrame; } ChannelCallFrame;
typedef struct { typedef struct {
uint64_t id; uint64_t id;
PMap(cstr_t) *subscribed_events; PMap(cstr_t) *subscribed_events;
bool is_job, enabled; bool is_job, closed;
msgpack_unpacker *unpacker; msgpack_unpacker *unpacker;
union { union {
Job *job; Job *job;
@ -51,21 +52,32 @@ typedef struct {
} data; } data;
uint64_t next_request_id; uint64_t next_request_id;
kvec_t(ChannelCallFrame *) call_stack; kvec_t(ChannelCallFrame *) call_stack;
size_t rpc_call_level;
} Channel; } Channel;
typedef struct {
Channel *channel;
MsgpackRpcRequestHandler handler;
Array args;
uint64_t request_id;
} RequestEvent;
#define RequestEventFreer(x)
KMEMPOOL_INIT(RequestEventPool, RequestEvent, RequestEventFreer)
kmempool_t(RequestEventPool) *request_event_pool = NULL;
static uint64_t next_id = 1; static uint64_t next_id = 1;
static PMap(uint64_t) *channels = NULL; static PMap(uint64_t) *channels = NULL;
static PMap(cstr_t) *event_strings = NULL; static PMap(cstr_t) *event_strings = NULL;
static msgpack_sbuffer out_buffer; static msgpack_sbuffer out_buffer;
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/channel.c.generated.h" # include "msgpack_rpc/channel.c.generated.h"
#endif #endif
/// Initializes the module /// Initializes the module
void channel_init(void) void channel_init(void)
{ {
request_event_pool = kmp_init(RequestEventPool);
channels = pmap_new(uint64_t)(); channels = pmap_new(uint64_t)();
event_strings = pmap_new(cstr_t)(); event_strings = pmap_new(cstr_t)();
msgpack_sbuffer_init(&out_buffer); msgpack_sbuffer_init(&out_buffer);
@ -104,12 +116,12 @@ uint64_t channel_from_job(char **argv)
channel, channel,
job_out, job_out,
job_err, job_err,
NULL, job_exit,
0, 0,
&status); &status);
if (status <= 0) { if (status <= 0) {
close_channel(channel); free_channel(channel);
return 0; return 0;
} }
@ -128,8 +140,7 @@ void channel_from_stream(uv_stream_t *stream)
// read stream // read stream
channel->data.streams.read = rstream_new(parse_msgpack, channel->data.streams.read = rstream_new(parse_msgpack,
rbuffer_new(CHANNEL_BUFFER_SIZE), rbuffer_new(CHANNEL_BUFFER_SIZE),
channel, channel);
NULL);
rstream_set_stream(channel->data.streams.read, stream); rstream_set_stream(channel->data.streams.read, stream);
rstream_start(channel->data.streams.read); rstream_start(channel->data.streams.read);
// write stream // write stream
@ -142,7 +153,7 @@ bool channel_exists(uint64_t id)
{ {
Channel *channel; Channel *channel;
return (channel = pmap_get(uint64_t)(channels, id)) != NULL return (channel = pmap_get(uint64_t)(channels, id)) != NULL
&& channel->enabled; && !channel->closed;
} }
/// Sends event/arguments to channel /// Sends event/arguments to channel
@ -157,7 +168,7 @@ bool channel_send_event(uint64_t id, char *name, Array args)
Channel *channel = NULL; Channel *channel = NULL;
if (id > 0) { if (id > 0) {
if (!(channel = pmap_get(uint64_t)(channels, id)) || !channel->enabled) { if (!(channel = pmap_get(uint64_t)(channels, id)) || channel->closed) {
api_free_array(args); api_free_array(args);
return false; return false;
} }
@ -183,7 +194,7 @@ Object channel_send_call(uint64_t id,
{ {
Channel *channel = NULL; Channel *channel = NULL;
if (!(channel = pmap_get(uint64_t)(channels, id)) || !channel->enabled) { if (!(channel = pmap_get(uint64_t)(channels, id)) || channel->closed) {
api_set_error(err, Exception, _("Invalid channel \"%" PRIu64 "\""), id); api_set_error(err, Exception, _("Invalid channel \"%" PRIu64 "\""), id);
api_free_array(args); api_free_array(args);
return NIL; return NIL;
@ -203,22 +214,11 @@ Object channel_send_call(uint64_t id,
// Send the msgpack-rpc request // Send the msgpack-rpc request
send_request(channel, request_id, method_name, args); send_request(channel, request_id, method_name, args);
EventSource channel_source = channel->is_job
? job_event_source(channel->data.job)
: rstream_event_source(channel->data.streams.read);
EventSource sources[] = {channel_source, NULL};
// Push the frame // Push the frame
ChannelCallFrame frame = {request_id, false, NIL}; ChannelCallFrame frame = {request_id, false, false, NIL};
kv_push(ChannelCallFrame *, channel->call_stack, &frame); kv_push(ChannelCallFrame *, channel->call_stack, &frame);
size_t size = kv_size(channel->call_stack); event_poll_until(-1, frame.returned);
(void)kv_pop(channel->call_stack);
do {
event_poll(-1, sources);
} while (
// Continue running if ...
channel->enabled && // the channel is still enabled
kv_size(channel->call_stack) >= size); // the call didn't return
if (frame.errored) { if (frame.errored) {
api_set_error(err, Exception, "%s", frame.result.data.string.data); api_set_error(err, Exception, "%s", frame.result.data.string.data);
@ -236,7 +236,7 @@ void channel_subscribe(uint64_t id, char *event)
{ {
Channel *channel; Channel *channel;
if (!(channel = pmap_get(uint64_t)(channels, id)) || !channel->enabled) { if (!(channel = pmap_get(uint64_t)(channels, id)) || channel->closed) {
abort(); abort();
} }
@ -258,7 +258,7 @@ void channel_unsubscribe(uint64_t id, char *event)
{ {
Channel *channel; Channel *channel;
if (!(channel = pmap_get(uint64_t)(channels, id)) || !channel->enabled) { if (!(channel = pmap_get(uint64_t)(channels, id)) || channel->closed) {
abort(); abort();
} }
@ -273,12 +273,11 @@ bool channel_close(uint64_t id)
{ {
Channel *channel; Channel *channel;
if (!(channel = pmap_get(uint64_t)(channels, id)) || !channel->enabled) { if (!(channel = pmap_get(uint64_t)(channels, id)) || channel->closed) {
return false; return false;
} }
channel_kill(channel); close_channel(channel);
channel->enabled = false;
return true; return true;
} }
@ -291,8 +290,7 @@ static void channel_from_stdio(void)
// read stream // read stream
channel->data.streams.read = rstream_new(parse_msgpack, channel->data.streams.read = rstream_new(parse_msgpack,
rbuffer_new(CHANNEL_BUFFER_SIZE), rbuffer_new(CHANNEL_BUFFER_SIZE),
channel, channel);
NULL);
rstream_set_file(channel->data.streams.read, 0); rstream_set_file(channel->data.streams.read, 0);
rstream_start(channel->data.streams.read); rstream_start(channel->data.streams.read);
// write stream // write stream
@ -320,23 +318,20 @@ static void job_err(RStream *rstream, void *data, bool eof)
} }
} }
static void job_exit(Job *job, void *data)
{
free_channel((Channel *)data);
}
static void parse_msgpack(RStream *rstream, void *data, bool eof) static void parse_msgpack(RStream *rstream, void *data, bool eof)
{ {
Channel *channel = data; Channel *channel = data;
channel->rpc_call_level++;
if (eof) { if (eof) {
char buf[256];
snprintf(buf,
sizeof(buf),
"Before returning from a RPC call, channel %" PRIu64 " was "
"closed by the client",
channel->id);
call_set_error(channel, buf);
goto end; goto end;
} }
uint32_t count = rstream_pending(rstream); size_t count = rstream_pending(rstream);
DLOG("Feeding the msgpack parser with %u bytes of data from RStream(%p)", DLOG("Feeding the msgpack parser with %u bytes of data from RStream(%p)",
count, count,
rstream); rstream);
@ -355,7 +350,7 @@ static void parse_msgpack(RStream *rstream, void *data, bool eof)
MSGPACK_UNPACK_SUCCESS) { MSGPACK_UNPACK_SUCCESS) {
if (kv_size(channel->call_stack) && is_rpc_response(&unpacked.data)) { if (kv_size(channel->call_stack) && is_rpc_response(&unpacked.data)) {
if (is_valid_rpc_response(&unpacked.data, channel)) { if (is_valid_rpc_response(&unpacked.data, channel)) {
call_stack_pop(&unpacked.data, channel); complete_call(&unpacked.data, channel);
} else { } else {
char buf[256]; char buf[256];
snprintf(buf, snprintf(buf,
@ -371,12 +366,7 @@ static void parse_msgpack(RStream *rstream, void *data, bool eof)
goto end; goto end;
} }
// Perform the call handle_request(channel, &unpacked.data);
WBuffer *resp = msgpack_rpc_call(channel->id, &unpacked.data, &out_buffer);
// write the response
if (!channel_write(channel, resp)) {
goto end;
}
} }
if (result == MSGPACK_UNPACK_NOMEM_ERROR) { if (result == MSGPACK_UNPACK_NOMEM_ERROR) {
@ -398,13 +388,92 @@ static void parse_msgpack(RStream *rstream, void *data, bool eof)
} }
end: end:
channel->rpc_call_level--; if (eof && !channel->is_job && !kv_size(channel->call_stack)) {
if (!channel->enabled && !kv_size(channel->call_stack)) { // The free_channel call is deferred for jobs because it's possible that
// Now it's safe to destroy the channel // job_stderr will called after this. For non-job channels, this is the
close_channel(channel); // last callback so it must be freed now.
free_channel(channel);
} }
} }
static void handle_request(Channel *channel, msgpack_object *request)
FUNC_ATTR_NONNULL_ALL
{
uint64_t request_id;
Error error = ERROR_INIT;
msgpack_rpc_validate(&request_id, request, &error);
if (error.set) {
// Validation failed, send response with error
channel_write(channel,
serialize_response(request_id, &error, NIL, &out_buffer));
return;
}
// Retrieve the request handler
MsgpackRpcRequestHandler handler;
msgpack_object method = request->via.array.ptr[2];
if (method.type == MSGPACK_OBJECT_BIN || method.type == MSGPACK_OBJECT_STR) {
handler = msgpack_rpc_get_handler_for(method.via.bin.ptr,
method.via.bin.size);
} else {
handler.fn = msgpack_rpc_handle_missing_method;
handler.defer = false;
}
Array args;
msgpack_rpc_to_array(request->via.array.ptr + 3, &args);
if (kv_size(channel->call_stack) || !handler.defer) {
call_request_handler(channel, handler, args, request_id);
return;
}
// Defer calling the request handler.
RequestEvent *event_data = kmp_alloc(RequestEventPool, request_event_pool);
event_data->channel = channel;
event_data->handler = handler;
event_data->args = args;
event_data->request_id = request_id;
event_push((Event) {
.handler = on_request_event,
.data = event_data
});
}
static void on_request_event(Event event)
{
RequestEvent *e = event.data;
call_request_handler(e->channel, e->handler, e->args, e->request_id);
kmp_free(RequestEventPool, request_event_pool, e);
}
static void call_request_handler(Channel *channel,
MsgpackRpcRequestHandler handler,
Array args,
uint64_t request_id)
{
Error error = ERROR_INIT;
Object result = handler.fn(channel->id, request_id, args, &error);
// send the response
msgpack_packer response;
msgpack_packer_init(&response, &out_buffer, msgpack_sbuffer_write);
if (error.set) {
ELOG("Error dispatching msgpack-rpc call: %s(request: id %" PRIu64 ")",
error.msg,
request_id);
channel_write(channel,
serialize_response(request_id, &error, NIL, &out_buffer));
}
DLOG("Successfully completed mspgack-rpc call(request id: %" PRIu64 ")",
request_id);
channel_write(channel,
serialize_response(request_id, &error, result, &out_buffer));
}
static bool channel_write(Channel *channel, WBuffer *buffer) static bool channel_write(Channel *channel, WBuffer *buffer)
{ {
bool success; bool success;
@ -501,26 +570,11 @@ static void unsubscribe(Channel *channel, char *event)
free(event_string); free(event_string);
} }
/// Close the channel streams/job. The channel resources will be freed by
/// free_channel later.
static void close_channel(Channel *channel) static void close_channel(Channel *channel)
{ {
pmap_del(uint64_t)(channels, channel->id); channel->closed = true;
msgpack_unpacker_free(channel->unpacker);
// Unsubscribe from all events
char *event_string;
map_foreach_value(channel->subscribed_events, event_string, {
unsubscribe(channel, event_string);
});
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->is_job) {
if (channel->data.job) { if (channel->data.job) {
job_stop(channel->data.job); job_stop(channel->data.job);
@ -537,6 +591,22 @@ static void channel_kill(Channel *channel)
} }
} }
static void free_channel(Channel *channel)
{
pmap_del(uint64_t)(channels, channel->id);
msgpack_unpacker_free(channel->unpacker);
// Unsubscribe from all events
char *event_string;
map_foreach_value(channel->subscribed_events, event_string, {
unsubscribe(channel, event_string);
});
pmap_free(cstr_t)(channel->subscribed_events);
kv_destroy(channel->call_stack);
free(channel);
}
static void close_cb(uv_handle_t *handle) static void close_cb(uv_handle_t *handle)
{ {
free(handle->data); free(handle->data);
@ -546,8 +616,7 @@ static void close_cb(uv_handle_t *handle)
static Channel *register_channel(void) static Channel *register_channel(void)
{ {
Channel *rv = xmalloc(sizeof(Channel)); Channel *rv = xmalloc(sizeof(Channel));
rv->enabled = true; rv->closed = false;
rv->rpc_call_level = 0;
rv->unpacker = msgpack_unpacker_new(MSGPACK_UNPACKER_INIT_BUFFER_SIZE); rv->unpacker = msgpack_unpacker_new(MSGPACK_UNPACKER_INIT_BUFFER_SIZE);
rv->id = next_id++; rv->id = next_id++;
rv->subscribed_events = pmap_new(cstr_t)(); rv->subscribed_events = pmap_new(cstr_t)();
@ -574,9 +643,11 @@ static bool is_valid_rpc_response(msgpack_object *obj, Channel *channel)
kv_size(channel->call_stack) - 1)->request_id; kv_size(channel->call_stack) - 1)->request_id;
} }
static void call_stack_pop(msgpack_object *obj, Channel *channel) static void complete_call(msgpack_object *obj, Channel *channel)
{ {
ChannelCallFrame *frame = kv_pop(channel->call_stack); ChannelCallFrame *frame = kv_A(channel->call_stack,
kv_size(channel->call_stack) - 1);
frame->returned = true;
frame->errored = obj->via.array.ptr[2].type != MSGPACK_OBJECT_NIL; frame->errored = obj->via.array.ptr[2].type != MSGPACK_OBJECT_NIL;
if (frame->errored) { if (frame->errored) {
@ -589,10 +660,11 @@ static void call_stack_pop(msgpack_object *obj, Channel *channel)
static void call_set_error(Channel *channel, char *msg) static void call_set_error(Channel *channel, char *msg)
{ {
for (size_t i = 0; i < kv_size(channel->call_stack); i++) { for (size_t i = 0; i < kv_size(channel->call_stack); i++) {
ChannelCallFrame *frame = kv_pop(channel->call_stack); ChannelCallFrame *frame = kv_A(channel->call_stack, i);
frame->returned = true;
frame->errored = true; frame->errored = true;
frame->result = STRING_OBJ(cstr_to_string(msg)); frame->result = STRING_OBJ(cstr_to_string(msg));
} }
channel->enabled = false; close_channel(channel);
} }

View File

@ -1,5 +1,5 @@
#ifndef NVIM_OS_CHANNEL_H #ifndef NVIM_MSGPACK_RPC_CHANNEL_H
#define NVIM_OS_CHANNEL_H #define NVIM_MSGPACK_RPC_CHANNEL_H
#include <stdbool.h> #include <stdbool.h>
#include <uv.h> #include <uv.h>
@ -10,6 +10,6 @@
#define METHOD_MAXLEN 512 #define METHOD_MAXLEN 512
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/channel.h.generated.h" # include "msgpack_rpc/channel.h.generated.h"
#endif #endif
#endif // NVIM_OS_CHANNEL_H #endif // NVIM_MSGPACK_RPC_CHANNEL_H

View File

@ -1,29 +1,23 @@
#ifndef NVIM_OS_MSGPACK_RPC_H #ifndef NVIM_MSGPACK_RPC_DEFS_H
#define NVIM_OS_MSGPACK_RPC_H #define NVIM_MSGPACK_RPC_DEFS_H
#include <stdint.h>
#include <msgpack.h> #include <msgpack.h>
#include "nvim/func_attr.h"
#include "nvim/api/private/defs.h"
#include "nvim/os/wstream.h"
typedef enum {
kUnpackResultOk, /// Successfully parsed a document
kUnpackResultFail, /// Got unexpected input
kUnpackResultNeedMore /// Need more data
} UnpackResult;
/// The rpc_method_handlers table, used in msgpack_rpc_dispatch(), stores /// The rpc_method_handlers table, used in msgpack_rpc_dispatch(), stores
/// functions of this type. /// functions of this type.
typedef Object (*rpc_method_handler_fn)(uint64_t channel_id, typedef struct {
msgpack_object *req, Object (*fn)(uint64_t channel_id,
Error *error); uint64_t request_id,
Array args,
Error *error);
bool defer; // Should the call be deferred to the main loop? This should
// be true if the function mutates editor data structures such
// as buffers, windows, tabs, or if it executes vimscript code.
} MsgpackRpcRequestHandler;
/// Initializes the msgpack-rpc method table /// Initializes the msgpack-rpc method table
void msgpack_rpc_init(void); void msgpack_rpc_init_method_table(void);
void msgpack_rpc_init_function_metadata(Dictionary *metadata); void msgpack_rpc_init_function_metadata(Dictionary *metadata);
@ -43,9 +37,7 @@ Object msgpack_rpc_dispatch(uint64_t channel_id,
Error *error) Error *error)
FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_NONNULL_ARG(3); FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_NONNULL_ARG(3);
MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name,
#ifdef INCLUDE_GENERATED_DECLARATIONS size_t name_len)
# include "os/msgpack_rpc.h.generated.h" FUNC_ATTR_NONNULL_ARG(1);
#endif #endif // NVIM_MSGPACK_RPC_DEFS_H
#endif // NVIM_OS_MSGPACK_RPC_H

View File

@ -1,14 +1,18 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <inttypes.h>
#include <msgpack.h> #include <msgpack.h>
#include "nvim/os/msgpack_rpc_helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/msgpack_rpc/helpers.h"
#include "nvim/msgpack_rpc/defs.h"
#include "nvim/vim.h" #include "nvim/vim.h"
#include "nvim/log.h"
#include "nvim/memory.h" #include "nvim/memory.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/msgpack_rpc_helpers.c.generated.h" # include "msgpack_rpc/helpers.c.generated.h"
#endif #endif
static msgpack_zone zone; static msgpack_zone zone;
@ -136,10 +140,13 @@ bool msgpack_rpc_to_object(msgpack_object *obj, Object *arg)
case MSGPACK_OBJECT_EXT: case MSGPACK_OBJECT_EXT:
switch (obj->via.ext.type) { switch (obj->via.ext.type) {
case kObjectTypeBuffer: case kObjectTypeBuffer:
arg->type = kObjectTypeBuffer;
return msgpack_rpc_to_buffer(obj, &arg->data.buffer); return msgpack_rpc_to_buffer(obj, &arg->data.buffer);
case kObjectTypeWindow: case kObjectTypeWindow:
arg->type = kObjectTypeWindow;
return msgpack_rpc_to_window(obj, &arg->data.window); return msgpack_rpc_to_window(obj, &arg->data.window);
case kObjectTypeTabpage: case kObjectTypeTabpage:
arg->type = kObjectTypeTabpage;
return msgpack_rpc_to_tabpage(obj, &arg->data.tabpage); return msgpack_rpc_to_tabpage(obj, &arg->data.tabpage);
} }
default: default:
@ -287,3 +294,136 @@ void msgpack_rpc_from_dictionary(Dictionary result, msgpack_packer *res)
msgpack_rpc_from_object(result.items[i].value, res); msgpack_rpc_from_object(result.items[i].value, res);
} }
} }
/// Finishes the msgpack-rpc call with an error message.
///
/// @param msg The error message
/// @param res A packer that contains the response
void msgpack_rpc_error(char *msg, msgpack_packer *res)
FUNC_ATTR_NONNULL_ALL
{
size_t len = strlen(msg);
// error message
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,
uint64_t request_id,
Array args,
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,
Array args,
msgpack_sbuffer *sbuffer,
size_t refcount)
FUNC_ATTR_NONNULL_ARG(4)
{
msgpack_packer pac;
msgpack_packer_init(&pac, sbuffer, msgpack_sbuffer_write);
msgpack_pack_array(&pac, request_id ? 4 : 3);
msgpack_pack_int(&pac, request_id ? 0 : 2);
if (request_id) {
msgpack_pack_uint64(&pac, request_id);
}
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);
api_free_array(args);
msgpack_sbuffer_clear(sbuffer);
return rv;
}
/// Serializes a msgpack-rpc response
WBuffer *serialize_response(uint64_t response_id,
Error *err,
Object arg,
msgpack_sbuffer *sbuffer)
FUNC_ATTR_NONNULL_ARG(2, 4)
{
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, response_id);
if (err->set) {
// error represented by a [type, message] array
msgpack_pack_array(&pac, 2);
msgpack_rpc_from_integer(err->type, &pac);
msgpack_rpc_from_string(cstr_as_string(err->msg), &pac);
// Nil result
msgpack_pack_nil(&pac);
} else {
// Nil error
msgpack_pack_nil(&pac);
// Return value
msgpack_rpc_from_object(arg, &pac);
}
WBuffer *rv = wstream_new_buffer(xmemdup(sbuffer->data, sbuffer->size),
sbuffer->size,
1, // responses only go though 1 channel
free);
api_free_object(arg);
msgpack_sbuffer_clear(sbuffer);
return rv;
}
void msgpack_rpc_validate(uint64_t *response_id,
msgpack_object *req,
Error *err)
{
// response id not known yet
*response_id = 0;
// Validate the basic structure of the msgpack-rpc payload
if (req->type != MSGPACK_OBJECT_ARRAY) {
api_set_error(err, Validation, _("Request is not an array"));
}
if (req->via.array.size != 4) {
api_set_error(err, Validation, _("Request array size should be 4"));
}
if (req->via.array.ptr[1].type != MSGPACK_OBJECT_POSITIVE_INTEGER) {
api_set_error(err, Validation, _("Id must be a positive integer"));
}
// Set the response id, which is the same as the request
*response_id = req->via.array.ptr[1].via.u64;
if (req->via.array.ptr[0].type != MSGPACK_OBJECT_POSITIVE_INTEGER) {
api_set_error(err, Validation, _("Message type must be an integer"));
}
if (req->via.array.ptr[0].via.u64 != 0) {
api_set_error(err, Validation, _("Message type must be 0"));
}
if (req->via.array.ptr[2].type != MSGPACK_OBJECT_BIN
&& req->via.array.ptr[2].type != MSGPACK_OBJECT_STR) {
api_set_error(err, Validation, _("Method must be a string"));
}
if (req->via.array.ptr[3].type != MSGPACK_OBJECT_ARRAY) {
api_set_error(err, Validation, _("Paremeters must be an array"));
}
}

View File

@ -0,0 +1,17 @@
#ifndef NVIM_MSGPACK_RPC_HELPERS_H
#define NVIM_MSGPACK_RPC_HELPERS_H
#include <stdint.h>
#include <stdbool.h>
#include <msgpack.h>
#include "nvim/os/wstream.h"
#include "nvim/api/private/defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "msgpack_rpc/helpers.h.generated.h"
#endif
#endif // NVIM_MSGPACK_RPC_HELPERS_H

View File

@ -5,8 +5,8 @@
#include <uv.h> #include <uv.h>
#include "nvim/os/channel.h" #include "nvim/msgpack_rpc/channel.h"
#include "nvim/os/server.h" #include "nvim/msgpack_rpc/server.h"
#include "nvim/os/os.h" #include "nvim/os/os.h"
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/vim.h" #include "nvim/vim.h"
@ -46,7 +46,7 @@ typedef struct {
static PMap(cstr_t) *servers = NULL; static PMap(cstr_t) *servers = NULL;
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/server.c.generated.h" # include "msgpack_rpc/server.c.generated.h"
#endif #endif
/// Initializes the module /// Initializes the module
@ -119,7 +119,8 @@ int server_start(const char *endpoint)
ip_end = strchr(addr, NUL); ip_end = strchr(addr, NUL);
} }
uint32_t addr_len = ip_end - addr; // (ip_end - addr) is always > 0, so convert to size_t
size_t addr_len = (size_t)(ip_end - addr);
if (addr_len > sizeof(ip) - 1) { if (addr_len > sizeof(ip) - 1) {
// Maximum length of an IP address buffer is 15(eg: 255.255.255.255) // Maximum length of an IP address buffer is 15(eg: 255.255.255.255)

View File

@ -0,0 +1,7 @@
#ifndef NVIM_MSGPACK_RPC_SERVER_H
#define NVIM_MSGPACK_RPC_SERVER_H
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "msgpack_rpc/server.h.generated.h"
#endif
#endif // NVIM_MSGPACK_RPC_SERVER_H

View File

@ -7,8 +7,10 @@
#include "nvim/os/event.h" #include "nvim/os/event.h"
#include "nvim/os/input.h" #include "nvim/os/input.h"
#include "nvim/os/channel.h" #include "nvim/msgpack_rpc/defs.h"
#include "nvim/os/server.h" #include "nvim/msgpack_rpc/channel.h"
#include "nvim/msgpack_rpc/server.h"
#include "nvim/msgpack_rpc/helpers.h"
#include "nvim/os/provider.h" #include "nvim/os/provider.h"
#include "nvim/os/signal.h" #include "nvim/os/signal.h"
#include "nvim/os/rstream.h" #include "nvim/os/rstream.h"
@ -25,25 +27,22 @@ KLIST_INIT(Event, Event, _destroy_event)
typedef struct { typedef struct {
bool timed_out; bool timed_out;
int32_t ms; int ms;
uv_timer_t *timer; uv_timer_t *timer;
} TimerData; } TimerData;
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/event.c.generated.h" # include "os/event.c.generated.h"
#endif #endif
static klist_t(Event) *deferred_events, *immediate_events; static klist_t(Event) *pending_events;
// NULL-terminated array of event sources that we should process immediately.
//
// Events from sources that are not contained in this array are processed
// later when `event_process` is called
static EventSource *immediate_sources = NULL;
void event_init(void) void event_init(void)
{ {
// early msgpack-rpc initialization
msgpack_rpc_init_method_table();
msgpack_rpc_helpers_init();
// Initialize the event queues // Initialize the event queues
deferred_events = kl_init(Event); pending_events = kl_init(Event);
immediate_events = kl_init(Event);
// Initialize input events // Initialize input events
input_init(); input_init();
// Timer to wake the event loop if a timeout argument is passed to // Timer to wake the event loop if a timeout argument is passed to
@ -52,9 +51,8 @@ void event_init(void)
signal_init(); signal_init();
// Jobs // Jobs
job_init(); job_init();
// Channels // finish mspgack-rpc initialization
channel_init(); channel_init();
// Servers
server_init(); server_init();
// Providers // Providers
provider_init(); provider_init();
@ -68,8 +66,7 @@ void event_teardown(void)
} }
// Wait for some event // Wait for some event
bool event_poll(int32_t ms, EventSource sources[]) void event_poll(int ms)
FUNC_ATTR_NONNULL_ARG(2)
{ {
uv_run_mode run_mode = UV_RUN_ONCE; uv_run_mode run_mode = UV_RUN_ONCE;
@ -100,18 +97,7 @@ bool event_poll(int32_t ms, EventSource sources[])
run_mode = UV_RUN_NOWAIT; run_mode = UV_RUN_NOWAIT;
} }
size_t processed_events; loop(run_mode);
do {
// Run one event loop iteration, blocking for events if run_mode is
// UV_RUN_ONCE
processed_events = loop(run_mode, sources);
} while (
// Continue running if ...
!processed_events && // we didn't process any immediate events
!event_has_deferred() && // no events are waiting to be processed
run_mode != UV_RUN_NOWAIT && // ms != 0
!timer_data.timed_out); // we didn't get a timeout
if (!(--recursive)) { if (!(--recursive)) {
// Again, only stop when we leave the top-level invocation // Again, only stop when we leave the top-level invocation
@ -123,68 +109,29 @@ bool event_poll(int32_t ms, EventSource sources[])
// once more to let libuv perform it's cleanup // once more to let libuv perform it's cleanup
uv_close((uv_handle_t *)&timer, NULL); uv_close((uv_handle_t *)&timer, NULL);
uv_close((uv_handle_t *)&timer_prepare, NULL); uv_close((uv_handle_t *)&timer_prepare, NULL);
processed_events += loop(UV_RUN_NOWAIT, sources); loop(UV_RUN_NOWAIT);
} }
return !timer_data.timed_out && (processed_events || event_has_deferred());
} }
bool event_has_deferred(void) bool event_has_deferred(void)
{ {
return !kl_empty(deferred_events); return !kl_empty(pending_events);
} }
// Queue an event // Queue an event
void event_push(Event event) void event_push(Event event)
{ {
bool defer = true; *kl_pushp(Event, pending_events) = event;
if (immediate_sources) {
size_t i;
EventSource src;
for (src = immediate_sources[i = 0]; src; src = immediate_sources[++i]) {
if (src == event.source) {
defer = false;
break;
}
}
}
*kl_pushp(Event, defer ? deferred_events : immediate_events) = event;
} }
void event_process(void) void event_process(void)
{ {
process_from(deferred_events);
}
// Runs the appropriate action for each queued event
static size_t process_from(klist_t(Event) *queue)
{
size_t count = 0;
Event event; Event event;
while (kl_shift(Event, queue, &event) == 0) { while (kl_shift(Event, pending_events, &event) == 0) {
switch (event.type) { event.handler(event);
case kEventSignal:
signal_handle(event);
break;
case kEventRStreamData:
rstream_read_event(event);
break;
case kEventJobExit:
job_exit_event(event);
break;
default:
abort();
}
count++;
} }
DLOG("Processed %u events", count);
return count;
} }
// Set a flag in the `event_poll` loop for signaling of a timeout // Set a flag in the `event_poll` loop for signaling of a timeout
@ -202,42 +149,9 @@ static void timer_prepare_cb(uv_prepare_t *handle)
uv_prepare_stop(handle); uv_prepare_stop(handle);
} }
static void requeue_deferred_events(void) static void loop(uv_run_mode run_mode)
{ {
size_t remaining = deferred_events->size;
DLOG("Number of deferred events: %u", remaining);
while (remaining--) {
// Re-push each deferred event to ensure it will be in the right queue
Event event;
kl_shift(Event, deferred_events, &event);
event_push(event);
DLOG("Re-queueing event");
}
DLOG("Number of deferred events: %u", deferred_events->size);
}
static size_t loop(uv_run_mode run_mode, EventSource *sources)
{
size_t count;
immediate_sources = sources;
// It's possible that some events from the immediate sources are waiting
// in the deferred queue. If so, move them to the immediate queue so they
// will be processed in order of arrival by the next `process_from` call.
requeue_deferred_events();
count = process_from(immediate_events);
if (count) {
// No need to enter libuv, events were already processed
return count;
}
DLOG("Enter event loop"); DLOG("Enter event loop");
uv_run(uv_default_loop(), run_mode); uv_run(uv_default_loop(), run_mode);
DLOG("Exit event loop"); DLOG("Exit event loop");
immediate_sources = NULL;
count = process_from(immediate_events);
return count;
} }

View File

@ -6,6 +6,27 @@
#include "nvim/os/event_defs.h" #include "nvim/os/event_defs.h"
#include "nvim/os/job_defs.h" #include "nvim/os/job_defs.h"
#include "nvim/os/time.h"
// Poll for events until a condition is true or a timeout has passed
#define event_poll_until(timeout, condition) \
do { \
int remaining = timeout; \
uint64_t before = (remaining > 0) ? os_hrtime() : 0; \
while (!(condition)) { \
event_poll(remaining); \
if (remaining == 0) { \
break; \
} else if (remaining > 0) { \
uint64_t now = os_hrtime(); \
remaining -= (int) ((now - before) / 1000000); \
before = now; \
if (remaining <= 0) { \
break; \
} \
} \
} \
} while (0)
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/event.h.generated.h" # include "os/event.h.generated.h"

View File

@ -6,25 +6,12 @@
#include "nvim/os/job_defs.h" #include "nvim/os/job_defs.h"
#include "nvim/os/rstream_defs.h" #include "nvim/os/rstream_defs.h"
typedef void * EventSource; typedef struct event Event;
typedef void (*event_handler)(Event event);
typedef enum { struct event {
kEventSignal, void *data;
kEventRStreamData, event_handler handler;
kEventJobExit };
} EventType;
typedef struct {
EventSource source;
EventType type;
union {
int signum;
struct {
RStream *ptr;
bool eof;
} rstream;
Job *job;
} data;
} Event;
#endif // NVIM_OS_EVENT_DEFS_H #endif // NVIM_OS_EVENT_DEFS_H

View File

@ -7,7 +7,6 @@
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/os/input.h" #include "nvim/os/input.h"
#include "nvim/os/event.h" #include "nvim/os/event.h"
#include "nvim/os/signal.h"
#include "nvim/os/rstream_defs.h" #include "nvim/os/rstream_defs.h"
#include "nvim/os/rstream.h" #include "nvim/os/rstream.h"
#include "nvim/ascii.h" #include "nvim/ascii.h"
@ -48,10 +47,7 @@ void input_init(void)
} }
read_buffer = rbuffer_new(READ_BUFFER_SIZE); read_buffer = rbuffer_new(READ_BUFFER_SIZE);
read_stream = rstream_new(read_cb, read_stream = rstream_new(read_cb, read_buffer, NULL);
read_buffer,
NULL,
NULL);
rstream_set_file(read_stream, read_cmd_fd); rstream_set_file(read_stream, read_cmd_fd);
} }
@ -76,7 +72,7 @@ void input_stop(void)
} }
// Low level input function. // Low level input function.
int os_inchar(uint8_t *buf, int maxlen, int32_t ms, int tb_change_cnt) int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt)
{ {
InbufPollResult result; InbufPollResult result;
@ -90,7 +86,7 @@ int os_inchar(uint8_t *buf, int maxlen, int32_t ms, int tb_change_cnt)
return 0; return 0;
} }
} else { } else {
if ((result = inbuf_poll(p_ut)) == kInputNone) { if ((result = inbuf_poll((int)p_ut)) == kInputNone) {
if (trigger_cursorhold() && maxlen >= 3 if (trigger_cursorhold() && maxlen >= 3
&& !typebuf_changed(tb_change_cnt)) { && !typebuf_changed(tb_change_cnt)) {
buf[0] = K_SPECIAL; buf[0] = K_SPECIAL;
@ -120,7 +116,9 @@ int os_inchar(uint8_t *buf, int maxlen, int32_t ms, int tb_change_cnt)
} }
convert_input(); convert_input();
return rbuffer_read(input_buffer, (char *)buf, maxlen); // Safe to convert rbuffer_read to int, it will never overflow since
// we use relatively small buffers.
return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen);
} }
// Check if a character is available for reading // Check if a character is available for reading
@ -167,23 +165,14 @@ void input_buffer_restore(String str)
free(str.data); free(str.data);
} }
static bool input_poll(int32_t ms) static bool input_poll(int ms)
{ {
if (embedded_mode) { event_poll_until(ms, input_ready());
EventSource input_sources[] = { signal_event_source(), NULL }; return input_ready();
return event_poll(ms, input_sources);
}
EventSource input_sources[] = {
rstream_event_source(read_stream),
NULL
};
return input_ready() || event_poll(ms, input_sources) || input_ready();
} }
// This is a replacement for the old `WaitForChar` function in os_unix.c // This is a replacement for the old `WaitForChar` function in os_unix.c
static InbufPollResult inbuf_poll(int32_t ms) static InbufPollResult inbuf_poll(int ms)
{ {
if (typebuf_was_filled || rbuffer_pending(input_buffer)) { if (typebuf_was_filled || rbuffer_pending(input_buffer)) {
return kInputAvail; return kInputAvail;
@ -235,7 +224,7 @@ static void read_cb(RStream *rstream, void *data, bool at_eof)
static void convert_input(void) static void convert_input(void)
{ {
if (!rbuffer_available(input_buffer)) { if (embedded_mode || !rbuffer_available(input_buffer)) {
// No input buffer space // No input buffer space
return; return;
} }
@ -273,9 +262,9 @@ static void convert_input(void)
char *inbuf = rbuffer_read_ptr(input_buffer); char *inbuf = rbuffer_read_ptr(input_buffer);
size_t count = rbuffer_pending(input_buffer), consume_count = 0; size_t count = rbuffer_pending(input_buffer), consume_count = 0;
for (int i = count - 1; i >= 0; i--) { for (int i = (int)count - 1; i >= 0; i--) {
if (inbuf[i] == 3) { if (inbuf[i] == 3) {
consume_count = i + 1; consume_count = (size_t)i;
break; break;
} }
} }
@ -304,6 +293,10 @@ static int push_event_key(uint8_t *buf, int maxlen)
// Check if there's pending input // Check if there's pending input
static bool input_ready(void) static bool input_ready(void)
{ {
return rstream_pending(read_stream) > 0 || eof; return typebuf_was_filled || // API call filled typeahead
event_has_deferred() || // Events must be processed
(!embedded_mode && (
rstream_pending(read_stream) > 0 || // Stdin input
eof)); // Stdin closed
} }

View File

@ -12,9 +12,7 @@
#include "nvim/os/wstream_defs.h" #include "nvim/os/wstream_defs.h"
#include "nvim/os/event.h" #include "nvim/os/event.h"
#include "nvim/os/event_defs.h" #include "nvim/os/event_defs.h"
#include "nvim/os/time.h"
#include "nvim/os/shell.h" #include "nvim/os/shell.h"
#include "nvim/os/signal.h"
#include "nvim/vim.h" #include "nvim/vim.h"
#include "nvim/memory.h" #include "nvim/memory.h"
#include "nvim/term.h" #include "nvim/term.h"
@ -99,25 +97,28 @@ void job_teardown(void)
// their status with `wait` or handling SIGCHLD. libuv does that // their status with `wait` or handling SIGCHLD. libuv does that
// automatically (and then calls `exit_cb`) but we have to give it a chance // automatically (and then calls `exit_cb`) but we have to give it a chance
// by running the loop one more time // by running the loop one more time
uv_run(uv_default_loop(), UV_RUN_NOWAIT); event_poll(0);
// Prepare to start shooting // Prepare to start shooting
for (i = 0; i < MAX_RUNNING_JOBS; i++) { for (i = 0; i < MAX_RUNNING_JOBS; i++) {
if ((job = table[i]) == NULL) { job = table[i];
continue;
}
// Still alive // Still alive
while (is_alive(job) && remaining_tries--) { while (job && is_alive(job) && remaining_tries--) {
os_delay(50, 0); os_delay(50, 0);
// Acknowledge child exits // Acknowledge child exits
uv_run(uv_default_loop(), UV_RUN_NOWAIT); event_poll(0);
// It's possible that the event_poll call removed the job from the table,
// reset 'job' so the next iteration won't run in that case.
job = table[i];
} }
if (is_alive(job)) { if (job && is_alive(job)) {
uv_process_kill(&job->proc, SIGKILL); uv_process_kill(&job->proc, SIGKILL);
} }
} }
// Last run to ensure all children were removed
event_poll(0);
} }
/// Tries to start a new job. /// Tries to start a new job.
@ -213,14 +214,8 @@ Job *job_start(char **argv,
job->in = wstream_new(maxmem); job->in = wstream_new(maxmem);
wstream_set_stream(job->in, (uv_stream_t *)&job->proc_stdin); wstream_set_stream(job->in, (uv_stream_t *)&job->proc_stdin);
// Start the readable streams // Start the readable streams
job->out = rstream_new(read_cb, job->out = rstream_new(read_cb, rbuffer_new(JOB_BUFFER_SIZE), job);
rbuffer_new(JOB_BUFFER_SIZE), job->err = rstream_new(read_cb, rbuffer_new(JOB_BUFFER_SIZE), job);
job,
job_event_source(job));
job->err = rstream_new(read_cb,
rbuffer_new(JOB_BUFFER_SIZE),
job,
job_event_source(job));
rstream_set_stream(job->out, (uv_stream_t *)&job->proc_stdout); rstream_set_stream(job->out, (uv_stream_t *)&job->proc_stdout);
rstream_set_stream(job->err, (uv_stream_t *)&job->proc_stderr); rstream_set_stream(job->err, (uv_stream_t *)&job->proc_stderr);
rstream_start(job->out); rstream_start(job->out);
@ -277,47 +272,33 @@ int job_wait(Job *job, int ms) FUNC_ATTR_NONNULL_ALL
int old_mode = cur_tmode; int old_mode = cur_tmode;
settmode(TMODE_COOK); settmode(TMODE_COOK);
EventSource sources[] = {job_event_source(job), signal_event_source(), NULL}; // Increase pending_refs to stop the exit_cb from being called, which
// could result in the job being freed before we have a chance
// to get the status.
job->pending_refs++;
event_poll_until(ms,
// Until...
got_int || // interrupted by the user
job->pending_refs == 1); // job exited
job->pending_refs--;
// keep track of the elapsed time if ms > 0 // we'll assume that a user frantically hitting interrupt doesn't like
uint64_t before = (ms > 0) ? os_hrtime() : 0; // the current job. Signal that it has to be killed.
if (got_int) {
while (1) { job_stop(job);
// check if the job has exited (and the status is available). event_poll(0);
if (job->pending_refs == 0) {
break;
}
event_poll(ms, sources);
// we'll assume that a user frantically hitting interrupt doesn't like
// the current job. Signal that it has to be killed.
if (got_int) {
job_stop(job);
}
if (ms == 0) {
break;
}
// check if the poll timed out, if not, decrease the ms to wait for the
// next run
if (ms > 0) {
uint64_t now = os_hrtime();
ms -= (int) ((now - before) / 1000000);
before = now;
// if the time elapsed is greater than the `ms` wait time, break
if (ms <= 0) {
break;
}
}
} }
settmode(old_mode); settmode(old_mode);
// return -1 for a timeout, the job status otherwise if (!job->pending_refs) {
return (job->pending_refs) ? -1 : (int) job->status; int status = (int) job->status;
job_exit_callback(job);
return status;
}
// return -1 for a timeout
return -1;
} }
/// Close the pipe used to write to the job. /// Close the pipe used to write to the job.
@ -369,14 +350,6 @@ bool job_write(Job *job, WBuffer *buffer)
return wstream_write(job->in, buffer); return wstream_write(job->in, buffer);
} }
/// Runs the read callback associated with the job exit event
///
/// @param event Object containing data necessary to invoke the callback
void job_exit_event(Event event)
{
job_exit_callback(event.data.job);
}
/// Get the job id /// Get the job id
/// ///
/// @param job A pointer to the job /// @param job A pointer to the job
@ -395,11 +368,6 @@ void *job_data(Job *job)
return job->data; return job->data;
} }
EventSource job_event_source(Job *job)
{
return job;
}
static void job_exit_callback(Job *job) static void job_exit_callback(Job *job)
{ {
// Free the slot now, 'exit_cb' may want to start another job to replace // Free the slot now, 'exit_cb' may want to start another job to replace
@ -470,7 +438,7 @@ static void read_cb(RStream *rstream, void *data, bool eof)
} }
if (eof && --job->pending_refs == 0) { if (eof && --job->pending_refs == 0) {
emit_exit_event(job); job_exit_callback(job);
} }
} }
@ -481,20 +449,10 @@ static void exit_cb(uv_process_t *proc, int64_t status, int term_signal)
job->status = status; job->status = status;
if (--job->pending_refs == 0) { if (--job->pending_refs == 0) {
emit_exit_event(job); job_exit_callback(job);
} }
} }
static void emit_exit_event(Job *job)
{
Event event = {
.source = job_event_source(job),
.type = kEventJobExit,
.data.job = job
};
event_push(event);
}
static void close_cb(uv_handle_t *handle) static void close_cb(uv_handle_t *handle)
{ {
Job *job = handle_get_job(handle); Job *job = handle_get_job(handle);

View File

@ -1,188 +0,0 @@
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>
#include <msgpack.h>
#include "nvim/vim.h"
#include "nvim/log.h"
#include "nvim/memory.h"
#include "nvim/os/wstream.h"
#include "nvim/os/msgpack_rpc.h"
#include "nvim/os/msgpack_rpc_helpers.h"
#include "nvim/api/private/helpers.h"
#include "nvim/func_attr.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/msgpack_rpc.c.generated.h"
#endif
/// Validates the basic structure of the msgpack-rpc call and fills `res`
/// with the basic response structure.
///
/// @param channel_id The channel id
/// @param req The parsed request object
/// @param res A packer that contains the response
WBuffer *msgpack_rpc_call(uint64_t channel_id,
msgpack_object *req,
msgpack_sbuffer *sbuffer)
FUNC_ATTR_NONNULL_ARG(2)
FUNC_ATTR_NONNULL_ARG(3)
{
uint64_t response_id;
Error error = ERROR_INIT;
msgpack_rpc_validate(&response_id, req, &error);
if (error.set) {
return serialize_response(response_id, &error, NIL, sbuffer);
}
// dispatch the call
Object rv = msgpack_rpc_dispatch(channel_id, req, &error);
// send the response
msgpack_packer response;
msgpack_packer_init(&response, sbuffer, msgpack_sbuffer_write);
if (error.set) {
ELOG("Error dispatching msgpack-rpc call: %s(request: id %" PRIu64 ")",
error.msg,
response_id);
return serialize_response(response_id, &error, NIL, sbuffer);
}
DLOG("Successfully completed mspgack-rpc call(request id: %" PRIu64 ")",
response_id);
return serialize_response(response_id, &error, rv, sbuffer);
}
/// Finishes the msgpack-rpc call with an error message.
///
/// @param msg The error message
/// @param res A packer that contains the response
void msgpack_rpc_error(char *msg, msgpack_packer *res)
FUNC_ATTR_NONNULL_ALL
{
size_t len = strlen(msg);
// error message
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,
Array args,
msgpack_sbuffer *sbuffer,
size_t refcount)
FUNC_ATTR_NONNULL_ARG(4)
{
msgpack_packer pac;
msgpack_packer_init(&pac, sbuffer, msgpack_sbuffer_write);
msgpack_pack_array(&pac, request_id ? 4 : 3);
msgpack_pack_int(&pac, request_id ? 0 : 2);
if (request_id) {
msgpack_pack_uint64(&pac, request_id);
}
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);
api_free_array(args);
msgpack_sbuffer_clear(sbuffer);
return rv;
}
/// Serializes a msgpack-rpc response
WBuffer *serialize_response(uint64_t response_id,
Error *err,
Object arg,
msgpack_sbuffer *sbuffer)
FUNC_ATTR_NONNULL_ARG(2, 4)
{
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, response_id);
if (err->set) {
// error represented by a [type, message] array
msgpack_pack_array(&pac, 2);
msgpack_rpc_from_integer(err->type, &pac);
msgpack_rpc_from_string(cstr_as_string(err->msg), &pac);
// Nil result
msgpack_pack_nil(&pac);
} else {
// Nil error
msgpack_pack_nil(&pac);
// Return value
msgpack_rpc_from_object(arg, &pac);
}
WBuffer *rv = wstream_new_buffer(xmemdup(sbuffer->data, sbuffer->size),
sbuffer->size,
1, // responses only go though 1 channel
free);
api_free_object(arg);
msgpack_sbuffer_clear(sbuffer);
return rv;
}
static void msgpack_rpc_validate(uint64_t *response_id,
msgpack_object *req,
Error *err)
{
// response id not known yet
*response_id = 0;
// Validate the basic structure of the msgpack-rpc payload
if (req->type != MSGPACK_OBJECT_ARRAY) {
api_set_error(err, Validation, _("Request is not an array"));
}
if (req->via.array.size != 4) {
api_set_error(err, Validation, _("Request array size should be 4"));
}
if (req->via.array.ptr[1].type != MSGPACK_OBJECT_POSITIVE_INTEGER) {
api_set_error(err, Validation, _("Id must be a positive integer"));
}
// Set the response id, which is the same as the request
*response_id = req->via.array.ptr[1].via.u64;
if (req->via.array.ptr[0].type != MSGPACK_OBJECT_POSITIVE_INTEGER) {
api_set_error(err, Validation, _("Message type must be an integer"));
}
if (req->via.array.ptr[0].via.u64 != 0) {
api_set_error(err, Validation, _("Message type must be 0"));
}
if (req->via.array.ptr[2].type != MSGPACK_OBJECT_BIN
&& req->via.array.ptr[2].type != MSGPACK_OBJECT_STR) {
api_set_error(err, Validation, _("Method must be a string"));
}
if (req->via.array.ptr[3].type != MSGPACK_OBJECT_ARRAY) {
api_set_error(err, Validation, _("Paremeters must be an array"));
}
}

View File

@ -1,16 +0,0 @@
#ifndef NVIM_OS_MSGPACK_RPC_HELPERS_H
#define NVIM_OS_MSGPACK_RPC_HELPERS_H
#include <stdint.h>
#include <stdbool.h>
#include <msgpack.h>
#include "nvim/api/private/defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/msgpack_rpc_helpers.h.generated.h"
#endif
#endif // NVIM_OS_MSGPACK_RPC_HELPERS_H

View File

@ -8,7 +8,7 @@
#include "nvim/api/vim.h" #include "nvim/api/vim.h"
#include "nvim/api/private/helpers.h" #include "nvim/api/private/helpers.h"
#include "nvim/api/private/defs.h" #include "nvim/api/private/defs.h"
#include "nvim/os/channel.h" #include "nvim/msgpack_rpc/channel.h"
#include "nvim/os/shell.h" #include "nvim/os/shell.h"
#include "nvim/os/os.h" #include "nvim/os/os.h"
#include "nvim/log.h" #include "nvim/log.h"

View File

@ -8,8 +8,6 @@
#include "nvim/os/uv_helpers.h" #include "nvim/os/uv_helpers.h"
#include "nvim/os/rstream_defs.h" #include "nvim/os/rstream_defs.h"
#include "nvim/os/rstream.h" #include "nvim/os/rstream.h"
#include "nvim/os/event_defs.h"
#include "nvim/os/event.h"
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/vim.h" #include "nvim/vim.h"
#include "nvim/memory.h" #include "nvim/memory.h"
@ -33,7 +31,6 @@ struct rstream {
uv_file fd; uv_file fd;
rstream_cb cb; rstream_cb cb;
bool free_handle; bool free_handle;
EventSource source_override;
}; };
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
@ -76,18 +73,13 @@ void rbuffer_consumed(RBuffer *rbuffer, size_t count)
void rbuffer_produced(RBuffer *rbuffer, size_t count) void rbuffer_produced(RBuffer *rbuffer, size_t count)
{ {
rbuffer->wpos += count; rbuffer->wpos += count;
DLOG("Received %u bytes from RStream(address: %p, source: %p)", DLOG("Received %u bytes from RStream(%p)", (size_t)cnt, rbuffer->rstream);
(size_t)cnt,
rbuffer->rstream,
rstream_event_source(rbuffer->rstream));
rbuffer_relocate(rbuffer); rbuffer_relocate(rbuffer);
if (rbuffer->rstream && rbuffer->wpos == rbuffer->capacity) { if (rbuffer->rstream && rbuffer->wpos == rbuffer->capacity) {
// The last read filled the buffer, stop reading for now // The last read filled the buffer, stop reading for now
rstream_stop(rbuffer->rstream); rstream_stop(rbuffer->rstream);
DLOG("Buffer for RStream(address: %p, source: %p) is full, stopping it", DLOG("Buffer for RStream(%p) is full, stopping it", rstream);
rstream,
rstream_event_source(rstream));
} }
} }
@ -180,13 +172,8 @@ void rbuffer_free(RBuffer *rbuffer)
/// for reading with `rstream_read` /// for reading with `rstream_read`
/// @param buffer RBuffer instance to associate with the RStream /// @param buffer RBuffer instance to associate with the RStream
/// @param data Some state to associate with the `RStream` instance /// @param data Some state to associate with the `RStream` instance
/// @param source_override Replacement for the default source used in events
/// emitted by this RStream. If NULL, the default is used.
/// @return The newly-allocated `RStream` instance /// @return The newly-allocated `RStream` instance
RStream * rstream_new(rstream_cb cb, RStream * rstream_new(rstream_cb cb, RBuffer *buffer, void *data)
RBuffer *buffer,
void *data,
EventSource source_override)
{ {
RStream *rv = xmalloc(sizeof(RStream)); RStream *rv = xmalloc(sizeof(RStream));
rv->buffer = buffer; rv->buffer = buffer;
@ -198,7 +185,6 @@ RStream * rstream_new(rstream_cb cb,
rv->fread_idle = NULL; rv->fread_idle = NULL;
rv->free_handle = false; rv->free_handle = false;
rv->file_type = UV_UNKNOWN_HANDLE; rv->file_type = UV_UNKNOWN_HANDLE;
rv->source_override = source_override ? source_override : rv;
return rv; return rv;
} }
@ -322,21 +308,6 @@ size_t rstream_read(RStream *rstream, char *buffer, size_t count)
return rbuffer_read(rstream->buffer, buffer, count); return rbuffer_read(rstream->buffer, buffer, count);
} }
/// Runs the read callback associated with the rstream
///
/// @param event Object containing data necessary to invoke the callback
void rstream_read_event(Event event)
{
RStream *rstream = event.data.rstream.ptr;
rstream->cb(rstream, rstream->data, event.data.rstream.eof);
}
EventSource rstream_event_source(RStream *rstream)
{
return rstream->source_override;
}
// Callbacks used by libuv // Callbacks used by libuv
// Called by libuv to allocate memory for reading. // Called by libuv to allocate memory for reading.
@ -357,13 +328,11 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf)
if (cnt <= 0) { if (cnt <= 0) {
if (cnt != UV_ENOBUFS) { if (cnt != UV_ENOBUFS) {
DLOG("Closing RStream(address: %p, source: %p)", DLOG("Closing RStream(%p)", rstream);
rstream,
rstream_event_source(rstream));
// Read error or EOF, either way stop the stream and invoke the callback // Read error or EOF, either way stop the stream and invoke the callback
// with eof == true // with eof == true
uv_read_stop(stream); uv_read_stop(stream);
emit_read_event(rstream, true); rstream->cb(rstream, rstream->data, true);
} }
return; return;
} }
@ -374,7 +343,7 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf)
// Data was already written, so all we need is to update 'wpos' to reflect // Data was already written, so all we need is to update 'wpos' to reflect
// the space actually used in the buffer. // the space actually used in the buffer.
rbuffer_produced(rstream->buffer, nread); rbuffer_produced(rstream->buffer, nread);
emit_read_event(rstream, false); rstream->cb(rstream, rstream->data, false);
} }
// Called by the by the 'idle' handle to emulate a reading event // Called by the by the 'idle' handle to emulate a reading event
@ -409,7 +378,6 @@ static void fread_idle_cb(uv_idle_t *handle)
if (req.result <= 0) { if (req.result <= 0) {
uv_idle_stop(rstream->fread_idle); uv_idle_stop(rstream->fread_idle);
emit_read_event(rstream, true);
return; return;
} }
@ -417,7 +385,6 @@ static void fread_idle_cb(uv_idle_t *handle)
size_t nread = (size_t) req.result; size_t nread = (size_t) req.result;
rbuffer_produced(rstream->buffer, nread); rbuffer_produced(rstream->buffer, nread);
rstream->fpos += nread; rstream->fpos += nread;
emit_read_event(rstream, false);
} }
static void close_cb(uv_handle_t *handle) static void close_cb(uv_handle_t *handle)
@ -426,19 +393,6 @@ static void close_cb(uv_handle_t *handle)
free(handle); free(handle);
} }
static void emit_read_event(RStream *rstream, bool eof)
{
Event event = {
.source = rstream_event_source(rstream),
.type = kEventRStreamData,
.data.rstream = {
.ptr = rstream,
.eof = eof
}
};
event_push(event);
}
static void rbuffer_relocate(RBuffer *rbuffer) static void rbuffer_relocate(RBuffer *rbuffer)
{ {
// Move data ... // Move data ...

View File

@ -1,7 +0,0 @@
#ifndef NVIM_OS_SERVER_H
#define NVIM_OS_SERVER_H
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/server.h.generated.h"
#endif
#endif // NVIM_OS_SERVER_H

View File

@ -1,7 +0,0 @@
#ifndef NVIM_OS_SERVER_DEFS_H
#define NVIM_OS_SERVER_DEFS_H
typedef struct server Server;
#endif // NVIM_OS_SERVER_DEFS_H

View File

@ -1,4 +1,5 @@
#include <string.h> #include <string.h>
#include <assert.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdlib.h> #include <stdlib.h>
@ -7,6 +8,7 @@
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/lib/kvec.h" #include "nvim/lib/kvec.h"
#include "nvim/log.h" #include "nvim/log.h"
#include "nvim/os/event.h"
#include "nvim/os/job.h" #include "nvim/os/job.h"
#include "nvim/os/rstream.h" #include "nvim/os/rstream.h"
#include "nvim/os/shell.h" #include "nvim/os/shell.h"
@ -58,11 +60,11 @@ typedef struct {
/// `shell_free_argv` when no longer needed. /// `shell_free_argv` when no longer needed.
char **shell_build_argv(const char_u *cmd, const char_u *extra_shell_opt) char **shell_build_argv(const char_u *cmd, const char_u *extra_shell_opt)
{ {
int argc = tokenize(p_sh, NULL) + tokenize(p_shcf, NULL); size_t argc = tokenize(p_sh, NULL) + tokenize(p_shcf, NULL);
char **rv = xmalloc((unsigned)((argc + 4) * sizeof(char *))); char **rv = xmalloc((unsigned)((argc + 4) * sizeof(char *)));
// Split 'shell' // Split 'shell'
int i = tokenize(p_sh, rv); size_t i = tokenize(p_sh, rv);
if (extra_shell_opt != NULL) { if (extra_shell_opt != NULL) {
// Push a copy of `extra_shell_opt` // Push a copy of `extra_shell_opt`
@ -212,7 +214,7 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg)
// Keep running the loop until all three handles are completely closed // Keep running the loop until all three handles are completely closed
while (pdata.exited < expected_exits) { while (pdata.exited < expected_exits) {
uv_run(uv_default_loop(), UV_RUN_ONCE); event_poll(0);
if (got_int) { if (got_int) {
// Forward SIGINT to the shell // Forward SIGINT to the shell
@ -356,9 +358,9 @@ static void system_data_cb(RStream *rstream, void *data, bool eof)
/// @param argv The vector that will be filled with copies of the parsed /// @param argv The vector that will be filled with copies of the parsed
/// words. It can be NULL if the caller only needs to count words. /// words. It can be NULL if the caller only needs to count words.
/// @return The number of words parsed. /// @return The number of words parsed.
static int tokenize(const char_u *str, char **argv) static size_t tokenize(const char_u *str, char **argv)
{ {
int argc = 0, len; size_t argc = 0, len;
char_u *p = (char_u *) str; char_u *p = (char_u *) str;
while (*p != NUL) { while (*p != NUL) {
@ -383,11 +385,11 @@ static int tokenize(const char_u *str, char **argv)
/// ///
/// @param str A pointer to the first character of the word /// @param str A pointer to the first character of the word
/// @return The offset from `str` at which the word ends. /// @return The offset from `str` at which the word ends.
static int word_length(const char_u *str) static size_t word_length(const char_u *str)
{ {
const char_u *p = str; const char_u *p = str;
bool inquote = false; bool inquote = false;
int length = 0; size_t length = 0;
// Move `p` to the end of shell word by advancing the pointer while it's // Move `p` to the end of shell word by advancing the pointer while it's
// inside a quote or it's a non-whitespace character // inside a quote or it's a non-whitespace character
@ -418,15 +420,15 @@ static void write_selection(uv_write_t *req)
// TODO(tarruda): use a static buffer for up to a limit(BUFFER_LENGTH) and // TODO(tarruda): use a static buffer for up to a limit(BUFFER_LENGTH) and
// only after filled we should start allocating memory(skip unnecessary // only after filled we should start allocating memory(skip unnecessary
// allocations for small writes) // allocations for small writes)
int buflen = BUFFER_LENGTH; size_t buflen = BUFFER_LENGTH;
pdata->wbuffer = (char *)xmalloc(buflen); pdata->wbuffer = (char *)xmalloc(buflen);
uv_buf_t uvbuf; uv_buf_t uvbuf;
linenr_T lnum = curbuf->b_op_start.lnum; linenr_T lnum = curbuf->b_op_start.lnum;
int off = 0; size_t off = 0;
int written = 0; size_t written = 0;
char_u *lp = ml_get(lnum); char_u *lp = ml_get(lnum);
int l; size_t l;
int len; size_t len;
for (;;) { for (;;) {
l = strlen((char *)lp + written); l = strlen((char *)lp + written);
@ -443,7 +445,7 @@ static void write_selection(uv_write_t *req)
pdata->wbuffer[off++] = NUL; pdata->wbuffer[off++] = NUL;
} else { } else {
char_u *s = vim_strchr(lp + written, NL); char_u *s = vim_strchr(lp + written, NL);
len = s == NULL ? l : s - (lp + written); len = s == NULL ? l : (size_t)(s - (lp + written));
while (off + len >= buflen) { while (off + len >= buflen) {
// Resize the buffer // Resize the buffer
buflen *= 2; buflen *= 2;
@ -584,6 +586,7 @@ static void exit_cb(uv_process_t *proc, int64_t status, int term_signal)
{ {
ProcessData *data = (ProcessData *)proc->data; ProcessData *data = (ProcessData *)proc->data;
data->exited++; data->exited++;
data->exit_status = status; assert(status <= INT_MAX);
data->exit_status = (int)status;
uv_close((uv_handle_t *)proc, NULL); uv_close((uv_handle_t *)proc, NULL);
} }

View File

@ -12,8 +12,6 @@
#include "nvim/memory.h" #include "nvim/memory.h"
#include "nvim/misc1.h" #include "nvim/misc1.h"
#include "nvim/misc2.h" #include "nvim/misc2.h"
#include "nvim/os/event_defs.h"
#include "nvim/os/event.h"
#include "nvim/os/signal.h" #include "nvim/os/signal.h"
static uv_signal_t sint, spipe, shup, squit, sterm, swinch; static uv_signal_t sint, spipe, shup, squit, sterm, swinch;
@ -72,45 +70,6 @@ void signal_accept_deadly(void)
rejecting_deadly = false; rejecting_deadly = false;
} }
void signal_handle(Event event)
{
int signum = event.data.signum;
switch (signum) {
case SIGINT:
got_int = true;
break;
#ifdef SIGPWR
case SIGPWR:
// Signal of a power failure(eg batteries low), flush the swap files to
// be safe
ml_sync_all(false, false);
break;
#endif
case SIGPIPE:
// Ignore
break;
case SIGWINCH:
shell_resized();
break;
case SIGTERM:
case SIGQUIT:
case SIGHUP:
if (!rejecting_deadly) {
deadly_signal(signum);
}
break;
default:
fprintf(stderr, "Invalid signal %d", signum);
break;
}
}
EventSource signal_event_source(void)
{
return &sint;
}
static char * signal_name(int signum) static char * signal_name(int signum)
{ {
switch (signum) { switch (signum) {
@ -154,20 +113,32 @@ static void deadly_signal(int signum)
static void signal_cb(uv_signal_t *handle, int signum) static void signal_cb(uv_signal_t *handle, int signum)
{ {
if (rejecting_deadly) { switch (signum) {
if (signum == SIGINT) { case SIGINT:
got_int = true; got_int = true;
} break;
#ifdef SIGPWR
return; case SIGPWR:
// Signal of a power failure(eg batteries low), flush the swap files to
// be safe
ml_sync_all(false, false);
break;
#endif
case SIGPIPE:
// Ignore
break;
case SIGWINCH:
shell_resized();
break;
case SIGTERM:
case SIGQUIT:
case SIGHUP:
if (!rejecting_deadly) {
deadly_signal(signum);
}
break;
default:
fprintf(stderr, "Invalid signal %d", signum);
break;
} }
Event event = {
.source = signal_event_source(),
.type = kEventSignal,
.data = {
.signum = signum
}
};
event_push(event);
} }

View File

@ -1,3 +1,4 @@
#include <assert.h>
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <time.h> #include <time.h>
@ -64,23 +65,6 @@ void os_microdelay(uint64_t microseconds, bool ignoreinput)
} }
} }
static void microdelay(uint64_t microseconds)
{
uint64_t hrtime;
int64_t ns = microseconds * 1000; // convert to nanoseconds
uv_mutex_lock(&delay_mutex);
while (ns > 0) {
hrtime = uv_hrtime();
if (uv_cond_timedwait(&delay_cond, &delay_mutex, ns) == UV_ETIMEDOUT)
break;
ns -= uv_hrtime() - hrtime;
}
uv_mutex_unlock(&delay_mutex);
}
/// Portable version of POSIX localtime_r() /// Portable version of POSIX localtime_r()
/// ///
/// @return NULL in case of error /// @return NULL in case of error
@ -112,3 +96,23 @@ struct tm *os_get_localtime(struct tm *result) FUNC_ATTR_NONNULL_ALL
time_t rawtime = time(NULL); time_t rawtime = time(NULL);
return os_localtime_r(&rawtime, result); return os_localtime_r(&rawtime, result);
} }
static void microdelay(uint64_t microseconds)
{
uint64_t elapsed = 0;
uint64_t ns = microseconds * 1000; // convert to nanoseconds
uint64_t base = uv_hrtime();
uv_mutex_lock(&delay_mutex);
while (elapsed < ns) {
if (uv_cond_timedwait(&delay_cond, &delay_mutex, ns - elapsed)
== UV_ETIMEDOUT)
break;
uint64_t now = uv_hrtime();
elapsed += now - base;
base = now;
}
uv_mutex_unlock(&delay_mutex);
}

View File

@ -54,8 +54,8 @@
#include "nvim/os/shell.h" #include "nvim/os/shell.h"
#include "nvim/os/signal.h" #include "nvim/os/signal.h"
#include "nvim/os/job.h" #include "nvim/os/job.h"
#include "nvim/os/msgpack_rpc.h" #include "nvim/msgpack_rpc/helpers.h"
#include "nvim/os/msgpack_rpc_helpers.h" #include "nvim/msgpack_rpc/defs.h"
#if defined(HAVE_SYS_IOCTL_H) #if defined(HAVE_SYS_IOCTL_H)
# include <sys/ioctl.h> # include <sys/ioctl.h>
@ -166,8 +166,6 @@ void mch_init(void)
mac_conv_init(); mac_conv_init();
#endif #endif
msgpack_rpc_init();
msgpack_rpc_helpers_init();
event_init(); event_init();
} }

View File

@ -23,6 +23,15 @@ end
describe('system()', function() describe('system()', function()
before_each(clear) before_each(clear)
it('sets the v:shell_error variable', function()
eval([[system("sh -c 'exit'")]])
eq(0, eval('v:shell_error'))
eval([[system("sh -c 'exit 1'")]])
eq(1, eval('v:shell_error'))
eval([[system("sh -c 'exit 5'")]])
eq(5, eval('v:shell_error'))
end)
describe('passing no input', function() describe('passing no input', function()
it('returns the program output', function() it('returns the program output', function()
eq("echoed", eval('system("echo -n echoed")')) eq("echoed", eval('system("echo -n echoed")'))
@ -83,6 +92,15 @@ describe('systemlist()', function()
-- string. -- string.
before_each(clear) before_each(clear)
it('sets the v:shell_error variable', function()
eval([[systemlist("sh -c 'exit'")]])
eq(0, eval('v:shell_error'))
eval([[systemlist("sh -c 'exit 1'")]])
eq(1, eval('v:shell_error'))
eval([[systemlist("sh -c 'exit 5'")]])
eq(5, eval('v:shell_error'))
end)
describe('passing string with linefeed characters as input', function() describe('passing string with linefeed characters as input', function()
it('splits the output on linefeed characters', function() it('splits the output on linefeed characters', function()
eq({'abc', 'def', 'ghi'}, eval([[systemlist("cat -", "abc\ndef\nghi")]])) eq({'abc', 'def', 'ghi'}, eval([[systemlist("cat -", "abc\ndef\nghi")]]))