diff options
73 files changed, 2662 insertions, 1225 deletions
diff --git a/.travis.yml b/.travis.yml index 09ef8bba46..9a62a8a942 100644 --- a/.travis.yml +++ b/.travis.yml @@ -57,13 +57,13 @@ jobs: env: > CLANG_SANITIZER=ASAN_UBSAN CMAKE_FLAGS="$CMAKE_FLAGS -DPREFER_LUA=ON" + sudo: true - os: linux compiler: gcc env: FUNCTIONALTEST=functionaltest-lua - os: linux - # Travis creates a cache per compiler. - # Set a different value here to store 32-bit - # dependencies in a separate cache. + # Travis creates a cache per compiler. Set a different value here to + # store 32-bit dependencies in a separate cache. compiler: gcc -m32 env: BUILD_32BIT=ON - os: osx diff --git a/CMakeLists.txt b/CMakeLists.txt index fb0cc262b1..74ff32a23d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -277,6 +277,8 @@ else() set(DEBUG 0) endif() +option(LOG_LIST_ACTIONS "Add list actions logging" OFF) + add_definitions(-DINCLUDE_GENERATED_DECLARATIONS) if(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_SYSTEM_NAME STREQUAL "Linux") diff --git a/config/config.h.in b/config/config.h.in index 962eefd7a7..410d9cc825 100644 --- a/config/config.h.in +++ b/config/config.h.in @@ -62,6 +62,7 @@ #ifndef UNIT_TESTING #cmakedefine HAVE_JEMALLOC +#cmakedefine LOG_LIST_ACTIONS #endif #cmakedefine HAVE_BE64TOH diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index d992c2cc77..0172f630cd 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -138,7 +138,7 @@ endforeach() file(GLOB_RECURSE RUNTIME_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} rgb.txt - *.vim *.lua *.dict *.py *.rb *.ps *.tutor *.tutor.json) + *.vim *.lua *.dict *.py *.rb *.ps *.spl *.tutor *.tutor.json) foreach(F ${RUNTIME_FILES}) get_filename_component(BASEDIR ${F} PATH) diff --git a/runtime/autoload/tutor.vim b/runtime/autoload/tutor.vim index 56e2283465..0f01190b9b 100644 --- a/runtime/autoload/tutor.vim +++ b/runtime/autoload/tutor.vim @@ -2,9 +2,6 @@ " Setup: {{{1 function! tutor#SetupVim() - if &columns < 90 - set columns=90 - endif if !exists('g:did_load_ftplugin') || g:did_load_ftplugin != 1 filetype plugin on endif diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index ae62498d35..7820b85bf4 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -7645,6 +7645,9 @@ system({cmd} [, {input}]) *system()* *E677* |writefile()| does with {binary} set to "b" (i.e. with a newline between each list item, and newlines inside list items converted to NULs). + When {input} is given and is a valid buffer id, the content of + the buffer is written to the file line by line, each line + terminated by a NL (and NUL where the text has NL). *E5677* Note: system() cannot write to or read from backgrounded ("&") shell commands, e.g.: > diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index baa522f343..b0fbe9cc35 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -148,8 +148,8 @@ local function highlight_line(line, linenr) end local function highlight_man_page() - local mod = vim.api.nvim_eval("&modifiable") - vim.api.nvim_command("set modifiable") + local mod = vim.api.nvim_buf_get_option(0, "modifiable") + vim.api.nvim_buf_set_option(0, "modifiable", true) local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) for i, line in ipairs(lines) do @@ -162,7 +162,7 @@ local function highlight_man_page() end buf_hls = {} - vim.api.nvim_command("let &modifiable = "..mod) + vim.api.nvim_buf_set_option(0, "modifiable", mod) end return { highlight_man_page = highlight_man_page } diff --git a/runtime/plugin/rplugin.vim b/runtime/plugin/rplugin.vim index ca15ec82d1..56163c894e 100644 --- a/runtime/plugin/rplugin.vim +++ b/runtime/plugin/rplugin.vim @@ -56,6 +56,6 @@ function! s:LoadRemotePlugins() abort endif endfunction -command! UpdateRemotePlugins call remote#host#UpdateRemotePlugins() +command! -bar UpdateRemotePlugins call remote#host#UpdateRemotePlugins() call s:LoadRemotePlugins() diff --git a/runtime/spell/en.utf-8.spl b/runtime/spell/en.utf-8.spl Binary files differnew file mode 100644 index 0000000000..83b9b8f7c2 --- /dev/null +++ b/runtime/spell/en.utf-8.spl diff --git a/src/nvim/README.md b/src/nvim/README.md index da87a0208e..1ece92e44d 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -1,10 +1,22 @@ -Nvim core source -================ +Nvim core +========= Module-specific details are documented at the top of each module (`terminal.c`, -`screen.c`, ...). +`screen.c`, …). -See `:help development` for more guidelines. +See `:help dev` for guidelines. + +Filename conventions +-------------------- + +The source files use extensions to hint about their purpose. + +- `*.c`, `*.generated.c` - full C files, with all includes, etc. +- `*.c.h` - parametrized C files, contain all necessary includes, but require + defining macros before actually using. Example: `typval_encode.c.h` +- `*.h` - full headers, with all includes. Does *not* apply to `*.generated.h`. +- `*.h.generated.h` - exported functions’ declarations. +- `*.c.generated.h` - static functions’ declarations. Logs ---- @@ -20,17 +32,36 @@ UI events are logged at level 0 (`DEBUG_LOG_LEVEL`). rm -rf build/ make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0" -Filename conventions --------------------- +Build with ASAN +--------------- -The source files use extensions to hint about their purpose. +Building Nvim with Clang sanitizers (Address Sanitizer: ASan, Undefined +Behavior Sanitizer: UBSan, Memory Sanitizer: MSan, Thread Sanitizer: TSan) is +a good way to catch undefined behavior, leaks and other errors as soon as they +happen. It's significantly faster than Valgrind. -- `*.c`, `*.generated.c` - full C files, with all includes, etc. -- `*.c.h` - parametrized C files, contain all necessary includes, but require - defining macros before actually using. Example: `typval_encode.c.h` -- `*.h` - full headers, with all includes. Does *not* apply to `*.generated.h`. -- `*.h.generated.h` - exported functions’ declarations. -- `*.c.generated.h` - static functions’ declarations. +Requires clang 3.4 or later: + + clang --version + +Build Nvim with sanitizer instrumentation: + + CC=clang make CMAKE_EXTRA_FLAGS="-DCLANG_ASAN_UBSAN=ON" + +Create a directory to store logs: + + mkdir -p "$HOME/logs" + +Enable the sanitizer(s) via these environment variables: + + # Change to detect_leaks=1 to detect memory leaks (slower). + export ASAN_OPTIONS="detect_leaks=0:log_path=$HOME/logs/asan" + export ASAN_SYMBOLIZER_PATH=/usr/lib/llvm-5.0/bin/llvm-symbolizer + + export MSAN_SYMBOLIZER_PATH=/usr/lib/llvm-5.0/bin/llvm-symbolizer + export TSAN_OPTIONS="external_symbolizer_path=/usr/lib/llvm-5.0/bin/llvm-symbolizer log_path=${HOME}/logs/tsan" + +Logs will be written to `${HOME}/logs/*san.PID`. TUI debugging ------------- diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index fdde28f2bb..01dde9dd09 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -806,7 +806,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, /// Clears highlights from a given source group and a range of lines /// -/// To clear a source group in the entire buffer, pass in 1 and -1 to +/// To clear a source group in the entire buffer, pass in 0 and -1 to /// line_start and line_end respectively. /// /// @param buffer Buffer handle diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 26ad7ac1a6..9baa996560 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -783,7 +783,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) break; case kObjectTypeArray: { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)obj.data.array.size); for (uint32_t i = 0; i < obj.data.array.size; i++) { Object item = obj.data.array.items[i]; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 172f2ce18e..0e7cc428d4 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -43,14 +43,15 @@ #endif /// Executes an ex-command. -/// On VimL error: Returns the VimL error; v:errmsg is not updated. +/// +/// On parse error: forwards the Vim error; does not update v:errmsg. +/// On runtime error: forwards the Vim error; does not update v:errmsg. /// /// @param command Ex-command string -/// @param[out] err Error details (including actual VimL error), if any +/// @param[out] err Error details (Vim error), if any void nvim_command(String command, Error *err) FUNC_API_SINCE(1) { - // Run the command try_start(); do_cmdline_cmd(command.data); update_screen(VALID); @@ -207,18 +208,49 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, return cstr_as_string(ptr); } -String nvim_command_output(String str, Error *err) +/// Executes an ex-command and returns its (non-error) output. +/// Shell |:!| output is not captured. +/// +/// On parse error: forwards the Vim error; does not update v:errmsg. +/// On runtime error: forwards the Vim error; does not update v:errmsg. +/// +/// @param command Ex-command string +/// @param[out] err Error details (Vim error), if any +String nvim_command_output(String command, Error *err) FUNC_API_SINCE(1) { - do_cmdline_cmd("redir => v:command_output"); - nvim_command(str, err); - do_cmdline_cmd("redir END"); + const int save_msg_silent = msg_silent; + garray_T *const save_capture_ga = capture_ga; + garray_T capture_local; + ga_init(&capture_local, 1, 80); + + try_start(); + msg_silent++; + capture_ga = &capture_local; + do_cmdline_cmd(command.data); + capture_ga = save_capture_ga; + msg_silent = save_msg_silent; + try_end(err); if (ERROR_SET(err)) { - return (String)STRING_INIT; + goto theend; + } + + if (capture_local.ga_len > 1) { + // redir always(?) prepends a newline; remove it. + char *s = capture_local.ga_data; + assert(s[0] == '\n'); + memmove(s, s + 1, (size_t)capture_local.ga_len); + s[capture_local.ga_len - 1] = '\0'; + return (String) { // Caller will free the memory. + .data = s, + .size = (size_t)(capture_local.ga_len - 1), + }; } - return cstr_to_string((char *)get_vim_var_str(VV_COMMAND_OUTPUT)); +theend: + ga_clear(&capture_local); + return (String)STRING_INIT; } /// Evaluates a VimL expression (:help expression). diff --git a/src/nvim/arabic.c b/src/nvim/arabic.c index 1ef51d2a2a..e120e6d492 100644 --- a/src/nvim/arabic.c +++ b/src/nvim/arabic.c @@ -393,491 +393,141 @@ static bool A_is_f(int cur_c) // Change shape - from ISO-8859-6/Isolated to Form-B Isolated static int chg_c_a2s(int cur_c) { - int tempc; - switch (cur_c) { - case a_HAMZA: - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: - tempc = a_s_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_s_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: - tempc = a_s_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_s_ALEF_HAMZA_BELOW; - break; - - case a_YEH_HAMZA: - tempc = a_s_YEH_HAMZA; - break; - - case a_ALEF: - tempc = a_s_ALEF; - break; - - case a_TEH_MARBUTA: - tempc = a_s_TEH_MARBUTA; - break; - - case a_DAL: - tempc = a_s_DAL; - break; - - case a_THAL: - tempc = a_s_THAL; - break; - - case a_REH: - tempc = a_s_REH; - break; - - case a_ZAIN: - tempc = a_s_ZAIN; - break; - - case a_TATWEEL: // exceptions - tempc = cur_c; - break; - - case a_WAW: - tempc = a_s_WAW; - break; - - case a_ALEF_MAKSURA: - tempc = a_s_ALEF_MAKSURA; - break; - - case a_BEH: - tempc = a_s_BEH; - break; - - case a_TEH: - tempc = a_s_TEH; - break; - - case a_THEH: - tempc = a_s_THEH; - break; - - case a_JEEM: - tempc = a_s_JEEM; - break; - - case a_HAH: - tempc = a_s_HAH; - break; - - case a_KHAH: - tempc = a_s_KHAH; - break; - - case a_SEEN: - tempc = a_s_SEEN; - break; - - case a_SHEEN: - tempc = a_s_SHEEN; - break; - - case a_SAD: - tempc = a_s_SAD; - break; - - case a_DAD: - tempc = a_s_DAD; - break; - - case a_TAH: - tempc = a_s_TAH; - break; - - case a_ZAH: - tempc = a_s_ZAH; - break; - - case a_AIN: - tempc = a_s_AIN; - break; - - case a_GHAIN: - tempc = a_s_GHAIN; - break; - - case a_FEH: - tempc = a_s_FEH; - break; - - case a_QAF: - tempc = a_s_QAF; - break; - - case a_KAF: - tempc = a_s_KAF; - break; - - case a_LAM: - tempc = a_s_LAM; - break; - - case a_MEEM: - tempc = a_s_MEEM; - break; - - case a_NOON: - tempc = a_s_NOON; - break; - - case a_HEH: - tempc = a_s_HEH; - break; - - case a_YEH: - tempc = a_s_YEH; - break; - - default: - tempc = 0; + case a_HAMZA: return a_s_HAMZA; + case a_ALEF_MADDA: return a_s_ALEF_MADDA; + case a_ALEF_HAMZA_ABOVE: return a_s_ALEF_HAMZA_ABOVE; + case a_WAW_HAMZA: return a_s_WAW_HAMZA; + case a_ALEF_HAMZA_BELOW: return a_s_ALEF_HAMZA_BELOW; + case a_YEH_HAMZA: return a_s_YEH_HAMZA; + case a_ALEF: return a_s_ALEF; + case a_TEH_MARBUTA: return a_s_TEH_MARBUTA; + case a_DAL: return a_s_DAL; + case a_THAL: return a_s_THAL; + case a_REH: return a_s_REH; + case a_ZAIN: return a_s_ZAIN; + case a_TATWEEL: return cur_c; // exceptions + case a_WAW: return a_s_WAW; + case a_ALEF_MAKSURA: return a_s_ALEF_MAKSURA; + case a_BEH: return a_s_BEH; + case a_TEH: return a_s_TEH; + case a_THEH: return a_s_THEH; + case a_JEEM: return a_s_JEEM; + case a_HAH: return a_s_HAH; + case a_KHAH: return a_s_KHAH; + case a_SEEN: return a_s_SEEN; + case a_SHEEN: return a_s_SHEEN; + case a_SAD: return a_s_SAD; + case a_DAD: return a_s_DAD; + case a_TAH: return a_s_TAH; + case a_ZAH: return a_s_ZAH; + case a_AIN: return a_s_AIN; + case a_GHAIN: return a_s_GHAIN; + case a_FEH: return a_s_FEH; + case a_QAF: return a_s_QAF; + case a_KAF: return a_s_KAF; + case a_LAM: return a_s_LAM; + case a_MEEM: return a_s_MEEM; + case a_NOON: return a_s_NOON; + case a_HEH: return a_s_HEH; + case a_YEH: return a_s_YEH; } - - return tempc; + return 0; } // Change shape - from ISO-8859-6/Isolated to Initial static int chg_c_a2i(int cur_c) { - int tempc; - switch (cur_c) { - case a_YEH_HAMZA: - tempc = a_i_YEH_HAMZA; - break; - - case a_HAMZA: // exceptions - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: // exceptions - tempc = a_s_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: // exceptions - tempc = a_s_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: // exceptions - tempc = a_s_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: // exceptions - tempc = a_s_ALEF_HAMZA_BELOW; - break; - - case a_ALEF: // exceptions - tempc = a_s_ALEF; - break; - - case a_TEH_MARBUTA: // exceptions - tempc = a_s_TEH_MARBUTA; - break; - - case a_DAL: // exceptions - tempc = a_s_DAL; - break; - - case a_THAL: // exceptions - tempc = a_s_THAL; - break; - - case a_REH: // exceptions - tempc = a_s_REH; - break; - - case a_ZAIN: // exceptions - tempc = a_s_ZAIN; - break; - - case a_TATWEEL: // exceptions - tempc = cur_c; - break; - - case a_WAW: // exceptions - tempc = a_s_WAW; - break; - - case a_ALEF_MAKSURA: // exceptions - tempc = a_s_ALEF_MAKSURA; - break; - - case a_BEH: - tempc = a_i_BEH; - break; - - case a_TEH: - tempc = a_i_TEH; - break; - - case a_THEH: - tempc = a_i_THEH; - break; - - case a_JEEM: - tempc = a_i_JEEM; - break; - - case a_HAH: - tempc = a_i_HAH; - break; - - case a_KHAH: - tempc = a_i_KHAH; - break; - - case a_SEEN: - tempc = a_i_SEEN; - break; - - case a_SHEEN: - tempc = a_i_SHEEN; - break; - - case a_SAD: - tempc = a_i_SAD; - break; - - case a_DAD: - tempc = a_i_DAD; - break; - - case a_TAH: - tempc = a_i_TAH; - break; - - case a_ZAH: - tempc = a_i_ZAH; - break; - - case a_AIN: - tempc = a_i_AIN; - break; - - case a_GHAIN: - tempc = a_i_GHAIN; - break; - - case a_FEH: - tempc = a_i_FEH; - break; - - case a_QAF: - tempc = a_i_QAF; - break; - - case a_KAF: - tempc = a_i_KAF; - break; - - case a_LAM: - tempc = a_i_LAM; - break; - - case a_MEEM: - tempc = a_i_MEEM; - break; - - case a_NOON: - tempc = a_i_NOON; - break; - - case a_HEH: - tempc = a_i_HEH; - break; - - case a_YEH: - tempc = a_i_YEH; - break; - - default: - tempc = 0; + case a_YEH_HAMZA: return a_i_YEH_HAMZA; + case a_HAMZA: return a_s_HAMZA; // exceptions + case a_ALEF_MADDA: return a_s_ALEF_MADDA; // exceptions + case a_ALEF_HAMZA_ABOVE: return a_s_ALEF_HAMZA_ABOVE; // exceptions + case a_WAW_HAMZA: return a_s_WAW_HAMZA; // exceptions + case a_ALEF_HAMZA_BELOW: return a_s_ALEF_HAMZA_BELOW; // exceptions + case a_ALEF: return a_s_ALEF; // exceptions + case a_TEH_MARBUTA: return a_s_TEH_MARBUTA; // exceptions + case a_DAL: return a_s_DAL; // exceptions + case a_THAL: return a_s_THAL; // exceptions + case a_REH: return a_s_REH; // exceptions + case a_ZAIN: return a_s_ZAIN; // exceptions + case a_TATWEEL: return cur_c; // exceptions + case a_WAW: return a_s_WAW; // exceptions + case a_ALEF_MAKSURA: return a_s_ALEF_MAKSURA; // exceptions + case a_BEH: return a_i_BEH; + case a_TEH: return a_i_TEH; + case a_THEH: return a_i_THEH; + case a_JEEM: return a_i_JEEM; + case a_HAH: return a_i_HAH; + case a_KHAH: return a_i_KHAH; + case a_SEEN: return a_i_SEEN; + case a_SHEEN: return a_i_SHEEN; + case a_SAD: return a_i_SAD; + case a_DAD: return a_i_DAD; + case a_TAH: return a_i_TAH; + case a_ZAH: return a_i_ZAH; + case a_AIN: return a_i_AIN; + case a_GHAIN: return a_i_GHAIN; + case a_FEH: return a_i_FEH; + case a_QAF: return a_i_QAF; + case a_KAF: return a_i_KAF; + case a_LAM: return a_i_LAM; + case a_MEEM: return a_i_MEEM; + case a_NOON: return a_i_NOON; + case a_HEH: return a_i_HEH; + case a_YEH: return a_i_YEH; } - - return tempc; + return 0; } // Change shape - from ISO-8859-6/Isolated to Medial static int chg_c_a2m(int cur_c) { - int tempc; - switch (cur_c) { - case a_HAMZA: // exception - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: // exception - tempc = a_f_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: // exception - tempc = a_f_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: // exception - tempc = a_f_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: // exception - tempc = a_f_ALEF_HAMZA_BELOW; - break; - - case a_YEH_HAMZA: - tempc = a_m_YEH_HAMZA; - break; - - case a_ALEF: // exception - tempc = a_f_ALEF; - break; - - case a_BEH: - tempc = a_m_BEH; - break; - - case a_TEH_MARBUTA: // exception - tempc = a_f_TEH_MARBUTA; - break; - - case a_TEH: - tempc = a_m_TEH; - break; - - case a_THEH: - tempc = a_m_THEH; - break; - - case a_JEEM: - tempc = a_m_JEEM; - break; - - case a_HAH: - tempc = a_m_HAH; - break; - - case a_KHAH: - tempc = a_m_KHAH; - break; - - case a_DAL: // exception - tempc = a_f_DAL; - break; - - case a_THAL: // exception - tempc = a_f_THAL; - break; - - case a_REH: // exception - tempc = a_f_REH; - break; - - case a_ZAIN: // exception - tempc = a_f_ZAIN; - break; - - case a_SEEN: - tempc = a_m_SEEN; - break; - - case a_SHEEN: - tempc = a_m_SHEEN; - break; - - case a_SAD: - tempc = a_m_SAD; - break; - - case a_DAD: - tempc = a_m_DAD; - break; - - case a_TAH: - tempc = a_m_TAH; - break; - - case a_ZAH: - tempc = a_m_ZAH; - break; - - case a_AIN: - tempc = a_m_AIN; - break; - - case a_GHAIN: - tempc = a_m_GHAIN; - break; - - case a_TATWEEL: // exception - tempc = cur_c; - break; - - case a_FEH: - tempc = a_m_FEH; - break; - - case a_QAF: - tempc = a_m_QAF; - break; - - case a_KAF: - tempc = a_m_KAF; - break; - - case a_LAM: - tempc = a_m_LAM; - break; - - case a_MEEM: - tempc = a_m_MEEM; - break; - - case a_NOON: - tempc = a_m_NOON; - break; - - case a_HEH: - tempc = a_m_HEH; - break; - - case a_WAW: // exception - tempc = a_f_WAW; - break; - - case a_ALEF_MAKSURA: // exception - tempc = a_f_ALEF_MAKSURA; - break; - - case a_YEH: - tempc = a_m_YEH; - break; - - default: - tempc = 0; + case a_HAMZA: return a_s_HAMZA; // exception + case a_ALEF_MADDA: return a_f_ALEF_MADDA; // exception + case a_ALEF_HAMZA_ABOVE: return a_f_ALEF_HAMZA_ABOVE; // exception + case a_WAW_HAMZA: return a_f_WAW_HAMZA; // exception + case a_ALEF_HAMZA_BELOW: return a_f_ALEF_HAMZA_BELOW; // exception + case a_YEH_HAMZA: return a_m_YEH_HAMZA; + case a_ALEF: return a_f_ALEF; // exception + case a_BEH: return a_m_BEH; + case a_TEH_MARBUTA: return a_f_TEH_MARBUTA; // exception + case a_TEH: return a_m_TEH; + case a_THEH: return a_m_THEH; + case a_JEEM: return a_m_JEEM; + case a_HAH: return a_m_HAH; + case a_KHAH: return a_m_KHAH; + case a_DAL: return a_f_DAL; // exception + case a_THAL: return a_f_THAL; // exception + case a_REH: return a_f_REH; // exception + case a_ZAIN: return a_f_ZAIN; // exception + case a_SEEN: return a_m_SEEN; + case a_SHEEN: return a_m_SHEEN; + case a_SAD: return a_m_SAD; + case a_DAD: return a_m_DAD; + case a_TAH: return a_m_TAH; + case a_ZAH: return a_m_ZAH; + case a_AIN: return a_m_AIN; + case a_GHAIN: return a_m_GHAIN; + case a_TATWEEL: return cur_c; // exception + case a_FEH: return a_m_FEH; + case a_QAF: return a_m_QAF; + case a_KAF: return a_m_KAF; + case a_LAM: return a_m_LAM; + case a_MEEM: return a_m_MEEM; + case a_NOON: return a_m_NOON; + case a_HEH: return a_m_HEH; + case a_WAW: return a_f_WAW; // exception + case a_ALEF_MAKSURA: return a_f_ALEF_MAKSURA; // exception + case a_YEH: return a_m_YEH; } - - return tempc; + return 0; } // Change shape - from ISO-8859-6/Isolated to final static int chg_c_a2f(int cur_c) { - int tempc; - // NOTE: these encodings need to be accounted for // // a_f_ALEF_MADDA; @@ -888,280 +538,87 @@ static int chg_c_a2f(int cur_c) // a_f_LAM_ALEF_HAMZA_BELOW; switch (cur_c) { - case a_HAMZA: // exception - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: - tempc = a_f_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_f_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: - tempc = a_f_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_f_ALEF_HAMZA_BELOW; - break; - - case a_YEH_HAMZA: - tempc = a_f_YEH_HAMZA; - break; - - case a_ALEF: - tempc = a_f_ALEF; - break; - - case a_BEH: - tempc = a_f_BEH; - break; - - case a_TEH_MARBUTA: - tempc = a_f_TEH_MARBUTA; - break; - - case a_TEH: - tempc = a_f_TEH; - break; - - case a_THEH: - tempc = a_f_THEH; - break; - - case a_JEEM: - tempc = a_f_JEEM; - break; - - case a_HAH: - tempc = a_f_HAH; - break; - - case a_KHAH: - tempc = a_f_KHAH; - break; - - case a_DAL: - tempc = a_f_DAL; - break; - - case a_THAL: - tempc = a_f_THAL; - break; - - case a_REH: - tempc = a_f_REH; - break; - - case a_ZAIN: - tempc = a_f_ZAIN; - break; - - case a_SEEN: - tempc = a_f_SEEN; - break; - - case a_SHEEN: - tempc = a_f_SHEEN; - break; - - case a_SAD: - tempc = a_f_SAD; - break; - - case a_DAD: - tempc = a_f_DAD; - break; - - case a_TAH: - tempc = a_f_TAH; - break; - - case a_ZAH: - tempc = a_f_ZAH; - break; - - case a_AIN: - tempc = a_f_AIN; - break; - - case a_GHAIN: - tempc = a_f_GHAIN; - break; - - case a_TATWEEL: // exception - tempc = cur_c; - break; - - case a_FEH: - tempc = a_f_FEH; - break; - - case a_QAF: - tempc = a_f_QAF; - break; - - case a_KAF: - tempc = a_f_KAF; - break; - - case a_LAM: - tempc = a_f_LAM; - break; - - case a_MEEM: - tempc = a_f_MEEM; - break; - - case a_NOON: - tempc = a_f_NOON; - break; - - case a_HEH: - tempc = a_f_HEH; - break; - - case a_WAW: - tempc = a_f_WAW; - break; - - case a_ALEF_MAKSURA: - tempc = a_f_ALEF_MAKSURA; - break; - - case a_YEH: - tempc = a_f_YEH; - break; - - default: - tempc = 0; + case a_HAMZA: return a_s_HAMZA; // exception + case a_ALEF_MADDA: return a_f_ALEF_MADDA; + case a_ALEF_HAMZA_ABOVE: return a_f_ALEF_HAMZA_ABOVE; + case a_WAW_HAMZA: return a_f_WAW_HAMZA; + case a_ALEF_HAMZA_BELOW: return a_f_ALEF_HAMZA_BELOW; + case a_YEH_HAMZA: return a_f_YEH_HAMZA; + case a_ALEF: return a_f_ALEF; + case a_BEH: return a_f_BEH; + case a_TEH_MARBUTA: return a_f_TEH_MARBUTA; + case a_TEH: return a_f_TEH; + case a_THEH: return a_f_THEH; + case a_JEEM: return a_f_JEEM; + case a_HAH: return a_f_HAH; + case a_KHAH: return a_f_KHAH; + case a_DAL: return a_f_DAL; + case a_THAL: return a_f_THAL; + case a_REH: return a_f_REH; + case a_ZAIN: return a_f_ZAIN; + case a_SEEN: return a_f_SEEN; + case a_SHEEN: return a_f_SHEEN; + case a_SAD: return a_f_SAD; + case a_DAD: return a_f_DAD; + case a_TAH: return a_f_TAH; + case a_ZAH: return a_f_ZAH; + case a_AIN: return a_f_AIN; + case a_GHAIN: return a_f_GHAIN; + case a_TATWEEL: return cur_c; // exception + case a_FEH: return a_f_FEH; + case a_QAF: return a_f_QAF; + case a_KAF: return a_f_KAF; + case a_LAM: return a_f_LAM; + case a_MEEM: return a_f_MEEM; + case a_NOON: return a_f_NOON; + case a_HEH: return a_f_HEH; + case a_WAW: return a_f_WAW; + case a_ALEF_MAKSURA: return a_f_ALEF_MAKSURA; + case a_YEH: return a_f_YEH; } - - return tempc; + return 0; } // Change shape - from Initial to Medial static int chg_c_i2m(int cur_c) { - int tempc; - switch (cur_c) { - case a_i_YEH_HAMZA: - tempc = a_m_YEH_HAMZA; - break; - - case a_i_BEH: - tempc = a_m_BEH; - break; - - case a_i_TEH: - tempc = a_m_TEH; - break; - - case a_i_THEH: - tempc = a_m_THEH; - break; - - case a_i_JEEM: - tempc = a_m_JEEM; - break; - - case a_i_HAH: - tempc = a_m_HAH; - break; - - case a_i_KHAH: - tempc = a_m_KHAH; - break; - - case a_i_SEEN: - tempc = a_m_SEEN; - break; - - case a_i_SHEEN: - tempc = a_m_SHEEN; - break; - - case a_i_SAD: - tempc = a_m_SAD; - break; - - case a_i_DAD: - tempc = a_m_DAD; - break; - - case a_i_TAH: - tempc = a_m_TAH; - break; - - case a_i_ZAH: - tempc = a_m_ZAH; - break; - - case a_i_AIN: - tempc = a_m_AIN; - break; - - case a_i_GHAIN: - tempc = a_m_GHAIN; - break; - - case a_i_FEH: - tempc = a_m_FEH; - break; - - case a_i_QAF: - tempc = a_m_QAF; - break; - - case a_i_KAF: - tempc = a_m_KAF; - break; - - case a_i_LAM: - tempc = a_m_LAM; - break; - - case a_i_MEEM: - tempc = a_m_MEEM; - break; - - case a_i_NOON: - tempc = a_m_NOON; - break; - - case a_i_HEH: - tempc = a_m_HEH; - break; - - case a_i_YEH: - tempc = a_m_YEH; - break; - - default: - tempc = 0; + case a_i_YEH_HAMZA: return a_m_YEH_HAMZA; + case a_i_BEH: return a_m_BEH; + case a_i_TEH: return a_m_TEH; + case a_i_THEH: return a_m_THEH; + case a_i_JEEM: return a_m_JEEM; + case a_i_HAH: return a_m_HAH; + case a_i_KHAH: return a_m_KHAH; + case a_i_SEEN: return a_m_SEEN; + case a_i_SHEEN: return a_m_SHEEN; + case a_i_SAD: return a_m_SAD; + case a_i_DAD: return a_m_DAD; + case a_i_TAH: return a_m_TAH; + case a_i_ZAH: return a_m_ZAH; + case a_i_AIN: return a_m_AIN; + case a_i_GHAIN: return a_m_GHAIN; + case a_i_FEH: return a_m_FEH; + case a_i_QAF: return a_m_QAF; + case a_i_KAF: return a_m_KAF; + case a_i_LAM: return a_m_LAM; + case a_i_MEEM: return a_m_MEEM; + case a_i_NOON: return a_m_NOON; + case a_i_HEH: return a_m_HEH; + case a_i_YEH: return a_m_YEH; } - - return tempc; + return 0; } // Change shape - from Final to Medial static int chg_c_f2m(int cur_c) { - int tempc; - switch (cur_c) { // NOTE: these encodings are multi-positional, no ? // case a_f_ALEF_MADDA: // case a_f_ALEF_HAMZA_ABOVE: // case a_f_ALEF_HAMZA_BELOW: - case a_f_YEH_HAMZA: - tempc = a_m_YEH_HAMZA; - break; - + case a_f_YEH_HAMZA: return a_m_YEH_HAMZA; case a_f_WAW_HAMZA: // exceptions case a_f_ALEF: case a_f_TEH_MARBUTA: @@ -1171,165 +628,60 @@ static int chg_c_f2m(int cur_c) case a_f_ZAIN: case a_f_WAW: case a_f_ALEF_MAKSURA: - tempc = cur_c; - break; - - case a_f_BEH: - tempc = a_m_BEH; - break; - - case a_f_TEH: - tempc = a_m_TEH; - break; - - case a_f_THEH: - tempc = a_m_THEH; - break; - - case a_f_JEEM: - tempc = a_m_JEEM; - break; - - case a_f_HAH: - tempc = a_m_HAH; - break; - - case a_f_KHAH: - tempc = a_m_KHAH; - break; - - case a_f_SEEN: - tempc = a_m_SEEN; - break; - - case a_f_SHEEN: - tempc = a_m_SHEEN; - break; - - case a_f_SAD: - tempc = a_m_SAD; - break; - - case a_f_DAD: - tempc = a_m_DAD; - break; - - case a_f_TAH: - tempc = a_m_TAH; - break; - - case a_f_ZAH: - tempc = a_m_ZAH; - break; - - case a_f_AIN: - tempc = a_m_AIN; - break; - - case a_f_GHAIN: - tempc = a_m_GHAIN; - break; - - case a_f_FEH: - tempc = a_m_FEH; - break; - - case a_f_QAF: - tempc = a_m_QAF; - break; - - case a_f_KAF: - tempc = a_m_KAF; - break; - - case a_f_LAM: - tempc = a_m_LAM; - break; - - case a_f_MEEM: - tempc = a_m_MEEM; - break; - - case a_f_NOON: - tempc = a_m_NOON; - break; - - case a_f_HEH: - tempc = a_m_HEH; - break; - - case a_f_YEH: - tempc = a_m_YEH; - break; - + return cur_c; + case a_f_BEH: return a_m_BEH; + case a_f_TEH: return a_m_TEH; + case a_f_THEH: return a_m_THEH; + case a_f_JEEM: return a_m_JEEM; + case a_f_HAH: return a_m_HAH; + case a_f_KHAH: return a_m_KHAH; + case a_f_SEEN: return a_m_SEEN; + case a_f_SHEEN: return a_m_SHEEN; + case a_f_SAD: return a_m_SAD; + case a_f_DAD: return a_m_DAD; + case a_f_TAH: return a_m_TAH; + case a_f_ZAH: return a_m_ZAH; + case a_f_AIN: return a_m_AIN; + case a_f_GHAIN: return a_m_GHAIN; + case a_f_FEH: return a_m_FEH; + case a_f_QAF: return a_m_QAF; + case a_f_KAF: return a_m_KAF; + case a_f_LAM: return a_m_LAM; + case a_f_MEEM: return a_m_MEEM; + case a_f_NOON: return a_m_NOON; + case a_f_HEH: return a_m_HEH; + case a_f_YEH: return a_m_YEH; // NOTE: these encodings are multi-positional, no ? // case a_f_LAM_ALEF_MADDA_ABOVE: // case a_f_LAM_ALEF_HAMZA_ABOVE: // case a_f_LAM_ALEF_HAMZA_BELOW: // case a_f_LAM_ALEF: - default: - tempc = 0; } - - return tempc; + return 0; } // Change shape - from Combination (2 char) to an Isolated. static int chg_c_laa2i(int hid_c) { - int tempc; - switch (hid_c) { - case a_ALEF_MADDA: - tempc = a_s_LAM_ALEF_MADDA_ABOVE; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_s_LAM_ALEF_HAMZA_ABOVE; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_s_LAM_ALEF_HAMZA_BELOW; - break; - - case a_ALEF: - tempc = a_s_LAM_ALEF; - break; - - default: - tempc = 0; + case a_ALEF_MADDA: return a_s_LAM_ALEF_MADDA_ABOVE; + case a_ALEF_HAMZA_ABOVE: return a_s_LAM_ALEF_HAMZA_ABOVE; + case a_ALEF_HAMZA_BELOW: return a_s_LAM_ALEF_HAMZA_BELOW; + case a_ALEF: return a_s_LAM_ALEF; } - - return tempc; + return 0; } // Change shape - from Combination-Isolated to Final. static int chg_c_laa2f(int hid_c) { - int tempc; - switch (hid_c) { - case a_ALEF_MADDA: - tempc = a_f_LAM_ALEF_MADDA_ABOVE; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_f_LAM_ALEF_HAMZA_ABOVE; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_f_LAM_ALEF_HAMZA_BELOW; - break; - - case a_ALEF: - tempc = a_f_LAM_ALEF; - break; - - default: - tempc = 0; + case a_ALEF_MADDA: return a_f_LAM_ALEF_MADDA_ABOVE; + case a_ALEF_HAMZA_ABOVE: return a_f_LAM_ALEF_HAMZA_ABOVE; + case a_ALEF_HAMZA_BELOW: return a_f_LAM_ALEF_HAMZA_BELOW; + case a_ALEF: return a_f_LAM_ALEF; } - - return tempc; + return 0; } // Do "half-shaping" on character "c". Return zero if no shaping. diff --git a/src/nvim/channel.c b/src/nvim/channel.c index efef95de01..265d4d8b89 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -4,6 +4,7 @@ #include "nvim/api/ui.h" #include "nvim/channel.h" #include "nvim/eval.h" +#include "nvim/eval/encode.h" #include "nvim/event/socket.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" @@ -522,32 +523,21 @@ err: return 0; } -/// NB: mutates buf in place! -static list_T *buffer_to_tv_list(char *buf, size_t count) -{ - list_T *ret = tv_list_alloc(); - char *ptr = buf; - size_t remaining = count; - size_t off = 0; - - while (off < remaining) { - // append the line - if (ptr[off] == NL) { - tv_list_append_string(ret, ptr, (ssize_t)off); - size_t skip = off + 1; - ptr += skip; - remaining -= skip; - off = 0; - continue; - } - if (ptr[off] == NUL) { - // Translate NUL to NL - ptr[off] = NL; - } - off++; - } - tv_list_append_string(ret, ptr, (ssize_t)off); - return ret; +/// Convert binary byte array to a readfile()-style list +/// +/// @param[in] buf Array to convert. +/// @param[in] len Array length. +/// +/// @return [allocated] Converted list. +static inline list_T *buffer_to_tv_list(const char *const buf, const size_t len) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE +{ + list_T *const l = tv_list_alloc(kListLenMayKnow); + // Empty buffer should be represented by [''], encode_list_write() thinks + // empty list is fine for the case. + tv_list_append_string(l, "", 0); + encode_list_write(l, buf, len); + return l; } // vimscript job callbacks must be executed on Nvim main loop diff --git a/src/nvim/eval.c b/src/nvim/eval.c index f34b6db8d8..28c590c0b4 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -402,7 +402,6 @@ static struct vimvar { VV(VV_OLDFILES, "oldfiles", VAR_LIST, 0), VV(VV_WINDOWID, "windowid", VAR_NUMBER, VV_RO_SBX), VV(VV_PROGPATH, "progpath", VAR_STRING, VV_RO), - VV(VV_COMMAND_OUTPUT, "command_output", VAR_STRING, 0), VV(VV_COMPLETED_ITEM, "completed_item", VAR_DICT, VV_RO), VV(VV_OPTION_NEW, "option_new", VAR_STRING, VV_RO), VV(VV_OPTION_OLD, "option_old", VAR_STRING, VV_RO), @@ -569,7 +568,7 @@ void eval_init(void) dict_T *const msgpack_types_dict = tv_dict_alloc(); for (size_t i = 0; i < ARRAY_SIZE(msgpack_type_names); i++) { - list_T *const type_list = tv_list_alloc(); + list_T *const type_list = tv_list_alloc(0); tv_list_set_lock(type_list, VAR_FIXED); tv_list_ref(type_list); dictitem_T *const di = tv_dict_item_alloc(msgpack_type_names[i]); @@ -592,7 +591,7 @@ void eval_init(void) dict_T *v_event = tv_dict_alloc(); v_event->dv_lock = VAR_FIXED; set_vim_var_dict(VV_EVENT, v_event); - set_vim_var_list(VV_ERRORS, tv_list_alloc()); + set_vim_var_list(VV_ERRORS, tv_list_alloc(kListLenUnknown)); set_vim_var_nr(VV_STDERR, CHAN_STDERR); set_vim_var_nr(VV_SEARCHFORWARD, 1L); set_vim_var_nr(VV_HLSEARCH, 1L); @@ -1547,6 +1546,7 @@ ex_let_vars ( assert(l != NULL); listitem_T *item = tv_list_first(l); + size_t rest_len = tv_list_len(l); while (*arg != ']') { arg = skipwhite(arg + 1); arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, (const char_u *)",;]", @@ -1554,13 +1554,14 @@ ex_let_vars ( if (arg == NULL) { return FAIL; } + rest_len--; item = TV_LIST_ITEM_NEXT(l, item); arg = skipwhite(arg); if (*arg == ';') { /* Put the rest of the list (may be empty) in the var after ';'. * Create a new list for this. */ - list_T *const rest_list = tv_list_alloc(); + list_T *const rest_list = tv_list_alloc(rest_len); while (item != NULL) { tv_list_append_tv(rest_list, TV_LIST_ITEM_TV(item)); item = TV_LIST_ITEM_NEXT(l, item); @@ -4513,7 +4514,7 @@ eval_index ( if (!empty2 && (n2 < 0 || n2 + 1 < n1)) { n2 = -1; } - l = tv_list_alloc(); + l = tv_list_alloc(n2 - n1 + 1); item = tv_list_find(rettv->vval.v_list, n1); while (n1++ <= n2) { tv_list_append_tv(l, TV_LIST_ITEM_TV(item)); @@ -4871,7 +4872,7 @@ static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate) list_T *l = NULL; if (evaluate) { - l = tv_list_alloc(); + l = tv_list_alloc(kListLenShouldKnow); } *arg = skipwhite(*arg + 1); @@ -6667,7 +6668,7 @@ static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) } rettv->v_type = VAR_STRING; } else { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, ARGCOUNT); for (idx = 0; idx < ARGCOUNT; idx++) { tv_list_append_string(rettv->vval.v_list, (const char *)alist_name(&ARGLIST[idx]), -1); @@ -6777,7 +6778,7 @@ static void assert_error(garray_T *gap) if (vp->vv_type != VAR_LIST || vimvars[VV_ERRORS].vv_list == NULL) { // Make sure v:errors is a list. - set_vim_var_list(VV_ERRORS, tv_list_alloc()); + set_vim_var_list(VV_ERRORS, tv_list_alloc(1)); } tv_list_append_string(vimvars[VV_ERRORS].vv_list, (const char *)gap->ga_data, (ptrdiff_t)gap->ga_len); @@ -8117,8 +8118,7 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) ga_append(capture_ga, NUL); rettv->v_type = VAR_STRING; - rettv->vval.v_string = vim_strsave(capture_ga->ga_data); - ga_clear(capture_ga); + rettv->vval.v_string = capture_ga->ga_data; capture_ga = save_capture_ga; } @@ -8227,7 +8227,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL); emsg_off--; if (rettv->v_type == VAR_LIST) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (result != NULL)); if (result != NULL) { tv_list_append_string(rettv->vval.v_list, (const char *)result, -1); } @@ -8250,8 +8250,8 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL); } else { - tv_list_alloc_ret(rettv); ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], -1); @@ -8268,7 +8268,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "menu_get(path [, modes])" function static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); int modes = MENU_ALL_MODES; if (argvars[1].v_type == VAR_STRING) { const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]); @@ -8429,7 +8429,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) } if (count < 0) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenUnknown); } if (*fname != NUL && !error) { @@ -9110,7 +9110,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } else if (strcmp(what, "args") == 0) { rettv->v_type = VAR_LIST; - if (tv_list_alloc_ret(rettv) != NULL) { + if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) { for (int i = 0; i < pt->pt_argc; i++) { tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]); } @@ -9134,8 +9134,10 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// Returns information about signs placed in a buffer as list of dicts. -static void get_buffer_signs(buf_T *buf, list_T *l) +static list_T *get_buffer_signs(buf_T *buf) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { + list_T *const l = tv_list_alloc(kListLenMayKnow); for (signlist_T *sign = buf->b_signlist; sign; sign = sign->next) { dict_T *const d = tv_dict_alloc(); @@ -9146,6 +9148,7 @@ static void get_buffer_signs(buf_T *buf, list_T *l) tv_list_append_dict(l, d); } + return l; } /// Returns buffer options, variables and other attributes in a dictionary. @@ -9169,7 +9172,7 @@ static dict_T *get_buffer_info(buf_T *buf) tv_dict_add_dict(dict, S_LEN("variables"), buf->b_vars); // List of windows displaying this buffer - list_T *const windows = tv_list_alloc(); + list_T *const windows = tv_list_alloc(kListLenMayKnow); FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->w_buffer == buf) { tv_list_append_number(windows, (varnumber_T)wp->handle); @@ -9179,9 +9182,7 @@ static dict_T *get_buffer_info(buf_T *buf) if (buf->b_signlist != NULL) { // List of signs placed in this buffer - list_T *const signs = tv_list_alloc(); - get_buffer_signs(buf, signs); - tv_dict_add_list(dict, S_LEN("signs"), signs); + tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf)); } return dict; @@ -9195,7 +9196,7 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool sel_buflisted = false; bool sel_bufloaded = false; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); // List of all the buffers or selected buffers if (argvars[0].v_type == VAR_DICT) { @@ -9254,35 +9255,33 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retlist, typval_T *rettv) { - char_u *p; - - rettv->v_type = VAR_STRING; + rettv->v_type = (retlist ? VAR_LIST : VAR_STRING); rettv->vval.v_string = NULL; - if (retlist) { - tv_list_alloc_ret(rettv); - } - if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0) + if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) { + if (retlist) { + tv_list_alloc_ret(rettv, 0); + } return; + } - if (!retlist) { - if (start >= 1 && start <= buf->b_ml.ml_line_count) - p = ml_get_buf(buf, start, FALSE); - else - p = (char_u *)""; - rettv->vval.v_string = vim_strsave(p); - } else { - if (end < start) - return; - - if (start < 1) + if (retlist) { + if (start < 1) { start = 1; - if (end > buf->b_ml.ml_line_count) + } + if (end > buf->b_ml.ml_line_count) { end = buf->b_ml.ml_line_count; + } + tv_list_alloc_ret(rettv, end - start + 1); while (start <= end) { tv_list_append_string(rettv->vval.v_list, (const char *)ml_get_buf(buf, start++, false), -1); } + } else { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count) + ? vim_strsave(ml_get_buf(buf, start, false)) + : NULL); } } @@ -9607,8 +9606,8 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) theend: pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); - tv_list_alloc_ret(rettv); ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], @@ -9902,7 +9901,7 @@ static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr) const linenr_T lnum = tv_get_lnum(argvars); if (argvars[1].v_type == VAR_UNKNOWN) { - end = 0; + end = lnum; retlist = false; } else { end = tv_get_lnum(&argvars[1]); @@ -9916,7 +9915,7 @@ static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T *rettv) { if (what_arg->v_type == VAR_UNKNOWN) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); if (is_qf || wp != NULL) { (void)get_errorlist(wp, -1, rettv->vval.v_list); } @@ -9951,7 +9950,7 @@ static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) matchitem_T *cur = curwin->w_match_head; int i; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); while (cur != NULL) { dict_T *dict = tv_dict_alloc(); if (cur->match.regprog == NULL) { @@ -9964,7 +9963,7 @@ static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (llpos->lnum == 0) { break; } - list_T *l = tv_list_alloc(); + list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0)); tv_list_append_number(l, (varnumber_T)llpos->lnum); if (llpos->col > 0) { tv_list_append_number(l, (varnumber_T)llpos->col); @@ -10013,7 +10012,7 @@ static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos) fp = var2fpos(&argvars[0], true, &fnum); } - list_T *l = tv_list_alloc_ret(rettv); + list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos)); tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0); tv_list_append_number(l, ((fp != NULL) ? (varnumber_T)fp->lnum @@ -10086,10 +10085,10 @@ static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (return_list) { rettv->v_type = VAR_LIST; - rettv->vval.v_list = + rettv->vval.v_list = get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList); if (rettv->vval.v_list == NULL) { - rettv->vval.v_list = tv_list_alloc(); + rettv->vval.v_list = tv_list_alloc(0); } tv_list_ref(rettv->vval.v_list); } else { @@ -10139,7 +10138,7 @@ static dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) tv_dict_add_nr(dict, S_LEN("tabnr"), tp_idx); - list_T *const l = tv_list_alloc(); + list_T *const l = tv_list_alloc(kListLenMayKnow); FOR_ALL_WINDOWS_IN_TAB(wp, tp) { tv_list_append_number(l, (varnumber_T)wp->handle); } @@ -10156,7 +10155,9 @@ static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tabpage_T *tparg = NULL; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN + ? 1 + : kListLenMayKnow)); if (argvars[0].v_type != VAR_UNKNOWN) { // Information about one tab page @@ -10255,7 +10256,7 @@ static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) { win_T *wparg = NULL; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); if (argvars[0].v_type != VAR_UNKNOWN) { wparg = win_id2wp(argvars); @@ -10480,9 +10481,9 @@ static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = ExpandOne( &xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL); } else { - tv_list_alloc_ret(rettv); ExpandOne(&xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], -1); @@ -10531,7 +10532,7 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (rettv->v_type == VAR_STRING) { rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); } else { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, ga.ga_len); for (int i = 0; i < ga.ga_len; i++) { tv_list_append_string(rettv->vval.v_list, ((const char **)(ga.ga_data))[i], -1); @@ -11112,6 +11113,7 @@ void get_user_input(const typval_T *const argvars, char defstr_buf[NUMBUFLEN]; char cancelreturn_buf[NUMBUFLEN]; char xp_name_buf[NUMBUFLEN]; + char def[1] = { 0 }; if (argvars[0].v_type == VAR_DICT) { if (argvars[1].v_type != VAR_UNKNOWN) { emsgf(_("E5050: {opts} must be the only argument")); @@ -11126,7 +11128,6 @@ void get_user_input(const typval_T *const argvars, if (defstr == NULL) { return; } - char def[1] = { 0 }; cancelreturn = tv_dict_get_string_buf_chk(dict, S_LEN("cancelreturn"), cancelreturn_buf, def); if (cancelreturn == NULL) { // error @@ -11431,7 +11432,7 @@ static void dict_list(typval_T *const tv, typval_T *const rettv, return; } - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict)); TV_DICT_ITER(tv->vval.v_dict, di, { typval_T tv = { .v_lock = VAR_UNLOCKED }; @@ -11448,7 +11449,7 @@ static void dict_list(typval_T *const tv, typval_T *const rettv, } case kDictListItems: { // items() - list_T *const sub_l = tv_list_alloc(); + list_T *const sub_l = tv_list_alloc(2); tv.v_type = VAR_LIST; tv.vval.v_list = sub_l; tv_list_ref(sub_l); @@ -11778,7 +11779,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - list_T *rv = tv_list_alloc(); + list_T *const rv = tv_list_alloc(tv_list_len(args)); // restore the parent queue for any jobs still alive for (i = 0; i < tv_list_len(args); i++) { @@ -12206,12 +12207,12 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, switch (type) { // matchlist(): return empty list when there are no matches. case kSomeMatchList: { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); break; } // matchstrpos(): return ["", -1, -1, -1] case kSomeMatchStrPos: { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, 4); tv_list_append_string(rettv->vval.v_list, "", 0); tv_list_append_number(rettv->vval.v_list, -1); tv_list_append_number(rettv->vval.v_list, -1); @@ -12518,10 +12519,12 @@ static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); - const int id = tv_get_number(&argvars[0]); + tv_list_alloc_ret(rettv, (id >= 1 && id <= 3 + ? 2 + : 0)); + if (id >= 1 && id <= 3) { matchitem_T *const m = (matchitem_T *)get_match(curwin, id); @@ -12707,8 +12710,8 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_listarg), "msgpackdump()"); return; } - list_T *ret_list = tv_list_alloc_ret(rettv); - list_T *list = argvars[0].vval.v_list; + list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); + list_T *const list = argvars[0].vval.v_list; msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write); const char *const msg = _("msgpackdump() argument, index %i"); // Assume that translation will not take more then 4 times more space @@ -12732,8 +12735,8 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_listarg), "msgpackparse()"); return; } - list_T *ret_list = tv_list_alloc_ret(rettv); - const list_T *list = argvars[0].vval.v_list; + list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); + const list_T *const list = argvars[0].vval.v_list; if (tv_list_len(list) == 0) { return; } @@ -12988,7 +12991,7 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else if (stride > 0 ? end + 1 < start : end - 1 > start) { emsgf(_("E727: Start past end")); } else { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (end - start) / stride); for (i = start; stride > 0 ? i <= end : i >= end; i += stride) { tv_list_append_number(rettv->vval.v_list, (varnumber_T)i); } @@ -13019,8 +13022,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - tv_list_alloc_ret(rettv); - list_T *const l = rettv->vval.v_list; + list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown); // Always open the file in binary mode, library functions have a mind of // their own about CR-LF conversion. @@ -13233,7 +13235,7 @@ static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr) STATIC_ASSERT(sizeof(u.prof) == sizeof(u) && sizeof(u.split) == sizeof(u), "type punning will produce incorrect results on this platform"); - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, 2); tv_list_append_number(rettv->vval.v_list, u.split.high); tv_list_append_number(rettv->vval.v_list, u.split.low); } @@ -13326,7 +13328,8 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (li == NULL) { // Didn't find "item2" after "item". emsgf(_(e_invrange)); } else { - tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv), cnt); + tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv, cnt), + cnt); } } } @@ -13356,7 +13359,7 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) { varnumber_T n = tv_get_number(&argvars[1]); if (argvars[0].v_type == VAR_LIST) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (n > 0) * n * tv_list_len(argvars[0].vval.v_list)); while (n-- > 0) { tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL); } @@ -14126,7 +14129,7 @@ static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) int lnum = 0; int col = 0; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, 2); if (searchpair_cmn(argvars, &match_pos) > 0) { lnum = match_pos.lnum; @@ -14294,18 +14297,14 @@ do_searchpair ( static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { pos_T match_pos; - int lnum = 0; - int col = 0; - int n; int flags = 0; - tv_list_alloc_ret(rettv); + const int n = search_cmn(argvars, &match_pos, &flags); - n = search_cmn(argvars, &match_pos, &flags); - if (n > 0) { - lnum = match_pos.lnum; - col = match_pos.col; - } + tv_list_alloc_ret(rettv, 2 + (!!(flags & SP_SUBPAT))); + + const int lnum = (n > 0 ? match_pos.lnum : 0); + const int col = (n > 0 ? match_pos.col : 0); tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum); tv_list_append_number(rettv->vval.v_list, (varnumber_T)col); @@ -14321,7 +14320,7 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) char **addrs = server_address_list(&n); // Copy addrs into a linked list. - list_T *l = tv_list_alloc_ret(rettv); + list_T *const l = tv_list_alloc_ret(rettv, n); for (size_t i = 0; i < n; i++) { tv_list_append_allocated_string(l, addrs[i]); } @@ -14717,7 +14716,7 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); if (di == NULL) { if (s == NULL) { - s = tv_list_alloc(); + s = tv_list_alloc(9); } // match from matchaddpos() @@ -15533,13 +15532,12 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) hlf_T attr = HLF_COUNT; size_t len = 0; - tv_list_alloc_ret(rettv); - if (argvars[0].v_type == VAR_UNKNOWN) { // Find the start and length of the badly spelled word. len = spell_move_to(curwin, FORWARD, true, true, &attr); if (len != 0) { word = (char *)get_cursor_pos_ptr(); + curwin->w_set_curswant = true; } } else if (curwin->w_p_spell && *curbuf->b_s.b_p_spl != NUL) { const char *str = tv_get_string_chk(&argvars[0]); @@ -15559,6 +15557,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) } assert(len <= INT_MAX); + tv_list_alloc_ret(rettv, 2); tv_list_append_string(rettv->vval.v_list, word, len); tv_list_append_string(rettv->vval.v_list, (attr == HLF_SPB ? "bad" @@ -15575,35 +15574,36 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool typeerr = false; int maxcount; - garray_T ga; + garray_T ga = GA_EMPTY_INIT_VALUE; bool need_capital = false; - tv_list_alloc_ret(rettv); - if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) { const char *const str = tv_get_string(&argvars[0]); if (argvars[1].v_type != VAR_UNKNOWN) { maxcount = tv_get_number_chk(&argvars[1], &typeerr); if (maxcount <= 0) { - return; + goto f_spellsuggest_return; } if (argvars[2].v_type != VAR_UNKNOWN) { need_capital = tv_get_number_chk(&argvars[2], &typeerr); if (typeerr) { - return; + goto f_spellsuggest_return; } } - } else + } else { maxcount = 25; + } spell_suggest_list(&ga, (char_u *)str, maxcount, need_capital, false); + } - for (int i = 0; i < ga.ga_len; i++) { - char *p = ((char **)ga.ga_data)[i]; - tv_list_append_allocated_string(rettv->vval.v_list, p); - } - ga_clear(&ga); +f_spellsuggest_return: + tv_list_alloc_ret(rettv, (ptrdiff_t)ga.ga_len); + for (int i = 0; i < ga.ga_len; i++) { + char *const p = ((char **)ga.ga_data)[i]; + tv_list_append_allocated_string(rettv->vval.v_list, p); } + ga_clear(&ga); } static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) @@ -15635,10 +15635,11 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) pat = "[\\x01- ]\\+"; } - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); - if (typeerr) + if (typeerr) { return; + } regmatch.regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING); if (regmatch.regprog != NULL) { @@ -16265,7 +16266,6 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) memset(str, NUL, sizeof(str)); - tv_list_alloc_ret(rettv); if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0 && (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) { (void)syn_get_id(curwin, lnum, col, false, NULL, false); @@ -16278,14 +16278,12 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) cchar = lcs_conceal; } if (cchar != NUL) { - if (has_mbyte) - (*mb_char2bytes)(cchar, str); - else - str[0] = cchar; + utf_char2bytes(cchar, str); } } } + tv_list_alloc_ret(rettv, 3); tv_list_append_number(rettv->vval.v_list, (syntax_flags & HL_CONCEAL) != 0); // -1 to auto-determine strlen tv_list_append_string(rettv->vval.v_list, (const char *)str, -1); @@ -16308,7 +16306,7 @@ static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) && lnum <= curbuf->b_ml.ml_line_count && col >= 0 && (size_t)col <= STRLEN(ml_get(lnum))) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); (void)syn_get_id(curwin, lnum, col, false, NULL, true); int id; @@ -16324,7 +16322,7 @@ static list_T *string_to_list(const char *str, size_t len, const bool keepempty) if (!keepempty && str[len - 1] == NL) { len--; } - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(kListLenMayKnow); encode_list_write(list, str, len); return list; } @@ -16370,7 +16368,7 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, if (res == NULL) { if (retlist) { // return an empty list when there's no output - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, 0); } else { rettv->vval.v_string = (char_u *) xstrdup(""); } @@ -16436,7 +16434,7 @@ static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } if (wp != NULL) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); while (wp != NULL) { tv_list_append_number(rettv->vval.v_list, wp->w_buffer->b_fnum); wp = wp->w_next; @@ -16534,7 +16532,7 @@ static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) char *fname; tagname_T tn; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenUnknown); fname = xmalloc(MAXPATHL); bool first = true; @@ -16563,8 +16561,8 @@ static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].v_type != VAR_UNKNOWN) { fname = tv_get_string(&argvars[1]); } - (void)get_tags(tv_list_alloc_ret(rettv), (char_u *)tag_pattern, - (char_u *)fname); + (void)get_tags(tv_list_alloc_ret(rettv, kListLenUnknown), + (char_u *)tag_pattern, (char_u *)fname); } /* @@ -16670,6 +16668,18 @@ static void f_test_garbagecollect_now(typval_T *argvars, garbage_collect(true); } +// "test_write_list_log()" function +static void f_test_write_list_log(typval_T *const argvars, + typval_T *const rettv, + FunPtr fptr) +{ + const char *const fname = tv_get_string_chk(&argvars[0]); + if (fname == NULL) { + return; + } + list_write_log(fname); +} + bool callback_from_typval(Callback *const callback, typval_T *const arg) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { @@ -16805,7 +16815,9 @@ static void add_timer_info_all(typval_T *rettv) /// "timer_info([timer])" function static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (argvars[0].v_type != VAR_UNKNOWN + ? 1 + : timers->table->n_occupied)); if (argvars[0].v_type != VAR_UNKNOWN) { if (argvars[0].v_type != VAR_NUMBER) { EMSG(_(e_number_exp)); @@ -17165,7 +17177,6 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_alloc_ret(rettv); dict_T *dict = rettv->vval.v_dict; - list_T *list; tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced); tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last); @@ -17175,9 +17186,7 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur); tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur); - list = tv_list_alloc(); - u_eval_tree(curbuf->b_u_oldhead, list); - tv_dict_add_list(dict, S_LEN("entries"), list); + tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead)); } /* @@ -17236,7 +17245,7 @@ static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "win_findbuf()" function static void f_win_findbuf(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); win_findbuf(argvars, rettv->vval.v_list); } @@ -17255,8 +17264,7 @@ static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "win_id2tabwin()" function static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); - win_id2tabwin(argvars, rettv->vval.v_list); + win_id2tabwin(argvars, rettv); } /// "win_id2win()" function @@ -17477,24 +17485,24 @@ write_list_error: /// Saves a typval_T as a string. /// -/// For lists, replaces NLs with NUL and separates items with NLs. +/// For lists or buffers, replaces NLs with NUL and separates items with NLs. /// -/// @param[in] tv A value to store as a string. -/// @param[out] len The length of the resulting string or -1 on error. +/// @param[in] tv Value to store as a string. +/// @param[out] len Length of the resulting string or -1 on error. /// @param[in] endnl If true, the output will end in a newline (if a list). /// @returns an allocated string if `tv` represents a VimL string, list, or /// number; NULL otherwise. static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { + *len = 0; if (tv->v_type == VAR_UNKNOWN) { - *len = 0; return NULL; } - // For types other than list, let tv_get_string_buf_chk() get the value or + // For other types, let tv_get_string_buf_chk() get the value or // print an error. - if (tv->v_type != VAR_LIST) { + if (tv->v_type != VAR_LIST && tv->v_type != VAR_NUMBER) { const char *ret = tv_get_string_chk(tv); if (ret && (*len = strlen(ret))) { return xmemdupz(ret, (size_t)(*len)); @@ -17504,8 +17512,40 @@ static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) } } + if (tv->v_type == VAR_NUMBER) { // Treat number as a buffer-id. + buf_T *buf = buflist_findnr(tv->vval.v_number); + if (buf) { + for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { + for (char_u *p = ml_get_buf(buf, lnum, false); *p != NUL; p++) { + *len += 1; + } + *len += 1; + } + } else { + EMSGN(_(e_nobufnr), tv->vval.v_number); + *len = -1; + return NULL; + } + + if (*len == 0) { + return NULL; + } + + char *ret = xmalloc(*len + 1); + char *end = ret; + for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { + for (char_u *p = ml_get_buf(buf, lnum, false); *p != NUL; p++) { + *end++ = (*p == '\n') ? NUL : *p; + } + *end++ = '\n'; + } + *end = NUL; + *len = end - ret; + return ret; + } + + assert(tv->v_type == VAR_LIST); // Pre-calculate the resulting length. - *len = 0; list_T *list = tv->vval.v_list; TV_LIST_ITER_CONST(list, li, { *len += strlen(tv_get_string(TV_LIST_ITEM_TV(li))) + 1; @@ -22369,7 +22409,7 @@ static void script_host_eval(char *name, typval_T *argvars, typval_T *rettv) return; } - list_T *args = tv_list_alloc(); + list_T *args = tv_list_alloc(1); tv_list_append_string(args, (const char *)argvars[0].vval.v_string, -1); *rettv = eval_call_provider(name, "eval", args); } diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 0c0a6881f6..b798eae187 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -86,7 +86,6 @@ typedef enum { VV_OLDFILES, VV_WINDOWID, VV_PROGPATH, - VV_COMMAND_OUTPUT, VV_COMPLETED_ITEM, VV_OPTION_NEW, VV_OPTION_OLD, diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 54cbc54d78..daa3b637a3 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -313,6 +313,7 @@ return { tempname={}, termopen={args={1, 2}}, test_garbagecollect_now={}, + test_write_list_log={args=1}, timer_info={args={0,1}}, timer_pause={args=2}, timer_start={args={2,3}}, diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index af4e055d23..17799b500c 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -150,7 +150,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, } obj_di->di_tv = obj.val; } else { - list_T *const kv_pair = tv_list_alloc(); + list_T *const kv_pair = tv_list_alloc(2); tv_list_append_list(last_container.special_val, kv_pair); tv_list_append_owned_tv(kv_pair, key.val); tv_list_append_owned_tv(kv_pair, obj.val); @@ -221,13 +221,18 @@ static inline int json_decoder_pop(ValuesStackItem obj, /// Create a new special dictionary that ought to represent a MAP /// /// @param[out] ret_tv Address where new special dictionary is saved. +/// @param[in] len Expected number of items to be populated before list +/// becomes accessible from VimL. It is still valid to +/// underpopulate a list, value only controls how many elements +/// will be allocated in advance. @see ListLenSpecials. /// /// @return [allocated] list which should contain key-value pairs. Return value /// may be safely ignored. -list_T *decode_create_map_special_dict(typval_T *const ret_tv) +list_T *decode_create_map_special_dict(typval_T *const ret_tv, + const ptrdiff_t len) FUNC_ATTR_NONNULL_ALL { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(len); tv_list_ref(list); create_special_dict(ret_tv, kMPMap, ((typval_T) { .v_type = VAR_LIST, @@ -263,7 +268,7 @@ typval_T decode_string(const char *const s, const size_t len, ? ((s != NULL) && (memchr(s, NUL, len) != NULL)) : (bool)hasnul); if (really_hasnul) { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(kListLenMayKnow); tv_list_ref(list); typval_T tv; create_special_dict(&tv, binary ? kMPBinary : kMPString, ((typval_T) { @@ -285,7 +290,7 @@ typval_T decode_string(const char *const s, const size_t len, .v_type = VAR_STRING, .v_lock = VAR_UNLOCKED, .vval = { .v_string = (char_u *)( - s_allocated ? (char *)s : xmemdupz(s, len)) }, + (s == NULL || s_allocated) ? (char *)s : xmemdupz(s, len)) }, }; } } @@ -843,7 +848,7 @@ json_decode_string_cycle_start: break; } case '[': { - list_T *list = tv_list_alloc(); + list_T *list = tv_list_alloc(kListLenMayKnow); tv_list_ref(list); typval_T tv = { .v_type = VAR_LIST, @@ -864,7 +869,7 @@ json_decode_string_cycle_start: list_T *val_list = NULL; if (next_map_special) { next_map_special = false; - val_list = decode_create_map_special_dict(&tv); + val_list = decode_create_map_special_dict(&tv, kListLenMayKnow); } else { dict_T *dict = tv_dict_alloc(); dict->dv_refcount++; @@ -964,7 +969,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) .vval = { .v_number = (varnumber_T) mobj.via.u64 }, }; } else { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(4); tv_list_ref(list); create_special_dict(rettv, kMPInteger, ((typval_T) { .v_type = VAR_LIST, @@ -987,7 +992,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) .vval = { .v_number = (varnumber_T) mobj.via.i64 }, }; } else { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(4); tv_list_ref(list); create_special_dict(rettv, kMPInteger, ((typval_T) { .v_type = VAR_LIST, @@ -1033,7 +1038,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) break; } case MSGPACK_OBJECT_ARRAY: { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)mobj.via.array.size); tv_list_ref(list); *rettv = (typval_T) { .v_type = VAR_LIST, @@ -1085,9 +1090,10 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) } break; msgpack_to_vim_generic_map: {} - list_T *const list = decode_create_map_special_dict(rettv); + list_T *const list = decode_create_map_special_dict( + rettv, (ptrdiff_t)mobj.via.map.size); for (size_t i = 0; i < mobj.via.map.size; i++) { - list_T *const kv_pair = tv_list_alloc(); + list_T *const kv_pair = tv_list_alloc(2); tv_list_append_list(list, kv_pair); typval_T key_tv = { .v_type = VAR_UNKNOWN }; @@ -1107,10 +1113,10 @@ msgpack_to_vim_generic_map: {} break; } case MSGPACK_OBJECT_EXT: { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(2); tv_list_ref(list); tv_list_append_number(list, mobj.via.ext.type); - list_T *const ext_val_list = tv_list_alloc(); + list_T *const ext_val_list = tv_list_alloc(kListLenMayKnow); tv_list_append_list(list, ext_val_list); create_special_dict(rettv, kMPExt, ((typval_T) { .v_type = VAR_LIST, diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index ac6c8c8aa6..c8b550f902 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -31,6 +31,7 @@ #include "nvim/message.h" // TODO(ZyX-I): Move line_breakcheck out of misc1 #include "nvim/misc1.h" // For line_breakcheck +#include "nvim/os/fileio.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/typval.c.generated.h" @@ -45,6 +46,70 @@ bool tv_in_free_unref_items = false; const char *const tv_empty_string = ""; //{{{1 Lists +//{{{2 List log +#ifdef LOG_LIST_ACTIONS +ListLog *list_log_first = NULL; +ListLog *list_log_last = NULL; + +/// Write list log to the given file +/// +/// @param[in] fname File to write log to. Will be appended to if already +/// present. +void list_write_log(const char *const fname) + FUNC_ATTR_NONNULL_ALL +{ + FileDescriptor fp; + const int fo_ret = file_open(&fp, fname, kFileCreate|kFileAppend, 0600); + if (fo_ret != 0) { + emsgf(_("E5142: Failed to open file %s: %s"), fname, os_strerror(fo_ret)); + return; + } + for (ListLog *chunk = list_log_first; chunk != NULL;) { + for (size_t i = 0; i < chunk->size; i++) { + char buf[10 + 1 + ((16 + 3) * 3) + (8 + 2) + 2]; + // act : hex " c:" len "[]" "\n\0" + const ListLogEntry entry = chunk->entries[i]; + const size_t snp_len = (size_t)snprintf( + buf, sizeof(buf), + "%-10.10s: l:%016" PRIxPTR "[%08d] 1:%016" PRIxPTR " 2:%016" PRIxPTR + "\n", + entry.action, entry.l, entry.len, entry.li1, entry.li2); + assert(snp_len + 1 == sizeof(buf)); + const ptrdiff_t fw_ret = file_write(&fp, buf, snp_len); + if (fw_ret != (ptrdiff_t)snp_len) { + assert(fw_ret < 0); + if (i) { + memmove(chunk->entries, chunk->entries + i, + sizeof(chunk->entries[0]) * (chunk->size - i)); + chunk->size -= i; + } + emsgf(_("E5143: Failed to write to file %s: %s"), + fname, os_strerror((int)fw_ret)); + return; + } + } + list_log_first = chunk->next; + xfree(chunk); + chunk = list_log_first; + } + const int fc_ret = file_close(&fp, true); + if (fc_ret != 0) { + emsgf(_("E5144: Failed to close file %s: %s"), fname, os_strerror(fc_ret)); + } +} + +#ifdef EXITFREE +/// Free list log +void list_free_log(void) +{ + for (ListLog *chunk = list_log_first; chunk != NULL;) { + list_log_first = chunk->next; + xfree(chunk); + chunk = list_log_first; + } +} +#endif +#endif //{{{2 List item /// Allocate a list item @@ -132,8 +197,14 @@ void tv_list_watch_fix(list_T *const l, const listitem_T *const item) /// /// Caller should take care of the reference count. /// +/// @param[in] len Expected number of items to be populated before list +/// becomes accessible from VimL. It is still valid to +/// underpopulate a list, value only controls how many elements +/// will be allocated in advance. Currently does nothing. +/// @see ListLenSpecials. +/// /// @return [allocated] new list. -list_T *tv_list_alloc(void) +list_T *tv_list_alloc(const ptrdiff_t len) FUNC_ATTR_NONNULL_RET { list_T *const list = xcalloc(1, sizeof(list_T)); @@ -145,6 +216,7 @@ list_T *tv_list_alloc(void) list->lv_used_prev = NULL; list->lv_used_next = gc_first_list; gc_first_list = list; + list_log(list, NULL, (void *)(uintptr_t)len, "alloc"); return list; } @@ -174,6 +246,8 @@ void tv_list_init_static10(staticList10_T *const sl) li->li_prev = li - 1; li->li_next = li + 1; } + list_log((const list_T *)sl, &sl->sl_items[0], &sl->sl_items[SL_SIZE - 1], + "s10init"); #undef SL_SIZE } @@ -185,6 +259,7 @@ void tv_list_init_static(list_T *const l) { memset(l, 0, sizeof(*l)); l->lv_refcount = DO_NOT_FREE_CNT; + list_log(l, NULL, NULL, "sinit"); } /// Free items contained in a list @@ -193,6 +268,7 @@ void tv_list_init_static(list_T *const l) void tv_list_free_contents(list_T *const l) FUNC_ATTR_NONNULL_ALL { + list_log(l, NULL, NULL, "freecont"); for (listitem_T *item = l->lv_first; item != NULL; item = l->lv_first) { // Remove the item before deleting it. l->lv_first = item->li_next; @@ -222,6 +298,7 @@ void tv_list_free_list(list_T *const l) if (l->lv_used_next != NULL) { l->lv_used_next->lv_used_prev = l->lv_used_prev; } + list_log(l, NULL, NULL, "freelist"); xfree(l); } @@ -266,6 +343,7 @@ void tv_list_drop_items(list_T *const l, listitem_T *const item, listitem_T *const item2) FUNC_ATTR_NONNULL_ALL { + list_log(l, item, item2, "drop"); // Notify watchers. for (listitem_T *ip = item; ip != item2->li_next; ip = ip->li_next) { l->lv_len--; @@ -283,6 +361,7 @@ void tv_list_drop_items(list_T *const l, listitem_T *const item, item->li_prev->li_next = item2->li_next; } l->lv_idx_item = NULL; + list_log(l, l->lv_first, l->lv_last, "afterdrop"); } /// Like tv_list_drop_items, but also frees all removed items @@ -290,6 +369,7 @@ void tv_list_remove_items(list_T *const l, listitem_T *const item, listitem_T *const item2) FUNC_ATTR_NONNULL_ALL { + list_log(l, item, item2, "remove"); tv_list_drop_items(l, item, item2); for (listitem_T *li = item;;) { tv_clear(TV_LIST_ITEM_TV(li)); @@ -314,6 +394,7 @@ void tv_list_move_items(list_T *const l, listitem_T *const item, const int cnt) FUNC_ATTR_NONNULL_ALL { + list_log(l, item, item2, "move"); tv_list_drop_items(l, item, item2); item->li_prev = tgt_l->lv_last; item2->li_next = NULL; @@ -324,6 +405,7 @@ void tv_list_move_items(list_T *const l, listitem_T *const item, } tgt_l->lv_last = item2; tgt_l->lv_len += cnt; + list_log(tgt_l, tgt_l->lv_first, tgt_l->lv_last, "movetgt"); } /// Insert list item @@ -352,6 +434,7 @@ void tv_list_insert(list_T *const l, listitem_T *const ni, } item->li_prev = ni; l->lv_len++; + list_log(l, ni, item, "insert"); } } @@ -378,6 +461,7 @@ void tv_list_insert_tv(list_T *const l, typval_T *const tv, void tv_list_append(list_T *const l, listitem_T *const item) FUNC_ATTR_NONNULL_ALL { + list_log(l, item, NULL, "append"); if (l->lv_last == NULL) { // empty list l->lv_first = item; @@ -521,7 +605,7 @@ list_T *tv_list_copy(const vimconv_T *const conv, list_T *const orig, return NULL; } - list_T *copy = tv_list_alloc(); + list_T *copy = tv_list_alloc(tv_list_len(orig)); tv_list_ref(copy); if (copyID != 0) { // Do this before adding the items, because one of the items may @@ -741,6 +825,7 @@ void tv_list_reverse(list_T *const l) if (tv_list_len(l) <= 1) { return; } + list_log(l, NULL, NULL, "reverse"); #define SWAP(a, b) \ do { \ tmp = a; \ @@ -779,6 +864,7 @@ void tv_list_item_sort(list_T *const l, ListSortItem *const ptrs, if (len <= 1) { return; } + list_log(l, NULL, NULL, "sort"); int i = 0; TV_LIST_ITER(l, li, { ptrs[i].item = li; @@ -867,6 +953,7 @@ listitem_T *tv_list_find(list_T *const l, int n) // Cache the used index. l->lv_idx = idx; l->lv_idx_item = item; + list_log(l, l->lv_idx_item, (void *)(uintptr_t)l->lv_idx, "find"); return item; } @@ -1817,12 +1904,16 @@ void tv_dict_set_keys_readonly(dict_T *const dict) /// Also sets reference count. /// /// @param[out] ret_tv Structure where list is saved. +/// @param[in] len Expected number of items to be populated before list +/// becomes accessible from VimL. It is still valid to +/// underpopulate a list, value only controls how many elements +/// will be allocated in advance. @see ListLenSpecials. /// /// @return [allocated] pointer to the created list. -list_T *tv_list_alloc_ret(typval_T *const ret_tv) +list_T *tv_list_alloc_ret(typval_T *const ret_tv, const ptrdiff_t len) FUNC_ATTR_NONNULL_ALL { - list_T *const l = tv_list_alloc(); + list_T *const l = tv_list_alloc(len); ret_tv->vval.v_list = l; ret_tv->v_type = VAR_LIST; ret_tv->v_lock = VAR_UNLOCKED; diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index c9a9a3e7e8..40a1738d9e 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -20,6 +20,9 @@ #include "nvim/gettext.h" #include "nvim/message.h" #include "nvim/macros.h" +#ifdef LOG_LIST_ACTIONS +# include "nvim/memory.h" +#endif /// Type used for VimL VAR_NUMBER values typedef int64_t varnumber_T; @@ -31,6 +34,25 @@ typedef double float_T; /// Refcount for dict or list that should not be freed enum { DO_NOT_FREE_CNT = (INT_MAX / 2) }; +/// Additional values for tv_list_alloc() len argument +enum { + /// List length is not known in advance + /// + /// To be used when there is neither a way to know how many elements will be + /// needed nor are any educated guesses. + kListLenUnknown = -1, + /// List length *should* be known, but is actually not + /// + /// All occurrences of this value should be eventually removed. This is for + /// the case when the only reason why list length is not known is that it + /// would be hard to code without refactoring, but refactoring is needed. + kListLenShouldKnow = -2, + /// List length may be known in advance, but it requires too much effort + /// + /// To be used when it looks impractical to determine list length. + kListLenMayKnow = -3, +} ListLenSpecials; + /// Maximal possible value of varnumber_T variable #define VARNUMBER_MAX INT64_MAX #define UVARNUMBER_MAX UINT64_MAX @@ -304,6 +326,96 @@ typedef struct { typedef int (*ListSorter)(const void *, const void *); +#ifdef LOG_LIST_ACTIONS + +/// List actions log entry +typedef struct { + uintptr_t l; ///< List log entry belongs to. + uintptr_t li1; ///< First list item log entry belongs to, if applicable. + uintptr_t li2; ///< Second list item log entry belongs to, if applicable. + int len; ///< List length when log entry was created. + const char *action; ///< Logged action. +} ListLogEntry; + +typedef struct list_log ListLog; + +/// List actions log +struct list_log { + ListLog *next; ///< Next chunk or NULL. + size_t capacity; ///< Number of entries in current chunk. + size_t size; ///< Current chunk size. + ListLogEntry entries[]; ///< Actual log entries. +}; + +extern ListLog *list_log_first; ///< First list log chunk, NULL if missing +extern ListLog *list_log_last; ///< Last list log chunk + +static inline ListLog *list_log_alloc(const size_t size) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Allocate a new log chunk and update globals +/// +/// @param[in] size Number of entries in a new chunk. +/// +/// @return [allocated] Newly allocated chunk. +static inline ListLog *list_log_new(const size_t size) +{ + ListLog *ret = xmalloc(offsetof(ListLog, entries) + + size * sizeof(ret->entries[0])); + ret->size = 0; + ret->capacity = size; + ret->next = NULL; + if (list_log_first == NULL) { + list_log_first = ret; + } else { + list_log_last->next = ret; + } + list_log_last = ret; + return ret; +} + +static inline void list_log(const list_T *const l, + const listitem_T *const li1, + const listitem_T *const li2, + const char *const action) + REAL_FATTR_ALWAYS_INLINE; + +/// Add new entry to log +/// +/// If last chunk was filled it uses twice as much memory to allocate the next +/// chunk. +/// +/// @param[in] l List to which entry belongs. +/// @param[in] li1 List item 1. +/// @param[in] li2 List item 2, often used for integers and not list items. +/// @param[in] action Logged action. +static inline void list_log(const list_T *const l, + const listitem_T *const li1, + const listitem_T *const li2, + const char *const action) +{ + ListLog *tgt; + if (list_log_first == NULL) { + tgt = list_log_new(128); + } else if (list_log_last->size == list_log_last->capacity) { + tgt = list_log_new(list_log_last->capacity * 2); + } else { + tgt = list_log_last; + } + tgt->entries[tgt->size++] = (ListLogEntry) { + .l = (uintptr_t)l, + .li1 = (uintptr_t)li1, + .li2 = (uintptr_t)li2, + .len = (l == NULL ? 0 : l->lv_len), + .action = action, + }; +} +#else +# define list_log(...) +# define list_write_log(...) +# define list_free_log() +#endif + // In a hashtab item "hi_key" points to "di_key" in a dictitem. // This avoids adding a pointer to the hashtab item. @@ -377,6 +489,7 @@ static inline int tv_list_len(const list_T *const l) /// @param[in] l List to check. static inline int tv_list_len(const list_T *const l) { + list_log(l, NULL, NULL, "len"); if (l == NULL) { return 0; } @@ -460,8 +573,10 @@ static inline listitem_T *tv_list_first(const list_T *const l) static inline listitem_T *tv_list_first(const list_T *const l) { if (l == NULL) { + list_log(l, NULL, NULL, "first"); return NULL; } + list_log(l, l->lv_first, NULL, "first"); return l->lv_first; } @@ -476,8 +591,10 @@ static inline listitem_T *tv_list_last(const list_T *const l) static inline listitem_T *tv_list_last(const list_T *const l) { if (l == NULL) { + list_log(l, NULL, NULL, "last"); return NULL; } + list_log(l, l->lv_last, NULL, "last"); return l->lv_last; } @@ -545,6 +662,7 @@ extern bool tv_in_free_unref_items; #define _TV_LIST_ITER_MOD(modifier, l, li, code) \ do { \ modifier list_T *const l_ = (l); \ + list_log(l_, NULL, NULL, "iter" #modifier); \ if (l_ != NULL) { \ for (modifier listitem_T *li = l_->lv_first; \ li != NULL; li = li->li_next) { \ diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index c5825963c0..06379f159c 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3541,6 +3541,9 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) getvcol(curwin, &curwin->w_cursor, &sc, NULL, NULL); curwin->w_cursor.col = regmatch.endpos[0].col - 1; + if (curwin->w_cursor.col < 0) { + curwin->w_cursor.col = 0; + } getvcol(curwin, &curwin->w_cursor, NULL, NULL, &ec); if (subflags.do_number || curwin->w_p_nu) { int numw = number_width(curwin) + 1; diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index ec4ce63e17..28b021d4e4 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -3758,7 +3758,7 @@ static void script_host_execute(char *name, exarg_T *eap) char *const script = script_get(eap, &len); if (script != NULL) { - list_T *const args = tv_list_alloc(); + list_T *const args = tv_list_alloc(3); // script tv_list_append_allocated_string(args, script); // current range @@ -3773,7 +3773,7 @@ static void script_host_execute_file(char *name, exarg_T *eap) uint8_t buffer[MAXPATHL]; vim_FullName((char *)eap->arg, (char *)buffer, sizeof(buffer), false); - list_T *args = tv_list_alloc(); + list_T *args = tv_list_alloc(3); // filename tv_list_append_string(args, (const char *)buffer, -1); // current range @@ -3784,7 +3784,7 @@ static void script_host_execute_file(char *name, exarg_T *eap) static void script_host_do_range(char *name, exarg_T *eap) { - list_T *args = tv_list_alloc(); + list_T *args = tv_list_alloc(3); tv_list_append_number(args, (int)eap->line1); tv_list_append_number(args, (int)eap->line2); tv_list_append_string(args, (const char *)eap->arg, -1); diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index a5a8804e1c..82fac8d78e 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2164,7 +2164,13 @@ getexmodeline ( /* Get one character at a time. Don't use inchar(), it can't handle * special characters. */ prev_char = c1; - c1 = vgetc(); + + // Check for a ":normal" command and no more characters left. + if (ex_normal_busy > 0 && typebuf.tb_len == 0) { + c1 = '\n'; + } else { + c1 = vgetc(); + } /* * Handle line editing. diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 1b5d3472ab..7df1bf8429 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -3366,6 +3366,10 @@ set_context_in_map_cmd ( arg = skipwhite(arg + 8); continue; } + if (STRNCMP(arg, "<special>", 9) == 0) { + arg = skipwhite(arg + 9); + continue; + } if (STRNCMP(arg, "<script>", 8) == 0) { arg = skipwhite(arg + 8); continue; @@ -3408,21 +3412,24 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file) for (round = 1; round <= 2; ++round) { count = 0; - for (i = 0; i < 6; ++i) { - if (i == 0) + for (i = 0; i < 7; i++) { + if (i == 0) { p = (char_u *)"<silent>"; - else if (i == 1) + } else if (i == 1) { p = (char_u *)"<unique>"; - else if (i == 2) + } else if (i == 2) { p = (char_u *)"<script>"; - else if (i == 3) + } else if (i == 3) { p = (char_u *)"<expr>"; - else if (i == 4 && !expand_buffer) + } else if (i == 4 && !expand_buffer) { p = (char_u *)"<buffer>"; - else if (i == 5) + } else if (i == 5) { p = (char_u *)"<nowait>"; - else + } else if (i == 6) { + p = (char_u *)"<special>"; + } else { continue; + } if (vim_regexec(regmatch, p, (colnr_T)0)) { if (round == 1) diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 0eb5a41b1e..eb0653c432 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -481,7 +481,7 @@ char_u *get_special_key_name(int c, int modifiers) return string; } -/// Try translating a <> name +/// Try translating a <> name ("keycode"). /// /// @param[in,out] srcp Source from which <> are translated. Is advanced to /// after the <> name if there is a match. @@ -619,9 +619,11 @@ int find_special_key(const char_u **srcp, const size_t src_len, int *const modp, // Modifier with single letter, or special key name. if (in_string && last_dash[1] == '\\' && last_dash[2] == '"') { - off = 2; + // Special case for a double-quoted string + off = l = 2; + } else { + l = mb_ptr2len(last_dash + 1); } - l = mb_ptr2len(last_dash + 1); if (modifiers != 0 && last_dash[l + 1] == '>') { key = PTR2CHAR(last_dash + off); } else { @@ -834,7 +836,7 @@ char_u *replace_termcodes(const char_u *from, const size_t from_len, } slen = trans_special(&src, (size_t)(end - src) + 1, result + dlen, true, - true); + false); if (slen) { dlen += slen; continue; diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 61cb428923..9e3063b164 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -211,7 +211,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) size_t len; const char *s = lua_tolstring(lstate, -2, &len); if (cur.special) { - list_T *const kv_pair = tv_list_alloc(); + list_T *const kv_pair = tv_list_alloc(2); typval_T s_tv = decode_string(s, len, kTrue, false, false); if (s_tv.v_type == VAR_UNKNOWN) { @@ -321,7 +321,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) switch (table_props.type) { case kObjectTypeArray: { cur.tv->v_type = VAR_LIST; - cur.tv->vval.v_list = tv_list_alloc(); + cur.tv->vval.v_list = tv_list_alloc((ptrdiff_t)table_props.maxidx); tv_list_ref(cur.tv->vval.v_list); if (table_props.maxidx != 0) { cur.container = true; @@ -338,7 +338,8 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) } else { cur.special = table_props.has_string_with_nul; if (table_props.has_string_with_nul) { - decode_create_map_special_dict(cur.tv); + decode_create_map_special_dict( + cur.tv, (ptrdiff_t)table_props.string_keys_num); assert(cur.tv->v_type == VAR_DICT); dictitem_T *const val_di = tv_dict_find(cur.tv->vval.v_dict, S_LEN("_VAL")); diff --git a/src/nvim/main.c b/src/nvim/main.c index 0b24023ad0..015df5d070 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -412,7 +412,7 @@ int main(int argc, char **argv) } // It's better to make v:oldfiles an empty list than NULL. if (get_vim_var_list(VV_OLDFILES) == NULL) { - set_vim_var_list(VV_OLDFILES, tv_list_alloc()); + set_vim_var_list(VV_OLDFILES, tv_list_alloc(0)); } /* diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 328b96fd5c..a66ab6a3cc 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -559,6 +559,7 @@ void time_to_bytes(time_t time_, uint8_t buf[8]) #include "nvim/tag.h" #include "nvim/window.h" #include "nvim/os/os.h" +#include "nvim/eval/typval.h" /* * Free everything that we allocated. @@ -692,6 +693,7 @@ void free_all_mem(void) free_screenlines(); clear_hl_tables(); + list_free_log(); } #endif diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 01c8e94bac..42417f75d5 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -714,7 +714,7 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) } } else { // visit recursively all children - list_T *children_list = tv_list_alloc(); + list_T *const children_list = tv_list_alloc(kListLenMayKnow); for (menu = menu->children; menu != NULL; menu = menu->next) { dict_T *dic = menu_get_recursive(menu, modes); if (tv_dict_len(dict) > 0) { diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index f7ee2950ef..a498c7f4da 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -749,8 +749,9 @@ open_line ( // Postpone calling changed_lines(), because it would mess up folding // with markers. // Skip mark_adjust when adding a line after the last one, there can't - // be marks there. - if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count) { + // be marks there. But still needed in diff mode. + if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count + || curwin->w_p_diff) { mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false); } did_append = true; @@ -1864,8 +1865,8 @@ void appended_lines(linenr_T lnum, long count) void appended_lines_mark(linenr_T lnum, long count) { // Skip mark_adjust when adding a line after the last one, there can't - // be marks there. - if (lnum + count < curbuf->b_ml.ml_line_count) { + // be marks there. But it's still needed in diff mode. + 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); diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 1103fe15d2..ab20be7246 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -5687,6 +5687,8 @@ static void nv_brackets(cmdarg_T *cap) cap->nchar == 's', false, NULL) == 0) { clearopbeep(cap->oap); break; + } else { + curwin->w_set_curswant = true; } if (cap->oap->op_type == OP_NOP && (fdo_flags & FDO_SEARCH) && KeyTyped) foldOpenCursor(); diff --git a/src/nvim/ops.c b/src/nvim/ops.c index a5e131190d..3a338b1417 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -2564,7 +2564,7 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) dict_T *dict = get_vim_var_dict(VV_EVENT); // the yanked text - list_T *list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(list, (const char *)reg->y_array[i], -1); } @@ -3177,9 +3177,9 @@ error: curbuf->b_op_start.lnum++; } // Skip mark_adjust when adding lines after the last one, there - // can't be marks there. + // can't be marks there. But still needed in diff mode. if (curbuf->b_op_start.lnum + (y_type == kMTCharWise) - 1 + nr_lines - < curbuf->b_ml.ml_line_count) { + < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise), (linenr_T)MAXLNUM, nr_lines, 0L, false); } @@ -4854,7 +4854,7 @@ static void *get_reg_wrap_one_line(char_u *s, int flags) if (!(flags & kGRegList)) { return s; } - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(1); tv_list_append_allocated_string(list, (char *)s); return list; } @@ -4904,7 +4904,7 @@ void *get_reg_contents(int regname, int flags) return NULL; if (flags & kGRegList) { - list_T *list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(list, (const char *)reg->y_array[i], -1); } @@ -5593,7 +5593,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) } free_register(reg); - list_T *const args = tv_list_alloc(); + list_T *const args = tv_list_alloc(1); const char regname = (char)name; tv_list_append_string(args, ®name, 1); @@ -5712,15 +5712,13 @@ static void set_clipboard(int name, yankreg_T *reg) return; } - list_T *lines = tv_list_alloc(); + list_T *const lines = tv_list_alloc( + (ptrdiff_t)reg->y_size + (reg->y_type != kMTCharWise)); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(lines, (const char *)reg->y_array[i], -1); } - list_T *args = tv_list_alloc(); - tv_list_append_list(args, lines); - char regtype; switch (reg->y_type) { case kMTLineWise: { @@ -5741,10 +5739,11 @@ static void set_clipboard(int name, yankreg_T *reg) assert(false); } } - tv_list_append_string(args, ®type, 1); - const char regname = (char)name; - tv_list_append_string(args, ®name, 1); + list_T *args = tv_list_alloc(3); + tv_list_append_list(args, lines); + tv_list_append_string(args, ®type, 1); + tv_list_append_string(args, ((char[]) { (char)name }), 1); (void)eval_call_provider("clipboard", "set", args); } diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index 5d68473982..d294f9139b 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -39,9 +39,9 @@ /// @param[in] fname File name to open. /// @param[in] flags Flags, @see FileOpenFlags. Currently reading from and /// writing to the file at once is not supported, so either -/// FILE_WRITE_ONLY or FILE_READ_ONLY is required. +/// kFileWriteOnly or kFileReadOnly is required. /// @param[in] mode Permissions for the newly created file (ignored if flags -/// does not have FILE_CREATE\*). +/// does not have kFileCreate\*). /// /// @return Error code (@see os_strerror()) or 0. int file_open(FileDescriptor *const ret_fp, const char *const fname, @@ -120,7 +120,7 @@ int file_open_fd(FileDescriptor *const ret_fp, const int fd, const bool wr) /// @param[in] fname File name to open. /// @param[in] flags Flags, @see FileOpenFlags. /// @param[in] mode Permissions for the newly created file (ignored if flags -/// does not have FILE_CREATE\*). +/// does not have kFileCreate\*). /// /// @return [allocated] Opened file or NULL in case of error. FileDescriptor *file_open_new(int *const error, const char *const fname, diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 7d6f2abd7f..405500767d 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -188,7 +188,7 @@ size_t input_enqueue(String keys) uint8_t buf[6] = { 0 }; unsigned int new_size = trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, true, - true); + false); if (new_size) { new_size = handle_mouse_event(&ptr, buf, new_size); diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 224e43008d..63252df3dc 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4193,7 +4193,7 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) } } if ((status == OK) && (flags & QF_GETLIST_ITEMS)) { - list_T *l = tv_list_alloc(); + list_T *l = tv_list_alloc(kListLenMayKnow); (void)get_errorlist(wp, qf_idx, l); tv_dict_add_list(retdict, S_LEN("items"), l); } diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index ddc3681867..e4de43b49e 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -7044,7 +7044,7 @@ list_T *reg_submatch_list(int no) colnr_T scol = rsm.sm_mmatch->startpos[no].col; colnr_T ecol = rsm.sm_mmatch->endpos[no].col; - list = tv_list_alloc(); + list = tv_list_alloc(elnum - slnum + 1); s = (const char *)reg_getline_submatch(slnum) + scol; if (slnum == elnum) { @@ -7063,7 +7063,7 @@ list_T *reg_submatch_list(int no) if (s == NULL || rsm.sm_match->endp[no] == NULL) { return NULL; } - list = tv_list_alloc(); + list = tv_list_alloc(1); tv_list_append_string(list, s, (const char *)rsm.sm_match->endp[no] - s); } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index ed96e98d32..8a29734025 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -3143,14 +3143,15 @@ win_line ( } --n_extra; } else { + int c0; + if (p_extra_free != NULL) { xfree(p_extra_free); p_extra_free = NULL; } - /* - * Get a character from the line itself. - */ - c = *ptr; + + // Get a character from the line itself. + c0 = c = *ptr; if (has_mbyte) { mb_c = c; if (enc_utf8) { @@ -3160,11 +3161,12 @@ win_line ( mb_utf8 = FALSE; if (mb_l > 1) { mb_c = utfc_ptr2char(ptr, u8cc); - /* Overlong encoded ASCII or ASCII with composing char - * is displayed normally, except a NUL. */ - if (mb_c < 0x80) - c = mb_c; - mb_utf8 = TRUE; + // Overlong encoded ASCII or ASCII with composing char + // is displayed normally, except a NUL. + if (mb_c < 0x80) { + c0 = c = mb_c; + } + mb_utf8 = true; /* At start of the line we can have a composing char. * Draw it as a space with a composing char. */ @@ -3428,10 +3430,8 @@ win_line ( char_attr = hl_combine_attr(char_attr, term_attrs[vcol]); } - /* - * Found last space before word: check for line break. - */ - if (wp->w_p_lbr && vim_isbreak(c) && !vim_isbreak(*ptr)) { + // Found last space before word: check for line break. + if (wp->w_p_lbr && c0 == c && vim_isbreak(c) && !vim_isbreak(*ptr)) { int mb_off = has_mbyte ? (*mb_head_off)(line, ptr - 1) : 0; char_u *p = ptr - (mb_off + 1); // TODO: is passing p for start of the line OK? diff --git a/src/nvim/search.c b/src/nvim/search.c index 1eb1a25a19..0a266382ec 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -1382,13 +1382,13 @@ int searchc(cmdarg_T *cap, int t_cmd) col -= (*mb_head_off)(p, p + col - 1) + 1; } if (lastc_bytelen == 1) { - if (p[col] == c && stop) + if (p[col] == c && stop) { break; - } else { - if (memcmp(p + col, lastc_bytes, lastc_bytelen) == 0 && stop) + } + } else if (STRNCMP(p + col, lastc_bytes, lastc_bytelen) == 0 && stop) { break; } - stop = TRUE; + stop = true; } } else { for (;; ) { diff --git a/src/nvim/shada.c b/src/nvim/shada.c index ce9303f14d..c00fd912ec 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1217,7 +1217,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) khash_t(fnamebufs) fname_bufs = KHASH_EMPTY_TABLE(fnamebufs); khash_t(strset) oldfiles_set = KHASH_EMPTY_TABLE(strset); if (get_old_files && (oldfiles_list == NULL || force)) { - oldfiles_list = tv_list_alloc(); + oldfiles_list = tv_list_alloc(kListLenUnknown); set_vim_var_list(VV_OLDFILES, oldfiles_list); } ShaDaReadResult srni_ret; diff --git a/src/nvim/spell.c b/src/nvim/spell.c index d2b2575f6a..38392509a5 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1484,21 +1484,23 @@ spell_move_to ( return found_len; } - if (curline) + if (curline) { break; // only check cursor line + } + + // If we are back at the starting line and searched it again there + // is no match, give up. + if (lnum == wp->w_cursor.lnum && wrapped) { + break; + } // Advance to next line. if (dir == BACKWARD) { - // If we are back at the starting line and searched it again there - // is no match, give up. - if (lnum == wp->w_cursor.lnum && wrapped) - break; - - if (lnum > 1) - --lnum; - else if (!p_ws) + if (lnum > 1) { + lnum--; + } else if (!p_ws) { break; // at first line and 'nowrapscan' - else { + } else { // Wrap around to the end of the buffer. May search the // starting line again and accept the last match. lnum = wp->w_buffer->b_ml.ml_line_count; @@ -1523,8 +1525,9 @@ spell_move_to ( // If we are back at the starting line and there is no match then // give up. - if (lnum == wp->w_cursor.lnum && (!found_one || wrapped)) + if (lnum == wp->w_cursor.lnum && !found_one) { break; + } // Skip the characters at the start of the next line that were // included in a match crossing line boundaries. @@ -2565,7 +2568,7 @@ static bool spell_iswordp(char_u *p, win_T *wp) int c; if (has_mbyte) { - l = MB_BYTE2LEN(*p); + l = MB_PTR2LEN(p); s = p; if (l == 1) { // be quick for ASCII @@ -3136,8 +3139,13 @@ spell_find_suggest ( if (su->su_badlen >= MAXWLEN) su->su_badlen = MAXWLEN - 1; // just in case STRLCPY(su->su_badword, su->su_badptr, su->su_badlen + 1); - (void)spell_casefold(su->su_badptr, su->su_badlen, - su->su_fbadword, MAXWLEN); + (void)spell_casefold(su->su_badptr, su->su_badlen, su->su_fbadword, MAXWLEN); + + // TODO(vim): make this work if the case-folded text is longer than the + // original text. Currently an illegal byte causes wrong pointer + // computations. + su->su_fbadword[su->su_badlen] = NUL; + // get caps flags for bad word su->su_badflags = badword_captype(su->su_badptr, su->su_badptr + su->su_badlen); @@ -4107,10 +4115,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so && goodword_ends) { int l; - if (has_mbyte) - l = MB_BYTE2LEN(fword[sp->ts_fidx]); - else - l = 1; + l = MB_PTR2LEN(fword + sp->ts_fidx); if (fword_ends) { // Copy the skipped character to preword. memmove(preword + sp->ts_prewordlen, @@ -4256,8 +4261,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Correct ts_fidx for the byte length of the // character (we didn't check that before). sp->ts_fidx = sp->ts_fcharstart - + MB_BYTE2LEN( - fword[sp->ts_fcharstart]); + + MB_PTR2LEN(fword + sp->ts_fcharstart); // For changing a composing character adjust // the score from SCORE_SUBST to @@ -4363,11 +4367,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // results. if (has_mbyte) { c = mb_ptr2char(fword + sp->ts_fidx); - stack[depth].ts_fidx += MB_BYTE2LEN(fword[sp->ts_fidx]); - if (enc_utf8 && utf_iscomposing(c)) + stack[depth].ts_fidx += MB_PTR2LEN(fword + sp->ts_fidx); + if (enc_utf8 && utf_iscomposing(c)) { stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP; - else if (c == mb_ptr2char(fword + stack[depth].ts_fidx)) + } else if (c == mb_ptr2char(fword + stack[depth].ts_fidx)) { stack[depth].ts_score -= SCORE_DEL - SCORE_DELDUP; + } } else { ++stack[depth].ts_fidx; if (fword[sp->ts_fidx] == fword[sp->ts_fidx + 1]) @@ -4549,9 +4554,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Undo the STATE_SWAP swap: "21" -> "12". p = fword + sp->ts_fidx; if (has_mbyte) { - n = MB_BYTE2LEN(*p); + n = MB_PTR2LEN(p); c = mb_ptr2char(p + n); - memmove(p + MB_BYTE2LEN(p[n]), p, n); + memmove(p + MB_PTR2LEN(p + n), p, n); mb_char2bytes(c, p); } else { c = *p; @@ -4624,11 +4629,11 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Undo STATE_SWAP3: "321" -> "123" p = fword + sp->ts_fidx; if (has_mbyte) { - n = MB_BYTE2LEN(*p); + n = MB_PTR2LEN(p); c2 = mb_ptr2char(p + n); - fl = MB_BYTE2LEN(p[n]); + fl = MB_PTR2LEN(p + n); c = mb_ptr2char(p + n + fl); - tl = MB_BYTE2LEN(p[n + fl]); + tl = MB_PTR2LEN(p + n + fl); memmove(p + fl + tl, p, n); mb_char2bytes(c, p); mb_char2bytes(c2, p + tl); @@ -4687,10 +4692,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Undo ROT3L: "231" -> "123" p = fword + sp->ts_fidx; if (has_mbyte) { - n = MB_BYTE2LEN(*p); - n += MB_BYTE2LEN(p[n]); + n = MB_PTR2LEN(p); + n += MB_PTR2LEN(p + n); c = mb_ptr2char(p + n); - tl = MB_BYTE2LEN(p[n]); + tl = MB_PTR2LEN(p + n); memmove(p + tl, p, n); mb_char2bytes(c, p); } else { @@ -4740,9 +4745,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so p = fword + sp->ts_fidx; if (has_mbyte) { c = mb_ptr2char(p); - tl = MB_BYTE2LEN(*p); - n = MB_BYTE2LEN(p[tl]); - n += MB_BYTE2LEN(p[tl + n]); + tl = MB_PTR2LEN(p); + n = MB_PTR2LEN(p + tl); + n += MB_PTR2LEN(p + tl + n); memmove(p, p + tl, n); mb_char2bytes(c, p + n); } else { diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 1f7f616782..df066e7ad3 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -3656,7 +3656,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) flags |= WF_REGION; l = *p - '0'; - if (l > spin->si_region_count) { + if (l == 0 || l > spin->si_region_count) { smsg(_("Invalid region nr in %s line %d: %s"), fname, lnum, p); break; diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 01d2728f3d..05bc6c9d96 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -3022,9 +3022,9 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing) next = skiptowhite(arg); if (*arg == NUL) { if (curwin->w_s->b_syn_conceal) { - MSG(_("syn conceal on")); + MSG(_("syn conceal on")); } else { - MSG(_("syn conceal off")); + MSG(_("syn conceal off")); } } else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) { curwin->w_s->b_syn_conceal = true; @@ -3050,9 +3050,9 @@ static void syn_cmd_case(exarg_T *eap, int syncing) next = skiptowhite(arg); if (*arg == NUL) { if (curwin->w_s->b_syn_ic) { - MSG(_("syntax case ignore")); + MSG(_("syntax case ignore")); } else { - MSG(_("syntax case match")); + MSG(_("syntax case match")); } } else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) { curwin->w_s->b_syn_ic = false; @@ -3078,11 +3078,11 @@ static void syn_cmd_spell(exarg_T *eap, int syncing) next = skiptowhite(arg); if (*arg == NUL) { if (curwin->w_s->b_syn_spell == SYNSPL_TOP) { - MSG(_("syntax spell toplevel")); + MSG(_("syntax spell toplevel")); } else if (curwin->w_s->b_syn_spell == SYNSPL_NOTOP) { - MSG(_("syntax spell notoplevel")); + MSG(_("syntax spell notoplevel")); } else { - MSG(_("syntax spell default")); + MSG(_("syntax spell default")); } } else if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8) { curwin->w_s->b_syn_spell = SYNSPL_TOP; @@ -5287,7 +5287,7 @@ get_id_list ( id += current_syn_inc_tag; } else if (name[1] == '@') { if (skip) { - id = -1; + id = -1; } else { id = syn_check_cluster(name + 2, (int)(end - p - 1)); } diff --git a/src/nvim/tag.c b/src/nvim/tag.c index be9d621c7d..f23465e501 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -667,7 +667,7 @@ do_tag ( fname = xmalloc(MAXPATHL + 1); cmd = xmalloc(CMDBUFFSIZE + 1); - list = tv_list_alloc(); + list = tv_list_alloc(num_matches); for (i = 0; i < num_matches; ++i) { int len, cmd_len; diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 5af8dd20cd..c1e6eedf94 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -40,6 +40,7 @@ SCRIPTS ?= $(SCRIPTS_DEFAULT) # Tests using runtest.vim. # Keep test_alot*.res as the last one, sort the others. NEW_TESTS ?= \ + test_arabic.vim \ test_autocmd.res \ test_bufwintabinfo.res \ test_changedtick.res \ @@ -81,11 +82,13 @@ NEW_TESTS ?= \ test_search.res \ test_signs.res \ test_smartindent.res \ + test_spell.res \ test_stat.res \ test_startup.res \ test_startup_utf8.res \ test_substitute.res \ test_syntax.res \ + test_system.res \ test_tabpage.res \ test_textobjects.res \ test_timers.res \ diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index 72cfea96c6..d8e2d1d718 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -1,5 +1,21 @@ " Functions shared by several tests. +" {Nvim} +" Filepath captured from output may be truncated, like this: +" /home/va...estdir/Xtest-tmpdir/nvimxbXN4i/10 +" Get last 2 segments, then combine with $TMPDIR. +func! Fix_truncated_tmpfile(fname) + " sanity check + if $TMPDIR ==# '' + throw '$TMPDIR is empty' + endif + if a:fname !~# $TMPDIR + throw '$TMPDIR not in fname: '.a:fname + endif + let last2segments = matchstr(a:fname, '[\/][^\/]\+[\/][^\/]\+$') + return $TMPDIR.last2segments +endfunc + " Get the name of the Python executable. " Also keeps it in s:python. func PythonProg() diff --git a/src/nvim/testdir/test_arabic.vim b/src/nvim/testdir/test_arabic.vim new file mode 100644 index 0000000000..9a16833a4c --- /dev/null +++ b/src/nvim/testdir/test_arabic.vim @@ -0,0 +1,472 @@ +" Simplistic testing of Arabic mode. + +if !has('arabic') || !has('multi_byte') + finish +endif + +source view_util.vim + +" Return list of Unicode characters at line lnum. +" Combining characters are treated as a single item. +func s:get_chars(lnum) + call cursor(a:lnum, 1) + let chars = [] + let numchars = strchars(getline('.'), 1) + for i in range(1, numchars) + exe 'norm ' i . '|' + let c=execute('ascii') + let c=substitute(c, '\n\?<.\{-}Hex\s*', 'U+', 'g') + let c=substitute(c, ',\s*Octal\s*\d*', '', 'g') + call add(chars, c) + endfor + return chars +endfunc + +func Test_arabic_toggle() + set arabic + call assert_equal(1, &rightleft) + call assert_equal(1, &arabicshape) + call assert_equal('arabic', &keymap) + call assert_equal(1, &delcombine) + + set iminsert=1 imsearch=1 + set arabic& + call assert_equal(0, &rightleft) + call assert_equal(1, &arabicshape) + call assert_equal('arabic', &keymap) + call assert_equal(1, &delcombine) + call assert_equal(0, &iminsert) + call assert_equal(-1, &imsearch) + + set arabicshape& keymap= delcombine& +endfunc + +func Test_arabic_input() + new + set arabic + " Typing sghl in Arabic insert mode should show the + " Arabic word 'Salaam' i.e. 'peace', spelled: + " SEEN, LAM, ALEF, MEEM. + " See: https://www.mediawiki.org/wiki/VisualEditor/Typing/Right-to-left + call feedkeys('isghl!', 'tx') + call assert_match("^ *!\uFEE1\uFEFC\uFEB3$", ScreenLines(1, &columns)[0]) + call assert_equal([ + \ 'U+0633', + \ 'U+0644 U+0627', + \ 'U+0645', + \ 'U+21'], s:get_chars(1)) + + " Without shaping, it should give individual Arabic letters. + set noarabicshape + call assert_match("^ *!\u0645\u0627\u0644\u0633$", ScreenLines(1, &columns)[0]) + call assert_equal([ + \ 'U+0633', + \ 'U+0644', + \ 'U+0627', + \ 'U+0645', + \ 'U+21'], s:get_chars(1)) + + set arabic& arabicshape& + bwipe! +endfunc + +func Test_arabic_toggle_keymap() + new + set arabic + call feedkeys("i12\<C-^>12\<C-^>12", 'tx') + call assert_match("^ *٢١21٢١$", ScreenLines(1, &columns)[0]) + call assert_equal('١٢12١٢', getline('.')) + set arabic& + bwipe! +endfunc + +func Test_delcombine() + new + set arabic + call feedkeys("isghl\<BS>\<BS>", 'tx') + call assert_match("^ *\uFEDE\uFEB3$", ScreenLines(1, &columns)[0]) + call assert_equal(['U+0633', 'U+0644'], s:get_chars(1)) + + " Now the same with 'nodelcombine' + set nodelcombine + %d + call feedkeys("isghl\<BS>\<BS>", 'tx') + call assert_match("^ *\uFEB1$", ScreenLines(1, &columns)[0]) + call assert_equal(['U+0633'], s:get_chars(1)) + set arabic& + bwipe! +endfunc + +" Values from src/arabic.h (not all used yet) +let s:a_COMMA = "\u060C" +let s:a_SEMICOLON = "\u061B" +let s:a_QUESTION = "\u061F" +let s:a_HAMZA = "\u0621" +let s:a_ALEF_MADDA = "\u0622" +let s:a_ALEF_HAMZA_ABOVE = "\u0623" +let s:a_WAW_HAMZA = "\u0624" +let s:a_ALEF_HAMZA_BELOW = "\u0625" +let s:a_YEH_HAMZA = "\u0626" +let s:a_ALEF = "\u0627" +let s:a_BEH = "\u0628" +let s:a_TEH_MARBUTA = "\u0629" +let s:a_TEH = "\u062a" +let s:a_THEH = "\u062b" +let s:a_JEEM = "\u062c" +let s:a_HAH = "\u062d" +let s:a_KHAH = "\u062e" +let s:a_DAL = "\u062f" +let s:a_THAL = "\u0630" +let s:a_REH = "\u0631" +let s:a_ZAIN = "\u0632" +let s:a_SEEN = "\u0633" +let s:a_SHEEN = "\u0634" +let s:a_SAD = "\u0635" +let s:a_DAD = "\u0636" +let s:a_TAH = "\u0637" +let s:a_ZAH = "\u0638" +let s:a_AIN = "\u0639" +let s:a_GHAIN = "\u063a" +let s:a_TATWEEL = "\u0640" +let s:a_FEH = "\u0641" +let s:a_QAF = "\u0642" +let s:a_KAF = "\u0643" +let s:a_LAM = "\u0644" +let s:a_MEEM = "\u0645" +let s:a_NOON = "\u0646" +let s:a_HEH = "\u0647" +let s:a_WAW = "\u0648" +let s:a_ALEF_MAKSURA = "\u0649" +let s:a_YEH = "\u064a" + +let s:a_FATHATAN = "\u064b" +let s:a_DAMMATAN = "\u064c" +let s:a_KASRATAN = "\u064d" +let s:a_FATHA = "\u064e" +let s:a_DAMMA = "\u064f" +let s:a_KASRA = "\u0650" +let s:a_SHADDA = "\u0651" +let s:a_SUKUN = "\u0652" + +let s:a_MADDA_ABOVE = "\u0653" +let s:a_HAMZA_ABOVE = "\u0654" +let s:a_HAMZA_BELOW = "\u0655" + +let s:a_ZERO = "\u0660" +let s:a_ONE = "\u0661" +let s:a_TWO = "\u0662" +let s:a_THREE = "\u0663" +let s:a_FOUR = "\u0664" +let s:a_FIVE = "\u0665" +let s:a_SIX = "\u0666" +let s:a_SEVEN = "\u0667" +let s:a_EIGHT = "\u0668" +let s:a_NINE = "\u0669" +let s:a_PERCENT = "\u066a" +let s:a_DECIMAL = "\u066b" +let s:a_THOUSANDS = "\u066c" +let s:a_STAR = "\u066d" +let s:a_MINI_ALEF = "\u0670" + +let s:a_s_FATHATAN = "\ufe70" +let s:a_m_TATWEEL_FATHATAN = "\ufe71" +let s:a_s_DAMMATAN = "\ufe72" + +let s:a_s_KASRATAN = "\ufe74" + +let s:a_s_FATHA = "\ufe76" +let s:a_m_FATHA = "\ufe77" +let s:a_s_DAMMA = "\ufe78" +let s:a_m_DAMMA = "\ufe79" +let s:a_s_KASRA = "\ufe7a" +let s:a_m_KASRA = "\ufe7b" +let s:a_s_SHADDA = "\ufe7c" +let s:a_m_SHADDA = "\ufe7d" +let s:a_s_SUKUN = "\ufe7e" +let s:a_m_SUKUN = "\ufe7f" + +let s:a_s_HAMZA = "\ufe80" +let s:a_s_ALEF_MADDA = "\ufe81" +let s:a_f_ALEF_MADDA = "\ufe82" +let s:a_s_ALEF_HAMZA_ABOVE = "\ufe83" +let s:a_f_ALEF_HAMZA_ABOVE = "\ufe84" +let s:a_s_WAW_HAMZA = "\ufe85" +let s:a_f_WAW_HAMZA = "\ufe86" +let s:a_s_ALEF_HAMZA_BELOW = "\ufe87" +let s:a_f_ALEF_HAMZA_BELOW = "\ufe88" +let s:a_s_YEH_HAMZA = "\ufe89" +let s:a_f_YEH_HAMZA = "\ufe8a" +let s:a_i_YEH_HAMZA = "\ufe8b" +let s:a_m_YEH_HAMZA = "\ufe8c" +let s:a_s_ALEF = "\ufe8d" +let s:a_f_ALEF = "\ufe8e" +let s:a_s_BEH = "\ufe8f" +let s:a_f_BEH = "\ufe90" +let s:a_i_BEH = "\ufe91" +let s:a_m_BEH = "\ufe92" +let s:a_s_TEH_MARBUTA = "\ufe93" +let s:a_f_TEH_MARBUTA = "\ufe94" +let s:a_s_TEH = "\ufe95" +let s:a_f_TEH = "\ufe96" +let s:a_i_TEH = "\ufe97" +let s:a_m_TEH = "\ufe98" +let s:a_s_THEH = "\ufe99" +let s:a_f_THEH = "\ufe9a" +let s:a_i_THEH = "\ufe9b" +let s:a_m_THEH = "\ufe9c" +let s:a_s_JEEM = "\ufe9d" +let s:a_f_JEEM = "\ufe9e" +let s:a_i_JEEM = "\ufe9f" +let s:a_m_JEEM = "\ufea0" +let s:a_s_HAH = "\ufea1" +let s:a_f_HAH = "\ufea2" +let s:a_i_HAH = "\ufea3" +let s:a_m_HAH = "\ufea4" +let s:a_s_KHAH = "\ufea5" +let s:a_f_KHAH = "\ufea6" +let s:a_i_KHAH = "\ufea7" +let s:a_m_KHAH = "\ufea8" +let s:a_s_DAL = "\ufea9" +let s:a_f_DAL = "\ufeaa" +let s:a_s_THAL = "\ufeab" +let s:a_f_THAL = "\ufeac" +let s:a_s_REH = "\ufead" +let s:a_f_REH = "\ufeae" +let s:a_s_ZAIN = "\ufeaf" +let s:a_f_ZAIN = "\ufeb0" +let s:a_s_SEEN = "\ufeb1" +let s:a_f_SEEN = "\ufeb2" +let s:a_i_SEEN = "\ufeb3" +let s:a_m_SEEN = "\ufeb4" +let s:a_s_SHEEN = "\ufeb5" +let s:a_f_SHEEN = "\ufeb6" +let s:a_i_SHEEN = "\ufeb7" +let s:a_m_SHEEN = "\ufeb8" +let s:a_s_SAD = "\ufeb9" +let s:a_f_SAD = "\ufeba" +let s:a_i_SAD = "\ufebb" +let s:a_m_SAD = "\ufebc" +let s:a_s_DAD = "\ufebd" +let s:a_f_DAD = "\ufebe" +let s:a_i_DAD = "\ufebf" +let s:a_m_DAD = "\ufec0" +let s:a_s_TAH = "\ufec1" +let s:a_f_TAH = "\ufec2" +let s:a_i_TAH = "\ufec3" +let s:a_m_TAH = "\ufec4" +let s:a_s_ZAH = "\ufec5" +let s:a_f_ZAH = "\ufec6" +let s:a_i_ZAH = "\ufec7" +let s:a_m_ZAH = "\ufec8" +let s:a_s_AIN = "\ufec9" +let s:a_f_AIN = "\ufeca" +let s:a_i_AIN = "\ufecb" +let s:a_m_AIN = "\ufecc" +let s:a_s_GHAIN = "\ufecd" +let s:a_f_GHAIN = "\ufece" +let s:a_i_GHAIN = "\ufecf" +let s:a_m_GHAIN = "\ufed0" +let s:a_s_FEH = "\ufed1" +let s:a_f_FEH = "\ufed2" +let s:a_i_FEH = "\ufed3" +let s:a_m_FEH = "\ufed4" +let s:a_s_QAF = "\ufed5" +let s:a_f_QAF = "\ufed6" +let s:a_i_QAF = "\ufed7" +let s:a_m_QAF = "\ufed8" +let s:a_s_KAF = "\ufed9" +let s:a_f_KAF = "\ufeda" +let s:a_i_KAF = "\ufedb" +let s:a_m_KAF = "\ufedc" +let s:a_s_LAM = "\ufedd" +let s:a_f_LAM = "\ufede" +let s:a_i_LAM = "\ufedf" +let s:a_m_LAM = "\ufee0" +let s:a_s_MEEM = "\ufee1" +let s:a_f_MEEM = "\ufee2" +let s:a_i_MEEM = "\ufee3" +let s:a_m_MEEM = "\ufee4" +let s:a_s_NOON = "\ufee5" +let s:a_f_NOON = "\ufee6" +let s:a_i_NOON = "\ufee7" +let s:a_m_NOON = "\ufee8" +let s:a_s_HEH = "\ufee9" +let s:a_f_HEH = "\ufeea" +let s:a_i_HEH = "\ufeeb" +let s:a_m_HEH = "\ufeec" +let s:a_s_WAW = "\ufeed" +let s:a_f_WAW = "\ufeee" +let s:a_s_ALEF_MAKSURA = "\ufeef" +let s:a_f_ALEF_MAKSURA = "\ufef0" +let s:a_s_YEH = "\ufef1" +let s:a_f_YEH = "\ufef2" +let s:a_i_YEH = "\ufef3" +let s:a_m_YEH = "\ufef4" +let s:a_s_LAM_ALEF_MADDA_ABOVE = "\ufef5" +let s:a_f_LAM_ALEF_MADDA_ABOVE = "\ufef6" +let s:a_s_LAM_ALEF_HAMZA_ABOVE = "\ufef7" +let s:a_f_LAM_ALEF_HAMZA_ABOVE = "\ufef8" +let s:a_s_LAM_ALEF_HAMZA_BELOW = "\ufef9" +let s:a_f_LAM_ALEF_HAMZA_BELOW = "\ufefa" +let s:a_s_LAM_ALEF = "\ufefb" +let s:a_f_LAM_ALEF = "\ufefc" + +let s:a_BYTE_ORDER_MARK = "\ufeff" + +func Test_shape_initial() + new + set arabicshape + + " Shaping arabic {testchar} non-arabic Tests chg_c_a2i(). + " pair[0] = testchar, pair[1] = next-result, pair[2] = current-result + for pair in [[s:a_YEH_HAMZA, s:a_f_GHAIN, s:a_i_YEH_HAMZA], + \ [s:a_HAMZA, s:a_s_GHAIN, s:a_s_HAMZA], + \ [s:a_ALEF_MADDA, s:a_s_GHAIN, s:a_s_ALEF_MADDA], + \ [s:a_ALEF_HAMZA_ABOVE, s:a_s_GHAIN, s:a_s_ALEF_HAMZA_ABOVE], + \ [s:a_WAW_HAMZA, s:a_s_GHAIN, s:a_s_WAW_HAMZA], + \ [s:a_ALEF_HAMZA_BELOW, s:a_s_GHAIN, s:a_s_ALEF_HAMZA_BELOW], + \ [s:a_ALEF, s:a_s_GHAIN, s:a_s_ALEF], + \ [s:a_TEH_MARBUTA, s:a_s_GHAIN, s:a_s_TEH_MARBUTA], + \ [s:a_DAL, s:a_s_GHAIN, s:a_s_DAL], + \ [s:a_THAL, s:a_s_GHAIN, s:a_s_THAL], + \ [s:a_REH, s:a_s_GHAIN, s:a_s_REH], + \ [s:a_ZAIN, s:a_s_GHAIN, s:a_s_ZAIN], + \ [s:a_TATWEEL, s:a_f_GHAIN, s:a_TATWEEL], + \ [s:a_WAW, s:a_s_GHAIN, s:a_s_WAW], + \ [s:a_ALEF_MAKSURA, s:a_s_GHAIN, s:a_s_ALEF_MAKSURA], + \ [s:a_BEH, s:a_f_GHAIN, s:a_i_BEH], + \ [s:a_TEH, s:a_f_GHAIN, s:a_i_TEH], + \ [s:a_THEH, s:a_f_GHAIN, s:a_i_THEH], + \ [s:a_JEEM, s:a_f_GHAIN, s:a_i_JEEM], + \ [s:a_HAH, s:a_f_GHAIN, s:a_i_HAH], + \ [s:a_KHAH, s:a_f_GHAIN, s:a_i_KHAH], + \ [s:a_SEEN, s:a_f_GHAIN, s:a_i_SEEN], + \ [s:a_SHEEN, s:a_f_GHAIN, s:a_i_SHEEN], + \ [s:a_SAD, s:a_f_GHAIN, s:a_i_SAD], + \ [s:a_DAD, s:a_f_GHAIN, s:a_i_DAD], + \ [s:a_TAH, s:a_f_GHAIN, s:a_i_TAH], + \ [s:a_ZAH, s:a_f_GHAIN, s:a_i_ZAH], + \ [s:a_AIN, s:a_f_GHAIN, s:a_i_AIN], + \ [s:a_GHAIN, s:a_f_GHAIN, s:a_i_GHAIN], + \ [s:a_FEH, s:a_f_GHAIN, s:a_i_FEH], + \ [s:a_QAF, s:a_f_GHAIN, s:a_i_QAF], + \ [s:a_KAF, s:a_f_GHAIN, s:a_i_KAF], + \ [s:a_LAM, s:a_f_GHAIN, s:a_i_LAM], + \ [s:a_MEEM, s:a_f_GHAIN, s:a_i_MEEM], + \ [s:a_NOON, s:a_f_GHAIN, s:a_i_NOON], + \ [s:a_HEH, s:a_f_GHAIN, s:a_i_HEH], + \ [s:a_YEH, s:a_f_GHAIN, s:a_i_YEH], + \ ] + call setline(1, s:a_GHAIN . pair[0] . ' ') + call assert_equal([pair[1] . pair[2] . ' '], ScreenLines(1, 3)) + endfor + + set arabicshape& + bwipe! +endfunc + +func Test_shape_isolated() + new + set arabicshape + + " Shaping non-arabic {testchar} non-arabic Tests chg_c_a2s(). + " pair[0] = testchar, pair[1] = current-result + for pair in [[s:a_HAMZA, s:a_s_HAMZA], + \ [s:a_ALEF_MADDA, s:a_s_ALEF_MADDA], + \ [s:a_ALEF_HAMZA_ABOVE, s:a_s_ALEF_HAMZA_ABOVE], + \ [s:a_WAW_HAMZA, s:a_s_WAW_HAMZA], + \ [s:a_ALEF_HAMZA_BELOW, s:a_s_ALEF_HAMZA_BELOW], + \ [s:a_YEH_HAMZA, s:a_s_YEH_HAMZA], + \ [s:a_ALEF, s:a_s_ALEF], + \ [s:a_TEH_MARBUTA, s:a_s_TEH_MARBUTA], + \ [s:a_DAL, s:a_s_DAL], + \ [s:a_THAL, s:a_s_THAL], + \ [s:a_REH, s:a_s_REH], + \ [s:a_ZAIN, s:a_s_ZAIN], + \ [s:a_TATWEEL, s:a_TATWEEL], + \ [s:a_WAW, s:a_s_WAW], + \ [s:a_ALEF_MAKSURA, s:a_s_ALEF_MAKSURA], + \ [s:a_BEH, s:a_s_BEH], + \ [s:a_TEH, s:a_s_TEH], + \ [s:a_THEH, s:a_s_THEH], + \ [s:a_JEEM, s:a_s_JEEM], + \ [s:a_HAH, s:a_s_HAH], + \ [s:a_KHAH, s:a_s_KHAH], + \ [s:a_SEEN, s:a_s_SEEN], + \ [s:a_SHEEN, s:a_s_SHEEN], + \ [s:a_SAD, s:a_s_SAD], + \ [s:a_DAD, s:a_s_DAD], + \ [s:a_TAH, s:a_s_TAH], + \ [s:a_ZAH, s:a_s_ZAH], + \ [s:a_AIN, s:a_s_AIN], + \ [s:a_GHAIN, s:a_s_GHAIN], + \ [s:a_FEH, s:a_s_FEH], + \ [s:a_QAF, s:a_s_QAF], + \ [s:a_KAF, s:a_s_KAF], + \ [s:a_LAM, s:a_s_LAM], + \ [s:a_MEEM, s:a_s_MEEM], + \ [s:a_NOON, s:a_s_NOON], + \ [s:a_HEH, s:a_s_HEH], + \ [s:a_YEH, s:a_s_YEH], + \ ] + call setline(1, ' ' . pair[0] . ' ') + call assert_equal([' ' . pair[1] . ' '], ScreenLines(1, 3)) + endfor + + set arabicshape& + bwipe! +endfunc + +func Test_shape_medial() + new + set arabicshape + + " Shaping arabic {testchar} arabic Tests chg_c_a2m(). + " pair[0] = testchar, pair[1] = next-result, pair[2] = current-result, + " pair[3] = previous-result + for pair in [[s:a_HAMZA, s:a_s_GHAIN, s:a_s_HAMZA, s:a_s_BEH], + \[s:a_ALEF_MADDA, s:a_s_GHAIN, s:a_f_ALEF_MADDA, s:a_i_BEH], + \[s:a_ALEF_HAMZA_ABOVE, s:a_s_GHAIN, s:a_f_ALEF_HAMZA_ABOVE, s:a_i_BEH], + \[s:a_WAW_HAMZA, s:a_s_GHAIN, s:a_f_WAW_HAMZA, s:a_i_BEH], + \[s:a_ALEF_HAMZA_BELOW, s:a_s_GHAIN, s:a_f_ALEF_HAMZA_BELOW, s:a_i_BEH], + \[s:a_YEH_HAMZA, s:a_f_GHAIN, s:a_m_YEH_HAMZA, s:a_i_BEH], + \[s:a_ALEF, s:a_s_GHAIN, s:a_f_ALEF, s:a_i_BEH], + \[s:a_BEH, s:a_f_GHAIN, s:a_m_BEH, s:a_i_BEH], + \[s:a_TEH_MARBUTA, s:a_s_GHAIN, s:a_f_TEH_MARBUTA, s:a_i_BEH], + \[s:a_TEH, s:a_f_GHAIN, s:a_m_TEH, s:a_i_BEH], + \[s:a_THEH, s:a_f_GHAIN, s:a_m_THEH, s:a_i_BEH], + \[s:a_JEEM, s:a_f_GHAIN, s:a_m_JEEM, s:a_i_BEH], + \[s:a_HAH, s:a_f_GHAIN, s:a_m_HAH, s:a_i_BEH], + \[s:a_KHAH, s:a_f_GHAIN, s:a_m_KHAH, s:a_i_BEH], + \[s:a_DAL, s:a_s_GHAIN, s:a_f_DAL, s:a_i_BEH], + \[s:a_THAL, s:a_s_GHAIN, s:a_f_THAL, s:a_i_BEH], + \[s:a_REH, s:a_s_GHAIN, s:a_f_REH, s:a_i_BEH], + \[s:a_ZAIN, s:a_s_GHAIN, s:a_f_ZAIN, s:a_i_BEH], + \[s:a_SEEN, s:a_f_GHAIN, s:a_m_SEEN, s:a_i_BEH], + \[s:a_SHEEN, s:a_f_GHAIN, s:a_m_SHEEN, s:a_i_BEH], + \[s:a_SAD, s:a_f_GHAIN, s:a_m_SAD, s:a_i_BEH], + \[s:a_DAD, s:a_f_GHAIN, s:a_m_DAD, s:a_i_BEH], + \[s:a_TAH, s:a_f_GHAIN, s:a_m_TAH, s:a_i_BEH], + \[s:a_ZAH, s:a_f_GHAIN, s:a_m_ZAH, s:a_i_BEH], + \[s:a_AIN, s:a_f_GHAIN, s:a_m_AIN, s:a_i_BEH], + \[s:a_GHAIN, s:a_f_GHAIN, s:a_m_GHAIN, s:a_i_BEH], + \[s:a_TATWEEL, s:a_f_GHAIN, s:a_TATWEEL, s:a_i_BEH], + \[s:a_FEH, s:a_f_GHAIN, s:a_m_FEH, s:a_i_BEH], + \[s:a_QAF, s:a_f_GHAIN, s:a_m_QAF, s:a_i_BEH], + \[s:a_KAF, s:a_f_GHAIN, s:a_m_KAF, s:a_i_BEH], + \[s:a_LAM, s:a_f_GHAIN, s:a_m_LAM, s:a_i_BEH], + \[s:a_MEEM, s:a_f_GHAIN, s:a_m_MEEM, s:a_i_BEH], + \[s:a_NOON, s:a_f_GHAIN, s:a_m_NOON, s:a_i_BEH], + \[s:a_HEH, s:a_f_GHAIN, s:a_m_HEH, s:a_i_BEH], + \[s:a_WAW, s:a_s_GHAIN, s:a_f_WAW, s:a_i_BEH], + \[s:a_ALEF_MAKSURA, s:a_s_GHAIN, s:a_f_ALEF_MAKSURA, s:a_i_BEH], + \[s:a_YEH, s:a_f_GHAIN, s:a_m_YEH, s:a_i_BEH], + \ ] + call setline(1, s:a_GHAIN . pair[0] . s:a_BEH) + call assert_equal([pair[1] . pair[2] . pair[3]], ScreenLines(1, 3)) + endfor + + set arabicshape& + bwipe! +endfunc + diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index dc9790a39c..115c228ce8 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -25,6 +25,26 @@ func Test_complete_wildmenu() set nowildmenu endfunc +func Test_map_completion() + if !has('cmdline_compl') + return + endif + call feedkeys(":map <unique> <si\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <unique> <silent>', getreg(':')) + call feedkeys(":map <script> <un\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <script> <unique>', getreg(':')) + call feedkeys(":map <expr> <sc\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <expr> <script>', getreg(':')) + call feedkeys(":map <buffer> <e\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <buffer> <expr>', getreg(':')) + call feedkeys(":map <nowait> <b\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <nowait> <buffer>', getreg(':')) + call feedkeys(":map <special> <no\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <special> <nowait>', getreg(':')) + call feedkeys(":map <silent> <sp\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <silent> <special>', getreg(':')) +endfunc + func Test_expr_completion() if !(has('cmdline_compl') && has('eval')) return diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 8ee82bd538..33617c9cbe 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -272,3 +272,98 @@ func Test_setting_cursor() call delete('Xtest1') call delete('Xtest2') endfunc + +func Test_diff_move_to() + new + call setline(1, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + diffthis + vnew + call setline(1, [1, '2x', 3, 4, 4, 5, '6x', 7, '8x', 9, '10x']) + diffthis + norm ]c + call assert_equal(2, line('.')) + norm 3]c + call assert_equal(9, line('.')) + norm 10]c + call assert_equal(11, line('.')) + norm [c + call assert_equal(9, line('.')) + norm 2[c + call assert_equal(5, line('.')) + norm 10[c + call assert_equal(2, line('.')) + %bwipe! +endfunc + +func Test_diffpatch() + " The patch program on MS-Windows may fail or hang. + if !executable('patch') || !has('unix') + return + endif + new + insert +*************** +*** 1,3 **** + 1 +! 2 + 3 +--- 1,4 ---- + 1 +! 2x + 3 ++ 4 +. + saveas Xpatch + bwipe! + new + call assert_fails('diffpatch Xpatch', 'E816:') + call setline(1, ['1', '2', '3']) + diffpatch Xpatch + call assert_equal(['1', '2x', '3', '4'], getline(1, '$')) + call delete('Xpatch') + bwipe! +endfunc + +func Test_diff_too_many_buffers() + for i in range(1, 8) + exe "new Xtest" . i + diffthis + endfor + new Xtest9 + call assert_fails('diffthis', 'E96:') + %bwipe! +endfunc + +func Test_diff_nomodifiable() + new + call setline(1, [1, 2, 3, 4]) + setl nomodifiable + diffthis + vnew + call setline(1, ['1x', 2, 3, 3, 4]) + diffthis + call assert_fails('norm dp', 'E793:') + setl nomodifiable + call assert_fails('norm do', 'E21:') + %bwipe! +endfunc + +func Test_diff_lastline() + enew! + only! + call setline(1, ['This is a ', 'line with five ', 'rows']) + diffthis + botright vert new + call setline(1, ['This is', 'a line with ', 'four rows']) + diffthis + 1 + call feedkeys("Je a\<CR>", 'tx') + call feedkeys("Je a\<CR>", 'tx') + let w1lines = winline() + wincmd w + $ + let w2lines = winline() + call assert_equal(w2lines, w1lines) + bwipe! + bwipe! +endfunc diff --git a/src/nvim/testdir/test_listlbr_utf8.vim b/src/nvim/testdir/test_listlbr_utf8.vim index 980d67d49d..56a4cc9b31 100644 --- a/src/nvim/testdir/test_listlbr_utf8.vim +++ b/src/nvim/testdir/test_listlbr_utf8.vim @@ -194,6 +194,33 @@ func Test_multibyte_sign_and_colorcolumn() call s:close_windows() endfunc +func Test_illegal_byte_and_breakat() + call s:test_windows("setl sbr= brk+=<") + vert resize 18 + call setline(1, repeat("\x80", 6)) + redraw! + let lines = s:screen_lines([1, 2], winwidth(0)) + let expect = [ +\ "<80><80><80><80><8", +\ "0><80> ", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows('setl brk&vim') +endfunc + +func Test_multibyte_wrap_and_breakat() + call s:test_windows("setl sbr= brk+=>") + call setline(1, repeat('a', 17) . repeat('あ', 2)) + redraw! + let lines = s:screen_lines([1, 2], winwidth(0)) + let expect = [ +\ "aaaaaaaaaaaaaaaaaあ>", +\ "あ ", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows('setl brk&vim') +endfunc + func Test_chinese_char_on_wrap_column() call s:test_windows("setl nolbr wrap sbr=") syntax off diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 4747d5704d..8635daa7ad 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1,5 +1,7 @@ " Test for various Normal mode commands +source shared.vim + func! Setup_NewWindow() 10new call setline(1, range(1,100)) @@ -1069,10 +1071,10 @@ func! Test_normal18_z_fold() endfunc func! Test_normal19_z_spell() - throw "skipped: Nvim 'spell' requires download" if !has("spell") || !has('syntax') return endif + " let $TMPDIR=fnamemodify($TMPDIR, ':.') new call append(0, ['1 good', '2 goood', '3 goood']) set spell spellfile=./Xspellfile.add spelllang=en @@ -1119,7 +1121,9 @@ func! Test_normal19_z_spell() " Test for zG let a=execute('unsilent norm! V$zG') call assert_match("Word '2 goood' added to .*", a) + set shortmess= let fname=matchstr(a, 'to\s\+\zs\f\+$') + let fname=Fix_truncated_tmpfile(fname) let cnt=readfile(fname) call assert_equal('2 goood', cnt[0]) diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index a333e7f206..03112df46f 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -298,3 +298,10 @@ func Test_searchpair() q! endfunc +func Test_searchc() + " These commands used to cause memory overflow in searchc(). + new + norm ixx + exe "norm 0t\u93cf" + bw! +endfunc diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim new file mode 100644 index 0000000000..21f2363731 --- /dev/null +++ b/src/nvim/testdir/test_spell.vim @@ -0,0 +1,821 @@ +" Test spell checking + +if !has('spell') + finish +endif + +func TearDown() + set nospell + call delete('Xtest.aff') + call delete('Xtest.dic') + call delete('Xtest.latin1.add') + call delete('Xtest.latin1.add.spl') + call delete('Xtest.latin1.spl') + call delete('Xtest.latin1.sug') +endfunc + +func Test_wrap_search() + new + call setline(1, ['The', '', 'A plong line with two zpelling mistakes', '', 'End']) + set spell wrapscan + normal ]s + call assert_equal('plong', expand('<cword>')) + normal ]s + call assert_equal('zpelling', expand('<cword>')) + normal ]s + call assert_equal('plong', expand('<cword>')) + bwipe! + set nospell +endfunc + +func Test_curswant() + new + call setline(1, ['Another plong line', 'abcdefghijklmnopq']) + set spell wrapscan + normal 0]s + call assert_equal('plong', expand('<cword>')) + normal j + call assert_equal(9, getcurpos()[2]) + normal 0[s + call assert_equal('plong', expand('<cword>')) + normal j + call assert_equal(9, getcurpos()[2]) + + normal 0]S + call assert_equal('plong', expand('<cword>')) + normal j + call assert_equal(9, getcurpos()[2]) + normal 0[S + call assert_equal('plong', expand('<cword>')) + normal j + call assert_equal(9, getcurpos()[2]) + + normal 1G0 + call assert_equal('plong', spellbadword()[0]) + normal j + call assert_equal(9, getcurpos()[2]) + + bwipe! + set nospell +endfunc + +func Test_z_equal_on_invalid_utf8_word() + split + set spell + call setline(1, "\xff") + norm z= + set nospell + bwipe! +endfunc + +func Test_spellreall() + new + set spell + call assert_fails('spellrepall', 'E752:') + call setline(1, ['A speling mistake. The same speling mistake.', + \ 'Another speling mistake.']) + call feedkeys(']s1z=', 'tx') + call assert_equal('A spelling mistake. The same speling mistake.', getline(1)) + call assert_equal('Another speling mistake.', getline(2)) + spellrepall + call assert_equal('A spelling mistake. The same spelling mistake.', getline(1)) + call assert_equal('Another spelling mistake.', getline(2)) + call assert_fails('spellrepall', 'E753:') + set spell& + bwipe! +endfunc + +func Test_zz_basic() + call LoadAffAndDic(g:test_data_aff1, g:test_data_dic1) + call RunGoodBad("wrong OK puts. Test the end", + \ "bad: inputs comment ok Ok. test d\xE9\xF4l end the", + \["Comment", "deol", "d\xE9\xF4r", "input", "OK", "output", "outputs", "outtest", "put", "puts", + \ "test", "testen", "testn", "the end", "uk", "wrong"], + \[ + \ ["bad", ["put", "uk", "OK"]], + \ ["inputs", ["input", "puts", "outputs"]], + \ ["comment", ["Comment", "outtest", "the end"]], + \ ["ok", ["OK", "uk", "put"]], + \ ["Ok", ["OK", "Uk", "Put"]], + \ ["test", ["Test", "testn", "testen"]], + \ ["d\xE9\xF4l", ["deol", "d\xE9\xF4r", "test"]], + \ ["end", ["put", "uk", "test"]], + \ ["the", ["put", "uk", "test"]], + \ ] + \ ) + + call assert_equal("gebletegek", soundfold('goobledygoook')) + call assert_equal("kepereneven", soundfold('koprnven')) + call assert_equal("everles gesvets etele", soundfold('oeverloos gezwets edale')) +endfunc + +" Postponed prefixes +func Test_zz_prefixes() + call LoadAffAndDic(g:test_data_aff2, g:test_data_dic1) + call RunGoodBad("puts", + \ "bad: inputs comment ok Ok end the. test d\xE9\xF4l", + \ ["Comment", "deol", "d\xE9\xF4r", "OK", "put", "input", "output", "puts", "outputs", "test", "outtest", "testen", "testn", "the end", "uk", "wrong"], + \ [ + \ ["bad", ["put", "uk", "OK"]], + \ ["inputs", ["input", "puts", "outputs"]], + \ ["comment", ["Comment"]], + \ ["ok", ["OK", "uk", "put"]], + \ ["Ok", ["OK", "Uk", "Put"]], + \ ["end", ["put", "uk", "deol"]], + \ ["the", ["put", "uk", "test"]], + \ ["test", ["Test", "testn", "testen"]], + \ ["d\xE9\xF4l", ["deol", "d\xE9\xF4r", "test"]], + \ ]) +endfunc + +"Compound words +func Test_zz_compound() + call LoadAffAndDic(g:test_data_aff3, g:test_data_dic3) + call RunGoodBad("foo m\xEF foobar foofoobar barfoo barbarfoo", + \ "bad: bar la foom\xEF barm\xEF m\xEFfoo m\xEFbar m\xEFm\xEF lala m\xEFla lam\xEF foola labar", + \ ["foo", "m\xEF"], + \ [ + \ ["bad", ["foo", "m\xEF"]], + \ ["bar", ["barfoo", "foobar", "foo"]], + \ ["la", ["m\xEF", "foo"]], + \ ["foom\xEF", ["foo m\xEF", "foo", "foofoo"]], + \ ["barm\xEF", ["barfoo", "m\xEF", "barbar"]], + \ ["m\xEFfoo", ["m\xEF foo", "foo", "foofoo"]], + \ ["m\xEFbar", ["foobar", "barbar", "m\xEF"]], + \ ["m\xEFm\xEF", ["m\xEF m\xEF", "m\xEF"]], + \ ["lala", []], + \ ["m\xEFla", ["m\xEF", "m\xEF m\xEF"]], + \ ["lam\xEF", ["m\xEF", "m\xEF m\xEF"]], + \ ["foola", ["foo", "foobar", "foofoo"]], + \ ["labar", ["barbar", "foobar"]], + \ ]) + + call LoadAffAndDic(g:test_data_aff4, g:test_data_dic4) + call RunGoodBad("word util bork prebork start end wordutil wordutils pro-ok bork borkbork borkborkbork borkborkborkbork borkborkborkborkbork tomato tomatotomato startend startword startwordword startwordend startwordwordend startwordwordwordend prebork preborkbork preborkborkbork nouword", + \ "bad: wordutilize pro borkborkborkborkborkbork tomatotomatotomato endstart endend startstart wordend wordstart preborkprebork preborkpreborkbork startwordwordwordwordend borkpreborkpreborkbork utilsbork startnouword", + \ ["bork", "prebork", "end", "pro-ok", "start", "tomato", "util", "utilize", "utils", "word", "nouword"], + \ [ + \ ["bad", ["end", "bork", "word"]], + \ ["wordutilize", ["word utilize", "wordutils", "wordutil"]], + \ ["pro", ["bork", "word", "end"]], + \ ["borkborkborkborkborkbork", ["bork borkborkborkborkbork", "borkbork borkborkborkbork", "borkborkbork borkborkbork"]], + \ ["tomatotomatotomato", ["tomato tomatotomato", "tomatotomato tomato", "tomato tomato tomato"]], + \ ["endstart", ["end start", "start"]], + \ ["endend", ["end end", "end"]], + \ ["startstart", ["start start"]], + \ ["wordend", ["word end", "word", "wordword"]], + \ ["wordstart", ["word start", "bork start"]], + \ ["preborkprebork", ["prebork prebork", "preborkbork", "preborkborkbork"]], + \ ["preborkpreborkbork", ["prebork preborkbork", "preborkborkbork", "preborkborkborkbork"]], + \ ["startwordwordwordwordend", ["startwordwordwordword end", "startwordwordwordword", "start wordwordwordword end"]], + \ ["borkpreborkpreborkbork", ["bork preborkpreborkbork", "bork prebork preborkbork", "bork preborkprebork bork"]], + \ ["utilsbork", ["utilbork", "utils bork", "util bork"]], + \ ["startnouword", ["start nouword", "startword", "startborkword"]], + \ ]) + +endfunc + +"Test affix flags with two characters +func Test_zz_affix() + call LoadAffAndDic(g:test_data_aff5, g:test_data_dic5) + call RunGoodBad("fooa1 fooa\xE9 bar prebar barbork prebarbork startprebar start end startend startmiddleend nouend", + \ "bad: foo fooa2 prabar probarbirk middle startmiddle middleend endstart startprobar startnouend", + \ ["bar", "barbork", "end", "fooa1", "fooa\xE9", "nouend", "prebar", "prebarbork", "start"], + \ [ + \ ["bad", ["bar", "end", "fooa1"]], + \ ["foo", ["fooa1", "fooa\xE9", "bar"]], + \ ["fooa2", ["fooa1", "fooa\xE9", "bar"]], + \ ["prabar", ["prebar", "bar", "bar bar"]], + \ ["probarbirk", ["prebarbork"]], + \ ["middle", []], + \ ["startmiddle", ["startmiddleend", "startmiddlebar"]], + \ ["middleend", []], + \ ["endstart", ["end start", "start"]], + \ ["startprobar", ["startprebar", "start prebar", "startbar"]], + \ ["startnouend", ["start nouend", "startend"]], + \ ]) + + call LoadAffAndDic(g:test_data_aff6, g:test_data_dic6) + call RunGoodBad("meea1 meea\xE9 bar prebar barbork prebarbork leadprebar lead end leadend leadmiddleend", + \ "bad: mee meea2 prabar probarbirk middle leadmiddle middleend endlead leadprobar", + \ ["bar", "barbork", "end", "lead", "meea1", "meea\xE9", "prebar", "prebarbork"], + \ [ + \ ["bad", ["bar", "end", "lead"]], + \ ["mee", ["meea1", "meea\xE9", "bar"]], + \ ["meea2", ["meea1", "meea\xE9", "lead"]], + \ ["prabar", ["prebar", "bar", "leadbar"]], + \ ["probarbirk", ["prebarbork"]], + \ ["middle", []], + \ ["leadmiddle", ["leadmiddleend", "leadmiddlebar"]], + \ ["middleend", []], + \ ["endlead", ["end lead", "lead", "end end"]], + \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]], + \ ]) + + call LoadAffAndDic(g:test_data_aff7, g:test_data_dic7) + call RunGoodBad("meea1 meea\xE9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail", + \ "bad: mee meea2 prabar probarmaat middle leadmiddle middletail taillead leadprobar", + \ ["bar", "barmeat", "lead", "meea1", "meea\xE9", "prebar", "prebarmeat", "tail"], + \ [ + \ ["bad", ["bar", "lead", "tail"]], + \ ["mee", ["meea1", "meea\xE9", "bar"]], + \ ["meea2", ["meea1", "meea\xE9", "lead"]], + \ ["prabar", ["prebar", "bar", "leadbar"]], + \ ["probarmaat", ["prebarmeat"]], + \ ["middle", []], + \ ["leadmiddle", ["leadmiddlebar"]], + \ ["middletail", []], + \ ["taillead", ["tail lead", "tail"]], + \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]], + \ ]) +endfunc + +func Test_zz_NOSLITSUGS() + call LoadAffAndDic(g:test_data_aff8, g:test_data_dic8) + call RunGoodBad("foo bar faabar", "bad: foobar barfoo", + \ ["bar", "faabar", "foo"], + \ [ + \ ["bad", ["bar", "foo"]], + \ ["foobar", ["faabar", "foo bar", "bar"]], + \ ["barfoo", ["bar foo", "bar", "foo"]], + \ ]) +endfunc + +" Numbers +func Test_zz_Numbers() + call LoadAffAndDic(g:test_data_aff9, g:test_data_dic9) + call RunGoodBad("0b1011 0777 1234 0x01ff", "", + \ ["bar", "foo"], + \ [ + \ ]) +endfunc + +function FirstSpellWord() + call feedkeys("/^start:\n", 'tx') + normal ]smm + let [str, a] = spellbadword() + return str +endfunc + +function SecondSpellWord() + normal `m]s + let [str, a] = spellbadword() + return str +endfunc + +"Test with SAL instead of SOFO items; test automatic reloading +func Test_zz_sal_and_addition() + throw 'skipped: Nvim does not support enc=latin1' + set enc=latin1 + set spellfile= + call writefile(g:test_data_dic1, "Xtest.dic") + call writefile(g:test_data_aff_sal, "Xtest.aff") + mkspell! Xtest Xtest + set spl=Xtest.latin1.spl spell + call assert_equal('kbltykk', soundfold('goobledygoook')) + call assert_equal('kprnfn', soundfold('koprnven')) + call assert_equal('*fls kswts tl', soundfold('oeverloos gezwets edale')) + + "also use an addition file + call writefile(["/regions=usgbnz", "elequint/2", "elekwint/3"], "Xtest.latin1.add") + mkspell! Xtest.latin1.add.spl Xtest.latin1.add + + bwipe! + call setline(1, ["start: elequint test elekwint test elekwent asdf"]) + + set spellfile=Xtest.latin1.add + call assert_equal("elekwent", FirstSpellWord()) + + set spl=Xtest_us.latin1.spl + call assert_equal("elequint", FirstSpellWord()) + call assert_equal("elekwint", SecondSpellWord()) + + set spl=Xtest_gb.latin1.spl + call assert_equal("elekwint", FirstSpellWord()) + call assert_equal("elekwent", SecondSpellWord()) + + set spl=Xtest_nz.latin1.spl + call assert_equal("elequint", FirstSpellWord()) + call assert_equal("elekwent", SecondSpellWord()) + + set spl=Xtest_ca.latin1.spl + call assert_equal("elequint", FirstSpellWord()) + call assert_equal("elekwint", SecondSpellWord()) +endfunc + +func Test_region_error() + messages clear + call writefile(["/regions=usgbnz", "elequint/0"], "Xtest.latin1.add") + mkspell! Xtest.latin1.add.spl Xtest.latin1.add + call assert_match('Invalid region nr in Xtest.latin1.add line 2: 0', execute('messages')) + call delete('Xtest.latin1.add') + call delete('Xtest.latin1.add.spl') +endfunc + +" Check using z= in new buffer (crash fixed by patch 7.4a.028). +func Test_zeq_crash() + new + set maxmem=512 spell + call feedkeys('iasdz=:\"', 'tx') + + bwipe! +endfunc + +func LoadAffAndDic(aff_contents, dic_contents) + throw 'skipped: Nvim does not support enc=latin1' + set enc=latin1 + set spellfile= + call writefile(a:aff_contents, "Xtest.aff") + call writefile(a:dic_contents, "Xtest.dic") + " Generate a .spl file from a .dic and .aff file. + mkspell! Xtest Xtest + " use that spell file + set spl=Xtest.latin1.spl spell +endfunc + +func ListWords() + spelldump + %yank + quit + return split(@", "\n") +endfunc + +func TestGoodBadBase() + exe '1;/^good:' + normal 0f:]s + let prevbad = '' + let result = [] + while 1 + let [bad, a] = spellbadword() + if bad == '' || bad == prevbad || bad == 'badend' + break + endif + let prevbad = bad + let lst = spellsuggest(bad, 3) + normal mm + + call add(result, [bad, lst]) + normal `m]s + endwhile + return result +endfunc + +func RunGoodBad(good, bad, expected_words, expected_bad_words) + bwipe! + call setline(1, ["good: ", a:good, a:bad, " badend "]) + let words = ListWords() + call assert_equal(a:expected_words, words[1:-1]) + let bad_words = TestGoodBadBase() + call assert_equal(a:expected_bad_words, bad_words) + bwipe! +endfunc + +let g:test_data_aff1 = [ + \"SET ISO8859-1", + \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ", + \"", + \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF", + \"", + \"SOFOFROM abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xBF", + \"SOFOTO ebctefghejklnnepkrstevvkesebctefghejklnnepkrstevvkeseeeeeeeceeeeeeeedneeeeeeeeeeepseeeeeeeeceeeeeeeedneeeeeeeeeeep?", + \"", + \"MIDWORD\t'-", + \"", + \"KEP =", + \"RAR ?", + \"BAD !", + \"", + \"PFX I N 1", + \"PFX I 0 in .", + \"", + \"PFX O Y 1", + \"PFX O 0 out .", + \"", + \"SFX S Y 2", + \"SFX S 0 s [^s]", + \"SFX S 0 es s", + \"", + \"SFX N N 3", + \"SFX N 0 en [^n]", + \"SFX N 0 nen n", + \"SFX N 0 n .", + \"", + \"REP 3", + \"REP g ch", + \"REP ch g", + \"REP svp s.v.p.", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF", + \ ] +let g:test_data_dic1 = [ + \"123456", + \"test/NO", + \"# comment", + \"wrong", + \"Comment", + \"OK", + \"uk", + \"put/ISO", + \"the end", + \"deol", + \"d\xE9\xF4r", + \ ] +let g:test_data_aff2 = [ + \"SET ISO8859-1", + \"", + \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF", + \"", + \"PFXPOSTPONE", + \"", + \"MIDWORD\t'-", + \"", + \"KEP =", + \"RAR ?", + \"BAD !", + \"", + \"PFX I N 1", + \"PFX I 0 in .", + \"", + \"PFX O Y 1", + \"PFX O 0 out [a-z]", + \"", + \"SFX S Y 2", + \"SFX S 0 s [^s]", + \"SFX S 0 es s", + \"", + \"SFX N N 3", + \"SFX N 0 en [^n]", + \"SFX N 0 nen n", + \"SFX N 0 n .", + \"", + \"REP 3", + \"REP g ch", + \"REP ch g", + \"REP svp s.v.p.", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF", + \ ] +let g:test_data_aff3 = [ + \"SET ISO8859-1", + \"", + \"COMPOUNDMIN 3", + \"COMPOUNDRULE m*", + \"NEEDCOMPOUND x", + \ ] +let g:test_data_dic3 = [ + \"1234", + \"foo/m", + \"bar/mx", + \"m\xEF/m", + \"la/mx", + \ ] +let g:test_data_aff4 = [ + \"SET ISO8859-1", + \"", + \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF", + \"", + \"COMPOUNDRULE m+", + \"COMPOUNDRULE sm*e", + \"COMPOUNDRULE sm+", + \"COMPOUNDMIN 3", + \"COMPOUNDWORDMAX 3", + \"COMPOUNDFORBIDFLAG t", + \"", + \"COMPOUNDSYLMAX 5", + \"SYLLABLE a\xE1e\xE9i\xEDo\xF3\xF6\xF5u\xFA\xFC\xFBy/aa/au/ea/ee/ei/ie/oa/oe/oo/ou/uu/ui", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF", + \"", + \"NEEDAFFIX x", + \"", + \"PFXPOSTPONE", + \"", + \"MIDWORD '-", + \"", + \"SFX q N 1", + \"SFX q 0 -ok .", + \"", + \"SFX a Y 2", + \"SFX a 0 s .", + \"SFX a 0 ize/t .", + \"", + \"PFX p N 1", + \"PFX p 0 pre .", + \"", + \"PFX P N 1", + \"PFX P 0 nou .", + \ ] +let g:test_data_dic4 = [ + \"1234", + \"word/mP", + \"util/am", + \"pro/xq", + \"tomato/m", + \"bork/mp", + \"start/s", + \"end/e", + \ ] +let g:test_data_aff5 = [ + \"SET ISO8859-1", + \"", + \"FLAG long", + \"", + \"NEEDAFFIX !!", + \"", + \"COMPOUNDRULE ssmm*ee", + \"", + \"NEEDCOMPOUND xx", + \"COMPOUNDPERMITFLAG pp", + \"", + \"SFX 13 Y 1", + \"SFX 13 0 bork .", + \"", + \"SFX a1 Y 1", + \"SFX a1 0 a1 .", + \"", + \"SFX a\xE9 Y 1", + \"SFX a\xE9 0 a\xE9 .", + \"", + \"PFX zz Y 1", + \"PFX zz 0 pre/pp .", + \"", + \"PFX yy Y 1", + \"PFX yy 0 nou .", + \ ] +let g:test_data_dic5 = [ + \"1234", + \"foo/a1a\xE9!!", + \"bar/zz13ee", + \"start/ss", + \"end/eeyy", + \"middle/mmxx", + \ ] +let g:test_data_aff6 = [ + \"SET ISO8859-1", + \"", + \"FLAG caplong", + \"", + \"NEEDAFFIX A!", + \"", + \"COMPOUNDRULE sMm*Ee", + \"", + \"NEEDCOMPOUND Xx", + \"", + \"COMPOUNDPERMITFLAG p", + \"", + \"SFX N3 Y 1", + \"SFX N3 0 bork .", + \"", + \"SFX A1 Y 1", + \"SFX A1 0 a1 .", + \"", + \"SFX A\xE9 Y 1", + \"SFX A\xE9 0 a\xE9 .", + \"", + \"PFX Zz Y 1", + \"PFX Zz 0 pre/p .", + \ ] +let g:test_data_dic6 = [ + \"1234", + \"mee/A1A\xE9A!", + \"bar/ZzN3Ee", + \"lead/s", + \"end/Ee", + \"middle/MmXx", + \ ] +let g:test_data_aff7 = [ + \"SET ISO8859-1", + \"", + \"FLAG num", + \"", + \"NEEDAFFIX 9999", + \"", + \"COMPOUNDRULE 2,77*123", + \"", + \"NEEDCOMPOUND 1", + \"COMPOUNDPERMITFLAG 432", + \"", + \"SFX 61003 Y 1", + \"SFX 61003 0 meat .", + \"", + \"SFX 391 Y 1", + \"SFX 391 0 a1 .", + \"", + \"SFX 111 Y 1", + \"SFX 111 0 a\xE9 .", + \"", + \"PFX 17 Y 1", + \"PFX 17 0 pre/432 .", + \ ] +let g:test_data_dic7 = [ + \"1234", + \"mee/391,111,9999", + \"bar/17,61003,123", + \"lead/2", + \"tail/123", + \"middle/77,1", + \ ] +let g:test_data_aff8 = [ + \"SET ISO8859-1", + \"", + \"NOSPLITSUGS", + \ ] +let g:test_data_dic8 = [ + \"1234", + \"foo", + \"bar", + \"faabar", + \ ] +let g:test_data_aff9 = [ + \ ] +let g:test_data_dic9 = [ + \"1234", + \"foo", + \"bar", + \ ] +let g:test_data_aff_sal = [ + \"SET ISO8859-1", + \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ", + \"", + \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF", + \"", + \"MIDWORD\t'-", + \"", + \"KEP =", + \"RAR ?", + \"BAD !", + \"", + \"PFX I N 1", + \"PFX I 0 in .", + \"", + \"PFX O Y 1", + \"PFX O 0 out .", + \"", + \"SFX S Y 2", + \"SFX S 0 s [^s]", + \"SFX S 0 es s", + \"", + \"SFX N N 3", + \"SFX N 0 en [^n]", + \"SFX N 0 nen n", + \"SFX N 0 n .", + \"", + \"REP 3", + \"REP g ch", + \"REP ch g", + \"REP svp s.v.p.", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF", + \"", + \"SAL AH(AEIOUY)-^ *H", + \"SAL AR(AEIOUY)-^ *R", + \"SAL A(HR)^ *", + \"SAL A^ *", + \"SAL AH(AEIOUY)- H", + \"SAL AR(AEIOUY)- R", + \"SAL A(HR) _", + \"SAL \xC0^ *", + \"SAL \xC5^ *", + \"SAL BB- _", + \"SAL B B", + \"SAL CQ- _", + \"SAL CIA X", + \"SAL CH X", + \"SAL C(EIY)- S", + \"SAL CK K", + \"SAL COUGH^ KF", + \"SAL CC< C", + \"SAL C K", + \"SAL DG(EIY) K", + \"SAL DD- _", + \"SAL D T", + \"SAL \xC9< E", + \"SAL EH(AEIOUY)-^ *H", + \"SAL ER(AEIOUY)-^ *R", + \"SAL E(HR)^ *", + \"SAL ENOUGH^$ *NF", + \"SAL E^ *", + \"SAL EH(AEIOUY)- H", + \"SAL ER(AEIOUY)- R", + \"SAL E(HR) _", + \"SAL FF- _", + \"SAL F F", + \"SAL GN^ N", + \"SAL GN$ N", + \"SAL GNS$ NS", + \"SAL GNED$ N", + \"SAL GH(AEIOUY)- K", + \"SAL GH _", + \"SAL GG9 K", + \"SAL G K", + \"SAL H H", + \"SAL IH(AEIOUY)-^ *H", + \"SAL IR(AEIOUY)-^ *R", + \"SAL I(HR)^ *", + \"SAL I^ *", + \"SAL ING6 N", + \"SAL IH(AEIOUY)- H", + \"SAL IR(AEIOUY)- R", + \"SAL I(HR) _", + \"SAL J K", + \"SAL KN^ N", + \"SAL KK- _", + \"SAL K K", + \"SAL LAUGH^ LF", + \"SAL LL- _", + \"SAL L L", + \"SAL MB$ M", + \"SAL MM M", + \"SAL M M", + \"SAL NN- _", + \"SAL N N", + \"SAL OH(AEIOUY)-^ *H", + \"SAL OR(AEIOUY)-^ *R", + \"SAL O(HR)^ *", + \"SAL O^ *", + \"SAL OH(AEIOUY)- H", + \"SAL OR(AEIOUY)- R", + \"SAL O(HR) _", + \"SAL PH F", + \"SAL PN^ N", + \"SAL PP- _", + \"SAL P P", + \"SAL Q K", + \"SAL RH^ R", + \"SAL ROUGH^ RF", + \"SAL RR- _", + \"SAL R R", + \"SAL SCH(EOU)- SK", + \"SAL SC(IEY)- S", + \"SAL SH X", + \"SAL SI(AO)- X", + \"SAL SS- _", + \"SAL S S", + \"SAL TI(AO)- X", + \"SAL TH @", + \"SAL TCH-- _", + \"SAL TOUGH^ TF", + \"SAL TT- _", + \"SAL T T", + \"SAL UH(AEIOUY)-^ *H", + \"SAL UR(AEIOUY)-^ *R", + \"SAL U(HR)^ *", + \"SAL U^ *", + \"SAL UH(AEIOUY)- H", + \"SAL UR(AEIOUY)- R", + \"SAL U(HR) _", + \"SAL V^ W", + \"SAL V F", + \"SAL WR^ R", + \"SAL WH^ W", + \"SAL W(AEIOU)- W", + \"SAL X^ S", + \"SAL X KS", + \"SAL Y(AEIOU)- Y", + \"SAL ZZ- _", + \"SAL Z S", + \ ] diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index a3bc04dcd0..f2dfdc7019 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -106,3 +106,11 @@ function! Test_substitute_variants() endfor endfor endfunction + +func Test_substitute_repeat() + " This caused an invalid memory access. + split Xfile + s/^/x + call feedkeys("Qsc\<CR>y", 'tx') + bwipe! +endfunc diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim new file mode 100644 index 0000000000..0446bd9105 --- /dev/null +++ b/src/nvim/testdir/test_system.vim @@ -0,0 +1,48 @@ +" Tests for system() and systemlist() + +function! Test_System() + if !executable('echo') || !executable('cat') || !executable('wc') + return + endif + let out = system('echo 123') + " On Windows we may get a trailing space. + if out != "123 \n" + call assert_equal("123\n", out) + endif + + let out = systemlist('echo 123') + " On Windows we may get a trailing space and CR. + if out != ["123 \r"] + call assert_equal(['123'], out) + endif + + call assert_equal('123', system('cat', '123')) + call assert_equal(['123'], systemlist('cat', '123')) + call assert_equal(["as\<NL>df"], systemlist('cat', ["as\<NL>df"])) + + new Xdummy + call setline(1, ['asdf', "pw\<NL>er", 'xxxx']) + let out = system('wc -l', bufnr('%')) + " On OS/X we get leading spaces + let out = substitute(out, '^ *', '', '') + call assert_equal("3\n", out) + + let out = systemlist('wc -l', bufnr('%')) + " On Windows we may get a trailing CR. + if out != ["3\r"] + " On OS/X we get leading spaces + if type(out) == v:t_list + let out[0] = substitute(out[0], '^ *', '', '') + endif + call assert_equal(['3'], out) + endif + + let out = systemlist('cat', bufnr('%')) + " On Windows we may get a trailing CR. + if out != ["asdf\r", "pw\<NL>er\r", "xxxx\r"] + call assert_equal(['asdf', "pw\<NL>er", 'xxxx'], out) + endif + bwipe! + + call assert_fails('call system("wc -l", 99999)', 'E86:') +endfunction diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index df5b41a64b..2349bd2ae9 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -500,7 +500,7 @@ static void final_column_wrap(UI *ui) UGrid *grid = &data->grid; if (grid->col == ui->width) { grid->col = 0; - if (grid->row < ui->height) { + if (grid->row < MIN(ui->height, grid->height - 1)) { grid->row++; } } diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c index 2b5e96ee60..e0880b4c76 100644 --- a/src/nvim/ugrid.c +++ b/src/nvim/ugrid.c @@ -135,6 +135,7 @@ static void destroy_cells(UGrid *grid) xfree(grid->cells[i]); } xfree(grid->cells); + grid->cells = NULL; } } diff --git a/src/nvim/undo.c b/src/nvim/undo.c index f611a3bb29..b902f82f31 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -2941,17 +2941,20 @@ bool curbufIsChanged(void) && (curbuf->b_changed || file_ff_differs(curbuf, true))); } -/* - * For undotree(): Append the list of undo blocks at "first_uhp" to "list". - * Recursive. - */ -void u_eval_tree(u_header_T *first_uhp, list_T *list) +/// Append the list of undo blocks to a newly allocated list +/// +/// For use in undotree(). Recursive. +/// +/// @param[in] first_uhp Undo blocks list to start with. +/// +/// @return [allocated] List with a representation of undo blocks. +list_T *u_eval_tree(const u_header_T *const first_uhp) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET { - u_header_T *uhp = first_uhp; - dict_T *dict; + list_T *const list = tv_list_alloc(kListLenMayKnow); - while (uhp != NULL) { - dict = tv_dict_alloc(); + for (const u_header_T *uhp = first_uhp; uhp != NULL; uhp = uhp->uh_prev.ptr) { + dict_T *const dict = tv_dict_alloc(); tv_dict_add_nr(dict, S_LEN("seq"), (varnumber_T)uhp->uh_seq); tv_dict_add_nr(dict, S_LEN("time"), (varnumber_T)uhp->uh_time); if (uhp == curbuf->b_u_newhead) { @@ -2965,14 +2968,12 @@ void u_eval_tree(u_header_T *first_uhp, list_T *list) } if (uhp->uh_alt_next.ptr != NULL) { - list_T *alt_list = tv_list_alloc(); - // Recursive call to add alternate undo tree. - u_eval_tree(uhp->uh_alt_next.ptr, alt_list); - tv_dict_add_list(dict, S_LEN("alt"), alt_list); + tv_dict_add_list(dict, S_LEN("alt"), u_eval_tree(uhp->uh_alt_next.ptr)); } tv_list_append_dict(list, dict); - uhp = uhp->uh_prev.ptr; } + + return list; } diff --git a/src/nvim/version.c b/src/nvim/version.c index 996c06c902..2181f13c41 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -1281,10 +1281,10 @@ static const int included_patches[] = { 157, 156, 155, - // 154, - // 153, + 154, + 153, 152, - // 151, + 151, 150, 149, 148, diff --git a/src/nvim/window.c b/src/nvim/window.c index b687781dfb..4dfc72f212 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5921,13 +5921,15 @@ void win_get_tabwin(handle_T id, int *tabnr, int *winnr) } } -void win_id2tabwin(typval_T *argvars, list_T *list) +void win_id2tabwin(typval_T *const argvars, typval_T *const rettv) { int winnr = 1; int tabnr = 1; handle_T id = (handle_T)tv_get_number(&argvars[0]); win_get_tabwin(id, &tabnr, &winnr); + + list_T *const list = tv_list_alloc_ret(rettv, 2); tv_list_append_number(list, tabnr); tv_list_append_number(list, winnr); } diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index ff28e3d133..39db831fe3 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -37,7 +37,7 @@ describe('api', function() os.remove(fname) end) - it("VimL error: fails (VimL error), does NOT update v:errmsg", function() + it("parse error: fails (specific error), does NOT update v:errmsg", function() -- Most API methods return generic errors (or no error) if a VimL -- expression fails; nvim_command returns the VimL error details. local status, rv = pcall(nvim, "command", "bogus_command") @@ -45,6 +45,82 @@ describe('api', function() eq("E492:", string.match(rv, "E%d*:")) -- VimL error was returned. eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. end) + + it("runtime error: fails (specific error)", function() + local status, rv = pcall(nvim, "command_output", "buffer 23487") + eq(false, status) -- nvim_command() failed. + eq("E86: Buffer 23487 does not exist", string.match(rv, "E%d*:.*")) + eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + end) + end) + + describe('nvim_command_output', function() + it('does not induce hit-enter prompt', function() + -- Induce a hit-enter prompt use nvim_input (non-blocking). + nvim('command', 'set cmdheight=1') + nvim('input', [[:echo "hi\nhi2"<CR>]]) + + -- Verify hit-enter prompt. + eq({mode='r', blocking=true}, nvim("get_mode")) + nvim('input', [[<C-c>]]) + + -- Verify NO hit-enter prompt. + nvim('command_output', [[echo "hi\nhi2"]]) + eq({mode='n', blocking=false}, nvim("get_mode")) + end) + + it('captures command output', function() + eq('this is\nspinal tap', + nvim('command_output', [[echo "this is\nspinal tap"]])) + end) + + it('captures empty command output', function() + eq('', nvim('command_output', 'echo')) + end) + + it('captures single-char command output', function() + eq('x', nvim('command_output', 'echo "x"')) + end) + + it('captures multiple commands', function() + eq('foo\n 1 %a "[No Name]" line 1', + nvim('command_output', 'echo "foo" | ls')) + end) + + it('captures nested execute()', function() + eq('\nnested1\nnested2\n 1 %a "[No Name]" line 1', + nvim('command_output', + [[echo execute('echo "nested1\nnested2"') | ls]])) + end) + + it('captures nested nvim_command_output()', function() + eq('nested1\nnested2\n 1 %a "[No Name]" line 1', + nvim('command_output', + [[echo nvim_command_output('echo "nested1\nnested2"') | ls]])) + end) + + it('does not return shell |:!| output', function() + eq(':!echo "foo"\r\n', nvim('command_output', [[!echo "foo"]])) + end) + + it("parse error: fails (specific error), does NOT update v:errmsg", function() + local status, rv = pcall(nvim, "command_output", "bogus commannnd") + eq(false, status) -- nvim_command_output() failed. + eq("E492: Not an editor command: bogus commannnd", + string.match(rv, "E%d*:.*")) + eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + -- Verify NO hit-enter prompt. + eq({mode='n', blocking=false}, nvim("get_mode")) + end) + + it("runtime error: fails (specific error)", function() + local status, rv = pcall(nvim, "command_output", "buffer 42") + eq(false, status) -- nvim_command_output() failed. + eq("E86: Buffer 42 does not exist", string.match(rv, "E%d*:.*")) + eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + -- Verify NO hit-enter prompt. + eq({mode='n', blocking=false}, nvim("get_mode")) + end) end) describe('nvim_eval', function() diff --git a/test/functional/autocmd/tabclose_spec.lua b/test/functional/autocmd/tabclose_spec.lua index fb777e7eea..b7c33dc3d8 100644 --- a/test/functional/autocmd/tabclose_spec.lua +++ b/test/functional/autocmd/tabclose_spec.lua @@ -11,10 +11,10 @@ describe('TabClosed', function() repeat nvim('command', 'tabnew') until nvim('eval', 'tabpagenr()') == 6 -- current tab is now 6 - eq("\ntabclosed:6:6:5", nvim('command_output', 'tabclose')) -- close last 6, current tab is now 5 - eq("\ntabclosed:5:5:4", nvim('command_output', 'close')) -- close last window on tab, closes tab - eq("\ntabclosed:2:2:3", nvim('command_output', '2tabclose')) -- close tab 2, current tab is now 3 - eq("\ntabclosed:1:1:2\ntabclosed:1:1:1", nvim('command_output', 'tabonly')) -- close tabs 1 and 2 + eq("tabclosed:6:6:5", nvim('command_output', 'tabclose')) -- close last 6, current tab is now 5 + eq("tabclosed:5:5:4", nvim('command_output', 'close')) -- close last window on tab, closes tab + eq("tabclosed:2:2:3", nvim('command_output', '2tabclose')) -- close tab 2, current tab is now 3 + eq("tabclosed:1:1:2\ntabclosed:1:1:1", nvim('command_output', 'tabonly')) -- close tabs 1 and 2 end) it('is triggered when closing a window via bdelete from another tab', function() @@ -23,7 +23,7 @@ describe('TabClosed', function() nvim('command', '1tabedit Xtestfile') nvim('command', 'normal! 1gt') eq({1, 3}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) - eq("\ntabclosed:2:2:1\ntabclosed:2:2:1", nvim('command_output', 'bdelete Xtestfile')) + eq("tabclosed:2:2:1\ntabclosed:2:2:1", nvim('command_output', 'bdelete Xtestfile')) eq({1, 1}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) end) @@ -35,7 +35,7 @@ describe('TabClosed', function() -- Only one tab is closed, and the alternate file is used for the other. eq({2, 3}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) - eq("\ntabclosed:2:2:2", nvim('command_output', 'bdelete Xtestfile2')) + eq("tabclosed:2:2:2", nvim('command_output', 'bdelete Xtestfile2')) eq('Xtestfile1', nvim('eval', 'bufname("")')) end) end) @@ -48,9 +48,9 @@ describe('TabClosed', function() nvim('command', 'tabnew') until nvim('eval', 'tabpagenr()') == 7 -- current tab is now 7 -- sanity check, we shouldn't match on tabs with numbers other than 2 - eq("\ntabclosed:7:7:6", nvim('command_output', 'tabclose')) + eq("tabclosed:7:7:6", nvim('command_output', 'tabclose')) -- close tab page 2, current tab is now 5 - eq("\ntabclosed:2:2:5\ntabclosed:match", nvim('command_output', '2tabclose')) + eq("tabclosed:2:2:5\ntabclosed:match", nvim('command_output', '2tabclose')) end) end) @@ -59,7 +59,7 @@ describe('TabClosed', function() nvim('command', 'au! TabClosed * echom "tabclosed:".expand("<afile>").":".expand("<amatch>").":".tabpagenr()') nvim('command', 'tabedit Xtestfile') eq({2, 2}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) - eq("\ntabclosed:2:2:1", nvim('command_output', 'close')) + eq("tabclosed:2:2:1", nvim('command_output', 'close')) eq({1, 1}, nvim('eval', '[tabpagenr(), tabpagenr("$")]')) end) end) diff --git a/test/functional/autocmd/tabnewentered_spec.lua b/test/functional/autocmd/tabnewentered_spec.lua index bdbe677132..59cac07b34 100644 --- a/test/functional/autocmd/tabnewentered_spec.lua +++ b/test/functional/autocmd/tabnewentered_spec.lua @@ -7,14 +7,14 @@ describe('TabNewEntered', function() it('matches when entering any new tab', function() clear() nvim('command', 'au! TabNewEntered * echom "tabnewentered:".tabpagenr().":".bufnr("")') - eq("\ntabnewentered:2:2", nvim('command_output', 'tabnew')) - eq("\n\"test.x2\" [New File]\ntabnewentered:3:3", nvim('command_output', 'tabnew test.x2')) + eq("tabnewentered:2:2", nvim('command_output', 'tabnew')) + eq("tabnewentered:3:3", nvim('command_output', 'tabnew test.x2')) end) end) describe('with FILE as <afile>', function() it('matches when opening a new tab for FILE', function() nvim('command', 'au! TabNewEntered Xtest-tabnewentered echom "tabnewentered:match"') - eq('\n"Xtest-tabnewentered" [New File]\ntabnewentered:4:4\ntabnewentered:match', + eq('tabnewentered:4:4\ntabnewentered:match', nvim('command_output', 'tabnew Xtest-tabnewentered')) end) end) @@ -24,7 +24,7 @@ describe('TabNewEntered', function() nvim('command', 'au! TabNewEntered * echom "entered"') nvim('command', 'tabnew test.x2') nvim('command', 'split') - eq('\nentered', nvim('command_output', 'execute "normal \\<C-W>T"')) + eq('entered', nvim('command_output', 'execute "normal \\<C-W>T"')) end) end) end) diff --git a/test/functional/eval/getline_spec.lua b/test/functional/eval/getline_spec.lua new file mode 100644 index 0000000000..3c56bde094 --- /dev/null +++ b/test/functional/eval/getline_spec.lua @@ -0,0 +1,39 @@ +local helpers = require('test.functional.helpers')(after_each) + +local call = helpers.call +local clear = helpers.clear +local eq = helpers.eq +local expect = helpers.expect + +describe('getline()', function() + before_each(function() + clear() + call('setline', 1, {'a', 'b', 'c'}) + expect([[ + a + b + c]]) + end) + + it('returns empty string for invalid line', function() + eq('', call('getline', -1)) + eq('', call('getline', 0)) + eq('', call('getline', 4)) + end) + + it('returns empty list for invalid range', function() + eq({}, call('getline', 2, 1)) + eq({}, call('getline', -1, 1)) + eq({}, call('getline', 4, 4)) + end) + + it('returns value of valid line', function() + eq('b', call('getline', 2)) + eq('a', call('getline', '.')) + end) + + it('returns value of valid range', function() + eq({'a', 'b'}, call('getline', 1, 2)) + eq({'a', 'b', 'c'}, call('getline', 1, 4)) + end) +end) diff --git a/test/functional/eval/system_spec.lua b/test/functional/eval/system_spec.lua index e2b12b6bc9..4d1630042b 100644 --- a/test/functional/eval/system_spec.lua +++ b/test/functional/eval/system_spec.lua @@ -5,6 +5,7 @@ local eq, call, clear, eval, feed_command, feed, nvim = helpers.eq, helpers.call, helpers.clear, helpers.eval, helpers.feed_command, helpers.feed, helpers.nvim local command = helpers.command +local exc_exec = helpers.exc_exec local iswin = helpers.iswin local Screen = require('test.functional.ui.screen') @@ -274,9 +275,12 @@ describe('system()', function() end) end) - describe('input passed as Number', function() - it('stringifies the input', function() - eq('1', eval('system("cat", 1)')) + describe('Number input', function() + it('is treated as a buffer id', function() + command("put ='text in buffer 1'") + eq('\ntext in buffer 1\n', eval('system("cat", 1)')) + eq('Vim(echo):E86: Buffer 42 does not exist', + exc_exec('echo system("cat", 42)')) end) end) diff --git a/test/functional/ex_cmds/map_spec.lua b/test/functional/ex_cmds/map_spec.lua index b46f83405e..84d5bc2335 100644 --- a/test/functional/ex_cmds/map_spec.lua +++ b/test/functional/ex_cmds/map_spec.lua @@ -5,6 +5,7 @@ local feed = helpers.feed local meths = helpers.meths local clear = helpers.clear local command = helpers.command +local expect = helpers.expect describe(':*map', function() before_each(clear) @@ -18,4 +19,10 @@ describe(':*map', function() feed('\24\25<C-x><C-y>') eq(4, meths.get_var('counter')) end) + + it(':imap <M-">', function() + command('imap <M-"> foo') + feed('i-<M-">-') + expect('-foo-') + end) end) diff --git a/test/functional/ex_cmds/sign_spec.lua b/test/functional/ex_cmds/sign_spec.lua index b37e6e8563..df0f5db860 100644 --- a/test/functional/ex_cmds/sign_spec.lua +++ b/test/functional/ex_cmds/sign_spec.lua @@ -16,8 +16,8 @@ describe('sign', function() nvim('command', 'sign place 34 line=3 name=Foo buffer='..buf2) -- now unplace without specifying a buffer nvim('command', 'sign unplace 34') - eq("\n--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf1)) - eq("\n--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf2)) + eq("--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf1)) + eq("--- Signs ---\n", nvim('command_output', 'sign place buffer='..buf2)) end) end) end) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 31a2c3b3ff..dfc4694272 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -644,7 +644,7 @@ local function alter_slashes(obj) end local function hexdump(str) - local len = string.len( str ) + local len = string.len(str) local dump = "" local hex = "" local asc = "" @@ -652,22 +652,20 @@ local function hexdump(str) for i = 1, len do if 1 == i % 8 then dump = dump .. hex .. asc .. "\n" - hex = string.format( "%04x: ", i - 1 ) + hex = string.format("%04x: ", i - 1) asc = "" end - local ord = string.byte( str, i ) - hex = hex .. string.format( "%02x ", ord ) + local ord = string.byte(str, i) + hex = hex .. string.format("%02x ", ord) if ord >= 32 and ord <= 126 then - asc = asc .. string.char( ord ) + asc = asc .. string.char(ord) else asc = asc .. "." end end - return dump .. hex - .. string.rep( " ", 8 - len % 8 ) .. asc - + return dump .. hex .. string.rep(" ", 8 - len % 8) .. asc end local module = { diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 745bfeecf9..a62c03b70f 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -6,15 +6,18 @@ local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') local Screen = require('test.functional.ui.screen') local eq = helpers.eq -local feed_data = thelpers.feed_data local feed_command = helpers.feed_command +local feed_data = thelpers.feed_data local clear = helpers.clear +local command = helpers.command +local eval = helpers.eval local nvim_dir = helpers.nvim_dir local retry = helpers.retry local nvim_prog = helpers.nvim_prog local nvim_set = helpers.nvim_set local ok = helpers.ok local read_file = helpers.read_file +local wait = helpers.wait if helpers.pending_win32(pending) then return end @@ -40,6 +43,28 @@ describe('tui', function() screen:detach() end) + it('rapid resize #7572 #7628', function() + -- Need buffer rows to provoke the behavior. + feed_data(":edit test/functional/fixtures/bigfile.txt:") + command('call jobresize(b:terminal_job_id, 58, 9)') + command('call jobresize(b:terminal_job_id, 62, 13)') + command('call jobresize(b:terminal_job_id, 100, 42)') + command('call jobresize(b:terminal_job_id, 37, 1000)') + -- Resize to <5 columns. + screen:try_resize(4, 44) + command('call jobresize(b:terminal_job_id, 4, 1000)') + -- Resize to 1 row, then to 1 column, then increase rows to 4. + screen:try_resize(44, 1) + command('call jobresize(b:terminal_job_id, 44, 1)') + screen:try_resize(1, 1) + command('call jobresize(b:terminal_job_id, 1, 1)') + screen:try_resize(1, 4) + command('call jobresize(b:terminal_job_id, 1, 4)') + screen:try_resize(57, 17) + command('call jobresize(b:terminal_job_id, 57, 17)') + eq(2, eval("1+1")) -- Still alive? + end) + it('accepts basic utf-8 input', function() feed_data('iabc\ntest1\ntest2') screen:expect([[ @@ -448,7 +473,7 @@ describe("tui 't_Co' (terminal colors)", function() nvim_set)) feed_data(":echo &t_Co\n") - helpers.wait() + wait() local tline if maxcolors == 8 or maxcolors == 16 then tline = "~ " diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index 73fe94c056..ffb6a26aef 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -849,7 +849,7 @@ describe('Ex commands coloring support', function() {EOB:~ }| | ]]) - eq('\nError detected while processing :\nE605: Exception not caught: 42', + eq('Error detected while processing :\nE605: Exception not caught: 42', meths.command_output('messages')) end) it('errors out when failing to get callback', function() diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua index 6babd4be77..3d1c42c3a0 100644 --- a/test/unit/eval/helpers.lua +++ b/test/unit/eval/helpers.lua @@ -312,7 +312,7 @@ local lua2typvalt_type_tab = { processed[l].lv_refcount = processed[l].lv_refcount + 1 return typvalt(eval.VAR_LIST, {v_list=processed[l]}) end - local lst = populate_list(eval.tv_list_alloc(), l, processed) + local lst = populate_list(eval.tv_list_alloc(#l), l, processed) return typvalt(eval.VAR_LIST, {v_list=lst}) end, [dict_type] = function(l, processed) @@ -433,7 +433,8 @@ local function int(n) end local function list(...) - return populate_list(ffi.gc(eval.tv_list_alloc(), eval.tv_list_unref), + return populate_list(ffi.gc(eval.tv_list_alloc(select('#', ...)), + eval.tv_list_unref), {...}, {}) end diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index b668144175..919a42fbb9 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -2407,7 +2407,7 @@ describe('typval.c', function() describe('list ret()', function() itp('works', function() local rettv = typvalt(lib.VAR_UNKNOWN) - local l = lib.tv_list_alloc_ret(rettv) + local l = lib.tv_list_alloc_ret(rettv, 0) eq(empty_list, typvalt2lua(rettv)) eq(rettv.vval.v_list, l) end) diff --git a/test/unit/keymap_spec.lua b/test/unit/keymap_spec.lua new file mode 100644 index 0000000000..595a19eb17 --- /dev/null +++ b/test/unit/keymap_spec.lua @@ -0,0 +1,71 @@ +local helpers = require("test.unit.helpers")(after_each) +local itp = helpers.gen_itp(it) + +local ffi = helpers.ffi +local eq = helpers.eq +local neq = helpers.neq + +local keymap = helpers.cimport("./src/nvim/keymap.h") + +describe('keymap.c', function() + + describe('find_special_key()', function() + local srcp = ffi.new('const unsigned char *[1]') + local modp = ffi.new('int[1]') + + itp('no keycode', function() + srcp[0] = 'abc' + eq(0, keymap.find_special_key(srcp, 3, modp, false, false, false)) + end) + + itp('keycode with multiple modifiers', function() + srcp[0] = '<C-M-S-A>' + neq(0, keymap.find_special_key(srcp, 9, modp, false, false, false)) + neq(0, modp[0]) + end) + + itp('case-insensitive', function() + -- Compare other capitalizations to this. + srcp[0] = '<C-A>' + local all_caps_key = + keymap.find_special_key(srcp, 5, modp, false, false, false) + local all_caps_mod = modp[0] + + srcp[0] = '<C-a>' + eq(all_caps_key, + keymap.find_special_key(srcp, 5, modp, false, false, false)) + eq(all_caps_mod, modp[0]) + + srcp[0] = '<c-A>' + eq(all_caps_key, + keymap.find_special_key(srcp, 5, modp, false, false, false)) + eq(all_caps_mod, modp[0]) + + srcp[0] = '<c-a>' + eq(all_caps_key, + keymap.find_special_key(srcp, 5, modp, false, false, false)) + eq(all_caps_mod, modp[0]) + end) + + itp('double-quote in keycode #7411', function() + -- Unescaped with in_string=false + srcp[0] = '<C-">' + eq(string.byte('"'), + keymap.find_special_key(srcp, 5, modp, false, false, false)) + + -- Unescaped with in_string=true + eq(0, keymap.find_special_key(srcp, 5, modp, false, false, true)) + + -- Escaped with in_string=false + srcp[0] = '<C-\\">' + -- Should fail because the key is invalid + -- (more than 1 non-modifier character). + eq(0, keymap.find_special_key(srcp, 6, modp, false, false, false)) + + -- Escaped with in_string=true + eq(string.byte('"'), + keymap.find_special_key(srcp, 6, modp, false, false, true)) + end) + end) + +end) |