diff options
96 files changed, 2718 insertions, 580 deletions
diff --git a/.gitignore b/.gitignore index 85b371b926..cf0a11804d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # Tools .ropeproject/ +# Visual Studio +/.vs/ # Build/deps dir /build/ diff --git a/.travis.yml b/.travis.yml index b275a5262d..fa884bd021 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,6 +53,7 @@ jobs: compiler: clang env: > CLANG_SANITIZER=ASAN_UBSAN + # Use Lua so that ASAN can test our embedded Lua support. 8fec4d53d0f6 CMAKE_FLAGS="$CMAKE_FLAGS -DPREFER_LUA=ON" sudo: true - os: linux diff --git a/CMakeLists.txt b/CMakeLists.txt index 4cafdef73f..91b7a06a5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,9 +13,9 @@ include(PreventInTreeBuilds) # Prefer our bundled versions of dependencies. if(DEFINED ENV{DEPS_BUILD_DIR}) -set(DEPS_PREFIX "$ENV{DEPS_BUILD_DIR}/usr" CACHE PATH "Path prefix for finding dependencies") + set(DEPS_PREFIX "$ENV{DEPS_BUILD_DIR}/usr" CACHE PATH "Path prefix for finding dependencies") else() -set(DEPS_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/.deps/usr" CACHE PATH "Path prefix for finding dependencies") + set(DEPS_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/.deps/usr" CACHE PATH "Path prefix for finding dependencies") endif() if(CMAKE_CROSSCOMPILING AND NOT UNIX) list(INSERT CMAKE_FIND_ROOT_PATH 0 ${DEPS_PREFIX}) @@ -53,6 +53,9 @@ if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Darwin") set(USE_FNAME_CASE TRUE) endif() +option(ENABLE_LIBINTL "enable libintl" ON) +option(ENABLE_LIBICONV "enable libiconv" ON) + # Set default build type. if(NOT CMAKE_BUILD_TYPE) message(STATUS "CMAKE_BUILD_TYPE not given, defaulting to 'Debug'.") @@ -331,6 +334,7 @@ if(PREFER_LUA) find_package(Lua REQUIRED) set(LUA_PREFERRED_INCLUDE_DIRS ${LUA_INCLUDE_DIR}) set(LUA_PREFERRED_LIBRARIES ${LUA_LIBRARIES}) + # Passive (not REQUIRED): if LUAJIT_FOUND is not set, nvim-test is skipped. find_package(LuaJit) else() find_package(LuaJit REQUIRED) @@ -399,31 +403,30 @@ if((CLANG_ASAN_UBSAN OR CLANG_MSAN OR CLANG_TSAN) AND NOT CMAKE_C_COMPILER_ID MA message(FATAL_ERROR "Sanitizers are only supported for Clang.") endif() -if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD|FreeBSD") - message(STATUS "detected OpenBSD/FreeBSD; disabled jemalloc. #5318") +if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD|FreeBSD|Windows") # see #5318 + message(STATUS "skipping jemalloc on this system: ${CMAKE_SYSTEM_NAME}") option(ENABLE_JEMALLOC "enable jemalloc" OFF) else() option(ENABLE_JEMALLOC "enable jemalloc" ON) endif() -if (ENABLE_JEMALLOC) +if(ENABLE_JEMALLOC) if(CLANG_ASAN_UBSAN OR CLANG_MSAN OR CLANG_TSAN) message(STATUS "Sanitizers have been enabled; don't use jemalloc.") else() - find_package(JeMalloc) - if(JEMALLOC_FOUND) - include_directories(SYSTEM ${JEMALLOC_INCLUDE_DIRS}) - endif() + find_package(JeMalloc REQUIRED) + include_directories(SYSTEM ${JEMALLOC_INCLUDE_DIRS}) endif() endif() -find_package(LibIntl) -if(LibIntl_FOUND) +if(ENABLE_LIBINTL) + # LibIntl (not Intl) selects our FindLibIntl.cmake script. #8464 + find_package(LibIntl REQUIRED) include_directories(SYSTEM ${LibIntl_INCLUDE_DIRS}) endif() -find_package(Iconv) -if(Iconv_FOUND) +if(ENABLE_LIBICONV) + find_package(Iconv REQUIRED) include_directories(SYSTEM ${Iconv_INCLUDE_DIRS}) endif() diff --git a/ci/build.ps1 b/ci/build.ps1 index 8eb237ccd1..7e686f3464 100644 --- a/ci/build.ps1 +++ b/ci/build.ps1 @@ -77,6 +77,7 @@ where.exe neovim-ruby-host.bat ; exitIfFailed cmd /c npm.cmd install -g neovim ; exitIfFailed where.exe neovim-node-host.cmd ; exitIfFailed +cmd /c npm link neovim function convertToCmakeArgs($vars) { return $vars.GetEnumerator() | foreach { "-D$($_.Key)=$($_.Value)" } diff --git a/ci/install.sh b/ci/install.sh index 50f3490b63..e95e2f29c1 100755 --- a/ci/install.sh +++ b/ci/install.sh @@ -33,3 +33,4 @@ fi echo "Install neovim npm package" npm install -g neovim +npm link neovim diff --git a/cmake/FindIconv.cmake b/cmake/FindIconv.cmake index f3a54e9d6f..1d0164dae9 100644 --- a/cmake/FindIconv.cmake +++ b/cmake/FindIconv.cmake @@ -8,7 +8,7 @@ include(LibFindMacros) find_path(ICONV_INCLUDE_DIR NAMES iconv.h) -find_library(ICONV_LIBRARY NAMES iconv) +find_library(ICONV_LIBRARY NAMES iconv libiconv) set(Iconv_PROCESS_INCLUDES ICONV_INCLUDE_DIR) if(ICONV_LIBRARY) diff --git a/cmake/FindLibIntl.cmake b/cmake/FindLibIntl.cmake index f8442566a9..738ae39983 100644 --- a/cmake/FindLibIntl.cmake +++ b/cmake/FindLibIntl.cmake @@ -34,9 +34,8 @@ if (LibIntl_INCLUDE_DIR) set(CMAKE_REQUIRED_INCLUDES "${LibIntl_INCLUDE_DIR}") endif() -# This is required because some operating systems don't have a separate -# libintl--it is built into glibc. So we only need to specify the library -# if one was actually found. +# On some systems (linux+glibc) libintl is passively available. +# So only specify the library if one was found. if (LibIntl_LIBRARY) set(CMAKE_REQUIRED_LIBRARIES "${LibIntl_LIBRARY}") endif() @@ -53,6 +52,13 @@ int main(int argc, char** argv) { }" HAVE_WORKING_LIBINTL) if (HAVE_WORKING_LIBINTL) + # On some systems (linux+glibc) libintl is passively available. + # If HAVE_WORKING_LIBINTL then we consider the requirement satisfied. + # Unset REQUIRED so that libfind_process(LibIntl) can proceed. + if(LibIntl_FIND_REQUIRED) + unset(LibIntl_FIND_REQUIRED) + endif() + check_variable_exists(_nl_msg_cat_cntr HAVE_NL_MSG_CAT_CNTR) endif() diff --git a/makedeps.bat b/makedeps.bat new file mode 100644 index 0000000000..a614885568 --- /dev/null +++ b/makedeps.bat @@ -0,0 +1,18 @@ +echo off + +if not defined VS150COMNTOOLS ( + echo error: missing VS150COMNTOOLS environment variable. + echo Run this script from the 'Developer Command Prompt'. + exit /b 1 +) + +echo on + +set CMAKE=%VS150COMNTOOLS%\..\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe + +mkdir .deps +cd .deps +"%CMAKE%" -G "Visual Studio 15 2017" "-DCMAKE_BUILD_TYPE=RelWithDebInfo" ..\third-party\ +"%CMAKE%" --build . --config RelWithDebInfo -- "/verbosity:normal" +cd .. + diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index f828f2cdc1..6c730ae6cd 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -79,6 +79,101 @@ As Nvim develops, its API may change only according the following "contract": - Deprecated functions will not be removed until Nvim version 2.0 ============================================================================== +Buffer update events *api-buffer-updates* + +API clients can "attach" to Nvim buffers to subscribe to buffer update events. +This is similar to |TextChanged| but more powerful and granular. + +Call |nvim_buf_attach| to receive these events on the channel: + + *nvim_buf_lines_event* +nvim_buf_lines_event[{buf}, {changedtick}, {firstline}, {lastline}, {linedata}, {more}] + + When the buffer text between {firstline} and {lastline} (end-exclusive, + zero-indexed) were changed to the new text in the {linedata} list. The + granularity is a line, i.e. if a single character is changed in the editor, + the entire line is sent. + + When {changedtick} is |v:null| this means the screen lines (display) changed + but not the buffer contents. {linedata} contains the changed screen lines. + This happens when |inccommand| shows a buffer preview. + + Properties:~ + {buf} API buffer handle (buffer number) + + {changedtick} value of |b:changedtick| for the buffer. If you send an API + command back to nvim you can check the value of |b:changedtick| as part of + your request to ensure that no other changes have been made. + + {firstline} integer line number of the first line that was replaced. + Zero-indexed: if line 1 was replaced then {firstline} will be 0, not 1. + {firstline} is always less than or equal to the number of lines that were + in the buffer before the lines were replaced. + + {lastline} integer line number of the first line that was not replaced + (i.e. the range {firstline}, {lastline} is end-exclusive). + Zero-indexed: if line numbers 2 to 5 were replaced, this will be 5 instead + of 6. {lastline} is always be less than or equal to the number of lines + that were in the buffer before the lines were replaced. {lastline} will be + -1 if the event is part of the initial update after attaching. + + {linedata} list of strings containing the contents of the new buffer + lines. Newline characters are omitted; empty lines are sent as empty + strings. + + {more} boolean, true for a "multipart" change notification: the current + change was chunked into multiple |nvim_buf_lines_event| notifications + (e.g. because it was too big). + +nvim_buf_changedtick_event[{buf}, {changedtick}] *nvim_buf_changedtick_event* + + When |b:changedtick| was incremented but no text was changed. Relevant for + undo/redo. + + Properties:~ + {buf} API buffer handle (buffer number) + {changedtick} new value of |b:changedtick| for the buffer + +nvim_buf_detach_event[{buf}] *nvim_buf_detach_event* + + When buffer is detached (i.e. updates are disabled). Triggered explicitly by + |nvim_buf_detach| or implicitly in these cases: + - Buffer was |abandon|ed and 'hidden' is not set. + - Buffer was reloaded, e.g. with |:edit| or an external change triggered + |:checktime| or 'autoread'. + - Generally: whenever the buffer contents are unloaded from memory. + + Properties:~ + {buf} API buffer handle (buffer number) + + +EXAMPLE ~ + +Calling |nvim_buf_attach| with send_buffer=true on an empty buffer, emits: > + nvim_buf_lines_event[{buf}, {changedtick}, 0, 0, [""], v:false] + +User adds two lines to the buffer, emits: > + nvim_buf_lines_event[{buf}, {changedtick}, 0, 0, ["line1", "line2"], v:false] + +User moves to a line containing the text "Hello world" and inserts "!", emits: > + nvim_buf_lines_event[{buf}, {changedtick}, {linenr}, {linenr} + 1, + ["Hello world!"], v:false] + +User moves to line 3 and deletes 20 lines using "20dd", emits: > + nvim_buf_lines_event[{buf}, {changedtick}, 2, 22, [], v:false] + +User selects lines 3-5 using |linewise-visual| mode and then types "p" to +paste a block of 6 lines, emits: > + nvim_buf_lines_event[{buf}, {changedtick}, 2, 5, + ['pasted line 1', 'pasted line 2', 'pasted line 3', 'pasted line 4', + 'pasted line 5', 'pasted line 6'], + v:false + ] + +User reloads the buffer with ":edit", emits: > + nvim_buf_detach_event[{buf}] + +============================================================================== Buffer highlighting *api-highlights* Nvim allows plugins to add position-based highlights to buffers. This is diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index fffdcef8d9..f404192abb 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -6789,10 +6789,12 @@ setpos({expr}, {list}) [bufnum, lnum, col, off, curswant] "bufnum" is the buffer number. Zero can be used for the - current buffer. Setting the cursor is only possible for - the current buffer. To set a mark in another buffer you can - use the |bufnr()| function to turn a file name into a buffer - number. + current buffer. When setting an uppercase mark "bufnum" is + used for the mark position. For other marks it specifies the + buffer to set the mark in. You can use the |bufnr()| function + to turn a file name into a buffer number. + For setting the cursor and the ' mark "bufnum" is ignored, + since these are associated with a window, not a buffer. Does not change the jumplist. "lnum" and "col" are the position in the buffer. The first diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 2d803792c8..bdedce8076 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -484,7 +484,9 @@ set_property( APPEND_STRING PROPERTY COMPILE_FLAGS " -DMAKE_LIB " ) -if(LUAJIT_FOUND) +if(NOT LUAJIT_FOUND) + message(STATUS "luajit not found, skipping nvim-test (unit tests) target") +else() set(NVIM_TEST_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES} ${LUAJIT_LIBRARIES}) add_library( nvim-test diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index fa4ad27e60..e1fe7617ff 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -25,6 +25,7 @@ #include "nvim/window.h" #include "nvim/undo.h" #include "nvim/ex_docmd.h" +#include "nvim/buffer_updates.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/buffer.c.generated.h" @@ -75,6 +76,59 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) return rv; } +/// Activate updates from this buffer to the current channel. +/// +/// @param buffer The buffer handle +/// @param send_buffer Set to true if the initial notification should contain +/// the whole buffer. If so, the first notification will be a +/// `nvim_buf_lines_event`. Otherwise, the first notification will be +/// a `nvim_buf_changedtick_event` +/// @param opts Optional parameters. Currently not used. +/// @param[out] err Details of an error that may have occurred +/// @return False when updates couldn't be enabled because the buffer isn't +/// loaded or `opts` contained an invalid key; otherwise True. +Boolean nvim_buf_attach(uint64_t channel_id, + Buffer buffer, + Boolean send_buffer, + Dictionary opts, + Error *err) + FUNC_API_SINCE(4) FUNC_API_REMOTE_ONLY +{ + if (opts.size > 0) { + api_set_error(err, kErrorTypeValidation, "dict isn't empty"); + return false; + } + + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return false; + } + + return buf_updates_register(buf, channel_id, send_buffer); +} +// +/// Deactivate updates from this buffer to the current channel. +/// +/// @param buffer The buffer handle +/// @param[out] err Details of an error that may have occurred +/// @return False when updates couldn't be disabled because the buffer +/// isn't loaded; otherwise True. +Boolean nvim_buf_detach(uint64_t channel_id, + Buffer buffer, + Error *err) + FUNC_API_SINCE(4) FUNC_API_REMOTE_ONLY +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return false; + } + + buf_updates_unregister(buf, channel_id); + return true; +} + /// Sets a buffer line /// /// @deprecated use nvim_buf_set_lines instead. @@ -184,23 +238,9 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, rv.size = (size_t)(end - start); rv.items = xcalloc(sizeof(Object), rv.size); - for (size_t i = 0; i < rv.size; i++) { - int64_t lnum = start + (int64_t)i; - - if (lnum >= MAXLNUM) { - api_set_error(err, kErrorTypeValidation, "Line index is too high"); - goto end; - } - - const char *bufstr = (char *)ml_get_buf(buf, (linenr_T)lnum, false); - Object str = STRING_OBJ(cstr_to_string(bufstr)); - - // Vim represents NULs as NLs, but this may confuse clients. - if (channel_id != VIML_INTERNAL_CALL) { - strchrsub(str.data.string.data, '\n', '\0'); - } - - rv.items[i] = str; + if (!buf_collect_lines(buf, rv.size, start, + (channel_id != VIML_INTERNAL_CALL), &rv, err)) { + goto end; } end: @@ -407,7 +447,7 @@ void nvim_buf_set_lines(uint64_t channel_id, false); } - changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra); + changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true); if (save_curbuf.br_buf == NULL) { fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra); diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c index f8eebcdb10..5207a57b88 100644 --- a/src/nvim/api/private/dispatch.c +++ b/src/nvim/api/private/dispatch.c @@ -29,6 +29,8 @@ static void msgpack_rpc_add_method_handler(String method, map_put(String, MsgpackRpcRequestHandler)(methods, method, handler); } +/// @param name API method name +/// @param name_len name size (includes terminating NUL) MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name, size_t name_len) { diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 692a0b51fd..f3e883de02 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -16,6 +16,7 @@ #include "nvim/vim.h" #include "nvim/buffer.h" #include "nvim/window.h" +#include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" @@ -742,6 +743,43 @@ String cstr_as_string(char *str) FUNC_ATTR_PURE return (String){ .data = str, .size = strlen(str) }; } +/// Collects `n` buffer lines into array `l`, optionally replacing newlines +/// with NUL. +/// +/// @param buf Buffer to get lines from +/// @param n Number of lines to collect +/// @param replace_nl Replace newlines ("\n") with NUL +/// @param start Line number to start from +/// @param[out] l Lines are copied here +/// @param err[out] Error, if any +/// @return true unless `err` was set +bool buf_collect_lines(buf_T *buf, size_t n, int64_t start, bool replace_nl, + Array *l, Error *err) +{ + for (size_t i = 0; i < n; i++) { + int64_t lnum = start + (int64_t)i; + + if (lnum >= MAXLNUM) { + if (err != NULL) { + api_set_error(err, kErrorTypeValidation, "Line index is too high"); + } + return false; + } + + const char *bufstr = (char *)ml_get_buf(buf, (linenr_T)lnum, false); + Object str = STRING_OBJ(cstr_to_string(bufstr)); + + if (replace_nl) { + // Vim represents NULs as NLs, but this may confuse clients. + strchrsub(str.data.string.data, '\n', '\0'); + } + + l->items[i] = str; + } + + return true; +} + /// Converts from type Object to a VimL value. /// /// @param obj Object to convert from. diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index b73ecc2d03..b3ae52602b 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -16,6 +16,7 @@ #include "nvim/api/private/dispatch.h" #include "nvim/api/buffer.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/msgpack_rpc/helpers.h" #include "nvim/lua/executor.h" #include "nvim/vim.h" #include "nvim/buffer.h" @@ -1163,6 +1164,11 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) MsgpackRpcRequestHandler handler = msgpack_rpc_get_handler_for(name.data, name.size); + if (handler.fn == msgpack_rpc_handle_missing_method) { + api_set_error(&nested_error, kErrorTypeException, "Invalid method: %s", + name.size > 0 ? name.data : "<empty>"); + break; + } Object result = handler.fn(channel_id, args, &nested_error); if (ERROR_SET(&nested_error)) { // error handled after loop diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index ba63822837..eb781b1be0 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -73,6 +73,7 @@ #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/os/input.h" +#include "nvim/buffer_updates.h" typedef enum { kBLSUnchanged = 0, @@ -574,6 +575,9 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last) /* Change directories when the 'acd' option is set. */ do_autochdir(); + // disable buffer updates for the current buffer + buf_updates_unregister_all(buf); + /* * Remove the buffer from the list. */ @@ -784,6 +788,8 @@ free_buffer_stuff ( map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs xfree(buf->b_start_fenc); buf->b_start_fenc = NULL; + + buf_updates_unregister_all(buf); } /* @@ -1732,9 +1738,11 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags) if (flags & BLN_DUMMY) buf->b_flags |= BF_DUMMY; buf_clear_file(buf); - clrallmarks(buf); /* clear marks */ - fmarks_check_names(buf); /* check file marks for this file */ - buf->b_p_bl = (flags & BLN_LISTED) ? TRUE : FALSE; /* init 'buflisted' */ + clrallmarks(buf); // clear marks + fmarks_check_names(buf); // check file marks for this file + buf->b_p_bl = (flags & BLN_LISTED) ? true : false; // init 'buflisted' + kv_destroy(buf->update_channels); + kv_init(buf->update_channels); if (!(flags & BLN_DUMMY)) { // Tricky: these autocommands may change the buffer list. They could also // split the window with re-using the one empty buffer. This may result in @@ -5185,7 +5193,7 @@ void sign_list_placed(buf_T *rbuf) while (buf != NULL && !got_int) { if (buf->b_signlist != NULL) { vim_snprintf(lbuf, BUFSIZ, _("Signs for %s:"), buf->b_fname); - MSG_PUTS_ATTR(lbuf, hl_attr(HLF_D)); + MSG_PUTS_ATTR(lbuf, HL_ATTR(HLF_D)); msg_putchar('\n'); } for (p = buf->b_signlist; p != NULL && !got_int; p = p->next) { diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 807baf02c1..50d8c822c1 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -38,6 +38,8 @@ typedef struct { #include "nvim/api/private/defs.h" // for Map(K, V) #include "nvim/map.h" +// for kvec +#include "nvim/lib/kvec.h" #define MODIFIABLE(buf) (buf->b_p_ma) @@ -771,6 +773,10 @@ struct file_buffer { BufhlInfo b_bufhl_info; // buffer stored highlights kvec_t(BufhlLine *) b_bufhl_move_space; // temporary space for highlights + + // array of channelids which have asked to receive updates for this + // buffer. + kvec_t(uint64_t) update_channels; }; /* diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c new file mode 100644 index 0000000000..c1b2828666 --- /dev/null +++ b/src/nvim/buffer_updates.c @@ -0,0 +1,210 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +#include "nvim/buffer_updates.h" +#include "nvim/memline.h" +#include "nvim/api/private/helpers.h" +#include "nvim/msgpack_rpc/channel.h" +#include "nvim/assert.h" + +// Register a channel. Return True if the channel was added, or already added. +// Return False if the channel couldn't be added because the buffer is +// unloaded. +bool buf_updates_register(buf_T *buf, uint64_t channel_id, bool send_buffer) +{ + // must fail if the buffer isn't loaded + if (buf->b_ml.ml_mfp == NULL) { + return false; + } + + // count how many channels are currently watching the buffer + size_t size = kv_size(buf->update_channels); + if (size) { + for (size_t i = 0; i < size; i++) { + if (kv_A(buf->update_channels, i) == channel_id) { + // buffer is already registered ... nothing to do + return true; + } + } + } + + // append the channelid to the list + kv_push(buf->update_channels, channel_id); + + if (send_buffer) { + Array args = ARRAY_DICT_INIT; + args.size = 6; + args.items = xcalloc(sizeof(Object), args.size); + + // the first argument is always the buffer handle + args.items[0] = BUFFER_OBJ(buf->handle); + args.items[1] = INTEGER_OBJ(buf->b_changedtick); + // the first line that changed (zero-indexed) + args.items[2] = INTEGER_OBJ(0); + // the last line that was changed + args.items[3] = INTEGER_OBJ(-1); + Array linedata = ARRAY_DICT_INIT; + + // collect buffer contents + + STATIC_ASSERT(SIZE_MAX >= MAXLNUM, "size_t smaller than MAXLNUM"); + size_t line_count = (size_t)buf->b_ml.ml_line_count; + + if (line_count >= 1) { + linedata.size = line_count; + linedata.items = xcalloc(sizeof(Object), line_count); + + buf_collect_lines(buf, line_count, 1, true, &linedata, NULL); + } + + args.items[4] = ARRAY_OBJ(linedata); + args.items[5] = BOOLEAN_OBJ(false); + + rpc_send_event(channel_id, "nvim_buf_lines_event", args); + } else { + buf_updates_changedtick_single(buf, channel_id); + } + + return true; +} + +void buf_updates_send_end(buf_T *buf, uint64_t channelid) +{ + Array args = ARRAY_DICT_INIT; + args.size = 1; + args.items = xcalloc(sizeof(Object), args.size); + args.items[0] = BUFFER_OBJ(buf->handle); + rpc_send_event(channelid, "nvim_buf_detach_event", args); +} + +void buf_updates_unregister(buf_T *buf, uint64_t channelid) +{ + size_t size = kv_size(buf->update_channels); + if (!size) { + return; + } + + // go through list backwards and remove the channel id each time it appears + // (it should never appear more than once) + size_t j = 0; + size_t found = 0; + for (size_t i = 0; i < size; i++) { + if (kv_A(buf->update_channels, i) == channelid) { + found++; + } else { + // copy item backwards into prior slot if needed + if (i != j) { + kv_A(buf->update_channels, j) = kv_A(buf->update_channels, i); + } + j++; + } + } + + if (found) { + // remove X items from the end of the array + buf->update_channels.size -= found; + + // make a new copy of the active array without the channelid in it + buf_updates_send_end(buf, channelid); + + if (found == size) { + kv_destroy(buf->update_channels); + kv_init(buf->update_channels); + } + } +} + +void buf_updates_unregister_all(buf_T *buf) +{ + size_t size = kv_size(buf->update_channels); + if (size) { + for (size_t i = 0; i < size; i++) { + buf_updates_send_end(buf, kv_A(buf->update_channels, i)); + } + kv_destroy(buf->update_channels); + kv_init(buf->update_channels); + } +} + +void buf_updates_send_changes(buf_T *buf, + linenr_T firstline, + int64_t num_added, + int64_t num_removed, + bool send_tick) +{ + // if one the channels doesn't work, put its ID here so we can remove it later + uint64_t badchannelid = 0; + + // notify each of the active channels + for (size_t i = 0; i < kv_size(buf->update_channels); i++) { + uint64_t channelid = kv_A(buf->update_channels, i); + + // send through the changes now channel contents now + Array args = ARRAY_DICT_INIT; + args.size = 6; + args.items = xcalloc(sizeof(Object), args.size); + + // the first argument is always the buffer handle + args.items[0] = BUFFER_OBJ(buf->handle); + + // next argument is b:changedtick + args.items[1] = send_tick ? INTEGER_OBJ(buf->b_changedtick) : NIL; + + // the first line that changed (zero-indexed) + args.items[2] = INTEGER_OBJ(firstline - 1); + + // the last line that was changed + args.items[3] = INTEGER_OBJ(firstline - 1 + num_removed); + + // linedata of lines being swapped in + Array linedata = ARRAY_DICT_INIT; + if (num_added > 0) { + STATIC_ASSERT(SIZE_MAX >= MAXLNUM, "size_t smaller than MAXLNUM"); + linedata.size = (size_t)num_added; + linedata.items = xcalloc(sizeof(Object), (size_t)num_added); + buf_collect_lines(buf, (size_t)num_added, firstline, true, &linedata, + NULL); + } + args.items[4] = ARRAY_OBJ(linedata); + args.items[5] = BOOLEAN_OBJ(false); + if (!rpc_send_event(channelid, "nvim_buf_lines_event", args)) { + // We can't unregister the channel while we're iterating over the + // update_channels array, so we remember its ID to unregister it at + // the end. + badchannelid = channelid; + } + } + + // We can only ever remove one dead channel at a time. This is OK because the + // change notifications are so frequent that many dead channels will be + // cleared up quickly. + if (badchannelid != 0) { + ELOG("Disabling buffer updates for dead channel %llu", badchannelid); + buf_updates_unregister(buf, badchannelid); + } +} + +void buf_updates_changedtick(buf_T *buf) +{ + // notify each of the active channels + for (size_t i = 0; i < kv_size(buf->update_channels); i++) { + uint64_t channel_id = kv_A(buf->update_channels, i); + buf_updates_changedtick_single(buf, channel_id); + } +} + +void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id) +{ + Array args = ARRAY_DICT_INIT; + args.size = 2; + args.items = xcalloc(sizeof(Object), args.size); + + // the first argument is always the buffer handle + args.items[0] = BUFFER_OBJ(buf->handle); + + // next argument is b:changedtick + args.items[1] = INTEGER_OBJ(buf->b_changedtick); + + // don't try and clean up dead channels here + rpc_send_event(channel_id, "nvim_buf_changedtick_event", args); +} diff --git a/src/nvim/buffer_updates.h b/src/nvim/buffer_updates.h new file mode 100644 index 0000000000..b2d0a62270 --- /dev/null +++ b/src/nvim/buffer_updates.h @@ -0,0 +1,10 @@ +#ifndef NVIM_BUFFER_UPDATES_H +#define NVIM_BUFFER_UPDATES_H + +#include "nvim/buffer_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "buffer_updates.h.generated.h" +#endif + +#endif // NVIM_BUFFER_UPDATES_H diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 64d743891b..6ad64bbb85 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -602,6 +602,7 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, // process_channel_event will modify the read buffer(convert NULs into NLs) if (chan->term) { terminal_receive(chan->term, ptr, count); + terminal_flush_output(chan->term); } rbuffer_consumed(buf, count); diff --git a/src/nvim/diff.c b/src/nvim/diff.c index f9e40ed06f..61e0b76558 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -2344,7 +2344,7 @@ void ex_diffgetput(exarg_T *eap) } } } - changed_lines(lnum, 0, lnum + count, (long)added); + changed_lines(lnum, 0, lnum + count, (long)added, true); if (dfree != NULL) { // Diff is deleted, update folds in other windows. diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index 6dbb0d05e0..218a3f0604 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -1711,7 +1711,7 @@ static void printdigraph(digr_T *dp) p += (*mb_char2bytes)(dp->result, p); *p = NUL; - msg_outtrans_attr(buf, hl_attr(HLF_8)); + msg_outtrans_attr(buf, HL_ATTR(HLF_8)); p = buf; if (char2cells(dp->result) == 1) { *p++ = ' '; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 462762aea0..f3e2663d76 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1473,10 +1473,11 @@ void edit_putchar(int c, int highlight) if (ScreenLines != NULL) { update_topline(); /* just in case w_topline isn't valid */ validate_cursor(); - if (highlight) - attr = hl_attr(HLF_8); - else + if (highlight) { + attr = HL_ATTR(HLF_8); + } else { attr = 0; + } pc_row = curwin->w_winrow + curwin->w_wrow; pc_col = curwin->w_wincol; pc_status = PC_STATUS_UNSET; @@ -1879,7 +1880,7 @@ static bool check_compl_option(bool dict_opt) edit_submode = NULL; msg_attr((dict_opt ? _("'dictionary' option is empty") - : _("'thesaurus' option is empty")), hl_attr(HLF_E)); + : _("'thesaurus' option is empty")), HL_ATTR(HLF_E)); if (emsg_silent == 0) { vim_beep(BO_COMPL); setcursor(); @@ -2772,8 +2773,8 @@ static void ins_compl_files(int count, char_u **files, int thesaurus, int flags, fp = mch_fopen((char *)files[i], "r"); /* open dictionary file */ if (flags != DICT_EXACT) { vim_snprintf((char *)IObuff, IOSIZE, - _("Scanning dictionary: %s"), (char *)files[i]); - (void)msg_trunc_attr(IObuff, TRUE, hl_attr(HLF_R)); + _("Scanning dictionary: %s"), (char *)files[i]); + (void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R)); } if (fp == NULL) { @@ -3733,15 +3734,15 @@ static int ins_compl_get_exp(pos_T *ini) dict_f = DICT_EXACT; } vim_snprintf((char *)IObuff, IOSIZE, _("Scanning: %s"), - ins_buf->b_fname == NULL - ? buf_spname(ins_buf) - : ins_buf->b_sfname == NULL - ? ins_buf->b_fname - : ins_buf->b_sfname); - (void)msg_trunc_attr(IObuff, TRUE, hl_attr(HLF_R)); - } else if (*e_cpt == NUL) + ins_buf->b_fname == NULL + ? buf_spname(ins_buf) + : ins_buf->b_sfname == NULL + ? ins_buf->b_fname + : ins_buf->b_sfname); + (void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R)); + } else if (*e_cpt == NUL) { break; - else { + } else { if (CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode)) { type = -1; } else if (*e_cpt == 'k' || *e_cpt == 's') { @@ -3760,9 +3761,10 @@ static int ins_compl_get_exp(pos_T *ini) else if (*e_cpt == ']' || *e_cpt == 't') { type = CTRL_X_TAGS; vim_snprintf((char *)IObuff, IOSIZE, _("Scanning tags.")); - (void)msg_trunc_attr(IObuff, TRUE, hl_attr(HLF_R)); - } else + (void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R)); + } else { type = -1; + } /* in any case e_cpt is advanced to the next entry */ (void)copy_option_part(&e_cpt, IObuff, IOSIZE, ","); @@ -4899,7 +4901,7 @@ static int ins_complete(int c, bool enable_pum) if (!p_smd) { msg_attr((const char *)edit_submode_extra, (edit_submode_highl < HLF_COUNT - ? hl_attr(edit_submode_highl) : 0)); + ? HL_ATTR(edit_submode_highl) : 0)); } } else { msg_clr_cmdline(); // necessary for "noshowmode" @@ -7545,9 +7547,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) curwin->w_cursor.coladd = 0; } - /* - * delete newline! - */ + // Delete newline! if (curwin->w_cursor.col == 0) { lnum = Insstart.lnum; if (curwin->w_cursor.lnum == lnum || revins_on) { @@ -7556,7 +7556,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) return false; } Insstart.lnum--; - Insstart.col = MAXCOL; + Insstart.col = (colnr_T)STRLEN(ml_get(Insstart.lnum)); } /* * In replace mode: diff --git a/src/nvim/eval.c b/src/nvim/eval.c index ffea88aa83..30c17af8c9 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6742,36 +6742,39 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *tofree; if (opt_msg_tv->v_type != VAR_UNKNOWN) { - tofree = (char_u *) encode_tv2string(opt_msg_tv, NULL); + tofree = (char_u *)encode_tv2echo(opt_msg_tv, NULL); ga_concat(gap, tofree); xfree(tofree); + ga_concat(gap, (char_u *)": "); + } + + if (atype == ASSERT_MATCH || atype == ASSERT_NOTMATCH) { + ga_concat(gap, (char_u *)"Pattern "); + } else if (atype == ASSERT_NOTEQUAL) { + ga_concat(gap, (char_u *)"Expected not equal to "); } else { - if (atype == ASSERT_MATCH || atype == ASSERT_NOTMATCH) { - ga_concat(gap, (char_u *)"Pattern "); - } else if (atype == ASSERT_NOTEQUAL) { - ga_concat(gap, (char_u *)"Expected not equal to "); - } else { - ga_concat(gap, (char_u *)"Expected "); - } - if (exp_str == NULL) { - tofree = (char_u *)encode_tv2string(exp_tv, NULL); - ga_concat_esc(gap, tofree); - xfree(tofree); + ga_concat(gap, (char_u *)"Expected "); + } + + if (exp_str == NULL) { + tofree = (char_u *)encode_tv2string(exp_tv, NULL); + ga_concat_esc(gap, tofree); + xfree(tofree); + } else { + ga_concat_esc(gap, exp_str); + } + + if (atype != ASSERT_NOTEQUAL) { + if (atype == ASSERT_MATCH) { + ga_concat(gap, (char_u *)" does not match "); + } else if (atype == ASSERT_NOTMATCH) { + ga_concat(gap, (char_u *)" does match "); } else { - ga_concat_esc(gap, exp_str); - } - if (atype != ASSERT_NOTEQUAL) { - if (atype == ASSERT_MATCH) { - ga_concat(gap, (char_u *)" does not match "); - } else if (atype == ASSERT_NOTMATCH) { - ga_concat(gap, (char_u *)" does match "); - } else { - ga_concat(gap, (char_u *)" but got "); - } - tofree = (char_u *)encode_tv2string(got_tv, NULL); - ga_concat_esc(gap, tofree); - xfree(tofree); + ga_concat(gap, (char_u *)" but got "); } + tofree = (char_u *)encode_tv2string(got_tv, NULL); + ga_concat_esc(gap, tofree); + xfree(tofree); } } @@ -8824,7 +8827,7 @@ static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } unsigned long count = (unsigned long)(foldend - foldstart + 1); - txt = ngettext("+-%s%3ld line: ", "+-%s%3ld lines: ", count); + txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count); r = xmalloc(STRLEN(txt) + STRLEN(dashes) // for %s + 20 // for %3ld @@ -14806,18 +14809,14 @@ static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) pos.col = 0; } if (name[0] == '.' && name[1] == NUL) { - // set cursor - if (fnum == curbuf->b_fnum) { - curwin->w_cursor = pos; - if (curswant >= 0) { - curwin->w_curswant = curswant - 1; - curwin->w_set_curswant = false; - } - check_cursor(); - rettv->vval.v_number = 0; - } else { - EMSG(_(e_invarg)); + // set cursor; "fnum" is ignored + curwin->w_cursor = pos; + if (curswant >= 0) { + curwin->w_curswant = curswant - 1; + curwin->w_set_curswant = false; } + check_cursor(); + rettv->vval.v_number = 0; } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) { // set mark if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) { @@ -20439,7 +20438,7 @@ static void list_func_head(ufunc_T *fp, int indent) MSG_PUTS(" "); MSG_PUTS("function "); if (fp->uf_name[0] == K_SPECIAL) { - MSG_PUTS_ATTR("<SNR>", hl_attr(HLF_8)); + MSG_PUTS_ATTR("<SNR>", HL_ATTR(HLF_8)); msg_puts((const char *)fp->uf_name + 3); } else { msg_puts((const char *)fp->uf_name); diff --git a/src/nvim/event/wstream.c b/src/nvim/event/wstream.c index d2fb52243c..2baa667e7d 100644 --- a/src/nvim/event/wstream.c +++ b/src/nvim/event/wstream.c @@ -14,7 +14,7 @@ #include "nvim/vim.h" #include "nvim/memory.h" -#define DEFAULT_MAXMEM 1024 * 1024 * 10 +#define DEFAULT_MAXMEM 1024 * 1024 * 2000 typedef struct { Stream *stream; diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index f575d58f05..4e0bdb777c 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -35,6 +35,7 @@ #include "nvim/fold.h" #include "nvim/getchar.h" #include "nvim/indent.h" +#include "nvim/buffer_updates.h" #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -279,7 +280,7 @@ void ex_align(exarg_T *eap) new_indent = 0; (void)set_indent(new_indent, 0); /* set indent */ } - changed_lines(eap->line1, 0, eap->line2 + 1, 0L); + changed_lines(eap->line1, 0, eap->line2 + 1, 0L, true); curwin->w_cursor = save_curpos; beginline(BL_WHITE | BL_FIX); } @@ -612,7 +613,7 @@ void ex_sort(exarg_T *eap) } else if (deleted < 0) { mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, false); } - changed_lines(eap->line1, 0, eap->line2 + 1, -deleted); + changed_lines(eap->line1, 0, eap->line2 + 1, -deleted, true); curwin->w_cursor.lnum = eap->line1; beginline(BL_WHITE | BL_FIX); @@ -744,8 +745,9 @@ void ex_retab(exarg_T *eap) if (curbuf->b_p_ts != new_ts) redraw_curbuf_later(NOT_VALID); - if (first_line != 0) - changed_lines(first_line, 0, last_line + 1, 0L); + if (first_line != 0) { + changed_lines(first_line, 0, last_line + 1, 0L, true); + } curwin->w_p_list = save_list; /* restore 'list' */ @@ -806,6 +808,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) */ last_line = curbuf->b_ml.ml_line_count; mark_adjust_nofold(line1, line2, last_line - line2, 0L, true); + changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines, false); if (dest >= line2) { mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, false); FOR_ALL_TAB_WINDOWS(tab, win) { @@ -828,6 +831,12 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) curbuf->b_op_start.col = curbuf->b_op_end.col = 0; mark_adjust_nofold(last_line - num_lines + 1, last_line, -(last_line - dest - extra), 0L, true); + changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false); + + // send update regarding the new lines that were added + if (kv_size(curbuf->update_channels)) { + buf_updates_send_changes(curbuf, dest + 1, num_lines, 0, true); + } /* * Now we delete the original text -- webb @@ -858,9 +867,14 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) last_line = curbuf->b_ml.ml_line_count; if (dest > last_line + 1) dest = last_line + 1; - changed_lines(line1, 0, dest, 0L); + changed_lines(line1, 0, dest, 0L, false); } else { - changed_lines(dest + 1, 0, line1 + num_lines, 0L); + changed_lines(dest + 1, 0, line1 + num_lines, 0L, false); + } + + // send nvim_buf_lines_event regarding lines that were deleted + if (kv_size(curbuf->update_channels)) { + buf_updates_send_changes(curbuf, line1 + extra, 0, num_lines, true); } return OK; @@ -1486,7 +1500,7 @@ void print_line_no_prefix(linenr_T lnum, int use_number, int list) if (curwin->w_p_nu || use_number) { vim_snprintf(numbuf, sizeof(numbuf), "%*" PRIdLINENR " ", number_width(curwin), lnum); - msg_puts_attr(numbuf, hl_attr(HLF_N)); // Highlight line nrs. + msg_puts_attr(numbuf, HL_ATTR(HLF_N)); // Highlight line nrs. } msg_prt_line(ml_get(lnum), list); } @@ -2428,6 +2442,7 @@ int do_ecmd( goto theend; } u_unchanged(curbuf); + buf_updates_unregister_all(curbuf); buf_freeall(curbuf, BFA_KEEP_UNDO); // Tell readfile() not to clear or reload undo info. @@ -3153,8 +3168,10 @@ static char_u *sub_parse_flags(char_u *cmd, subflags_T *subflags, /// /// The usual escapes are supported as described in the regexp docs. /// +/// @param do_buf_event If `true`, send buffer updates. /// @return buffer used for 'inccommand' preview -static buf_T *do_sub(exarg_T *eap, proftime_T timeout) +static buf_T *do_sub(exarg_T *eap, proftime_T timeout, + bool do_buf_event) { long i = 0; regmmatch_T regmatch; @@ -3618,7 +3635,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) msg_no_more = TRUE; /* write message same highlighting as for * wait_return */ - smsg_attr(hl_attr(HLF_R), + smsg_attr(HL_ATTR(HLF_R), _("replace with %s (y/n/a/q/l/^E/^Y)?"), sub); msg_no_more = FALSE; msg_scroll = i; @@ -4000,7 +4017,14 @@ skip: * the line number before the change (same as adding the number of * deleted lines). */ i = curbuf->b_ml.ml_line_count - old_line_count; - changed_lines(first_line, 0, last_line - i, i); + changed_lines(first_line, 0, last_line - i, i, false); + + if (kv_size(curbuf->update_channels)) { + int64_t num_added = last_line - first_line; + int64_t num_removed = num_added - i; + buf_updates_send_changes(curbuf, first_line, num_added, num_removed, + do_buf_event); + } } xfree(sub_firstline); /* may have to free allocated copy of the line */ @@ -6246,7 +6270,7 @@ void ex_substitute(exarg_T *eap) { bool preview = (State & CMDPREVIEW); if (*p_icm == NUL || !preview) { // 'inccommand' is disabled - (void)do_sub(eap, profile_zero()); + (void)do_sub(eap, profile_zero(), true); return; } @@ -6270,7 +6294,7 @@ void ex_substitute(exarg_T *eap) // Don't show search highlighting during live substitution bool save_hls = p_hls; p_hls = false; - buf_T *preview_buf = do_sub(eap, profile_setlimit(p_rdt)); + buf_T *preview_buf = do_sub(eap, profile_setlimit(p_rdt), false); p_hls = save_hls; if (save_changedtick != curbuf->b_changedtick) { diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index ce02808ad3..c87e3d4c66 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -698,7 +698,7 @@ return { }, { command='delfunction', - flags=bit.bor(NEEDARG, WORD1, CMDWIN), + flags=bit.bor(BANG, NEEDARG, WORD1, CMDWIN), addr_type=ADDR_LINES, func='ex_delfunction', }, @@ -3082,7 +3082,7 @@ return { }, { command='windo', - flags=bit.bor(BANG, NEEDARG, EXTRA, NOTRLCOM, RANGE, NOTADR, DFLALL), + flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, RANGE, NOTADR, DFLALL), addr_type=ADDR_WINDOWS, func='ex_listdo', }, diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 96d2102156..e95890adbf 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -1512,7 +1512,7 @@ int buf_write_all(buf_T *buf, int forceit) (linenr_T)1, buf->b_ml.ml_line_count, NULL, false, forceit, true, false)); if (curbuf != old_curbuf) { - msg_source(hl_attr(HLF_W)); + msg_source(HL_ATTR(HLF_W)); MSG(_("Warning: Entered other buffer unexpectedly (check autocommands)")); } return retval; @@ -3258,7 +3258,7 @@ retry: ga.ga_len--; } else { // lines like ":map xx yy^M" will have failed if (!sp->error) { - msg_source(hl_attr(HLF_W)); + msg_source(HL_ATTR(HLF_W)); EMSG(_("W15: Warning: Wrong line separator, ^M may be missing")); } sp->error = true; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index e1efd5710d..709dc60b13 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4949,7 +4949,7 @@ static void uc_list(char_u *name, size_t name_len) msg_putchar(gap != &ucmds ? 'b' : ' '); msg_putchar(' '); - msg_outtrans_attr(cmd->uc_name, hl_attr(HLF_D)); + msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D)); len = (int)STRLEN(cmd->uc_name) + 4; do { @@ -6767,8 +6767,8 @@ static void ex_tabs(exarg_T *eap) msg_putchar('\n'); vim_snprintf((char *)IObuff, IOSIZE, _("Tab page %d"), tabcount++); - msg_outtrans_attr(IObuff, hl_attr(HLF_T)); - ui_flush(); /* output one line at a time */ + msg_outtrans_attr(IObuff, HL_ATTR(HLF_T)); + ui_flush(); // output one line at a time os_breakcheck(); FOR_ALL_WINDOWS_IN_TAB(wp, tp) { diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index f62b0a2060..d2db309c4f 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -295,10 +295,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) redir_off = true; // don't redirect the typed command if (!cmd_silent) { - s->i = msg_scrolled; - msg_scrolled = 0; // avoid wait_return message gotocmdline(true); - msg_scrolled += s->i; redrawcmdprompt(); // draw prompt or indent set_cmdspos(); } @@ -349,7 +346,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) // redraw the statusline for statuslines that display the current mode // using the mode() function. - if (KeyTyped) { + if (KeyTyped && msg_scrolled == 0) { curwin->w_redr_status = true; redraw_statuslines(); } @@ -382,7 +379,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) tl_ret = try_leave(&tstate, &err); if (!tl_ret && ERROR_SET(&err)) { msg_putchar('\n'); - msg_printf_attr(hl_attr(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg); + msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg); api_clear_error(&err); redrawcmd(); } @@ -465,7 +462,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) if (!tl_ret && ERROR_SET(&err)) { msg_putchar('\n'); - msg_printf_attr(hl_attr(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg); + msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg); api_clear_error(&err); } @@ -629,6 +626,7 @@ static int command_line_execute(VimState *state, int key) // Entered command line, move it up cmdline_row--; redrawcmd(); + wild_menu_showing = 0; } else if (save_p_ls != -1) { // restore 'laststatus' and 'winminheight' p_ls = save_p_ls; @@ -639,13 +637,13 @@ static int command_line_execute(VimState *state, int key) restore_cmdline(&s->save_ccline); redrawcmd(); save_p_ls = -1; + wild_menu_showing = 0; } else { win_redraw_last_status(topframe); wild_menu_showing = 0; // must be before redraw_statuslines #8385 redraw_statuslines(); } KeyTyped = skt; - wild_menu_showing = 0; if (ccline.input_fn) { RedrawingDisabled = old_RedrawingDisabled; } @@ -2588,7 +2586,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) #define PRINT_ERRMSG(...) \ do { \ msg_putchar('\n'); \ - msg_printf_attr(hl_attr(HLF_E)|MSG_HIST, __VA_ARGS__); \ + msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, __VA_ARGS__); \ printed_errmsg = true; \ } while (0) bool ret = true; @@ -4154,13 +4152,13 @@ static int showmatches(expand_T *xp, int wildmenu) lines = (num_files + columns - 1) / columns; } - attr = hl_attr(HLF_D); /* find out highlighting for directories */ + attr = HL_ATTR(HLF_D); // find out highlighting for directories if (xp->xp_context == EXPAND_TAGS_LISTFILES) { - MSG_PUTS_ATTR(_("tagname"), hl_attr(HLF_T)); + MSG_PUTS_ATTR(_("tagname"), HL_ATTR(HLF_T)); msg_clr_eos(); msg_advance(maxlen - 3); - MSG_PUTS_ATTR(_(" kind file\n"), hl_attr(HLF_T)); + MSG_PUTS_ATTR(_(" kind file\n"), HL_ATTR(HLF_T)); } /* list the files line by line */ @@ -4168,12 +4166,12 @@ static int showmatches(expand_T *xp, int wildmenu) lastlen = 999; for (k = i; k < num_files; k += lines) { if (xp->xp_context == EXPAND_TAGS_LISTFILES) { - msg_outtrans_attr(files_found[k], hl_attr(HLF_D)); + msg_outtrans_attr(files_found[k], HL_ATTR(HLF_D)); p = files_found[k] + STRLEN(files_found[k]) + 1; msg_advance(maxlen + 1); msg_puts((const char *)p); msg_advance(maxlen + 3); - msg_puts_long_attr(p + 2, hl_attr(HLF_D)); + msg_puts_long_attr(p + 2, HL_ATTR(HLF_D)); break; } for (j = maxlen - lastlen; --j >= 0; ) diff --git a/src/nvim/farsi.c b/src/nvim/farsi.c index 5801a2d8fb..fae2c805f9 100644 --- a/src/nvim/farsi.c +++ b/src/nvim/farsi.c @@ -1603,7 +1603,7 @@ static void conv_to_pvim(void) // Assume the screen has been messed up: clear it and redraw. redraw_later(CLEAR); - MSG_ATTR((const char *)farsi_text_1, hl_attr(HLF_S)); + MSG_ATTR((const char *)farsi_text_1, HL_ATTR(HLF_S)); } /// Convert the Farsi VIM into Farsi 3342 standard. @@ -1624,7 +1624,7 @@ static void conv_to_pstd(void) // Assume the screen has been messed up: clear it and redraw. redraw_later(CLEAR); - msg_attr((const char *)farsi_text_2, hl_attr(HLF_S)); + msg_attr((const char *)farsi_text_2, HL_ATTR(HLF_S)); } /// left-right swap the characters in buf[len]. diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 520aedaac7..0417c3daed 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -3694,7 +3694,7 @@ nofail: retval = FAIL; if (end == 0) { - const int attr = hl_attr(HLF_E); // Set highlight for error messages. + const int attr = HL_ATTR(HLF_E); // Set highlight for error messages. MSG_PUTS_ATTR(_("\nWARNING: Original file may be lost or damaged\n"), attr | MSG_HIST); MSG_PUTS_ATTR(_( @@ -3901,7 +3901,7 @@ static int check_mtime(buf_T *buf, FileInfo *file_info) msg_silent = 0; // Must give this prompt. // Don't use emsg() here, don't want to flush the buffers. msg_attr(_("WARNING: The file has been changed since reading it!!!"), - hl_attr(HLF_E)); + HL_ATTR(HLF_E)); if (ask_yesno(_("Do you really want to write to it"), true) == 'n') { return FAIL; } @@ -5020,9 +5020,9 @@ buf_check_timestamp ( } else { if (!autocmd_busy) { msg_start(); - msg_puts_attr(tbuf, hl_attr(HLF_E) + MSG_HIST); + msg_puts_attr(tbuf, HL_ATTR(HLF_E) + MSG_HIST); if (*mesg2 != NUL) { - msg_puts_attr(mesg2, hl_attr(HLF_W) + MSG_HIST); + msg_puts_attr(mesg2, HL_ATTR(HLF_W) + MSG_HIST); } msg_clr_eos(); (void)msg_end(); @@ -5445,13 +5445,13 @@ static void show_autocmd(AutoPat *ap, event_T event) if (event != last_event || ap->group != last_group) { if (ap->group != AUGROUP_DEFAULT) { if (AUGROUP_NAME(ap->group) == NULL) { - msg_puts_attr(get_deleted_augroup(), hl_attr(HLF_E)); + msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E)); } else { - msg_puts_attr(AUGROUP_NAME(ap->group), hl_attr(HLF_T)); + msg_puts_attr(AUGROUP_NAME(ap->group), HL_ATTR(HLF_T)); } msg_puts(" "); } - msg_puts_attr(event_nr2name(event), hl_attr(HLF_T)); + msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T)); last_event = event; last_group = ap->group; msg_putchar('\n'); diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 316fbef47c..282b72b67a 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -20,6 +20,7 @@ #include "nvim/ex_docmd.h" #include "nvim/func_attr.h" #include "nvim/indent.h" +#include "nvim/buffer_updates.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -742,8 +743,20 @@ deleteFold ( /* Deleting markers may make cursor column invalid. */ check_cursor_col(); - if (last_lnum > 0) - changed_lines(first_lnum, (colnr_T)0, last_lnum, 0L); + if (last_lnum > 0) { + changed_lines(first_lnum, (colnr_T)0, last_lnum, 0L, false); + + // send one nvim_buf_lines_event at the end + if (kv_size(curbuf->update_channels)) { + // last_lnum is the line *after* the last line of the outermost fold + // that was modified. Note also that deleting a fold might only require + // the modification of the *first* line of the fold, but we send through a + // notification that includes every line that was part of the fold + int64_t num_changed = last_lnum - first_lnum; + buf_updates_send_changes(curbuf, first_lnum, num_changed, + num_changed, true); + } + } } /* clearFolding() {{{2 */ @@ -1590,7 +1603,15 @@ static void foldCreateMarkers(linenr_T start, linenr_T end) /* Update both changes here, to avoid all folds after the start are * changed when the start marker is inserted and the end isn't. */ - changed_lines(start, (colnr_T)0, end, 0L); + changed_lines(start, (colnr_T)0, end, 0L, false); + + if (kv_size(curbuf->update_channels)) { + // Note: foldAddMarker() may not actually change start and/or end if + // u_save() is unable to save the buffer line, but we send the + // nvim_buf_lines_event anyway since it won't do any harm. + int64_t num_changed = 1 + end - start; + buf_updates_send_changes(curbuf, start, num_changed, num_changed, true); + } } /* foldAddMarker() {{{2 */ @@ -1785,7 +1806,7 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, unsigned long count = (unsigned long)(lnume - lnum + 1); vim_snprintf((char *)buf, FOLD_TEXT_LEN, - ngettext("+--%3ld line folded", + NGETTEXT("+--%3ld line folded", "+--%3ld lines folded ", count), count); text = buf; diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 2ee1e5d4c5..15fcafb584 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -223,6 +223,11 @@ for i = 1, #functions do output:write('\n } else if (args.items['..(j - 1)..'].type == kObjectTypeInteger && args.items['..(j - 1)..'].data.integer >= 0) {') output:write('\n '..converted..' = (handle_T)args.items['..(j - 1)..'].data.integer;') end + -- accept empty lua tables as empty dictionarys + if rt:match('^Dictionary') then + output:write('\n } else if (args.items['..(j - 1)..'].type == kObjectTypeArray && args.items['..(j - 1)..'].data.array.size == 0) {') + output:write('\n '..converted..' = (Dictionary)ARRAY_DICT_INIT;') + end output:write('\n } else {') output:write('\n api_set_error(error, kErrorTypeException, "Wrong type for argument '..j..', expecting '..param[1]..'");') output:write('\n goto cleanup;') diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 9a2ecbfbd8..690a83af50 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -3197,9 +3197,9 @@ showmap ( } while (len < 12); if (mp->m_noremap == REMAP_NONE) { - msg_puts_attr("*", hl_attr(HLF_8)); + msg_puts_attr("*", HL_ATTR(HLF_8)); } else if (mp->m_noremap == REMAP_SCRIPT) { - msg_puts_attr("&", hl_attr(HLF_8)); + msg_puts_attr("&", HL_ATTR(HLF_8)); } else { msg_putchar(' '); } @@ -3212,7 +3212,7 @@ showmap ( /* Use FALSE below if we only want things like <Up> to show up as such on * the rhs, and not M-x etc, TRUE gets both -- webb */ if (*mp->m_str == NUL) { - msg_puts_attr("<Nop>", hl_attr(HLF_8)); + msg_puts_attr("<Nop>", HL_ATTR(HLF_8)); } else { // Remove escaping of CSI, because "m_str" is in a format to be used // as typeahead. diff --git a/src/nvim/gettext.h b/src/nvim/gettext.h index 60317b8484..acc7e3a92c 100644 --- a/src/nvim/gettext.h +++ b/src/nvim/gettext.h @@ -10,10 +10,11 @@ # else # define N_(x) x # endif +# define NGETTEXT(x, xs, n) ngettext(x, xs, n) #else # define _(x) ((char *)(x)) # define N_(x) x -# define ngettext(x, xs, n) ((n) == 1 ? (x) : (xs)) +# define NGETTEXT(x, xs, n) ((n) == 1 ? (x) : (xs)) # define bindtextdomain(x, y) // empty # define bind_textdomain_codeset(x, y) // empty # define textdomain(x) // empty diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index abc4773d84..b3a9eabdb8 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -584,7 +584,7 @@ static void prt_header(prt_settings_T *psettings, int pagenum, linenr_T lnum) static void prt_message(char_u *s) { screen_fill((int)Rows - 1, (int)Rows, 0, (int)Columns, ' ', ' ', 0); - screen_puts(s, (int)Rows - 1, 0, hl_attr(HLF_R)); + screen_puts(s, (int)Rows - 1, 0, HL_ATTR(HLF_R)); ui_flush(); } diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 5d7bd26a2b..ab33cf7863 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -496,9 +496,9 @@ staterr: if (p_csverbose) { msg_clr_eos(); - (void)smsg_attr(hl_attr(HLF_R), - _("Added cscope database %s"), - csinfo[i].fname); + (void)smsg_attr(HL_ATTR(HLF_R), + _("Added cscope database %s"), + csinfo[i].fname); } } @@ -1258,8 +1258,8 @@ static void cs_kill_execute( { if (p_csverbose) { msg_clr_eos(); - (void)smsg_attr(hl_attr(HLF_R) | MSG_HIST, - _("cscope connection %s closed"), cname); + (void)smsg_attr(HL_ATTR(HLF_R) | MSG_HIST, + _("cscope connection %s closed"), cname); } cs_release_csp(i, TRUE); } @@ -1590,16 +1590,16 @@ static void cs_print_tags_priv(char **matches, char **cntxts, char *buf = xmalloc(newsize); size_t bufsize = newsize; // Track available bufsize (void)snprintf(buf, bufsize, cstag_msg, ptag); - MSG_PUTS_ATTR(buf, hl_attr(HLF_T)); + MSG_PUTS_ATTR(buf, HL_ATTR(HLF_T)); msg_clr_eos(); // restore matches[0] *ptag_end = '\t'; // Column headers for match number, line number and filename. - MSG_PUTS_ATTR(_("\n # line"), hl_attr(HLF_T)); + MSG_PUTS_ATTR(_("\n # line"), HL_ATTR(HLF_T)); msg_advance(msg_col + 2); - MSG_PUTS_ATTR(_("filename / context / line\n"), hl_attr(HLF_T)); + MSG_PUTS_ATTR(_("filename / context / line\n"), HL_ATTR(HLF_T)); for (size_t i = 0; i < num_matches; i++) { assert(strcnt(matches[i], '\t') >= 2); @@ -1626,8 +1626,8 @@ static void cs_print_tags_priv(char **matches, char **cntxts, bufsize = newsize; } (void)snprintf(buf, bufsize, csfmt_str, i + 1, lno); - MSG_PUTS_ATTR(buf, hl_attr(HLF_CM)); - MSG_PUTS_LONG_ATTR(cs_pathcomponents(fname), hl_attr(HLF_CM)); + MSG_PUTS_ATTR(buf, HL_ATTR(HLF_CM)); + MSG_PUTS_LONG_ATTR(cs_pathcomponents(fname), HL_ATTR(HLF_CM)); // compute the required space for the context char *context = cntxts[i] ? cntxts[i] : globalcntx; @@ -1915,7 +1915,7 @@ static int cs_reset(exarg_T *eap) * "Added cscope database..." */ snprintf(buf, ARRAY_SIZE(buf), " (#%zu)", i); - MSG_PUTS_ATTR(buf, hl_attr(HLF_R)); + MSG_PUTS_ATTR(buf, HL_ATTR(HLF_R)); } } xfree(dblist[i]); @@ -1927,7 +1927,7 @@ static int cs_reset(exarg_T *eap) xfree(fllist); if (p_csverbose) { - msg_attr(_("All cscope databases reset"), hl_attr(HLF_R) | MSG_HIST); + msg_attr(_("All cscope databases reset"), HL_ATTR(HLF_R) | MSG_HIST); } return CSCOPE_SUCCESS; } /* cs_reset */ @@ -1993,7 +1993,7 @@ static int cs_show(exarg_T *eap) else { MSG_PUTS_ATTR( _(" # pid database name prepend path\n"), - hl_attr(HLF_T)); + HL_ATTR(HLF_T)); for (size_t i = 0; i < csinfo_size; i++) { if (csinfo[i].fname == NULL) continue; diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 3cd26a5bf7..7cfe3f4a18 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -106,39 +106,41 @@ int setmark_pos(int c, pos_T *pos, int fnum) return OK; } + // Can't set a mark in a non-existant buffer. + buf_T *buf = buflist_findnr(fnum); + if (buf == NULL) { + return FAIL; + } + if (c == '"') { - RESET_FMARK(&curbuf->b_last_cursor, *pos, curbuf->b_fnum); + RESET_FMARK(&buf->b_last_cursor, *pos, buf->b_fnum); return OK; } /* Allow setting '[ and '] for an autocommand that simulates reading a * file. */ if (c == '[') { - curbuf->b_op_start = *pos; + buf->b_op_start = *pos; return OK; } if (c == ']') { - curbuf->b_op_end = *pos; + buf->b_op_end = *pos; return OK; } if (c == '<' || c == '>') { - if (c == '<') - curbuf->b_visual.vi_start = *pos; - else - curbuf->b_visual.vi_end = *pos; - if (curbuf->b_visual.vi_mode == NUL) - /* Visual_mode has not yet been set, use a sane default. */ - curbuf->b_visual.vi_mode = 'v'; + if (c == '<') { + buf->b_visual.vi_start = *pos; + } else { + buf->b_visual.vi_end = *pos; + } + if (buf->b_visual.vi_mode == NUL) { + // Visual_mode has not yet been set, use a sane default. + buf->b_visual.vi_mode = 'v'; + } return OK; } - buf_T *buf = buflist_findnr(fnum); - // Can't set a mark in a non-existant buffer. - if (buf == NULL) { - return FAIL; - } - if (ASCII_ISLOWER(c)) { i = c - 'a'; RESET_FMARK(buf->b_namedm + i, *pos, fnum); @@ -358,13 +360,14 @@ pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum) } else if (c == '<' || c == '>') { /* start/end of visual area */ startp = &buf->b_visual.vi_start; endp = &buf->b_visual.vi_end; - if ((c == '<') == lt(*startp, *endp)) + if (((c == '<') == lt(*startp, *endp) || endp->lnum == 0) + && startp->lnum != 0) { posp = startp; - else + } else { posp = endp; - /* - * For Visual line mode, set mark at begin or end of line - */ + } + + // For Visual line mode, set mark at begin or end of line if (buf->b_visual.vi_mode == 'V') { pos_copy = *posp; posp = &pos_copy; @@ -647,8 +650,8 @@ void do_marks(exarg_T *eap) show_one_mark(-1, arg, NULL, NULL, false); } -static void -show_one_mark ( +static void +show_one_mark( int c, char_u *arg, pos_T *p, @@ -687,9 +690,10 @@ show_one_mark ( mustfree = TRUE; } if (name != NULL) { - msg_outtrans_attr(name, current ? hl_attr(HLF_D) : 0); - if (mustfree) + msg_outtrans_attr(name, current ? HL_ATTR(HLF_D) : 0); + if (mustfree) { xfree(name); + } } } ui_flush(); /* show one line at a time */ @@ -800,8 +804,8 @@ void ex_jumps(exarg_T *eap) curwin->w_jumplist[i].fmark.mark.col); msg_outtrans(IObuff); msg_outtrans_attr(name, - curwin->w_jumplist[i].fmark.fnum == curbuf->b_fnum - ? hl_attr(HLF_D) : 0); + curwin->w_jumplist[i].fmark.fnum == curbuf->b_fnum + ? HL_ATTR(HLF_D) : 0); xfree(name); os_breakcheck(); } @@ -826,7 +830,7 @@ void ex_changes(exarg_T *eap) int i; char_u *name; - /* Highlight title */ + // Highlight title MSG_PUTS_TITLE(_("\nchange line col text")); for (i = 0; i < curbuf->b_changelistlen && !got_int; ++i) { @@ -842,7 +846,7 @@ void ex_changes(exarg_T *eap) curbuf->b_changelist[i].mark.col); msg_outtrans(IObuff); name = mark_line(&curbuf->b_changelist[i].mark, 17); - msg_outtrans_attr(name, hl_attr(HLF_D)); + msg_outtrans_attr(name, HL_ATTR(HLF_D)); xfree(name); os_breakcheck(); } diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index a52ab9f5d3..05e326104b 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -37,6 +37,8 @@ #ifdef HAVE_LOCALE_H # include <locale.h> #endif +#include "nvim/eval.h" +#include "nvim/path.h" #include "nvim/iconv.h" #include "nvim/mbyte.h" #include "nvim/charset.h" @@ -72,6 +74,9 @@ struct interval { # include "unicode_tables.generated.h" #endif +char_u e_loadlib[] = "E370: Could not load library %s"; +char_u e_loadfunc[] = "E448: Could not load library function %s"; + // To speed up BYTELEN(); keep a lookup table to quickly get the length in // bytes of a UTF-8 character from the first byte of a UTF-8 string. Bytes // which are illegal when used as the first byte have a 1. The NUL byte has @@ -2038,9 +2043,10 @@ void * my_iconv_open(char_u *to, char_u *from) return (void *)-1; /* detected a broken iconv() previously */ #ifdef DYNAMIC_ICONV - /* Check if the iconv.dll can be found. */ - if (!iconv_enabled(true)) + // Check if the iconv.dll can be found. + if (!iconv_enabled(true)) { return (void *)-1; + } #endif fd = iconv_open((char *)enc_skip(to), (char *)enc_skip(from)); @@ -2162,7 +2168,7 @@ static HINSTANCE hMsvcrtDLL = 0; # ifndef DYNAMIC_ICONV_DLL # define DYNAMIC_ICONV_DLL "iconv.dll" -# define DYNAMIC_ICONV_DLL_ALT "libiconv.dll" +# define DYNAMIC_ICONV_DLL_ALT "libiconv-2.dll" # endif # ifndef DYNAMIC_MSVCRT_DLL # define DYNAMIC_MSVCRT_DLL "msvcrt.dll" @@ -2208,6 +2214,35 @@ static void * get_iconv_import_func(HINSTANCE hInst, return NULL; } +// Load library "name". +HINSTANCE vimLoadLib(char *name) +{ + HINSTANCE dll = NULL; + + // NOTE: Do not use mch_dirname() and mch_chdir() here, they may call + // vimLoadLib() recursively, which causes a stack overflow. + WCHAR old_dirw[MAXPATHL]; + + // Path to exe dir. + char *buf = xstrdup((char *)get_vim_var_str(VV_PROGPATH)); + // ptrdiff_t len = ; + // assert(len > 0); + buf[path_tail_with_sep(buf) - buf] = '\0'; + + if (GetCurrentDirectoryW(MAXPATHL, old_dirw) != 0) { + // Change directory to where the executable is, both to make + // sure we find a .dll there and to avoid looking for a .dll + // in the current directory. + SetCurrentDirectory((LPCSTR)buf); + // TODO(justinmk): use uv_dlopen instead. see os_libcall + dll = LoadLibrary(name); + SetCurrentDirectoryW(old_dirw); + } + + return dll; +} + + /* * Try opening the iconv.dll and return TRUE if iconv() can be used. */ @@ -2255,10 +2290,13 @@ bool iconv_enabled(bool verbose) void iconv_end(void) { - if (hIconvDLL != 0) + if (hIconvDLL != 0) { + // TODO(justinmk): use uv_dlclose instead. FreeLibrary(hIconvDLL); - if (hMsvcrtDLL != 0) + } + if (hMsvcrtDLL != 0) { FreeLibrary(hMsvcrtDLL); + } hIconvDLL = 0; hMsvcrtDLL = 0; } diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 3b0cac0456..0449af1e2b 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -767,7 +767,7 @@ void ml_recover(void) recoverymode = TRUE; called_from_main = (curbuf->b_ml.ml_mfp == NULL); - attr = hl_attr(HLF_E); + attr = HL_ATTR(HLF_E); /* * If the file name ends in ".s[uvw][a-z]" we assume this is the swap file. diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 42417f75d5..1bbd07686b 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -825,8 +825,8 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) msg_outnum((long)menu->priority); MSG_PUTS(" "); } - /* Same highlighting as for directories!? */ - msg_outtrans_attr(menu->name, hl_attr(HLF_D)); + // Same highlighting as for directories!? + msg_outtrans_attr(menu->name, HL_ATTR(HLF_D)); } if (menu != NULL && menu->children == NULL) { @@ -854,7 +854,7 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) msg_putchar(' '); MSG_PUTS(" "); if (*menu->strings[bit] == NUL) { - msg_puts_attr("<Nop>", hl_attr(HLF_8)); + msg_puts_attr("<Nop>", HL_ATTR(HLF_8)); } else { msg_outtrans_special(menu->strings[bit], false); } diff --git a/src/nvim/message.c b/src/nvim/message.c index 63accaaa23..7935bcbc2f 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -286,7 +286,7 @@ void trunc_string(char_u *s, char_u *buf, int room_in, int buflen) half = i = (int)STRLEN(s); for (;;) { do { - half = half - (*mb_head_off)(s, s + half - 1) - 1; + half = half - utf_head_off(s, s + half - 1) - 1; } while (half > 0 && utf_iscomposing(utf_ptr2char(s + half))); n = ptr2cells(s + half); if (len + n > room || half == 0) { @@ -434,7 +434,7 @@ void msg_source(int attr) } p = get_emsg_lnum(); if (p != NULL) { - msg_attr(p, hl_attr(HLF_N)); + msg_attr(p, HL_ATTR(HLF_N)); xfree(p); last_sourcing_lnum = sourcing_lnum; /* only once for each line */ } @@ -557,7 +557,7 @@ int emsg(const char_u *s_) emsg_on_display = true; // remember there is an error message msg_scroll++; // don't overwrite a previous message - attr = hl_attr(HLF_E); // set highlight mode for error messages + attr = HL_ATTR(HLF_E); // set highlight mode for error messages if (msg_scrolled != 0) { need_wait_return = true; // needed in case emsg() is called after } // wait_return has reset need_wait_return @@ -1034,9 +1034,10 @@ static void hit_return_msg(void) if (got_int) MSG_PUTS(_("Interrupt: ")); - MSG_PUTS_ATTR(_("Press ENTER or type command to continue"), hl_attr(HLF_R)); - if (!msg_use_printf()) + MSG_PUTS_ATTR(_("Press ENTER or type command to continue"), HL_ATTR(HLF_R)); + if (!msg_use_printf()) { msg_clr_eos(); + } p_more = save_p_more; } @@ -1140,7 +1141,7 @@ void msg_home_replace(char_u *fname) void msg_home_replace_hl(char_u *fname) { - msg_home_replace_attr(fname, hl_attr(HLF_D)); + msg_home_replace_attr(fname, HL_ATTR(HLF_D)); } static void msg_home_replace_attr(char_u *fname, int attr) @@ -1230,7 +1231,7 @@ int msg_outtrans_len_attr(char_u *msgstr, int len, int attr) } plain_start = str + mb_l; msg_puts_attr((const char *)transchar(c), - (attr == 0 ? hl_attr(HLF_8) : attr)); + (attr == 0 ? HL_ATTR(HLF_8) : attr)); retval += char2cells(c); } len -= mb_l - 1; @@ -1244,7 +1245,7 @@ int msg_outtrans_len_attr(char_u *msgstr, int len, int attr) msg_puts_attr_len(plain_start, str - plain_start, attr); } plain_start = str + 1; - msg_puts_attr((const char *)s, attr == 0 ? hl_attr(HLF_8) : attr); + msg_puts_attr((const char *)s, attr == 0 ? HL_ATTR(HLF_8) : attr); retval += (int)STRLEN(s); } else { retval++; @@ -1299,7 +1300,7 @@ int msg_outtrans_special( } const char_u *str = strstart; int retval = 0; - int attr = hl_attr(HLF_8); + int attr = HL_ATTR(HLF_8); while (*str != NUL) { const char *string; @@ -1501,18 +1502,18 @@ void msg_prt_line(char_u *s, int list) } else { c = lcs_tab1; c_extra = lcs_tab2; - attr = hl_attr(HLF_8); + attr = HL_ATTR(HLF_8); } } else if (c == 160 && list && lcs_nbsp != NUL) { c = lcs_nbsp; - attr = hl_attr(HLF_8); + attr = HL_ATTR(HLF_8); } else if (c == NUL && list && lcs_eol != NUL) { p_extra = (char_u *)""; c_extra = NUL; n_extra = 1; c = lcs_eol; - attr = hl_attr(HLF_AT); - --s; + attr = HL_ATTR(HLF_AT); + s--; } else if (c != NUL && (n = byte2cells(c)) > 1) { n_extra = n - 1; p_extra = transchar_byte(c); @@ -1520,13 +1521,13 @@ void msg_prt_line(char_u *s, int list) c = *p_extra++; /* Use special coloring to be able to distinguish <hex> from * the same in plain text. */ - attr = hl_attr(HLF_8); + attr = HL_ATTR(HLF_8); } else if (c == ' ' && trail != NULL && s > trail) { c = lcs_trail; - attr = hl_attr(HLF_8); + attr = HL_ATTR(HLF_8); } else if (c == ' ' && list && lcs_space != NUL) { c = lcs_space; - attr = hl_attr(HLF_8); + attr = HL_ATTR(HLF_8); } } @@ -1547,13 +1548,12 @@ static char_u *screen_puts_mbyte(char_u *s, int l, int attr) { int cw; - msg_didout = TRUE; /* remember that line is not empty */ + msg_didout = true; // remember that line is not empty cw = (*mb_ptr2cells)(s); - if (cw > 1 && ( - cmdmsg_rl ? msg_col <= 1 : - msg_col == Columns - 1)) { - /* Doesn't fit, print a highlighted '>' to fill it up. */ - msg_screen_putchar('>', hl_attr(HLF_AT)); + if (cw > 1 + && (cmdmsg_rl ? msg_col <= 1 : msg_col == Columns - 1)) { + // Doesn't fit, print a highlighted '>' to fill it up. + msg_screen_putchar('>', HL_ATTR(HLF_AT)); return s; } @@ -1585,7 +1585,7 @@ void msg_puts(const char *s) void msg_puts_title(const char *s) { - msg_puts_attr(s, hl_attr(HLF_T)); + msg_puts_attr(s, HL_ATTR(HLF_T)); } /* @@ -1607,7 +1607,7 @@ void msg_puts_long_len_attr(char_u *longstr, int len, int attr) if (len > room && room >= 20) { slen = (room - 3) / 2; msg_outtrans_len_attr(longstr, slen, attr); - msg_puts_attr("...", hl_attr(HLF_8)); + msg_puts_attr("...", HL_ATTR(HLF_8)); } msg_outtrans_len_attr(longstr + len - slen, slen, attr); } @@ -1886,7 +1886,7 @@ static void msg_scroll_up(void) if (dy_flags & DY_MSGSEP) { if (msg_scrolled == 0) { screen_fill(Rows-p_ch-1, Rows-p_ch, 0, (int)Columns, - fill_msgsep, fill_msgsep, hl_attr(HLF_MSGSEP)); + fill_msgsep, fill_msgsep, HL_ATTR(HLF_MSGSEP)); } int nscroll = MIN(msg_scrollsize()+1, Rows); ui_call_set_scroll_region(Rows-nscroll, Rows-1, 0, Columns-1); @@ -2437,7 +2437,7 @@ void msg_moremsg(int full) int attr; char_u *s = (char_u *)_("-- More --"); - attr = hl_attr(HLF_M); + attr = HL_ATTR(HLF_M); screen_puts(s, (int)Rows - 1, 0, attr); if (full) screen_puts((char_u *) @@ -2720,7 +2720,7 @@ void give_warning(char_u *message, bool hl) FUNC_ATTR_NONNULL_ARG(1) xfree(keep_msg); keep_msg = NULL; if (hl) { - keep_msg_attr = hl_attr(HLF_W); + keep_msg_attr = HL_ATTR(HLF_W); } else { keep_msg_attr = 0; } @@ -3069,7 +3069,7 @@ void display_confirm_msg(void) // Avoid that 'q' at the more prompt truncates the message here. confirm_msg_used++; if (confirm_msg != NULL) { - msg_puts_attr((const char *)confirm_msg, hl_attr(HLF_M)); + msg_puts_attr((const char *)confirm_msg, HL_ATTR(HLF_M)); } confirm_msg_used--; } diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index 28455f0ba9..a5da9d3220 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -28,6 +28,7 @@ #include "nvim/getchar.h" #include "nvim/indent.h" #include "nvim/indent_c.h" +#include "nvim/buffer_updates.h" #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -106,7 +107,8 @@ open_line ( char_u *p; char_u saved_char = NUL; // init for GCC pos_T *pos; - bool do_si = (!p_paste && curbuf->b_p_si && !curbuf->b_p_cin); + bool do_si = (!p_paste && curbuf->b_p_si && !curbuf->b_p_cin + && *curbuf->b_p_inde == NUL); bool no_si = false; // reset did_si afterwards int first_char = NUL; // init for GCC int vreplace_mode; @@ -835,8 +837,8 @@ open_line ( saved_line = NULL; if (did_append) { changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col, - curwin->w_cursor.lnum + 1, 1L); - did_append = FALSE; + curwin->w_cursor.lnum + 1, 1L, true); + did_append = false; /* Move marks after the line break to the new line. */ if (flags & OPENLINE_MARKFIX) @@ -853,8 +855,9 @@ open_line ( */ curwin->w_cursor.lnum = old_cursor.lnum + 1; } - if (did_append) - changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L); + if (did_append) { + changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true); + } curwin->w_cursor.col = newcol; curwin->w_cursor.coladd = 0; @@ -1819,6 +1822,10 @@ void changed_bytes(linenr_T lnum, colnr_T col) { changedOneline(curbuf, lnum); changed_common(lnum, col, lnum + 1, 0L); + // notify any channels that are watching + if (kv_size(curbuf->update_channels)) { + buf_updates_send_changes(curbuf, lnum, 1, 1, true); + } /* Diff highlighting in other diff windows may need to be updated too. */ if (curwin->w_p_diff) { @@ -1859,7 +1866,7 @@ static void changedOneline(buf_T *buf, linenr_T lnum) */ void appended_lines(linenr_T lnum, long count) { - changed_lines(lnum + 1, 0, lnum + 1, count); + changed_lines(lnum + 1, 0, lnum + 1, count, true); } /* @@ -1872,7 +1879,7 @@ void appended_lines_mark(linenr_T lnum, long count) if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false); } - changed_lines(lnum + 1, 0, lnum + 1, count); + changed_lines(lnum + 1, 0, lnum + 1, count, true); } /* @@ -1882,7 +1889,7 @@ void appended_lines_mark(linenr_T lnum, long count) */ void deleted_lines(linenr_T lnum, long count) { - changed_lines(lnum, 0, lnum + count, -count); + changed_lines(lnum, 0, lnum + count, -count, true); } /* @@ -1893,7 +1900,7 @@ void deleted_lines(linenr_T lnum, long count) void deleted_lines_mark(linenr_T lnum, long count) { mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false); - changed_lines(lnum, 0, lnum + count, -count); + changed_lines(lnum, 0, lnum + count, -count, true); } /* @@ -1908,12 +1915,16 @@ void deleted_lines_mark(linenr_T lnum, long count) * Takes care of calling changed() and updating b_mod_*. * Careful: may trigger autocommands that reload the buffer. */ -void -changed_lines ( - linenr_T lnum, /* first line with change */ - colnr_T col, /* column in first line with change */ - linenr_T lnume, /* line below last changed line */ - long xtra /* number of extra lines (negative when deleting) */ +void +changed_lines( + linenr_T lnum, // first line with change + colnr_T col, // column in first line with change + linenr_T lnume, // line below last changed line + long xtra, // number of extra lines (negative when deleting) + bool do_buf_event // some callers like undo/redo call changed_lines() + // and then increment b_changedtick *again*. This flag + // allows these callers to send the nvim_buf_lines_event + // events after they're done modifying b_changedtick. ) { changed_lines_buf(curbuf, lnum, lnume, xtra); @@ -1937,6 +1948,12 @@ changed_lines ( } changed_common(lnum, col, lnume, xtra); + + if (do_buf_event && kv_size(curbuf->update_channels)) { + int64_t num_added = (int64_t)(lnume + xtra - lnum); + int64_t num_removed = lnume - lnum; + buf_updates_send_changes(curbuf, lnum, num_added, num_removed, true); + } } /// Mark line range in buffer as changed. @@ -2202,8 +2219,8 @@ change_warning ( msg_start(); if (msg_row == Rows - 1) msg_col = col; - msg_source(hl_attr(HLF_W)); - MSG_PUTS_ATTR(_(w_readonly), hl_attr(HLF_W) | MSG_HIST); + msg_source(HL_ATTR(HLF_W)); + MSG_PUTS_ATTR(_(w_readonly), HL_ATTR(HLF_W) | MSG_HIST); set_vim_var_string(VV_WARNINGMSG, _(w_readonly), -1); msg_clr_eos(); (void)msg_end(); @@ -2243,7 +2260,7 @@ int ask_yesno(const char *const str, const bool direct) int r = ' '; while (r != 'y' && r != 'n') { // Same highlighting as for wait_return. - smsg_attr(hl_attr(HLF_R), "%s (y/n)?", str); + smsg_attr(HL_ATTR(HLF_R), "%s (y/n)?", str); if (direct) { r = get_keystroke(); } else { @@ -2549,8 +2566,8 @@ void vim_beep(unsigned val) /* When 'verbose' is set and we are sourcing a script or executing a * function give the user a hint where the beep comes from. */ if (vim_strchr(p_debug, 'e') != NULL) { - msg_source(hl_attr(HLF_W)); - msg_attr(_("Beep!"), hl_attr(HLF_W)); + msg_source(HL_ATTR(HLF_W)); + msg_attr(_("Beep!"), HL_ATTR(HLF_W)); } } } diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 26b84b7cc7..6d0c270a51 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -312,24 +312,30 @@ static void handle_request(Channel *channel, msgpack_object *request) api_clear_error(&error); return; } + // Retrieve the request handler MsgpackRpcRequestHandler handler; + Array args = ARRAY_DICT_INIT; msgpack_object *method = msgpack_rpc_method(request); if (method) { handler = msgpack_rpc_get_handler_for(method->via.bin.ptr, method->via.bin.size); + if (handler.fn == msgpack_rpc_handle_missing_method) { + String m = method->via.bin.size > 0 + ? cbuf_to_string(method->via.bin.ptr, method->via.bin.size) + : cstr_to_string("<empty>"); + ADD(args, STRING_OBJ(m)); + handler.async = true; + } else if (!msgpack_rpc_to_array(msgpack_rpc_args(request), &args)) { + handler.fn = msgpack_rpc_handle_invalid_arguments; + handler.async = true; + } } else { handler.fn = msgpack_rpc_handle_missing_method; handler.async = true; } - Array args = ARRAY_DICT_INIT; - if (!msgpack_rpc_to_array(msgpack_rpc_args(request), &args)) { - handler.fn = msgpack_rpc_handle_invalid_arguments; - handler.async = true; - } - RequestEvent *evdata = xmalloc(sizeof(RequestEvent)); evdata->channel = channel; evdata->handler = handler; diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c index fecae11d45..e18c4472b5 100644 --- a/src/nvim/msgpack_rpc/helpers.c +++ b/src/nvim/msgpack_rpc/helpers.c @@ -493,7 +493,8 @@ Object msgpack_rpc_handle_missing_method(uint64_t channel_id, Array args, Error *error) { - api_set_error(error, kErrorTypeException, "Invalid method name"); + api_set_error(error, kErrorTypeException, "Invalid method: %s", + args.size > 0 ? args.items[0].data.string.data : "?"); return NIL; } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index a2aaf8f9af..a7c4c255b7 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -2933,8 +2933,9 @@ void check_visual_highlight(void) static bool did_check = false; if (full_screen) { - if (!did_check && hl_attr(HLF_V) == 0) + if (!did_check && HL_ATTR(HLF_V) == 0) { MSG(_("Warning: terminal cannot highlight")); + } did_check = true; } } @@ -6140,7 +6141,7 @@ static void n_swapchar(cmdarg_T *cap) curwin->w_set_curswant = true; if (did_change) { changed_lines(startpos.lnum, startpos.col, curwin->w_cursor.lnum + 1, - 0L); + 0L, true); curbuf->b_op_start = startpos; curbuf->b_op_end = curwin->w_cursor; if (curbuf->b_op_end.col > 0) diff --git a/src/nvim/ops.c b/src/nvim/ops.c index d874768dfc..45de76f80a 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -214,7 +214,7 @@ void op_shift(oparg_T *oap, int curs_top, int amount) ++curwin->w_cursor.lnum; } - changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L); + changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true); if (oap->motion_type == kMTBlockWise) { curwin->w_cursor.lnum = oap->start.lnum; @@ -570,7 +570,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def } } /* for all lnum */ - changed_lines(oap->start.lnum + 1, 0, oap->end.lnum + 1, 0L); + changed_lines(oap->start.lnum + 1, 0, oap->end.lnum + 1, 0L, true); State = oldstate; } @@ -632,12 +632,13 @@ void op_reindent(oparg_T *oap, Indenter how) /* Mark changed lines so that they will be redrawn. When Visual * highlighting was present, need to continue until the last line. When * there is no change still need to remove the Visual highlighting. */ - if (last_changed != 0) + if (last_changed != 0) { changed_lines(first_changed, 0, - oap->is_VIsual ? start_lnum + oap->line_count : - last_changed + 1, 0L); - else if (oap->is_VIsual) + oap->is_VIsual ? start_lnum + oap->line_count : + last_changed + 1, 0L, true); + } else if (oap->is_VIsual) { redraw_curbuf_later(INVERTED); + } if (oap->line_count > p_report) { i = oap->line_count - (i + 1); @@ -1455,7 +1456,7 @@ int op_delete(oparg_T *oap) check_cursor_col(); changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col, - oap->end.lnum + 1, 0L); + oap->end.lnum + 1, 0L, true); oap->line_count = 0; // no lines deleted } else if (oap->motion_type == kMTLineWise) { if (oap->op_type == OP_CHANGE) { @@ -1822,7 +1823,7 @@ int op_replace(oparg_T *oap, int c) curwin->w_cursor = oap->start; check_cursor(); - changed_lines(oap->start.lnum, oap->start.col, oap->end.lnum + 1, 0L); + changed_lines(oap->start.lnum, oap->start.col, oap->end.lnum + 1, 0L, true); /* Set "'[" and "']" marks. */ curbuf->b_op_start = oap->start; @@ -1856,8 +1857,9 @@ void op_tilde(oparg_T *oap) did_change |= one_change; } - if (did_change) - changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L); + if (did_change) { + changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true); + } } else { // not block mode if (oap->motion_type == kMTLineWise) { oap->start.col = 0; @@ -1881,7 +1883,7 @@ void op_tilde(oparg_T *oap) } if (did_change) { changed_lines(oap->start.lnum, oap->start.col, oap->end.lnum + 1, - 0L); + 0L, true); } } @@ -2264,7 +2266,7 @@ int op_change(oparg_T *oap) } } check_cursor(); - changed_lines(oap->start.lnum + 1, 0, oap->end.lnum + 1, 0L); + changed_lines(oap->start.lnum + 1, 0, oap->end.lnum + 1, 0L, true); xfree(ins_text); } } @@ -3033,7 +3035,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) curwin->w_cursor.col += bd.startspaces; } - changed_lines(lnum, 0, curwin->w_cursor.lnum, nr_lines); + changed_lines(lnum, 0, curwin->w_cursor.lnum, nr_lines, true); /* Set '[ mark. */ curbuf->b_op_start = curwin->w_cursor; @@ -3210,10 +3212,10 @@ error: // note changed text for displaying and folding if (y_type == kMTCharWise) { changed_lines(curwin->w_cursor.lnum, col, - curwin->w_cursor.lnum + 1, nr_lines); + curwin->w_cursor.lnum + 1, nr_lines, true); } else { changed_lines(curbuf->b_op_start.lnum, 0, - curbuf->b_op_start.lnum, nr_lines); + curbuf->b_op_start.lnum, nr_lines, true); } /* put '] mark at last inserted character */ @@ -3332,7 +3334,7 @@ void ex_display(exarg_T *eap) if (arg != NULL && *arg == NUL) arg = NULL; - int attr = hl_attr(HLF_8); + int attr = HL_ATTR(HLF_8); /* Highlight title */ MSG_PUTS_TITLE(_("\n--- Registers ---")); @@ -3693,7 +3695,7 @@ int do_join(size_t count, /* Only report the change in the first line here, del_lines() will report * the deleted line. */ changed_lines(curwin->w_cursor.lnum, currsize, - curwin->w_cursor.lnum + 1, 0L); + curwin->w_cursor.lnum + 1, 0L, true); /* * Delete following lines. To do this we move the cursor there @@ -4363,7 +4365,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd) } change_cnt = do_addsub(oap->op_type, &pos, 0, amount); if (change_cnt) { - changed_lines(pos.lnum, 0, pos.lnum + 1, 0L); + changed_lines(pos.lnum, 0, pos.lnum + 1, 0L, true); } } else { int one_change; @@ -4419,7 +4421,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd) } } if (change_cnt) { - changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L); + changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true); } if (!change_cnt && oap->is_VIsual) { diff --git a/src/nvim/option.c b/src/nvim/option.c index 26fc164c6c..882289c8b8 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -3963,8 +3963,8 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, static char *w_arabic = N_( "W17: Arabic requires UTF-8, do ':set encoding=utf-8'"); - msg_source(hl_attr(HLF_W)); - msg_attr(_(w_arabic), hl_attr(HLF_W)); + msg_source(HL_ATTR(HLF_W)); + msg_attr(_(w_arabic), HL_ATTR(HLF_W)); set_vim_var_string(VV_WARNINGMSG, _(w_arabic), -1); } diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 7fb4a93b54..25c4cc4f92 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -373,11 +373,10 @@ void expand_env_esc(char_u *restrict srcp, *var++ = *tail++; } *var = NUL; - // Use os_get_user_directory() to get the user directory. - // If this function fails, the shell is used to - // expand ~user. This is slower and may fail if the shell - // does not support ~user (old versions of /bin/sh). - var = (char_u *)os_get_user_directory((char *)dst + 1); + // Get the user directory. If this fails the shell is used to expand + // ~user, which is slower and may fail on old versions of /bin/sh. + var = (*dst == NUL) ? NULL + : (char_u *)os_get_user_directory((char *)dst + 1); mustfree = true; if (var == NULL) { expand_T xpc; diff --git a/src/nvim/os/users.c b/src/nvim/os/users.c index 82bb918f70..c6463c2c92 100644 --- a/src/nvim/os/users.c +++ b/src/nvim/os/users.c @@ -75,11 +75,10 @@ int os_get_uname(uv_uid_t uid, char *s, size_t len) char *os_get_user_directory(const char *name) { #if defined(HAVE_GETPWNAM) && defined(HAVE_PWD_H) - struct passwd *pw; - if (name == NULL) { + if (name == NULL || *name == NUL) { return NULL; } - pw = getpwnam(name); // NOLINT(runtime/threadsafe_fn) + struct passwd *pw = getpwnam(name); // NOLINT(runtime/threadsafe_fn) if (pw != NULL) { // save the string from the static passwd entry into malloced memory return xstrdup(pw->pw_dir); diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h index db93f016bf..356094baa1 100644 --- a/src/nvim/os/win_defs.h +++ b/src/nvim/os/win_defs.h @@ -59,7 +59,6 @@ #define BACKSLASH_IN_FILENAME #ifdef _MSC_VER -typedef SSIZE_T ssize_t; typedef int mode_t; #endif diff --git a/src/nvim/path.c b/src/nvim/path.c index 4f3f7c0661..61cfaea84a 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -1097,17 +1097,18 @@ static bool has_env_var(char_u *p) } #ifdef SPECIAL_WILDCHAR -/* - * Return TRUE if "p" contains a special wildcard character. - * Allowing for escaping. - */ + +// Return TRUE if "p" contains a special wildcard character, one that Vim +// cannot expand, requires using a shell. static bool has_special_wildchar(char_u *p) { for (; *p; mb_ptr_adv(p)) { - if (*p == '\\' && p[1] != NUL) - ++p; - else if (vim_strchr((char_u *)SPECIAL_WILDCHAR, *p) != NULL) + // Allow for escaping + if (*p == '\\' && p[1] != NUL) { + p++; + } else if (vim_strchr((char_u *)SPECIAL_WILDCHAR, *p) != NULL) { return true; + } } return false; } @@ -2033,7 +2034,7 @@ int expand_wildcards(int num_pat, char_u **pat, int *num_files, char_u ***files, break; } if (match_file_list(p_wig, (*files)[i], ffname)) { - // remove this matching files from the list + // remove this matching file from the list xfree((*files)[i]); for (j = i; j + 1 < *num_files; j++) { (*files)[j] = (*files)[j + 1]; diff --git a/src/nvim/po/CMakeLists.txt b/src/nvim/po/CMakeLists.txt index 94cc63baea..a7b910f0eb 100644 --- a/src/nvim/po/CMakeLists.txt +++ b/src/nvim/po/CMakeLists.txt @@ -1,4 +1,4 @@ -find_package(Gettext) +find_package(Gettext REQUIRED) find_program(XGETTEXT_PRG xgettext) find_program(ICONV_PRG iconv) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 1c3cb5d6b2..aeb27a5cac 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2260,7 +2260,7 @@ void qf_list(exarg_T *eap) vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i, (char *)fname); msg_outtrans_attr(IObuff, i == qi->qf_lists[qi->qf_curlist].qf_index - ? hl_attr(HLF_QFL) : hl_attr(HLF_D)); + ? HL_ATTR(HLF_QFL) : HL_ATTR(HLF_D)); if (qfp->qf_lnum == 0) { IObuff[0] = NUL; } else if (qfp->qf_col == 0) { @@ -2271,7 +2271,7 @@ void qf_list(exarg_T *eap) } vim_snprintf((char *)IObuff + STRLEN(IObuff), IOSIZE, "%s:", (char *)qf_types(qfp->qf_type, qfp->qf_nr)); - msg_puts_attr((const char *)IObuff, hl_attr(HLF_N)); + msg_puts_attr((const char *)IObuff, HL_ATTR(HLF_N)); if (qfp->qf_pattern != NULL) { qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE); xstrlcat((char *)IObuff, ":", IOSIZE); diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index ee7d6d8500..ef02b6529c 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -479,6 +479,8 @@ static char_u *regprop(char_u *); #endif static char_u e_missingbracket[] = N_("E769: Missing ] after %s["); +static char_u e_reverse_range[] = N_("E944: Reverse range in character class"); +static char_u e_large_class[] = N_("E945: Range too large in character class"); static char_u e_unmatchedpp[] = N_("E53: Unmatched %s%%("); static char_u e_unmatchedp[] = N_("E54: Unmatched %s("); static char_u e_unmatchedpar[] = N_("E55: Unmatched %s)"); @@ -2232,15 +2234,18 @@ collection: if (endc == '\\' && !reg_cpo_lit) endc = coll_get_char(); - if (startc > endc) - EMSG_RET_NULL(_(e_invrange)); + if (startc > endc) { + EMSG_RET_NULL(_(e_reverse_range)); + } if (has_mbyte && ((*mb_char2len)(startc) > 1 || (*mb_char2len)(endc) > 1)) { - /* Limit to a range of 256 chars */ - if (endc > startc + 256) - EMSG_RET_NULL(_(e_invrange)); - while (++startc <= endc) + // Limit to a range of 256 chars + if (endc > startc + 256) { + EMSG_RET_NULL(_(e_large_class)); + } + while (++startc <= endc) { regmbc(startc); + } } else { while (++startc <= endc) regc(startc); @@ -4241,26 +4246,28 @@ regmatch ( int opndc = 0, inpc; opnd = OPERAND(scan); - /* Safety check (just in case 'encoding' was changed since - * compiling the program). */ + // Safety check (just in case 'encoding' was changed since + // compiling the program). if ((len = (*mb_ptr2len)(opnd)) < 2) { status = RA_NOMATCH; break; } - if (enc_utf8) - opndc = mb_ptr2char(opnd); + if (enc_utf8) { + opndc = utf_ptr2char(opnd); + } if (enc_utf8 && utf_iscomposing(opndc)) { /* When only a composing char is given match at any * position where that composing char appears. */ status = RA_NOMATCH; for (i = 0; reginput[i] != NUL; i += utf_ptr2len(reginput + i)) { - inpc = mb_ptr2char(reginput + i); + inpc = utf_ptr2char(reginput + i); if (!utf_iscomposing(inpc)) { - if (i > 0) + if (i > 0) { break; + } } else if (opndc == inpc) { - /* Include all following composing chars. */ - len = i + mb_ptr2len(reginput + i); + // Include all following composing chars. + len = i + utfc_ptr2len(reginput + i); status = RA_MATCH; break; } diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 0b8e979ca2..c2b1b97ce9 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -1711,8 +1711,9 @@ collection: if (emit_range) { endc = startc; startc = oldstartc; - if (startc > endc) - EMSG_RET_FAIL(_(e_invrange)); + if (startc > endc) { + EMSG_RET_FAIL(_(e_reverse_range)); + } if (endc > startc + 2) { /* Emit a range instead of the sequence of @@ -1804,9 +1805,9 @@ collection: int plen; nfa_do_multibyte: - /* plen is length of current char with composing chars */ + // plen is length of current char with composing chars if (enc_utf8 && ((*mb_char2len)(c) - != (plen = (*mb_ptr2len)(old_regparse)) + != (plen = utfc_ptr2len(old_regparse)) || utf_iscomposing(c))) { int i = 0; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 4299002084..f36d408b25 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -3106,9 +3106,9 @@ win_line ( if (n_extra > 0) { if (c_extra != NUL) { c = c_extra; - mb_c = c; /* doesn't handle non-utf-8 multi-byte! */ - if (enc_utf8 && (*mb_char2len)(c) > 1) { - mb_utf8 = TRUE; + mb_c = c; // doesn't handle non-utf-8 multi-byte! + if (enc_utf8 && utf_char2len(c) > 1) { + mb_utf8 = true; u8cc[0] = 0; c = 0xc0; } else @@ -3118,15 +3118,15 @@ win_line ( if (has_mbyte) { mb_c = c; if (enc_utf8) { - /* If the UTF-8 character is more than one byte: - * Decode it into "mb_c". */ - mb_l = (*mb_ptr2len)(p_extra); - mb_utf8 = FALSE; - if (mb_l > n_extra) + // If the UTF-8 character is more than one byte: + // Decode it into "mb_c". + mb_l = utfc_ptr2len(p_extra); + mb_utf8 = false; + if (mb_l > n_extra) { mb_l = 1; - else if (mb_l > 1) { + } else if (mb_l > 1) { mb_c = utfc_ptr2char(p_extra, u8cc); - mb_utf8 = TRUE; + mb_utf8 = true; c = 0xc0; } } else { @@ -3177,10 +3177,10 @@ win_line ( if (has_mbyte) { mb_c = c; if (enc_utf8) { - /* If the UTF-8 character is more than one byte: Decode it - * into "mb_c". */ - mb_l = (*mb_ptr2len)(ptr); - mb_utf8 = FALSE; + // If the UTF-8 character is more than one byte: Decode it + // into "mb_c". + mb_l = utfc_ptr2len(ptr); + mb_utf8 = false; if (mb_l > 1) { mb_c = utfc_ptr2char(ptr, u8cc); // Overlong encoded ASCII or ASCII with composing char @@ -3486,7 +3486,7 @@ win_line ( extra_attr = win_hl_attr(wp, HLF_0); saved_attr2 = char_attr; // save current attr mb_c = c; - if (enc_utf8 && (*mb_char2len)(c) > 1) { + if (enc_utf8 && utf_char2len(c) > 1) { mb_utf8 = true; u8cc[0] = 0; c = 0xc0; @@ -3501,12 +3501,13 @@ win_line ( extra_attr = win_hl_attr(wp, HLF_0); saved_attr2 = char_attr; // save current attr mb_c = c; - if (enc_utf8 && (*mb_char2len)(c) > 1) { - mb_utf8 = TRUE; + if (enc_utf8 && utf_char2len(c) > 1) { + mb_utf8 = true; u8cc[0] = 0; c = 0xc0; - } else - mb_utf8 = FALSE; + } else { + mb_utf8 = false; + } } } @@ -3602,8 +3603,8 @@ win_line ( extra_attr = win_hl_attr(wp, HLF_0); saved_attr2 = char_attr; // save current attr mb_c = c; - if (enc_utf8 && (*mb_char2len)(c) > 1) { - mb_utf8 = TRUE; + if (enc_utf8 && utf_char2len(c) > 1) { + mb_utf8 = true; u8cc[0] = 0; c = 0xc0; } @@ -3647,8 +3648,8 @@ win_line ( extra_attr = win_hl_attr(wp, HLF_AT); n_attr = 1; mb_c = c; - if (enc_utf8 && (*mb_char2len)(c) > 1) { - mb_utf8 = TRUE; + if (enc_utf8 && utf_char2len(c) > 1) { + mb_utf8 = true; u8cc[0] = 0; c = 0xc0; } else @@ -3762,8 +3763,8 @@ win_line ( n_skip = 1; } mb_c = c; - if (enc_utf8 && (*mb_char2len)(c) > 1) { - mb_utf8 = TRUE; + if (enc_utf8 && utf_char2len(c) > 1) { + mb_utf8 = true; u8cc[0] = 0; c = 0xc0; } else @@ -3816,8 +3817,8 @@ win_line ( extra_attr = win_hl_attr(wp, HLF_AT); } mb_c = c; - if (enc_utf8 && (*mb_char2len)(c) > 1) { - mb_utf8 = TRUE; + if (enc_utf8 && utf_char2len(c) > 1) { + mb_utf8 = true; u8cc[0] = 0; c = 0xc0; } else { @@ -4044,8 +4045,8 @@ win_line ( c = lcs_ext; char_attr = win_hl_attr(wp, HLF_AT); mb_c = c; - if (enc_utf8 && (*mb_char2len)(c) > 1) { - mb_utf8 = TRUE; + if (enc_utf8 && utf_char2len(c) > 1) { + mb_utf8 = true; u8cc[0] = 0; c = 0xc0; } else @@ -4061,7 +4062,8 @@ win_line ( * Also highlight the 'colorcolumn' if it is different than * 'cursorcolumn' */ vcol_save_attr = -1; - if (draw_state == WL_LINE && !lnum_in_visual_area) { + if (draw_state == WL_LINE && !lnum_in_visual_area + && search_attr == 0 && area_attr == 0) { if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol && lnum != wp->w_cursor.lnum) { vcol_save_attr = char_attr; @@ -4889,7 +4891,7 @@ win_redr_status_matches ( screen_puts(buf, row, 0, attr); if (selstart != NULL && highlight) { *selend = NUL; - screen_puts(selstart, row, selstart_col, hl_attr(HLF_WM)); + screen_puts(selstart, row, selstart_col, HL_ATTR(HLF_WM)); } screen_fill(row, row + 1, clen, (int)Columns, fillchar, fillchar, attr); @@ -5154,7 +5156,7 @@ win_redr_custom ( stl = p_tal; row = 0; fillchar = ' '; - attr = hl_attr(HLF_TPF); + attr = HL_ATTR(HLF_TPF); maxwidth = Columns; use_sandbox = was_set_insecurely((char_u *)"tabline", 0); } else { @@ -5567,7 +5569,7 @@ static void update_window_hl(win_T *wp, bool invalid) wp->w_hl_attr_normal = 0; } if (wp != curwin) { - wp->w_hl_attr_normal = hl_combine_attr(hl_attr(HLF_INACTIVE), + wp->w_hl_attr_normal = hl_combine_attr(HL_ATTR(HLF_INACTIVE), wp->w_hl_attr_normal); } @@ -5576,7 +5578,7 @@ static void update_window_hl(win_T *wp, bool invalid) if (wp->w_hl_ids[hlf] > 0) { attr = syn_id2attr(wp->w_hl_ids[hlf]); } else { - attr = hl_attr(hlf); + attr = HL_ATTR(hlf); } if (wp->w_hl_attr_normal != 0) { attr = hl_combine_attr(wp->w_hl_attr_normal, attr); @@ -6653,7 +6655,7 @@ int showmode(void) /* Position on the last line in the window, column 0 */ msg_pos_mode(); - attr = hl_attr(HLF_CM); /* Highlight mode */ + attr = HL_ATTR(HLF_CM); // Highlight mode if (do_mode) { MSG_PUTS_ATTR("--", attr); // CTRL-X in Insert mode @@ -6801,7 +6803,7 @@ void clearmode(void) { msg_pos_mode(); if (Recording) { - recording_mode(hl_attr(HLF_CM)); + recording_mode(HL_ATTR(HLF_CM)); } msg_clr_eos(); } @@ -6832,8 +6834,8 @@ static void draw_tabline(void) int modified; int c; int len; - int attr_nosel = hl_attr(HLF_TP); - int attr_fill = hl_attr(HLF_TPF); + int attr_nosel = HL_ATTR(HLF_TP); + int attr_fill = HL_ATTR(HLF_TPF); char_u *p; int room; int use_sep_chars = (t_colors < 8 diff --git a/src/nvim/search.c b/src/nvim/search.c index 84782497a0..cb59eb6d04 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -130,8 +130,8 @@ typedef struct SearchedFile { * * returns FAIL if failed, OK otherwise. */ -int -search_regcomp ( +int +search_regcomp( char_u *pat, int pat_save, int pat_use, @@ -2121,9 +2121,9 @@ static int check_linecomment(char_u *line) * Show the match only if it is visible on the screen. * If there isn't a match, then beep. */ -void -showmatch ( - int c /* char to show match for */ +void +showmatch( + int c // char to show match for ) { pos_T *lpos, save_cursor; @@ -2377,8 +2377,14 @@ findpar ( ++curr; curwin->w_cursor.lnum = curr; if (curr == curbuf->b_ml.ml_line_count && what != '}') { - if ((curwin->w_cursor.col = (colnr_T)STRLEN(ml_get(curr))) != 0) { - --curwin->w_cursor.col; + char_u *line = ml_get(curr); + + // Put the cursor on the last character in the last line and make the + // motion inclusive. + if ((curwin->w_cursor.col = (colnr_T)STRLEN(line)) != 0) { + curwin->w_cursor.col--; + curwin->w_cursor.col -= + (*mb_head_off)(line, line + curwin->w_cursor.col); *pincl = true; } } else @@ -2483,8 +2489,8 @@ static int cls(void) * Returns FAIL if the cursor was already at the end of the file. * If eol is TRUE, last word stops at end of line (for operators). */ -int -fwd_word ( +int +fwd_word( long count, int bigword, /* "W", "E" or "B" */ int eol @@ -2666,8 +2672,8 @@ finished: * * Returns FAIL if start of the file was reached. */ -int -bckend_word ( +int +bckend_word( long count, int bigword, /* TRUE for "B" */ int eol /* TRUE: stop at end of line. */ @@ -2756,8 +2762,8 @@ static void find_first_blank(pos_T *posp) /* * Skip count/2 sentences and count/2 separating white spaces. */ -static void -findsent_forward ( +static void +findsent_forward( long count, int at_start_sent /* cursor is at start of sentence */ ) @@ -2776,8 +2782,8 @@ findsent_forward ( * Find word under cursor, cursor at end. * Used while an operator is pending, and in Visual mode. */ -int -current_word ( +int +current_word( oparg_T *oap, long count, int include, /* TRUE: include word and white space */ @@ -3084,8 +3090,8 @@ extend: * Find block under the cursor, cursor at end. * "what" and "other" are two matching parenthesis/brace/etc. */ -int -current_block ( +int +current_block( oparg_T *oap, long count, int include, /* TRUE == include white space */ @@ -3282,8 +3288,8 @@ static int in_html_tag(int end_tag) /* * Find tag block under the cursor, cursor at end. */ -int -current_tagblock ( +int +current_tagblock( oparg_T *oap, long count_arg, int include /* TRUE == include white space */ @@ -3465,8 +3471,8 @@ theend: return retval; } -int -current_par ( +int +current_par( oparg_T *oap, long count, int include, /* TRUE == include white space */ @@ -3632,8 +3638,8 @@ extend: * as a quote. * Returns column number of "quotechar" or -1 when not found. */ -static int -find_next_quote ( +static int +find_next_quote( char_u *line, int col, int quotechar, @@ -3664,8 +3670,8 @@ find_next_quote ( * as a quote. * Return the found column or zero. */ -static int -find_prev_quote ( +static int +find_prev_quote( char_u *line, int col_start, int quotechar, @@ -3694,8 +3700,8 @@ find_prev_quote ( * Find quote under the cursor, cursor at end. * Returns TRUE if found, else FALSE. */ -int -current_quote ( +int +current_quote( oparg_T *oap, long count, int include, /* TRUE == include quote char */ @@ -3920,8 +3926,8 @@ current_quote ( * Find next search match under cursor, cursor at end. * Used while an operator is pending, and in Visual mode. */ -int -current_search ( +int +current_search( long count, int forward /* move forward or backwards */ ) @@ -4116,19 +4122,19 @@ int linewhite(linenr_T lnum) * Find identifiers or defines in included files. * If p_ic && (compl_cont_status & CONT_SOL) then ptr must be in lowercase. */ -void -find_pattern_in_path ( - char_u *ptr, /* pointer to search pattern */ - int dir, /* direction of expansion */ - size_t len, /* length of search pattern */ - int whole, /* match whole words only */ - int skip_comments, /* don't match inside comments */ - int type, /* Type of search; are we looking for a type? - a macro? */ +void +find_pattern_in_path( + char_u *ptr, // pointer to search pattern + int dir, // direction of expansion + size_t len, // length of search pattern + int whole, // match whole words only + int skip_comments, // don't match inside comments + int type, // Type of search; are we looking for a type? + // a macro? long count, - int action, /* What to do when we find it */ - linenr_T start_lnum, /* first line to start searching */ - linenr_T end_lnum /* last line for searching */ + int action, // What to do when we find it + linenr_T start_lnum, // first line to start searching + linenr_T end_lnum // last line for searching ) { SearchedFile *files; /* Stack of included files */ @@ -4282,7 +4288,7 @@ find_pattern_in_path ( if (new_fname != NULL) { /* using "new_fname" is more reliable, e.g., when * 'includeexpr' is set. */ - msg_outtrans_attr(new_fname, hl_attr(HLF_D)); + msg_outtrans_attr(new_fname, HL_ATTR(HLF_D)); } else { /* * Isolate the file name. @@ -4320,7 +4326,7 @@ find_pattern_in_path ( } save_char = p[i]; p[i] = NUL; - msg_outtrans_attr(p, hl_attr(HLF_D)); + msg_outtrans_attr(p, HL_ATTR(HLF_D)); p[i] = save_char; } @@ -4367,11 +4373,11 @@ find_pattern_in_path ( files[depth].lnum = 0; files[depth].matched = FALSE; if (action == ACTION_EXPAND) { - msg_hist_off = TRUE; /* reset in msg_trunc_attr() */ - vim_snprintf((char*)IObuff, IOSIZE, - _("Scanning included file: %s"), - (char *)new_fname); - msg_trunc_attr(IObuff, TRUE, hl_attr(HLF_R)); + msg_hist_off = true; // reset in msg_trunc_attr() + vim_snprintf((char *)IObuff, IOSIZE, + _("Scanning included file: %s"), + (char *)new_fname); + msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R)); } else if (p_verbose >= 5) { verbose_enter(); smsg(_("Searching included file %s"), @@ -4724,7 +4730,7 @@ static void show_pat_in_path(char_u *line, int type, int did_show, int action, F msg_puts((const char *)IObuff); snprintf((char *)IObuff, IOSIZE, "%4ld", *lnum); // Show line nr. // Highlight line numbers. - msg_puts_attr((const char *)IObuff, hl_attr(HLF_N)); + msg_puts_attr((const char *)IObuff, HL_ATTR(HLF_N)); msg_puts(" "); } msg_prt_line(line, FALSE); diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 686962704a..0db1578e8d 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1031,8 +1031,9 @@ static bool can_compound(slang_T *slang, char_u *word, char_u *flags) if (enc_utf8) { // Need to convert the single byte flags to utf8 characters. p = uflags; - for (i = 0; flags[i] != NUL; ++i) - p += mb_char2bytes(flags[i], p); + for (i = 0; flags[i] != NUL; i++) { + p += utf_char2bytes(flags[i], p); + } *p = NUL; p = uflags; } else @@ -4269,28 +4270,23 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // the score from SCORE_SUBST to // SCORE_SUBCOMP. if (enc_utf8 - && utf_iscomposing( - mb_ptr2char(tword - + sp->ts_twordlen - - sp->ts_tcharlen)) - && utf_iscomposing( - mb_ptr2char(fword - + sp->ts_fcharstart))) - sp->ts_score -= - SCORE_SUBST - SCORE_SUBCOMP; - - // For a similar character adjust score from - // SCORE_SUBST to SCORE_SIMILAR. - else if (!soundfold - && slang->sl_has_map - && similar_chars(slang, - mb_ptr2char(tword - + sp->ts_twordlen - - sp->ts_tcharlen), - mb_ptr2char(fword - + sp->ts_fcharstart))) - sp->ts_score -= - SCORE_SUBST - SCORE_SIMILAR; + && utf_iscomposing(utf_ptr2char(tword + sp->ts_twordlen + - sp->ts_tcharlen)) + && utf_iscomposing(utf_ptr2char(fword + + sp->ts_fcharstart))) { + sp->ts_score -= SCORE_SUBST - SCORE_SUBCOMP; + } else if (!soundfold + && slang->sl_has_map + && similar_chars(slang, + mb_ptr2char(tword + + sp->ts_twordlen + - sp->ts_tcharlen), + mb_ptr2char(fword + + sp->ts_fcharstart))) { + // For a similar character adjust score from + // SCORE_SUBST to SCORE_SIMILAR. + sp->ts_score -= SCORE_SUBST - SCORE_SIMILAR; + } } else if (sp->ts_isdiff == DIFF_INSERT && sp->ts_twordlen > sp->ts_tcharlen) { p = tword + sp->ts_twordlen - sp->ts_tcharlen; diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 26de519f3c..68f0422f7d 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -3591,7 +3591,7 @@ syn_list_one ( {0, NULL} }; - attr = hl_attr(HLF_D); /* highlight like directories */ + attr = HL_ATTR(HLF_D); // highlight like directories /* list the keywords for "id" */ if (!syncing) { @@ -3691,9 +3691,9 @@ static void syn_list_cluster(int id) msg_advance(endcol); if (SYN_CLSTR(curwin->w_s)[id].scl_list != NULL) { - put_id_list("cluster", SYN_CLSTR(curwin->w_s)[id].scl_list, hl_attr(HLF_D)); + put_id_list("cluster", SYN_CLSTR(curwin->w_s)[id].scl_list, HL_ATTR(HLF_D)); } else { - msg_puts_attr("cluster", hl_attr(HLF_D)); + msg_puts_attr("cluster", HL_ATTR(HLF_D)); msg_puts("=NONE"); } } @@ -7186,7 +7186,7 @@ static void highlight_list_one(int id) if (sgp->sg_link && !got_int) { (void)syn_list_header(didh, 9999, id); didh = true; - msg_puts_attr("links to", hl_attr(HLF_D)); + msg_puts_attr("links to", HL_ATTR(HLF_D)); msg_putchar(' '); msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name); } @@ -7234,8 +7234,8 @@ static int highlight_list_arg(int id, int didh, int type, int iarg, didh = TRUE; if (!got_int) { if (*name != NUL) { - MSG_PUTS_ATTR(name, hl_attr(HLF_D)); - MSG_PUTS_ATTR("=", hl_attr(HLF_D)); + MSG_PUTS_ATTR(name, HL_ATTR(HLF_D)); + MSG_PUTS_ATTR("=", HL_ATTR(HLF_D)); } msg_outtrans(ts); } @@ -7507,7 +7507,7 @@ static int syn_add_group(char_u *name) } else if (!ASCII_ISALNUM(*p) && *p != '_') { /* This is an error, but since there previously was no check only * give a warning. */ - msg_source(hl_attr(HLF_W)); + msg_source(HL_ATTR(HLF_W)); MSG(_("W18: Invalid character in group name")); break; } @@ -7749,10 +7749,12 @@ static void highlight_list(void) { int i; - for (i = 10; --i >= 0; ) - highlight_list_two(i, hl_attr(HLF_D)); - for (i = 40; --i >= 0; ) + for (i = 10; --i >= 0; ) { + highlight_list_two(i, HL_ATTR(HLF_D)); + } + for (i = 40; --i >= 0; ) { highlight_list_two(99, 0); + } } static void highlight_list_two(int cnt, int attr) diff --git a/src/nvim/tag.c b/src/nvim/tag.c index d7bdf97c48..473381a13c 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -512,10 +512,10 @@ do_tag ( if (msg_col == 0) msg_didout = FALSE; /* overwrite previous message */ msg_start(); - MSG_PUTS_ATTR(_(" # pri kind tag"), hl_attr(HLF_T)); + MSG_PUTS_ATTR(_(" # pri kind tag"), HL_ATTR(HLF_T)); msg_clr_eos(); taglen_advance(taglen); - MSG_PUTS_ATTR(_("file\n"), hl_attr(HLF_T)); + MSG_PUTS_ATTR(_("file\n"), HL_ATTR(HLF_T)); for (i = 0; i < num_matches && !got_int; i++) { parse_match(matches[i], &tagp); @@ -535,15 +535,15 @@ do_tag ( } msg_advance(13); msg_outtrans_len_attr(tagp.tagname, - (int)(tagp.tagname_end - tagp.tagname), - hl_attr(HLF_T)); + (int)(tagp.tagname_end - tagp.tagname), + HL_ATTR(HLF_T)); msg_putchar(' '); taglen_advance(taglen); /* Find out the actual file name. If it is long, truncate * it and put "..." in the middle */ p = tag_full_fname(&tagp); - msg_puts_long_attr(p, hl_attr(HLF_D)); + msg_puts_long_attr(p, HL_ATTR(HLF_D)); xfree(p); if (msg_col > 0) @@ -573,8 +573,8 @@ do_tag ( p = tagp.tagkind_end; continue; } - /* print all other extra fields */ - attr = hl_attr(HLF_CM); + // print all other extra fields + attr = HL_ATTR(HLF_CM); while (*p && *p != '\r' && *p != '\n') { if (msg_col + ptr2cells(p) >= Columns) { msg_putchar('\n'); @@ -849,7 +849,7 @@ do_tag ( if ((num_matches > prev_num_matches || new_tag) && num_matches > 1) { if (ic) { - msg_attr((const char *)IObuff, hl_attr(HLF_W)); + msg_attr((const char *)IObuff, HL_ATTR(HLF_W)); } else { msg(IObuff); } @@ -960,7 +960,7 @@ void do_tags(exarg_T *eap) tagstack[i].fmark.mark.lnum); msg_outtrans(IObuff); msg_outtrans_attr(name, tagstack[i].fmark.fnum == curbuf->b_fnum - ? hl_attr(HLF_D) : 0); + ? HL_ATTR(HLF_D) : 0); xfree(name); } ui_flush(); /* show one line at a time */ diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 39cb2b6372..c2370de0f8 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -528,6 +528,13 @@ void terminal_send(Terminal *term, char *data, size_t size) term->opts.write_cb(data, size, term->opts.data); } +void terminal_flush_output(Terminal *term) +{ + size_t len = vterm_output_read(term->vt, term->textbuf, + sizeof(term->textbuf)); + terminal_send(term, term->textbuf, len); +} + void terminal_send_key(Terminal *term, int c) { VTermModifier mod = VTERM_MOD_NONE; @@ -545,9 +552,7 @@ void terminal_send_key(Terminal *term, int c) vterm_keyboard_unichar(term->vt, (uint32_t)c, mod); } - size_t len = vterm_output_read(term->vt, term->textbuf, - sizeof(term->textbuf)); - terminal_send(term, term->textbuf, (size_t)len); + terminal_flush_output(term); } void terminal_receive(Terminal *term, char *data, size_t len) @@ -982,7 +987,7 @@ static bool send_mouse_event(Terminal *term, int c) mouse_action(term, button, row, col, drag, 0); size_t len = vterm_output_read(term->vt, term->textbuf, - sizeof(term->textbuf)); + sizeof(term->textbuf)); terminal_send(term, term->textbuf, (size_t)len); return false; } @@ -1234,7 +1239,9 @@ static void refresh_screen(Terminal *term, buf_T *buf) int change_start = row_to_linenr(term, term->invalid_start); int change_end = change_start + changed; - changed_lines(change_start, 0, change_end, added); + changed_lines(change_start, 0, change_end, added, + // Don't send nvim_buf_lines_event for :terminal buffer. + false); term->invalid_start = INT_MAX; term->invalid_end = -1; } diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index c1ede08c31..a161f14bc8 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -88,6 +88,8 @@ NEW_TESTS ?= \ test_options.res \ test_profile.res \ test_put.res \ + test_python2.res \ + test_python3.res \ test_quickfix.res \ test_quotestar.res \ test_recover.res \ diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 8139f00f0e..a998bd90f1 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -366,6 +366,15 @@ func Test_cmdline_complete_wildoptions() bw! endfunc +func Test_cmdline_complete_user_cmd() + command! -complete=color -nargs=1 Foo : + call feedkeys(":Foo \<Tab>\<Home>\"\<cr>", 'tx') + call assert_equal('"Foo blue', @:) + call feedkeys(":Foo b\<Tab>\<Home>\"\<cr>", 'tx') + call assert_equal('"Foo blue', @:) + delcommand Foo +endfunc + " using a leading backslash here set cpo+=C diff --git a/src/nvim/testdir/test_listlbr_utf8.vim b/src/nvim/testdir/test_listlbr_utf8.vim index 56a4cc9b31..b648a3361b 100644 --- a/src/nvim/testdir/test_listlbr_utf8.vim +++ b/src/nvim/testdir/test_listlbr_utf8.vim @@ -194,6 +194,21 @@ func Test_multibyte_sign_and_colorcolumn() call s:close_windows() endfunc +func Test_colorcolumn_priority() + call s:test_windows('setl cc=4 cuc hls') + call setline(1, ["xxyy", ""]) + norm! gg + exe "normal! /xxyy\<CR>" + norm! G + redraw! + let line_attr = s:screen_attr(1, [1, &cc]) + " Search wins over CursorColumn + call assert_equal(line_attr[1], line_attr[0]) + " Search wins over Colorcolumn + call assert_equal(line_attr[2], line_attr[3]) + call s:close_windows('setl hls&vim') +endfunc + func Test_illegal_byte_and_breakat() call s:test_windows("setl sbr= brk+=<") vert resize 18 diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim index f5e4c4b90c..f4fe1c2705 100644 --- a/src/nvim/testdir/test_mapping.vim +++ b/src/nvim/testdir/test_mapping.vim @@ -104,7 +104,7 @@ func Test_map_langmap() imap a c call feedkeys("Go\<C-R>a\<Esc>", "xt") call assert_equal('bbbb', getline('$')) - + " langmap should not apply in Command-line mode set langmap=+{ nolangremap call feedkeys(":call append(line('$'), '+')\<CR>", "xt") @@ -160,3 +160,41 @@ func Test_map_meta_quotes() set nomodified iunmap <M-"> endfunc + +func Test_abbr_after_line_join() + new + abbr foo bar + set backspace=indent,eol,start + exe "normal o\<BS>foo " + call assert_equal("bar ", getline(1)) + bwipe! + unabbr foo + set backspace& +endfunc + +func Test_map_timeout() + nnoremap aaaa :let got_aaaa = 1<CR> + nnoremap bb :let got_bb = 1<CR> + nmap b aaa + new + func ExitInsert(timer) + let g:line = getline(1) + call feedkeys("\<Esc>", "t") + endfunc + set timeout timeoutlen=200 + call timer_start(300, 'ExitInsert') + " After the 'b' Vim waits for another character to see if it matches 'bb'. + " When it times out it is expanded to "aaa", but there is no wait for + " "aaaa". Can't check that reliably though. + call feedkeys("b", "xt!") + call assert_equal("aa", g:line) + call assert_false(exists('got_aaa')) + call assert_false(exists('got_bb')) + + bwipe! + nunmap aaaa + nunmap bb + nunmap b + set timeoutlen& + delfunc ExitInsert +endfunc diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim index d00b1ddc88..18a0c71aab 100644 --- a/src/nvim/testdir/test_marks.vim +++ b/src/nvim/testdir/test_marks.vim @@ -24,3 +24,47 @@ function! Test_Incr_Marks() call assert_equal("XXX 123 123", getline(3)) enew! endfunction + +func Test_setpos() + new one + let onebuf = bufnr('%') + let onewin = win_getid() + call setline(1, ['aaa', 'bbb', 'ccc']) + new two + let twobuf = bufnr('%') + let twowin = win_getid() + call setline(1, ['aaa', 'bbb', 'ccc']) + + " for the cursor the buffer number is ignored + call setpos(".", [0, 2, 1, 0]) + call assert_equal([0, 2, 1, 0], getpos(".")) + call setpos(".", [onebuf, 3, 3, 0]) + call assert_equal([0, 3, 3, 0], getpos(".")) + + call setpos("''", [0, 1, 3, 0]) + call assert_equal([0, 1, 3, 0], getpos("''")) + call setpos("''", [onebuf, 2, 2, 0]) + call assert_equal([0, 2, 2, 0], getpos("''")) + + " buffer-local marks + for mark in ["'a", "'\"", "'[", "']", "'<", "'>"] + call win_gotoid(twowin) + call setpos(mark, [0, 2, 1, 0]) + call assert_equal([0, 2, 1, 0], getpos(mark), "for mark " . mark) + call setpos(mark, [onebuf, 1, 3, 0]) + call win_gotoid(onewin) + call assert_equal([0, 1, 3, 0], getpos(mark), "for mark " . mark) + endfor + + " global marks + call win_gotoid(twowin) + call setpos("'N", [0, 2, 1, 0]) + call assert_equal([twobuf, 2, 1, 0], getpos("'N")) + call setpos("'N", [onebuf, 1, 3, 0]) + call assert_equal([onebuf, 1, 3, 0], getpos("'N")) + + call win_gotoid(onewin) + bwipe! + call win_gotoid(twowin) + bwipe! +endfunc diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 10d4c5dd94..27ac084ef0 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -2387,3 +2387,15 @@ func Test_changelist() %bwipe! let &ul = save_ul endfunc + +func Test_delete_until_paragraph() + if !has('multi_byte') + return + endif + new + normal grádv} + call assert_equal('á', getline(1)) + normal grád} + call assert_equal('', getline(1)) + bwipe! +endfunc diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index eb42e35bd3..5ae8528ee9 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -22,6 +22,13 @@ function! Test_whichwrap() set whichwrap& endfunction +function! Test_isfname() + " This used to cause Vim to access uninitialized memory. + set isfname= + call assert_equal("~X", expand("~X")) + set isfname& +endfunction + function! Test_options() let caught = 'ok' try diff --git a/src/nvim/testdir/test_python2.vim b/src/nvim/testdir/test_python2.vim new file mode 100644 index 0000000000..fb98c1eda7 --- /dev/null +++ b/src/nvim/testdir/test_python2.vim @@ -0,0 +1,24 @@ +" Test for python 2 commands. +" TODO: move tests from test87.in here. + +if !has('python') + finish +endif + +func Test_pydo() + " Check deleting lines does not trigger ml_get error. + py import vim + new + call setline(1, ['one', 'two', 'three']) + pydo vim.command("%d_") + bwipe! + + " Check switching to another buffer does not trigger ml_get error. + new + let wincount = winnr('$') + call setline(1, ['one', 'two', 'three']) + pydo vim.command("new") + call assert_equal(wincount + 1, winnr('$')) + bwipe! + bwipe! +endfunc diff --git a/src/nvim/testdir/test_python3.vim b/src/nvim/testdir/test_python3.vim new file mode 100644 index 0000000000..bb241dacb1 --- /dev/null +++ b/src/nvim/testdir/test_python3.vim @@ -0,0 +1,24 @@ +" Test for python 2 commands. +" TODO: move tests from test88.in here. + +if !has('python3') + finish +endif + +func Test_py3do() + " Check deleting lines does not trigger an ml_get error. + py3 import vim + new + call setline(1, ['one', 'two', 'three']) + py3do vim.command("%d_") + bwipe! + + " Check switching to another buffer does not trigger an ml_get error. + new + let wincount = winnr('$') + call setline(1, ['one', 'two', 'three']) + py3do vim.command("new") + call assert_equal(wincount + 1, winnr('$')) + bwipe! + bwipe! +endfunc diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim index ecd686743e..97638e9aac 100644 --- a/src/nvim/testdir/test_regexp_utf8.vim +++ b/src/nvim/testdir/test_regexp_utf8.vim @@ -109,12 +109,10 @@ func s:classes_test() call assert_equal('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', alnumchars) call assert_equal("\b", backspacechar) call assert_equal("\t ", blankchars) - " Commented out: it succeeds on Linux and Windows, but fails on macOs in Travis. - " call assert_equal("\x01\x02\x03\x04\x05\x06\x07\b\t\n\x0b\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\e\x1c\x1d\x1e\x1f\x7f", cntrlchars) + call assert_equal("\x01\x02\x03\x04\x05\x06\x07\b\t\n\x0b\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\e\x1c\x1d\x1e\x1f\x7f", cntrlchars) call assert_equal("0123456789", digitchars) call assert_equal("\<Esc>", escapechar) - " Commented out: it succeeds on Linux and Windows, but fails on macOs in Travis. - " call assert_equal('!"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~', graphchars) + call assert_equal('!"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~', graphchars) call assert_equal('abcdefghijklmnopqrstuvwxyzµßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ', lowerchars) call assert_equal(' !"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ', printchars) call assert_equal('!"#$%&''()*+,-./:;<=>?@[\]^_`{|}~', punctchars) @@ -168,3 +166,20 @@ func Test_eow_with_optional() call assert_equal(expected, actual) endfor endfunc + +func Test_reversed_range() + for re in range(0, 2) + exe 'set re=' . re + call assert_fails('call match("abc def", "[c-a]")', 'E944:') + endfor + set re=0 +endfunc + +func Test_large_class() + set re=1 + call assert_fails('call match("abc def", "[\u3000-\u4000]")', 'E945:') + set re=2 + call assert_equal(0, 'abc def' =~# '[\u3000-\u4000]') + call assert_equal(1, "\u3042" =~# '[\u3000-\u4000]') + set re=0 +endfunc diff --git a/src/nvim/testdir/test_smartindent.vim b/src/nvim/testdir/test_smartindent.vim index d00eac9798..9e93a55eb0 100644 --- a/src/nvim/testdir/test_smartindent.vim +++ b/src/nvim/testdir/test_smartindent.vim @@ -1,3 +1,4 @@ +" Tests for smartindent " Tests for not doing smart indenting when it isn't set. function! Test_nosmartindent() @@ -12,3 +13,29 @@ function! Test_nosmartindent() call assert_equal(" #test", getline(1)) enew! | close endfunction + +function MyIndent() +endfunction + +" When 'indentexpr' is set, setting 'si' has no effect. +function Test_smartindent_has_no_effect() + new + exe "normal! i\<Tab>one\<Esc>" + set noautoindent + set smartindent + set indentexpr= + exe "normal! Gotwo\<Esc>" + call assert_equal("\ttwo", getline("$")) + + set indentexpr=MyIndent + exe "normal! Gothree\<Esc>" + call assert_equal("three", getline("$")) + + delfunction! MyIndent + set autoindent& + set smartindent& + set indentexpr& + bwipe! +endfunction + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/undo.c b/src/nvim/undo.c index e1ae4b4cc0..c5ec077d01 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -92,6 +92,7 @@ #include "nvim/eval.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/buffer_updates.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/message.h" @@ -1672,7 +1673,7 @@ void u_undo(int count) undo_undoes = TRUE; else undo_undoes = !undo_undoes; - u_doit(count, false); + u_doit(count, false, true); } /* @@ -1685,7 +1686,7 @@ void u_redo(int count) undo_undoes = false; } - u_doit(count, false); + u_doit(count, false, true); } /// Undo and remove the branch from the undo tree. @@ -1697,7 +1698,9 @@ bool u_undo_and_forget(int count) count = 1; } undo_undoes = true; - u_doit(count, true); + u_doit(count, true, + // Don't send nvim_buf_lines_event for u_undo_and_forget(). + false); if (curbuf->b_u_curhead == NULL) { // nothing was undone. @@ -1732,7 +1735,11 @@ bool u_undo_and_forget(int count) } /// Undo or redo, depending on `undo_undoes`, `count` times. -static void u_doit(int startcount, bool quiet) +/// +/// @param startcount How often to undo or redo +/// @param quiet If `true`, don't show messages +/// @param do_buf_event If `true`, send the changedtick with the buffer updates +static void u_doit(int startcount, bool quiet, bool do_buf_event) { int count = startcount; @@ -1768,7 +1775,7 @@ static void u_doit(int startcount, bool quiet) break; } - u_undoredo(true); + u_undoredo(true, do_buf_event); } else { if (curbuf->b_u_curhead == NULL || get_undolevel() <= 0) { beep_flush(); /* nothing to redo */ @@ -1779,7 +1786,7 @@ static void u_doit(int startcount, bool quiet) break; } - u_undoredo(FALSE); + u_undoredo(false, do_buf_event); /* Advance for next redo. Set "newhead" when at the end of the * redoable changes. */ @@ -2026,8 +2033,8 @@ void undo_time(long step, int sec, int file, int absolute) || (uhp->uh_seq == target && !above)) break; curbuf->b_u_curhead = uhp; - u_undoredo(TRUE); - uhp->uh_walk = nomark; /* don't go back down here */ + u_undoredo(true, true); + uhp->uh_walk = nomark; // don't go back down here } /* @@ -2082,7 +2089,7 @@ void undo_time(long step, int sec, int file, int absolute) break; } - u_undoredo(FALSE); + u_undoredo(false, true); /* Advance "curhead" to below the header we last used. If it * becomes NULL then we need to set "newhead" to this leaf. */ @@ -2105,16 +2112,15 @@ void undo_time(long step, int sec, int file, int absolute) u_undo_end(did_undo, absolute, false); } -/* - * u_undoredo: common code for undo and redo - * - * The lines in the file are replaced by the lines in the entry list at - * curbuf->b_u_curhead. The replaced lines in the file are saved in the entry - * list for the next undo/redo. - * - * When "undo" is TRUE we go up in the tree, when FALSE we go down. - */ -static void u_undoredo(int undo) +/// u_undoredo: common code for undo and redo +/// +/// The lines in the file are replaced by the lines in the entry list at +/// curbuf->b_u_curhead. The replaced lines in the file are saved in the entry +/// list for the next undo/redo. +/// +/// @param undo If `true`, go up the tree. Down if `false`. +/// @param do_buf_event If `true`, send buffer updates. +static void u_undoredo(int undo, bool do_buf_event) { char_u **newarray = NULL; linenr_T oldsize; @@ -2242,7 +2248,7 @@ static void u_undoredo(int undo) } } - changed_lines(top + 1, 0, bot, newsize - oldsize); + changed_lines(top + 1, 0, bot, newsize - oldsize, do_buf_event); /* set '[ and '] mark */ if (top + 1 < curbuf->b_op_start.lnum) @@ -2277,6 +2283,13 @@ static void u_undoredo(int undo) unchanged(curbuf, FALSE); } + // because the calls to changed()/unchanged() above will bump b_changedtick + // again, we need to send a nvim_buf_lines_event with just the new value of + // b:changedtick + if (do_buf_event && kv_size(curbuf->update_channels)) { + buf_updates_changedtick(curbuf); + } + /* * restore marks from before undo/redo */ @@ -2521,7 +2534,7 @@ void ex_undolist(exarg_T *eap) msg_start(); msg_puts_attr(_("number changes when saved"), - hl_attr(HLF_T)); + HL_ATTR(HLF_T)); for (int i = 0; i < ga.ga_len && !got_int; i++) { msg_putchar('\n'); if (got_int) { diff --git a/src/nvim/version.c b/src/nvim/version.c index be160e678e..203b53472c 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -1983,7 +1983,8 @@ static void do_intro_line(long row, char_u *mesg, int attr) } } assert(row <= INT_MAX && col <= INT_MAX); - screen_puts_len(p, l, (int)row, (int)col, *p == '<' ? hl_attr(HLF_8) : attr); + screen_puts_len(p, l, (int)row, (int)col, + *p == '<' ? HL_ATTR(HLF_8) : attr); col += clen; } } diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 0c13d331c8..1fe4e53faf 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -272,8 +272,8 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext() // Enums need a typecast to be used as array index (for Ultrix). -#define hl_attr(n) highlight_attr[(int)(n)] -#define term_str(n) term_strings[(int)(n)] +#define HL_ATTR(n) highlight_attr[(int)(n)] +#define TERM_STR(n) term_strings[(int)(n)] /// Maximum number of bytes in a multi-byte character. It can be one 32-bit /// character of up to 6 bytes, or one 16-bit character of up to three bytes diff --git a/src/nvim/window.c b/src/nvim/window.c index 82fffe305c..ebde81bca6 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -3732,7 +3732,7 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid, if (restart_edit) redraw_later(VALID); /* causes status line redraw */ - if (hl_attr(HLF_INACTIVE) + if (HL_ATTR(HLF_INACTIVE) || (prevwin && prevwin->w_hl_ids[HLF_INACTIVE]) || curwin->w_hl_ids[HLF_INACTIVE]) { redraw_all_later(NOT_VALID); diff --git a/test/functional/api/buffer_updates_spec.lua b/test/functional/api/buffer_updates_spec.lua new file mode 100644 index 0000000000..6da790b871 --- /dev/null +++ b/test/functional/api/buffer_updates_spec.lua @@ -0,0 +1,743 @@ +local helpers = require('test.functional.helpers')(after_each) +local eq, ok = helpers.eq, helpers.ok +local buffer, command, eval, nvim, next_msg = helpers.buffer, + helpers.command, helpers.eval, helpers.nvim, helpers.next_msg +local expect_err = helpers.expect_err +local write_file = helpers.write_file + +local origlines = {"original line 1", + "original line 2", + "original line 3", + "original line 4", + "original line 5", + "original line 6"} + +local function expectn(name, args) + -- expect the next message to be the specified notification event + eq({'notification', name, args}, next_msg()) +end + +local function sendkeys(keys) + nvim('input', keys) + -- give nvim some time to process msgpack requests before possibly sending + -- more key presses - otherwise they all pile up in the queue and get + -- processed at once + local ntime = os.clock() + 0.1 + repeat until os.clock() > ntime +end + +local function open(activate, lines) + local filename = helpers.tmpname() + write_file(filename, table.concat(lines, "\n").."\n", true) + command('edit ' .. filename) + local b = nvim('get_current_buf') + -- what is the value of b:changedtick? + local tick = eval('b:changedtick') + + -- Enable buffer events, ensure that the nvim_buf_lines_event messages + -- arrive as expected + if activate then + local firstline = 0 + ok(buffer('attach', b, true, {})) + expectn('nvim_buf_lines_event', {b, tick, firstline, -1, lines, false}) + end + + return b, tick, filename +end + +local function editoriginal(activate, lines) + if not lines then + lines = origlines + end + -- load up the file with the correct contents + helpers.clear() + return open(activate, lines) +end + +local function reopen(buf, expectedlines) + ok(buffer('detach', buf)) + expectn('nvim_buf_detach_event', {buf}) + -- for some reason the :edit! increments tick by 2 + command('edit!') + local tick = eval('b:changedtick') + ok(buffer('attach', buf, true, {})) + local firstline = 0 + expectn('nvim_buf_lines_event', {buf, tick, firstline, -1, expectedlines, false}) + command('normal! gg') + return tick +end + +local function reopenwithfolds(b) + -- discard any changes to the buffer + local tick = reopen(b, origlines) + + -- use markers for folds, make all folds open by default + command('setlocal foldmethod=marker foldlevel=20') + + -- add a fold + command('2,4fold') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 1, 4, {'original line 2/*{{{*/', + 'original line 3', + 'original line 4/*}}}*/'}, false}) + -- make a new fold that wraps lines 1-6 + command('1,6fold') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 6, {'original line 1/*{{{*/', + 'original line 2/*{{{*/', + 'original line 3', + 'original line 4/*}}}*/', + 'original line 5', + 'original line 6/*}}}*/'}, false}) + return tick +end + +describe('API: buffer events:', function() + it('when lines are added', function() + local b, tick = editoriginal(true) + + -- add a new line at the start of the buffer + command('normal! GyyggP') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 0, {'original line 6'}, false}) + + -- add multiple lines at the start of the file + command('normal! GkkyGggP') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 0, {'original line 4', + 'original line 5', + 'original line 6'}, false}) + + -- add one line to the middle of the file, several times + command('normal! ggYjjp') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 3, 3, {'original line 4'}, false}) + command('normal! p') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 4, 4, {'original line 4'}, false}) + command('normal! p') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 5, 5, {'original line 4'}, false}) + + -- add multiple lines to the middle of the file + command('normal! gg4Yjjp') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 3, 3, {'original line 4', + 'original line 5', + 'original line 6', + 'original line 4'}, false}) + + -- add one line to the end of the file + command('normal! ggYGp') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 17, 17, {'original line 4'}, false}) + + -- add one line to the end of the file, several times + command('normal! ggYGppp') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 18, 18, {'original line 4'}, false}) + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 19, 19, {'original line 4'}, false}) + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 20, 20, {'original line 4'}, false}) + + -- add several lines to the end of the file, several times + command('normal! gg4YGp') + command('normal! Gp') + command('normal! Gp') + local firstfour = {'original line 4', + 'original line 5', + 'original line 6', + 'original line 4'} + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 21, 21, firstfour, false}) + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 25, 25, firstfour, false}) + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 29, 29, firstfour, false}) + + -- create a new empty buffer and wipe out the old one ... this will + -- turn off buffer events + command('enew!') + expectn('nvim_buf_detach_event', {b}) + + -- add a line at the start of an empty file + command('enew') + tick = eval('b:changedtick') + local b2 = nvim('get_current_buf') + ok(buffer('attach', b2, true, {})) + expectn('nvim_buf_lines_event', {b2, tick, 0, -1, {""}, false}) + eval('append(0, ["new line 1"])') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b2, tick, 0, 0, {'new line 1'}, false}) + + -- turn off buffer events manually + buffer('detach', b2) + expectn('nvim_buf_detach_event', {b2}) + + -- add multiple lines to a blank file + command('enew!') + local b3 = nvim('get_current_buf') + ok(buffer('attach', b3, true, {})) + tick = eval('b:changedtick') + expectn('nvim_buf_lines_event', {b3, tick, 0, -1, {""}, false}) + eval('append(0, ["new line 1", "new line 2", "new line 3"])') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b3, tick, 0, 0, {'new line 1', + 'new line 2', + 'new line 3'}, false}) + + -- use the API itself to add a line to the start of the buffer + buffer('set_lines', b3, 0, 0, true, {'New First Line'}) + tick = tick + 1 + expectn('nvim_buf_lines_event', {b3, tick, 0, 0, {"New First Line"}, false}) + end) + + it('when lines are removed', function() + local b, tick = editoriginal(true) + + -- remove one line from start of file + command('normal! dd') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 1, {}, false}) + + -- remove multiple lines from the start of the file + command('normal! 4dd') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 4, {}, false}) + + -- remove multiple lines from middle of file + tick = reopen(b, origlines) + command('normal! jj3dd') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 2, 5, {}, false}) + + -- remove one line from the end of the file + tick = reopen(b, origlines) + command('normal! Gdd') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 5, 6, {}, false}) + + -- remove multiple lines from the end of the file + tick = reopen(b, origlines) + command('normal! 4G3dd') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 3, 6, {}, false}) + + -- pretend to remove heaps lines from the end of the file but really + -- just remove two + tick = reopen(b, origlines) + command('normal! Gk5dd') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 4, 6, {}, false}) + end) + + it('when text is changed', function() + local b, tick = editoriginal(true) + + -- some normal text editing + command('normal! A555') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 1, {'original line 1555'}, false}) + command('normal! jj8X') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 2, 3, {'origin3'}, false}) + + -- modify multiple lines at once using visual block mode + tick = reopen(b, origlines) + command('normal! jjw') + sendkeys('<C-v>jjllx') + tick = tick + 1 + expectn('nvim_buf_lines_event', + {b, tick, 2, 5, {'original e 3', 'original e 4', 'original e 5'}, false}) + + -- replace part of a line line using :s + tick = reopen(b, origlines) + command('3s/line 3/foo/') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 2, 3, {'original foo'}, false}) + + -- replace parts of several lines line using :s + tick = reopen(b, origlines) + command('%s/line [35]/foo/') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 2, 5, {'original foo', + 'original line 4', + 'original foo'}, false}) + + -- type text into the first line of a blank file, one character at a time + command('enew!') + tick = 2 + expectn('nvim_buf_detach_event', {b}) + local bnew = nvim('get_current_buf') + ok(buffer('attach', bnew, true, {})) + expectn('nvim_buf_lines_event', {bnew, tick, 0, -1, {''}, false}) + sendkeys('i') + sendkeys('h') + sendkeys('e') + sendkeys('l') + sendkeys('l') + sendkeys('o\nworld') + expectn('nvim_buf_lines_event', {bnew, tick + 1, 0, 1, {'h'}, false}) + expectn('nvim_buf_lines_event', {bnew, tick + 2, 0, 1, {'he'}, false}) + expectn('nvim_buf_lines_event', {bnew, tick + 3, 0, 1, {'hel'}, false}) + expectn('nvim_buf_lines_event', {bnew, tick + 4, 0, 1, {'hell'}, false}) + expectn('nvim_buf_lines_event', {bnew, tick + 5, 0, 1, {'hello'}, false}) + expectn('nvim_buf_lines_event', {bnew, tick + 6, 0, 1, {'hello', ''}, false}) + expectn('nvim_buf_lines_event', {bnew, tick + 7, 1, 2, {'world'}, false}) + end) + + it('when lines are replaced', function() + local b, tick = editoriginal(true) + + -- blast away parts of some lines with visual mode + command('normal! jjwvjjllx') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 2, 3, {'original '}, false}) + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 3, 4, {}, false}) + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 3, 4, {'e 5'}, false}) + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 2, 3, {'original e 5'}, false}) + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 3, 4, {}, false}) + + -- blast away a few lines using :g + tick = reopen(b, origlines) + command('global/line [35]/delete') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 2, 3, {}, false}) + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 3, 4, {}, false}) + end) + + it('when lines are filtered', function() + -- Test filtering lines with !cat + local b, tick = editoriginal(true, {"A", "C", "E", "B", "D", "F"}) + + command('silent 2,5!cat') + -- the change comes through as two changes: + -- 1) addition of the new lines after the filtered lines + -- 2) removal of the original lines + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 5, 5, {"C", "E", "B", "D"}, false}) + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 1, 5, {}, false}) + end) + + it('when you use "o"', function() + local b, tick = editoriginal(true, {'AAA', 'BBB'}) + command('set noautoindent nosmartindent') + + -- use 'o' to start a new line from a line with no indent + command('normal! o') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 1, 1, {""}, false}) + + -- undo the change, indent line 1 a bit, and try again + command('undo') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 1, 2, {}, false}) + tick = tick + 1 + expectn('nvim_buf_changedtick_event', {b, tick}) + command('set autoindent') + command('normal! >>') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 1, {"\tAAA"}, false}) + command('normal! ommm') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 1, 1, {"\t"}, false}) + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 1, 2, {"\tmmm"}, false}) + + -- undo the change, and try again with 'O' + command('undo') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 1, 2, {'\t'}, false}) + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 1, 2, {}, false}) + tick = tick + 1 + expectn('nvim_buf_changedtick_event', {b, tick}) + command('normal! ggOmmm') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 0, {"\t"}, false}) + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 1, {"\tmmm"}, false}) + end) + + it('deactivates if the buffer is changed externally', function() + -- Test changing file from outside vim and reloading using :edit + local lines = {"Line 1", "Line 2"}; + local b, tick, filename = editoriginal(true, lines) + + command('normal! x') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 1, {'ine 1'}, false}) + command('undo') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 1, {'Line 1'}, false}) + tick = tick + 1 + expectn('nvim_buf_changedtick_event', {b, tick}) + + -- change the file directly + write_file(filename, "another line\n", true, true) + + -- reopen the file and watch buffer events shut down + command('edit') + expectn('nvim_buf_detach_event', {b}) + end) + + it('channel can watch many buffers at once', function() + -- edit 3 buffers, make sure they all have windows visible so that when we + -- move between buffers, none of them are unloaded + local b1, tick1 = editoriginal(true, {'A1', 'A2'}) + local b1nr = eval('bufnr("")') + command('split') + local b2, tick2 = open(true, {'B1', 'B2'}) + local b2nr = eval('bufnr("")') + command('split') + local b3, tick3 = open(true, {'C1', 'C2'}) + local b3nr = eval('bufnr("")') + + -- make a new window for moving between buffers + command('split') + + command('b'..b1nr) + command('normal! x') + tick1 = tick1 + 1 + expectn('nvim_buf_lines_event', {b1, tick1, 0, 1, {'1'}, false}) + command('undo') + tick1 = tick1 + 1 + expectn('nvim_buf_lines_event', {b1, tick1, 0, 1, {'A1'}, false}) + tick1 = tick1 + 1 + expectn('nvim_buf_changedtick_event', {b1, tick1}) + + command('b'..b2nr) + command('normal! x') + tick2 = tick2 + 1 + expectn('nvim_buf_lines_event', {b2, tick2, 0, 1, {'1'}, false}) + command('undo') + tick2 = tick2 + 1 + expectn('nvim_buf_lines_event', {b2, tick2, 0, 1, {'B1'}, false}) + tick2 = tick2 + 1 + expectn('nvim_buf_changedtick_event', {b2, tick2}) + + command('b'..b3nr) + command('normal! x') + tick3 = tick3 + 1 + expectn('nvim_buf_lines_event', {b3, tick3, 0, 1, {'1'}, false}) + command('undo') + tick3 = tick3 + 1 + expectn('nvim_buf_lines_event', {b3, tick3, 0, 1, {'C1'}, false}) + tick3 = tick3 + 1 + expectn('nvim_buf_changedtick_event', {b3, tick3}) + end) + + it('does not get confused if enabled/disabled many times', + function() + local channel = nvim('get_api_info')[1] + local b, tick = editoriginal(false) + + -- Enable buffer events many times. + ok(buffer('attach', b, true, {})) + ok(buffer('attach', b, true, {})) + ok(buffer('attach', b, true, {})) + ok(buffer('attach', b, true, {})) + ok(buffer('attach', b, true, {})) + expectn('nvim_buf_lines_event', {b, tick, 0, -1, origlines, false}) + eval('rpcnotify('..channel..', "Hello There")') + expectn('Hello There', {}) + + -- Disable buffer events many times. + ok(buffer('detach', b)) + ok(buffer('detach', b)) + ok(buffer('detach', b)) + ok(buffer('detach', b)) + ok(buffer('detach', b)) + expectn('nvim_buf_detach_event', {b}) + eval('rpcnotify('..channel..', "Hello Again")') + expectn('Hello Again', {}) + end) + + it('can notify several channels at once', function() + helpers.clear() + + -- create several new sessions, in addition to our main API + local sessions = {} + local pipe = helpers.new_pipename() + eval("serverstart('"..pipe.."')") + sessions[1] = helpers.connect(pipe) + sessions[2] = helpers.connect(pipe) + sessions[3] = helpers.connect(pipe) + + local function request(sessionnr, method, ...) + local status, rv = sessions[sessionnr]:request(method, ...) + if not status then + error(rv[2]) + end + return rv + end + + local function wantn(sessionid, name, args) + local session = sessions[sessionid] + eq({'notification', name, args}, session:next_message()) + end + + -- Edit a new file, but don't enable buffer events. + local lines = {'AAA', 'BBB'} + local b, tick = open(false, lines) + + -- Enable buffer events for sessions 1, 2 and 3. + ok(request(1, 'nvim_buf_attach', b, true, {})) + ok(request(2, 'nvim_buf_attach', b, true, {})) + ok(request(3, 'nvim_buf_attach', b, true, {})) + wantn(1, 'nvim_buf_lines_event', {b, tick, 0, -1, lines, false}) + wantn(2, 'nvim_buf_lines_event', {b, tick, 0, -1, lines, false}) + wantn(3, 'nvim_buf_lines_event', {b, tick, 0, -1, lines, false}) + + -- Change the buffer. + command('normal! x') + tick = tick + 1 + wantn(1, 'nvim_buf_lines_event', {b, tick, 0, 1, {'AA'}, false}) + wantn(2, 'nvim_buf_lines_event', {b, tick, 0, 1, {'AA'}, false}) + wantn(3, 'nvim_buf_lines_event', {b, tick, 0, 1, {'AA'}, false}) + + -- Stop watching on channel 1. + ok(request(1, 'nvim_buf_detach', b)) + wantn(1, 'nvim_buf_detach_event', {b}) + + -- Undo the change to buffer 1. + command('undo') + tick = tick + 1 + wantn(2, 'nvim_buf_lines_event', {b, tick, 0, 1, {'AAA'}, false}) + wantn(3, 'nvim_buf_lines_event', {b, tick, 0, 1, {'AAA'}, false}) + tick = tick + 1 + wantn(2, 'nvim_buf_changedtick_event', {b, tick}) + wantn(3, 'nvim_buf_changedtick_event', {b, tick}) + + -- make sure there are no other pending nvim_buf_lines_event messages going to + -- channel 1 + local channel1 = request(1, 'nvim_get_api_info')[1] + eval('rpcnotify('..channel1..', "Hello")') + wantn(1, 'Hello', {}) + + -- close the buffer and channels 2 and 3 should get a nvim_buf_detach_event + -- notification + command('edit') + wantn(2, 'nvim_buf_detach_event', {b}) + wantn(3, 'nvim_buf_detach_event', {b}) + + -- make sure there are no other pending nvim_buf_lines_event messages going to + -- channel 1 + channel1 = request(1, 'nvim_get_api_info')[1] + eval('rpcnotify('..channel1..', "Hello Again")') + wantn(1, 'Hello Again', {}) + end) + + it('works with :diffput and :diffget', function() + if os.getenv("APPVEYOR") then + pending("Fails on appveyor for some reason.", function() end) + end + + local b1, tick1 = editoriginal(true, {"AAA", "BBB"}) + local channel = nvim('get_api_info')[1] + command('diffthis') + command('rightbelow vsplit') + local b2, tick2 = open(true, {"BBB", "CCC"}) + command('diffthis') + -- go back to first buffer, and push the 'AAA' line to the second buffer + command('1wincmd w') + command('normal! gg') + command('diffput') + tick2 = tick2 + 1 + expectn('nvim_buf_lines_event', {b2, tick2, 0, 0, {"AAA"}, false}) + + -- use :diffget to grab the other change from buffer 2 + command('normal! G') + command('diffget') + tick1 = tick1 + 1 + expectn('nvim_buf_lines_event', {b1, tick1, 2, 2, {"CCC"}, false}) + + eval('rpcnotify('..channel..', "Goodbye")') + expectn('Goodbye', {}) + end) + + it('works with :sort', function() + -- test for :sort + local b, tick = editoriginal(true, {"B", "D", "C", "A", "E"}) + command('%sort') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 5, {"A", "B", "C", "D", "E"}, false}) + end) + + it('works with :left', function() + local b, tick = editoriginal(true, {" A", " B", "B", "\tB", "\t\tC"}) + command('2,4left') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 1, 4, {"B", "B", "B"}, false}) + end) + + it('works with :right', function() + local b, tick = editoriginal(true, {" A", + "\t B", + "\t \tBB", + " \tB", + "\t\tC"}) + command('set ts=2 et') + command('2,4retab') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 1, 4, {" B", " BB", " B"}, false}) + end) + + it('works with :move', function() + local b, tick = editoriginal(true, origlines) + -- move text down towards the end of the file + command('2,3move 4') + tick = tick + 2 + expectn('nvim_buf_lines_event', {b, tick, 4, 4, {"original line 2", + "original line 3"}, false}) + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 1, 3, {}, false}) + + -- move text up towards the start of the file + tick = reopen(b, origlines) + command('4,5move 2') + tick = tick + 2 + expectn('nvim_buf_lines_event', {b, tick, 2, 2, {"original line 4", + "original line 5"}, false}) + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 5, 7, {}, false}) + end) + + it('when you manually add/remove folds', function() + local b = editoriginal(true) + local tick = reopenwithfolds(b) + + -- delete the inner fold + command('normal! zR3Gzd') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 1, 4, {'original line 2', + 'original line 3', + 'original line 4'}, false}) + -- delete the outer fold + command('normal! zd') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 6, origlines, false}) + + -- discard changes and put the folds back + tick = reopenwithfolds(b) + + -- remove both folds at once + command('normal! ggzczD') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 6, origlines, false}) + + -- discard changes and put the folds back + tick = reopenwithfolds(b) + + -- now delete all folds at once + command('normal! zE') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 6, origlines, false}) + + -- create a fold from line 4 to the end of the file + command('normal! 4GA/*{{{*/') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 3, 4, {'original line 4/*{{{*/'}, false}) + + -- delete the fold which only has one marker + command('normal! Gzd') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 3, 6, {'original line 4', + 'original line 5', + 'original line 6'}, false}) + end) + + it('detaches if the buffer is closed', function() + local b, tick = editoriginal(true, {'AAA'}) + local channel = nvim('get_api_info')[1] + + -- Test that buffer events are working. + command('normal! x') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 1, {'AA'}, false}) + command('undo') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 1, {'AAA'}, false}) + tick = tick + 1 + expectn('nvim_buf_changedtick_event', {b, tick}) + + -- close our buffer by creating a new one + command('enew') + expectn('nvim_buf_detach_event', {b}) + + -- Reopen the original buffer, make sure there are no buffer events sent. + command('b1') + command('normal! x') + + eval('rpcnotify('..channel..', "Hello There")') + expectn('Hello There', {}) + end) + + it('stays attached if the buffer is hidden', function() + local b, tick = editoriginal(true, {'AAA'}) + local channel = nvim('get_api_info')[1] + + -- Test that buffer events are working. + command('normal! x') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 1, {'AA'}, false}) + command('undo') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 1, {'AAA'}, false}) + tick = tick + 1 + expectn('nvim_buf_changedtick_event', {b, tick}) + + -- Close our buffer by creating a new one. + command('set hidden') + command('enew') + + -- Assert that no nvim_buf_detach_event is sent. + eval('rpcnotify('..channel..', "Hello There")') + expectn('Hello There', {}) + + -- Reopen the original buffer, assert that buffer events are still active. + command('b1') + command('normal! x') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 1, {'AA'}, false}) + end) + + it('detaches if the buffer is unloaded/deleted/wiped', + function() + -- start with a blank nvim + helpers.clear() + -- need to make a new window with a buffer because :bunload doesn't let you + -- unload the last buffer + for _, cmd in ipairs({'bunload', 'bdelete', 'bwipeout'}) do + command('new') + -- open a brand spanking new file + local b = open(true, {'AAA'}) + + -- call :bunload or whatever the command is, and then check that we + -- receive a nvim_buf_detach_event + command(cmd) + expectn('nvim_buf_detach_event', {b}) + end + end) + + it('does not send the buffer content if not requested', function() + helpers.clear() + local b, tick = editoriginal(false) + ok(buffer('attach', b, false, {})) + expectn('nvim_buf_changedtick_event', {b, tick}) + end) + + it('returns a proper error on nonempty options dict', function() + helpers.clear() + local b = editoriginal(false) + expect_err("dict isn't empty", buffer, 'attach', b, false, {builtin="asfd"}) + end) + +end) diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index fed53a3dfd..76bf338d97 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -5,7 +5,7 @@ local eq, eval = helpers.eq, helpers.eval local command = helpers.command local meths = helpers.meths -describe('highlight api',function() +describe('API: highlight',function() local expected_rgb = { background = Screen.colors.Yellow, foreground = Screen.colors.Red, diff --git a/test/functional/api/rpc_fixture.lua b/test/functional/api/rpc_fixture.lua index 423864740f..e885a525af 100644 --- a/test/functional/api/rpc_fixture.lua +++ b/test/functional/api/rpc_fixture.lua @@ -31,7 +31,7 @@ end local function on_notification(event, args) if event == 'ping' and #args == 0 then - session:notify("vim_eval", "rpcnotify(g:channel, 'pong')") + session:notify("nvim_eval", "rpcnotify(g:channel, 'pong')") end end diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua index e79a60fb10..856e5ca4d2 100644 --- a/test/functional/api/server_requests_spec.lua +++ b/test/functional/api/server_requests_spec.lua @@ -222,7 +222,7 @@ describe('server -> client', function() end) it('returns an error if the request failed', function() - expect_err('Vim:Invalid method name', + expect_err('Vim:Invalid method: does%-not%-exist', eval, "rpcrequest(vim, 'does-not-exist')") end) end) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 3a686c84e7..e4b343c123 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -20,9 +20,20 @@ local format_string = global_helpers.format_string local intchar2lua = global_helpers.intchar2lua local mergedicts_copy = global_helpers.mergedicts_copy -describe('api', function() +describe('API', function() before_each(clear) + it('validates requests', function() + expect_err('Invalid method: bogus', + request, 'bogus') + expect_err('Invalid method: … の り 。…', + request, '… の り 。…') + expect_err('Invalid method: <empty>', + request, '') + expect_err("can't serialize object", + request, nil) + end) + describe('nvim_command', function() it('works', function() local fname = helpers.tmpname() @@ -924,7 +935,7 @@ describe('api', function() {'i_am_not_a_method', {'xx'}}, {'nvim_set_var', {'avar', 10}}, } - eq({{}, {0, error_types.Exception.id, 'Invalid method name'}}, + eq({{}, {0, error_types.Exception.id, 'Invalid method: i_am_not_a_method'}}, meths.call_atomic(req)) eq(5, meths.get_var('avar')) end) diff --git a/test/functional/eval/setpos_spec.lua b/test/functional/eval/setpos_spec.lua index 6a8b3a8732..935f387bcc 100644 --- a/test/functional/eval/setpos_spec.lua +++ b/test/functional/eval/setpos_spec.lua @@ -27,9 +27,8 @@ describe('setpos() function', function() eq(getpos("."), {0, 2, 1, 0}) setpos(".", {2, 1, 1, 0}) eq(getpos("."), {0, 1, 1, 0}) - -- Ensure get an error attempting to set position to another buffer local ret = exc_exec('call setpos(".", [1, 1, 1, 0])') - eq('Vim(call):E474: Invalid argument', ret) + eq(0, ret) end) it('can set lowercase marks in the current buffer', function() setpos("'d", {0, 2, 1, 0}) diff --git a/test/functional/ex_cmds/debug_spec.lua b/test/functional/ex_cmds/debug_spec.lua new file mode 100644 index 0000000000..5dad8098ea --- /dev/null +++ b/test/functional/ex_cmds/debug_spec.lua @@ -0,0 +1,110 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local feed = helpers.feed +local clear = helpers.clear + +describe(':debug', function() + local screen + before_each(function() + clear() + screen = Screen.new(50, 14) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {bold = true, reverse = true}, + [3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [4] = {bold = true, foreground = Screen.colors.SeaGreen4}, + }) + screen:attach() + end) + it('scrolls messages correctly', function() + feed(':echoerr bork<cr>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + {3:E121: Undefined variable: bork} | + {3:E15: Invalid expression: bork} | + {4:Press ENTER or type command to continue}^ | + ]]) + + feed(':debug echo "aa"| echo "bb"<cr>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + {3:E121: Undefined variable: bork} | + {3:E15: Invalid expression: bork} | + Entering Debug mode. Type "cont" to continue. | + cmd: echo "aa"| echo "bb" | + >^ | + ]]) + + feed('step<cr>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + {3:E121: Undefined variable: bork} | + {3:E15: Invalid expression: bork} | + Entering Debug mode. Type "cont" to continue. | + cmd: echo "aa"| echo "bb" | + >step | + aa | + cmd: echo "bb" | + >^ | + ]]) + + feed('step<cr>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {2: }| + {3:E121: Undefined variable: bork} | + {3:E15: Invalid expression: bork} | + Entering Debug mode. Type "cont" to continue. | + cmd: echo "aa"| echo "bb" | + >step | + aa | + cmd: echo "bb" | + >step | + bb | + {4:Press ENTER or type command to continue}^ | + ]]) + + feed('<cr>') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) +end) diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua index 381461dc4f..10703465aa 100644 --- a/test/functional/legacy/assert_spec.lua +++ b/test/functional/legacy/assert_spec.lua @@ -77,6 +77,11 @@ describe('assert function:', function() eq('Vim(call):E724: unable to correctly dump variable with self-referencing container', exc_exec('call CheckAssert()')) end) + + it('can specify a message and get a message about what failed', function() + call('assert_equal', 'foo', 'bar', 'testing') + expected_errors({"testing: Expected 'foo' but got 'bar'"}) + end) end) -- assert_notequal({expected}, {actual}[, {msg}]) @@ -164,10 +169,10 @@ describe('assert function:', function() call assert_true('', 'file two') ]]) expected_errors({ - tmpname_one .. " line 1: 'equal assertion failed'", - tmpname_one .. " line 2: 'true assertion failed'", - tmpname_one .. " line 3: 'false assertion failed'", - tmpname_two .. " line 1: 'file two'", + tmpname_one .. " line 1: equal assertion failed: Expected 1 but got 100", + tmpname_one .. " line 2: true assertion failed: Expected False but got 'true'", + tmpname_one .. " line 3: false assertion failed: Expected True but got 'false'", + tmpname_two .. " line 1: file two: Expected True but got ''", }) end) @@ -198,7 +203,7 @@ describe('assert function:', function() it('should set v:errors to msg when given and match fails', function() call('assert_match', 'bar.*foo', 'foobar', 'wrong') - expected_errors({"'wrong'"}) + expected_errors({"wrong: Pattern 'bar.*foo' does not match 'foobar'"}) end) end) diff --git a/test/functional/provider/nodejs_spec.lua b/test/functional/provider/nodejs_spec.lua index f69c3e7c78..07a00f8a8c 100644 --- a/test/functional/provider/nodejs_spec.lua +++ b/test/functional/provider/nodejs_spec.lua @@ -16,7 +16,6 @@ end before_each(function() clear() - command([[let $NODE_PATH = get(split(system('npm root -g'), "\n"), 0, '')]]) end) describe('nodejs host', function() @@ -28,21 +27,18 @@ describe('nodejs host', function() it('works', function() local fname = 'Xtest-nodejs-hello.js' write_file(fname, [[ - const socket = process.env.NVIM_LISTEN_ADDRESS; const neovim = require('neovim'); - const nvim = neovim.attach({socket: socket}); + const nvim = neovim.attach({socket: process.env.NVIM_LISTEN_ADDRESS}); nvim.command('let g:job_out = "hello"'); - nvim.command('call jobstop(g:job_id)'); ]]) command('let g:job_id = jobstart(["node", "'..fname..'"])') - retry(nil, 2000, function() eq('hello', eval('g:job_out')) end) + retry(nil, 3000, function() eq('hello', eval('g:job_out')) end) end) it('plugin works', function() local fname = 'Xtest-nodejs-hello-plugin.js' write_file(fname, [[ - const socket = process.env.NVIM_LISTEN_ADDRESS; const neovim = require('neovim'); - const nvim = neovim.attach({socket: socket}); + const nvim = neovim.attach({socket: process.env.NVIM_LISTEN_ADDRESS}); class TestPlugin { hello() { @@ -54,6 +50,6 @@ describe('nodejs host', function() plugin.instance.hello(); ]]) command('let g:job_id = jobstart(["node", "'..fname..'"])') - retry(nil, 2000, function() eq('hello-plugin', eval('g:job_out')) end) + retry(nil, 3000, function() eq('hello-plugin', eval('g:job_out')) end) end) end) diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index 4f22f7385d..f98add41a0 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -47,6 +47,16 @@ describe(':terminal', function() ]]) end) + it("reads output buffer on terminal reporting #4151", function() + if helpers.pending_win32(pending) then return end + if iswin() then + feed_command([[terminal powershell -NoProfile -NoLogo -Command Write-Host -NoNewline "\"$([char]27)[6n\""; Start-Sleep -Milliseconds 500 ]]) + else + feed_command([[terminal printf '\e[6n'; sleep 0.5 ]]) + end + screen:expect('%^%[%[1;1R', nil, nil, nil, true) + end) + it("in normal-mode :split does not move cursor", function() if iswin() then feed_command([[terminal for /L \\%I in (1,0,2) do ( echo foo & ping -w 100 -n 1 127.0.0.1 > nul )]]) diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index 41c290a462..5ce49822e5 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -22,6 +22,8 @@ describe('external cmdline', function() [1] = {bold = true, foreground = Screen.colors.Blue1}, [2] = {reverse = true}, [3] = {bold = true, reverse = true}, + [4] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [5] = {bold = true, foreground = Screen.colors.SeaGreen4}, }) screen:set_on_event_handler(function(name, data) if name == "cmdline_show" then @@ -157,24 +159,87 @@ describe('external cmdline', function() end) end) - it("redraws statusline on entering", function() - command('set laststatus=2') - command('set statusline=%{mode()}') - feed(':') - screen:expect([[ - | - {1:~ }| - {1:~ }| - {3:c^ }| - | - ]], nil, nil, function() - eq({{ - content = { { {}, "" } }, - firstc = ":", - indent = 0, - pos = 0, - prompt = "" - }}, cmdline) + describe("redraws statusline on entering", function() + before_each(function() + command('set laststatus=2') + command('set statusline=%{mode()}') + end) + + it('from normal mode', function() + feed(':') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {3:c^ }| + | + ]], nil, nil, function() + eq({{ + content = { { {}, "" } }, + firstc = ":", + indent = 0, + pos = 0, + prompt = "" + }}, cmdline) + end) + end) + + it('but not with scrolled messages', function() + screen:try_resize(50,10) + feed(':echoerr doesnotexist<cr>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {3: }| + {4:E121: Undefined variable: doesnotexist} | + {4:E15: Invalid expression: doesnotexist} | + {5:Press ENTER or type command to continue}^ | + ]]) + feed(':echoerr doesnotexist<cr>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {3: }| + {4:E121: Undefined variable: doesnotexist} | + {4:E15: Invalid expression: doesnotexist} | + {4:E121: Undefined variable: doesnotexist} | + {4:E15: Invalid expression: doesnotexist} | + {5:Press ENTER or type command to continue}^ | + ]]) + + feed(':echoerr doesnotexist<cr>') + screen:expect([[ + | + {1:~ }| + {3: }| + {4:E121: Undefined variable: doesnotexist} | + {4:E15: Invalid expression: doesnotexist} | + {4:E121: Undefined variable: doesnotexist} | + {4:E15: Invalid expression: doesnotexist} | + {4:E121: Undefined variable: doesnotexist} | + {4:E15: Invalid expression: doesnotexist} | + {5:Press ENTER or type command to continue}^ | + ]]) + + feed('<cr>') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {3:n }| + | + ]]) end) end) diff --git a/test/helpers.lua b/test/helpers.lua index 0d3fe1316b..a774a67df3 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -259,7 +259,7 @@ local function check_cores(app, force) else initial_path = '.' re = '/core[^/]*$' - exc_re = { '^/%.deps$', local_tmpdir } + exc_re = { '^/%.deps$', local_tmpdir, '^/%node_modules$' } db_cmd = gdb_db_cmd random_skip = true end diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index d3843e302d..8aae8ea9e4 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -45,8 +45,10 @@ option(USE_BUNDLED_LUA "Use the bundled version of lua." OFF) if(USE_BUNDLED AND MSVC) option(USE_BUNDLED_GETTEXT "Use the bundled version of gettext." ON) + option(USE_BUNDLED_LIBICONV "Use the bundled version of libiconv." ON) else() option(USE_BUNDLED_GETTEXT "Use the bundled version of gettext." OFF) + option(USE_BUNDLED_LIBICONV "Use the bundled version of libiconv." OFF) endif() option(USE_EXISTING_SRC_DIR "Skip download of deps sources in case of existing source directory." OFF) @@ -157,7 +159,6 @@ set(WINGUI_SHA256 260efc686423e2529360b6a45c8e241fbbf276c8de6b04d44f45ab5b6fe8df set(WIN32YANK_X86_URL https://github.com/equalsraf/win32yank/releases/download/v0.0.4/win32yank-x86.zip) set(WIN32YANK_X86_SHA256 62f34e5a46c5d4a7b3f3b512e1ff7b77fedd432f42581cbe825233a996eed62c) - set(WIN32YANK_X86_64_URL https://github.com/equalsraf/win32yank/releases/download/v0.0.4/win32yank-x64.zip) set(WIN32YANK_X86_64_SHA256 33a747a92da60fb65e668edbf7661d3d902411a2d545fe9dc08623cecd142a20) @@ -167,6 +168,9 @@ set(WINPTY_SHA256 35a48ece2ff4acdcbc8299d4920de53eb86b1fb41e64d2fe5ae7898931bcee set(GETTEXT_URL https://ftp.gnu.org/pub/gnu/gettext/gettext-0.19.8.1.tar.gz) set(GETTEXT_SHA256 ff942af0e438ced4a8b0ea4b0b6e0d6d657157c5e2364de57baa279c1c125c43) +set(LIBICONV_URL https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.15.tar.gz) +set(LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178) + if(USE_BUNDLED_UNIBILIUM) include(BuildUnibilium) endif() @@ -215,6 +219,10 @@ if(USE_BUNDLED_GETTEXT) include(BuildGettext) endif() +if(USE_BUNDLED_LIBICONV) + include(BuildLibiconv) +endif() + include(GetBinaryDeps) if(WIN32) diff --git a/third-party/cmake/BuildGettext.cmake b/third-party/cmake/BuildGettext.cmake index 1dd206a91b..45264167a5 100644 --- a/third-party/cmake/BuildGettext.cmake +++ b/third-party/cmake/BuildGettext.cmake @@ -15,15 +15,18 @@ if(MSVC) PATCH_COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/gettext init COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/gettext apply --ignore-whitespace ${CMAKE_CURRENT_SOURCE_DIR}/patches/gettext-Fix-compilation-on-a-system-without-alloca.patch + ${CMAKE_CURRENT_SOURCE_DIR}/patches/gettext-Fix-building-with-MSVC.patch CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/cmake/GettextCMakeLists.txt - ${DEPS_BUILD_DIR}/src/gettext/gettext-runtime/CMakeLists.txt - COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/gettext/gettext-runtime + ${DEPS_BUILD_DIR}/src/gettext/CMakeLists.txt + COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/gettext -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} # Pass toolchain -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DLIBICONV_INCLUDE_DIRS=${DEPS_INSTALL_DIR}/include + -DLIBICONV_LIBRARIES=${DEPS_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}libcharset${CMAKE_STATIC_LIBRARY_SUFFIX}$<SEMICOLON>${DEPS_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}libiconv${CMAKE_STATIC_LIBRARY_SUFFIX} BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) @@ -32,3 +35,6 @@ else() endif() list(APPEND THIRD_PARTY_DEPS gettext) +if(USE_BUNDLED_LIBICONV) + add_dependencies(gettext libiconv) +endif() diff --git a/third-party/cmake/BuildLibiconv.cmake b/third-party/cmake/BuildLibiconv.cmake new file mode 100644 index 0000000000..dc3d8fe4c3 --- /dev/null +++ b/third-party/cmake/BuildLibiconv.cmake @@ -0,0 +1,31 @@ +if(MSVC) + + ExternalProject_Add(libiconv + PREFIX ${DEPS_BUILD_DIR} + URL ${LIBICONV_URL} + DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/libiconv + DOWNLOAD_COMMAND ${CMAKE_COMMAND} + -DPREFIX=${DEPS_BUILD_DIR} + -DDOWNLOAD_DIR=${DEPS_DOWNLOAD_DIR}/libiconv + -DURL=${LIBICONV_URL} + -DEXPECTED_SHA256=${LIBICONV_SHA256} + -DTARGET=libiconv + -DUSE_EXISTING_SRC_DIR=${USE_EXISTING_SRC_DIR} + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/DownloadAndExtractFile.cmake + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/LibiconvCMakeLists.txt + ${DEPS_BUILD_DIR}/src/libiconv/CMakeLists.txt + COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/libiconv + -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} + # Pass toolchain + -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} + INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) + +else() + message(FATAL_ERROR "Trying to build libiconv in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}") +endif() + +list(APPEND THIRD_PARTY_DEPS libiconv) diff --git a/third-party/cmake/GettextCMakeLists.txt b/third-party/cmake/GettextCMakeLists.txt index 60ee3b6f99..67ec0d113f 100644 --- a/third-party/cmake/GettextCMakeLists.txt +++ b/third-party/cmake/GettextCMakeLists.txt @@ -1,9 +1,12 @@ cmake_minimum_required(VERSION 2.8.7) -project(libintl C) +project(gettext C) -include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) +# Adds PREFIX to each item in LIST +macro(PREFIX_LIST_ITEMS LIST PREFIX) + string(REPLACE ";" ";${PREFIX}" ${LIST} ";${${LIST}}") +endmacro() -file(READ config.h.in CONFIG_CONTENT) +file(READ gettext-runtime/config.h.in CONFIG_CONTENT) string(REPLACE "#undef HAVE_GETCWD" "#define HAVE_GETCWD 1" CONFIG_CONTENT ${CONFIG_CONTENT}) string(REPLACE "#undef uintmax_t" " #if _WIN64 @@ -14,70 +17,255 @@ string(REPLACE "#undef uintmax_t" " # define uintmax_t unsigned long #endif" CONFIG_CONTENT ${CONFIG_CONTENT}) -file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/config.h ${CONFIG_CONTENT}) +file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/gettext-runtime/config.h ${CONFIG_CONTENT}) set(HAVE_NEWLOCALE 0) set(HAVE_POSIX_PRINTF 0) set(HAVE_SNPRINTF 0) set(HAVE_ASPRINTF 0) set(HAVE_WPRINTF 0) -configure_file(intl/libgnuintl.in.h libgnuintl.h) +configure_file(gettext-runtime/intl/libgnuintl.in.h + ${CMAKE_CURRENT_BINARY_DIR}/gettext-runtime/intl/libgnuintl.h) set(LOCALDIR "gettext") +set(LIBDIR "gettext") +set(PKGDATADIR "gettext") +set(PACKAGE_SUFFIX "gettext") add_definitions(-DLOCALEDIR=\"${LOCALDIR}\" -DLOCALE_ALIAS_PATH=\"${LOCALDIR}\" -DLIBDIR=\"${LOCALDIR}\" - -DINSTALLDIR=\"${LOCALDIR}\") - -add_definitions(-DBUILDING_LIBINTL - -DIN_LIBINTL - -DENABLE_RELOCATABLE=1 - -DIN_LIBRARY - -DNO_XMALLOC - -Dset_relocation_prefix=libintl_set_relocation_prefix - -Drelocate=libintl_relocate - -DHAVE_CONFIG_H - -D_CRT_SECURE_NO_WARNINGS) - -FILE(GLOB SOURCES - intl/bindtextdom.c - intl/dcgettext.c - intl/dcigettext.c - intl/dcngettext.c - intl/dgettext.c - intl/dngettext.c - intl/explodename.c - intl/finddomain.c - intl/gettext.c - intl/hash-string.c - intl/l10nflist.c - intl/langprefs.c - intl/loadmsgcat.c - intl/localcharset.c - intl/localealias.c - intl/localename.c - intl/lock.c - intl/log.c - intl/ngettext.c - intl/plural-exp.c - intl/plural.c - intl/printf.c - intl/relocatable.c - intl/setlocale.c - intl/textdomain.c - intl/threadlib.c - intl/version.c) - -add_library(libintl ${SOURCES}) + -DINSTALLDIR=\"${LOCALDIR}\" + -DEXEEXT=\".exe\" + -DLOCALEDIR=\"${LOCALDIR}\" + -DLIBDIR=\"${LIBDIR}\" + -DPACKAGE_SUFFIX=\"${PACKAGE_SUFFIX}\" + -DGETTEXTDATADIR=\"${PKGDATADIR}\" + -DBISON_LOCALEDIR=\"${LOCALDIR}\" + -DHAVE_CONFIG_H) + +set(libintl_SOURCES + bindtextdom.c dcgettext.c dcigettext.c dcngettext.c dgettext.c dngettext.c + explodename.c finddomain.c gettext.c hash-string.c l10nflist.c langprefs.c + loadmsgcat.c localcharset.c localealias.c localename.c lock.c log.c ngettext.c + plural-exp.c plural.c printf.c relocatable.c setlocale.c textdomain.c + threadlib.c version.c) +PREFIX_LIST_ITEMS(libintl_SOURCES "gettext-runtime/intl/") + +add_library(libintl ${libintl_SOURCES}) +set_property(TARGET libintl APPEND PROPERTY INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_BINARY_DIR}/gettext-runtime + ${CMAKE_CURRENT_BINARY_DIR}/gettext-runtime/intl) +set_property(TARGET libintl APPEND PROPERTY COMPILE_DEFINITIONS + BUILDING_LIBINTL + IN_LIBINTL + ENABLE_RELOCATABLE=1 + IN_LIBRARY + NO_XMALLOC + set_relocation_prefix=libintl_set_relocation_prefix + relocate=libintl_relocate + HAVE_CONFIG_H + _CRT_SECURE_NO_WARNINGS) + + +file(READ gettext-tools/config.h.in CONFIG_CONTENT) +string(REPLACE "#undef ENDIANNESS" "#define ENDIANNESS 0" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef GNULIB_FWRITEERROR" "#define GNULIB_FWRITEERROR 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef HAVE_DECL_STRERROR_R" "#define HAVE_DECL_STRERROR_R 0" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef HAVE_DUP2" "#define HAVE_DUP2 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef HAVE_LIBUNISTRING" "#define HAVE_LIBUNISTRING 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef HAVE_STDINT_H_WITH_UINTMAX" "#define HAVE_STDINT_H_WITH_UINTMAX 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef HAVE_STDINT_H" "#define HAVE_STDINT_H 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef HAVE_STRING_H" "#define HAVE_STRING_H 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef HAVE_SYS_TIMEB_H" "#define HAVE_SYS_TIMEB_H 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef HAVE__FTIME" "#define HAVE__FTIME 1" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef ICONV_CONST" "#define ICONV_CONST const" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef PACKAGE" "#define PACKAGE \"gettext\"\n#define gettext_VERSION" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef VERSION" "#define VERSION \"\"" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef mode_t" "#define mode_t int" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef pid_t" "#define pid_t int" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef restrict" "#define restrict __restrict" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef ssize_t" "#include <BaseTsd.h>\n#define ssize_t SSIZE_T" CONFIG_CONTENT ${CONFIG_CONTENT}) +string(REPLACE "#undef uid_t" "#define uid_t int" CONFIG_CONTENT ${CONFIG_CONTENT}) +file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/gettext-tools/config.h ${CONFIG_CONTENT}) + +set(libgettextsrc_COMMON_SOURCE + message.c po-error.c po-xerror.c read-catalog-abstract.c po-lex.c + po-gram-gen.c po-charset.c read-po.c read-properties.c read-stringtable.c + open-catalog.c dir-list.c + str-list.c) + +set(libgettextsrc_FORMAT_SOURCE + format.c format-invalid.h format-c.c format-c-parse.h format-sh.c + format-python.c format-python-brace.c format-lisp.c format-elisp.c + format-librep.c format-scheme.c format-java.c format-csharp.c format-awk.c + format-pascal.c format-ycp.c format-tcl.c format-perl.c format-perl-brace.c + format-php.c format-gcc-internal.c format-gfc-internal.c format-qt.c + format-qt-plural.c format-kde.c format-kde-kuit.c format-boost.c format-lua.c + format-javascript.c) + +set(libgettextsrc_SOURCES + ${libgettextsrc_COMMON_SOURCE} read-catalog.c color.c write-catalog.c + write-properties.c write-stringtable.c write-po.c msgl-ascii.c msgl-iconv.c + msgl-equal.c msgl-cat.c msgl-header.c msgl-english.c msgl-check.c file-list.c + msgl-charset.c po-time.c plural-exp.c plural-eval.c plural-count.c + plural-table.c quote.h sentence.h sentence.c ${libgettextsrc_FORMAT_SOURCE} + read-desktop.c locating-rule.c its.c search-path.c) +PREFIX_LIST_ITEMS(libgettextsrc_SOURCES "gettext-tools/src/") + +set(GLIBC_SOURCE + uniname/uniname.c javaexec.c unsetenv.c classpath.c setenv.c xsetenv.c + sh-quote.c execute.c javaversion.c csharpcomp.c csharpexec.c javacomp.c + gettimeofday.c getdtablesize.c fcntl.c dup-safer-flag.c cloexec.c + fd-safer-flag.c fd-safer.c pipe2.c pipe2-safer.c spawn-pipe.c xmemdup0.c + secure_getenv.c tmpdir.c tempname.c mkdtemp.c fnmatch.c clean-temp.c + gl_array_list.c tputs.c wait-process.c waitpid.c getdelim.c getline.c + sigprocmask.c sigaction.c addext.c argmatch.c backupfile.c basename.c + c-strcasecmp.c c-strncasecmp.c c-strstr.c closeout.c concat-filename.c + error-progname.c error.c exitfail.c file-ostream.c fstrcmp.c full-write.c + fwriteerror.c getopt.c getopt1.c hash.c libxml/buf.c localcharset.c malloca.c + mbchar.c mbslen.c mbsstr.c mbswidth.c obstack.c ostream.c html-ostream.c + fd-ostream.c styled-ostream.c progname.c html-styled-ostream.c printf-args.c + printf-parse.c propername.c quotearg.c rawmemchr.c safe-read.c safe-write.c + stpcpy.c stpncpy.c strchrnul.c striconveh.c striconveha.c strnlen1.c + term-ostream.c term-styled-ostream.c tparm.c trim.c gcd.c gl_linkedhash_list.c + uniconv/u8-conv-from-enc.c unictype/ctype_space.c unilbrk/lbrktables.c + unilbrk/u8-possible-linebreaks.c unilbrk/u8-width-linebreaks.c + unilbrk/ulc-common.c unilbrk/ulc-width-linebreaks.c unistr/u16-mbtouc-aux.c + unistr/u16-mbtouc.c unistr/u8-check.c unistr/u8-mblen.c unistr/u8-mbtouc-aux.c + unistr/u8-mbtouc-unsafe-aux.c unistr/u8-mbtouc-unsafe.c unistr/u8-mbtouc.c + unistr/u8-mbtoucr.c unistr/u8-prev.c unistr/u8-uctomb-aux.c unistr/u8-uctomb.c + uniwidth/width.c vasnprintf.c vasprintf.c wcwidth.c xasprintf.c + xconcat-filename.c xerror.c xmalloc.c xstrdup.c xvasprintf.c glib/ghash.c + glib/glist.c glib/gmessages.c glib/gprimes.c glib/gstrfuncs.c glib/gstring.c + libcroco/cr-additional-sel.c libcroco/cr-attr-sel.c libcroco/cr-cascade.c + libcroco/cr-declaration.c libcroco/cr-doc-handler.c libcroco/cr-enc-handler.c + libcroco/cr-fonts.c libcroco/cr-input.c libcroco/cr-num.c + libcroco/cr-om-parser.c libcroco/cr-parser.c libcroco/cr-parsing-location.c + libcroco/cr-prop-list.c libcroco/cr-pseudo.c libcroco/cr-rgb.c + libcroco/cr-sel-eng.c libcroco/cr-selector.c libcroco/cr-simple-sel.c + libcroco/cr-statement.c libcroco/cr-string.c libcroco/cr-style.c + libcroco/cr-stylesheet.c libcroco/cr-term.c libcroco/cr-tknzr.c + libcroco/cr-token.c libcroco/cr-utils.c libxml/DOCBparser.c + libxml/HTMLparser.c libxml/HTMLtree.c libxml/SAX.c libxml/SAX2.c libxml/c14n.c + libxml/catalog.c libxml/chvalid.c libxml/debugXML.c libxml/dict.c + libxml/encoding.c libxml/entities.c libxml/error.c libxml/globals.c + libxml/hash.c libxml/legacy.c libxml/list.c libxml/nanoftp.c libxml/nanohttp.c + libxml/parser.c libxml/parserInternals.c libxml/pattern.c libxml/relaxng.c + libxml/schematron.c libxml/threads.c libxml/tree.c libxml/trionan.c + libxml/uri.c libxml/valid.c libxml/xinclude.c libxml/xlink.c libxml/xmlIO.c + libxml/xmlmemory.c libxml/xmlmodule.c libxml/xmlreader.c libxml/xmlregexp.c + libxml/xmlsave.c libxml/xmlschemas.c libxml/xmlschemastypes.c + libxml/xmlstring.c libxml/xmlunicode.c libxml/xmlwriter.c libxml/xpath.c + libxml/xpointer.c fatal-signal.c copy-file.c) +PREFIX_LIST_ITEMS(GLIBC_SOURCE "gettext-tools/gnulib-lib/") + +set(libgettextsrc_SOURCES ${libgettextsrc_SOURCES} ${GLIBC_SOURCE}) + +set(HEADER_TEMPLATES_PATH "gettext-tools/gnulib-lib") +set(HEADER_TEMPLATES_ABS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${HEADER_TEMPLATES_PATH}") +file(GLOB_RECURSE HEADER_TEMPLATES "${HEADER_TEMPLATES_ABS_PATH}/*.in.h") +list(REMOVE_ITEM HEADER_TEMPLATES "${HEADER_TEMPLATES_ABS_PATH}/stdint.in.h") +list(REMOVE_ITEM HEADER_TEMPLATES "${HEADER_TEMPLATES_ABS_PATH}/wchar.in.h") +foreach(HEADER_TEMPLATE ${HEADER_TEMPLATES}) + file(READ ${HEADER_TEMPLATE} HEADER_CONTENT) + string(REPLACE "/* The definition of _GL_ARG_NONNULL is copied here. */" "#include \"arg-nonnull.h\"" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "/* The definition of _GL_WARN_ON_USE is copied here. */" "#include \"warn-on-use.h\"" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "/* The definitions of _GL_FUNCDECL_RPL etc. are copied here. */" "#include \"c++defs.h\"" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@GNULIB_LSTAT@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@GNULIB_MBSINIT@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@GNULIB_SIGACTION@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@GNULIB_SIGPROCMASK@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@GNULIB_STPCPY@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@GNULIB_STPNCPY@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@GNULIB_STRCHRNUL@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@HAVE_ISWCNTRL@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@HAVE_WCTYPE_T@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@PRAGMA_COLUMNS@" "" HEADER_CONTENT "${HEADER_CONTENT}") + + string(REGEX REPLACE "^${HEADER_TEMPLATES_ABS_PATH}/" "" HEADER_PATH "${HEADER_TEMPLATE}") + string(REPLACE ".in" "" HEADER_PATH ${HEADER_PATH}) + string(REPLACE "_" "/" HEADER_PATH ${HEADER_PATH}) + # find_file will create a cache entry for the variable + # SYSTEM_HEADER, so reset it before each call + set(SYSTEM_HEADER "SYSTEM_HEADER-NOTFOUND") + find_file(SYSTEM_HEADER ${HEADER_PATH} PATHS "${LIBICONV_INCLUDE_DIRS}") + if(SYSTEM_HEADER) + # Gnulib uses #include_next to extend system header files, + # but MSVC doesn't support it, so a regular include directive + # with a relative path is used instead + string(REGEX REPLACE ".*/(.*/${HEADER_PATH})" "../\\1" + INCLUDE_PATH "${SYSTEM_HEADER}") + string(REGEX REPLACE "@INCLUDE_NEXT[^@]*@ @NEXT_[^@\n]+@" + "include <${INCLUDE_PATH}>" HEADER_CONTENT "${HEADER_CONTENT}") + endif() + + # Default any remaining template variables to 0 + string(REGEX REPLACE "@[^@\n]+@" "0" HEADER_CONTENT "${HEADER_CONTENT}") + + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${HEADER_TEMPLATES_PATH}/${HEADER_PATH}" "${HEADER_CONTENT}") +endforeach() + +add_library(libgettextsrc ${libgettextsrc_SOURCES}) +target_link_libraries(libgettextsrc ${LIBICONV_LIBRARIES}) + +set(msgmerge_SOURCES + msgmerge.c + msgl-fsearch.c + lang-table.c + ) +PREFIX_LIST_ITEMS(msgmerge_SOURCES "gettext-tools/src/") + +add_executable(msgmerge ${msgmerge_SOURCES}) +target_link_libraries(msgmerge libgettextsrc) +add_dependencies(msgmerge libgettextsrc libintl) + +set(msgfmt_SOURCES + msgfmt.c + write-mo.c + write-java.c + write-csharp.c + write-resources.c + write-tcl.c + write-qt.c + write-desktop.c + write-xml.c) +PREFIX_LIST_ITEMS(msgfmt_SOURCES "gettext-tools/src/") + +add_executable(msgfmt ${msgfmt_SOURCES} gettext-runtime/intl/hash-string.c) +target_link_libraries(msgfmt libgettextsrc) +add_dependencies(msgfmt libgettextsrc libintl) + +set(xgettext_SOURCES + xgettext.c x-c.c x-po.c x-sh.c x-python.c x-lisp.c x-elisp.c x-librep.c + x-scheme.c x-smalltalk.c x-java.c x-csharp.c x-awk.c x-ycp.c x-tcl.c x-perl.c + x-php.c x-rst.c x-lua.c x-javascript.c x-vala.c x-desktop.c) +PREFIX_LIST_ITEMS(xgettext_SOURCES "gettext-tools/src/") + +add_executable(xgettext ${xgettext_SOURCES}) +target_link_libraries(xgettext libgettextsrc) +add_dependencies(xgettext libgettextsrc libintl) + +set_property(TARGET msgmerge msgfmt xgettext libgettextsrc APPEND PROPERTY + INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_SOURCE_DIR}/gettext-runtime/intl + ${CMAKE_CURRENT_SOURCE_DIR}/gettext-tools/libgettextpo + ${CMAKE_CURRENT_SOURCE_DIR}/gettext-tools/gnulib-lib + ${CMAKE_CURRENT_SOURCE_DIR}/gettext-tools/gnulib-lib/libcroco + ${CMAKE_CURRENT_SOURCE_DIR}/build-aux/snippet + ${CMAKE_CURRENT_BINARY_DIR}/gettext-runtime/intl + ${CMAKE_CURRENT_BINARY_DIR}/gettext-tools + ${CMAKE_CURRENT_BINARY_DIR}/gettext-tools/gnulib-lib + ${LIBICONV_INCLUDE_DIRS}) include(GNUInstallDirs) install(FILES - ${CMAKE_CURRENT_BINARY_DIR}/libgnuintl.h + ${CMAKE_CURRENT_BINARY_DIR}/gettext-runtime/intl/libgnuintl.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} RENAME libintl.h) -install(TARGETS libintl +install(TARGETS libintl msgmerge msgfmt xgettext ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/third-party/cmake/LibiconvCMakeLists.txt b/third-party/cmake/LibiconvCMakeLists.txt new file mode 100644 index 0000000000..d14b8529d4 --- /dev/null +++ b/third-party/cmake/LibiconvCMakeLists.txt @@ -0,0 +1,97 @@ +cmake_minimum_required(VERSION 2.8.7) +project(libiconv C) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/srclib + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/srclib + ${CMAKE_CURRENT_SOURCE_DIR}/build-aux/snippet) + +configure_file(config.h.in config.h) +file(READ "${CMAKE_CURRENT_BINARY_DIR}/config.h" CONFIG_CONTENT) +string(REPLACE "#undef EILSEQ" "" CONFIG_CONTENT "${CONFIG_CONTENT}") +string(REPLACE "#undef HAVE_MBRTOWC" "#define HAVE_MBRTOWC 1" CONFIG_CONTENT "${CONFIG_CONTENT}") +string(REPLACE "#undef HAVE_MBSINIT" "#define HAVE_MBSINIT 1" CONFIG_CONTENT "${CONFIG_CONTENT}") +string(REPLACE "#undef HAVE_WCRTOMB" "#define HAVE_WCRTOMB 1" CONFIG_CONTENT "${CONFIG_CONTENT}") +string(REPLACE "#undef HAVE_DECL___ARGV" "#define HAVE_DECL___ARGV 1" CONFIG_CONTENT "${CONFIG_CONTENT}") +string(REPLACE "#undef HAVE_WORKING_O_NOFOLLOW" "#define HAVE_WORKING_O_NOFOLLOW 0" CONFIG_CONTENT "${CONFIG_CONTENT}") +string(REPLACE "#undef ICONV_CONST" "#define ICONV_CONST const" CONFIG_CONTENT "${CONFIG_CONTENT}") +string(REPLACE "#undef WORDS_LITTLEENDIAN" "#define WORDS_LITTLEENDIAN 1" CONFIG_CONTENT "${CONFIG_CONTENT}") +string(REPLACE "#undef HAVE_DECL_STRERROR_R" "#define HAVE_DECL_STRERROR_R 0" CONFIG_CONTENT "${CONFIG_CONTENT}") +string(REPLACE "#undef mode_t" "#define mode_t int" CONFIG_CONTENT "${CONFIG_CONTENT}") +string(REPLACE "#undef ssize_t" "#include <BaseTsd.h>\n#define ssize_t SSIZE_T" CONFIG_CONTENT "${CONFIG_CONTENT}") +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/config.h" "${CONFIG_CONTENT}") + +set(BROKEN_WCHAR_H 0) +set(HAVE_VISIBILITY 0) +set(HAVE_WCHAR_T 1) +set(ICONV_CONST "const") +set(USE_MBSTATE_T 0) +configure_file(libcharset/include/localcharset.h.build.in localcharset.h) +configure_file(include/iconv.h.build.in iconv.h) + +add_definitions(-DLIBDIR -D_CRT_SECURE_NO_WARNINGS) + +add_library(libcharset libcharset/lib/localcharset.c) + +add_library(libiconv lib/iconv.c) +target_link_libraries(libiconv libcharset) + +add_executable(iconv src/iconv.c srclib/progname.c srclib/getprogname.c + srclib/safe-read.c srclib/uniwidth/width.c srclib/error.c srclib/xmalloc.c + srclib/basename-lgpl.c) +target_link_libraries(iconv libiconv) + +set(HEADER_TEMPLATES_PATH "srclib") +set(HEADER_TEMPLATES_ABS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${HEADER_TEMPLATES_PATH}") +file(GLOB_RECURSE HEADER_TEMPLATES "${HEADER_TEMPLATES_ABS_PATH}/*.in.h") +list(REMOVE_ITEM HEADER_TEMPLATES "${HEADER_TEMPLATES_ABS_PATH}/stdint.in.h") +list(REMOVE_ITEM HEADER_TEMPLATES "${HEADER_TEMPLATES_ABS_PATH}/wchar.in.h") +foreach(HEADER_TEMPLATE ${HEADER_TEMPLATES}) + file(READ ${HEADER_TEMPLATE} HEADER_CONTENT) + string(REPLACE "/* The definition of _GL_ARG_NONNULL is copied here. */" "#include \"arg-nonnull.h\"" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "/* The definition of _GL_WARN_ON_USE is copied here. */" "#include \"warn-on-use.h\"" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "/* The definitions of _GL_FUNCDECL_RPL etc. are copied here. */" "#include \"c++defs.h\"" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@GNULIB_LSTAT@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@GNULIB_SIGACTION@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@GNULIB_SIGPROCMASK@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@HAVE_ISWCNTRL@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@HAVE_WCTYPE_T@" "1" HEADER_CONTENT "${HEADER_CONTENT}") + string(REPLACE "@PRAGMA_COLUMNS@" "" HEADER_CONTENT "${HEADER_CONTENT}") + + string(REGEX REPLACE "^${HEADER_TEMPLATES_ABS_PATH}/" "" HEADER_PATH "${HEADER_TEMPLATE}") + string(REPLACE ".in" "" HEADER_PATH ${HEADER_PATH}) + string(REPLACE "_" "/" HEADER_PATH ${HEADER_PATH}) + # find_file will create a cache entry for the variable + # SYSTEM_HEADER, so reset it before each call + set(SYSTEM_HEADER "SYSTEM_HEADER-NOTFOUND") + find_file(SYSTEM_HEADER ${HEADER_PATH} PATHS "${LIBICONV_INCLUDE_DIRS}") + if(SYSTEM_HEADER) + # Gnulib uses #include_next to extend system header files, + # but MSVC doesn't support it, so a regular include directive + # with a relative path is used instead + string(REGEX REPLACE ".*/(.*/${HEADER_PATH})" "../\\1" + INCLUDE_PATH "${SYSTEM_HEADER}") + string(REGEX REPLACE "@INCLUDE_NEXT[^@]*@ @NEXT_[^@\n]+@" + "include <${INCLUDE_PATH}>" HEADER_CONTENT "${HEADER_CONTENT}") + endif() + + # Default any remaining template variables to 0 + string(REGEX REPLACE "@[^@\n]+@" "0" HEADER_CONTENT "${HEADER_CONTENT}") + + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${HEADER_TEMPLATES_PATH}/${HEADER_PATH}" "${HEADER_CONTENT}") +endforeach() + +include(GNUInstallDirs) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/iconv.h + ${CMAKE_CURRENT_BINARY_DIR}/localcharset.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +install(TARGETS libcharset libiconv iconv + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/third-party/patches/gettext-Fix-building-with-MSVC.patch b/third-party/patches/gettext-Fix-building-with-MSVC.patch new file mode 100644 index 0000000000..d15901bce3 --- /dev/null +++ b/third-party/patches/gettext-Fix-building-with-MSVC.patch @@ -0,0 +1,50 @@ +diff --git a/gettext-tools/config.h.in b/gettext-tools/config.h.in +index 6818a4d..9842a71 100644 +--- a/gettext-tools/config.h.in ++++ b/gettext-tools/config.h.in +@@ -3147,7 +3147,7 @@ + #define PAGE_WIDTH 79 + + /* On Windows, variables that may be in a DLL must be marked specially. */ +-#if ((defined _MSC_VER && defined _DLL) || defined WOE32DLL) && !defined IN_RELOCWRAPPER ++#if ((defined _MSC_VER && defined DLL_IMPORT) || defined WOE32DLL) && !defined IN_RELOCWRAPPER + # define DLL_VARIABLE __declspec (dllimport) + #else + # define DLL_VARIABLE +diff --git a/gettext-tools/gnulib-lib/javaversion.c b/gettext-tools/gnulib-lib/javaversion.c +index d760c32..4867fda 100644 +--- a/gettext-tools/gnulib-lib/javaversion.c ++++ b/gettext-tools/gnulib-lib/javaversion.c +@@ -39,7 +39,7 @@ + #define _(str) gettext (str) + + /* Get PKGDATADIR. */ +-#include "configmake.h" ++#define PKGDATADIR "" + + + struct locals +diff --git a/gettext-tools/libgettextpo/xalloc.h b/gettext-tools/libgettextpo/xalloc.h +index f4a329e..a38dcf1 100644 +--- a/gettext-tools/libgettextpo/xalloc.h ++++ b/gettext-tools/libgettextpo/xalloc.h +@@ -60,7 +60,7 @@ extern "C" { + in charge of honoring the three previous items. This is the + function to call when one wants the program to die because of a + memory allocation failure. */ +-extern void xalloc_die (void) ++extern _Noreturn void xalloc_die (void) + #if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5)) && !__STRICT_ANSI__ + __attribute__ ((__noreturn__)) + #endif +diff --git a/gettext-tools/src/plural-exp.c b/gettext-tools/src/plural-exp.c +index d5b9deb..e2c6bc4 100644 +--- a/gettext-tools/src/plural-exp.c ++++ b/gettext-tools/src/plural-exp.c +@@ -17,5 +17,5 @@ + + /* Include the expression parsing code from libintl, with different function + names. */ +-#include "../intl/pluralx.c" ++#include "../intl/plural.c" + #include "../../gettext-runtime/intl/plural-exp.c" |