diff options
40 files changed, 749 insertions, 487 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 947de61988..1edaa63de6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -250,7 +250,7 @@ check_c_source_compiles(" int main(void) { void *trace[1]; - int trace_size = backtrace(trace, 1); + backtrace(trace, 1); return 0; } " HAVE_EXECINFO_BACKTRACE) @@ -258,7 +258,7 @@ int main(void) check_c_source_compiles(" int main(void) { - int a; + int a = 42; __builtin_add_overflow(a, a, &a); __builtin_sub_overflow(a, a, &a); return 0; @@ -668,15 +668,6 @@ else() COMMENT "lualint: LUACHECK_PRG not defined") endif() -if(FLAKE8_PRG) - add_custom_target(pylint - COMMAND ${FLAKE8_PRG} contrib/ scripts/ src/ test/ - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) -else() - add_custom_target(pylint false - COMMENT "flake8: FLAKE8_PRG not defined") -endif() - set(CPACK_PACKAGE_NAME "Neovim") set(CPACK_PACKAGE_VENDOR "neovim.io") set(CPACK_PACKAGE_VERSION ${NVIM_VERSION_MEDIUM}) @@ -138,8 +138,13 @@ functionaltest-lua: | nvim lualint: | build/.ran-cmake deps $(BUILD_CMD) -C build lualint -pylint: | build/.ran-cmake deps - $(BUILD_CMD) -C build pylint +pylint: + flake8 contrib/ scripts/ src/ test/ + +# Run pylint only if flake8 is installed. +_opt_pylint: + @command -v flake8 && { $(MAKE) pylint; exit $$?; } \ + || echo "SKIP: pylint (flake8 not found)" unittest: | nvim +$(BUILD_CMD) -C build unittest @@ -182,7 +187,7 @@ appimage: appimage-%: bash scripts/genappimage.sh $* -lint: check-single-includes clint lualint pylint +lint: check-single-includes clint lualint _opt_pylint # Generic pattern rules, allowing for `make build/bin/nvim` etc. # Does not work with "Unix Makefiles". diff --git a/ci/build.ps1 b/ci/build.ps1 index da1ad2f4b6..42066c462b 100644 --- a/ci/build.ps1 +++ b/ci/build.ps1 @@ -137,8 +137,10 @@ if ($uploadToCodecov) { # Old tests # Add MSYS to path, required for e.g. `find` used in test scripts. # But would break functionaltests, where its `more` would be used then. +$OldPath = $env:PATH $env:PATH = "C:\msys64\usr\bin;$env:PATH" & "C:\msys64\mingw$bits\bin\mingw32-make.exe" -C $(Convert-Path ..\src\nvim\testdir) VERBOSE=1 +$env:PATH = $OldPath if ($uploadToCodecov) { bash -l /c/projects/neovim/ci/common/submit_coverage.sh oldtest diff --git a/cmake/FindLibLUV.cmake b/cmake/FindLibLUV.cmake index 66f827214e..784e3fd249 100644 --- a/cmake/FindLibLUV.cmake +++ b/cmake/FindLibLUV.cmake @@ -14,12 +14,6 @@ set(LIBLUV_DEFINITIONS ${PC_LIBLUV_CFLAGS_OTHER}) find_path(LIBLUV_INCLUDE_DIR luv/luv.h PATHS ${PC_LIBLUV_INCLUDEDIR} ${PC_LIBLUV_INCLUDE_DIRS}) -# If we're asked to use static linkage, add libluv.a as a preferred library name. -if(LIBLUV_USE_STATIC) - list(APPEND LIBLUV_NAMES - "${CMAKE_STATIC_LIBRARY_PREFIX}luv${CMAKE_STATIC_LIBRARY_SUFFIX}") -endif() - list(APPEND LIBLUV_NAMES luv) find_library(LIBLUV_LIBRARY NAMES ${LIBLUV_NAMES} diff --git a/cmake/FindLibTermkey.cmake b/cmake/FindLibTermkey.cmake index 6e09a692c8..3e0c7f1bfd 100644 --- a/cmake/FindLibTermkey.cmake +++ b/cmake/FindLibTermkey.cmake @@ -14,12 +14,6 @@ set(LIBTERMKEY_DEFINITIONS ${PC_LIBTERMKEY_CFLAGS_OTHER}) find_path(LIBTERMKEY_INCLUDE_DIR termkey.h PATHS ${PC_LIBTERMKEY_INCLUDEDIR} ${PC_LIBTERMKEY_INCLUDE_DIRS}) -# If we're asked to use static linkage, add libuv.a as a preferred library name. -if(LIBTERMKEY_USE_STATIC) - list(APPEND LIBTERMKEY_NAMES - "${CMAKE_STATIC_LIBRARY_PREFIX}termkey${CMAKE_STATIC_LIBRARY_SUFFIX}") -endif() - list(APPEND LIBTERMKEY_NAMES termkey) find_library(LIBTERMKEY_LIBRARY NAMES ${LIBTERMKEY_NAMES} diff --git a/cmake/FindLibUV.cmake b/cmake/FindLibUV.cmake index e94a243ec6..951fb0435e 100644 --- a/cmake/FindLibUV.cmake +++ b/cmake/FindLibUV.cmake @@ -4,9 +4,6 @@ # LIBUV_FOUND - system has libuv # LIBUV_INCLUDE_DIRS - the libuv include directories # LIBUV_LIBRARIES - link these to use libuv -# -# Set the LIBUV_USE_STATIC variable to specify if static libraries should -# be preferred to shared ones. find_package(PkgConfig) if (PKG_CONFIG_FOUND) @@ -16,12 +13,6 @@ endif() find_path(LIBUV_INCLUDE_DIR uv.h HINTS ${PC_LIBUV_INCLUDEDIR} ${PC_LIBUV_INCLUDE_DIRS}) -# If we're asked to use static linkage, add libuv.a as a preferred library name. -if(LIBUV_USE_STATIC) - list(APPEND LIBUV_NAMES - "${CMAKE_STATIC_LIBRARY_PREFIX}uv${CMAKE_STATIC_LIBRARY_SUFFIX}") -endif(LIBUV_USE_STATIC) - list(APPEND LIBUV_NAMES uv) find_library(LIBUV_LIBRARY NAMES ${LIBUV_NAMES} diff --git a/cmake/FindLibVterm.cmake b/cmake/FindLibVterm.cmake index e11d1caddc..469494ddfd 100644 --- a/cmake/FindLibVterm.cmake +++ b/cmake/FindLibVterm.cmake @@ -4,34 +4,7 @@ # LIBVTERM_INCLUDE_DIRS - The libvterm include directories # LIBVTERM_LIBRARIES - The libraries needed to use libvterm -find_package(PkgConfig) -if (PKG_CONFIG_FOUND) - pkg_check_modules(PC_LIBVTERM QUIET vterm) -endif() +include(LibFindMacros) -set(LIBVTERM_DEFINITIONS ${PC_LIBVTERM_CFLAGS_OTHER}) - -find_path(LIBVTERM_INCLUDE_DIR vterm.h - PATHS ${PC_LIBVTERM_INCLUDEDIR} ${PC_LIBVTERM_INCLUDE_DIRS}) - -# If we're asked to use static linkage, add libuv.a as a preferred library name. -if(LIBVTERM_USE_STATIC) - list(APPEND LIBVTERM_NAMES - "${CMAKE_STATIC_LIBRARY_PREFIX}vterm${CMAKE_STATIC_LIBRARY_SUFFIX}") -endif() - -list(APPEND LIBVTERM_NAMES vterm) - -find_library(LIBVTERM_LIBRARY NAMES ${LIBVTERM_NAMES} - HINTS ${PC_LIBVTERM_LIBDIR} ${PC_LIBVTERM_LIBRARY_DIRS}) - -set(LIBVTERM_LIBRARIES ${LIBVTERM_LIBRARY}) -set(LIBVTERM_INCLUDE_DIRS ${LIBVTERM_INCLUDE_DIR}) - -include(FindPackageHandleStandardArgs) -# handle the QUIETLY and REQUIRED arguments and set LIBVTERM_FOUND to TRUE -# if all listed variables are TRUE -find_package_handle_standard_args(LibVterm DEFAULT_MSG - LIBVTERM_LIBRARY LIBVTERM_INCLUDE_DIR) - -mark_as_advanced(LIBVTERM_INCLUDE_DIR LIBVTERM_LIBRARY) +libfind_pkg_detect(LIBVTERM vterm FIND_PATH vterm.h FIND_LIBRARY vterm) +libfind_process(LIBVTERM REQUIRED) diff --git a/cmake/FindLuaJit.cmake b/cmake/FindLuaJit.cmake index d60b6f09be..72795afefd 100644 --- a/cmake/FindLuaJit.cmake +++ b/cmake/FindLuaJit.cmake @@ -15,13 +15,6 @@ find_path(LUAJIT_INCLUDE_DIR luajit.h PATHS ${PC_LUAJIT_INCLUDEDIR} ${PC_LUAJIT_INCLUDE_DIRS} PATH_SUFFIXES luajit-2.0 luajit-2.1) -# If we're asked to use static linkage, add libluajit-5.1.a as a preferred -# library name. -if(LUAJIT_USE_STATIC) - list(APPEND LUAJIT_NAMES - "${CMAKE_STATIC_LIBRARY_PREFIX}luajit-5.1${CMAKE_STATIC_LIBRARY_SUFFIX}") -endif() - if(MSVC) list(APPEND LUAJIT_NAMES lua51) elseif(MINGW) diff --git a/cmake/FindMsgpack.cmake b/cmake/FindMsgpack.cmake index df4efa9c41..26eb19d498 100644 --- a/cmake/FindMsgpack.cmake +++ b/cmake/FindMsgpack.cmake @@ -26,13 +26,6 @@ else() set(MSGPACK_VERSION_STRING) endif() -# If we're asked to use static linkage, add libmsgpack{,c}.a as a preferred library name. -if(MSGPACK_USE_STATIC) - list(APPEND MSGPACK_NAMES - "${CMAKE_STATIC_LIBRARY_PREFIX}msgpackc${CMAKE_STATIC_LIBRARY_SUFFIX}" - "${CMAKE_STATIC_LIBRARY_PREFIX}msgpack${CMAKE_STATIC_LIBRARY_SUFFIX}") -endif() - if(MSVC) # The import library for the msgpack DLL has a different name list(APPEND MSGPACK_NAMES msgpackc_import) diff --git a/contrib/local.mk.example b/contrib/local.mk.example index a8c8e9cefb..5a31ded59b 100644 --- a/contrib/local.mk.example +++ b/contrib/local.mk.example @@ -47,18 +47,6 @@ # # DEPS_CMAKE_FLAGS += -DUSE_BUNDLED=OFF -# By default, bundled libraries are statically linked to nvim. -# This has no effect for non-bundled deps, which are always dynamically linked. -# Uncomment these entries to instead use dynamic linking. -# -# CMAKE_EXTRA_FLAGS += -DLIBTERMKEY_USE_STATIC=OFF -# CMAKE_EXTRA_FLAGS += -DLIBUNIBILIUM_USE_STATIC=OFF -# CMAKE_EXTRA_FLAGS += -DLIBUV_USE_STATIC=OFF -# CMAKE_EXTRA_FLAGS += -DLIBVTERM_USE_STATIC=OFF -# CMAKE_EXTRA_FLAGS += -DLUAJIT_USE_STATIC=OFF -# CMAKE_EXTRA_FLAGS += -DMSGPACK_USE_STATIC=OFF -# -# # .DEFAULT_GOAL := nvim # # Run doxygen over the source code. diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 709e5885e4..2c6b053994 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -200,17 +200,26 @@ User reloads the buffer with ":edit", emits: > nvim_buf_detach_event[{buf}] *api-buffer-updates-lua* -In-process lua plugins can also recieve buffer updates, in the form of lua +In-process lua plugins can also receive buffer updates, in the form of lua callbacks. These callbacks are called frequently in various contexts, buffer contents or window layout should not be changed inside these |textlock|. |vim.schedule| can be used to defer these operations to the main loop, where they are allowed. |nvim_buf_attach| will take keyword args for the callbacks. "on_lines" will -receive parameters ("lines", {buf}, {changedtick}, {firstline}, {lastline}, {new_lastline}). -Unlike remote channels the text contents are not passed. The new text can be -accessed inside the callback as -`vim.api.nvim_buf_get_lines(buf, firstline, new_lastline, true)` +receive parameters ("lines", {buf}, {changedtick}, {firstline}, {lastline}, +{new_lastline}, {old_byte_size}[, {old_utf32_size}, {old_utf16_size}]). +Unlike remote channel events the text contents are not passed. The new text can +be accessed inside the callback as + + `vim.api.nvim_buf_get_lines(buf, firstline, new_lastline, true)` + +{old_byte_size} is the total size of the replaced region {firstline} to +{lastline} in bytes, including the final newline after {lastline}. if +`utf_sizes` is set to true in |nvim_buf_attach()| keyword args, then the +UTF-32 and UTF-16 sizes of the deleted region is also passed as additional +arguments {old_utf32_size} and {old_utf16_size}. + "on_changedtick" is invoked when |b:changedtick| was incremented but no text was changed. The parameters recieved are ("changedtick", {buf}, {changedtick}). diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt index 7ddcb6cc92..a9b8c5fae8 100644 --- a/runtime/doc/if_lua.txt +++ b/runtime/doc/if_lua.txt @@ -459,6 +459,24 @@ vim.stricmp({a}, {b}) *vim.stricmp()* are equal, {a} is greater than {b} or {a} is lesser than {b}, respectively. +vim.str_utfindex({str}[, {index}]) *vim.str_utfindex()* + Convert byte index to UTF-32 and UTF-16 indicies. If {index} is not + supplied, the length of the string is used. All indicies are zero-based. + Returns two values: the UTF-32 and UTF-16 indicies respectively. + + Embedded NUL bytes are treated as terminating the string. Invalid + UTF-8 bytes, and embedded surrogates are counted as one code + point each. An {index} in the middle of a UTF-8 sequence is rounded + upwards to the end of that sequence. + +vim.str_byteindex({str}, {index}[, {use_utf16}]) *vim.str_byteindex()* + Convert UTF-32 or UTF-16 {index} to byte index. If {use_utf16} is not + supplied, it defaults to false (use UTF-32). Returns the byte index. + + Invalid UTF-8 and NUL is treated like by |vim.str_byteindex()|. An {index} + in the middle of a UTF-16 sequence is rounded upwards to the end of that + sequence. + vim.schedule({callback}) *vim.schedule()* Schedules {callback} to be invoked soon by the main event-loop. Useful to avoid |textlock| or other temporary restrictions. diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index b0b65545ab..c6f82e9d85 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -109,9 +109,11 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) /// `nvim_buf_lines_event`. Otherwise, the first notification will be /// a `nvim_buf_changedtick_event`. Not used for lua callbacks. /// @param opts Optional parameters. -/// `on_lines`: lua callback received on change. +/// `on_lines`: lua callback received on change. /// `on_changedtick`: lua callback received on changedtick /// increment without text change. +/// `utf_sizes`: include UTF-32 and UTF-16 size of +/// the replaced region. /// See |api-buffer-updates-lua| for more information /// @param[out] err Error details, if any /// @return False when updates couldn't be enabled because the buffer isn't @@ -156,6 +158,12 @@ Boolean nvim_buf_attach(uint64_t channel_id, } cb.on_detach = v->data.luaref; v->data.integer = LUA_NOREF; + } else if (is_lua && strequal("utf_sizes", k.data)) { + if (v->type != kObjectTypeBoolean) { + api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean"); + goto error; + } + cb.utf_sizes = v->data.boolean; } else { api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); goto error; @@ -1176,6 +1184,30 @@ free_exit: return 0; } +Dictionary nvim__buf_stats(Buffer buffer, Error *err) +{ + Dictionary rv = ARRAY_DICT_INIT; + + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return rv; + } + + // Number of times the cached line was flushed. + // This should generally not increase while editing the same + // line in the same mode. + PUT(rv, "flush_count", INTEGER_OBJ(buf->flush_count)); + // lnum of current line + PUT(rv, "current_lnum", INTEGER_OBJ(buf->b_ml.ml_line_lnum)); + // whether the line has unflushed changes. + PUT(rv, "line_dirty", BOOLEAN_OBJ(buf->b_ml.ml_flags & ML_LINE_DIRTY)); + // NB: this should be zero at any time API functions are called, + // this exists to debug issues + PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes)); + + return rv; +} + // Check if deleting lines made the cursor position invalid. // Changed lines from `lo` to `hi`; added `extra` lines (negative if deleted). static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra) diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 143737b478..b11eaefdd0 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -459,8 +459,9 @@ typedef struct { LuaRef on_lines; LuaRef on_changedtick; LuaRef on_detach; + bool utf_sizes; } BufUpdateCallbacks; -#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF } +#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, false } #define BUF_HAS_QF_ENTRY 1 #define BUF_HAS_LL_ENTRY 2 @@ -802,11 +803,26 @@ struct file_buffer { kvec_t(BufhlLine *) b_bufhl_move_space; // temporary space for highlights - // array of channelids which have asked to receive updates for this + // array of channel_id:s which have asked to receive updates for this // buffer. kvec_t(uint64_t) update_channels; + // array of lua callbacks for buffer updates. kvec_t(BufUpdateCallbacks) update_callbacks; + // whether an update callback has requested codepoint size of deleted regions. + bool update_need_codepoints; + + // Measurements of the deleted or replaced region since the last update + // event. Some consumers of buffer changes need to know the byte size (like + // tree-sitter) or the corresponding UTF-32/UTF-16 size (like LSP) of the + // deleted text. + size_t deleted_bytes; + size_t deleted_codepoints; + size_t deleted_codeunits; + + // The number for times the current line has been flushed in the memline. + int flush_count; + int b_diff_failed; // internal diff failed for this buffer }; diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c index 21efda9fd9..3604578b50 100644 --- a/src/nvim/buffer_updates.c +++ b/src/nvim/buffer_updates.c @@ -26,6 +26,9 @@ bool buf_updates_register(buf_T *buf, uint64_t channel_id, if (channel_id == LUA_INTERNAL_CALL) { kv_push(buf->update_callbacks, cb); + if (cb.utf_sizes) { + buf->update_need_codepoints = true; + } return true; } @@ -169,6 +172,10 @@ void buf_updates_send_changes(buf_T *buf, int64_t num_removed, bool send_tick) { + size_t deleted_codepoints, deleted_codeunits; + size_t deleted_bytes = ml_flush_deleted_bytes(buf, &deleted_codepoints, + &deleted_codeunits); + if (!buf_updates_active(buf)) { return; } @@ -231,8 +238,8 @@ void buf_updates_send_changes(buf_T *buf, bool keep = true; if (cb.on_lines != LUA_NOREF) { Array args = ARRAY_DICT_INIT; - Object items[5]; - args.size = 5; + Object items[8]; + args.size = 6; // may be increased to 8 below args.items = items; // the first argument is always the buffer handle @@ -250,6 +257,13 @@ void buf_updates_send_changes(buf_T *buf, // the last line in the updated range args.items[4] = INTEGER_OBJ(firstline - 1 + num_added); + // byte count of previous contents + args.items[5] = INTEGER_OBJ((Integer)deleted_bytes); + if (cb.utf_sizes) { + args.size = 8; + args.items[6] = INTEGER_OBJ((Integer)deleted_codepoints); + args.items[7] = INTEGER_OBJ((Integer)deleted_codeunits); + } textlock++; Object res = executor_exec_lua_cb(cb.on_lines, "lines", args, true); textlock--; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index b315639681..bad844e9d5 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4060,10 +4060,9 @@ static char_u *replace_makeprg(exarg_T *eap, char_u *p, char_u **cmdlinep) return p; } -/* - * Expand file name in Ex command argument. - * Return FAIL for failure, OK otherwise. - */ +// Expand file name in Ex command argument. +// When an error is detected, "errormsgp" is set to a non-NULL pointer. +// Return FAIL for failure, OK otherwise. int expand_filename(exarg_T *eap, char_u **cmdlinep, char_u **errormsgp) { int has_wildcards; /* need to expand wildcards */ diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 410c68017d..882bf1b830 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -1761,6 +1761,9 @@ failed: ml_delete(curbuf->b_ml.ml_line_count, false); linecnt--; } + curbuf->deleted_bytes = 0; + curbuf->deleted_codepoints = 0; + curbuf->deleted_codeunits = 0; linecnt = curbuf->b_ml.ml_line_count - linecnt; if (filesize == 0) linecnt = 0; diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index f7614fd3e1..03f64c2019 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1945,6 +1945,7 @@ static int vgetorpeek(int advance) // No matching mapping found or found a non-matching mapping that // matches at least what the matching mapping matched keylen = 0; + (void)keylen; // suppress clang/dead assignment // If there was no mapping, use the character from the typeahead // buffer right here. Otherwise, use the mapping (loop around). if (mp == NULL) { diff --git a/src/nvim/globals.h b/src/nvim/globals.h index d13b121562..b095e759d9 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -627,6 +627,8 @@ EXTERN pos_T Insstart_orig; EXTERN int orig_line_count INIT(= 0); /* Line count when "gR" started */ EXTERN int vr_lines_changed INIT(= 0); /* #Lines changed by "gR" so far */ +// increase around internal delete/replace +EXTERN int inhibit_delete_count INIT(= 0); /* * These flags are set based upon 'fileencoding'. diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 4051354d65..29682e8add 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -112,6 +112,65 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL return 1; } +/// convert byte index to UTF-32 and UTF-16 indicies +/// +/// Expects a string and an optional index. If no index is supplied, the length +/// of the string is returned. +/// +/// Returns two values: the UTF-32 and UTF-16 indicies. +static int nlua_str_utfindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL +{ + size_t s1_len; + const char *s1 = luaL_checklstring(lstate, 1, &s1_len); + intptr_t idx; + if (lua_gettop(lstate) >= 2) { + idx = luaL_checkinteger(lstate, 2); + if (idx < 0 || idx > (intptr_t)s1_len) { + return luaL_error(lstate, "index out of range"); + } + } else { + idx = (intptr_t)s1_len; + } + + size_t codepoints = 0, codeunits = 0; + mb_utflen((const char_u *)s1, (size_t)idx, &codepoints, &codeunits); + + lua_pushinteger(lstate, (long)codepoints); + lua_pushinteger(lstate, (long)codeunits); + + return 2; +} + +/// convert UTF-32 or UTF-16 indicies to byte index. +/// +/// Expects up to three args: string, index and use_utf16. +/// If use_utf16 is not supplied it defaults to false (use UTF-32) +/// +/// Returns the byte index. +static int nlua_str_byteindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL +{ + size_t s1_len; + const char *s1 = luaL_checklstring(lstate, 1, &s1_len); + intptr_t idx = luaL_checkinteger(lstate, 2); + if (idx < 0) { + return luaL_error(lstate, "index out of range"); + } + bool use_utf16 = false; + if (lua_gettop(lstate) >= 3) { + use_utf16 = lua_toboolean(lstate, 3); + } + + ssize_t byteidx = mb_utf_index_to_bytes((const char_u *)s1, s1_len, + (size_t)idx, use_utf16); + if (byteidx == -1) { + return luaL_error(lstate, "index out of range"); + } + + lua_pushinteger(lstate, (long)byteidx); + + return 1; +} + static void nlua_luv_error_event(void **argv) { char *error = (char *)argv[0]; @@ -220,6 +279,12 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL // stricmp lua_pushcfunction(lstate, &nlua_stricmp); lua_setfield(lstate, -2, "stricmp"); + // str_utfindex + lua_pushcfunction(lstate, &nlua_str_utfindex); + lua_setfield(lstate, -2, "str_utfindex"); + // str_byteindex + lua_pushcfunction(lstate, &nlua_str_byteindex); + lua_setfield(lstate, -2, "str_byteindex"); // schedule lua_pushcfunction(lstate, &nlua_schedule); lua_setfield(lstate, -2, "schedule"); diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 3017f3c855..fae7635d34 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -1438,6 +1438,64 @@ int utf16_to_utf8(const wchar_t *strw, char **str) #endif +/// Measure the length of a string in corresponding UTF-32 and UTF-16 units. +/// +/// Invalid UTF-8 bytes, or embedded surrogates, count as one code point/unit +/// each. +/// +/// The out parameters are incremented. This is used to measure the size of +/// a buffer region consisting of multiple line segments. +/// +/// @param s the string +/// @param len maximum length (an earlier NUL terminates) +/// @param[out] codepoints incremented with UTF-32 code point size +/// @param[out] codeunits incremented with UTF-16 code unit size +void mb_utflen(const char_u *s, size_t len, size_t *codepoints, + size_t *codeunits) + FUNC_ATTR_NONNULL_ALL +{ + size_t count = 0, extra = 0; + size_t clen; + for (size_t i = 0; i < len && s[i] != NUL; i += clen) { + clen = utf_ptr2len_len(s+i, len-i); + // NB: gets the byte value of invalid sequence bytes. + // we only care whether the char fits in the BMP or not + int c = (clen > 1) ? utf_ptr2char(s+i) : s[i]; + count++; + if (c > 0xFFFF) { + extra++; + } + } + *codepoints += count; + *codeunits += count + extra; +} + +ssize_t mb_utf_index_to_bytes(const char_u *s, size_t len, + size_t index, bool use_utf16_units) + FUNC_ATTR_NONNULL_ALL +{ + size_t count = 0; + size_t clen, i; + if (index == 0) { + return 0; + } + for (i = 0; i < len && s[i] != NUL; i += clen) { + clen = utf_ptr2len_len(s+i, len-i); + // NB: gets the byte value of invalid sequence bytes. + // we only care whether the char fits in the BMP or not + int c = (clen > 1) ? utf_ptr2char(s+i) : s[i]; + count++; + if (use_utf16_units && c > 0xFFFF) { + count++; + } + if (count >= index) { + return i+clen; + } + } + return -1; +} + + /* * Version of strnicmp() that handles multi-byte characters. * Needed for Big5, Shift-JIS and UTF-8 encoding. Other DBCS encodings can diff --git a/src/nvim/memline.c b/src/nvim/memline.c index b027459706..3220c7d9b8 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -2383,6 +2383,23 @@ static int ml_append_int( return OK; } +void ml_add_deleted_len(char_u *ptr, ssize_t len) +{ + if (inhibit_delete_count) { + return; + } + if (len == -1) { + len = STRLEN(ptr); + } + curbuf->deleted_bytes += len+1; + if (curbuf->update_need_codepoints) { + mb_utflen(ptr, len, &curbuf->deleted_codepoints, + &curbuf->deleted_codeunits); + curbuf->deleted_codepoints++; // NL char + curbuf->deleted_codeunits++; + } +} + /* * Replace line lnum, with buffering, in current buffer. * @@ -2403,13 +2420,24 @@ int ml_replace(linenr_T lnum, char_u *line, bool copy) if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL) return FAIL; + bool readlen = true; + if (copy) { line = vim_strsave(line); } - if (curbuf->b_ml.ml_line_lnum != lnum) /* other line buffered */ - ml_flush_line(curbuf); /* flush it */ - else if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) /* same line allocated */ - xfree(curbuf->b_ml.ml_line_ptr); /* free it */ + if (curbuf->b_ml.ml_line_lnum != lnum) { // other line buffered + ml_flush_line(curbuf); // flush it + } else if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) { // same line allocated + ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, -1); + readlen = false; // already added the length + + xfree(curbuf->b_ml.ml_line_ptr); // free it + } + + if (readlen && kv_size(curbuf->update_callbacks)) { + ml_add_deleted_len(ml_get_buf(curbuf, lnum, false), -1); + } + curbuf->b_ml.ml_line_ptr = line; curbuf->b_ml.ml_line_lnum = lnum; curbuf->b_ml.ml_flags = (curbuf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY; @@ -2491,6 +2519,10 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) else line_size = ((dp->db_index[idx - 1]) & DB_INDEX_MASK) - line_start; + // Line should always have an NL char internally (represented as NUL), + // even if 'noeol' is set. + assert(line_size >= 1); + ml_add_deleted_len((char_u *)dp + line_start, line_size-1); /* * special case: If there is only one line in the data block it becomes empty. @@ -2676,6 +2708,17 @@ void ml_clearmarked(void) return; } +size_t ml_flush_deleted_bytes(buf_T *buf, size_t *codepoints, size_t *codeunits) +{ + size_t ret = buf->deleted_bytes; + *codepoints = buf->deleted_codepoints; + *codeunits = buf->deleted_codeunits; + buf->deleted_bytes = 0; + buf->deleted_codepoints = 0; + buf->deleted_codeunits = 0; + return ret; +} + /* * flush ml_line if necessary */ @@ -2704,6 +2747,8 @@ static void ml_flush_line(buf_T *buf) return; entered = TRUE; + buf->flush_count++; + lnum = buf->b_ml.ml_line_lnum; new_line = buf->b_ml.ml_line_ptr; diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index db0d56b5fd..a62fa6d585 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -780,6 +780,7 @@ open_line ( did_append = FALSE; } + inhibit_delete_count++; if (newindent || did_si ) { @@ -821,6 +822,7 @@ open_line ( did_si = false; } } + inhibit_delete_count--; /* * In REPLACE mode, for each character in the extra leader, there must be @@ -1685,6 +1687,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) bool was_alloced = ml_line_alloced(); // check if oldp was allocated char_u *newp; if (was_alloced) { + ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, oldlen); newp = oldp; // use same allocated memory } else { // need to allocate a new line newp = xmalloc((size_t)(oldlen + 1 - count)); diff --git a/src/nvim/spell.c b/src/nvim/spell.c index cc214616f4..404f279e73 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1807,9 +1807,11 @@ void count_common_word(slang_T *lp, char_u *word, int len, int count) char_u buf[MAXWLEN]; char_u *p; - if (len == -1) + if (len == -1) { p = word; - else { + } else if (len >= MAXWLEN) { + return; + } else { STRLCPY(buf, word, len + 1); p = buf; } diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index d8d529d0f6..3b0f42aacd 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -1306,7 +1306,7 @@ static void refresh_screen(Terminal *term, buf_T *buf) static void adjust_topline(Terminal *term, buf_T *buf, long added) { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->w_buffer == buf) { linenr_T ml_end = buf->b_ml.ml_line_count; bool following = ml_end == wp->w_cursor.lnum + added; // cursor at end? diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index b3438cc649..d7b8ca9e9c 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -1,4 +1,5 @@ " Test spell checking +" Note: this file uses latin1 encoding, but is used with utf-8 encoding. if !has('spell') finish @@ -351,6 +352,18 @@ func Test_zeq_crash() bwipe! endfunc +" Check handling a word longer than MAXWLEN. +func Test_spell_long_word() + set enc=utf-8 + new + call setline(1, "d\xCC\xB4\xCC\xBD\xCD\x88\xCD\x94a\xCC\xB5\xCD\x84\xCD\x84\xCC\xA8\xCD\x9Cr\xCC\xB5\xCC\x8E\xCD\x85\xCD\x85k\xCC\xB6\xCC\x89\xCC\x9D \xCC\xB6\xCC\x83\xCC\x8F\xCC\xA4\xCD\x8Ef\xCC\xB7\xCC\x81\xCC\x80\xCC\xA9\xCC\xB0\xCC\xAC\xCC\xA2\xCD\x95\xCD\x87\xCD\x8D\xCC\x9E\xCD\x99\xCC\xAD\xCC\xAB\xCC\x97\xCC\xBBo\xCC\xB6\xCC\x84\xCC\x95\xCC\x8C\xCC\x8B\xCD\x9B\xCD\x9C\xCC\xAFr\xCC\xB7\xCC\x94\xCD\x83\xCD\x97\xCC\x8C\xCC\x82\xCD\x82\xCD\x80\xCD\x91\xCC\x80\xCC\xBE\xCC\x82\xCC\x8F\xCC\xA3\xCD\x85\xCC\xAE\xCD\x8D\xCD\x99\xCC\xBC\xCC\xAB\xCC\xA7\xCD\x88c\xCC\xB7\xCD\x83\xCC\x84\xCD\x92\xCC\x86\xCC\x83\xCC\x88\xCC\x92\xCC\x94\xCC\xBE\xCC\x9D\xCC\xAF\xCC\x98\xCC\x9D\xCC\xBB\xCD\x8E\xCC\xBB\xCC\xB3\xCC\xA3\xCD\x8E\xCD\x99\xCC\xA5\xCC\xAD\xCC\x99\xCC\xB9\xCC\xAE\xCC\xA5\xCC\x9E\xCD\x88\xCC\xAE\xCC\x9E\xCC\xA9\xCC\x97\xCC\xBC\xCC\x99\xCC\xA5\xCD\x87\xCC\x97\xCD\x8E\xCD\x94\xCC\x99\xCC\x9D\xCC\x96\xCD\x94\xCC\xAB\xCC\xA7\xCC\xA5\xCC\x98\xCC\xBB\xCC\xAF\xCC\xABe\xCC\xB7\xCC\x8E\xCC\x82\xCD\x86\xCD\x9B\xCC\x94\xCD\x83\xCC\x85\xCD\x8A\xCD\x8C\xCC\x8B\xCD\x92\xCD\x91\xCC\x8F\xCC\x81\xCD\x95\xCC\xA2\xCC\xB9\xCC\xB2\xCD\x9C\xCC\xB1\xCC\xA6\xCC\xB3\xCC\xAF\xCC\xAE\xCC\x9C\xCD\x99s\xCC\xB8\xCC\x8C\xCC\x8E\xCC\x87\xCD\x81\xCD\x82\xCC\x86\xCD\x8C\xCD\x8C\xCC\x8B\xCC\x84\xCC\x8C\xCD\x84\xCD\x9B\xCD\x86\xCC\x93\xCD\x90\xCC\x85\xCC\x94\xCD\x98\xCD\x84\xCD\x92\xCD\x8B\xCC\x90\xCC\x83\xCC\x8F\xCD\x84\xCD\x81\xCD\x9B\xCC\x90\xCD\x81\xCC\x8F\xCC\xBD\xCC\x88\xCC\xBF\xCC\x88\xCC\x84\xCC\x8E\xCD\x99\xCD\x94\xCC\x99\xCD\x99\xCC\xB0\xCC\xA8\xCC\xA3\xCC\xA8\xCC\x96\xCC\x99\xCC\xAE\xCC\xBC\xCC\x99\xCD\x9A\xCC\xB2\xCC\xB1\xCC\x9F\xCC\xBB\xCC\xA6\xCD\x85\xCC\xAA\xCD\x89\xCC\x9D\xCC\x99\xCD\x96\xCC\xB1\xCC\xB1\xCC\x99\xCC\xA6\xCC\xA5\xCD\x95\xCC\xB2\xCC\xA0\xCD\x99 within") + set spell spelllang=en + redraw + redraw! + bwipe! + set nospell +endfunc + func LoadAffAndDic(aff_contents, dic_contents) throw 'skipped: Nvim does not support enc=latin1' set enc=latin1 diff --git a/src/nvim/version.c b/src/nvim/version.c index f3326beb4c..74a4852def 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -2030,14 +2030,19 @@ static void version_msg(char *s) version_msg_wrap((char_u *)s, false); } -/// List all features aligned in columns, dictionary style. +/// List all features. +/// This does not use list_in_columns (as in Vim), because there are only a +/// few, and we do not start at a new line. static void list_features(void) { - list_in_columns((char_u **)features, -1, -1); - if (msg_col > 0) { - msg_putchar('\n'); + version_msg(_("\n\nFeatures: ")); + for (int i = 0; features[i] != NULL; i++) { + version_msg(features[i]); + if (features[i+1] != NULL) { + version_msg(" "); + } } - MSG_PUTS("See \":help feature-compile\"\n\n"); + version_msg("\nSee \":help feature-compile\"\n\n"); } /// List string items nicely aligned in columns. @@ -2146,8 +2151,6 @@ void list_version(void) } #endif // ifdef HAVE_PATHDEF - version_msg(_("\n\nFeatures: ")); - list_features(); #ifdef SYS_VIMRC_FILE diff --git a/test/functional/fixtures/shell-test.c b/test/functional/fixtures/shell-test.c index f1357c5dbb..550e5dd997 100644 --- a/test/functional/fixtures/shell-test.c +++ b/test/functional/fixtures/shell-test.c @@ -40,6 +40,7 @@ static void help(void) puts(" 0: foo bar"); puts(" ..."); puts(" 96: foo bar"); + puts(" shell-test REP_NODELAY N {text}"); puts(" shell-test INTERACT"); puts(" Prints \"interact $ \" to stderr, and waits for \"exit\" input."); } @@ -66,7 +67,8 @@ int main(int argc, char **argv) if (argc >= 3) { fprintf(stderr, "%s\n", argv[2]); } - } else if (strcmp(argv[1], "REP") == 0) { + } else if (strcmp(argv[1], "REP") == 0 || + strcmp(argv[1], "REP_NODELAY") == 0) { if (argc != 4) { fprintf(stderr, "REP expects exactly 3 arguments\n"); return 4; @@ -76,10 +78,17 @@ int main(int argc, char **argv) fprintf(stderr, "Invalid count: %s\n", argv[2]); return 4; } - for (int i = 0; i < count; i++) { - printf("%d: %s\n", i, argv[3]); - fflush(stdout); - usleep(1000); // Wait 1 ms (simulate typical output). + if (strcmp(argv[1], "REP_NODELAY") == 0) { + for (int i = 0; i < count; i++) { + printf("%d: %s\n", i, argv[3]); + fflush(stdout); + } + } else { + for (int i = 0; i < count; i++) { + printf("%d: %s\n", i, argv[3]); + fflush(stdout); + usleep(1000); // Wait 1 ms (simulate typical output). + } } } else if (strcmp(argv[1], "UTF-8") == 0) { // test split-up UTF-8 sequence diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 8223290760..4dde733e1e 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -1,6 +1,7 @@ require('coxpcall') local luv = require('luv') local lfs = require('lfs') +local mpack = require('mpack') local global_helpers = require('test.helpers') -- nvim client: Found in .deps/usr/share/lua/<version>/nvim/ if "bundled". @@ -20,26 +21,32 @@ local sleep = global_helpers.sleep local tbl_contains = global_helpers.tbl_contains local write_file = global_helpers.write_file +local module = { + NIL = mpack.NIL, + mkdir = lfs.mkdir, +} + local start_dir = lfs.currentdir() -- XXX: NVIM_PROG takes precedence, QuickBuild sets it. -local nvim_prog = ( +module.nvim_prog = ( os.getenv('NVIM_PROG') or os.getenv('NVIM_PRG') or global_helpers.test_build_dir .. '/bin/nvim' ) -- Default settings for the test session. -local nvim_set = 'set shortmess+=IS background=light noswapfile noautoindent' - ..' laststatus=1 undodir=. directory=. viewdir=. backupdir=.' - ..' belloff= wildoptions-=pum noshowcmd noruler nomore' -local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', 'NONE', - '--cmd', nvim_set, '--embed'} +module.nvim_set = ( + 'set shortmess+=IS background=light noswapfile noautoindent' + ..' laststatus=1 undodir=. directory=. viewdir=. backupdir=.' + ..' belloff= wildoptions-=pum noshowcmd noruler nomore') +module.nvim_argv = { + module.nvim_prog, '-u', 'NONE', '-i', 'NONE', + '--cmd', module.nvim_set, '--embed'} -- Directory containing nvim. -local nvim_dir = nvim_prog:gsub("[/\\][^/\\]+$", "") -if nvim_dir == nvim_prog then - nvim_dir = "." +module.nvim_dir = module.nvim_prog:gsub("[/\\][^/\\]+$", "") +if module.nvim_dir == module.nvim_prog then + module.nvim_dir = "." end -local mpack = require('mpack') local tmpname = global_helpers.tmpname local uname = global_helpers.uname local prepend_argv @@ -69,26 +76,27 @@ if prepend_argv then for i = 1, len do new_nvim_argv[i] = prepend_argv[i] end - for i = 1, #nvim_argv do - new_nvim_argv[i + len] = nvim_argv[i] + for i = 1, #module.nvim_argv do + new_nvim_argv[i + len] = module.nvim_argv[i] end - nvim_argv = new_nvim_argv + module.nvim_argv = new_nvim_argv + module.prepend_argv = prepend_argv end local session, loop_running, last_error, method_error -local function get_session() +function module.get_session() return session end -local function set_session(s, keep) +function module.set_session(s, keep) if session and not keep then session:close() end session = s end -local function request(method, ...) +function module.request(method, ...) local status, rv = session:request(method, ...) if not status then if loop_running then @@ -101,14 +109,14 @@ local function request(method, ...) return rv end -local function next_msg(timeout) +function module.next_msg(timeout) return session:next_message(timeout and timeout or 10000) end -local function expect_twostreams(msgs1, msgs2) +function module.expect_twostreams(msgs1, msgs2) local pos1, pos2 = 1, 1 while pos1 <= #msgs1 or pos2 <= #msgs2 do - local msg = next_msg() + local msg = module.next_msg() if pos1 <= #msgs1 and pcall(eq, msgs1[pos1], msg) then pos1 = pos1 + 1 elseif pos2 <= #msgs2 then @@ -131,7 +139,7 @@ end -- -- ignore: List of ignored event names. -- seqs: List of one or more potential event sequences. -local function expect_msg_seq(...) +function module.expect_msg_seq(...) if select('#', ...) < 1 then error('need at least 1 argument') end @@ -161,7 +169,7 @@ local function expect_msg_seq(...) local expected_seq = seqs[anum] -- Collect enough messages to compare the next expected sequence. while #actual_seq < #expected_seq do - local msg = next_msg(10000) -- Big timeout for ASAN/valgrind. + local msg = module.next_msg(10000) -- Big timeout for ASAN/valgrind. local msg_type = msg and msg[2] or nil if msg == nil then error(cat_err(final_error, @@ -192,11 +200,11 @@ local function call_and_stop_on_error(lsession, ...) return result end -local function set_method_error(err) +function module.set_method_error(err) method_error = err end -local function run_session(lsession, request_cb, notification_cb, setup_cb, timeout) +function module.run_session(lsession, request_cb, notification_cb, setup_cb, timeout) local on_request, on_notification, on_setup if request_cb then @@ -232,43 +240,43 @@ local function run_session(lsession, request_cb, notification_cb, setup_cb, time end end -local function run(request_cb, notification_cb, setup_cb, timeout) - run_session(session, request_cb, notification_cb, setup_cb, timeout) +function module.run(request_cb, notification_cb, setup_cb, timeout) + module.run_session(session, request_cb, notification_cb, setup_cb, timeout) end -local function stop() +function module.stop() session:stop() end -local function nvim_prog_abs() +function module.nvim_prog_abs() -- system(['build/bin/nvim']) does not work for whatever reason. It must -- be executable searched in $PATH or something starting with / or ./. - if nvim_prog:match('[/\\]') then - return request('nvim_call_function', 'fnamemodify', {nvim_prog, ':p'}) + if module.nvim_prog:match('[/\\]') then + return module.request('nvim_call_function', 'fnamemodify', {module.nvim_prog, ':p'}) else - return nvim_prog + return module.nvim_prog end end -- Executes an ex-command. VimL errors manifest as client (lua) errors, but -- v:errmsg will not be updated. -local function nvim_command(cmd) - request('nvim_command', cmd) +function module.command(cmd) + module.request('nvim_command', cmd) end -- Evaluates a VimL expression. -- Fails on VimL error, but does not update v:errmsg. -local function nvim_eval(expr) - return request('nvim_eval', expr) +function module.eval(expr) + return module.request('nvim_eval', expr) end -local os_name = (function() +module.os_name = (function() local name = nil return (function() if not name then - if nvim_eval('has("win32")') == 1 then + if module.eval('has("win32")') == 1 then name = 'windows' - elseif nvim_eval('has("macunix")') == 1 then + elseif module.eval('has("macunix")') == 1 then name = 'osx' else name = 'unix' @@ -278,38 +286,38 @@ local os_name = (function() end) end)() -local function iswin() +function module.iswin() return package.config:sub(1,1) == '\\' end -- Executes a VimL function. -- Fails on VimL error, but does not update v:errmsg. -local function nvim_call(name, ...) - return request('nvim_call_function', name, {...}) +function module.call(name, ...) + return module.request('nvim_call_function', name, {...}) end -- Sends user input to Nvim. -- Does not fail on VimL error, but v:errmsg will be updated. local function nvim_feed(input) while #input > 0 do - local written = request('nvim_input', input) + local written = module.request('nvim_input', input) input = input:sub(written + 1) end end -local function feed(...) +function module.feed(...) for _, v in ipairs({...}) do nvim_feed(dedent(v)) end end -local function rawfeed(...) +function module.rawfeed(...) for _, v in ipairs({...}) do nvim_feed(dedent(v)) end end -local function merge_args(...) +function module.merge_args(...) local i = 1 local argv = {} for anum = 1,select('#', ...) do @@ -361,15 +369,15 @@ local function remove_args(args, args_rm) return new_args end -local function spawn(argv, merge, env) +function module.spawn(argv, merge, env) local child_stream = ChildProcessStream.spawn( - merge and merge_args(prepend_argv, argv) or argv, + merge and module.merge_args(prepend_argv, argv) or argv, env) return Session.new(child_stream) end -- Creates a new Session connected by domain socket (named pipe) or TCP. -local function connect(file_or_address) +function module.connect(file_or_address) local addr, port = string.match(file_or_address, "(.*):(%d+)") local stream = (addr and port) and TcpStream.open(addr, port) or SocketStream.open(file_or_address) @@ -378,7 +386,7 @@ end -- Calls fn() until it succeeds, up to `max` times or until `max_ms` -- milliseconds have passed. -local function retry(max, max_ms, fn) +function module.retry(max, max_ms, fn) assert(max == nil or max > 0) assert(max_ms == nil or max_ms > 0) local tries = 1 @@ -410,8 +418,16 @@ end -- Example: -- clear('-e') -- clear{args={'-e'}, args_rm={'-i'}, env={TERM=term}} -local function clear(...) - local args = {unpack(nvim_argv)} +function module.clear(...) + local argv, env = module.new_argv(...) + module.set_session(module.spawn(argv, nil, env)) +end + +-- Builds an argument list for use in clear(). +-- +--@see clear() for parameters. +function module.new_argv(...) + local args = {unpack(module.nvim_argv)} table.insert(args, '--headless') local new_args local env = nil @@ -450,21 +466,21 @@ local function clear(...) for _, arg in ipairs(new_args) do table.insert(args, arg) end - set_session(spawn(args, nil, env)) + return args, env end -local function insert(...) +function module.insert(...) nvim_feed('i') for _, v in ipairs({...}) do local escaped = v:gsub('<', '<lt>') - rawfeed(escaped) + module.rawfeed(escaped) end nvim_feed('<ESC>') end -- Executes an ex-command by user input. Because nvim_input() is used, VimL -- errors will not manifest as client (lua) errors. Use command() for that. -local function feed_command(...) +function module.feed_command(...) for _, v in ipairs({...}) do if v:sub(1, 1) ~= '/' then -- not a search command, prefix with colon @@ -476,10 +492,10 @@ local function feed_command(...) end local sourced_fnames = {} -local function source(code) +function module.source(code) local fname = tmpname() write_file(fname, code) - nvim_command('source '..fname) + module.command('source '..fname) -- DO NOT REMOVE FILE HERE. -- do_source() has a habit of checking whether files are “same” by using inode -- and device IDs. If you run two source() calls in quick succession there is @@ -495,76 +511,76 @@ local function source(code) return fname end -local function set_shell_powershell() - source([[ +function module.set_shell_powershell() + module.source([[ set shell=powershell shellquote=( shellpipe=\| shellredir=> shellxquote= let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command Remove-Item -Force alias:sleep; Remove-Item -Force alias:cat;' ]]) end -local function nvim(method, ...) - return request('nvim_'..method, ...) +function module.nvim(method, ...) + return module.request('nvim_'..method, ...) end local function ui(method, ...) - return request('nvim_ui_'..method, ...) + return module.request('nvim_ui_'..method, ...) end -local function nvim_async(method, ...) +function module.nvim_async(method, ...) session:notify('nvim_'..method, ...) end -local function buffer(method, ...) - return request('nvim_buf_'..method, ...) +function module.buffer(method, ...) + return module.request('nvim_buf_'..method, ...) end -local function window(method, ...) - return request('nvim_win_'..method, ...) +function module.window(method, ...) + return module.request('nvim_win_'..method, ...) end -local function tabpage(method, ...) - return request('nvim_tabpage_'..method, ...) +function module.tabpage(method, ...) + return module.request('nvim_tabpage_'..method, ...) end -local function curbuf(method, ...) +function module.curbuf(method, ...) if not method then - return nvim('get_current_buf') + return module.nvim('get_current_buf') end - return buffer(method, 0, ...) + return module.buffer(method, 0, ...) end -local function wait() +function module.wait() -- Execute 'nvim_eval' (a deferred function) to block -- until all pending input is processed. session:request('nvim_eval', '1') end -local function curbuf_contents() - wait() -- Before inspecting the buffer, process all input. - return table.concat(curbuf('get_lines', 0, -1, true), '\n') +function module.curbuf_contents() + module.wait() -- Before inspecting the buffer, process all input. + return table.concat(module.curbuf('get_lines', 0, -1, true), '\n') end -local function curwin(method, ...) +function module.curwin(method, ...) if not method then - return nvim('get_current_win') + return module.nvim('get_current_win') end - return window(method, 0, ...) + return module.window(method, 0, ...) end -local function curtab(method, ...) +function module.curtab(method, ...) if not method then - return nvim('get_current_tabpage') + return module.nvim('get_current_tabpage') end - return tabpage(method, 0, ...) + return module.tabpage(method, 0, ...) end -local function expect(contents) - return eq(dedent(contents), curbuf_contents()) +function module.expect(contents) + return eq(dedent(contents), module.curbuf_contents()) end -local function expect_any(contents) +function module.expect_any(contents) contents = dedent(contents) - return ok(nil ~= string.find(curbuf_contents(), contents, 1, true)) + return ok(nil ~= string.find(module.curbuf_contents(), contents, 1, true)) end local function do_rmdir(path) @@ -584,8 +600,8 @@ local function do_rmdir(path) else -- Try Nvim delete(): it handles `readonly` attribute on Windows, -- and avoids Lua cross-version/platform incompatibilities. - if -1 == nvim_call('delete', abspath) then - local hint = (os_name() == 'windows' + if -1 == module.call('delete', abspath) then + local hint = (module.os_name() == 'windows' and ' (hint: try :%bwipeout! before rmdir())' or '') error('delete() failed'..hint..': '..abspath) end @@ -600,12 +616,12 @@ local function do_rmdir(path) end end -local function rmdir(path) +function module.rmdir(path) local ret, _ = pcall(do_rmdir, path) - if not ret and os_name() == "windows" then + if not ret and module.os_name() == "windows" then -- Maybe "Permission denied"; try again after changing the nvim -- process to the top-level directory. - nvim_command([[exe 'cd '.fnameescape(']]..start_dir.."')") + module.command([[exe 'cd '.fnameescape(']]..start_dir.."')") ret, _ = pcall(do_rmdir, path) end -- During teardown, the nvim process may not exit quickly enough, then rmdir() @@ -616,20 +632,20 @@ local function rmdir(path) end end -local exc_exec = function(cmd) - nvim_command(([[ +function module.exc_exec(cmd) + module.command(([[ try execute "%s" catch let g:__exception = v:exception endtry ]]):format(cmd:gsub('\n', '\\n'):gsub('[\\"]', '\\%0'))) - local ret = nvim_eval('get(g:, "__exception", 0)') - nvim_command('unlet! g:__exception') + local ret = module.eval('get(g:, "__exception", 0)') + module.command('unlet! g:__exception') return ret end -local function create_callindex(func) +function module.create_callindex(func) local table = {} setmetatable(table, { __index = function(tbl, arg1) @@ -643,7 +659,7 @@ end -- Helper to skip tests. Returns true in Windows systems. -- pending_fn is pending() from busted -local function pending_win32(pending_fn) +function module.pending_win32(pending_fn) if uname() == 'Windows' then if pending_fn ~= nil then pending_fn('FIXME: Windows', function() end) @@ -656,7 +672,7 @@ end -- Calls pending() and returns `true` if the system is too slow to -- run fragile or expensive tests. Else returns `false`. -local function skip_fragile(pending_fn, cond) +function module.skip_fragile(pending_fn, cond) if pending_fn == nil or type(pending_fn) ~= type(function()end) then error("invalid pending_fn") end @@ -670,7 +686,7 @@ local function skip_fragile(pending_fn, cond) return false end -local function meth_pcall(...) +function module.meth_pcall(...) local ret = {pcall(...)} if type(ret[2]) == 'string' then ret[2] = ret[2]:gsub('^[^:]+:%d+: ', '') @@ -678,66 +694,66 @@ local function meth_pcall(...) return ret end -local funcs = create_callindex(nvim_call) -local meths = create_callindex(nvim) -local uimeths = create_callindex(ui) -local bufmeths = create_callindex(buffer) -local winmeths = create_callindex(window) -local tabmeths = create_callindex(tabpage) -local curbufmeths = create_callindex(curbuf) -local curwinmeths = create_callindex(curwin) -local curtabmeths = create_callindex(curtab) +module.funcs = module.create_callindex(module.call) +module.meths = module.create_callindex(module.nvim) +module.uimeths = module.create_callindex(ui) +module.bufmeths = module.create_callindex(module.buffer) +module.winmeths = module.create_callindex(module.window) +module.tabmeths = module.create_callindex(module.tabpage) +module.curbufmeths = module.create_callindex(module.curbuf) +module.curwinmeths = module.create_callindex(module.curwin) +module.curtabmeths = module.create_callindex(module.curtab) -local function exec_lua(code, ...) - return meths.execute_lua(code, {...}) +function module.exec_lua(code, ...) + return module.meths.execute_lua(code, {...}) end -local function redir_exec(cmd) - meths.set_var('__redir_exec_cmd', cmd) - nvim_command([[ +function module.redir_exec(cmd) + module.meths.set_var('__redir_exec_cmd', cmd) + module.command([[ redir => g:__redir_exec_output silent! execute g:__redir_exec_cmd redir END ]]) - local ret = meths.get_var('__redir_exec_output') - meths.del_var('__redir_exec_output') - meths.del_var('__redir_exec_cmd') + local ret = module.meths.get_var('__redir_exec_output') + module.meths.del_var('__redir_exec_output') + module.meths.del_var('__redir_exec_cmd') return ret end -local function get_pathsep() - return iswin() and '\\' or '/' +function module.get_pathsep() + return module.iswin() and '\\' or '/' end -local function pathroot() +function module.pathroot() local pathsep = package.config:sub(1,1) - return iswin() and (nvim_dir:sub(1,2)..pathsep) or '/' + return module.iswin() and (module.nvim_dir:sub(1,2)..pathsep) or '/' end -- Returns a valid, platform-independent $NVIM_LISTEN_ADDRESS. -- Useful for communicating with child instances. -local function new_pipename() +function module.new_pipename() -- HACK: Start a server temporarily, get the name, then stop it. - local pipename = nvim_eval('serverstart()') - funcs.serverstop(pipename) + local pipename = module.eval('serverstart()') + module.funcs.serverstop(pipename) return pipename end -local function missing_provider(provider) +function module.missing_provider(provider) if provider == 'ruby' or provider == 'node' then - local prog = funcs['provider#' .. provider .. '#Detect']() + local prog = module.funcs['provider#' .. provider .. '#Detect']() return prog == '' and (provider .. ' not detected') or false elseif provider == 'python' or provider == 'python3' then local py_major_version = (provider == 'python3' and 3 or 2) - local errors = funcs['provider#pythonx#Detect'](py_major_version)[2] + local errors = module.funcs['provider#pythonx#Detect'](py_major_version)[2] return errors ~= '' and errors or false else assert(false, 'Unknown provider: ' .. provider) end end -local function alter_slashes(obj) - if not iswin() then +function module.alter_slashes(obj) + if not module.iswin() then return obj end if type(obj) == 'string' then @@ -746,7 +762,7 @@ local function alter_slashes(obj) elseif type(obj) == 'table' then local ret = {} for k, v in pairs(obj) do - ret[k] = alter_slashes(v) + ret[k] = module.alter_slashes(v) end return ret else @@ -756,21 +772,21 @@ end local load_factor = nil -local function load_adjust(num) +function module.load_adjust(num) if load_factor == nil then -- Compute load factor only once. - clear() - request('nvim_command', 'source src/nvim/testdir/load.vim') - load_factor = request('nvim_eval', 'g:test_load_factor') + module.clear() + module.request('nvim_command', 'source src/nvim/testdir/load.vim') + load_factor = module.request('nvim_eval', 'g:test_load_factor') end return math.ceil(num * load_factor) end -local function parse_context(ctx) +function module.parse_context(ctx) local parsed = {} for _, item in ipairs({'regs', 'jumps', 'buflist', 'gvars'}) do parsed[item] = filter(function(v) return type(v) == 'table' - end, nvim_call('msgpackparse', ctx[item])) + end, module.call('msgpackparse', ctx[item])) end parsed['buflist'] = parsed['buflist'][1] return map(function(v) @@ -781,78 +797,6 @@ local function parse_context(ctx) end, parsed) end -local module = { - NIL = mpack.NIL, - alter_slashes = alter_slashes, - buffer = buffer, - bufmeths = bufmeths, - call = nvim_call, - create_callindex = create_callindex, - clear = clear, - command = nvim_command, - connect = connect, - curbuf = curbuf, - curbuf_contents = curbuf_contents, - curbufmeths = curbufmeths, - curtab = curtab, - curtabmeths = curtabmeths, - curwin = curwin, - curwinmeths = curwinmeths, - eval = nvim_eval, - exc_exec = exc_exec, - exec_lua = exec_lua, - expect = expect, - expect_any = expect_any, - expect_msg_seq = expect_msg_seq, - expect_twostreams = expect_twostreams, - feed = feed, - feed_command = feed_command, - funcs = funcs, - get_pathsep = get_pathsep, - get_session = get_session, - insert = insert, - iswin = iswin, - merge_args = merge_args, - meth_pcall = meth_pcall, - meths = meths, - missing_provider = missing_provider, - mkdir = lfs.mkdir, - load_adjust = load_adjust, - new_pipename = new_pipename, - next_msg = next_msg, - nvim = nvim, - nvim_argv = nvim_argv, - nvim_async = nvim_async, - nvim_dir = nvim_dir, - nvim_prog = nvim_prog, - nvim_prog_abs = nvim_prog_abs, - nvim_set = nvim_set, - os_name = os_name, - parse_context = parse_context, - pathroot = pathroot, - pending_win32 = pending_win32, - prepend_argv = prepend_argv, - rawfeed = rawfeed, - redir_exec = redir_exec, - request = request, - retry = retry, - rmdir = rmdir, - run = run, - run_session = run_session, - set_session = set_session, - set_method_error = set_method_error, - set_shell_powershell = set_shell_powershell, - skip_fragile = skip_fragile, - source = source, - spawn = spawn, - stop = stop, - tabmeths = tabmeths, - tabpage = tabpage, - uimeths = uimeths, - wait = wait, - window = window, - winmeths = winmeths, -} module = global_helpers.tbl_extend('error', module, global_helpers) return function(after_each) diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index c419d89be3..990cb97fec 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -5,28 +5,31 @@ local command = helpers.command local meths = helpers.meths local clear = helpers.clear local eq = helpers.eq +local exec_lua = helpers.exec_lua +local feed = helpers.feed local origlines = {"original line 1", "original line 2", "original line 3", "original line 4", "original line 5", - "original line 6"} + "original line 6", + " indented line"} describe('lua: buffer event callbacks', function() before_each(function() clear() - meths.execute_lua([[ + exec_lua([[ local events = {} - function test_register(bufnr, id, changedtick) + function test_register(bufnr, id, changedtick, utf_sizes) local function callback(...) table.insert(events, {id, ...}) if test_unreg == id then return true end end - local opts = {on_lines=callback, on_detach=callback} + local opts = {on_lines=callback, on_detach=callback, utf_sizes=utf_sizes} if changedtick then opts.on_changedtick = callback end @@ -38,55 +41,166 @@ describe('lua: buffer event callbacks', function() events = {} return ret_events end - ]], {}) + ]]) end) - it('works', function() + + -- verifying the sizes with nvim_buf_get_offset is nice (checks we cannot + -- assert the wrong thing), but masks errors with unflushed lines (as + -- nvim_buf_get_offset forces a flush of the memline). To be safe run the + -- test both ways. + local function check(verify,utf_sizes) + local lastsize meths.buf_set_lines(0, 0, -1, true, origlines) - meths.execute_lua("return test_register(...)", {0, "test1"}) + if verify then + lastsize = meths.buf_get_offset(0, meths.buf_line_count(0)) + end + exec_lua("return test_register(...)", 0, "test1",false,utf_sizes) local tick = meths.buf_get_changedtick(0) + local verify_name = "test1" + local function check_events(expected) + local events = exec_lua("return get_events(...)" ) + if utf_sizes then + -- this test case uses ASCII only, so sizes sshould be the same. + -- Unicode is tested below. + for _, event in ipairs(expected) do + event[9] = event[8] + event[10] = event[8] + end + end + eq(expected, events) + if verify then + for _, event in ipairs(events) do + if event[1] == verify_name and event[2] == "lines" then + local startline, endline = event[5], event[7] + local newrange = meths.buf_get_offset(0, endline) - meths.buf_get_offset(0, startline) + local newsize = meths.buf_get_offset(0, meths.buf_line_count(0)) + local oldrange = newrange + lastsize - newsize + eq(oldrange, event[8]) + lastsize = newsize + end + end + end + end + + command('set autoindent') command('normal! GyyggP') tick = tick + 1 - eq({{ "test1", "lines", 1, tick, 0, 0, 1 }}, - meths.execute_lua("return get_events(...)", {})) + check_events({{ "test1", "lines", 1, tick, 0, 0, 1, 0}}) meths.buf_set_lines(0, 3, 5, true, {"changed line"}) tick = tick + 1 - eq({{ "test1", "lines", 1, tick, 3, 5, 4 }}, - meths.execute_lua("return get_events(...)", {})) + check_events({{ "test1", "lines", 1, tick, 3, 5, 4, 32 }}) - meths.execute_lua("return test_register(...)", {0, "test2", true}) + exec_lua("return test_register(...)", 0, "test2", true, utf_sizes) tick = tick + 1 command('undo') -- plugins can opt in to receive changedtick events, or choose -- to only recieve actual changes. - eq({{ "test1", "lines", 1, tick, 3, 4, 5 }, - { "test2", "lines", 1, tick, 3, 4, 5 }, - { "test2", "changedtick", 1, tick+1 } }, - meths.execute_lua("return get_events(...)", {})) + check_events({{ "test1", "lines", 1, tick, 3, 4, 5, 13 }, + { "test2", "lines", 1, tick, 3, 4, 5, 13 }, + { "test2", "changedtick", 1, tick+1 } }) tick = tick + 1 -- simulate next callback returning true - meths.execute_lua("test_unreg = 'test1'", {}) + exec_lua("test_unreg = 'test1'") meths.buf_set_lines(0, 6, 7, true, {"x1","x2","x3"}) tick = tick + 1 -- plugins can opt in to receive changedtick events, or choose -- to only recieve actual changes. - eq({{ "test1", "lines", 1, tick, 6, 7, 9 }, - { "test2", "lines", 1, tick, 6, 7, 9 }}, - meths.execute_lua("return get_events(...)", {})) + check_events({{ "test1", "lines", 1, tick, 6, 7, 9, 16 }, + { "test2", "lines", 1, tick, 6, 7, 9, 16 }}) + + verify_name = "test2" meths.buf_set_lines(0, 1, 1, true, {"added"}) tick = tick + 1 - eq({{ "test2", "lines", 1, tick, 1, 1, 2 }}, - meths.execute_lua("return get_events(...)", {})) + check_events({{ "test2", "lines", 1, tick, 1, 1, 2, 0 }}) + + feed('wix') + tick = tick + 1 + check_events({{ "test2", "lines", 1, tick, 4, 5, 5, 16 }}) + + -- check hot path for multiple insert + feed('yz') + tick = tick + 1 + check_events({{ "test2", "lines", 1, tick, 4, 5, 5, 17 }}) + + feed('<bs>') + tick = tick + 1 + check_events({{ "test2", "lines", 1, tick, 4, 5, 5, 19 }}) + + feed('<esc>Go') + tick = tick + 1 + check_events({{ "test2", "lines", 1, tick, 11, 11, 12, 0 }}) + + feed('x') + tick = tick + 1 + check_events({{ "test2", "lines", 1, tick, 11, 12, 12, 5 }}) command('bwipe!') - eq({{ "test2", "detach", 1 }}, - meths.execute_lua("return get_events(...)", {})) + check_events({{ "test2", "detach", 1 }}) + end + + it('works', function() + check(false) end) + + it('works with verify', function() + check(true) + end) + + it('works with utf_sizes and ASCII text', function() + check(false,true) + end) + + it('works with utf_sizes and unicode text', function() + local unicode_text = {"ascii text", + "latin text åäö", + "BMP text ɧ αλφά", + "BMP text 汉语 ↥↧", + "SMP 🤦 🦄🦃", + "combining å بِيَّة"} + meths.buf_set_lines(0, 0, -1, true, unicode_text) + feed('gg') + exec_lua("return test_register(...)", 0, "test1", false, true) + local tick = meths.buf_get_changedtick(0) + + feed('dd') + tick = tick + 1 + eq({{ "test1", "lines", 1, tick, 0, 1, 0, 11, 11, 11 }}, exec_lua("return get_events(...)" )) + + feed('A<bs>') + tick = tick + 1 + eq({{ "test1", "lines", 1, tick, 0, 1, 1, 18, 15, 15 }}, exec_lua("return get_events(...)" )) + + feed('<esc>jylp') + tick = tick + 1 + eq({{ "test1", "lines", 1, tick, 1, 2, 2, 21, 16, 16 }}, exec_lua("return get_events(...)" )) + + feed('+eea<cr>') + tick = tick + 1 + eq({{ "test1", "lines", 1, tick, 2, 3, 4, 23, 15, 15 }}, exec_lua("return get_events(...)" )) + + feed('<esc>jdw') + tick = tick + 1 + -- non-BMP chars count as 2 UTF-2 codeunits + eq({{ "test1", "lines", 1, tick, 4, 5, 5, 18, 9, 12 }}, exec_lua("return get_events(...)" )) + + feed('+rx') + tick = tick + 1 + -- count the individual codepoints of a composed character. + eq({{ "test1", "lines", 1, tick, 5, 6, 6, 27, 20, 20 }}, exec_lua("return get_events(...)" )) + + feed('kJ') + tick = tick + 1 + -- NB: this is inefficient (but not really wrong). + eq({{ "test1", "lines", 1, tick, 4, 5, 5, 14, 5, 8 }, + { "test1", "lines", 1, tick+1, 5, 6, 5, 27, 20, 20 }}, exec_lua("return get_events(...)" )) + end) + end) diff --git a/test/functional/lua/utility_functions_spec.lua b/test/functional/lua/utility_functions_spec.lua index 780d3a1565..0d93914119 100644 --- a/test/functional/lua/utility_functions_spec.lua +++ b/test/functional/lua/utility_functions_spec.lua @@ -2,12 +2,12 @@ local helpers = require('test.functional.helpers')(after_each) local funcs = helpers.funcs -local meths = helpers.meths local clear = helpers.clear local eq = helpers.eq local eval = helpers.eval local feed = helpers.feed local meth_pcall = helpers.meth_pcall +local exec_lua = helpers.exec_lua before_each(clear) @@ -110,28 +110,53 @@ describe('lua function', function() eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0B\\0")')) end) + it("vim.str_utfindex/str_byteindex", function() + exec_lua([[_G.test_text = "xy åäö ɧ 汉语 ↥ 🤦x🦄 å بِيَّ"]]) + local indicies32 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,29,33,34,35,37,38,40,42,44,46,48} + local indicies16 = {[0]=0,1,2,3,5,7,9,10,12,13,16,19,20,23,24,28,28,29,33,33,34,35,37,38,40,42,44,46,48} + for i,k in pairs(indicies32) do + eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ...)", i), i) + end + for i,k in pairs(indicies16) do + eq(k, exec_lua("return vim.str_byteindex(_G.test_text, ..., true)", i), i) + end + local i32, i16 = 0, 0 + for k = 0,48 do + if indicies32[i32] < k then + i32 = i32 + 1 + end + if indicies16[i16] < k then + i16 = i16 + 1 + if indicies16[i16+1] == indicies16[i16] then + i16 = i16 + 1 + end + end + eq({i32, i16}, exec_lua("return {vim.str_utfindex(_G.test_text, ...)}", k), k) + end + end) + it("vim.schedule", function() - meths.execute_lua([[ + exec_lua([[ test_table = {} vim.schedule(function() table.insert(test_table, "xx") end) table.insert(test_table, "yy") - ]], {}) - eq({"yy","xx"}, meths.execute_lua("return test_table", {})) + ]]) + eq({"yy","xx"}, exec_lua("return test_table")) -- type checked args eq({false, 'Error executing lua: vim.schedule: expected function'}, - meth_pcall(meths.execute_lua, "vim.schedule('stringly')", {})) + meth_pcall(exec_lua, "vim.schedule('stringly')")) eq({false, 'Error executing lua: vim.schedule: expected function'}, - meth_pcall(meths.execute_lua, "vim.schedule()", {})) + meth_pcall(exec_lua, "vim.schedule()")) - meths.execute_lua([[ + exec_lua([[ vim.schedule(function() error("big failure\nvery async") end) - ]], {}) + ]]) feed("<cr>") eq('Error executing vim.schedule lua callback: [string "<nvim>"]:2: big failure\nvery async', eval("v:errmsg")) @@ -139,7 +164,7 @@ describe('lua function', function() it("vim.split", function() local split = function(str, sep) - return meths.execute_lua('return vim.split(...)', {str, sep}) + return exec_lua('return vim.split(...)', str, sep) end local tests = { @@ -172,7 +197,7 @@ describe('lua function', function() it('vim.trim', function() local trim = function(s) - return meths.execute_lua('return vim.trim(...)', { s }) + return exec_lua('return vim.trim(...)', s) end local trims = { @@ -194,7 +219,7 @@ describe('lua function', function() it('vim.inspect', function() -- just make sure it basically works, it has its own test suite local inspect = function(t, opts) - return meths.execute_lua('return vim.inspect(...)', { t, opts }) + return exec_lua('return vim.inspect(...)', t, opts) end eq('2', inspect(2)) @@ -202,18 +227,18 @@ describe('lua function', function() inspect({ a = { b = 1 } }, { newline = '+', indent = '' })) -- special value vim.inspect.KEY works - eq('{ KEY_a = "x", KEY_b = "y"}', meths.execute_lua([[ + eq('{ KEY_a = "x", KEY_b = "y"}', exec_lua([[ return vim.inspect({a="x", b="y"}, {newline = '', process = function(item, path) if path[#path] == vim.inspect.KEY then return 'KEY_'..item end return item end}) - ]], {})) + ]])) end) it("vim.deepcopy", function() - local is_dc = meths.execute_lua([[ + local is_dc = exec_lua([[ local a = { x = { 1, 2 }, y = 5} local b = vim.deepcopy(a) @@ -222,7 +247,7 @@ describe('lua function', function() return b.x[1] == 1 and b.x[2] == 2 and b.y == 5 and count == 2 and tostring(a) ~= tostring(b) - ]], {}) + ]]) assert(is_dc) end) diff --git a/test/functional/plugin/shada_spec.lua b/test/functional/plugin/shada_spec.lua index 778dc4e219..c0104a58f7 100644 --- a/test/functional/plugin/shada_spec.lua +++ b/test/functional/plugin/shada_spec.lua @@ -1,4 +1,3 @@ -local paths = require('test.config.paths') local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local eq, nvim_eval, nvim_command, nvim, exc_exec, funcs, nvim_feed, curbuf = @@ -13,14 +12,8 @@ local shada_helpers = require('test.functional.shada.helpers') local get_shada_rw = shada_helpers.get_shada_rw local function reset(shada_file) - -- TODO(justinmk): why is this needed? - local rtp_value = ('\'%s/runtime\''):format( - paths.test_source_path:gsub('\'', '\'\'')) - clear{args_rm={'-u', '-i'}, - args={'-u', 'NORC', + clear{ args={'-u', 'NORC', '-i', shada_file or 'NONE', - '--cmd', 'set laststatus&', - '--cmd', 'let &runtimepath='..rtp_value, }} end @@ -2554,6 +2547,7 @@ describe('syntax/shada.vim', function() it('works', function() nvim_command('syntax on') nvim_command('setlocal syntax=shada') + nvim_command('set laststatus&') curbuf('set_lines', 0, 1, true, { 'Header with timestamp ' .. epoch .. ':', ' % Key Value', @@ -2890,4 +2884,3 @@ describe('syntax/shada.vim', function() eq(exp, act) end) end) - diff --git a/test/functional/shada/buffers_spec.lua b/test/functional/shada/buffers_spec.lua index a4746c2205..04c9c01d7c 100644 --- a/test/functional/shada/buffers_spec.lua +++ b/test/functional/shada/buffers_spec.lua @@ -1,26 +1,22 @@ --- ShaDa buffer list saving/reading support +-- shada buffer list saving/reading support local helpers = require('test.functional.helpers')(after_each) local nvim_command, funcs, eq, curbufmeths = helpers.command, helpers.funcs, helpers.eq, helpers.curbufmeths local shada_helpers = require('test.functional.shada.helpers') -local reset, set_additional_cmd, clear = - shada_helpers.reset, shada_helpers.set_additional_cmd, - shada_helpers.clear +local reset, clear = shada_helpers.reset, shada_helpers.clear -describe('ShaDa support code', function() +describe('shada support code', function() local testfilename = 'Xtestfile-functional-shada-buffers' local testfilename_2 = 'Xtestfile-functional-shada-buffers-2' - before_each(reset) after_each(clear) it('is able to dump and restore buffer list', function() - set_additional_cmd('set shada+=%') - reset() + reset('set shada+=%') nvim_command('edit ' .. testfilename) nvim_command('edit ' .. testfilename_2) nvim_command('qall') - reset() + reset('set shada+=%') eq(3, funcs.bufnr('$')) eq('', funcs.bufname(1)) eq(testfilename, funcs.bufname(2)) @@ -28,11 +24,9 @@ describe('ShaDa support code', function() end) it('does not restore buffer list without % in &shada', function() - set_additional_cmd('set shada+=%') - reset() + reset('set shada+=%') nvim_command('edit ' .. testfilename) nvim_command('edit ' .. testfilename_2) - set_additional_cmd('') nvim_command('qall') reset() eq(1, funcs.bufnr('$')) @@ -40,61 +34,57 @@ describe('ShaDa support code', function() end) it('does not dump buffer list without % in &shada', function() + reset() nvim_command('edit ' .. testfilename) nvim_command('edit ' .. testfilename_2) - set_additional_cmd('set shada+=%') nvim_command('qall') - reset() + reset('set shada+=%') eq(1, funcs.bufnr('$')) eq('', funcs.bufname(1)) end) it('does not dump unlisted buffer', function() - set_additional_cmd('set shada+=%') - reset() + reset('set shada+=%') nvim_command('edit ' .. testfilename) nvim_command('edit ' .. testfilename_2) curbufmeths.set_option('buflisted', false) nvim_command('qall') - reset() + reset('set shada+=%') eq(2, funcs.bufnr('$')) eq('', funcs.bufname(1)) eq(testfilename, funcs.bufname(2)) end) it('does not dump quickfix buffer', function() - set_additional_cmd('set shada+=%') - reset() + reset('set shada+=%') nvim_command('edit ' .. testfilename) nvim_command('edit ' .. testfilename_2) curbufmeths.set_option('buftype', 'quickfix') nvim_command('qall') - reset() + reset('set shada+=%') eq(2, funcs.bufnr('$')) eq('', funcs.bufname(1)) eq(testfilename, funcs.bufname(2)) end) it('does not dump unnamed buffers', function() - set_additional_cmd('set shada+=% hidden') - reset() + reset('set shada+=% hidden') curbufmeths.set_lines(0, 1, true, {'foo'}) nvim_command('enew') curbufmeths.set_lines(0, 1, true, {'bar'}) eq(2, funcs.bufnr('$')) nvim_command('qall!') - reset() + reset('set shada+=% hidden') eq(1, funcs.bufnr('$')) eq('', funcs.bufname(1)) end) it('restores 1 buffer with %1 in &shada, #5759', function() - set_additional_cmd('set shada+=%1') - reset() + reset('set shada+=%1') nvim_command('edit ' .. testfilename) nvim_command('edit ' .. testfilename_2) nvim_command('qall') - reset() + reset('set shada+=%1') eq(2, funcs.bufnr('$')) eq('', funcs.bufname(1)) eq(testfilename, funcs.bufname(2)) diff --git a/test/functional/shada/helpers.lua b/test/functional/shada/helpers.lua index d5e061bb50..fb3ec4a87c 100644 --- a/test/functional/shada/helpers.lua +++ b/test/functional/shada/helpers.lua @@ -1,47 +1,39 @@ local helpers = require('test.functional.helpers')(nil) -local spawn, set_session, meths, nvim_prog = - helpers.spawn, helpers.set_session, helpers.meths, helpers.nvim_prog -local write_file, merge_args = helpers.write_file, helpers.merge_args +local meths = helpers.meths +local write_file = helpers.write_file +local concat_tables = helpers.concat_tables local mpack = require('mpack') local tmpname = helpers.tmpname() -local append_argv = nil -local function nvim_argv(shada_file, embed) - if embed == nil then - embed = true +-- o={ +-- args=…, +-- args_rm=…, +-- shadafile=…, +-- } +local function reset(o) + assert(o == nil or type(o) == 'table' or type(o) == 'string') + o = o and o or {} + local args_rm = o.args_rm or {} + table.insert(args_rm, '-i') + local args={ + '-i', o.shadafile or tmpname, + } + if type(o) == 'string' then + args = concat_tables(args, {'--cmd', o}) + elseif o.args then + args = concat_tables(args, o.args) end - local argv = {nvim_prog, '-u', 'NONE', '-i', shada_file or tmpname, '-N', - '--cmd', 'set shortmess+=I background=light noswapfile', - '--headless', embed and '--embed' or nil} - if helpers.prepend_argv or append_argv then - return merge_args(helpers.prepend_argv, argv, append_argv) - else - return argv - end -end - -local reset = function(shada_file) - set_session(spawn(nvim_argv(shada_file))) + helpers.clear{ + args_rm=args_rm, + args=args, + } meths.set_var('tmpname', tmpname) end -local set_additional_cmd = function(s) - append_argv = {'--cmd', s} -end - -local function add_argv(...) - if select('#', ...) == 0 then - append_argv = nil - else - append_argv = {...} - end -end - local clear = function() os.remove(tmpname) - append_argv = nil end local get_shada_rw = function(fname) @@ -89,10 +81,7 @@ end return { reset=reset, - set_additional_cmd=set_additional_cmd, - add_argv=add_argv, clear=clear, get_shada_rw=get_shada_rw, read_shada_file=read_shada_file, - nvim_argv=nvim_argv, } diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index e6450e68b3..e319fd9e6b 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -6,11 +6,7 @@ local meths, curwinmeths, curbufmeths, nvim_command, funcs, eq = local exc_exec, redir_exec = helpers.exc_exec, helpers.redir_exec local shada_helpers = require('test.functional.shada.helpers') -local reset, set_additional_cmd, clear = - shada_helpers.reset, shada_helpers.set_additional_cmd, - shada_helpers.clear -local add_argv = shada_helpers.add_argv -local nvim_argv = shada_helpers.nvim_argv +local reset, clear = shada_helpers.reset, shada_helpers.clear local nvim_current_line = function() return curwinmeths.get_cursor()[1] @@ -71,8 +67,7 @@ describe('ShaDa support code', function() nvim_command('2') nvim_command('kB') nvim_command('wshada') - set_additional_cmd('set shada=\'0,f0') - reset() + reset('set shada=\'0,f0') nvim_command('language C') nvim_command('normal! `A') eq(testfilename, funcs.fnamemodify(curbufmeths.get_name(), ':t')) @@ -223,17 +218,32 @@ describe('ShaDa support code', function() -- during -c used to add item with zero lnum to jump list. it('does not create incorrect file for non-existent buffers when writing from -c', function() - add_argv('--cmd', 'silent edit ' .. non_existent_testfilename, '-c', 'qall') - local argv = nvim_argv(nil, false) -- no --embed + local argv = helpers.new_argv{ + args_rm={ + '-i', + '--embed', -- no --embed + }, + args={ + '-i', meths.get_var('tmpname'), -- Use same shada file as parent. + '--cmd', 'silent edit '..non_existent_testfilename, + '-c', 'qall'}, + } eq('', funcs.system(argv)) eq(0, exc_exec('rshada')) end) it('does not create incorrect file for non-existent buffers opened from -c', function() - add_argv('-c', 'silent edit ' .. non_existent_testfilename, - '-c', 'autocmd VimEnter * qall') - local argv = nvim_argv(nil, false) -- no --embed + local argv = helpers.new_argv{ + args_rm={ + '-i', + '--embed', -- no --embed + }, + args={ + '-i', meths.get_var('tmpname'), -- Use same shada file as parent. + '-c', 'silent edit '..non_existent_testfilename, + '-c', 'autocmd VimEnter * qall'}, + } eq('', funcs.system(argv)) eq(0, exc_exec('rshada')) end) diff --git a/test/functional/shada/registers_spec.lua b/test/functional/shada/registers_spec.lua index 71af14aba8..1f06cbe350 100644 --- a/test/functional/shada/registers_spec.lua +++ b/test/functional/shada/registers_spec.lua @@ -3,9 +3,7 @@ local helpers = require('test.functional.helpers')(after_each) local nvim_command, funcs, eq = helpers.command, helpers.funcs, helpers.eq local shada_helpers = require('test.functional.shada.helpers') -local reset, set_additional_cmd, clear = - shada_helpers.reset, shada_helpers.set_additional_cmd, - shada_helpers.clear +local reset, clear = shada_helpers.reset, shada_helpers.clear local setreg = function(name, contents, typ) if type(contents) == 'string' then @@ -52,9 +50,8 @@ describe('ShaDa support code', function() setreg('c', {'d', 'e', ''}, 'c') setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') - set_additional_cmd('set shada=\'0,<0') nvim_command('qall') - reset() + reset('set shada=\'0,<0') eq({{'d', 'e', ''}, 'v'}, getreg('c')) eq({{'a', 'b', 'cde'}, 'V'}, getreg('l')) eq({{'bca', 'abc', 'cba'}, '\0223'}, getreg('b')) @@ -76,9 +73,8 @@ describe('ShaDa support code', function() setreg('c', {'d', 'e', ''}, 'c') setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') - set_additional_cmd('set shada=\'0,\\"0') nvim_command('qall') - reset() + reset('set shada=\'0,\\"0') eq({{'d', 'e', ''}, 'v'}, getreg('c')) eq({{'a', 'b', 'cde'}, 'V'}, getreg('l')) eq({{'bca', 'abc', 'cba'}, '\0223'}, getreg('b')) @@ -142,7 +138,6 @@ describe('ShaDa support code', function() reset() -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 setreg('e', {'\171«'}, 'c') - set_additional_cmd('') nvim_command('qall') reset() eq({{'\171«'}, 'v'}, getreg('e')) diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index 5f7daf73e5..ff63aed235 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -15,7 +15,6 @@ local shada_helpers = require('test.functional.shada.helpers') local reset, clear, get_shada_rw = shada_helpers.reset, shada_helpers.clear, shada_helpers.get_shada_rw local read_shada_file = shada_helpers.read_shada_file -local set_additional_cmd = shada_helpers.set_additional_cmd local wshada, _, shada_fname, clean = get_shada_rw('Xtest-functional-shada-shada.shada') @@ -244,8 +243,7 @@ describe('ShaDa support code', function() funcs.mkdir(dirname, '', 0) eq(0, funcs.filewritable(dirname)) - set_additional_cmd('set shada=') - reset(dirshada) + reset{shadafile=dirshada, args={'--cmd', 'set shada='}} meths.set_option('shada', '\'10') eq('Vim(wshada):E886: System error while opening ShaDa file ' .. 'Xtest-functional-shada-shada.d/main.shada for reading to merge ' diff --git a/test/functional/shada/variables_spec.lua b/test/functional/shada/variables_spec.lua index f817bcef74..74bbceddcc 100644 --- a/test/functional/shada/variables_spec.lua +++ b/test/functional/shada/variables_spec.lua @@ -4,9 +4,7 @@ local meths, funcs, nvim_command, eq, exc_exec = helpers.meths, helpers.funcs, helpers.command, helpers.eq, helpers.exc_exec local shada_helpers = require('test.functional.shada.helpers') -local reset, set_additional_cmd, clear = - shada_helpers.reset, shada_helpers.set_additional_cmd, - shada_helpers.clear +local reset, clear = shada_helpers.reset, shada_helpers.clear describe('ShaDa support code', function() before_each(reset) @@ -25,8 +23,7 @@ describe('ShaDa support code', function() local autotest = function(tname, varname, varval, val_is_expr) it('is able to dump and read back ' .. tname .. ' variable automatically', function() - set_additional_cmd('set shada+=!') - reset() + reset('set shada+=!') if val_is_expr then nvim_command('let g:' .. varname .. ' = ' .. varval) varval = meths.get_var(varname) @@ -36,7 +33,7 @@ describe('ShaDa support code', function() -- Exit during `reset` is not a regular exit: it does not write shada -- automatically nvim_command('qall') - reset() + reset('set shada+=!') eq(varval, meths.get_var(varname)) end) end @@ -55,8 +52,7 @@ describe('ShaDa support code', function() meths.set_var('STRVAR', 'foo') nvim_command('set shada+=!') nvim_command('wshada') - set_additional_cmd('set shada-=!') - reset() + reset('set shada-=!') nvim_command('rshada') eq(0, funcs.exists('g:STRVAR')) end) @@ -98,7 +94,6 @@ describe('ShaDa support code', function() meths.set_var('LSTVAR', {'«'}) meths.set_var('DCTVAR', {['«']='«'}) meths.set_var('NESTEDVAR', {['«']={{'«'}, {['«']='«'}, {a='Test'}}}) - set_additional_cmd('') nvim_command('qall') reset() eq('«', meths.get_var('STRVAR')) @@ -131,11 +126,10 @@ describe('ShaDa support code', function() nvim_command('let F = function("tr")') meths.set_var('U', '10') nvim_command('set shada+=!') - set_additional_cmd('set shada+=!') eq('Vim(wshada):E5004: Error while dumping variable g:F, itself: attempt to dump function reference', exc_exec('wshada')) meths.set_option('shada', '') - reset() + reset('set shada+=!') eq('10', meths.get_var('U')) end) @@ -148,8 +142,7 @@ describe('ShaDa support code', function() eq('Vim(wshada):E5005: Unable to dump variable g:L: container references itself in index 0', exc_exec('wshada')) meths.set_option('shada', '') - set_additional_cmd('set shada+=!') - reset() + reset('set shada+=!') eq('10', meths.get_var('U')) end) end) diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua index 9a30ea73c4..24bf66e2d8 100644 --- a/test/functional/ui/output_spec.lua +++ b/test/functional/ui/output_spec.lua @@ -51,12 +51,7 @@ describe("shell command :!", function() end) it("throttles shell-command output greater than ~10KB", function() - if helpers.skip_fragile(pending, - (helpers.isCI('travis') and helpers.os_name() == 'osx')) then - return - end - child_session.feed_data( - ":!for i in $(seq 2 30000); do echo XXXXXXXXXX $i; done\n") + child_session.feed_data(":!"..nvim_dir.."/shell-test REP_NODELAY 30001 foo\n") -- If we observe any line starting with a dot, then throttling occurred. -- Avoid false failure on slow systems. @@ -65,10 +60,10 @@ describe("shell command :!", function() -- Final chunk of output should always be displayed, never skipped. -- (Throttling is non-deterministic, this test is merely a sanity check.) screen:expect([[ - XXXXXXXXXX 29997 | - XXXXXXXXXX 29998 | - XXXXXXXXXX 29999 | - XXXXXXXXXX 30000 | + 29997: foo | + 29998: foo | + 29999: foo | + 30000: foo | | {10:Press ENTER or type command to continue}{1: } | {3:-- TERMINAL --} | diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index b5d3dd9f47..24dbc65bd0 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -456,8 +456,8 @@ else if bytes_written == -1 then local err = ffi.errno(0) if err ~= ffi.C.kPOSIXErrnoEINTR then - assert(false, ("write() error: %u: %s"):format( - err, ffi.string(ffi.C.strerror(err)))) + assert(false, ("write() error: %u: %s ('%s')"):format( + err, ffi.string(ffi.C.strerror(err)), s)) end elseif bytes_written == 0 then break |