diff options
69 files changed, 1314 insertions, 1040 deletions
| diff --git a/.ci/clang-asan.sh b/.ci/clang-asan.sh index 0dff61f297..c6f28848d1 100644 --- a/.ci/clang-asan.sh +++ b/.ci/clang-asan.sh @@ -2,8 +2,6 @@  set_environment /opt/neovim-deps/64 -install_functional_test_deps -  sudo pip install cpp-coveralls  clang_version=3.4 diff --git a/.ci/common.sh b/.ci/common.sh index 7939f2f4b7..76faf595c8 100644 --- a/.ci/common.sh +++ b/.ci/common.sh @@ -47,12 +47,6 @@ install_prebuilt_deps() {  	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"  rm -rf "$tmpdir"  mkdir -p "$tmpdir" @@ -73,3 +67,5 @@ install_prebuilt_deps  sudo apt-mark hold oracle-java7-installer oracle-java8-installer  sudo apt-get update + +export CFLAGS='-DMIN_LOG_LEVEL=0'  # force verification of DLOG macros diff --git a/.ci/gcc-32.sh b/.ci/gcc-32.sh index ec51cfab69..c128e91988 100644 --- a/.ci/gcc-32.sh +++ b/.ci/gcc-32.sh @@ -1,10 +1,5 @@  . "$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  # Need this to keep apt-get from removing gcc when installing libncurses diff --git a/.ci/gcc.sh b/.ci/gcc.sh index 842e34405a..90063e48f3 100644 --- a/.ci/gcc.sh +++ b/.ci/gcc.sh @@ -2,8 +2,6 @@  set_environment /opt/neovim-deps/64 -install_functional_test_deps -  sudo pip install cpp-coveralls  sudo apt-get install valgrind @@ -13,8 +11,16 @@ export VALGRIND_LOG="$tmpdir/valgrind-%p.log"  CMAKE_EXTRA_FLAGS="-DTRAVIS_CI_BUILD=ON -DUSE_GCOV=ON"  $MAKE_CMD CMAKE_EXTRA_FLAGS="${CMAKE_EXTRA_FLAGS}" unittest -$MAKE_CMD test +if ! $MAKE_CMD test; then +	valgrind_check "$tmpdir" +	exit 1 +fi +valgrind_check "$tmpdir" + +if ! $MAKE_CMD oldtest; then +	valgrind_check "$tmpdir" +	exit 1 +fi  valgrind_check "$tmpdir" -$MAKE_CMD oldtest  coveralls --encoding iso-8859-1 || echo 'coveralls upload failed.' diff --git a/.travis.yml b/.travis.yml index 2746cba4ca..9bf7844a3e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,10 @@ before_install:    # That allows to test changing the group of the file by `os_fchown`.    - sudo groupadd chown_test    - sudo usermod -a -G chown_test ${USER} +  # Need xvfb for running some tests with xclip +  - export DISPLAY=:99.0 +  - sh -e /etc/init.d/xvfb start +  - sudo apt-get install xclip  script:    # This will pass the environment variables down to a bash process which runs    # as $USER, while retaining the environment variables defined and belonging diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8ef6d78d40..e56380ad61 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,11 +9,6 @@  - Look at [Waffle][waffle] to see who is working on what issues.  - Refer to the [the wiki][wiki] for detailed guidance. -### What not to do - -Please avoid broad cosmetic/style changes which increase merge conflicts and add -excessive noise to `git blame`. -  ## Issues  - Search existing issues before raising a new one. @@ -56,11 +51,20 @@ When submitting pull requests, include one of the following tokens in the title:  #### Coding style -Code changes should follow the [Neovim style guide][style]. - -Please run [`clint.py`][clint] to detect style errors. It is not perfect and may -have false positives and negatives. To have `clint.py` ignore certain special -cases, put `// NOLINT` at the end of the line. +We have a [style guide][style] that all new code should follow. However, vast +swathes of the existing vim codebase violate it to some degree, and fixing +them would increase merge conflicts and add noise to `git blame`. Please weigh +those costs when making cosmetic changes. As a rule of thumb, avoid pull +requests dominated by style changes. Feel free to fix up lines that you happen +to be modifying anyway, as long as they look consistent with their +surroundings. Fix anything that looks outright +[barbarous](http://www.orwell.ru/library/essays/politics/english/e_polit) -- +especially if you can't find any editor settings that make it look ok -- but +otherwise err on the side of leaving things as they are. + +For new code, please run [`clint.py`][clint] to detect style errors. It is not +perfect and may have false positives and negatives. To have `clint.py` ignore +certain special cases, put `// NOLINT` at the end of the line.  #### Commit guidelines diff --git a/clint-files.txt b/clint-files.txt index d6bf7b8807..99f2fecc7c 100644 --- a/clint-files.txt +++ b/clint-files.txt @@ -32,8 +32,6 @@ src/nvim/os/job.c  src/nvim/os/job.h  src/nvim/os/job_defs.h  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/rstream.c  src/nvim/os/rstream.h @@ -44,10 +42,12 @@ src/nvim/os/signal.c  src/nvim/os/signal.h  src/nvim/os/time.c  src/nvim/os/time.h -src/nvim/os/server.c -src/nvim/os/server.h -src/nvim/os/channel.c -src/nvim/os/channel.h +src/nvim/msgpack_rpc/server.c +src/nvim/msgpack_rpc/server.h +src/nvim/msgpack_rpc/channel.c +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.h  src/nvim/profile.c diff --git a/cmake/InstallHelpers.cmake b/cmake/InstallHelpers.cmake index de2f970cc1..faf980bd47 100644 --- a/cmake/InstallHelpers.cmake +++ b/cmake/InstallHelpers.cmake @@ -89,6 +89,13 @@ function(install_helper)        WORLD_READ)    endif() +  if(NOT _install_helper_PROGRAM_PERMISSIONS) +    set(_install_helper_PROGRAM_PERMISSIONS +      OWNER_READ OWNER_WRITE OWNER_EXECUTE +      GROUP_READ GROUP_EXECUTE +      WORLD_READ WORLD_EXECUTE) +  endif() +    if(_install_helper_RENAME)      set(RENAME RENAME ${_install_helper_RENAME})    endif() @@ -130,7 +137,7 @@ function(install_helper)      install(        PROGRAMS ${_install_helper_PROGRAMS}        DESTINATION ${_install_helper_DESTINATION} -      PERMISSIONS ${_install_helper_FILE_PERMISSIONS} +      PERMISSIONS ${_install_helper_PROGRAM_PERMISSIONS}        ${RENAME})    endif()  endfunction() diff --git a/contrib/YouCompleteMe/README.md b/contrib/YouCompleteMe/README.md index 940f49363b..cd6393cd03 100644 --- a/contrib/YouCompleteMe/README.md +++ b/contrib/YouCompleteMe/README.md @@ -2,7 +2,7 @@  ## What is this? -This provides the necessary to configure vim's YCM plugin to provide C semantic support (completion, go-to-definition, etc) for the neovim project. +This provides the code necessary to configure vim's YCM plugin to provide C semantic support (completion, go-to-definition, etc) for the Neovim project.  ## Installation @@ -17,6 +17,6 @@ cp contrib/YouCompleteMe/ycm_extra_conf.py src/.ycm_extra_conf.py  echo .ycm_extra_conf.py >> .git/info/exclude  make -(somewhere in you .vimrc files) +(Add the following somewhere in your .nvimrc files)  autocmd FileType c nnoremap <buffer> <silent> <C-]> :YcmCompleter GoTo<cr>  ``` diff --git a/contrib/YouCompleteMe/ycm_extra_conf.py b/contrib/YouCompleteMe/ycm_extra_conf.py index ae5d3d1a36..0066f96dc0 100644 --- a/contrib/YouCompleteMe/ycm_extra_conf.py +++ b/contrib/YouCompleteMe/ycm_extra_conf.py @@ -8,7 +8,8 @@ def DirectoryOfThisScript():  def GetDatabase(): -    compilation_database_folder = DirectoryOfThisScript() + '/../build' +    compilation_database_folder = os.path.join(DirectoryOfThisScript(), +                                               '..', 'build')      if os.path.exists(compilation_database_folder):          return ycm_core.CompilationDatabase(compilation_database_folder)      return None diff --git a/runtime/plugin/python_setup.vim b/runtime/plugin/python_setup.vim index db8c6e3251..3a6c61bd1d 100644 --- a/runtime/plugin/python_setup.vim +++ b/runtime/plugin/python_setup.vim @@ -42,5 +42,5 @@ let s:pyhost_id = rpcstart(s:python_interpreter,  " by the user's vimrc would not get has('python') == 1  if rpcrequest(s:pyhost_id, 'python_eval', '"o"+"k"') != 'ok' || !has('python')    " Something went wrong -  rpcstop(s:pyhost_id) +  call rpcstop(s:pyhost_id)  endif diff --git a/scripts/msgpack-gen.lua b/scripts/msgpack-gen.lua index 916597afda..9645784f00 100644 --- a/scripts/msgpack-gen.lua +++ b/scripts/msgpack-gen.lua @@ -34,6 +34,8 @@ c_params = Ct(c_void + c_param_list)  c_proto = Ct(    Cg(c_type, 'return_type') * Cg(c_id, 'name') *    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(';')    )  grammar = Ct((c_proto + c_comment + c_preproc + ws) ^ 1) @@ -84,6 +86,7 @@ end  output = io.open(outputf, 'wb')  output:write([[ +#include <inttypes.h>  #include <stdbool.h>  #include <stdint.h>  #include <assert.h> @@ -92,8 +95,8 @@ output:write([[  #include "nvim/map.h"  #include "nvim/log.h"  #include "nvim/vim.h" -#include "nvim/os/msgpack_rpc.h" -#include "nvim/os/msgpack_rpc_helpers.h" +#include "nvim/msgpack_rpc/helpers.h" +#include "nvim/msgpack_rpc/defs.h"  #include "nvim/api/private/helpers.h"  #include "nvim/api/private/defs.h"  ]]) @@ -159,9 +162,9 @@ for i = 1, #functions do    local fn = functions[i]    local args = {} -  output:write('static Object handle_'..fn.name..'(uint64_t channel_id, msgpack_object *req, Error *error)') +  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  DLOG("Received msgpack-rpc call to '..fn.name..'(request id: %" PRIu64 ")", req->via.array.ptr[1].via.u64);') +  output:write('\n  Object ret = NIL;')    -- Declare/initialize variables that will hold converted arguments    for j = 1, #fn.parameters do      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]))..';')    end    output:write('\n') -  output:write('\n  if (req->via.array.ptr[3].via.array.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  if (args.size != '..#fn.parameters..') {') +  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    goto cleanup;')    output:write('\n  }\n') @@ -179,14 +182,18 @@ for i = 1, #functions do    for j = 1, #fn.parameters do      local converted, convert_arg, param, arg      param = fn.parameters[j] -    arg = '(req->via.array.ptr[3].via.array.ptr + '..(j - 1)..')'      converted = 'arg_'..j -    convert_arg = 'msgpack_rpc_to_'..real_type(param[1]):lower() -    output:write('\n  if (!'..convert_arg..'('..arg..', &'..converted..')) {') -    output:write('\n    snprintf(error->msg, sizeof(error->msg), "Wrong type for argument '..j..', expecting '..param[1]..'");') -    output:write('\n    error->set = true;') -    output:write('\n    goto cleanup;') -    output:write('\n  }\n') +    if real_type(param[1]) ~= 'Object' then +      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    error->set = true;') +      output:write('\n    goto cleanup;') +      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    end @@ -228,7 +235,7 @@ for i = 1, #functions do    end    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    -- Now generate the cleanup label for freeing memory allocated for the    -- arguments @@ -238,20 +245,16 @@ for i = 1, #functions do      local param = fn.parameters[j]      output:write('\n  api_free_'..string.lower(real_type(param[1]))..'(arg_'..j..');')    end -  if fn.return_type ~= 'void' then -    output:write('\n  return ret;\n}\n\n'); -  else -    output:write('\n  return NIL;\n}\n\n'); -  end +  output:write('\n  return ret;\n}\n\n');  end  -- Generate a function that initializes method names with handler functions  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  for i = 1, #functions do    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..'", '.. -               '.size = sizeof("'..fn.name..'") - 1}, handle_'.. -               fn.name..');\n') +               '.size = sizeof("'..fn.name..'") - 1}, '.. +               '(MsgpackRpcRequestHandler) {.fn = handle_'..  fn.name.. +               ', .defer = '..tostring(fn.deferred)..'});\n')    if #fn.name > max_fname_len then      max_fname_len = #fn.name @@ -273,26 +277,21 @@ end  output:write('\n}\n\n')  output:write([[ -Object msgpack_rpc_dispatch(uint64_t channel_id, -                            msgpack_object *req, -                            Error *error) +MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name, +                                                     size_t name_len)  { -  msgpack_object method = req->via.array.ptr[2]; -  rpc_method_handler_fn handler = NULL; - -  if (method.type == MSGPACK_OBJECT_BIN || method.type == MSGPACK_OBJECT_STR) { -]]) -output:write('    handler = map_get(String, rpc_method_handler_fn)') -output:write('(methods, (String){.data=(char *)method.via.bin.ptr,') -output:write('.size=min(method.via.bin.size, '..max_fname_len..')});\n') -output:write([[ -  } - -  if (!handler) { -    handler = msgpack_rpc_handle_missing_method; +  String m = { +    .data=(char *)name, +    .size=min(name_len, ]]..max_fname_len..[[) +  }; +  MsgpackRpcRequestHandler rv = +    map_get(String, MsgpackRpcRequestHandler)(methods, m); + +  if (!rv.fn) { +    rv.fn = msgpack_rpc_handle_missing_method;    } -  return handler(channel_id, req, error); +  return rv;  }  ]]) diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 208df31596..a77e5e27a0 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -3,7 +3,7 @@ include(CheckLibraryExists)  set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto)  set(DISPATCH_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/msgpack-gen.lua)  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(HEADER_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/gendeclarations.lua)  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}/api)  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}/os)  file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/api)  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)  foreach(sfile ${NEOVIM_SOURCES}) @@ -36,8 +38,7 @@ endforeach()  list(REMOVE_ITEM NEOVIM_SOURCES ${to_remove}) -set(CONV_SRCS -  api.c +set(CONV_SOURCES    arabic.c    cursor.c    garray.c @@ -46,31 +47,24 @@ set(CONV_SRCS    map.c    memory.c    misc2.c -  map.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 -  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( -  ${CONV_SRCS} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wconversion") +  ${CONV_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wconversion")  if(CMAKE_C_COMPILER_ID MATCHES "Clang")    if(DEFINED ENV{SANITIZE}) @@ -126,7 +120,7 @@ add_custom_command(OUTPUT ${MSGPACK_DISPATCH}    COMMAND ${LUA_PRG} ${DISPATCH_GENERATOR} ${API_HEADERS} ${MSGPACK_DISPATCH}    DEPENDS      ${API_HEADERS} -    ${MSGPACK_RPC_HEADER} +    ${MSGPACK_RPC_HEADERS}      ${DISPATCH_GENERATOR}  ) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 4ff5845bd4..982003a31a 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -69,6 +69,7 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)  /// @param line The new line.  /// @param[out] err Details of an error that may have occurred  void buffer_set_line(Buffer buffer, Integer index, String line, Error *err) +  FUNC_ATTR_DEFERRED  {    Object l = STRING_OBJ(line);    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[out] err Details of an error that may have occurred  void buffer_del_line(Buffer buffer, Integer index, Error *err) +  FUNC_ATTR_DEFERRED  {    Array array = ARRAY_DICT_INIT;    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,                        ArrayOf(String) replacement,                        Error *err) +  FUNC_ATTR_DEFERRED  {    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  /// @return The old value  Object buffer_set_var(Buffer buffer, String name, Object value, Error *err) +  FUNC_ATTR_DEFERRED  {    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[out] err Details of an error that may have occurred  void buffer_set_option(Buffer buffer, String name, Object value, Error *err) +  FUNC_ATTR_DEFERRED  {    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[out] err Details of an error that may have occurred  void buffer_set_name(Buffer buffer, String name, Error *err) +  FUNC_ATTR_DEFERRED  {    buf_T *buf = find_buffer_by_handle(buffer, err); @@ -444,6 +450,7 @@ void buffer_insert(Buffer buffer,                     Integer lnum,                     ArrayOf(String) lines,                     Error *err) +  FUNC_ATTR_DEFERRED  {    buffer_set_line_slice(buffer, lnum, lnum, false, true, lines, err);  } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 093cb0e55f..a7b48f3b7e 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1,4 +1,5 @@  #include <assert.h> +#include <inttypes.h>  #include <stdbool.h>  #include <stdlib.h>  #include <string.h> diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index 3e5d00671a..cb06825731 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -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  /// @return The tab page handle  Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err) +  FUNC_ATTR_DEFERRED  {    tabpage_T *tab = find_tab_by_handle(tabpage, err); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 5e0f3e0c32..b6bac1588a 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -10,7 +10,7 @@  #include "nvim/api/private/helpers.h"  #include "nvim/api/private/defs.h"  #include "nvim/api/buffer.h" -#include "nvim/os/channel.h" +#include "nvim/msgpack_rpc/channel.h"  #include "nvim/os/provider.h"  #include "nvim/vim.h"  #include "nvim/buffer.h" @@ -24,6 +24,7 @@  #include "nvim/misc2.h"  #include "nvim/term.h"  #include "nvim/getchar.h" +#include "nvim/os/input.h"  #define LINE_BUFFER_SIZE 4096 @@ -31,19 +32,12 @@  # include "api/vim.c.generated.h"  #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  ///  /// @param str The command str  /// @param[out] err Details of an error that may have occurred  void vim_command(String str, Error *err) +  FUNC_ATTR_DEFERRED  {    // Run the command    try_start(); @@ -58,6 +52,7 @@ void vim_command(String str, Error *err)  /// @param mode specifies the mapping options  /// @see feedkeys()  void vim_feedkeys(String keys, String mode) +  FUNC_ATTR_DEFERRED  {    bool remap = true;    bool typed = false; @@ -85,6 +80,18 @@ void vim_feedkeys(String keys, String mode)      typebuf_was_filled = true;  } +/// Pass input keys to Neovim. Unlike `vim_feedkeys`, this will use a +/// lower-level input buffer and the call is not deferred. +/// This is the most reliable way to emulate real user input. +/// +/// @param keys to be typed +/// @return The number bytes actually written, which can be lower than +///         requested if the buffer becomes full. +Integer vim_input(String keys) +{ +  return (Integer)input_enqueue(keys); +} +  /// Replace any terminal codes with the internal representation  ///  /// @see replace_termcodes @@ -103,6 +110,19 @@ String vim_replace_termcodes(String str, Boolean from_part, Boolean do_lt,    return cstr_as_string(ptr);  } +String vim_command_output(String str, Error *err) +{ +  do_cmdline_cmd((char_u *)"redir => v:command_output"); +  vim_command(str, err); +  do_cmdline_cmd((char_u *)"redir END"); + +  if (err->set) { +    return (String) STRING_INIT; +  } + +  return cstr_to_string((char *)get_vim_var_str(VV_COMMAND_OUTPUT)); +} +  /// Evaluates the expression str using the vim internal expression  /// evaluator (see |expression|).  /// Dictionaries and lists are recursively expanded. @@ -111,6 +131,7 @@ String vim_replace_termcodes(String str, Boolean from_part, Boolean do_lt,  /// @param[out] err Details of an error that may have occurred  /// @return The expanded object  Object vim_eval(String str, Error *err) +  FUNC_ATTR_DEFERRED  {    Object rv;    // Evaluate the expression @@ -230,6 +251,7 @@ String vim_get_current_line(Error *err)  /// @param line The line contents  /// @param[out] err Details of an error that may have occurred  void vim_set_current_line(String line, Error *err) +  FUNC_ATTR_DEFERRED  {    buffer_set_line(curbuf->handle, curwin->w_cursor.lnum - 1, line, err);  } @@ -238,6 +260,7 @@ void vim_set_current_line(String line, Error *err)  ///  /// @param[out] err Details of an error that may have occurred  void vim_del_current_line(Error *err) +  FUNC_ATTR_DEFERRED  {    buffer_del_line(curbuf->handle, curwin->w_cursor.lnum - 1, err);  } @@ -259,6 +282,7 @@ Object vim_get_var(String name, Error *err)  /// @param[out] err Details of an error that may have occurred  /// @return the old value if any  Object vim_set_var(String name, Object value, Error *err) +  FUNC_ATTR_DEFERRED  {    return dict_set_value(&globvardict, name, value, err);  } @@ -289,6 +313,7 @@ Object vim_get_option(String name, Error *err)  /// @param value The new option value  /// @param[out] err Details of an error that may have occurred  void vim_set_option(String name, Object value, Error *err) +  FUNC_ATTR_DEFERRED  {    set_option_to(NULL, SREQ_GLOBAL, name, value, err);  } @@ -297,6 +322,7 @@ void vim_set_option(String name, Object value, Error *err)  ///  /// @param str The message  void vim_out_write(String str) +  FUNC_ATTR_DEFERRED  {    write_msg(str, false);  } @@ -305,6 +331,7 @@ void vim_out_write(String str)  ///  /// @param str The message  void vim_err_write(String str) +  FUNC_ATTR_DEFERRED  {    write_msg(str, true);  } @@ -314,6 +341,7 @@ void vim_err_write(String str)  ///  /// @param str The message  void vim_report_error(String str) +  FUNC_ATTR_DEFERRED  {    vim_err_write(str);    vim_err_write((String) {.data = "\n", .size = 1}); @@ -357,6 +385,7 @@ Buffer vim_get_current_buffer(void)  /// @param id The buffer handle  /// @param[out] err Details of an error that may have occurred  void vim_set_current_buffer(Buffer buffer, Error *err) +  FUNC_ATTR_DEFERRED  {    buf_T *buf = find_buffer_by_handle(buffer, err); @@ -407,6 +436,7 @@ Window vim_get_current_window(void)  ///  /// @param handle The window handle  void vim_set_current_window(Window window, Error *err) +  FUNC_ATTR_DEFERRED  {    win_T *win = find_window_by_handle(window, err); @@ -462,6 +492,7 @@ Tabpage vim_get_current_tabpage(void)  /// @param handle The tab page handle  /// @param[out] err Details of an error that may have occurred  void vim_set_current_tabpage(Tabpage tabpage, Error *err) +  FUNC_ATTR_DEFERRED  {    tabpage_T *tp = find_tab_by_handle(tabpage, err); diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 751518424b..fde1ebfa4c 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -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[out] err Details of an error that may have occurred  void window_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) +  FUNC_ATTR_DEFERRED  {    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[out] err Details of an error that may have occurred  void window_set_height(Window window, Integer height, Error *err) +  FUNC_ATTR_DEFERRED  {    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[out] err Details of an error that may have occurred  void window_set_width(Window window, Integer width, Error *err) +  FUNC_ATTR_DEFERRED  {    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  /// @return The old value  Object window_set_var(Window window, String name, Object value, Error *err) +  FUNC_ATTR_DEFERRED  {    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[out] err Details of an error that may have occurred  void window_set_option(Window window, String name, Object value, Error *err) +  FUNC_ATTR_DEFERRED  {    win_T *win = find_window_by_handle(window, err); diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 6ce3c77c66..b76bad67f6 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -4423,8 +4423,8 @@ linenr_T buf_delsign(      }      /* When deleted the last sign needs to redraw the windows to remove the -     * sign column. Not when curwin is NULL (this means we're exiting). */ -    if (buf->b_signlist != NULL && curwin != NULL) { +     * sign column. */ +    if (buf->b_signlist == NULL) {          redraw_buf_later(buf, NOT_VALID);          changed_cline_bef_curs();      } @@ -4479,11 +4479,9 @@ void buf_delete_signs(buf_T *buf)      signlist_T *next;      // When deleting the last sign need to redraw the windows to remove the -    // sign column. -    if (buf->b_signlist != NULL) { +    // sign column. Not when curwin is NULL (this means we're exiting). +    if (buf->b_signlist != NULL && curwin != NULL){        redraw_buf_later(buf, NOT_VALID); -      // TODO(oni-link): Is this call necessary if curwin is not a viewport -      // for buf?        changed_cline_bef_curs();      } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index bfb332a149..d0af4b8249 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -18,6 +18,8 @@  #include <stdbool.h>  #include <math.h> +#include "nvim/lib/klist.h" +  #include "nvim/assert.h"  #include "nvim/vim.h"  #include "nvim/ascii.h" @@ -81,11 +83,12 @@  #include "nvim/os/rstream.h"  #include "nvim/os/rstream_defs.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/vim.h"  #include "nvim/os/dl.h"  #include "nvim/os/provider.h" +#include "nvim/os/event.h"  #define DICT_MAXNEST 100        /* maximum nesting of lists and dicts */ @@ -421,7 +424,8 @@ static struct vimvar {    {VV_NAME("oldfiles",         VAR_LIST), 0},    {VV_NAME("windowid",         VAR_NUMBER), VV_RO},    {VV_NAME("progpath",         VAR_STRING), VV_RO}, -  {VV_NAME("job_data",         VAR_LIST), 0} +  {VV_NAME("job_data",         VAR_LIST), 0}, +  {VV_NAME("command_output",   VAR_STRING), 0}  };  /* shorthand */ @@ -443,6 +447,15 @@ static dictitem_T vimvars_var;                  /* variable used for v: */  #define FNE_CHECK_START 2       /* find_name_end(): check name starts with                                     valid character */ +// Memory pool for reusing JobEvent structures +typedef struct { +  int id; +  char *name, *type, *received; +} JobEvent; +#define JobEventFreer(x) +KMEMPOOL_INIT(JobEventPool, JobEvent, JobEventFreer) +kmempool_t(JobEventPool) *job_event_pool = NULL; +  /*   * Initialize the global and v: variables.   */ @@ -478,6 +491,7 @@ void eval_init(void)    set_vim_var_nr(VV_HLSEARCH, 1L);    set_reg_var(0);    /* default for v:register is not 0 but '"' */ +  job_event_pool = kmp_init(JobEventPool);  }  #if defined(EXITFREE) || defined(PROTO) @@ -19508,49 +19522,65 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags)    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->received = NULL;                                     \ +    if (r) {                                                         \ +      size_t read_count = rstream_pending(r);                        \ +      event_data->received = xmalloc(read_count + 1);                \ +      rstream_read(r, event_data->received, read_count);             \ +      event_data->received[read_count] = NUL;                        \ +    }                                                                \ +    event_data->id = job_id(j);                                      \ +    event_data->name = job_data(j);                                  \ +    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)  {    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)  {    if (!eof) { -    on_job_data(rstream, data, eof, "stderr"); +    push_job_event(data, rstream, "stderr");    }  }  static void on_job_exit(Job *job, void *data)  { -  apply_job_autocmds(job, data, "exit", NULL); -  free(data); +  push_job_event(job, NULL, "exit");  } -static void on_job_data(RStream *rstream, void *data, bool eof, char *type) +static void on_job_event(Event event)  { -  Job *job = data; -  uint32_t read_count = rstream_pending(rstream); -  char *str = xmalloc(read_count + 1); - -  rstream_read(rstream, str, read_count); -  str[read_count] = NUL; -  apply_job_autocmds(job, job_data(job), type, str); +  JobEvent *data = event.data; +  apply_job_autocmds(data->id, data->name, data->type, data->received); +  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(int id, char *name, char *type, char *received)  {    // Create the list which will be set to v:job_data    list_T *list = list_alloc(); -  list_append_number(list, job_id(job)); +  list_append_number(list, id);    list_append_string(list, (uint8_t *)type, -1); -  if (str) { +  if (received) {      listitem_T *str_slot = listitem_alloc();      str_slot->li_tv.v_type = VAR_STRING;      str_slot->li_tv.v_lock = 0; -    str_slot->li_tv.vval.v_string = (uint8_t *)str; +    str_slot->li_tv.vval.v_string = (uint8_t *)received;      list_append(list, str_slot);    } @@ -19558,6 +19588,11 @@ static void apply_job_autocmds(Job *job, char *name, char *type, char *str)    set_vim_var_list(VV_JOB_DATA, list);    // Call JobActivity autocommands    apply_autocmds(EVENT_JOBACTIVITY, (uint8_t *)name, NULL, TRUE, NULL); + +  if (!received) { +    // This must be the exit event. Free the name. +    free(name); +  }  }  static void script_host_eval(char *method, typval_T *argvars, typval_T *rettv) diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 2f36a46f70..e96106dfb3 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -64,6 +64,7 @@ enum {      VV_WINDOWID,      VV_PROGPATH,      VV_JOB_DATA, +    VV_COMMAND_OUTPUT,      VV_LEN, /* number of v: vars */  }; diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 5db950f120..f5fa16a139 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -5908,12 +5908,13 @@ void ex_sign(exarg_T *eap)  		arg = skipwhite(arg);  		if (idx == SIGNCMD_UNPLACE && *arg == NUL)  		{ -		    /* ":sign unplace {id}": remove placed sign by number */ -         FOR_ALL_BUFFERS(buf) { -           if ((lnum = buf_delsign(buf, id)) != 0) -               update_debug_sign(buf, lnum); -            return; -         } +		  // ":sign unplace {id}": remove placed sign by number +		  FOR_ALL_BUFFERS(buf) { +		    if ((lnum = buf_delsign(buf, id)) != 0) { +		      update_debug_sign(buf, lnum); +                    } +		  } +		  return;  		}  	    }  	} @@ -5923,7 +5924,7 @@ void ex_sign(exarg_T *eap)  	 * Leave "arg" pointing to {fname}.  	 */ -   buf_T *buf = NULL; +        buf_T *buf = NULL;  	for (;;)  	{  	    if (STRNCMP(arg, "line=", 5) == 0) @@ -6343,3 +6344,4 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg)    }  } +// vim: tabstop=8 diff --git a/src/nvim/func_attr.h b/src/nvim/func_attr.h index c75d0ab312..519f61c763 100644 --- a/src/nvim/func_attr.h +++ b/src/nvim/func_attr.h @@ -179,6 +179,7 @@  #endif  #ifdef DEFINE_FUNC_ATTRIBUTES +  #define FUNC_ATTR_DEFERRED    #define FUNC_ATTR_MALLOC REAL_FATTR_MALLOC    #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) diff --git a/src/nvim/lib/klist.h b/src/nvim/lib/klist.h index e4a90fef33..f8dc7d4c43 100644 --- a/src/nvim/lib/klist.h +++ b/src/nvim/lib/klist.h @@ -39,6 +39,8 @@      static inline kmp_##name##_t *kmp_init_##name(void) {               \          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) {         \          size_t k;                                                       \          for (k = 0; k < mp->n; ++k) {                                   \ diff --git a/src/nvim/log.c b/src/nvim/log.c index da28a18509..e8e0c9bbb9 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -20,7 +20,7 @@  # include "log.c.generated.h"  #endif -bool do_log(int log_level, const char *func_name, int line_num, +bool do_log(int log_level, const char *func_name, int line_num, bool eol,              const char* fmt, ...) FUNC_ATTR_UNUSED  {    FILE *log_file = open_log_file(); @@ -31,8 +31,8 @@ bool do_log(int log_level, const char *func_name, int line_num,    va_list args;    va_start(args, fmt); -  bool ret = v_do_log_to_file(log_file, log_level, func_name, line_num, fmt, -                              args); +  bool ret = v_do_log_to_file(log_file, log_level, func_name, line_num, eol, +                              fmt, args);    va_end(args);    if (log_file != stderr && log_file != stdout) { @@ -45,13 +45,13 @@ bool do_log(int log_level, const char *func_name, int line_num,  ///  /// @return The FILE* specified by the USR_LOG_FILE path or stderr in case of  ///         error -static FILE *open_log_file(void) +FILE *open_log_file(void)  {    static bool opening_log_file = false;    // check if it's a recursive call    if (opening_log_file) { -    do_log_to_file(stderr, ERROR_LOG_LEVEL, __func__, __LINE__, +    do_log_to_file(stderr, ERROR_LOG_LEVEL, __func__, __LINE__, true,                     "Trying to LOG() recursively! Please fix it.");      return stderr;    } @@ -81,7 +81,7 @@ static FILE *open_log_file(void)  open_log_file_error:    opening_log_file = false; -  do_log_to_file(stderr, ERROR_LOG_LEVEL, __func__, __LINE__, +  do_log_to_file(stderr, ERROR_LOG_LEVEL, __func__, __LINE__, true,                   "Couldn't open USR_LOG_FILE, logging to stderr! This may be "                   "caused by attempting to LOG() before initialization "                   "functions are called (e.g. init_homedir())."); @@ -89,20 +89,20 @@ open_log_file_error:  }  static bool do_log_to_file(FILE *log_file, int log_level, -                           const char *func_name, int line_num, +                           const char *func_name, int line_num, bool eol,                             const char* fmt, ...)  {    va_list args;    va_start(args, fmt); -  bool ret = v_do_log_to_file(log_file, log_level, func_name, line_num, fmt, -                              args); +  bool ret = v_do_log_to_file(log_file, log_level, func_name, line_num, eol, +                              fmt, args);    va_end(args);    return ret;  }  static bool v_do_log_to_file(FILE *log_file, int log_level, -                             const char *func_name, int line_num, +                             const char *func_name, int line_num, bool eol,                               const char* fmt, va_list args)  {    static const char *log_levels[] = { @@ -133,7 +133,9 @@ static bool v_do_log_to_file(FILE *log_file, int log_level,    if (vfprintf(log_file, fmt, args) < 0) {      return false;    } -  fputc('\n', log_file); +  if (eol) { +    fputc('\n', log_file); +  }    if (fflush(log_file) == EOF) {      return false;    } diff --git a/src/nvim/log.h b/src/nvim/log.h index f1ee63a4e2..152e90760e 100644 --- a/src/nvim/log.h +++ b/src/nvim/log.h @@ -1,6 +1,7 @@  #ifndef NVIM_LOG_H  #define NVIM_LOG_H +#include <stdio.h>  #include <stdbool.h>  #define DEBUG_LOG_LEVEL 0 @@ -9,9 +10,13 @@  #define ERROR_LOG_LEVEL 3  #define DLOG(...) +#define DLOGN(...)  #define ILOG(...) +#define ILOGN(...)  #define WLOG(...) +#define WLOGN(...)  #define ELOG(...) +#define ELOGN(...)  // Logging is disabled if NDEBUG or DISABLE_LOG is defined.  #ifdef NDEBUG @@ -28,22 +33,38 @@  #  if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL  #    undef DLOG -#    define DLOG(...) do_log(DEBUG_LOG_LEVEL, __func__, __LINE__, __VA_ARGS__) +#    undef DLOGN +#    define DLOG(...) do_log(DEBUG_LOG_LEVEL, __func__, __LINE__, true, \ +       __VA_ARGS__) +#    define DLOGN(...) do_log(DEBUG_LOG_LEVEL, __func__, __LINE__, false, \ +       __VA_ARGS__)  #  endif  #  if MIN_LOG_LEVEL <= INFO_LOG_LEVEL  #    undef ILOG -#    define ILOG(...) do_log(INFO_LOG_LEVEL, __func__, __LINE__, __VA_ARGS__) +#    undef ILOGN +#    define ILOG(...) do_log(INFO_LOG_LEVEL, __func__, __LINE__, true, \ +       __VA_ARGS__) +#    define ILOGN(...) do_log(INFO_LOG_LEVEL, __func__, __LINE__, false, \ +       __VA_ARGS__)  #  endif  #  if MIN_LOG_LEVEL <= WARNING_LOG_LEVEL  #    undef WLOG -#    define WLOG(...) do_log(WARNING_LOG_LEVEL, __func__, __LINE__, __VA_ARGS__) +#    undef WLOGN +#    define WLOG(...) do_log(WARNING_LOG_LEVEL, __func__, __LINE__, true, \ +       __VA_ARGS__) +#    define WLOGN(...) do_log(WARNING_LOG_LEVEL, __func__, __LINE__, false, \ +       __VA_ARGS__)  #  endif  #  if MIN_LOG_LEVEL <= ERROR_LOG_LEVEL  #    undef ELOG -#    define ELOG(...) do_log(ERROR_LOG_LEVEL, __func__, __LINE__, __VA_ARGS__) +#    undef ELOGN +#    define ELOG(...) do_log(ERROR_LOG_LEVEL, __func__, __LINE__, true, \ +       __VA_ARGS__) +#    define ELOGN(...) do_log(ERROR_LOG_LEVEL, __func__, __LINE__, false, \ +       __VA_ARGS__)  #  endif  #endif diff --git a/src/nvim/main.c b/src/nvim/main.c index 128d1a784c..a63ffb4a31 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -59,7 +59,7 @@  #include "nvim/os/input.h"  #include "nvim/os/os.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/helpers.h" diff --git a/src/nvim/map.c b/src/nvim/map.c index 24aa38d67d..3f485cb952 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -6,7 +6,7 @@  #include "nvim/map_defs.h"  #include "nvim/vim.h"  #include "nvim/memory.h" -#include "nvim/os/msgpack_rpc.h" +#include "nvim/msgpack_rpc/defs.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(ptr_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) diff --git a/src/nvim/map.h b/src/nvim/map.h index 616516c3e1..5ade6dcf15 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -5,7 +5,7 @@  #include "nvim/map_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)                                                       \    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(ptr_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_free(T, U) map_##T##_##U##_free diff --git a/src/nvim/os/channel.c b/src/nvim/msgpack_rpc/channel.c index 959fbc6e73..43bed54b2c 100644 --- a/src/nvim/os/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -5,9 +5,11 @@  #include <uv.h>  #include <msgpack.h> +#include "nvim/lib/klist.h" +  #include "nvim/api/private/helpers.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/rstream.h"  #include "nvim/os/rstream_defs.h" @@ -15,8 +17,7 @@  #include "nvim/os/wstream_defs.h"  #include "nvim/os/job.h"  #include "nvim/os/job_defs.h" -#include "nvim/os/msgpack_rpc.h" -#include "nvim/os/msgpack_rpc_helpers.h" +#include "nvim/msgpack_rpc/helpers.h"  #include "nvim/vim.h"  #include "nvim/ascii.h"  #include "nvim/memory.h" @@ -30,16 +31,21 @@  #define CHANNEL_BUFFER_SIZE 0xffff +#if MIN_LOG_LEVEL > DEBUG_LOG_LEVEL +#define log_client_msg(...) +#define log_server_msg(...) +#endif +  typedef struct {    uint64_t request_id; -  bool errored; +  bool returned, errored;    Object result;  } ChannelCallFrame;  typedef struct {    uint64_t id;    PMap(cstr_t) *subscribed_events; -  bool is_job, enabled; +  bool is_job, closed;    msgpack_unpacker *unpacker;    union {      Job *job; @@ -51,21 +57,32 @@ typedef struct {    } data;    uint64_t next_request_id;    kvec_t(ChannelCallFrame *) call_stack; -  size_t rpc_call_level;  } 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 PMap(uint64_t) *channels = NULL;  static PMap(cstr_t) *event_strings = NULL;  static msgpack_sbuffer out_buffer;  #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os/channel.c.generated.h" +# include "msgpack_rpc/channel.c.generated.h"  #endif  /// Initializes the module  void channel_init(void)  { +  request_event_pool = kmp_init(RequestEventPool);    channels = pmap_new(uint64_t)();    event_strings = pmap_new(cstr_t)();    msgpack_sbuffer_init(&out_buffer); @@ -104,12 +121,12 @@ uint64_t channel_from_job(char **argv)                                  channel,                                  job_out,                                  job_err, -                                NULL, +                                job_exit,                                  0,                                  &status);    if (status <= 0) { -    close_channel(channel); +    free_channel(channel);      return 0;    } @@ -128,8 +145,7 @@ void channel_from_stream(uv_stream_t *stream)    // read stream    channel->data.streams.read = rstream_new(parse_msgpack,                                             rbuffer_new(CHANNEL_BUFFER_SIZE), -                                           channel, -                                           NULL); +                                           channel);    rstream_set_stream(channel->data.streams.read, stream);    rstream_start(channel->data.streams.read);    // write stream @@ -142,7 +158,7 @@ bool channel_exists(uint64_t id)  {    Channel *channel;    return (channel = pmap_get(uint64_t)(channels, id)) != NULL -    && channel->enabled; +    && !channel->closed;  }  /// Sends event/arguments to channel @@ -157,7 +173,7 @@ bool channel_send_event(uint64_t id, char *name, Array args)    Channel *channel = NULL;    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);        return false;      } @@ -183,7 +199,7 @@ Object channel_send_call(uint64_t id,  {    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_free_array(args);      return NIL; @@ -203,28 +219,21 @@ Object channel_send_call(uint64_t id,    // Send the msgpack-rpc request    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 -  ChannelCallFrame frame = {request_id, false, NIL}; +  ChannelCallFrame frame = {request_id, false, false, NIL};    kv_push(ChannelCallFrame *, channel->call_stack, &frame); -  size_t size = kv_size(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 +  event_poll_until(-1, frame.returned); +  (void)kv_pop(channel->call_stack);    if (frame.errored) {      api_set_error(err, Exception, "%s", frame.result.data.string.data);      return NIL;    } +  if (channel->closed && !kv_size(channel->call_stack)) { +    free_channel(channel); +  } +    return frame.result;  } @@ -236,7 +245,7 @@ void channel_subscribe(uint64_t id, char *event)  {    Channel *channel; -  if (!(channel = pmap_get(uint64_t)(channels, id)) || !channel->enabled) { +  if (!(channel = pmap_get(uint64_t)(channels, id)) || channel->closed) {      abort();    } @@ -258,7 +267,7 @@ void channel_unsubscribe(uint64_t id, char *event)  {    Channel *channel; -  if (!(channel = pmap_get(uint64_t)(channels, id)) || !channel->enabled) { +  if (!(channel = pmap_get(uint64_t)(channels, id)) || channel->closed) {      abort();    } @@ -273,12 +282,11 @@ bool channel_close(uint64_t id)  {    Channel *channel; -  if (!(channel = pmap_get(uint64_t)(channels, id)) || !channel->enabled) { +  if (!(channel = pmap_get(uint64_t)(channels, id)) || channel->closed) {      return false;    } -  channel_kill(channel); -  channel->enabled = false; +  close_channel(channel);    return true;  } @@ -291,8 +299,7 @@ static void channel_from_stdio(void)    // read stream    channel->data.streams.read = rstream_new(parse_msgpack,                                             rbuffer_new(CHANNEL_BUFFER_SIZE), -                                           channel, -                                           NULL); +                                           channel);    rstream_set_file(channel->data.streams.read, 0);    rstream_start(channel->data.streams.read);    // write stream @@ -320,23 +327,22 @@ 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)  {    Channel *channel = data; -  channel->rpc_call_level++;    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; +    close_channel(channel); +    call_set_error(channel, "Channel was closed by the client"); +    return;    } -  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)",         count,         rstream); @@ -353,9 +359,12 @@ static void parse_msgpack(RStream *rstream, void *data, bool eof)    // Deserialize everything we can.    while ((result = msgpack_unpacker_next(channel->unpacker, &unpacked)) ==        MSGPACK_UNPACK_SUCCESS) { -    if (kv_size(channel->call_stack) && is_rpc_response(&unpacked.data)) { +    bool is_response = is_rpc_response(&unpacked.data); +    log_client_msg(channel->id, !is_response, unpacked.data); + +    if (kv_size(channel->call_stack) && is_response) {        if (is_valid_rpc_response(&unpacked.data, channel)) { -        call_stack_pop(&unpacked.data, channel); +        complete_call(&unpacked.data, channel);        } else {          char buf[256];          snprintf(buf, @@ -368,15 +377,10 @@ static void parse_msgpack(RStream *rstream, void *data, bool eof)        }        msgpack_unpacked_destroy(&unpacked);        // Bail out from this event loop iteration -      goto end; +      return;      } -    // Perform the call -    WBuffer *resp = msgpack_rpc_call(channel->id, &unpacked.data, &out_buffer); -    // write the response -    if (!channel_write(channel, resp)) { -      goto end; -    } +    handle_request(channel, &unpacked.data);    }    if (result == MSGPACK_UNPACK_NOMEM_ERROR) { @@ -396,13 +400,82 @@ static void parse_msgpack(RStream *rstream, void *data, bool eof)                             "This error can also happen when deserializing "                             "an object with high level of nesting");    } +} -end: -  channel->rpc_call_level--; -  if (!channel->enabled && !kv_size(channel->call_stack)) { -    // Now it's safe to destroy the channel -    close_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(channel->id, +                                     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); +  channel_write(channel, serialize_response(channel->id, +                                            request_id, +                                            &error, +                                            result, +                                            &out_buffer)); +  // All arguments were freed already, but we still need to free the array +  free(args.items);  }  static bool channel_write(Channel *channel, WBuffer *buffer) @@ -433,7 +506,11 @@ static void send_error(Channel *channel, uint64_t id, char *err)  {    Error e = ERROR_INIT;    api_set_error(&e, Exception, "%s", err); -  channel_write(channel, serialize_response(id, &e, NIL, &out_buffer)); +  channel_write(channel, serialize_response(channel->id, +                                            id, +                                            &e, +                                            NIL, +                                            &out_buffer));  }  static void send_request(Channel *channel, @@ -442,7 +519,12 @@ static void send_request(Channel *channel,                           Array args)  {    String method = {.size = strlen(name), .data = name}; -  channel_write(channel, serialize_request(id, method, args, &out_buffer, 1)); +  channel_write(channel, serialize_request(channel->id, +                                           id, +                                           method, +                                           args, +                                           &out_buffer, +                                           1));  }  static void send_event(Channel *channel, @@ -450,7 +532,12 @@ static void send_event(Channel *channel,                         Array args)  {    String method = {.size = strlen(name), .data = name}; -  channel_write(channel, serialize_request(0, method, args, &out_buffer, 1)); +  channel_write(channel, serialize_request(channel->id, +                                           0, +                                           method, +                                           args, +                                           &out_buffer, +                                           1));  }  static void broadcast_event(char *name, Array args) @@ -472,6 +559,7 @@ static void broadcast_event(char *name, Array args)    String method = {.size = strlen(name), .data = name};    WBuffer *buffer = serialize_request(0, +                                      0,                                        method,                                        args,                                        &out_buffer, @@ -501,26 +589,15 @@ static void unsubscribe(Channel *channel, char *event)    free(event_string);  } +/// Close the channel streams/job. The channel resources will be freed by +/// free_channel later.  static void close_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); -  channel_kill(channel); - -  free(channel); -} +  if (channel->closed) { +    return; +  } -static void channel_kill(Channel *channel) -{ +  channel->closed = true;    if (channel->is_job) {      if (channel->data.job) {        job_stop(channel->data.job); @@ -528,15 +605,31 @@ static void channel_kill(Channel *channel)    } else {      rstream_free(channel->data.streams.read);      wstream_free(channel->data.streams.write); -    if (channel->data.streams.uv) { -      uv_close((uv_handle_t *)channel->data.streams.uv, close_cb); +    uv_handle_t *handle = (uv_handle_t *)channel->data.streams.uv; +    if (handle) { +      uv_close(handle, close_cb);      } else { -      // When the stdin channel closes, it's time to go        mch_exit(0);      }    }  } +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)  {    free(handle->data); @@ -546,8 +639,7 @@ static void close_cb(uv_handle_t *handle)  static Channel *register_channel(void)  {    Channel *rv = xmalloc(sizeof(Channel)); -  rv->enabled = true; -  rv->rpc_call_level = 0; +  rv->closed = false;    rv->unpacker = msgpack_unpacker_new(MSGPACK_UNPACKER_INIT_BUFFER_SIZE);    rv->id = next_id++;    rv->subscribed_events = pmap_new(cstr_t)(); @@ -574,9 +666,11 @@ static bool is_valid_rpc_response(msgpack_object *obj, Channel *channel)                               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;    if (frame->errored) { @@ -589,10 +683,88 @@ static void call_stack_pop(msgpack_object *obj, Channel *channel)  static void call_set_error(Channel *channel, char *msg)  {    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->result = STRING_OBJ(cstr_to_string(msg));    } -  channel->enabled = false; +  close_channel(channel);  } + +static WBuffer *serialize_request(uint64_t channel_id, +                                  uint64_t request_id, +                                  String method, +                                  Array args, +                                  msgpack_sbuffer *sbuffer, +                                  size_t refcount) +{ +  msgpack_packer pac; +  msgpack_packer_init(&pac, sbuffer, msgpack_sbuffer_write); +  msgpack_rpc_serialize_request(request_id, method, args, &pac); +  log_server_msg(channel_id, sbuffer); +  WBuffer *rv = wstream_new_buffer(xmemdup(sbuffer->data, sbuffer->size), +                                   sbuffer->size, +                                   refcount, +                                   free); +  msgpack_sbuffer_clear(sbuffer); +  api_free_array(args); +  return rv; +} + +static WBuffer *serialize_response(uint64_t channel_id, +                                   uint64_t response_id, +                                   Error *err, +                                   Object arg, +                                   msgpack_sbuffer *sbuffer) +{ +  msgpack_packer pac; +  msgpack_packer_init(&pac, sbuffer, msgpack_sbuffer_write); +  msgpack_rpc_serialize_response(response_id, err, arg, &pac); +  log_server_msg(channel_id, sbuffer); +  WBuffer *rv = wstream_new_buffer(xmemdup(sbuffer->data, sbuffer->size), +                                   sbuffer->size, +                                   1,  // responses only go though 1 channel +                                   free); +  msgpack_sbuffer_clear(sbuffer); +  api_free_object(arg); +  return rv; +} + +#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL +#define REQ "[request]      " +#define RES "[response]     " +#define NOT "[notification] " + +static void log_server_msg(uint64_t channel_id, +                           msgpack_sbuffer *packed) +{ +  msgpack_unpacked unpacked; +  msgpack_unpacked_init(&unpacked); +  msgpack_unpack_next(&unpacked, packed->data, packed->size, NULL); +  uint64_t type = unpacked.data.via.array.ptr[0].via.u64; +  DLOGN("[msgpack-rpc] nvim -> client(%" PRIu64 ") ", channel_id); +  FILE *f = open_log_file(); +  fprintf(f, type ? (type == 1 ? RES : NOT) : REQ); +  log_msg_close(f, unpacked.data); +  msgpack_unpacked_destroy(&unpacked); +} + +static void log_client_msg(uint64_t channel_id, +                           bool is_request, +                           msgpack_object msg) +{ +  DLOGN("[msgpack-rpc] client(%" PRIu64 ") -> nvim ", channel_id); +  FILE *f = open_log_file(); +  fprintf(f, is_request ? REQ : RES); +  log_msg_close(f, msg); +} + +static void log_msg_close(FILE *f, msgpack_object msg) +{ +  msgpack_object_print(f, msg); +  fputc('\n', f); +  fflush(f); +  fclose(f); +} +#endif diff --git a/src/nvim/os/channel.h b/src/nvim/msgpack_rpc/channel.h index bb409bfde9..df742fe368 100644 --- a/src/nvim/os/channel.h +++ b/src/nvim/msgpack_rpc/channel.h @@ -1,5 +1,5 @@ -#ifndef NVIM_OS_CHANNEL_H -#define NVIM_OS_CHANNEL_H +#ifndef NVIM_MSGPACK_RPC_CHANNEL_H +#define NVIM_MSGPACK_RPC_CHANNEL_H  #include <stdbool.h>  #include <uv.h> @@ -10,6 +10,6 @@  #define METHOD_MAXLEN 512  #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os/channel.h.generated.h" +# include "msgpack_rpc/channel.h.generated.h"  #endif -#endif  // NVIM_OS_CHANNEL_H +#endif  // NVIM_MSGPACK_RPC_CHANNEL_H diff --git a/src/nvim/os/msgpack_rpc.h b/src/nvim/msgpack_rpc/defs.h index 3476d791ea..13067fb7b4 100644 --- a/src/nvim/os/msgpack_rpc.h +++ b/src/nvim/msgpack_rpc/defs.h @@ -1,29 +1,23 @@ -#ifndef NVIM_OS_MSGPACK_RPC_H -#define NVIM_OS_MSGPACK_RPC_H - -#include <stdint.h> +#ifndef NVIM_MSGPACK_RPC_DEFS_H +#define NVIM_MSGPACK_RPC_DEFS_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  /// functions of this type. -typedef Object (*rpc_method_handler_fn)(uint64_t channel_id, -                                        msgpack_object *req, -                                        Error *error); - +typedef struct { +  Object (*fn)(uint64_t channel_id, +               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 -void msgpack_rpc_init(void); +void msgpack_rpc_init_method_table(void);  void msgpack_rpc_init_function_metadata(Dictionary *metadata); @@ -43,9 +37,7 @@ Object msgpack_rpc_dispatch(uint64_t channel_id,                              Error *error)    FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_NONNULL_ARG(3); - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os/msgpack_rpc.h.generated.h" -#endif - -#endif  // NVIM_OS_MSGPACK_RPC_H +MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name, +                                                     size_t name_len) +  FUNC_ATTR_NONNULL_ARG(1); +#endif  // NVIM_MSGPACK_RPC_DEFS_H diff --git a/src/nvim/os/msgpack_rpc_helpers.c b/src/nvim/msgpack_rpc/helpers.c index b14de8245c..4414aadb15 100644 --- a/src/nvim/os/msgpack_rpc_helpers.c +++ b/src/nvim/msgpack_rpc/helpers.c @@ -1,14 +1,18 @@  #include <stdint.h>  #include <stdbool.h> +#include <inttypes.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/log.h"  #include "nvim/memory.h"  #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os/msgpack_rpc_helpers.c.generated.h" +# include "msgpack_rpc/helpers.c.generated.h"  #endif  static msgpack_zone zone; @@ -136,10 +140,13 @@ bool msgpack_rpc_to_object(msgpack_object *obj, Object *arg)      case MSGPACK_OBJECT_EXT:        switch (obj->via.ext.type) {          case kObjectTypeBuffer: +          arg->type = kObjectTypeBuffer;            return msgpack_rpc_to_buffer(obj, &arg->data.buffer);          case kObjectTypeWindow: +          arg->type = kObjectTypeWindow;            return msgpack_rpc_to_window(obj, &arg->data.window);          case kObjectTypeTabpage: +          arg->type = kObjectTypeTabpage;            return msgpack_rpc_to_tabpage(obj, &arg->data.tabpage);        }      default: @@ -287,3 +294,116 @@ void msgpack_rpc_from_dictionary(Dictionary result, msgpack_packer *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) +void msgpack_rpc_serialize_request(uint64_t request_id, +                                   String method, +                                   Array args, +                                   msgpack_packer *pac) +  FUNC_ATTR_NONNULL_ARG(4) +{ +  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); +} + +/// Serializes a msgpack-rpc response +void msgpack_rpc_serialize_response(uint64_t response_id, +                                    Error *err, +                                    Object arg, +                                    msgpack_packer *pac) +  FUNC_ATTR_NONNULL_ARG(2, 4) +{ +  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); +  } +} + +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")); +  } +} diff --git a/src/nvim/msgpack_rpc/helpers.h b/src/nvim/msgpack_rpc/helpers.h new file mode 100644 index 0000000000..bf161d54e0 --- /dev/null +++ b/src/nvim/msgpack_rpc/helpers.h @@ -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 + diff --git a/src/nvim/os/server.c b/src/nvim/msgpack_rpc/server.c index 9f7f5b34da..087ba24111 100644 --- a/src/nvim/os/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -5,8 +5,8 @@  #include <uv.h> -#include "nvim/os/channel.h" -#include "nvim/os/server.h" +#include "nvim/msgpack_rpc/channel.h" +#include "nvim/msgpack_rpc/server.h"  #include "nvim/os/os.h"  #include "nvim/ascii.h"  #include "nvim/vim.h" @@ -46,7 +46,7 @@ typedef struct {  static PMap(cstr_t) *servers = NULL;  #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os/server.c.generated.h" +# include "msgpack_rpc/server.c.generated.h"  #endif  /// Initializes the module @@ -119,7 +119,8 @@ int server_start(const char *endpoint)      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) {      // Maximum length of an IP address buffer is 15(eg: 255.255.255.255) diff --git a/src/nvim/msgpack_rpc/server.h b/src/nvim/msgpack_rpc/server.h new file mode 100644 index 0000000000..f1a6703938 --- /dev/null +++ b/src/nvim/msgpack_rpc/server.h @@ -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 diff --git a/src/nvim/os/event.c b/src/nvim/os/event.c index a460b2db96..2dee529452 100644 --- a/src/nvim/os/event.c +++ b/src/nvim/os/event.c @@ -7,8 +7,10 @@  #include "nvim/os/event.h"  #include "nvim/os/input.h" -#include "nvim/os/channel.h" -#include "nvim/os/server.h" +#include "nvim/msgpack_rpc/defs.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/signal.h"  #include "nvim/os/rstream.h" @@ -25,25 +27,22 @@ KLIST_INIT(Event, Event, _destroy_event)  typedef struct {    bool timed_out; -  int32_t ms; +  int ms;    uv_timer_t *timer;  } TimerData;  #ifdef INCLUDE_GENERATED_DECLARATIONS  # include "os/event.c.generated.h"  #endif -static klist_t(Event) *deferred_events, *immediate_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; +static klist_t(Event) *pending_events;  void event_init(void)  { +  // early msgpack-rpc initialization +  msgpack_rpc_init_method_table(); +  msgpack_rpc_helpers_init();    // Initialize the event queues -  deferred_events = kl_init(Event); -  immediate_events = kl_init(Event); +  pending_events = kl_init(Event);    // Initialize input events    input_init();    // Timer to wake the event loop if a timeout argument is passed to @@ -52,9 +51,8 @@ void event_init(void)    signal_init();    // Jobs    job_init(); -  // Channels +  // finish mspgack-rpc initialization    channel_init(); -  // Servers    server_init();    // Providers    provider_init(); @@ -68,8 +66,7 @@ void event_teardown(void)  }  // Wait for some event -bool event_poll(int32_t ms, EventSource sources[]) -  FUNC_ATTR_NONNULL_ARG(2) +void event_poll(int ms)  {    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;    } -  size_t processed_events; - -  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 +  loop(run_mode);    if (!(--recursive)) {      // 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      uv_close((uv_handle_t *)&timer, 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)  { -  return !kl_empty(deferred_events); +  return !kl_empty(pending_events);  }  // Queue an event  void event_push(Event event)  { -  bool defer = true; - -  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; +  *kl_pushp(Event, pending_events) = event;  } -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) +void event_process(void)  { -  size_t count = 0;    Event event; -  while (kl_shift(Event, queue, &event) == 0) { -    switch (event.type) { -      case kEventSignal: -        signal_handle(event); -        break; -      case kEventRStreamData: -        rstream_read_event(event); -        break; -      case kEventJobExit: -        job_exit_event(event); -        break; -      default: -        abort(); -    } -    count++; +  while (kl_shift(Event, pending_events, &event) == 0) { +    event.handler(event);    } - -  DLOG("Processed %u events", count); - -  return count;  }  // 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);  } -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");    uv_run(uv_default_loop(), run_mode);    DLOG("Exit event loop"); -  immediate_sources = NULL; -  count = process_from(immediate_events); -  return count;  } diff --git a/src/nvim/os/event.h b/src/nvim/os/event.h index 29e304adc8..f8139e978d 100644 --- a/src/nvim/os/event.h +++ b/src/nvim/os/event.h @@ -6,6 +6,27 @@  #include "nvim/os/event_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  # include "os/event.h.generated.h" diff --git a/src/nvim/os/event_defs.h b/src/nvim/os/event_defs.h index dbee3e2ba7..2dd9403d9f 100644 --- a/src/nvim/os/event_defs.h +++ b/src/nvim/os/event_defs.h @@ -6,25 +6,12 @@  #include "nvim/os/job_defs.h"  #include "nvim/os/rstream_defs.h" -typedef void * EventSource; +typedef struct event Event; +typedef void (*event_handler)(Event event); -typedef enum { -  kEventSignal, -  kEventRStreamData, -  kEventJobExit -} EventType; - -typedef struct { -  EventSource source; -  EventType type; -  union { -    int signum; -    struct { -      RStream *ptr; -      bool eof; -    } rstream; -    Job *job; -  } data; -} Event; +struct event { +  void *data; +  event_handler handler; +};  #endif  // NVIM_OS_EVENT_DEFS_H diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 36c2bb6d9b..bdaf9ecdda 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -23,6 +23,7 @@ static const int kLibuvSuccess = 0;  ///  /// @return `0` on success, a libuv error code on failure.  int os_chdir(const char *path) +  FUNC_ATTR_NONNULL_ALL  {    if (p_verbose >= 5) {      verbose_enter(); @@ -38,6 +39,7 @@ int os_chdir(const char *path)  /// @param len Length of `buf`.  /// @return `OK` for success, `FAIL` for failure.  int os_dirname(char_u *buf, size_t len) +  FUNC_ATTR_NONNULL_ALL  {    assert(buf && len); @@ -53,6 +55,7 @@ int os_dirname(char_u *buf, size_t len)  ///  /// @return `true` if `fname` is a directory.  bool os_isdir(const char_u *name) +  FUNC_ATTR_NONNULL_ALL  {    int32_t mode = os_getperm(name);    if (mode < 0) { @@ -78,6 +81,7 @@ bool os_isdir(const char_u *name)  ///  /// @return `false` otherwise.  bool os_can_exe(const char_u *name, char_u **abspath) +  FUNC_ATTR_NONNULL_ARG(1)  {    // If it's an absolute or relative path don't need to use $PATH.    if (path_is_absolute_path(name) || @@ -100,6 +104,7 @@ bool os_can_exe(const char_u *name, char_u **abspath)  // Return true if "name" is an executable file, false if not or it doesn't  // exist.  static bool is_executable(const char_u *name) +  FUNC_ATTR_NONNULL_ALL  {    int32_t mode = os_getperm(name); @@ -121,6 +126,7 @@ static bool is_executable(const char_u *name)  ///  /// @return `true` if `name` is an executable inside `$PATH`.  static bool is_executable_in_path(const char_u *name, char_u **abspath) +  FUNC_ATTR_NONNULL_ARG(1)  {    const char *path = getenv("PATH");    // PATH environment variable does not exist or is empty. @@ -176,6 +182,7 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath)  ///        not `O_CREAT` or `O_TMPFILE`), subject to the current umask  /// @return file descriptor, or negative `errno` on failure  int os_open(const char* path, int flags, int mode) +  FUNC_ATTR_NONNULL_ALL  {    uv_fs_t open_req;    int r = uv_fs_open(uv_default_loop(), &open_req, path, flags, mode, NULL); @@ -188,6 +195,7 @@ int os_open(const char* path, int flags, int mode)  ///  /// @return OK on success, FAIL if a failure occurred.  static bool os_stat(const char *name, uv_stat_t *statbuf) +  FUNC_ATTR_NONNULL_ALL  {    uv_fs_t request;    int result = uv_fs_stat(uv_default_loop(), &request, name, NULL); @@ -200,6 +208,7 @@ static bool os_stat(const char *name, uv_stat_t *statbuf)  ///  /// @return `-1` when `name` doesn't exist.  int32_t os_getperm(const char_u *name) +  FUNC_ATTR_NONNULL_ALL  {    uv_stat_t statbuf;    if (os_stat((char *)name, &statbuf)) { @@ -213,6 +222,7 @@ int32_t os_getperm(const char_u *name)  ///  /// @return `OK` for success, `FAIL` for failure.  int os_setperm(const char_u *name, int perm) +  FUNC_ATTR_NONNULL_ALL  {    uv_fs_t request;    int result = uv_fs_chmod(uv_default_loop(), &request, @@ -233,6 +243,7 @@ int os_setperm(const char_u *name, int perm)  /// @note If the `owner` or `group` is specified as `-1`, then that ID is not  /// changed.  int os_fchown(int file_descriptor, uv_uid_t owner, uv_gid_t group) +  FUNC_ATTR_NONNULL_ALL  {    uv_fs_t request;    int result = uv_fs_fchown(uv_default_loop(), &request, file_descriptor, @@ -245,6 +256,7 @@ int os_fchown(int file_descriptor, uv_uid_t owner, uv_gid_t group)  ///  /// @return `true` if `name` exists.  bool os_file_exists(const char_u *name) +  FUNC_ATTR_NONNULL_ALL  {    uv_stat_t statbuf;    return os_stat((char *)name, &statbuf); @@ -254,6 +266,7 @@ bool os_file_exists(const char_u *name)  ///  /// @return `true` if `name` is readonly.  bool os_file_is_readonly(const char *name) +  FUNC_ATTR_NONNULL_ALL  {    return access(name, W_OK) != 0;  } @@ -264,6 +277,7 @@ bool os_file_is_readonly(const char *name)  /// @return `1` if `name` is writable,  /// @return `2` for a directory which we have rights to write into.  int os_file_is_writable(const char *name) +  FUNC_ATTR_NONNULL_ALL  {    if (access(name, W_OK) == 0) {      if (os_isdir((char_u *)name)) { @@ -278,6 +292,7 @@ int os_file_is_writable(const char *name)  ///  /// @return `OK` for success, `FAIL` for failure.  int os_rename(const char_u *path, const char_u *new_path) +  FUNC_ATTR_NONNULL_ALL  {    uv_fs_t request;    int result = uv_fs_rename(uv_default_loop(), &request, @@ -295,6 +310,7 @@ int os_rename(const char_u *path, const char_u *new_path)  ///  /// @return `0` for success, non-zero for failure.  int os_mkdir(const char *path, int32_t mode) +  FUNC_ATTR_NONNULL_ALL  {    uv_fs_t request;    int result = uv_fs_mkdir(uv_default_loop(), &request, path, mode, NULL); @@ -310,6 +326,7 @@ int os_mkdir(const char *path, int32_t mode)  ///                  failure.  /// @return `0` for success, non-zero for failure.  int os_mkdtemp(const char *template, char *path) +  FUNC_ATTR_NONNULL_ALL  {    uv_fs_t request;    int result = uv_fs_mkdtemp(uv_default_loop(), &request, template, NULL); @@ -324,6 +341,7 @@ int os_mkdtemp(const char *template, char *path)  ///  /// @return `0` for success, non-zero for failure.  int os_rmdir(const char *path) +  FUNC_ATTR_NONNULL_ALL  {    uv_fs_t request;    int result = uv_fs_rmdir(uv_default_loop(), &request, path, NULL); @@ -335,6 +353,7 @@ int os_rmdir(const char *path)  ///  /// @return `0` for success, non-zero for failure.  int os_remove(const char *path) +  FUNC_ATTR_NONNULL_ALL  {    uv_fs_t request;    int result = uv_fs_unlink(uv_default_loop(), &request, path, NULL); @@ -348,6 +367,7 @@ int os_remove(const char *path)  /// @param[out] file_info Pointer to a FileInfo to put the information in.  /// @return `true` on success, `false` for failure.  bool os_fileinfo(const char *path, FileInfo *file_info) +  FUNC_ATTR_NONNULL_ALL  {    return os_stat(path, &(file_info->stat));  } @@ -358,6 +378,7 @@ bool os_fileinfo(const char *path, FileInfo *file_info)  /// @param[out] file_info Pointer to a FileInfo to put the information in.  /// @return `true` on success, `false` for failure.  bool os_fileinfo_link(const char *path, FileInfo *file_info) +  FUNC_ATTR_NONNULL_ALL  {    uv_fs_t request;    int result = uv_fs_lstat(uv_default_loop(), &request, path, NULL); @@ -372,6 +393,7 @@ bool os_fileinfo_link(const char *path, FileInfo *file_info)  /// @param[out] file_info Pointer to a FileInfo to put the information in.  /// @return `true` on success, `false` for failure.  bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info) +  FUNC_ATTR_NONNULL_ALL  {    uv_fs_t request;    int result = uv_fs_fstat(uv_default_loop(), &request, file_descriptor, NULL); @@ -385,6 +407,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info)  /// @return `true` if the two FileInfos represent the same file.  bool os_fileinfo_id_equal(const FileInfo *file_info_1,                             const FileInfo *file_info_2) +  FUNC_ATTR_NONNULL_ALL  {    return file_info_1->stat.st_ino == file_info_2->stat.st_ino           && file_info_1->stat.st_dev == file_info_2->stat.st_dev; @@ -395,6 +418,7 @@ bool os_fileinfo_id_equal(const FileInfo *file_info_1,  /// @param file_info Pointer to the `FileInfo`  /// @param[out] file_id Pointer to a `FileID`  void os_fileinfo_id(const FileInfo *file_info, FileID *file_id) +  FUNC_ATTR_NONNULL_ALL  {    file_id->inode = file_info->stat.st_ino;    file_id->device_id = file_info->stat.st_dev; @@ -406,6 +430,7 @@ void os_fileinfo_id(const FileInfo *file_info, FileID *file_id)  /// @param file_info Pointer to the `FileInfo`  /// @return the inode number  uint64_t os_fileinfo_inode(const FileInfo *file_info) +  FUNC_ATTR_NONNULL_ALL  {    return file_info->stat.st_ino;  } @@ -443,6 +468,7 @@ uint64_t os_fileinfo_blocksize(const FileInfo *file_info)  /// @param[out] file_info Pointer to a `FileID` to fill in.  /// @return `true` on sucess, `false` for failure.  bool os_fileid(const char *path, FileID *file_id) +  FUNC_ATTR_NONNULL_ALL  {    uv_stat_t statbuf;    if (os_stat(path, &statbuf)) { @@ -459,6 +485,7 @@ bool os_fileid(const char *path, FileID *file_id)  /// @param file_id_2 Pointer to second `FileID`  /// @return `true` if the two `FileID`s represent te same file.  bool os_fileid_equal(const FileID *file_id_1, const FileID *file_id_2) +  FUNC_ATTR_NONNULL_ALL  {    return file_id_1->inode == file_id_2->inode           && file_id_1->device_id == file_id_2->device_id; @@ -471,6 +498,7 @@ bool os_fileid_equal(const FileID *file_id_1, const FileID *file_id_2)  /// @return `true` if the `FileID` and the `FileInfo` represent te same file.  bool os_fileid_equal_fileinfo(const FileID *file_id,                                  const FileInfo *file_info) +  FUNC_ATTR_NONNULL_ALL  {    return file_id->inode == file_info->stat.st_ino           && file_id->device_id == file_info->stat.st_dev; diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index a18d735ce6..d948a48b64 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -1,3 +1,4 @@ +#include <assert.h>  #include <string.h>  #include <stdint.h>  #include <stdbool.h> @@ -7,7 +8,6 @@  #include "nvim/api/private/defs.h"  #include "nvim/os/input.h"  #include "nvim/os/event.h" -#include "nvim/os/signal.h"  #include "nvim/os/rstream_defs.h"  #include "nvim/os/rstream.h"  #include "nvim/ascii.h" @@ -20,8 +20,8 @@  #include "nvim/getchar.h"  #include "nvim/term.h" -#define READ_BUFFER_SIZE 0xffff -#define INPUT_BUFFER_SIZE 4096 +#define READ_BUFFER_SIZE 0xfff +#define INPUT_BUFFER_SIZE (READ_BUFFER_SIZE * 4)  typedef enum {    kInputNone, @@ -48,10 +48,7 @@ void input_init(void)    }    read_buffer = rbuffer_new(READ_BUFFER_SIZE); -  read_stream = rstream_new(read_cb, -                            read_buffer, -                            NULL, -                            NULL); +  read_stream = rstream_new(read_cb, read_buffer, NULL);    rstream_set_file(read_stream, read_cmd_fd);  } @@ -76,7 +73,7 @@ void input_stop(void)  }  // 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; @@ -90,7 +87,7 @@ int os_inchar(uint8_t *buf, int maxlen, int32_t ms, int tb_change_cnt)        return 0;      }    } else { -    if ((result = inbuf_poll(p_ut)) == kInputNone) { +    if ((result = inbuf_poll((int)p_ut)) == kInputNone) {        if (trigger_cursorhold() && maxlen >= 3            && !typebuf_changed(tb_change_cnt)) {          buf[0] = K_SPECIAL; @@ -119,8 +116,9 @@ int os_inchar(uint8_t *buf, int maxlen, int32_t ms, int tb_change_cnt)      return 0;    } -  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 @@ -133,8 +131,8 @@ bool os_char_avail(void)  // In cooked mode we should get SIGINT, no need to check.  void os_breakcheck(void)  { -  if (curr_tmode == TMODE_RAW && input_poll(0)) -    convert_input(); +  if (curr_tmode == TMODE_RAW) +    input_poll(0);  }  /// Test whether a file descriptor refers to a terminal. @@ -167,23 +165,21 @@ void input_buffer_restore(String str)    free(str.data);  } -static bool input_poll(int32_t ms) +size_t input_enqueue(String keys)  { -  if (embedded_mode) { -    EventSource input_sources[] = { signal_event_source(), NULL }; -    return event_poll(ms, input_sources); -  } - -  EventSource input_sources[] = { -    rstream_event_source(read_stream), -    NULL -  }; +  size_t rv = rbuffer_write(input_buffer, keys.data, keys.size); +  process_interrupts(); +  return rv; +} -  return input_ready() || event_poll(ms, input_sources) || input_ready(); +static bool input_poll(int ms) +{ +  event_poll_until(ms, input_ready()); +  return input_ready();  }  // 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)) {      return kInputAvail; @@ -230,12 +226,14 @@ static void read_cb(RStream *rstream, void *data, bool at_eof)      }    } +  convert_input(); +  process_interrupts();    started_reading = true;  }  static void convert_input(void)  { -  if (!rbuffer_available(input_buffer)) { +  if (embedded_mode || !rbuffer_available(input_buffer)) {      // No input buffer space      return;    } @@ -248,24 +246,32 @@ static void convert_input(void)    if (convert) {      // Perform input conversion according to `input_conv` -    size_t unconverted_length; +    size_t unconverted_length = 0;      data = (char *)string_convert_ext(&input_conv,                                        (uint8_t *)data,                                        (int *)&converted_length,                                        (int *)&unconverted_length); -    data_length = rbuffer_pending(read_buffer) - unconverted_length; +    data_length -= unconverted_length;    } -  // Write processed data to input buffer -  size_t consumed = rbuffer_write(input_buffer, data, data_length); +  // The conversion code will be gone eventually, for now assume `input_buffer` +  // always has space for the converted data(it's many times the size of +  // `read_buffer`, so it's hard to imagine a scenario where the converted data +  // doesn't fit) +  assert(converted_length <= rbuffer_available(input_buffer)); +  // Write processed data to input buffer. +  (void)rbuffer_write(input_buffer, data, converted_length);    // Adjust raw buffer pointers -  rbuffer_consumed(read_buffer, consumed); +  rbuffer_consumed(read_buffer, data_length);    if (convert) {      // data points to memory allocated by `string_convert_ext`, free it.      free(data);    } +} +static void process_interrupts(void) +{    if (!ctrl_c_interrupts) {      return;    } @@ -273,17 +279,17 @@ static void convert_input(void)    char *inbuf = rbuffer_read_ptr(input_buffer);    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) { -      consume_count = i + 1; +      got_int = true; +      consume_count = (size_t)i;        break;      }    } -  if (consume_count) { +  if (got_int) {      // Remove everything typed before the CTRL-C      rbuffer_consumed(input_buffer, consume_count); -    got_int = true;    }  } @@ -304,6 +310,10 @@ static int push_event_key(uint8_t *buf, int maxlen)  // Check if there's pending input  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 && ( +            rbuffer_pending(input_buffer) > 0 ||  // Stdin input +            eof));                                // Stdin closed  } diff --git a/src/nvim/os/job.c b/src/nvim/os/job.c index 2ca1023290..f8ad6874c9 100644 --- a/src/nvim/os/job.c +++ b/src/nvim/os/job.c @@ -12,17 +12,29 @@  #include "nvim/os/wstream_defs.h"  #include "nvim/os/event.h"  #include "nvim/os/event_defs.h" -#include "nvim/os/time.h"  #include "nvim/os/shell.h" -#include "nvim/os/signal.h"  #include "nvim/vim.h"  #include "nvim/memory.h" -#include "nvim/term.h"  #define EXIT_TIMEOUT 25  #define MAX_RUNNING_JOBS 100  #define JOB_BUFFER_SIZE 0xFFFF +#define close_job_stream(job, stream, type)                                \ +  do {                                                                     \ +    if (job->stream) {                                                     \ +      type##stream_free(job->stream);                                      \ +      job->stream = NULL;                                                  \ +      if (!uv_is_closing((uv_handle_t *)&job->proc_std##stream)) {         \ +        uv_close((uv_handle_t *)&job->proc_std##stream, close_cb);         \ +      }                                                                    \ +    }                                                                      \ +  } while (0) + +#define close_job_in(job) close_job_stream(job, in, w) +#define close_job_out(job) close_job_stream(job, out, r) +#define close_job_err(job) close_job_stream(job, err, r) +  struct job {    // Job id the index in the job table plus one.    int id; @@ -30,13 +42,9 @@ struct job {    int64_t status;    // Number of polls after a SIGTERM that will trigger a SIGKILL    int exit_timeout; -  // exit_cb may be called while there's still pending data from stdout/stderr. -  // We use this reference count to ensure the JobExit event is only emitted -  // when stdout/stderr are drained -  int pending_refs; -  // Same as above, but for freeing the job memory which contains -  // libuv handles. Only after all are closed the job can be safely freed. -  int pending_closes; +  // Number of references to the job. The job resources will only be freed by +  // close_cb when this is 0 +  int refcount;    // If the job was already stopped    bool stopped;    // Data associated with the job @@ -99,25 +107,28 @@ void job_teardown(void)    // their status with `wait` or handling SIGCHLD. libuv does that    // automatically (and then calls `exit_cb`) but we have to give it a chance    // by running the loop one more time -  uv_run(uv_default_loop(), UV_RUN_NOWAIT); +  event_poll(0);    // Prepare to start shooting    for (i = 0; i < MAX_RUNNING_JOBS; i++) { -    if ((job = table[i]) == NULL) { -      continue; -    } +    job = table[i];      // Still alive -    while (is_alive(job) && remaining_tries--) { +    while (job && is_alive(job) && remaining_tries--) {        os_delay(50, 0);        // 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);      }    } +  // Last run to ensure all children were removed +  event_poll(0);  }  /// Tries to start a new job. @@ -163,8 +174,7 @@ Job *job_start(char **argv,    job->id = i + 1;    *status = job->id;    job->status = -1; -  job->pending_refs = 3; -  job->pending_closes = 4; +  job->refcount = 4;    job->data = data;    job->stdout_cb = stdout_cb;    job->stderr_cb = stderr_cb; @@ -205,7 +215,6 @@ Job *job_start(char **argv,    // Spawn the job    if (uv_spawn(uv_default_loop(), &job->proc, &job->proc_opts) != 0) { -    free_job(job);      *status = -1;      return NULL;    } @@ -213,14 +222,8 @@ Job *job_start(char **argv,    job->in = wstream_new(maxmem);    wstream_set_stream(job->in, (uv_stream_t *)&job->proc_stdin);    // Start the readable streams -  job->out = rstream_new(read_cb, -                         rbuffer_new(JOB_BUFFER_SIZE), -                         job, -                         job_event_source(job)); -  job->err = rstream_new(read_cb, -                         rbuffer_new(JOB_BUFFER_SIZE), -                         job, -                         job_event_source(job)); +  job->out = rstream_new(read_cb, rbuffer_new(JOB_BUFFER_SIZE), job); +  job->err = rstream_new(read_cb, rbuffer_new(JOB_BUFFER_SIZE), job);    rstream_set_stream(job->out, (uv_stream_t *)&job->proc_stdout);    rstream_set_stream(job->err, (uv_stream_t *)&job->proc_stderr);    rstream_start(job->out); @@ -273,51 +276,30 @@ void job_stop(Job *job)  ///         is possible on some OS.  int job_wait(Job *job, int ms) FUNC_ATTR_NONNULL_ALL  { -  // switch to cooked so `got_int` will be set if the user interrupts -  int old_mode = cur_tmode; -  settmode(TMODE_COOK); - -  EventSource sources[] = {job_event_source(job), signal_event_source(), NULL}; - -  // keep track of the elapsed time if ms > 0 -  uint64_t before = (ms > 0) ? os_hrtime() : 0; - -  while (1) { -    // check if the job has exited (and the status is available). -    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; -      } -    } +  // Increase refcount to stop the job from being freed before we have a +  // chance to get the status. +  job->refcount++; +  event_poll_until(ms, +      // Until... +      got_int ||                // interrupted by the user +      job->refcount == 1);  // job exited + +  // 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); +    event_poll(0);    } -  settmode(old_mode); +  if (!--job->refcount) { +    int status = (int) job->status; +    // Manually invoke close_cb to free the job resources +    close_cb((uv_handle_t *)&job->proc); +    return status; +  } -  // return -1 for a timeout, the job status otherwise -  return (job->pending_refs) ? -1 : (int) job->status; +  // return -1 for a timeout +  return  -1;  }  /// Close the pipe used to write to the job. @@ -331,15 +313,7 @@ int job_wait(Job *job, int ms) FUNC_ATTR_NONNULL_ALL  /// @param job The job instance  void job_close_in(Job *job) FUNC_ATTR_NONNULL_ALL  { -  if (!job->in) { -    return; -  } - -  // let other functions in the job module know that the in pipe is no more -  wstream_free(job->in); -  job->in = NULL; - -  uv_close((uv_handle_t *)&job->proc_stdin, close_cb); +  close_job_in(job);  }  /// All writes that complete after calling this function will be reported @@ -369,14 +343,6 @@ bool job_write(Job *job, WBuffer *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  ///  /// @param job A pointer to the job @@ -395,11 +361,6 @@ void *job_data(Job *job)    return job->data;  } -EventSource job_event_source(Job *job) -{ -  return job; -} -  static void job_exit_callback(Job *job)  {    // Free the slot now, 'exit_cb' may want to start another job to replace @@ -411,9 +372,6 @@ static void job_exit_callback(Job *job)      job->exit_cb(job, job->data);    } -  // Free the job resources -  free_job(job); -    // Stop polling job status if this was the last    job_count--;    if (job_count == 0) { @@ -426,16 +384,6 @@ static bool is_alive(Job *job)    return uv_process_kill(&job->proc, 0) == 0;  } -static void free_job(Job *job) -{ -  uv_close((uv_handle_t *)&job->proc_stdout, close_cb); -  if (job->in) { -    uv_close((uv_handle_t *)&job->proc_stdin, close_cb); -  } -  uv_close((uv_handle_t *)&job->proc_stderr, close_cb); -  uv_close((uv_handle_t *)&job->proc, close_cb); -} -  /// Iterates the table, sending SIGTERM to stopped jobs and SIGKILL to those  /// that didn't die from SIGTERM after a while(exit_timeout is 0).  static void job_prepare_cb(uv_prepare_t *handle) @@ -465,12 +413,14 @@ static void read_cb(RStream *rstream, void *data, bool eof)    if (rstream == job->out) {      job->stdout_cb(rstream, data, eof); +    if (eof) { +      close_job_out(job); +    }    } else {      job->stderr_cb(rstream, data, eof); -  } - -  if (eof && --job->pending_refs == 0) { -    emit_exit_event(job); +    if (eof) { +      close_job_err(job); +    }    }  } @@ -480,41 +430,29 @@ static void exit_cb(uv_process_t *proc, int64_t status, int term_signal)    Job *job = handle_get_job((uv_handle_t *)proc);    job->status = status; -  if (--job->pending_refs == 0) { -    emit_exit_event(job); -  } -} - -static void emit_exit_event(Job *job) -{ -  Event event = { -    .source = job_event_source(job), -    .type = kEventJobExit, -    .data.job = job -  }; -  event_push(event); +  uv_close((uv_handle_t *)&job->proc, close_cb);  }  static void close_cb(uv_handle_t *handle)  {    Job *job = handle_get_job(handle); -  if (--job->pending_closes == 0) { -    // Only free the job memory after all the associated handles are properly -    // closed by libuv -    rstream_free(job->out); -    rstream_free(job->err); -    if (job->in) { -      wstream_free(job->in); -    } +  if (handle == (uv_handle_t *)&job->proc) { +    // Make sure all streams are properly closed to trigger callback invocation +    // when job->proc is closed +    close_job_in(job); +    close_job_out(job); +    close_job_err(job); +  } -    // Free data memory of process and pipe handles, that was allocated -    // by handle_set_job in job_start. +  if (--job->refcount == 0) { +    // Invoke the exit_cb +    job_exit_callback(job); +    // Free all memory allocated for the job      free(job->proc.data);      free(job->proc_stdin.data);      free(job->proc_stdout.data);      free(job->proc_stderr.data); -      shell_free_argv(job->proc_opts.args);      free(job);    } diff --git a/src/nvim/os/msgpack_rpc.c b/src/nvim/os/msgpack_rpc.c deleted file mode 100644 index 55bc006ad1..0000000000 --- a/src/nvim/os/msgpack_rpc.c +++ /dev/null @@ -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")); -  } -} diff --git a/src/nvim/os/msgpack_rpc_helpers.h b/src/nvim/os/msgpack_rpc_helpers.h deleted file mode 100644 index aede6b1587..0000000000 --- a/src/nvim/os/msgpack_rpc_helpers.h +++ /dev/null @@ -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 - diff --git a/src/nvim/os/provider.c b/src/nvim/os/provider.c index d4fffaa053..414c8841fa 100644 --- a/src/nvim/os/provider.c +++ b/src/nvim/os/provider.c @@ -8,7 +8,7 @@  #include "nvim/api/vim.h"  #include "nvim/api/private/helpers.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/os.h"  #include "nvim/log.h" diff --git a/src/nvim/os/rstream.c b/src/nvim/os/rstream.c index 8f1c30de50..beff404fd0 100644 --- a/src/nvim/os/rstream.c +++ b/src/nvim/os/rstream.c @@ -8,8 +8,6 @@  #include "nvim/os/uv_helpers.h"  #include "nvim/os/rstream_defs.h"  #include "nvim/os/rstream.h" -#include "nvim/os/event_defs.h" -#include "nvim/os/event.h"  #include "nvim/ascii.h"  #include "nvim/vim.h"  #include "nvim/memory.h" @@ -33,7 +31,6 @@ struct rstream {    uv_file fd;    rstream_cb cb;    bool free_handle; -  EventSource source_override;  };  #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -76,18 +73,14 @@ void rbuffer_consumed(RBuffer *rbuffer, size_t count)  void rbuffer_produced(RBuffer *rbuffer, size_t count)  {    rbuffer->wpos += count; -  DLOG("Received %u bytes from RStream(address: %p, source: %p)", -       (size_t)cnt, -       rbuffer->rstream, -       rstream_event_source(rbuffer->rstream)); +  DLOG("Received %u bytes from RStream(%p)", (size_t)count, rbuffer->rstream);    rbuffer_relocate(rbuffer);    if (rbuffer->rstream && rbuffer->wpos == rbuffer->capacity) {      // The last read filled the buffer, stop reading for now +    //      rstream_stop(rbuffer->rstream); -    DLOG("Buffer for RStream(address: %p, source: %p) is full, stopping it", -         rstream, -         rstream_event_source(rstream)); +    DLOG("Buffer for RStream(%p) is full, stopping it", rbuffer->rstream);    }  } @@ -180,13 +173,8 @@ void rbuffer_free(RBuffer *rbuffer)  ///        for reading with `rstream_read`  /// @param buffer RBuffer instance to associate with the RStream  /// @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 -RStream * rstream_new(rstream_cb cb, -                      RBuffer *buffer, -                      void *data, -                      EventSource source_override) +RStream * rstream_new(rstream_cb cb, RBuffer *buffer, void *data)  {    RStream *rv = xmalloc(sizeof(RStream));    rv->buffer = buffer; @@ -198,7 +186,6 @@ RStream * rstream_new(rstream_cb cb,    rv->fread_idle = NULL;    rv->free_handle = false;    rv->file_type = UV_UNKNOWN_HANDLE; -  rv->source_override = source_override ? source_override : rv;    return rv;  } @@ -322,21 +309,6 @@ size_t rstream_read(RStream *rstream, char *buffer, size_t 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  // Called by libuv to allocate memory for reading. @@ -357,13 +329,11 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf)    if (cnt <= 0) {      if (cnt != UV_ENOBUFS) { -      DLOG("Closing RStream(address: %p, source: %p)", -           rstream, -           rstream_event_source(rstream)); +      DLOG("Closing RStream(%p)", rstream);        // Read error or EOF, either way stop the stream and invoke the callback        // with eof == true        uv_read_stop(stream); -      emit_read_event(rstream, true); +      rstream->cb(rstream, rstream->data, true);      }      return;    } @@ -374,7 +344,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    // the space actually used in the buffer.    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 @@ -409,7 +379,6 @@ static void fread_idle_cb(uv_idle_t *handle)    if (req.result <= 0) {      uv_idle_stop(rstream->fread_idle); -    emit_read_event(rstream, true);      return;    } @@ -417,7 +386,6 @@ static void fread_idle_cb(uv_idle_t *handle)    size_t nread = (size_t) req.result;    rbuffer_produced(rstream->buffer, nread);    rstream->fpos += nread; -  emit_read_event(rstream, false);  }  static void close_cb(uv_handle_t *handle) @@ -426,21 +394,9 @@ static void close_cb(uv_handle_t *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)  { +  assert(rbuffer->rpos <= rbuffer->wpos);    // Move data ...    memmove(        rbuffer->data,  // ...to the beginning of the buffer(rpos 0) diff --git a/src/nvim/os/server.h b/src/nvim/os/server.h deleted file mode 100644 index 43592a91e4..0000000000 --- a/src/nvim/os/server.h +++ /dev/null @@ -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 diff --git a/src/nvim/os/server_defs.h b/src/nvim/os/server_defs.h deleted file mode 100644 index 08cdf55428..0000000000 --- a/src/nvim/os/server_defs.h +++ /dev/null @@ -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 - diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 453cc6d605..d5464f7975 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -1,4 +1,5 @@  #include <string.h> +#include <assert.h>  #include <stdbool.h>  #include <stdlib.h> @@ -7,6 +8,7 @@  #include "nvim/ascii.h"  #include "nvim/lib/kvec.h"  #include "nvim/log.h" +#include "nvim/os/event.h"  #include "nvim/os/job.h"  #include "nvim/os/rstream.h"  #include "nvim/os/shell.h" @@ -58,11 +60,11 @@ typedef struct {  ///         `shell_free_argv` when no longer needed.  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 *)));    // Split 'shell' -  int i = tokenize(p_sh, rv); +  size_t i = tokenize(p_sh, rv);    if (extra_shell_opt != NULL) {      // 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    while (pdata.exited < expected_exits) { -    uv_run(uv_default_loop(), UV_RUN_ONCE); +    event_poll(0);      if (got_int) {        // 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  ///        words. It can be NULL if the caller only needs to count words.  /// @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;    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  /// @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;    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    // 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    // only after filled we should start allocating memory(skip unnecessary    // allocations for small writes) -  int buflen = BUFFER_LENGTH; +  size_t buflen = BUFFER_LENGTH;    pdata->wbuffer = (char *)xmalloc(buflen);    uv_buf_t uvbuf;    linenr_T lnum = curbuf->b_op_start.lnum; -  int off = 0; -  int written = 0; +  size_t off = 0; +  size_t written = 0;    char_u      *lp = ml_get(lnum); -  int l; -  int len; +  size_t l; +  size_t len;    for (;;) {      l = strlen((char *)lp + written); @@ -443,7 +445,7 @@ static void write_selection(uv_write_t *req)        pdata->wbuffer[off++] = NUL;      } else {        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) {          // Resize the buffer          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;    data->exited++; -  data->exit_status = status; +  assert(status <= INT_MAX); +  data->exit_status = (int)status;    uv_close((uv_handle_t *)proc, NULL);  } diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index 2f93cfb08a..36f7b37c48 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -12,8 +12,6 @@  #include "nvim/memory.h"  #include "nvim/misc1.h"  #include "nvim/misc2.h" -#include "nvim/os/event_defs.h" -#include "nvim/os/event.h"  #include "nvim/os/signal.h"  static uv_signal_t sint, spipe, shup, squit, sterm, swinch; @@ -72,45 +70,6 @@ void signal_accept_deadly(void)    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)  {    switch (signum) { @@ -154,20 +113,32 @@ static void deadly_signal(int signum)  static void signal_cb(uv_signal_t *handle, int signum)  { -  if (rejecting_deadly) { -    if (signum == SIGINT) { +  switch (signum) { +    case SIGINT:        got_int = true; -    } - -    return; +      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;    } - -  Event event = { -    .source = signal_event_source(), -    .type = kEventSignal, -    .data = { -      .signum = signum -    } -  }; -  event_push(event);  } diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index e3b76ac833..a4871ef499 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -1,3 +1,4 @@ +#include <assert.h>  #include <stdint.h>  #include <stdbool.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()  ///  /// @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);    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); +} diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index 52f57f8262..0ad15bc433 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -54,8 +54,8 @@  #include "nvim/os/shell.h"  #include "nvim/os/signal.h"  #include "nvim/os/job.h" -#include "nvim/os/msgpack_rpc.h" -#include "nvim/os/msgpack_rpc_helpers.h" +#include "nvim/msgpack_rpc/helpers.h" +#include "nvim/msgpack_rpc/defs.h"  #if defined(HAVE_SYS_IOCTL_H)  # include <sys/ioctl.h> @@ -166,8 +166,6 @@ void mch_init(void)    mac_conv_init();  #endif -  msgpack_rpc_init(); -  msgpack_rpc_helpers_init();    event_init();  } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 4e6123f206..ac726f7988 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -3478,6 +3478,11 @@ win_line (              int    i;              int    saved_nextra = n_extra; +            if (is_concealing && vcol_off > 0) { +              // there are characters to conceal +              tab_len += vcol_off; +            } +              /* if n_extra > 0, it gives the number of chars to use for               * a tab, else we need to calculate the width for a tab */              len = (tab_len * mb_char2len(lcs_tab2)); @@ -3495,6 +3500,12 @@ win_line (                n_extra += mb_char2len(lcs_tab2) - (saved_nextra > 0 ? 1: 0);              }              p_extra = p_extra_free; + +            // n_extra will be increased by FIX_FOX_BOGUSCOLS +            // macro below, so need to adjust for that here +            if (is_concealing && vcol_off > 0) { +              n_extra -= vcol_off; +            }            }            /* Tab alignment should be identical regardless of             * 'conceallevel' value. So tab compensates of all diff --git a/src/nvim/testdir/test100.in b/src/nvim/testdir/test100.in index f9f5f9119f..f6d2e3711b 100644 --- a/src/nvim/testdir/test100.in +++ b/src/nvim/testdir/test100.in @@ -18,6 +18,7 @@ STARTTEST  :call FillBuffer()  :call feedkeys(":earlier 10\n", 't')  :call UndoLevel() +:set ft=unix  :%w! test.out  :new two  :0put ='TWO: expecting global undolevels: 5, local undolevels: 2 (first) then 10 (afterwards)' @@ -27,6 +28,7 @@ STARTTEST  :call UndoLevel()  :setlocal ul=10  :call UndoLevel() +:set ft=unix  :%w >> test.out  :wincmd p  :redir >>test.out | echo "global value shouldn't be changed and still be 5!" | echo 'ONE: expecting global undolevels: 5, local undolevels: -123456 (default)'|:setglobal undolevels? | echon ' global' | setlocal undolevels? | echon ' local' |echo "" |redir end @@ -35,6 +37,7 @@ STARTTEST  :1put ='global value should be changed to 50'  :2put ='THREE: expecting global undolevels: 50, local undolevels: -123456 (default)'  :call UndoLevel() +:set ft=unix  :%w >> test.out  :"sleep 10  :" diff --git a/src/nvim/testdir/test35.in b/src/nvim/testdir/test35.in deleted file mode 100644 index ba97911a1d..0000000000 --- a/src/nvim/testdir/test35.in +++ /dev/null @@ -1,21 +0,0 @@ -Test Ctrl-A and Ctrl-X, which increment and decrement decimal, hexadecimal, -and octal numbers. - -STARTTEST -/^start-here -:set nrformats=octal,hex -j102ll64128$ -:set nrformats=octal -0102l2w65129blx6lD -:set nrformats=hex -0101l257Txldt    -:set nrformats= -0200l100w78k -:$-3,$wq! test.out -ENDTEST - -start-here -100     0x100     077     0 -100     0x100     077      -100     0x100     077     0xfF     0xFf -100     0x100     077      diff --git a/src/nvim/testdir/test35.ok b/src/nvim/testdir/test35.ok deleted file mode 100644 index 093ad958ac..0000000000 --- a/src/nvim/testdir/test35.ok +++ /dev/null @@ -1,4 +0,0 @@ -0     0x0ff     0000     -1 -0     1x100     0777777 --1     0x0     078     0xFE     0xfe --100     -100x100     000      diff --git a/src/nvim/testdir/test72.in b/src/nvim/testdir/test72.in index 0821764c3c..4700d86981 100644 --- a/src/nvim/testdir/test72.in +++ b/src/nvim/testdir/test72.in @@ -8,6 +8,7 @@ STARTTEST  :" Test 'undofile': first a simple one-line change.  :set nocompatible viminfo+=nviminfo visualbell  :set ul=100 undofile nomore +:set ft=unix  :e! Xtestfile  ggdGithis is one line:set ul=100  :s/one/ONE/ diff --git a/src/nvim/testdir/test75.in b/src/nvim/testdir/test75.in index b7f2783f54..8fabccdf52 100644 --- a/src/nvim/testdir/test75.in +++ b/src/nvim/testdir/test75.in @@ -23,16 +23,16 @@ STARTTEST  Go:"  :" Outside of the range, minimum  :inoremap <Char-0x1040> a -:call feedkeys("a\u1040\<Esc>") +:execute "normal a\u1040\<Esc>"  :" Inside of the range, minimum  :inoremap <Char-0x103f> b -:call feedkeys("a\u103f\<Esc>") +:execute "normal a\u103f\<Esc>"  :" Inside of the range, maximum  :inoremap <Char-0xf03f> c -:call feedkeys("a\uf03f\<Esc>") +:execute "normal a\uf03f\<Esc>"  :" Outside of the range, maximum  :inoremap <Char-0xf040> d -:call feedkeys("a\uf040\<Esc>") +:execute "normal a\uf040\<Esc>"  :"  :/^eof/+1,$w! test.out  :qa! diff --git a/src/nvim/testdir/test_listlbr.in b/src/nvim/testdir/test_listlbr.in index 0cce4c23a5..2f28126554 100644 --- a/src/nvim/testdir/test_listlbr.in +++ b/src/nvim/testdir/test_listlbr.in @@ -46,6 +46,16 @@ STARTTEST  :redraw!  :let line=ScreenChar(winwidth(0))  :call DoRecordScreen() +:let line="_S_\t bla" +:$put =line +:$ +:norm! zt +:let g:test ="Test 5: set linebreak with conceal and set list and tab displayed by different char (line may not be truncated)" +:set cpo&vim list linebreak conceallevel=2 concealcursor=nv listchars=tab:ab +:syn match ConcealVar contained /_/ conceal +:syn match All /.*/ contains=ConcealVar +:let line=ScreenChar(winwidth(0)) +:call DoRecordScreen()  :%w! test.out  :qa!  ENDTEST diff --git a/src/nvim/testdir/test_listlbr.ok b/src/nvim/testdir/test_listlbr.ok index be323d4dc7..9b8037f4d3 100644 --- a/src/nvim/testdir/test_listlbr.ok +++ b/src/nvim/testdir/test_listlbr.ok @@ -25,3 +25,10 @@ Test 4: set linebreak with tab and 1 line as long as screen: should break!  +aaaaaaaaaaaaaaaaaa   ~                     ~                    +_S_	 bla + +Test 5: set linebreak with conceal and set list and tab displayed by different char (line may not be truncated) +Sabbbbbb bla         +~                    +~                    +~                    diff --git a/src/nvim/undo.c b/src/nvim/undo.c index fe782053a7..7a57f70498 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -1009,7 +1009,7 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash)    int fd;    FILE        *fp = NULL;    int perm; -  int write_ok = FALSE; +  bool write_ok = false;    if (name == NULL) {      file_name = u_get_undo_file_name(buf->b_ffname, FALSE); @@ -1116,7 +1116,8 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash)     */    FileInfo file_info_old;    FileInfo file_info_new; -  if (os_fileinfo((char *)buf->b_ffname, &file_info_old) +  if (buf->b_ffname != NULL +      && os_fileinfo((char *)buf->b_ffname, &file_info_old)        && os_fileinfo((char *)file_name, &file_info_new)        && file_info_old.stat.st_gid != file_info_new.stat.st_gid        && os_fchown(fd, -1, file_info_old.stat.st_gid) != 0) { @@ -1177,7 +1178,7 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash)    }    if (put_bytes(fp, (long_u)UF_HEADER_END_MAGIC, 2) == OK) -    write_ok = TRUE; +    write_ok = true;  #ifdef U_DEBUG    if (headers_written != buf->b_u_numhead) {      EMSGN("Written %" PRId64 " headers, ...", headers_written); diff --git a/src/nvim/version.c b/src/nvim/version.c index e26da2c607..dea307925b 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -175,6 +175,36 @@ static char *(features[]) = {  };  static int included_patches[] = { +  //488, +  //487, +  //486, +  //485, +  //484, +  //483, +  //482, +  //481, +  //480, +  //479, +  //478, +  //477, +  //476, +  //475, +  //474, +  //473, +  //472, +  //471, +  //470, +  //469, +  //468, +  //467, +  //465, +  //464, +  //463, +  //462, +  //461, +  //460, +  //459, +  //458,    //457,    //456,    //455, @@ -199,10 +229,10 @@ static int included_patches[] = {    436,    //435,    //434, -  //433, +  433,    //432 NA    //431 NA -  //430, +  //430 NA    //429 NA    //428 NA    //427, @@ -226,7 +256,7 @@ static int included_patches[] = {    //409 NA    408,    407, -  //406, +  406,    405,    //404 NA    //403 NA @@ -234,12 +264,12 @@ static int included_patches[] = {    //401 NA    //400 NA    //399 NA -  //398, +  //398 NA    397,    //396,    //395, -  //394, -  //393, +  //394 NA +  //393 NA    392,    391,    //390, @@ -887,21 +917,11 @@ void intro_message(int colon)      "",      N_("type  :q<Enter>               to exit         "),      N_("type  :help<Enter>  or  <F1>  for on-line help"), -    NULL, -    "", -    N_("Running in Vi compatible mode"), -    N_("type  :set nocp<Enter>        for Vim defaults"), -    N_("type  :help cp-default<Enter> for info on this"),    };    // blanklines = screen height - # message lines    blanklines = (int)Rows - ((sizeof(lines) / sizeof(char *)) - 1); -  if (!p_cp) { -    // add 4 for not showing "Vi compatible" message -    blanklines += 4; -  } -    // Don't overwrite a statusline.  Depends on 'cmdheight'.    if (p_ls > 1) {      blanklines -= Rows - topframe->fr_height; diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 6a61607d2e..abf9c9efcd 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -105,4 +105,10 @@ describe('vim_* functions', function()        eq(nvim('get_windows')[2], nvim('get_current_window'))      end)    end) + +  it('can throw exceptions', function() +    local status, err = pcall(nvim, 'get_option', 'invalid-option') +    eq(false, status) +    ok(err:match('Invalid option name') ~= nil) +  end)  end) diff --git a/test/functional/ex_cmds/sign_spec.lua b/test/functional/ex_cmds/sign_spec.lua new file mode 100644 index 0000000000..74e1aa4702 --- /dev/null +++ b/test/functional/ex_cmds/sign_spec.lua @@ -0,0 +1,25 @@ +local helpers = require('test.functional.helpers') +local clear, nvim, buffer, curbuf, curwin, eq, ok = +  helpers.clear, helpers.nvim, helpers.buffer, helpers.curbuf, helpers.curwin, +  helpers.eq, helpers.ok + +describe('sign', function() +  describe('unplace {id}', function() +    describe('without specifying buffer', function() +      it('deletes the sign from all buffers', function() +        -- place a sign with id 34 to first buffer +        nvim('command', 'sign define Foo text=+ texthl=Delimiter linehl=Comment') +        local buf1 = nvim('eval', 'bufnr("%")') +        nvim('command', 'sign place 34 line=3 name=Foo buffer='..buf1) +        -- create a second buffer and place the sign on it as well +        nvim('command', 'new') +        local buf2 = nvim('eval', 'bufnr("%")') +        nvim('command', 'sign place 34 line=3 name=Foo buffer='..buf2) +        -- now unplace without specifying a buffer +        nvim('command', 'sign unplace 34') +        eq("\n--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf1)) +        eq("\n--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf2)) +      end) +    end) +  end) +end) diff --git a/test/functional/ex_cmds/wundo_spec.lua b/test/functional/ex_cmds/wundo_spec.lua new file mode 100644 index 0000000000..d8bd8a7031 --- /dev/null +++ b/test/functional/ex_cmds/wundo_spec.lua @@ -0,0 +1,20 @@ +-- Specs for +-- :wundo + +local helpers = require('test.functional.helpers') +local execute, eq, clear, eval, feed = +  helpers.execute, helpers.eq, helpers.clear, helpers.eval, helpers.feed + + +describe(':wundo', function() +  before_each(clear) + +  it('safely fails on new, non-empty buffer', function() +    feed('iabc<esc>') +    execute('wundo foo') -- This should not segfault. #1027 +    --TODO: check messages for error message + +    os.remove(eval('getcwd()') .. '/foo') --cleanup +  end) + +end) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 324af6a232..d9107543ea 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -25,19 +25,63 @@ end  local session +local rawfeed  local function restart()    local loop = Loop.new()    local msgpack_stream = MsgpackStream.new(loop)    local async_session = AsyncSession.new(msgpack_stream)    session = Session.new(async_session)    loop:spawn(nvim_argv) +  rawfeed([[:function BeforeEachTest() +    set all& +    redir => groups +    silent augroup +    redir END +    for group in split(groups) +      exe 'augroup '.group +      autocmd! +      augroup END +    endfor +    autocmd! +    tabnew +    let curbufnum = eval(bufnr('%')) +    redir => buflist +    silent ls! +    redir END +    let bufnums = [] +    for buf in split(buflist, '\n') +      let bufnum = eval(split(buf, '[ u]')[0]) +      if bufnum != curbufnum +        call add(bufnums, bufnum) +      endif +    endfor +    if len(bufnums) > 0 +      exe 'silent bwipeout! '.join(bufnums, ' ') +    endif +    silent tabonly +    for k in keys(g:) +      exe 'unlet g:'.k +    endfor +    filetype plugin indent off +    mapclear +    mapclear! +    abclear +    comclear +  endfunction +  ]])  end -restart() + +local loop_running, last_error  local function request(method, ...)    local status, rv = session:request(method, ...)    if not status then -    error(rv[2]) +    if loop_running then +      last_error = rv[2] +      session:stop() +    else +      error(rv[2]) +    end    end    return rv  end @@ -47,7 +91,14 @@ local function next_message()  end  local function run(request_cb, notification_cb, setup_cb) +  loop_running = true    session:run(request_cb, notification_cb, setup_cb) +  loop_running = false +  if last_error then +    local err = last_error +    last_error = nil +    error(err) +  end  end  local function stop() @@ -115,7 +166,7 @@ local function feed(...)    end  end -local function rawfeed(...) +function rawfeed(...)    for _, v in ipairs({...}) do      nvim_feed(dedent(v), 'nt')    end @@ -138,14 +189,6 @@ local function execute(...)    end  end -local  function eval(expr) -  local status, result = pcall(function() return nvim_eval(expr) end) -  if not status then -    error('Failed to evaluate expression "' .. expr .. '"') -  end -  return result -end -  local function eq(expected, actual)    return assert.are.same(expected, actual)  end @@ -158,44 +201,6 @@ local function expect(contents, first, last, buffer_index)    return eq(dedent(contents), buffer_slice(first, last, buffer_index))  end -rawfeed([[:function BeforeEachTest() -  set all& -  redir => groups -  silent augroup -  redir END -  for group in split(groups) -    exe 'augroup '.group -    autocmd! -    augroup END -  endfor -  autocmd! -  tabnew -  let curbufnum = eval(bufnr('%')) -  redir => buflist -  silent ls! -  redir END -  let bufnums = [] -  for buf in split(buflist, '\n') -    let bufnum = eval(split(buf, '[ u]')[0]) -    if bufnum != curbufnum -      call add(bufnums, bufnum) -    endif -  endfor -  if len(bufnums) > 0 -    exe 'silent bwipeout! '.join(bufnums, ' ') -  endif -  silent tabonly -  for k in keys(g:) -    exe 'unlet g:'.k -  endfor -  filetype plugin indent off -  mapclear -  mapclear! -  abclear -  comclear -endfunction -]]) -  local function ok(expr)    assert.is_true(expr) @@ -245,6 +250,8 @@ local function curtab(method, ...)    return tabpage(method, tab, ...)  end +restart() +  return {    clear = clear,    restart = restart, @@ -252,7 +259,8 @@ return {    insert = insert,    feed = feed,    execute = execute, -  eval = eval, +  eval = nvim_eval, +  command = nvim_command,    request = request,    next_message = next_message,    run = run, diff --git a/test/functional/job/job_spec.lua b/test/functional/job/job_spec.lua new file mode 100644 index 0000000000..b2a65f8269 --- /dev/null +++ b/test/functional/job/job_spec.lua @@ -0,0 +1,59 @@ + +local helpers = require('test.functional.helpers') +local clear, nvim, eq, neq, ok, expect, eval, next_message, run, stop, session +  = helpers.clear, helpers.nvim, helpers.eq, helpers.neq, helpers.ok, +  helpers.expect, helpers.eval, helpers.next_message, helpers.run, +  helpers.stop, helpers.session + +local channel = nvim('get_api_info')[1] + +describe('jobs', function() +  before_each(clear) + +  -- Creates the string to make an autocmd to notify us. +  local notify_str = function(expr) +    return "au! JobActivity xxx call rpcnotify("..channel..", "..expr..")" +  end + +  it('returns 0 when it fails to start', function() +    local status, rv = pcall(eval, "jobstart('', '')") +    eq(false, status) +    ok(rv ~= nil) +  end) + +  it('calls JobActivity when the job writes and exits', function() +    nvim('command', notify_str('v:job_data[1]')) +    nvim('command', "call jobstart('xxx', 'echo')") +    eq({'notification', 'stdout', {}}, next_message()) +    eq({'notification', 'exit', {}}, next_message()) +  end) + +  it('allows interactive commands', function() +    nvim('command', notify_str('v:job_data[2]')) +    nvim('command', "let j = jobstart('xxx', 'cat', ['-'])") +    neq(0, eval('j')) +    nvim('command', "call jobsend(j, 'abc')") +    eq({'notification', 'abc', {}}, next_message()) +    nvim('command', "call jobsend(j, '123')") +    eq({'notification', '123', {}}, next_message()) +    nvim('command', notify_str('v:job_data[1])')) +    nvim('command', "call jobstop(j)") +    eq({'notification', 'exit', {}}, next_message()) +  end) + +  it('will not allow jobsend/stop on a non-existent job', function() +    eq(false, pcall(eval, "jobsend(-1, 'lol')")) +    eq(false, pcall(eval, "jobstop(-1, 'lol')")) +  end) + +  it('will not allow jobstop twice on the same job', function() +    nvim('command', "let j = jobstart('xxx', 'cat', ['-'])") +    neq(0, eval('j')) +    eq(true, pcall(eval, "jobstop(j)")) +    eq(false, pcall(eval, "jobstop(j)")) +  end) + +  it('will not cause a memory leak if we leave a job running', function() +    nvim('command', "call jobstart('xxx', 'cat', ['-'])") +  end) +end) diff --git a/test/functional/legacy/035_increment_and_decrement_spec.lua b/test/functional/legacy/035_increment_and_decrement_spec.lua new file mode 100644 index 0000000000..20c0cc4206 --- /dev/null +++ b/test/functional/legacy/035_increment_and_decrement_spec.lua @@ -0,0 +1,44 @@ +-- Test Ctrl-A and Ctrl-X, which increment and decrement decimal, hexadecimal, +-- and octal numbers. + +local helpers = require('test.functional.helpers') +local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert +local execute, expect = helpers.execute, helpers.expect + +describe('increment and decrement commands', function() +  setup(clear) + +  it('should work', function() +    -- Insert some numbers in various bases. +    insert([[ +      100     0x100     077     0 +      100     0x100     077 +      100     0x100     077     0xfF     0xFf +      100     0x100     077]]) + +    -- Increment and decrement numbers in the first row, interpreting the +    -- numbers as decimal, octal or hexadecimal. +    execute('set nrformats=octal,hex', '1') +    feed('102ll64128$') + +    -- For the second row, treat the numbers as decimal or octal. +    -- 0x100 should be interpreted as decimal 0, the character x, and decimal 100. +    execute('set nrformats=octal', '2') +    feed('0102l2w65129blx6lD') + +    -- For the third row, treat the numbers as decimal or hexadecimal. +    -- 077 should be interpreted as decimal 77. +    execute('set nrformats=hex', '3') +    feed('0101l257Txldt   ') + +    -- For the last row, interpret all numbers as decimal. +    execute('set nrformats=', '4') +    feed('0200l100w78') + +    expect([[ +      0     0x0ff     0000     -1 +      0     1x100     0777777 +      -1     0x0     078     0xFE     0xfe +      -100     -100x100     000]]) +  end) +end) diff --git a/test/functional/shell/viml_system_spec.lua b/test/functional/shell/viml_system_spec.lua index effabe715c..a8bab8e26e 100644 --- a/test/functional/shell/viml_system_spec.lua +++ b/test/functional/shell/viml_system_spec.lua @@ -19,10 +19,30 @@ local function delete_file(name)    end  end +-- Some tests require the xclip program and a x server. +local xclip = nil +do  +  if os.getenv('DISPLAY') then +    local proc = io.popen('which xclip')  +    xclip = proc:read() +    proc:close() +  end +end  describe('system()', function()    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')) +    eval([[system('this-should-not-exist')]]) +    eq(127, eval('v:shell_error')) +  end) +    describe('passing no input', function()      it('returns the program output', function()        eq("echoed", eval('system("echo -n echoed")')) @@ -76,6 +96,15 @@ describe('system()', function()        end)      end)    end) + +  if xclip then +    describe("with a program that doesn't close stdout", function() +      it('will exit properly after passing input', function() +        eq(nil, eval([[system('xclip -i -selection clipboard', 'clip-data')]])) +        eq('clip-data', eval([[system('xclip -o -selection clipboard')]])) +      end) +    end) +  end  end)  describe('systemlist()', function() @@ -83,6 +112,17 @@ describe('systemlist()', function()    -- string.    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')) +    eval([[systemlist('this-should-not-exist')]]) +    eq(127, eval('v:shell_error')) +  end) +    describe('passing string with linefeed characters as input', function()      it('splits the output on linefeed characters', function()        eq({'abc', 'def', 'ghi'}, eval([[systemlist("cat -", "abc\ndef\nghi")]])) @@ -122,4 +162,15 @@ describe('systemlist()', function()        end)      end)    end) + +  if xclip then +    describe("with a program that doesn't close stdout", function() +      it('will exit properly after passing input', function() +        eq(nil, eval( +          "systemlist('xclip -i -selection clipboard', ['clip', 'data'])")) +        eq({'clip', 'data'}, eval( +          "systemlist('xclip -o -selection clipboard')")) +      end) +    end) +  end  end) | 
