diff options
44 files changed, 605 insertions, 414 deletions
diff --git a/.travis.yml b/.travis.yml index 3cd21170b7..b275a5262d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,6 +41,10 @@ env: # default target name for functional tests - FUNCTIONALTEST=functionaltest - CI_TARGET=tests + # Environment variables for ccache + - CCACHE_COMPRESS=1 + - CCACHE_SLOPPINESS=time_macros,file_macro + - CCACHE_BASEDIR="$TRAVIS_BUILD_DIR" jobs: include: @@ -60,7 +64,7 @@ jobs: - os: linux # Travis creates a cache per compiler. Set a different value here to # store 32-bit dependencies in a separate cache. - compiler: gcc -m32 + compiler: gcc env: BUILD_32BIT=ON - os: osx compiler: clang @@ -87,7 +91,6 @@ install: ci/install.sh before_script: ci/before_script.sh script: ci/script.sh before_cache: ci/before_cache.sh -after_success: ci/after_success.sh addons: apt: @@ -118,6 +121,7 @@ branches: cache: apt: true + ccache: true directories: - "$HOME/.cache/pip" - "$HOME/.cache/nvim-deps" @@ -11,7 +11,7 @@ [](https://codecov.io/gh/neovim/neovim) [](https://scan.coverity.com/projects/2227) [](https://neovim.io/doc/reports/clang) -[](https://neovim.io/doc/reports/pvs) +[](https://neovim.io/doc/reports/pvs/PVS-studio.html.d) [](https://buildd.debian.org/neovim) [](https://github.com/neovim/neovim/releases/) diff --git a/ci/before_cache.sh b/ci/before_cache.sh index d99b8e68c6..c8a1fd5b71 100755 --- a/ci/before_cache.sh +++ b/ci/before_cache.sh @@ -14,6 +14,9 @@ rm -f "${HOME}/.cache/pip/selfcheck.json" echo "before_cache.sh: cache size" du -d 2 "${HOME}/.cache" | sort -n +echo "before_cache.sh: ccache stats" +ccache -s 2>/dev/null || true + # Update the third-party dependency cache only if the build was successful. if ended_successfully; then rm -rf "${HOME}/.cache/nvim-deps" diff --git a/ci/before_script.sh b/ci/before_script.sh index 445996a8df..49b4e068b5 100755 --- a/ci/before_script.sh +++ b/ci/before_script.sh @@ -20,6 +20,9 @@ if [[ -n "${LLVM_SYMBOLIZER}" ]] && [[ ! $(type -P "${LLVM_SYMBOLIZER}") ]]; the exit 1 fi +# Show ccache stats so we can compare in before_cache +ccache -s 2>/dev/null || true + if [[ "${TRAVIS_OS_NAME}" == osx ]]; then # Adds user to a dummy group. # That allows to test changing the group of the file by `os_fchown`. diff --git a/ci/common/build.sh b/ci/common/build.sh index 53d20ebf9b..a3cf64d47a 100644 --- a/ci/common/build.sh +++ b/ci/common/build.sh @@ -30,7 +30,9 @@ build_deps() { mkdir -p "${DEPS_DOWNLOAD_DIR}" # Use cached dependencies if $CACHE_MARKER exists. - if test -f "${CACHE_MARKER}" && ! test "${CACHE_ENABLE}" = "false" ; then + if test "${CACHE_ENABLE}" = "false" ; then + export CCACHE_RECACHE=1 + elif test -f "${CACHE_MARKER}" ; then echo "Using third-party dependencies from Travis cache (last update: $(_stat "${CACHE_MARKER}"))." cp -r "${HOME}/.cache/nvim-deps"/. "${DEPS_BUILD_DIR}" cp -r "${HOME}/.cache/nvim-deps-downloads" "${DEPS_DOWNLOAD_DIR}" diff --git a/ci/install.sh b/ci/install.sh index 053549d6db..50f3490b63 100755 --- a/ci/install.sh +++ b/ci/install.sh @@ -11,6 +11,8 @@ if [[ "${TRAVIS_OS_NAME}" == osx ]]; then brew install ninja brew install gettext brew reinstall -s libtool + brew install ccache + export PATH="/usr/local/opt/ccache/libexec:$PATH" fi echo "Install neovim module for Python 3." diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 4adab1aa76..7067ff6c1d 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -51,7 +51,7 @@ function! s:shellify(cmd) abort return a:cmd endif return join(map(copy(a:cmd), - \'v:val =~# ''\m[\-.a-zA-Z_/]'' ? shellescape(v:val) : v:val'), ' ') + \'v:val =~# ''\m[^\-.a-zA-Z_/]'' ? shellescape(v:val) : v:val'), ' ') endfunction " Run a system command and timeout after 30 seconds. @@ -70,7 +70,8 @@ function! s:system(cmd, ...) abort let jobid = jobstart(a:cmd, opts) if jobid < 1 - call health#report_error(printf('Command error (job=%d): %s', jobid, s:shellify(a:cmd))) + call health#report_error(printf('Command error (job=%d): `%s` (in %s)', + \ jobid, s:shellify(a:cmd), string(getcwd()))) let s:shell_error = 1 return opts.output endif @@ -84,8 +85,8 @@ function! s:system(cmd, ...) abort call health#report_error(printf('Command timed out: %s', s:shellify(a:cmd))) call jobstop(jobid) elseif s:shell_error != 0 && !ignore_error - call health#report_error(printf("Command error (job=%d): %s\nOutput: %s", jobid, - \ s:shellify(a:cmd), opts.output)) + call health#report_error(printf("Command error (job=%d): `%s` (in %s)\nOutput: %s", + \ jobid, s:shellify(a:cmd), string(getcwd()), opts.output)) endif return opts.output @@ -185,7 +186,9 @@ function! s:version_info(python) abort endif let nvim_path = s:trim(s:system([ - \ a:python, '-c', 'import neovim; print(neovim.__file__)'])) + \ a:python, '-c', + \ 'import sys; sys.path.remove(""); ' . + \ 'import neovim; print(neovim.__file__)'])) if s:shell_error || empty(nvim_path) return [python_version, 'unable to load neovim Python module', pypi_version, \ nvim_path] diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index 4d43a4582b..f21e2ee4f3 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -19,7 +19,7 @@ function! s:init() abort let s:find_arg = '-l' endif " Check for -l support. - call s:get_page(s:get_path('', 'man')[0:-2]) + call s:get_page(s:get_path('', 'man')) catch /E145:/ " Ignore the error in restricted mode catch /command error .*/ @@ -213,14 +213,16 @@ endfunction function! s:get_path(sect, name) abort if empty(a:sect) - return s:system(['man', s:find_arg, a:name]) + " Some man implementations (OpenBSD) return all available paths from the + " search command, so we get() the first one. #8341 + return substitute(get(split(s:system(['man', s:find_arg, a:name])), 0, ''), '\n\+$', '', '') endif " '-s' flag handles: " - tokens like 'printf(echo)' " - sections starting with '-' " - 3pcap section (found on macOS) " - commas between sections (for section priority) - return s:system(['man', s:find_arg, s:section_arg, a:sect, a:name]) + return substitute(s:system(['man', s:find_arg, s:section_arg, a:sect, a:name]), '\n\+$', '', '') endfunction function! s:verify_exists(sect, name) abort @@ -233,13 +235,10 @@ function! s:verify_exists(sect, name) abort let path = s:get_path('', a:name) endtry endtry - " We need to extract the section from the path because sometimes - " the actual section of the manpage is more specific than the section - " we provided to `man`. Try ':Man 3 App::CLI'. - " Also on linux, it seems that the name is case insensitive. So if one does - " ':Man PRIntf', we still want the name of the buffer to be 'printf' or - " whatever the correct capitilization is. - let path = path[:len(path)-2] + " Extract the section from the path, because sometimes the actual section is + " more specific than what we provided to `man` (try `:Man 3 App::CLI`). + " Also on linux, name seems to be case-insensitive. So for `:Man PRIntf`, we + " still want the name of the buffer to be 'printf'. return s:extract_sect_and_name_path(path) + [path] endfunction diff --git a/runtime/autoload/msgpack.vim b/runtime/autoload/msgpack.vim index a10ac32469..09c99c940d 100644 --- a/runtime/autoload/msgpack.vim +++ b/runtime/autoload/msgpack.vim @@ -40,9 +40,10 @@ function s:msgpack_init_python() abort return s:msgpack_python_type endif let s:msgpack_python_initialized = 1 - for suf in ['', '3'] + for suf in (has('win32') ? ['3'] : ['', '3']) try execute 'python' . suf + \. "\n" \. "def shada_dict_strftime():\n" \. " import datetime\n" \. " import vim\n" @@ -60,12 +61,15 @@ function s:msgpack_init_python() abort \. " fmt = vim.eval('a:format')\n" \. " timestr = vim.eval('a:string')\n" \. " timestamp = datetime.datetime.strptime(timestr, fmt)\n" - \. " timestamp = int(timestamp.timestamp())\n" + \. " try:\n" + \. " timestamp = int(timestamp.timestamp())\n" + \. " except:\n" + \. " timestamp = int(timestamp.strftime('%s'))\n" \. " if timestamp > 2 ** 31:\n" - \. " tsabs = abs(timestamp)" + \. " tsabs = abs(timestamp)\n" \. " return ('{\"_TYPE\": v:msgpack_types.integer,'\n" \. " + '\"_VAL\": [{sign},{v1},{v2},{v3}]}').format(\n" - \. " sign=1 if timestamp >= 0 else -1,\n" + \. " sign=(1 if timestamp >= 0 else -1),\n" \. " v1=((tsabs >> 62) & 0x3),\n" \. " v2=((tsabs >> 31) & (2 ** 31 - 1)),\n" \. " v3=(tsabs & (2 ** 31 - 1)))\n" diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt index 94e7899b6a..8c6edc0fb8 100644 --- a/runtime/doc/nvim_terminal_emulator.txt +++ b/runtime/doc/nvim_terminal_emulator.txt @@ -96,7 +96,6 @@ global configuration. - 'list' is disabled - 'wrap' is disabled -- 'relativenumber' is disabled in |Terminal-mode| (and cannot be enabled) You can change the defaults with a TermOpen autocommand: > au TermOpen * setlocal list diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index a63bef5622..1f880eeb36 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -3924,16 +3924,6 @@ A jump table for the options with a short description can be found at |Q_op|. because the 'w' is used before the next mapping is done. See also |key-mapping|. - *'maxmem'* *'mm'* -'maxmem' 'mm' number (default between 256 to 5120 (system - dependent) or half the amount of memory - available) - global - Maximum amount of memory (in Kbyte) to use for one buffer. When this - limit is reached allocating extra memory for a buffer will cause - other memory to be freed. The maximum usable value is about 2000000. - Use this to work without a limit. Also see 'maxmemtot'. - *'maxmempattern'* *'mmp'* 'maxmempattern' 'mmp' number (default 1000) global @@ -3947,20 +3937,6 @@ A jump table for the options with a short description can be found at |Q_op|. "\(.\)*" on a very long line. ".*" works much better. Vim may run out of memory before hitting the 'maxmempattern' limit. - *'maxmemtot'* *'mmt'* -'maxmemtot' 'mmt' number (default between 2048 and 10240 (system - dependent) or half the amount of memory - available) - global - Maximum amount of memory in Kbyte to use for all buffers together. - The maximum usable value is about 2000000 (2 Gbyte). Use this to work - without a limit. - On 64 bit machines higher values might work. But hey, do you really - need more than 2 Gbyte for text editing? Keep in mind that text is - stored in the swap file, one can edit files > 2 Gbyte anyway. We do - need the memory to store undo info. - Also see 'maxmem'. - *'menuitems'* *'mis'* 'menuitems' 'mis' number (default 25) global diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index a0ca17cc4a..7067c60d2f 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -768,9 +768,7 @@ Short explanation of each option: *option-list* 'maxcombine' 'mco' maximum nr of combining characters displayed 'maxfuncdepth' 'mfd' maximum recursive depth for user functions 'maxmapdepth' 'mmd' maximum recursive depth for mapping -'maxmem' 'mm' maximum memory (in Kbyte) used for one buffer 'maxmempattern' 'mmp' maximum memory (in Kbyte) used for pattern search -'maxmemtot' 'mmt' maximum memory (in Kbyte) used for all buffers 'menuitems' 'mis' maximum number of items in a menu 'mkspellmem' 'msm' memory used before |:mkspell| compresses the tree 'modeline' 'ml' recognize modelines at start or end of file diff --git a/runtime/doc/recover.txt b/runtime/doc/recover.txt index 0533157072..ffea514870 100644 --- a/runtime/doc/recover.txt +++ b/runtime/doc/recover.txt @@ -63,10 +63,8 @@ following ones in your vimrc: This is also very handy when editing files on floppy. Of course you will have to create that "tmp" directory for this to work! -For read-only files, a swap file is not used. Unless the file is big, causing -the amount of memory used to be higher than given with 'maxmem' or -'maxmemtot'. And when making a change to a read-only file, the swap file is -created anyway. +For read-only files, a swap file is not used right away. The swap file is +created only when making changes. The 'swapfile' option can be reset to avoid creating a swapfile. And the |:noswapfile| modifier can be used to not create a swapfile for a new buffer. diff --git a/runtime/doc/usr_11.txt b/runtime/doc/usr_11.txt index 42009519df..42aa1d9100 100644 --- a/runtime/doc/usr_11.txt +++ b/runtime/doc/usr_11.txt @@ -294,8 +294,6 @@ If you really don't want to see this message, you can add the 'A' flag to the disk. 'updatetime' Timeout after which the swap file is flushed to disk. 'directory' List of directory names where to store the swap file. -'maxmem' Limit for memory usage before writing text to the swap file. -'maxmemtot' Same, but for all files in total. ============================================================================== diff --git a/runtime/doc/vi_diff.txt b/runtime/doc/vi_diff.txt index 917e0e6f80..139cd3749a 100644 --- a/runtime/doc/vi_diff.txt +++ b/runtime/doc/vi_diff.txt @@ -38,18 +38,6 @@ Information for undo and text in registers is kept in memory, thus when making undo levels and the text that can be kept in registers. Other things are also kept in memory: Command-line history, error messages for Quickfix mode, etc. -Memory usage limits -------------------- - -The option 'maxmem' ('mm') is used to set the maximum memory used for one -buffer (in kilobytes). 'maxmemtot' is used to set the maximum memory used for -all buffers (in kilobytes). The defaults depend on the system used. -These are not hard limits, but tell Vim when to move text into a swap file. -If you don't like Vim to swap to a file, set 'maxmem' and 'maxmemtot' to a -very large value. The swap file will then only be used for recovery. If you -don't want a swap file at all, set 'updatecount' to 0, or use the "-n" -argument when starting Vim. - ============================================================================== 2. The most interesting additions *vim-additions* diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 3575a420b7..6ec8220db6 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -402,6 +402,8 @@ Other options: *'imactivatekey'* *'imak'* *'imstatusfunc'* *'imsf'* *'macatsui'* + 'maxmem' Nvim delegates memory-management to the OS. + 'maxmemtot' Nvim delegates memory-management to the OS. *'restorescreen'* *'rs'* *'norestorescreen'* *'nors'* 'shelltype' *'shortname'* *'sn'* *'noshortname'* *'nosn'* diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 13d0db0390..81eb3f11fd 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -996,10 +996,6 @@ call append("$", "updatecount\tnumber of characters typed to cause a swap file u call append("$", " \tset uc=" . &uc) call append("$", "updatetime\ttime in msec after which the swap file will be updated") call append("$", " \tset ut=" . &ut) -call append("$", "maxmem\tmaximum amount of memory in Kbyte used for one buffer") -call append("$", " \tset mm=" . &mm) -call append("$", "maxmemtot\tmaximum amount of memory in Kbyte used for all buffers") -call append("$", " \tset mmt=" . &mmt) call <SID>Header("command line editing") diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 72542ed3e4..b922036893 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -120,9 +120,7 @@ bool try_end(Error *err) // try_enter/try_leave. trylevel--; - // Without this it stops processing all subsequent VimL commands and - // generates strange error messages if I e.g. try calling Test() in a - // cycle + // Set by emsg(), affects aborting(). See also enter_cleanup(). did_emsg = false; if (got_int) { diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 46ec8fe10b..df4912a51e 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -46,8 +46,7 @@ /// Executes an ex-command. /// -/// On parse error: forwards the Vim error; does not update v:errmsg. -/// On runtime error: forwards the Vim error; does not update v:errmsg. +/// On execution error: fails with VimL error, does not update v:errmsg. /// /// @param command Ex-command string /// @param[out] err Error details (Vim error), if any @@ -103,7 +102,8 @@ Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err) } /// Passes input keys to Nvim. -/// On VimL error: Does not fail, but updates v:errmsg. +/// +/// On execution error: does not fail, but updates v:errmsg. /// /// @param keys to be typed /// @param mode mapping options @@ -169,7 +169,8 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi) } /// Passes keys to Nvim as raw user-input. -/// On VimL error: Does not fail, but updates v:errmsg. +/// +/// On execution error: does not fail, but updates v:errmsg. /// /// Unlike `nvim_feedkeys`, this uses a lower-level input buffer and the call /// is not deferred. This is the most reliable way to send real user input. @@ -213,8 +214,7 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, /// 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. +/// On execution error: fails with VimL error, does not update v:errmsg. /// /// @param command Ex-command string /// @param[out] err Error details (Vim error), if any @@ -259,7 +259,8 @@ theend: /// Evaluates a VimL expression (:help expression). /// Dictionaries and Lists are recursively expanded. -/// On VimL error: Returns a generic error; v:errmsg is not updated. +/// +/// On execution error: fails with VimL error, does not update v:errmsg. /// /// @param expr VimL expression string /// @param[out] err Error details, if any @@ -267,41 +268,79 @@ theend: Object nvim_eval(String expr, Error *err) FUNC_API_SINCE(1) { + static int recursive = 0; // recursion depth Object rv = OBJECT_INIT; - // Evaluate the expression + + // `msg_list` controls the collection of abort-causing non-exception errors, + // which would otherwise be ignored. This pattern is from do_cmdline(). + struct msglist **saved_msg_list = msg_list; + struct msglist *private_msg_list; + msg_list = &private_msg_list; + private_msg_list = NULL; + + // Initialize `force_abort` and `suppress_errthrow` at the top level. + if (!recursive) { + force_abort = false; + suppress_errthrow = false; + current_exception = NULL; + // `did_emsg` is set by emsg(), which cancels execution. + did_emsg = false; + } + recursive++; try_start(); typval_T rettv; - if (eval0((char_u *)expr.data, &rettv, NULL, true) == FAIL) { - api_set_error(err, kErrorTypeException, "Failed to evaluate expression"); - } + int ok = eval0((char_u *)expr.data, &rettv, NULL, true); if (!try_end(err)) { - // No errors, convert the result - rv = vim_to_object(&rettv); + if (ok == FAIL) { + // Should never happen, try_end() should get the error. #8371 + api_set_error(err, kErrorTypeException, "Failed to evaluate expression"); + } else { + rv = vim_to_object(&rettv); + } } - // Free the Vim object tv_clear(&rettv); + msg_list = saved_msg_list; // Restore the exception context. + recursive--; return rv; } -/// Calls a VimL function with the given arguments +/// Execute lua code. Parameters (if any) are available as `...` inside the +/// chunk. The chunk can return a value. /// -/// On VimL error: Returns a generic error; v:errmsg is not updated. +/// Only statements are executed. To evaluate an expression, prefix it +/// with `return`: return my_function(...) /// -/// @param fname Function to call -/// @param args Function arguments packed in an Array +/// @param code lua code to execute +/// @param args Arguments to the code +/// @param[out] err Details of an error encountered while parsing +/// or executing the lua code. +/// +/// @return Return value of lua code if present or NIL. +Object nvim_execute_lua(String code, Array args, Error *err) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY +{ + return executor_exec_lua_api(code, args, err); +} + +/// Calls a VimL function. +/// +/// @param fn Function name +/// @param args Function arguments +/// @param self `self` dict, or NULL for non-dict functions /// @param[out] err Error details, if any /// @return Result of the function call -Object nvim_call_function(String fname, Array args, Error *err) - FUNC_API_SINCE(1) +static Object _call_function(String fn, Array args, dict_T *self, Error *err) { + static int recursive = 0; // recursion depth Object rv = OBJECT_INIT; + if (args.size > MAX_FUNC_ARGS) { api_set_error(err, kErrorTypeValidation, - "Function called with too many arguments."); + "Function called with too many arguments"); return rv; } @@ -314,21 +353,36 @@ Object nvim_call_function(String fname, Array args, Error *err) } } + // `msg_list` controls the collection of abort-causing non-exception errors, + // which would otherwise be ignored. This pattern is from do_cmdline(). + struct msglist **saved_msg_list = msg_list; + struct msglist *private_msg_list; + msg_list = &private_msg_list; + private_msg_list = NULL; + + // Initialize `force_abort` and `suppress_errthrow` at the top level. + if (!recursive) { + force_abort = false; + suppress_errthrow = false; + current_exception = NULL; + // `did_emsg` is set by emsg(), which cancels execution. + did_emsg = false; + } + recursive++; try_start(); - // Call the function typval_T rettv; int dummy; - int r = call_func((char_u *)fname.data, (int)fname.size, - &rettv, (int)args.size, vim_args, NULL, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, - true, NULL, NULL); - if (r == FAIL) { - api_set_error(err, kErrorTypeException, "Error calling function."); - } + // call_func() retval is deceptive, ignore it. Instead we set `msg_list` + // (see above) to capture abort-causing non-exception errors. + (void)call_func((char_u *)fn.data, (int)fn.size, &rettv, (int)args.size, + vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, true, NULL, self); if (!try_end(err)) { rv = vim_to_object(&rettv); } tv_clear(&rettv); + msg_list = saved_msg_list; // Restore the exception context. + recursive--; free_vim_args: while (i > 0) { @@ -338,22 +392,102 @@ free_vim_args: return rv; } -/// Execute lua code. Parameters (if any) are available as `...` inside the -/// chunk. The chunk can return a value. +/// Calls a VimL function with the given arguments. /// -/// Only statements are executed. To evaluate an expression, prefix it -/// with `return`: return my_function(...) +/// On execution error: fails with VimL error, does not update v:errmsg. /// -/// @param code lua code to execute -/// @param args Arguments to the code -/// @param[out] err Details of an error encountered while parsing -/// or executing the lua code. +/// @param fn Function to call +/// @param args Function arguments packed in an Array +/// @param[out] err Error details, if any +/// @return Result of the function call +Object nvim_call_function(String fn, Array args, Error *err) + FUNC_API_SINCE(1) +{ + return _call_function(fn, args, NULL, err); +} + +/// Calls a VimL |Dictionary-function| with the given arguments. /// -/// @return Return value of lua code if present or NIL. -Object nvim_execute_lua(String code, Array args, Error *err) - FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY +/// On execution error: fails with VimL error, does not update v:errmsg. +/// +/// @param dict Dictionary, or String evaluating to a VimL |self| dict +/// @param fn Name of the function defined on the VimL dict +/// @param args Function arguments packed in an Array +/// @param[out] err Error details, if any +/// @return Result of the function call +Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err) + FUNC_API_SINCE(4) { - return executor_exec_lua_api(code, args, err); + Object rv = OBJECT_INIT; + + typval_T rettv; + bool mustfree = false; + switch (dict.type) { + case kObjectTypeString: { + try_start(); + if (eval0((char_u *)dict.data.string.data, &rettv, NULL, true) == FAIL) { + api_set_error(err, kErrorTypeException, + "Failed to evaluate dict expression"); + } + if (try_end(err)) { + return rv; + } + // Evaluation of the string arg created a new dict or increased the + // refcount of a dict. Not necessary for a RPC dict. + mustfree = true; + break; + } + case kObjectTypeDictionary: { + if (!object_to_vim(dict, &rettv, err)) { + goto end; + } + break; + } + default: { + api_set_error(err, kErrorTypeValidation, + "dict argument type must be String or Dictionary"); + return rv; + } + } + dict_T *self_dict = rettv.vval.v_dict; + if (rettv.v_type != VAR_DICT || !self_dict) { + api_set_error(err, kErrorTypeValidation, "dict not found"); + goto end; + } + + if (fn.data && fn.size > 0 && dict.type != kObjectTypeDictionary) { + dictitem_T *const di = tv_dict_find(self_dict, fn.data, (ptrdiff_t)fn.size); + if (di == NULL) { + api_set_error(err, kErrorTypeValidation, "Not found: %s", fn.data); + goto end; + } + if (di->di_tv.v_type == VAR_PARTIAL) { + api_set_error(err, kErrorTypeValidation, + "partial function not supported"); + goto end; + } + if (di->di_tv.v_type != VAR_FUNC) { + api_set_error(err, kErrorTypeValidation, "Not a function: %s", fn.data); + goto end; + } + fn = (String) { + .data = (char *)di->di_tv.vval.v_string, + .size = strlen((char *)di->di_tv.vval.v_string), + }; + } + + if (!fn.data || fn.size < 1) { + api_set_error(err, kErrorTypeValidation, "Invalid (empty) function name"); + goto end; + } + + rv = _call_function(fn, args, self_dict, err); +end: + if (mustfree) { + tv_clear(&rettv); + } + + return rv; } /// Calculates the number of display cells occupied by `text`. @@ -841,27 +975,26 @@ Array nvim_get_api_info(uint64_t channel_id) return rv; } -/// Call many api methods atomically +/// Calls many API methods atomically. /// -/// This has two main usages: Firstly, to perform several requests from an -/// async context atomically, i.e. without processing requests from other rpc -/// clients or redrawing or allowing user interaction in between. Note that api -/// methods that could fire autocommands or do event processing still might do -/// so. For instance invoking the :sleep command might call timer callbacks. -/// Secondly, it can be used to reduce rpc overhead (roundtrips) when doing -/// many requests in sequence. +/// This has two main usages: +/// 1. To perform several requests from an async context atomically, i.e. +/// without interleaving redraws, RPC requests from other clients, or user +/// interactions (however API methods may trigger autocommands or event +/// processing which have such side-effects, e.g. |:sleep| may wake timers). +/// 2. To minimize RPC overhead (roundtrips) of a sequence of many requests. /// /// @param calls an array of calls, where each call is described by an array /// with two elements: the request name, and an array of arguments. /// @param[out] err Details of a validation error of the nvim_multi_request call -/// itself, i e malformatted `calls` parameter. Errors from called methods will +/// itself, i.e. malformed `calls` parameter. Errors from called methods will /// be indicated in the return value, see below. /// /// @return an array with two elements. The first is an array of return /// values. The second is NIL if all calls succeeded. If a call resulted in /// an error, it is a three-element array with the zero-based index of the call /// which resulted in an error, the error type and the error message. If an -/// error ocurred, the values from all preceding calls will still be returned. +/// error occurred, the values from all preceding calls will still be returned. Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index bc4c12e0b7..9e475bf66c 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -1700,6 +1700,9 @@ static void printdigraph(digr_T *dp) *p++ = dp->char1; *p++ = dp->char2; *p++ = ' '; + *p = NUL; + msg_outtrans(buf); + p = buf; // add a space to draw a composing char on if (utf_iscomposing(dp->result)) { @@ -1707,6 +1710,9 @@ static void printdigraph(digr_T *dp) } p += (*mb_char2bytes)(dp->result, p); + *p = NUL; + msg_outtrans_attr(buf, hl_attr(HLF_8)); + p = buf; if (char2cells(dp->result) == 1) { *p++ = ' '; } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index b9df155919..126e9e0da9 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6241,20 +6241,21 @@ bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) /// invoked function uses them. It is called like this: /// new_argcount = argv_func(current_argcount, argv, called_func_argcount) /// -/// Return FAIL when the function can't be called, OK otherwise. -/// Also returns OK when an error was encountered while executing the function. +/// @return FAIL if function cannot be called, else OK (even if an error +/// occurred while executing the function! Set `msg_list` to capture +/// the error, see do_cmdline()). int call_func( const char_u *funcname, // name of the function int len, // length of "name" - typval_T *rettv, // return value goes here + typval_T *rettv, // [out] value goes here int argcount_in, // number of "argvars" typval_T *argvars_in, // vars for arguments, must have "argcount" // PLUS ONE elements! ArgvFunc argv_func, // function to fill in argvars linenr_T firstline, // first line of range linenr_T lastline, // last line of range - int *doesrange, // return: function handled range + int *doesrange, // [out] function handled range bool evaluate, partial_T *partial, // optional, can be NULL dict_T *selfdict_in // Dictionary for "self" @@ -6428,21 +6429,25 @@ call_func( return ret; } -/* - * Give an error message with a function name. Handle <SNR> things. - * "ermsg" is to be passed without translation, use N_() instead of _(). - */ +/// Give an error message with a function name. Handle <SNR> things. +/// +/// @param ermsg must be passed without translation (use N_() instead of _()). +/// @param name function name static void emsg_funcname(char *ermsg, char_u *name) { - char_u *p; + char_u *p; - if (*name == K_SPECIAL) + if (*name == K_SPECIAL) { p = concat_str((char_u *)"<SNR>", name + 3); - else + } else { p = name; + } + EMSG2(_(ermsg), p); - if (p != name) + + if (p != name) { xfree(p); + } } /* diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index e23945c842..7f7851f078 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -28,22 +28,21 @@ #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_eval.c.generated.h" #endif -/* - * Exception handling terms: - * - * :try ":try" command \ - * ... try block | - * :catch RE ":catch" command | - * ... catch clause |- try conditional - * :finally ":finally" command | - * ... finally clause | - * :endtry ":endtry" command / - * - * The try conditional may have any number of catch clauses and at most one - * finally clause. A ":throw" command can be inside the try block, a catch - * clause, the finally clause, or in a function called or script sourced from - * there or even outside the try conditional. Try conditionals may be nested. - */ + +// Exception handling terms: +// +// :try ":try" command ─┐ +// ... try block │ +// :catch RE ":catch" command │ +// ... catch clause ├─ try conditional +// :finally ":finally" command │ +// ... finally clause │ +// :endtry ":endtry" command ─┘ +// +// The try conditional may have any number of catch clauses and at most one +// finally clause. A ":throw" command can be inside the try block, a catch +// clause, the finally clause, or in a function called or script sourced from +// there or even outside the try conditional. Try conditionals may be nested. // Configuration whether an exception is thrown on error or interrupt. When // the preprocessor macros below evaluate to FALSE, an error (did_emsg) or diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index e1198d6f1c..96388a2a9d 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -347,6 +347,13 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) redrawcmd(); } + // redraw the statusline for statuslines that display the current mode + // using the mode() function. + if (KeyTyped) { + curwin->w_redr_status = true; + redraw_statuslines(); + } + did_emsg = false; got_int = false; s->state.check = command_line_check; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 0f2b37e41b..ddc58c2425 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -544,10 +544,6 @@ EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer #define FOR_ALL_BUFFERS_BACKWARDS(buf) \ for (buf_T *buf = lastbuf; buf != NULL; buf = buf->b_prev) -/* Flag that is set when switching off 'swapfile'. It means that all blocks - * are to be loaded into memory. Shouldn't be global... */ -EXTERN int mf_dont_release INIT(= FALSE); /* don't release blocks */ - /* * List of files being edited (global argument list). curwin->w_alist points * to this when the window is using the global argument list. diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c index 4eeba12b87..f6e03e2532 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -61,8 +61,6 @@ #define MEMFILE_PAGE_SIZE 4096 /// default page size -static size_t total_mem_used = 0; /// total memory used for memfiles - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "memfile.c.generated.h" #endif @@ -99,7 +97,6 @@ memfile_T *mf_open(char_u *fname, int flags) mfp->mf_used_first = NULL; // used list is empty mfp->mf_used_last = NULL; mfp->mf_dirty = false; - mfp->mf_used_count = 0; mf_hash_init(&mfp->mf_hash); mf_hash_init(&mfp->mf_trans); mfp->mf_page_size = MEMFILE_PAGE_SIZE; @@ -136,25 +133,6 @@ memfile_T *mf_open(char_u *fname, int flags) mfp->mf_neg_count = 0; mfp->mf_infile_count = mfp->mf_blocknr_max; - // Compute maximum number of pages ('maxmem' is in Kbytes): - // 'mammem' * 1Kbyte / page-size-in-bytes. - // Avoid overflow by first reducing page size as much as possible. - { - int shift = 10; - unsigned page_size = mfp->mf_page_size; - - while (shift > 0 && (page_size & 1) == 0) { - page_size /= 2; - --shift; - } - - assert(p_mm <= LONG_MAX >> shift); // check we don't overflow - assert((uintmax_t)(p_mm << shift) <= UINT_MAX); // check we can cast safely - mfp->mf_used_count_max = (unsigned)(p_mm << shift) / page_size; - if (mfp->mf_used_count_max < 10) - mfp->mf_used_count_max = 10; - } - return mfp; } @@ -198,7 +176,6 @@ void mf_close(memfile_T *mfp, bool del_file) // free entries in used list for (bhdr_T *hp = mfp->mf_used_first, *nextp; hp != NULL; hp = nextp) { - total_mem_used -= hp->bh_page_count * mfp->mf_page_size; nextp = hp->bh_next; mf_free_bhdr(hp); } @@ -223,12 +200,9 @@ void mf_close_file(buf_T *buf, bool getlines) if (getlines) { // get all blocks in memory by accessing all lines (clumsy!) - mf_dont_release = true; - for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; ++lnum) { + for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { (void)ml_get_buf(buf, lnum, false); } - mf_dont_release = false; - // TODO(elmart): should check if all blocks are really in core } if (close(mfp->mf_fd) < 0) { // close the file @@ -246,13 +220,6 @@ void mf_close_file(buf_T *buf, bool getlines) /// and the size it indicates differs from what was guessed. void mf_new_page_size(memfile_T *mfp, unsigned new_size) { - // Correct the memory used for block 0 to the new size, because it will be - // freed with that size later on. - if (new_size >= mfp->mf_page_size) { - total_mem_used += new_size - mfp->mf_page_size; - } else { - total_mem_used -= mfp->mf_page_size - new_size; - } mfp->mf_page_size = new_size; } @@ -262,10 +229,7 @@ void mf_new_page_size(memfile_T *mfp, unsigned new_size) /// @param page_count Desired number of pages. bhdr_T *mf_new(memfile_T *mfp, bool negative, unsigned page_count) { - // If we reached the maximum size for the used memory blocks, release one. - // If a bhdr_T is returned, use it and adjust the page_count if necessary. - // If no bhdr_T is returned, a new one will be created. - bhdr_T *hp = mf_release(mfp, page_count); // the block to be returned + bhdr_T *hp = NULL; // Decide on the number to use: // If there is a free block, use its number. @@ -273,34 +237,22 @@ bhdr_T *mf_new(memfile_T *mfp, bool negative, unsigned page_count) // a positive number. bhdr_T *freep = mfp->mf_free_first; // first free block if (!negative && freep != NULL && freep->bh_page_count >= page_count) { - // If the block in the free list has more pages, take only the number - // of pages needed and allocate a new bhdr_T with data. - // - // If the number of pages matches and mf_release() did not return a - // bhdr_T, use the bhdr_T from the free list and allocate the data. - // - // If the number of pages matches and mf_release() returned a bhdr_T, - // just use the number and free the bhdr_T from the free list if (freep->bh_page_count > page_count) { - if (hp == NULL) { - hp = mf_alloc_bhdr(mfp, page_count); - } + // If the block in the free list has more pages, take only the number + // of pages needed and allocate a new bhdr_T with data. + hp = mf_alloc_bhdr(mfp, page_count); hp->bh_bnum = freep->bh_bnum; freep->bh_bnum += page_count; freep->bh_page_count -= page_count; - } else if (hp == NULL) { // need to allocate memory for this block + } else { // need to allocate memory for this block + // If the number of pages matches use the bhdr_T from the free list and + // allocate the data. void *p = xmalloc(mfp->mf_page_size * page_count); hp = mf_rem_free(mfp); hp->bh_data = p; - } else { // use the number, remove entry from free list - freep = mf_rem_free(mfp); - hp->bh_bnum = freep->bh_bnum; - xfree(freep); } } else { // get a new number - if (hp == NULL) { - hp = mf_alloc_bhdr(mfp, page_count); - } + hp = mf_alloc_bhdr(mfp, page_count); if (negative) { hp->bh_bnum = mfp->mf_blocknr_min--; mfp->mf_neg_count++; @@ -341,13 +293,7 @@ bhdr_T *mf_get(memfile_T *mfp, blocknr_T nr, unsigned page_count) // could check here if the block is in the free list - // Check if we need to flush an existing block. - // If so, use that block. - // If not, allocate a new block. - hp = mf_release(mfp, page_count); - if (hp == NULL) { - hp = mf_alloc_bhdr(mfp, page_count); - } + hp = mf_alloc_bhdr(mfp, page_count); hp->bh_bnum = nr; hp->bh_flags = 0; @@ -514,8 +460,6 @@ static void mf_ins_used(memfile_T *mfp, bhdr_T *hp) } else { hp->bh_next->bh_prev = hp; } - mfp->mf_used_count += hp->bh_page_count; - total_mem_used += hp->bh_page_count * mfp->mf_page_size; } /// Remove block from memfile's used list. @@ -530,82 +474,6 @@ static void mf_rem_used(memfile_T *mfp, bhdr_T *hp) mfp->mf_used_first = hp->bh_next; else hp->bh_prev->bh_next = hp->bh_next; - - mfp->mf_used_count -= hp->bh_page_count; - total_mem_used -= hp->bh_page_count * mfp->mf_page_size; -} - -/// Try to release the least recently used block from the used list if the -/// number of used memory blocks gets too big. -/// -/// @return The block header, when release needed and possible. -/// Resulting block header includes memory block, so it can be -/// reused. Page count is checked to be right. -/// NULL, when release not needed, or not possible. -/// Not needed when number of blocks less than allowed maximum and -/// total memory used below 'maxmemtot'. -/// Not possible when: -/// - Called while closing file. -/// - Tried to create swap file but couldn't. -/// - All blocks are locked. -/// - Unlocked dirty block found, but flush failed. -static bhdr_T *mf_release(memfile_T *mfp, unsigned page_count) -{ - // don't release while in mf_close_file() - if (mf_dont_release) - return NULL; - - /// Need to release a block if the number of blocks for this memfile is - /// higher than the maximum one or total memory used is over 'maxmemtot'. - bool need_release = (mfp->mf_used_count >= mfp->mf_used_count_max - || (total_mem_used >> 10) >= (size_t)p_mmt); - - /// Try to create swap file if the amount of memory used is getting too high. - if (mfp->mf_fd < 0 && need_release && p_uc) { - // find for which buffer this memfile is - buf_T *buf = NULL; - FOR_ALL_BUFFERS(bp) { - if (bp->b_ml.ml_mfp == mfp) { - buf = bp; - break; - } - } - if (buf != NULL && buf->b_may_swap) { - ml_open_file(buf); - } - } - - /// Don't release a block if: - /// there is no file for this memfile - /// or - /// the number of blocks for this memfile is lower than the maximum - /// and - /// total memory used is not up to 'maxmemtot' - if (mfp->mf_fd < 0 || !need_release) - return NULL; - - bhdr_T *hp; - for (hp = mfp->mf_used_last; hp != NULL; hp = hp->bh_prev) - if (!(hp->bh_flags & BH_LOCKED)) - break; - if (hp == NULL) // not a single one that can be released - return NULL; - - // If the block is dirty, write it. - // If the write fails we don't free it. - if ((hp->bh_flags & BH_DIRTY) && mf_write(mfp, hp) == FAIL) - return NULL; - - mf_rem_used(mfp, hp); - mf_rem_hash(mfp, hp); - - /// Make sure page_count of bh_data is right. - if (hp->bh_page_count != page_count) { - xfree(hp->bh_data); - hp->bh_data = xmalloc(mfp->mf_page_size * page_count); - hp->bh_page_count = page_count; - } - return hp; } /// Release as many blocks as possible. diff --git a/src/nvim/memfile_defs.h b/src/nvim/memfile_defs.h index b3c2f3564c..2402d2147d 100644 --- a/src/nvim/memfile_defs.h +++ b/src/nvim/memfile_defs.h @@ -56,7 +56,6 @@ typedef struct mf_hashtab { /// /// The used list is a doubly linked list, most recently used block first. /// The blocks in the used list have a block of memory allocated. -/// mf_used_count is the number of pages in the used list. /// The hash lists are used to quickly find a block in the used list. /// The free list is a single linked list, not sorted. /// The blocks in the free list have no block of memory allocated and @@ -95,8 +94,6 @@ typedef struct memfile { bhdr_T *mf_free_first; /// first block header in free list bhdr_T *mf_used_first; /// mru block header in used list bhdr_T *mf_used_last; /// lru block header in used list - unsigned mf_used_count; /// number of pages in used list - unsigned mf_used_count_max; /// maximum number of pages in memory mf_hashtab_T mf_hash; /// hash lists mf_hashtab_T mf_trans; /// trans lists blocknr_T mf_blocknr_max; /// highest positive block number + 1 diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 1f388dd34c..06de9fda67 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1766,7 +1766,7 @@ errorret: * Don't use the last used line when 'swapfile' is reset, need to load all * blocks. */ - if (buf->b_ml.ml_line_lnum != lnum || mf_dont_release) { + if (buf->b_ml.ml_line_lnum != lnum) { ml_flush_line(buf); /* @@ -2767,9 +2767,8 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) if (buf->b_ml.ml_locked) { if (ML_SIMPLE(action) && buf->b_ml.ml_locked_low <= lnum - && buf->b_ml.ml_locked_high >= lnum - && !mf_dont_release) { - /* remember to update pointer blocks and stack later */ + && buf->b_ml.ml_locked_high >= lnum) { + // remember to update pointer blocks and stack later if (action == ML_INSERT) { ++(buf->b_ml.ml_locked_lineadd); ++(buf->b_ml.ml_locked_high); diff --git a/src/nvim/option.c b/src/nvim/option.c index 96b2413c8f..1da259e6b8 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -619,27 +619,6 @@ void set_init_1(void) } } - /* - * 'maxmemtot' and 'maxmem' may have to be adjusted for available memory - */ - opt_idx = findoption("maxmemtot"); - if (opt_idx >= 0) { - { - // Use half of amount of memory available to Vim. - // If too much to fit in uintptr_t, get uintptr_t max. - uint64_t available_kib = os_get_total_mem_kib(); - uintptr_t n = (available_kib / 2 > UINTPTR_MAX // -V547 - ? UINTPTR_MAX - : (uintptr_t)(available_kib /2)); - options[opt_idx].def_val[VI_DEFAULT] = (char_u *)n; - opt_idx = findoption("maxmem"); - if (opt_idx >= 0) { - options[opt_idx].def_val[VI_DEFAULT] = (char_u *)n; - } - } - } - - { char_u *cdpath; char_u *buf; @@ -3672,10 +3651,11 @@ static void set_option_scriptID_idx(int opt_idx, int opt_flags, int id) options[opt_idx].last_set = last_set; } if (both || (opt_flags & OPT_LOCAL)) { - if (indir & PV_BUF) + if (indir & PV_BUF) { curbuf->b_p_scriptID[indir & PV_MASK] = last_set; - else if (indir & PV_WIN) + } else if (indir & PV_WIN) { curwin->w_p_scriptID[indir & PV_MASK] = last_set; + } } } diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 670af1cc76..f7dfa65053 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -499,9 +499,7 @@ EXTERN long p_mat; // 'matchtime' EXTERN long p_mco; // 'maxcombine' EXTERN long p_mfd; // 'maxfuncdepth' EXTERN long p_mmd; // 'maxmapdepth' -EXTERN long p_mm; // 'maxmem' EXTERN long p_mmp; // 'maxmempattern' -EXTERN long p_mmt; // 'maxmemtot' EXTERN long p_mis; // 'menuitems' EXTERN char_u *p_msm; // 'mkspellmem' EXTERN long p_mls; // 'modelines' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 66018b2475..f1f559fff0 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1512,13 +1512,6 @@ return { defaults={if_true={vi=1000}} }, { - full_name='maxmem', abbreviation='mm', - type='number', scope={'global'}, - vi_def=true, - varname='p_mm', - defaults={if_true={vi=macros('DFLT_MAXMEM')}} - }, - { full_name='maxmempattern', abbreviation='mmp', type='number', scope={'global'}, vi_def=true, @@ -1526,13 +1519,6 @@ return { defaults={if_true={vi=1000}} }, { - full_name='maxmemtot', abbreviation='mmt', - type='number', scope={'global'}, - vi_def=true, - varname='p_mmt', - defaults={if_true={vi=macros('DFLT_MAXMEMTOT')}} - }, - { full_name='menuitems', abbreviation='mis', type='number', scope={'global'}, vi_def=true, diff --git a/src/nvim/os/os_defs.h b/src/nvim/os/os_defs.h index f81785675e..c29af5c160 100644 --- a/src/nvim/os/os_defs.h +++ b/src/nvim/os/os_defs.h @@ -25,15 +25,6 @@ // Command-processing buffer. Use large buffers for all platforms. #define CMDBUFFSIZE 1024 -// Use up to 5 Mbyte for a buffer. -#ifndef DFLT_MAXMEM -# define DFLT_MAXMEM (5 * 1024) -#endif -// use up to 10 Mbyte for Vim. -#ifndef DFLT_MAXMEMTOT -# define DFLT_MAXMEMTOT (10 * 1024) -#endif - // Note: Some systems need both string.h and strings.h (Savage). However, // some systems can't handle both, only use string.h in that case. #include <string.h> diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 2e64eb864f..f034ac33f1 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -309,6 +309,9 @@ void update_screen(int type) if (wp->w_winrow + wp->w_height + wp->w_status_height > valid) { wp->w_redr_status = true; } + if (valid == 0) { + redraw_tabline = true; + } } } else if (msg_scrolled > Rows - 5) { // clearing is faster type = CLEAR; @@ -5307,7 +5310,9 @@ void screen_getbytes(int row, int col, char_u *bytes, int *attrp) bytes[0] = ScreenLines[off]; bytes[1] = NUL; - bytes[utfc_char2bytes(off, bytes)] = NUL; + if (ScreenLinesUC[off] != 0) { + bytes[utfc_char2bytes(off, bytes)] = NUL; + } } } diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index d006477c80..f68bb2458d 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -396,10 +396,8 @@ void terminal_enter(void) win_T *save_curwin = curwin; int save_w_p_cul = curwin->w_p_cul; int save_w_p_cuc = curwin->w_p_cuc; - int save_w_p_rnu = curwin->w_p_rnu; curwin->w_p_cul = false; curwin->w_p_cuc = false; - curwin->w_p_rnu = false; adjust_topline(s->term, buf, 0); // scroll to end // erase the unfocused cursor @@ -417,7 +415,6 @@ void terminal_enter(void) if (save_curwin == curwin) { // save_curwin may be invalid (window closed)! curwin->w_p_cul = save_w_p_cul; curwin->w_p_cuc = save_w_p_cuc; - curwin->w_p_rnu = save_w_p_rnu; } // draw the unfocused cursor diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index 21f2363731..a2828b21d2 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -315,7 +315,7 @@ endfunc " Check using z= in new buffer (crash fixed by patch 7.4a.028). func Test_zeq_crash() new - set maxmem=512 spell + set spell call feedkeys('iasdz=:\"', 'tx') bwipe! diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua index 18229b54ff..e79a60fb10 100644 --- a/test/functional/api/server_requests_spec.lua +++ b/test/functional/api/server_requests_spec.lua @@ -11,6 +11,7 @@ local ok = helpers.ok local meths = helpers.meths local spawn, nvim_argv = helpers.spawn, helpers.nvim_argv local set_session = helpers.set_session +local expect_err = helpers.expect_err describe('server -> client', function() local cid @@ -221,9 +222,8 @@ describe('server -> client', function() end) it('returns an error if the request failed', function() - local status, err = pcall(eval, "rpcrequest(vim, 'does-not-exist')") - eq(false, status) - ok(nil ~= string.match(err, 'Failed to evaluate expression')) + expect_err('Vim:Invalid method name', + eval, "rpcrequest(vim, 'does-not-exist')") end) end) @@ -250,7 +250,7 @@ describe('server -> client', function() end) after_each(function() - funcs.jobstop(jobid) + pcall(funcs.jobstop, jobid) end) if helpers.pending_win32(pending) then return end @@ -261,7 +261,7 @@ describe('server -> client', function() eq({'notification', 'pong', {}}, next_msg()) eq("done!",funcs.rpcrequest(jobid, "write_stderr", "fluff\n")) eq({'notification', 'stderr', {0, {'fluff', ''}}}, next_msg()) - funcs.rpcrequest(jobid, "exit") + pcall(funcs.rpcrequest, jobid, "exit") eq({'notification', 'stderr', {0, {''}}}, next_msg()) eq({'notification', 'exit', {0, 0}}, next_msg()) end) @@ -308,8 +308,8 @@ describe('server -> client', function() it('via ipv4 address', function() local server = spawn(nvim_argv) set_session(server) - local address = funcs.serverstart("127.0.0.1:") - if #address == 0 then + local status, address = pcall(funcs.serverstart, "127.0.0.1:") + if not status then pending('no ipv4 stack', function() end) return end @@ -320,8 +320,8 @@ describe('server -> client', function() it('via ipv6 address', function() local server = spawn(nvim_argv) set_session(server) - local address = funcs.serverstart('::1:') - if #address == 0 then + local status, address = pcall(funcs.serverstart, '::1:') + if not status then pending('no ipv6 stack', function() end) return end diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 599280d684..71fa6632c7 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -4,17 +4,20 @@ local global_helpers = require('test.helpers') local NIL = helpers.NIL local clear, nvim, eq, neq = helpers.clear, helpers.nvim, helpers.eq, helpers.neq +local command = helpers.command +local eval = helpers.eval +local funcs = helpers.funcs +local iswin = helpers.iswin +local meth_pcall = helpers.meth_pcall +local meths = helpers.meths local ok, nvim_async, feed = helpers.ok, helpers.nvim_async, helpers.feed local os_name = helpers.os_name -local meths = helpers.meths -local funcs = helpers.funcs local request = helpers.request -local meth_pcall = helpers.meth_pcall -local command = helpers.command -local iswin = helpers.iswin +local source = helpers.source -local intchar2lua = global_helpers.intchar2lua +local expect_err = global_helpers.expect_err local format_string = global_helpers.format_string +local intchar2lua = global_helpers.intchar2lua local mergedicts_copy = global_helpers.mergedicts_copy describe('api', function() @@ -38,20 +41,20 @@ describe('api', function() os.remove(fname) end) - 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. + it('VimL validation error: fails with specific error', function() local status, rv = pcall(nvim, "command", "bogus_command") eq(false, status) -- nvim_command() failed. eq("E492:", string.match(rv, "E%d*:")) -- VimL error was returned. - eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + eq('', nvim('eval', 'v:errmsg')) -- v:errmsg was not updated. + eq('', eval('v:exception')) end) - it("runtime error: fails (specific error)", function() + it('VimL execution error: fails with 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. + eq('', eval('v:errmsg')) -- v:errmsg was not updated. + eq('', eval('v:exception')) end) end) @@ -107,21 +110,21 @@ describe('api', function() eq(':!echo foo\r\n\nfoo'..win_lf..'\n', nvim('command_output', [[!echo foo]])) end) - it("parse error: fails (specific error), does NOT update v:errmsg", function() + it('VimL validation error: fails with specific error', 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. + eq('', 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() + it('VimL execution error: fails with 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. + eq('', eval('v:errmsg')) -- v:errmsg was not updated. -- Verify NO hit-enter prompt. eq({mode='n', blocking=false}, nvim("get_mode")) end) @@ -143,11 +146,10 @@ describe('api', function() eq(2, request("vim_eval", "1+1")) end) - it("VimL error: fails (generic error), does NOT update v:errmsg", function() - local status, rv = pcall(nvim, "eval", "bogus expression") - eq(false, status) -- nvim_eval() failed. - ok(nil ~= string.find(rv, "Failed to evaluate expression")) - eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + it("VimL error: returns error details, does NOT update v:errmsg", function() + expect_err('E121: Undefined variable: bogus', request, + 'nvim_eval', 'bogus expression') + eq('', eval('v:errmsg')) -- v:errmsg was not updated. end) end) @@ -159,11 +161,100 @@ describe('api', function() eq('foo', nvim('call_function', 'simplify', {'this/./is//redundant/../../../foo'})) end) - it("VimL error: fails (generic error), does NOT update v:errmsg", function() - local status, rv = pcall(nvim, "call_function", "bogus function", {"arg1"}) - eq(false, status) -- nvim_call_function() failed. - ok(nil ~= string.find(rv, "Error calling function")) - eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + it("VimL validation error: returns specific error, does NOT update v:errmsg", function() + expect_err('E117: Unknown function: bogus function', request, + 'nvim_call_function', 'bogus function', {'arg1'}) + expect_err('E119: Not enough arguments for function: atan', request, + 'nvim_call_function', 'atan', {}) + eq('', eval('v:exception')) + eq('', eval('v:errmsg')) -- v:errmsg was not updated. + end) + + it("VimL error: returns error details, does NOT update v:errmsg", function() + expect_err('E808: Number or Float required', request, + 'nvim_call_function', 'atan', {'foo'}) + expect_err('Invalid channel stream "xxx"', request, + 'nvim_call_function', 'chanclose', {999, 'xxx'}) + expect_err('E900: Invalid channel id', request, + 'nvim_call_function', 'chansend', {999, 'foo'}) + eq('', eval('v:exception')) + eq('', eval('v:errmsg')) -- v:errmsg was not updated. + end) + + it("VimL exception: returns exception details, does NOT update v:errmsg", function() + source([[ + function! Foo() abort + throw 'wtf' + endfunction + ]]) + expect_err('wtf', request, + 'nvim_call_function', 'Foo', {}) + eq('', eval('v:exception')) + eq('', eval('v:errmsg')) -- v:errmsg was not updated. + end) + + it('validates args', function() + local too_many_args = { 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x' } + source([[ + function! Foo(...) abort + echo a:000 + endfunction + ]]) + -- E740 + expect_err('Function called with too many arguments', request, + 'nvim_call_function', 'Foo', too_many_args) + end) + end) + + describe('nvim_call_dict_function', function() + it('invokes VimL dict function', function() + source([[ + function! F(name) dict + return self.greeting.', '.a:name.'!' + endfunction + let g:test_dict_fn = { 'greeting':'Hello', 'F':function('F') } + + let g:test_dict_fn2 = { 'greeting':'Hi' } + function g:test_dict_fn2.F2(name) + return self.greeting.', '.a:name.' ...' + endfunction + ]]) + + -- :help Dictionary-function + eq('Hello, World!', nvim('call_dict_function', 'g:test_dict_fn', 'F', {'World'})) + -- Funcref is sent as NIL over RPC. + eq({ greeting = 'Hello', F = NIL }, nvim('get_var', 'test_dict_fn')) + + -- :help numbered-function + eq('Hi, Moon ...', nvim('call_dict_function', 'g:test_dict_fn2', 'F2', {'Moon'})) + -- Funcref is sent as NIL over RPC. + eq({ greeting = 'Hi', F2 = NIL }, nvim('get_var', 'test_dict_fn2')) + + -- Function specified via RPC dict. + source('function! G() dict\n return "@".(self.result)."@"\nendfunction') + eq('@it works@', nvim('call_dict_function', { result = 'it works', G = 'G'}, 'G', {})) + end) + + it('validates args', function() + command('let g:d={"baz":"zub","meep":[]}') + expect_err('Not found: bogus', request, + 'nvim_call_dict_function', 'g:d', 'bogus', {1,2}) + expect_err('Not a function: baz', request, + 'nvim_call_dict_function', 'g:d', 'baz', {1,2}) + expect_err('Not a function: meep', request, + 'nvim_call_dict_function', 'g:d', 'meep', {1,2}) + expect_err('E117: Unknown function: f', request, + 'nvim_call_dict_function', { f = '' }, 'f', {1,2}) + expect_err('Not a function: f', request, + 'nvim_call_dict_function', "{ 'f': '' }", 'f', {1,2}) + expect_err('dict argument type must be String or Dictionary', request, + 'nvim_call_dict_function', 42, 'f', {1,2}) + expect_err('Failed to evaluate dict expression', request, + 'nvim_call_dict_function', 'foo', 'f', {1,2}) + expect_err('dict not found', request, + 'nvim_call_dict_function', '42', 'f', {1,2}) + expect_err('Invalid %(empty%) function name', request, + 'nvim_call_dict_function', "{ 'f': '' }", '', {1,2}) end) end) @@ -301,7 +392,7 @@ describe('api', function() '\tLast set from API client %(channel id %d+%)')) nvim('execute_lua', 'vim.api.nvim_set_option("equalalways", true)', {}) - local status, rv = pcall(nvim, 'command_output', + status, rv = pcall(nvim, 'command_output', 'verbose set equalalways?') eq(true, status) eq(' equalalways\n\tLast set from Lua', rv) diff --git a/test/functional/eval/buf_functions_spec.lua b/test/functional/eval/buf_functions_spec.lua index 7de58766b9..c0e264d4c9 100644 --- a/test/functional/eval/buf_functions_spec.lua +++ b/test/functional/eval/buf_functions_spec.lua @@ -15,6 +15,7 @@ local curwinmeths = helpers.curwinmeths local curtabmeths = helpers.curtabmeths local get_pathsep = helpers.get_pathsep local rmdir = helpers.rmdir +local expect_err = helpers.expect_err local fname = 'Xtest-functional-eval-buf_functions' local fname2 = fname .. '.2' @@ -296,8 +297,8 @@ describe('setbufvar() function', function() eq('Vim(call):E461: Illegal variable name: b:', exc_exec('call setbufvar(1, "", 0)')) eq(true, bufmeths.get_var(buf1, 'number')) - funcs.setbufvar(1, 'changedtick', true) - -- eq(true, bufmeths.get_var(buf1, 'changedtick')) + expect_err('Vim:E46: Cannot change read%-only variable "b:changedtick"', + funcs.setbufvar, 1, 'changedtick', true) eq(2, funcs.getbufvar(1, 'changedtick')) end) end) diff --git a/test/functional/eval/match_functions_spec.lua b/test/functional/eval/match_functions_spec.lua index 7989b22b5e..0ec465a34c 100644 --- a/test/functional/eval/match_functions_spec.lua +++ b/test/functional/eval/match_functions_spec.lua @@ -6,6 +6,7 @@ local clear = helpers.clear local funcs = helpers.funcs local command = helpers.command local exc_exec = helpers.exc_exec +local expect_err = helpers.expect_err before_each(clear) @@ -40,9 +41,11 @@ describe('setmatches()', function() end) it('fails with -1 if highlight group is not defined', function() - eq(-1, funcs.setmatches({{group=1, pattern=2, id=3, priority=4}})) + expect_err('E28: No such highlight group name: 1', funcs.setmatches, + {{group=1, pattern=2, id=3, priority=4}}) eq({}, funcs.getmatches()) - eq(-1, funcs.setmatches({{group=1, pos1={2}, pos2={6}, id=3, priority=4, conceal=5}})) + expect_err('E28: No such highlight group name: 1', funcs.setmatches, + {{group=1, pos1={2}, pos2={6}, id=3, priority=4, conceal=5}}) eq({}, funcs.getmatches()) end) end) diff --git a/test/functional/eval/server_spec.lua b/test/functional/eval/server_spec.lua index 4e4aed864b..563e619b39 100644 --- a/test/functional/eval/server_spec.lua +++ b/test/functional/eval/server_spec.lua @@ -5,6 +5,7 @@ local clear, funcs, meths = helpers.clear, helpers.funcs, helpers.meths local iswin = helpers.iswin local ok = helpers.ok local matches = helpers.matches +local expect_err = helpers.expect_err local function clear_serverlist() for _, server in pairs(funcs.serverlist()) do @@ -86,22 +87,23 @@ describe('server', function() local expected = {} local v4 = '127.0.0.1:12345' - s = funcs.serverstart(v4) - if #s > 0 then + local status, _ = pcall(funcs.serverstart, v4) + if status then table.insert(expected, v4) - funcs.serverstart(v4) -- exists already; ignore + pcall(funcs.serverstart, v4) -- exists already; ignore end local v6 = '::1:12345' - s = funcs.serverstart(v6) - if #s > 0 then + status, _ = pcall(funcs.serverstart, v6) + if status then table.insert(expected, v6) - funcs.serverstart(v6) -- exists already; ignore + pcall(funcs.serverstart, v6) -- exists already; ignore end eq(expected, funcs.serverlist()) clear_serverlist() - funcs.serverstart('127.0.0.1:65536') -- invalid port + expect_err('Failed to start server: invalid argument', + funcs.serverstart, '127.0.0.1:65536') -- invalid port eq({}, funcs.serverlist()) end) diff --git a/test/functional/ex_cmds/digraphs_spec.lua b/test/functional/ex_cmds/digraphs_spec.lua new file mode 100644 index 0000000000..f2d0b76739 --- /dev/null +++ b/test/functional/ex_cmds/digraphs_spec.lua @@ -0,0 +1,38 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local feed = helpers.feed +local Screen = require('test.functional.ui.screen') + +describe(':digraphs', function() + local screen + before_each(function() + clear() + screen = Screen.new(65, 8) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [4] = {bold = true}, + [5] = {background = Screen.colors.LightGrey}, + [6] = {foreground = Screen.colors.Blue1}, + [7] = {bold = true, reverse = true}, + }) + screen:attach() + end) + + it('displays digraphs', function() + feed(':digraphs<CR>') + screen:expect([[ + A@ {6:Å} 197 E` {6:È} 200 E^ {6:Ê} 202 E" {6:Ë} 203 I` {6:Ì} 204 | + I^ {6:Î} 206 I" {6:Ï} 207 N~ {6:Ñ} 209 O` {6:Ò} 210 O^ {6:Ô} 212 | + O~ {6:Õ} 213 /\ {6:×} 215 U` {6:Ù} 217 U^ {6:Û} 219 Ip {6:Þ} 222 | + a` {6:à} 224 a^ {6:â} 226 a~ {6:ã} 227 a" {6:ä} 228 a@ {6:å} 229 | + e` {6:è} 232 e^ {6:ê} 234 e" {6:ë} 235 i` {6:ì} 236 i^ {6:î} 238 | + n~ {6:ñ} 241 o` {6:ò} 242 o^ {6:ô} 244 o~ {6:õ} 245 u` {6:ù} 249 | + u^ {6:û} 251 y" {6:ÿ} 255 | + {3:Press ENTER or type command to continue}^ | + ]]) + end) + +end) + diff --git a/test/functional/legacy/file_perm_spec.lua b/test/functional/legacy/file_perm_spec.lua index 77e82352c5..d61fdc9b83 100644 --- a/test/functional/legacy/file_perm_spec.lua +++ b/test/functional/legacy/file_perm_spec.lua @@ -15,7 +15,7 @@ describe('Test getting and setting file permissions', function() it('file permissions', function() eq('', call('getfperm', tempfile)) - eq(0, call('setfperm', tempfile, 'r------')) + eq(0, call('setfperm', tempfile, 'r--------')) call('writefile', {'one'}, tempfile) eq(9, call('len', call('getfperm', tempfile))) diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index 6da0001cea..760105df6b 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -51,12 +51,12 @@ describe('luaeval()', function() end) describe('recursive lua values', function() it('are successfully transformed', function() - funcs.luaeval('rawset(_G, "d", {})') - funcs.luaeval('rawset(d, "d", d)') + command('lua rawset(_G, "d", {})') + command('lua rawset(d, "d", d)') eq('\n{\'d\': {...@0}}', funcs.execute('echo luaeval("d")')) - funcs.luaeval('rawset(_G, "l", {})') - funcs.luaeval('table.insert(l, l)') + command('lua rawset(_G, "l", {})') + command('lua table.insert(l, l)') eq('\n[[...@0]]', funcs.execute('echo luaeval("l")')) end) end) diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index f8680678ef..41c290a462 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -157,6 +157,27 @@ describe('external cmdline', function() end) end) + it("redraws statusline on entering", function() + command('set laststatus=2') + command('set statusline=%{mode()}') + feed(':') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {3:c^ }| + | + ]], nil, nil, function() + eq({{ + content = { { {}, "" } }, + firstc = ":", + indent = 0, + pos = 0, + prompt = "" + }}, cmdline) + end) + end) + it("works with input()", function() feed(':call input("input", "default")<cr>') screen:expect([[ diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index 7fafd6b352..06e4a19354 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -240,8 +240,8 @@ describe('Screen', function() end) end) - describe('tabnew', function() - it('creates a new buffer', function() + describe('tabs', function() + it('tabnew creates a new buffer', function() command('sp') command('vsp') command('vsp') @@ -299,6 +299,62 @@ describe('Screen', function() | ]]) end) + + it('tabline is redrawn after messages', function() + command('tabnew') + screen:expect([[ + {4: [No Name] }{2: [No Name] }{3: }{4:X}| + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + + feed(':echo "'..string.rep('x\\n', 11)..'"<cr>') + screen:expect([[ + {1: }| + x | + x | + x | + x | + x | + x | + x | + x | + x | + x | + x | + | + {7:Press ENTER or type command to continue}^ | + ]]) + + feed('<cr>') + screen:expect([[ + {4: [No Name] }{2: [No Name] }{3: }{4:X}| + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) end) describe('insert mode', function() @@ -645,4 +701,43 @@ describe('Screen', function() ]]) end) end) + + -- Regression test for #8357 + it('does not have artifacts after temporary chars in insert mode', function() + command('inoremap jk <esc>') + feed('ifooj') + screen:expect([[ + foo^j | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {2:-- INSERT --} | + ]]) + feed('k') + screen:expect([[ + fo^o | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) end) |