diff options
93 files changed, 3266 insertions, 525 deletions
diff --git a/cmake.deps/CMakeLists.txt b/cmake.deps/CMakeLists.txt index 27374a2a9a..5427382783 100644 --- a/cmake.deps/CMakeLists.txt +++ b/cmake.deps/CMakeLists.txt @@ -140,15 +140,15 @@ set(HOSTDEPS_CXX_COMPILER "${DEPS_CXX_COMPILER}") include(ExternalProject) -set(LIBUV_URL https://github.com/libuv/libuv/archive/730e07e2f77a4001bdf6894112271c926399f5a8.tar.gz) -set(LIBUV_SHA256 271869759a7dbdaf1d1bf75f1ec388a7307592153b34ebb52d3934715cbaac8a) +set(LIBUV_URL https://github.com/libuv/libuv/archive/v1.44.2.tar.gz) +set(LIBUV_SHA256 e6e2ba8b4c349a4182a33370bb9be5e23c51b32efb9b9e209d0e8556b73a48da) set(MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/c-4.0.0/msgpack-c-4.0.0.tar.gz) set(MSGPACK_SHA256 420fe35e7572f2a168d17e660ef981a589c9cbe77faa25eb34a520e1fcc032c8) # https://github.com/LuaJIT/LuaJIT/tree/v2.1 -set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/4ef96cff887c268cc676f9b4b1dc9c54a693efd5.tar.gz) -set(LUAJIT_SHA256 ae913e33be80dded08a2fc368787f168305c22808519c962553bf4c8668e9856) +set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/50936d784474747b4569d988767f1b5bab8bb6d0.tar.gz) +set(LUAJIT_SHA256 4d44e4709130b031c1c2c81cf5c102dfce877bf454409dabba03249e18870e66) set(LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz) set(LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333) @@ -165,9 +165,9 @@ set(LIBTERMKEY_SHA256 6945bd3c4aaa83da83d80a045c5563da4edd7d0374c62c0d35aec09eb3 set(LIBVTERM_URL https://www.leonerd.org.uk/code/libvterm/libvterm-0.1.4.tar.gz) set(LIBVTERM_SHA256 bc70349e95559c667672fc8c55b9527d9db9ada0fb80a3beda533418d782d3dd) -set(LUV_VERSION 1.43.0-0) -set(LUV_URL https://github.com/luvit/luv/archive/9f80386338af7d164ff1f47d480ee1ae775cb0ef.tar.gz) -set(LUV_SHA256 a6fe420f06944c0d84a173fccff2eb0d14dfd1293bc24666a580b98dd1a7254f) +set(LUV_VERSION 1.44.2-0) +set(LUV_URL https://github.com/luvit/luv/archive/1.44.2-0.tar.gz) +set(LUV_SHA256 44ccda27035bfe683e6325a2a93f2c254be1eb76bde6efc2bd37c56a7af7b00a) set(LUA_COMPAT53_URL https://github.com/keplerproject/lua-compat-5.3/archive/v0.9.tar.gz) set(LUA_COMPAT53_SHA256 ad05540d2d96a48725bb79a1def35cf6652a4e2ec26376e2617c8ce2baa6f416) @@ -193,8 +193,8 @@ set(LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc891 set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/v0.20.1.tar.gz) set(TREESITTER_C_SHA256 ffcc2ef0eded59ad1acec9aec4f9b0c7dd209fc1a85d85f8b0e81298e3dddcc2) -set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.20.6.tar.gz) -set(TREESITTER_SHA256 4d37eaef8a402a385998ff9aca3e1043b4a3bba899bceeff27a7178e1165b9de) +set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/1f1b1eb4501ed0a2d195d37f7de15f72aa10acd0.tar.gz) +set(TREESITTER_SHA256 a324bdb7ff507cd5157a36b908224d60ba638f8e3363070dd51aa383c7cbd790) if(USE_BUNDLED_UNIBILIUM) include(BuildUnibilium) diff --git a/cmake/GenerateVersion.cmake b/cmake/GenerateVersion.cmake new file mode 100644 index 0000000000..b9313f2498 --- /dev/null +++ b/cmake/GenerateVersion.cmake @@ -0,0 +1,48 @@ +# Handle generating version from Git. +set(use_git_version 0) +if(NVIM_VERSION_MEDIUM) + message(STATUS "USING NVIM_VERSION_MEDIUM: ${NVIM_VERSION_MEDIUM}") + return() +endif() + +find_program(GIT_EXECUTABLE git) +if(NOT GIT_EXECUTABLE) + message(AUTHOR_WARNING "Skipping version-string generation (cannot find git)") + file(WRITE "${OUTPUT}" "") + return() +endif() + +execute_process( + COMMAND git describe --first-parent --tags --always --dirty + OUTPUT_VARIABLE GIT_TAG + ERROR_VARIABLE ERR + RESULT_VARIABLE RES +) + +if("${RES}" EQUAL 1) + if(EXISTS ${OUTPUT}) + message(STATUS "Unable to extract version-string from git: keeping the last known version") + else() + # this will only be executed once since the file will get generated afterwards + message(AUTHOR_WARNING "Git tag extraction failed with: " "${ERR}") + file(WRITE "${OUTPUT}" "") + endif() + return() +endif() + +string(STRIP "${GIT_TAG}" GIT_TAG) +string(REGEX REPLACE "^v[0-9]+.[0-9]+.[0-9]+-" "" NVIM_VERSION_GIT "${GIT_TAG}") +set(NVIM_VERSION_MEDIUM + "v${NVIM_VERSION_MAJOR}.${NVIM_VERSION_MINOR}.${NVIM_VERSION_PATCH}-dev-${NVIM_VERSION_GIT}" +) +set(NVIM_VERSION_STRING "#define NVIM_VERSION_MEDIUM \"${NVIM_VERSION_MEDIUM}\"\n") +string(SHA1 CURRENT_VERSION_HASH "${NVIM_VERSION_STRING}") + +if(EXISTS ${OUTPUT}) + file(SHA1 "${OUTPUT}" NVIM_VERSION_HASH) +endif() + +if(NOT "${NVIM_VERSION_HASH}" STREQUAL "${CURRENT_VERSION_HASH}") + message(STATUS "Updating NVIM_VERSION_MEDIUM: ${NVIM_VERSION_MEDIUM}") + file(WRITE "${OUTPUT}" "${NVIM_VERSION_STRING}") +endif() diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt index 571fbaec95..b905f53db7 100644 --- a/runtime/doc/change.txt +++ b/runtime/doc/change.txt @@ -633,6 +633,9 @@ Directory for temporary files is created in the first possible directory of: actually work differently. You can use `:&&` to keep the flags. + *&-default* + Mapped to ":&&<CR>" by default. |default-mappings| + *g&* g& Synonym for `:%s//~/&` (repeat last substitute with last search pattern on all lines with the same flags). diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 8e853aaf9e..bacf160206 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -89,6 +89,7 @@ of these in your config by simply removing the mapping, e.g. ":unmap Y". inoremap <C-W> <C-G>u<C-W> xnoremap * y/\V<C-R>"<CR> xnoremap # y?\V<C-R>"<CR> + nnoremap & :&&<CR> < Default Autocommands ~ *default-autocmds* @@ -350,7 +351,8 @@ Commands: Functions: |input()| and |inputdialog()| support for each other’s features (return on cancel and completion respectively) via dictionary argument (replaces all - other arguments if used). + other arguments if used), and "cancelreturn" can have any type if passed in + a dictionary. |input()| and |inputdialog()| support user-defined cmdline highlighting. Highlight groups: diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 554b5f0bfa..0e72aae188 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -739,13 +739,18 @@ function lsp.start(config, opts) end config.name = config.name or (config.cmd[1] and vim.fs.basename(config.cmd[1])) or nil local bufnr = api.nvim_get_current_buf() - for _, client in pairs(lsp.get_active_clients()) do - if reuse_client(client, config) then - lsp.buf_attach_client(bufnr, client.id) - return client.id + for _, clients in ipairs({ uninitialized_clients, lsp.get_active_clients() }) do + for _, client in pairs(clients) do + if reuse_client(client, config) then + lsp.buf_attach_client(bufnr, client.id) + return client.id + end end end local client_id = lsp.start_client(config) + if client_id == nil then + return nil -- lsp.start_client will have printed an error + end lsp.buf_attach_client(bufnr, client_id) return client_id end diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 0cc2b6d2a4..103e85abfd 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -221,6 +221,9 @@ end local predicate_handlers = { ['eq?'] = function(match, _, source, predicate) local node = match[predicate[2]] + if not node then + return true + end local node_text = M.get_node_text(node, source) local str @@ -241,6 +244,9 @@ local predicate_handlers = { ['lua-match?'] = function(match, _, source, predicate) local node = match[predicate[2]] + if not node then + return true + end local regex = predicate[3] return string.find(M.get_node_text(node, source), regex) end, @@ -265,6 +271,9 @@ local predicate_handlers = { return function(match, _, source, pred) local node = match[pred[2]] + if not node then + return true + end local regex = compiled_vim_regexes[pred[3]] return regex:match_str(M.get_node_text(node, source)) end @@ -272,6 +281,9 @@ local predicate_handlers = { ['contains?'] = function(match, _, source, predicate) local node = match[predicate[2]] + if not node then + return true + end local node_text = M.get_node_text(node, source) for i = 3, #predicate do @@ -285,6 +297,9 @@ local predicate_handlers = { ['any-of?'] = function(match, _, source, predicate) local node = match[predicate[2]] + if not node then + return true + end local node_text = M.get_node_text(node, source) -- Since 'predicate' will not be used by callers of this function, use it diff --git a/runtime/syntax/lsp_markdown.vim b/runtime/syntax/lsp_markdown.vim index 90d3185673..4be7595807 100644 --- a/runtime/syntax/lsp_markdown.vim +++ b/runtime/syntax/lsp_markdown.vim @@ -1,23 +1,34 @@ " Vim syntax file -" Language: lsp_markdown -" Maintainer: Michael Lingelbach <m.j.lbach@gmail.com -" URL: http://neovim.io -" Remark: Uses markdown syntax file +" Language: Markdown-like LSP docstrings +" Maintainer: https://github.com/neovim/neovim +" URL: http://neovim.io +" Remark: Uses markdown syntax file -" always source the system included markdown instead of any other installed -" markdown.vim syntax files +" Source the default Nvim markdown syntax, not other random ones. execute 'source' expand('<sfile>:p:h') .. '/markdown.vim' syn cluster mkdNonListItem add=mkdEscape,mkdNbsp +" Don't highlight invalid markdown syntax in LSP docstrings. +syn clear markdownError + syn clear markdownEscape syntax region markdownEscape matchgroup=markdownEscape start=/\\\ze[\\\x60*{}\[\]()#+\-,.!_>~|"$%&'\/:;<=?@^ ]/ end=/./ containedin=ALL keepend oneline concealends -" conceal html entities +" Conceal backticks (which delimit code fragments). +" We ignore g:markdown_syntax_conceal here. +syn region markdownCode matchgroup=markdownCodeDelimiter start="`" end="`" keepend contains=markdownLineStart concealends +syn region markdownCode matchgroup=markdownCodeDelimiter start="`` \=" end=" \=``" keepend contains=markdownLineStart concealends +syn region markdownCode matchgroup=markdownCodeDelimiter start="^\s*````*.*$" end="^\s*````*\ze\s*$" keepend concealends + +" Highlight code fragments. +hi def link markdownCode Special + +" Conceal HTML entities. syntax match mkdNbsp / / conceal cchar= syntax match mkdLt /</ conceal cchar=< syntax match mkdGt />/ conceal cchar=> syntax match mkdAmp /&/ conceal cchar=& syntax match mkdQuot /"/ conceal cchar=" -hi def link mkdEscape special +hi def link mkdEscape Special diff --git a/scripts/update_version_stamp.lua b/scripts/update_version_stamp.lua deleted file mode 100755 index 0342e08f31..0000000000 --- a/scripts/update_version_stamp.lua +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env lua --- --- Script to update the Git version stamp during build. --- This is called via the custom update_version_stamp target in --- src/nvim/CMakeLists.txt. --- --- arg[1]: file in which to update the version string --- arg[2]: prefix to use always ("vX.Y.Z") - -local function die(msg) - io.stderr:write(string.format('%s: %s\n', arg[0], msg)) - -- No error, fall back to using generated "-dev" version. - os.exit(0) -end - -local function iswin() - return package.config:sub(1,1) == '\\' -end - -if #arg ~= 2 then - die(string.format("Expected two args, got %d", #arg)) -end - -local versiondeffile = arg[1] -local prefix = arg[2] - -local dev_null = iswin() and 'NUL' or '/dev/null' -local described = io.popen('git describe --first-parent --dirty 2>'..dev_null):read('*l') -if not described then - described = io.popen('git describe --first-parent --tags --always --dirty'):read('*l') -end -if not described then - io.open(versiondeffile, 'w'):write('\n') - die('git-describe failed, using empty include file.') -end - --- `git describe` annotates the most recent tagged release; for pre-release --- builds we append that to the dev version -local with_prefix = prefix -if prefix:match('-dev$') ~= nil then - with_prefix = prefix .. '+' .. described:gsub('^v%d+%.%d+%.%d+-', '') -end - --- Read existing include file. -local current = io.open(versiondeffile, 'r') -if current then - current = current:read('*l') -end - --- Write new include file, if different. -local new = '#define NVIM_VERSION_MEDIUM "'..with_prefix..'"' -if current ~= new then - io.open(versiondeffile, 'w'):write(new .. '\n') -end diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index de4af8e77b..360993de68 100755 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -222,31 +222,17 @@ function(get_preproc_output varname iname) endif() endfunction() -# Handle generating version from Git. -set(use_git_version 0) -if(NVIM_VERSION_MEDIUM) - message(STATUS "NVIM_VERSION_MEDIUM: ${NVIM_VERSION_MEDIUM}") -elseif(EXISTS ${PROJECT_SOURCE_DIR}/.git) - find_program(GIT_EXECUTABLE git) - if(GIT_EXECUTABLE) - message(STATUS "Using NVIM_VERSION_MEDIUM from Git") - set(use_git_version 1) - else() - message(STATUS "Skipping version-string generation (cannot find git)") - endif() -endif() -if(use_git_version) - # Create a update_version_stamp target to update the version during build. - file(RELATIVE_PATH relbuild "${PROJECT_SOURCE_DIR}" "${CMAKE_BINARY_DIR}") - add_custom_target(update_version_stamp ALL - COMMAND ${LUA_PRG} scripts/update_version_stamp.lua - ${relbuild}/cmake.config/auto/versiondef_git.h - "v${NVIM_VERSION_MAJOR}.${NVIM_VERSION_MINOR}.${NVIM_VERSION_PATCH}${NVIM_VERSION_PRERELEASE}" - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - BYPRODUCTS ${CMAKE_BINARY_DIR}/cmake.config/auto/versiondef_git.h) -else() - file(WRITE ${CMAKE_BINARY_DIR}/cmake.config/auto/versiondef_git.h "") -endif() +set(NVIM_VERSION_GIT_H ${PROJECT_BINARY_DIR}/cmake.config/auto/versiondef_git.h) +add_custom_target(update_version_stamp + COMMAND ${CMAKE_COMMAND} + -DNVIM_VERSION_MAJOR=${NVIM_VERSION_MAJOR} + -DNVIM_VERSION_MINOR=${NVIM_VERSION_MINOR} + -DNVIM_VERSION_PATCH=${NVIM_VERSION_PATCH} + -DNVIM_VERSION_PRERELEASE=${NVIM_VERSION_PRERELEASE} + -DOUTPUT=${NVIM_VERSION_GIT_H} + -P ${PROJECT_SOURCE_DIR}/cmake/GenerateVersion.cmake + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + BYPRODUCTS ${NVIM_VERSION_GIT_H}) # NVIM_GENERATED_FOR_HEADERS: generated headers to be included in headers # NVIM_GENERATED_FOR_SOURCES: generated headers to be included in sources @@ -280,9 +266,9 @@ foreach(sfile ${NVIM_SOURCES} get_preproc_output(PREPROC_OUTPUT ${gf_i}) set(depends "${HEADER_GENERATOR}" "${sfile}") - if(use_git_version AND "${f}" STREQUAL "version.c") + if("${f}" STREQUAL "version.c") # Ensure auto/versiondef_git.h exists after "make clean". - list(APPEND depends update_version_stamp) + list(APPEND depends "${NVIM_VERSION_GIT_H}") endif() add_custom_command( OUTPUT "${gf_c_h}" "${gf_h_h}" diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 7e1eae9632..f937450107 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -94,11 +94,19 @@ static char *e_buflocked = N_("E937: Attempt to delete a buffer that is in use") // Number of times free_buffer() was called. static int buf_free_count = 0; +static int top_file_num = 1; ///< highest file number + typedef enum { kBffClearWinInfo = 1, kBffInitChangedtick = 2, } BufFreeFlags; +/// @return the highest possible buffer number +int get_highest_fnum(void) +{ + return top_file_num - 1; +} + /// Read data from buffer for retrying. /// /// @param read_stdin read file from stdin, otherwise fifo @@ -443,6 +451,7 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i return false; } + // check no autocommands closed the window if (win != NULL // Avoid bogus clang warning. && win_valid_any_tab(win)) { // Set b_last_cursor when closing the last window for the buffer. @@ -1643,8 +1652,6 @@ void no_write_message_nobang(const buf_T *const buf) // functions for dealing with the buffer list // -static int top_file_num = 1; ///< highest file number - /// Initialize b:changedtick and changedtick_val attribute /// /// @param[out] buf Buffer to initialize for. @@ -3136,18 +3143,16 @@ void maketitle(void) if (*p_titlestring != NUL) { if (stl_syntax & STL_IN_TITLE) { int use_sandbox = false; - int save_called_emsg = called_emsg; + const int called_emsg_before = called_emsg; use_sandbox = was_set_insecurely(curwin, "titlestring", 0); - called_emsg = false; build_stl_str_hl(curwin, buf, sizeof(buf), (char *)p_titlestring, use_sandbox, 0, maxlen, NULL, NULL); title_str = buf; - if (called_emsg) { + if (called_emsg > called_emsg_before) { set_string_option_direct("titlestring", -1, "", OPT_FREE, SID_ERROR); } - called_emsg |= save_called_emsg; } else { title_str = (char *)p_titlestring; } @@ -3252,17 +3257,15 @@ void maketitle(void) if (*p_iconstring != NUL) { if (stl_syntax & STL_IN_ICON) { int use_sandbox = false; - int save_called_emsg = called_emsg; + const int called_emsg_before = called_emsg; use_sandbox = was_set_insecurely(curwin, "iconstring", 0); - called_emsg = false; build_stl_str_hl(curwin, icon_str, sizeof(buf), (char *)p_iconstring, use_sandbox, 0, 0, NULL, NULL); - if (called_emsg) { + if (called_emsg > called_emsg_before) { set_string_option_direct("iconstring", -1, "", OPT_FREE, SID_ERROR); } - called_emsg |= save_called_emsg; } else { icon_str = (char *)p_iconstring; } @@ -5268,9 +5271,9 @@ bool bt_terminal(const buf_T *const buf) return buf != NULL && buf->b_p_bt[0] == 't'; } -/// @return true if "buf" is a "nofile", "acwrite", "terminal" or "prompt" / +/// @return true if "buf" is a "nofile", "acwrite", "terminal" or "prompt" /// buffer. This means the buffer name is not a file name. -bool bt_nofile(const buf_T *const buf) +bool bt_nofilename(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f') @@ -5279,6 +5282,13 @@ bool bt_nofile(const buf_T *const buf) || buf->b_p_bt[0] == 'p'); } +/// @return true if "buf" has 'buftype' set to "nofile". +bool bt_nofile(const buf_T *const buf) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return buf != NULL && buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f'; +} + /// @return true if "buf" is a "nowrite", "nofile", "terminal" or "prompt" /// buffer. bool bt_dontwrite(const buf_T *const buf) @@ -5330,7 +5340,7 @@ char *buf_spname(buf_T *buf) } // There is no _file_ when 'buftype' is "nofile", b_sfname // contains the name as specified by the user. - if (bt_nofile(buf)) { + if (bt_nofilename(buf)) { if (buf->b_fname != NULL) { return buf->b_fname; } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index c7173c2078..096fcba981 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6554,7 +6554,8 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const const char *prompt = ""; const char *defstr = ""; - const char *cancelreturn = NULL; + typval_T *cancelreturn = NULL; + typval_T cancelreturn_strarg2 = TV_INITIAL_VALUE; const char *xp_name = NULL; Callback input_callback = { .type = kCallbackNone }; char prompt_buf[NUMBUFLEN]; @@ -6576,13 +6577,9 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const if (defstr == NULL) { return; } - cancelreturn = tv_dict_get_string_buf_chk(dict, S_LEN("cancelreturn"), - cancelreturn_buf, def); - if (cancelreturn == NULL) { // error - return; - } - if (*cancelreturn == NUL) { - cancelreturn = NULL; + dictitem_T *cancelreturn_di = tv_dict_find(dict, S_LEN("cancelreturn")); + if (cancelreturn_di != NULL) { + cancelreturn = &cancelreturn_di->di_tv; } xp_name = tv_dict_get_string_buf_chk(dict, S_LEN("completion"), xp_name_buf, def); @@ -6606,15 +6603,16 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const return; } if (argvars[2].v_type != VAR_UNKNOWN) { - const char *const arg2 = tv_get_string_buf_chk(&argvars[2], - cancelreturn_buf); - if (arg2 == NULL) { + const char *const strarg2 = tv_get_string_buf_chk(&argvars[2], cancelreturn_buf); + if (strarg2 == NULL) { return; } if (inputdialog) { - cancelreturn = arg2; + cancelreturn_strarg2.v_type = VAR_STRING; + cancelreturn_strarg2.vval.v_string = (char *)strarg2; + cancelreturn = &cancelreturn_strarg2; } else { - xp_name = arg2; + xp_name = strarg2; } } } @@ -6662,7 +6660,7 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const callback_free(&input_callback); if (rettv->vval.v_string == NULL && cancelreturn != NULL) { - rettv->vval.v_string = xstrdup(cancelreturn); + tv_copy(cancelreturn, rettv); } xfree(xp_arg); @@ -7296,7 +7294,7 @@ void timer_due_cb(TimeWatcher *tw, void *data) { timer_T *timer = (timer_T *)data; int save_did_emsg = did_emsg; - int save_called_emsg = called_emsg; + const int called_emsg_before = called_emsg; const bool save_ex_pressedreturn = get_pressedreturn(); if (timer->stopped || timer->paused) { @@ -7313,19 +7311,17 @@ void timer_due_cb(TimeWatcher *tw, void *data) argv[0].v_type = VAR_NUMBER; argv[0].vval.v_number = timer->timer_id; typval_T rettv = TV_INITIAL_VALUE; - called_emsg = false; callback_call(&timer->callback, 1, argv, &rettv); // Handle error message - if (called_emsg && did_emsg) { + if (called_emsg > called_emsg_before && did_emsg) { timer->emsg_count++; if (current_exception != NULL) { discard_current_exception(); } } did_emsg = save_did_emsg; - called_emsg = save_called_emsg; set_pressedreturn(save_ex_pressedreturn); if (timer->emsg_count >= 3) { diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 6466e06010..f231146d7c 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -482,7 +482,7 @@ static buf_T *find_buffer(typval_T *avar) * buffer, these don't use the full path. */ FOR_ALL_BUFFERS(bp) { if (bp->b_fname != NULL - && (path_with_url(bp->b_fname) || bt_nofile(bp)) + && (path_with_url(bp->b_fname) || bt_nofilename(bp)) && STRCMP(bp->b_fname, avar->vval.v_string) == 0) { buf = bp; break; @@ -3902,15 +3902,14 @@ static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr) typval_T argv = TV_INITIAL_VALUE; typval_T exprval = TV_INITIAL_VALUE; bool error = false; - int save_called_emsg = called_emsg; - called_emsg = false; + const int called_emsg_before = called_emsg; LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, timeout, eval_expr_typval(&expr, &argv, 0, &exprval) != OK || tv_get_number_chk(&exprval, &error) - || called_emsg || error || got_int); + || called_emsg > called_emsg_before || error || got_int); - if (called_emsg || error) { + if (called_emsg > called_emsg_before || error) { rettv->vval.v_number = -3; } else if (got_int) { got_int = false; @@ -3920,8 +3919,6 @@ static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 0; } - called_emsg = save_called_emsg; - // Stop dummy timer time_watcher_stop(tw); time_watcher_close(tw, dummy_timer_close_cb); diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 1955a0b3d0..28e1893b31 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -1980,17 +1980,14 @@ theend: /// @return OK if it's OK, FAIL if it is not. int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int other) { - /* - * write to other file or b_flags set or not writing the whole file: - * overwriting only allowed with '!' - */ + // Write to another file or b_flags set or not writing the whole file: + // overwriting only allowed with '!' if ((other || (buf->b_flags & BF_NOTEDITED) || ((buf->b_flags & BF_NEW) && vim_strchr(p_cpo, CPO_OVERNEW) == NULL) || (buf->b_flags & BF_READERR)) && !p_wa - && !bt_nofile(buf) && os_path_exists((char_u *)ffname)) { if (!eap->forceit && !eap->append) { #ifdef UNIX @@ -3874,6 +3871,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T curwin->w_cursor.col = 0; } getvcol(curwin, &curwin->w_cursor, NULL, NULL, &ec); + curwin->w_cursor.col = regmatch.startpos[0].col; if (subflags.do_number || curwin->w_p_nu) { int numw = number_width(curwin) + 1; sc += numw; @@ -3883,7 +3881,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T prompt = xmallocz((size_t)ec + 1); memset(prompt, ' ', (size_t)sc); memset(prompt + sc, '^', (size_t)(ec - sc) + 1); - resp = getcmdline_prompt((char)(-1), prompt, 0, EXPAND_NOTHING, NULL, CALLBACK_NONE); + resp = getcmdline_prompt(-1, prompt, 0, EXPAND_NOTHING, NULL, CALLBACK_NONE); msg_putchar('\n'); xfree(prompt); if (resp != NULL) { @@ -4861,8 +4859,7 @@ void ex_help(exarg_T *eap) * Re-use an existing help window or open a new one. * Always open a new one for ":tab help". */ - if (!bt_help(curwin->w_buffer) - || cmdmod.cmod_tab != 0) { + if (!bt_help(curwin->w_buffer) || cmdmod.cmod_tab != 0) { if (cmdmod.cmod_tab != 0) { wp = NULL; } else { diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 897928abec..e57dc5d13f 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -1018,8 +1018,7 @@ void check_arg_idx(win_T *win) // We are editing the current entry in the argument list. // Set "arg_had_last" if it's also the last one win->w_arg_idx_invalid = false; - if (win->w_arg_idx == WARGCOUNT(win) - 1 - && win->w_alist == &global_alist) { + if (win->w_arg_idx == WARGCOUNT(win) - 1 && win->w_alist == &global_alist) { arg_had_last = true; } } @@ -1150,8 +1149,7 @@ void do_argfile(exarg_T *eap, int argn) } curwin->w_arg_idx = argn; - if (argn == ARGCOUNT - 1 - && curwin->w_alist == &global_alist) { + if (argn == ARGCOUNT - 1 && curwin->w_alist == &global_alist) { arg_had_last = true; } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 2899e17039..4e6846bf21 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -227,6 +227,8 @@ void do_exmode(void) emsg(_(e_emptybuf)); } else { if (ex_pressedreturn) { + // Make sure the message overwrites the right line and isn't throttled. + msg_scroll_flush(); // go up one line, to overwrite the ":<CR>" line, so the // output doesn't contain empty lines. msg_row = prev_msg_row; @@ -908,6 +910,15 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) msg_list = saved_msg_list; + // Cleanup if "cs_emsg_silent_list" remains. + if (cstack.cs_emsg_silent_list != NULL) { + eslist_T *elem, *temp; + for (elem = cstack.cs_emsg_silent_list; elem != NULL; elem = temp) { + temp = elem->next; + xfree(elem); + } + } + /* * If there was too much output to fit on the command line, ask the user to * hit return before redrawing the screen. With the ":global" command we do @@ -4605,8 +4616,9 @@ char *invalid_range(exarg_T *eap) } break; case ADDR_BUFFERS: - if (eap->line1 < firstbuf->b_fnum - || eap->line2 > lastbuf->b_fnum) { + // Only a boundary check, not whether the buffers actually + // exist. + if (eap->line1 < 1 || eap->line2 > get_highest_fnum()) { return _(e_invrange); } break; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index a5783f4ced..92c9d83045 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2646,7 +2646,7 @@ char_u *getcmdline(int firstc, long count, int indent, bool do_concat FUNC_ATTR_ /// @param[in] highlight_callback Callback used for highlighting user input. /// /// @return [allocated] Command line or NULL. -char *getcmdline_prompt(const char firstc, const char *const prompt, const int attr, +char *getcmdline_prompt(const int firstc, const char *const prompt, const int attr, const int xp_context, const char *const xp_arg, const Callback highlight_callback) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index a7f4080b22..6ca6da9cd0 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -187,7 +187,7 @@ static int ses_do_win(win_T *wp) } if (wp->w_buffer->b_fname == NULL // When 'buftype' is "nofile" can't restore the window contents. - || (!wp->w_buffer->terminal && bt_nofile(wp->w_buffer))) { + || (!wp->w_buffer->terminal && bt_nofilename(wp->w_buffer))) { return ssop_flags & SSOP_BLANK; } if (bt_help(wp->w_buffer)) { @@ -363,7 +363,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr return FAIL; } } else if (wp->w_buffer->b_ffname != NULL - && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal)) { + && (!bt_nofilename(wp->w_buffer) || wp->w_buffer->terminal)) { // Load the file. // Editing a file in this buffer: use ":edit file". @@ -706,7 +706,7 @@ static int makeopens(FILE *fd, char_u *dirnow) if (ses_do_win(wp) && wp->w_buffer->b_ffname != NULL && !bt_help(wp->w_buffer) - && !bt_nofile(wp->w_buffer)) { + && !bt_nofilename(wp->w_buffer)) { if (need_tabnext && put_line(fd, "tabnext") == FAIL) { return FAIL; } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index febde53ce2..6782465ef1 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -2286,7 +2286,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en && reset_changed && whole && buf == curbuf - && !bt_nofile(buf) + && !bt_nofilename(buf) && !filtering && (!append || vim_strchr(p_cpo, CPO_FNAMEAPP) != NULL) && vim_strchr(p_cpo, CPO_FNAMEW) != NULL) { @@ -2360,22 +2360,22 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if (append) { if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEAPPENDCMD, - sfname, sfname, FALSE, curbuf, eap))) { - if (overwriting && bt_nofile(curbuf)) { - nofile_err = TRUE; + sfname, sfname, false, curbuf, eap))) { + if (overwriting && bt_nofilename(curbuf)) { + nofile_err = true; } else { apply_autocmds_exarg(EVENT_FILEAPPENDPRE, - sfname, sfname, FALSE, curbuf, eap); + sfname, sfname, false, curbuf, eap); } } } else if (filtering) { apply_autocmds_exarg(EVENT_FILTERWRITEPRE, - NULL, sfname, FALSE, curbuf, eap); + NULL, sfname, false, curbuf, eap); } else if (reset_changed && whole) { int was_changed = curbufIsChanged(); did_cmd = apply_autocmds_exarg(EVENT_BUFWRITECMD, - sfname, sfname, FALSE, curbuf, eap); + sfname, sfname, false, curbuf, eap); if (did_cmd) { if (was_changed && !curbufIsChanged()) { /* Written everything correctly and BufWriteCmd has reset @@ -2385,21 +2385,21 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en u_update_save_nr(curbuf); } } else { - if (overwriting && bt_nofile(curbuf)) { - nofile_err = TRUE; + if (overwriting && bt_nofilename(curbuf)) { + nofile_err = true; } else { apply_autocmds_exarg(EVENT_BUFWRITEPRE, - sfname, sfname, FALSE, curbuf, eap); + sfname, sfname, false, curbuf, eap); } } } else { if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEWRITECMD, - sfname, sfname, FALSE, curbuf, eap))) { - if (overwriting && bt_nofile(curbuf)) { - nofile_err = TRUE; + sfname, sfname, false, curbuf, eap))) { + if (overwriting && bt_nofilename(curbuf)) { + nofile_err = true; } else { apply_autocmds_exarg(EVENT_FILEWRITEPRE, - sfname, sfname, FALSE, curbuf, eap); + sfname, sfname, false, curbuf, eap); } } } @@ -4306,7 +4306,7 @@ void shorten_buf_fname(buf_T *buf, char_u *dirname, int force) char *p; if (buf->b_fname != NULL - && !bt_nofile(buf) + && !bt_nofilename(buf) && !path_with_url(buf->b_fname) && (force || buf->b_sfname == NULL diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 01d4ab086e..8d896aef31 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -229,7 +229,7 @@ EXTERN bool did_emsg; // set by emsg() when the message EXTERN bool called_vim_beep; // set if vim_beep() is called EXTERN bool did_emsg_syntax; // did_emsg set because of a // syntax error -EXTERN int called_emsg; // always set by emsg() +EXTERN int called_emsg; // always incremented by emsg() EXTERN int ex_exitval INIT(= 0); // exit value for ex mode EXTERN bool emsg_on_display INIT(= false); // there is an error message EXTERN bool rc_did_emsg INIT(= false); // vim_regcomp() called emsg() diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index 9a4db520a6..7f4df66b4d 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -2125,6 +2125,10 @@ void init_default_mappings(void) add_map("<C-W>", "<C-G>u<C-W>", MODE_INSERT, false); add_map("*", "y/\\\\V<C-R>\"<CR>", MODE_VISUAL, false); add_map("#", "y?\\\\V<C-R>\"<CR>", MODE_VISUAL, false); + + // Use : instead of <Cmd> so that ranges are supported (e.g. 3& repeats the substitution on the + // next 3 lines) + add_map("&", ":&&<CR>", MODE_NORMAL, false); } /// Add a mapping. Unlike @ref do_map this copies the string arguments, so diff --git a/src/nvim/match.c b/src/nvim/match.c index d5e3d8cddc..e17a95569c 100644 --- a/src/nvim/match.c +++ b/src/nvim/match.c @@ -398,7 +398,7 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_ linenr_T l; colnr_T matchcol; long nmatched = 0; - int save_called_emsg = called_emsg; + const int called_emsg_before = called_emsg; // for :{range}s/pat only highlight inside the range if (lnum < search_first_line || lnum > search_last_line) { @@ -421,7 +421,6 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_ // Repeat searching for a match until one is found that includes "mincol" // or none is found in this line. - called_emsg = false; for (;;) { // Stop searching after passing the time limit. if (profile_passed_limit(shl->tm)) { @@ -468,7 +467,7 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_ if (regprog_is_copy) { cur->match.regprog = cur->hl.rm.regprog; } - if (called_emsg || got_int || timed_out) { + if (called_emsg > called_emsg_before || got_int || timed_out) { // Error while handling regexp: stop using this regexp. if (shl == search_hl) { // don't free regprog in the match list, it's a copy @@ -495,9 +494,6 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_ break; // useful match found } } - - // Restore called_emsg for assert_fails(). - called_emsg = save_called_emsg; } /// Advance to the match in window "wp" line "lnum" or past it. diff --git a/src/nvim/message.c b/src/nvim/message.c index 6ab0a5d3e6..2c96613bb3 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -637,7 +637,7 @@ static bool emsg_multiline(const char *s, bool multiline) return true; } - called_emsg = true; + called_emsg++; // If "emsg_severe" is true: When an error exception is to be thrown, // prefer this message over previous messages for the same command. diff --git a/src/nvim/move.c b/src/nvim/move.c index 99ca5060cd..bd68ad6f97 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -786,8 +786,7 @@ void curs_columns(win_T *wp, int may_scroll) } else { wp->w_wrow = wp->w_height_inner - 1 - wp->w_empty_rows; } - } else if (wp->w_p_wrap - && wp->w_width_inner != 0) { + } else if (wp->w_p_wrap && wp->w_width_inner != 0) { width = textwidth + win_col_off2(wp); // long line wrapping, adjust wp->w_wrow @@ -1083,8 +1082,7 @@ bool scrolldown(long line_count, int byfold) * and move the cursor onto the displayed part of the window. */ int wrow = curwin->w_wrow; - if (curwin->w_p_wrap - && curwin->w_width_inner != 0) { + if (curwin->w_p_wrap && curwin->w_width_inner != 0) { validate_virtcol(); validate_cheight(); wrow += curwin->w_cline_height - 1 - diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 0eb8a8f59b..b675abfb7d 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -6012,8 +6012,7 @@ static void nv_g_home_m_cmd(cmdarg_T *cap) cap->oap->motion_type = kMTCharWise; cap->oap->inclusive = false; - if (curwin->w_p_wrap - && curwin->w_width_inner != 0) { + if (curwin->w_p_wrap && curwin->w_width_inner != 0) { int width1 = curwin->w_width_inner - curwin_col_off(); int width2 = width1 + curwin_col_off2(); diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index ba151b5ce2..90c0b6f5c6 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -726,8 +726,7 @@ static int pum_set_selected(int n, int repeat) if (!resized && (curbuf->b_nwindows == 1) && (curbuf->b_fname == NULL) - && (curbuf->b_p_bt[0] == 'n') - && (curbuf->b_p_bt[2] == 'f') + && bt_nofile(curbuf) && (curbuf->b_p_bh[0] == 'w')) { // Already a "wipeout" buffer, make it empty. while (!buf_is_empty(curbuf)) { diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index bb6cbc35bc..4c49d30819 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -2327,7 +2327,6 @@ regprog_T *vim_regcomp(char *expr_arg, int re_flags) { regprog_T *prog = NULL; char_u *expr = (char_u *)expr_arg; - int save_called_emsg; regexp_engine = p_re; @@ -2360,8 +2359,7 @@ regprog_T *vim_regcomp(char *expr_arg, int re_flags) // // First try the NFA engine, unless backtracking was requested. // - save_called_emsg = called_emsg; - called_emsg = false; + const int called_emsg_before = called_emsg; if (regexp_engine != BACKTRACKING_ENGINE) { prog = nfa_regengine.regcomp(expr, re_flags + (regexp_engine == AUTOMATIC_ENGINE ? RE_AUTO : 0)); @@ -2388,13 +2386,12 @@ regprog_T *vim_regcomp(char *expr_arg, int re_flags) // also fails for patterns that it can't handle well but are still valid // patterns, thus a retry should work. // But don't try if an error message was given. - if (regexp_engine == AUTOMATIC_ENGINE && !called_emsg) { + if (regexp_engine == AUTOMATIC_ENGINE && called_emsg == called_emsg_before) { regexp_engine = BACKTRACKING_ENGINE; report_re_switch(expr); prog = bt_regengine.regcomp(expr, re_flags); } } - called_emsg |= save_called_emsg; if (prog != NULL) { // Store the info needed to call regcomp() again when the engine turns out diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 3d69d317fd..de837720c1 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -5316,7 +5316,7 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) } fillchar = wp->w_p_fcs_chars.wbr; - attr = (wp == curwin) ? HL_ATTR(HLF_WBR) : HL_ATTR(HLF_WBRNC); + attr = (wp == curwin) ? win_hl_attr(wp, HLF_WBR) : win_hl_attr(wp, HLF_WBRNC); maxwidth = wp->w_width_inner; use_sandbox = was_set_insecurely(wp, "winbar", 0); @@ -6533,13 +6533,11 @@ static void win_redr_ruler(win_T *wp, bool always) } if (*p_ruf && p_ch > 0 && !ui_has(kUIMessages)) { - int save_called_emsg = called_emsg; - called_emsg = false; + const int called_emsg_before = called_emsg; win_redr_custom(wp, false, true); - if (called_emsg) { + if (called_emsg > called_emsg_before) { set_string_option_direct("rulerformat", -1, "", OPT_FREE, SID_ERROR); } - called_emsg |= save_called_emsg; return; } diff --git a/src/nvim/search.c b/src/nvim/search.c index a915594e26..f3061b4dc4 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -556,7 +556,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, long nmatched; int submatch = 0; bool first_match = true; - int save_called_emsg = called_emsg; + const int called_emsg_before = called_emsg; bool break_loop = false; linenr_T stop_lnum = 0; // stop after this line number when != 0 proftime_T *tm = NULL; // timeout limit or NULL @@ -579,7 +579,6 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, /* * find the string */ - called_emsg = FALSE; do { // loop for count // When not accepting a match at the start position set "extra_col" to a // non-zero value. Don't do that when starting at MAXCOL, since MAXCOL + 1 @@ -651,7 +650,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, break; } // Abort searching on an error (e.g., out of stack). - if (called_emsg || (timed_out != NULL && *timed_out)) { + if (called_emsg > called_emsg_before || (timed_out != NULL && *timed_out)) { break; } if (nmatched > 0) { @@ -908,7 +907,8 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, // Stop the search if wrapscan isn't set, "stop_lnum" is // specified, after an interrupt, after a match and after looping // twice. - if (!p_ws || stop_lnum != 0 || got_int || called_emsg + if (!p_ws || stop_lnum != 0 || got_int + || called_emsg > called_emsg_before || (timed_out != NULL && *timed_out) || break_loop || found || loop) { @@ -933,7 +933,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, extra_arg->sa_wrapped = true; } } - if (got_int || called_emsg + if (got_int || called_emsg > called_emsg_before || (timed_out != NULL && *timed_out) || break_loop) { break; @@ -942,8 +942,6 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, vim_regfree(regmatch.regprog); - called_emsg |= save_called_emsg; - if (!found) { // did not find it if (got_int) { emsg(_(e_interr)); @@ -4409,7 +4407,7 @@ static int is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direct int nmatched = 0; int result = -1; pos_T pos; - int save_called_emsg = called_emsg; + const int called_emsg_before = called_emsg; int flag = 0; if (pattern == NULL) { @@ -4435,7 +4433,6 @@ static int is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direct SEARCH_KEEP + flag, RE_SEARCH, NULL) != FAIL) { // Zero-width pattern should match somewhere, then we can check if // start and end are in the same position. - called_emsg = false; do { regmatch.startpos[0].col++; nmatched = vim_regexec_multi(®match, curwin, curbuf, @@ -4449,14 +4446,13 @@ static int is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direct ? regmatch.startpos[0].col < pos.col : regmatch.startpos[0].col > pos.col); - if (!called_emsg) { + if (called_emsg == called_emsg_before) { result = (nmatched != 0 && regmatch.startpos[0].lnum == regmatch.endpos[0].lnum && regmatch.startpos[0].col == regmatch.endpos[0].col); } } - called_emsg |= save_called_emsg; vim_regfree(regmatch.regprog); return result; } @@ -5303,6 +5299,16 @@ void f_matchfuzzypos(typval_T *argvars, typval_T *rettv, FunPtr fptr) do_fuzzymatch(argvars, rettv, true); } +/// Get line "lnum" and copy it into "buf[LSIZE]". +/// The copy is made because the regexp may make the line invalid when using a +/// mark. +static char_u *get_line_and_copy(linenr_T lnum, char_u *buf) +{ + char_u *line = ml_get(lnum); + STRLCPY(buf, line, LSIZE); + return buf; +} + /// Find identifiers or defines in included files. /// If p_ic && (compl_cont_status & CONT_SOL) then ptr must be in lowercase. /// @@ -5399,7 +5405,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo if (lnum > end_lnum) { // do at least one line lnum = end_lnum; } - line = ml_get(lnum); + line = get_line_and_copy(lnum, file_line); for (;;) { if (incl_regmatch.regprog != NULL @@ -5687,7 +5693,7 @@ search_line: if (lnum >= end_lnum) { goto exit_matched; } - line = ml_get(++lnum); + line = get_line_and_copy(++lnum, file_line); } else if (vim_fgets(line = file_line, LSIZE, files[depth].fp)) { goto exit_matched; @@ -5879,7 +5885,7 @@ exit_matched: if (++lnum > end_lnum) { break; } - line = ml_get(lnum); + line = get_line_and_copy(lnum, file_line); } already = NULL; } diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 04eb9dd2bc..9d2fd2637d 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -3905,12 +3905,12 @@ static wordnode_T *wordtree_alloc(spellinfo_T *spin) /// Return true if "word" contains valid word characters. /// Control characters and trailing '/' are invalid. Space is OK. -static bool valid_spell_word(const char_u *word) +static bool valid_spell_word(const char_u *word, const char_u *end) { - if (!utf_valid_string(word, NULL)) { + if (!utf_valid_string(word, end)) { return false; } - for (const char_u *p = word; *p != NUL; p += utfc_ptr2len((const char *)p)) { + for (const char_u *p = word; *p != NUL && p < end; p += utfc_ptr2len((const char *)p)) { if (*p < ' ' || (p[0] == '/' && p[1] == NUL)) { return false; } @@ -3939,7 +3939,7 @@ static int store_word(spellinfo_T *spin, char_u *word, int flags, int region, co int res = OK; // Avoid adding illegal bytes to the word tree. - if (!valid_spell_word(word)) { + if (!valid_spell_word(word, word + len)) { return FAIL; } @@ -5536,7 +5536,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo int i; char_u *spf; - if (!valid_spell_word(word)) { + if (!valid_spell_word(word, word + len)) { emsg(_(e_illegal_character_in_word)); return; } diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 5b62d11bc9..28b3b6c1ef 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -1863,6 +1863,7 @@ parse_line: // For "normal" tags: Do a quick check if the tag matches. // This speeds up tag searching a lot! if (orgpat.headlen) { + memset(&tagp, 0, sizeof(tagp)); tagp.tagname = lbuf; tagp.tagname_end = (char_u *)vim_strchr((char *)lbuf, TAB); if (tagp.tagname_end == NULL) { @@ -2784,7 +2785,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) // A :ta from a help file will keep the b_help flag set. For ":ptag" // we need to use the flag from the window where we came from. if (l_g_do_tagpreview != 0) { - keep_help_flag = curwin_save->w_buffer->b_help; + keep_help_flag = bt_help(curwin_save->w_buffer); } else { keep_help_flag = curbuf->b_help; } diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index d0a666a049..6b16e888a9 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -153,6 +153,9 @@ func RunTheTest(test) " directory after executing the test. let save_cwd = getcwd() + " Align Nvim defaults to Vim. + source setup.vim + if exists("*SetUp") try call SetUp() @@ -191,9 +194,6 @@ func RunTheTest(test) endtry endif - " Align Nvim defaults to Vim. - source setup.vim - " Clear any autocommands and put back the catch-all for SwapExists. au! au SwapExists * call HandleSwapExists() @@ -364,24 +364,25 @@ let s:flaky_tests = [ \ 'Test_cursorhold_insert()', \ 'Test_exit_callback_interval()', \ 'Test_map_timeout_with_timer_interrupt()', - \ 'Test_oneshot()', \ 'Test_out_cb()', - \ 'Test_paused()', \ 'Test_popup_and_window_resize()', \ 'Test_quoteplus()', \ 'Test_quotestar()', \ 'Test_reltime()', - \ 'Test_repeat_many()', - \ 'Test_repeat_three()', \ 'Test_state()', - \ 'Test_stop_all_in_callback()', \ 'Test_term_mouse_double_click_to_create_tab()', \ 'Test_term_mouse_multiple_clicks_to_visually_select()', \ 'Test_terminal_composing_unicode()', \ 'Test_terminal_redir_file()', \ 'Test_terminal_tmap()', + \ 'Test_timer_oneshot()', + \ 'Test_timer_paused()', + \ 'Test_timer_repeat_many()', + \ 'Test_timer_repeat_three()', + \ 'Test_timer_stop_all_in_callback()', + \ 'Test_timer_stop_in_callback()', + \ 'Test_timer_with_partial_callback()', \ 'Test_termwinscroll()', - \ 'Test_with_partial_callback()', \ ] " Locate Test_ functions and execute them. diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index f8db2ea120..e6c0762729 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -1,33 +1,34 @@ -" Align Nvim defaults to Vim. -set backspace= -set complete=.,w,b,u,t,i -set directory& -set directory^=. -set fillchars=vert:\|,fold:- -set formatoptions=tcq -set fsync -set laststatus=1 -set listchars=eol:$ -set joinspaces -set nohidden nosmarttab noautoindent noautoread noruler noshowcmd -set nohlsearch noincsearch -set nrformats=bin,octal,hex -set shortmess=filnxtToOS -set sidescroll=0 -set tags=./tags,tags -set undodir& -set undodir^=. -set wildoptions= -set startofline -set sessionoptions& -set sessionoptions+=options -set viewoptions& -set viewoptions+=options -set switchbuf= - -" Make "Q" switch to Ex mode. -" This does not work for all tests. -nnoremap Q gQ +if exists('s:did_load') + " Align Nvim defaults to Vim. + set backspace= + set complete=.,w,b,u,t,i + set directory& + set directory^=. + set fillchars=vert:\|,fold:- + set formatoptions=tcq + set fsync + set laststatus=1 + set listchars=eol:$ + set joinspaces + set nohidden nosmarttab noautoindent noautoread noruler noshowcmd + set nohlsearch noincsearch + set nrformats=bin,octal,hex + set shortmess=filnxtToOS + set sidescroll=0 + set tags=./tags,tags + set undodir& + set undodir^=. + set wildoptions= + set startofline + set sessionoptions& + set sessionoptions+=options + set viewoptions& + set viewoptions+=options + set switchbuf= + " Make "Q" switch to Ex mode. + " This does not work for all tests. + nnoremap Q gQ +endif " Common preparations for running tests. diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim index 164149476f..ca7c8574cb 100644 --- a/src/nvim/testdir/test_arglist.vim +++ b/src/nvim/testdir/test_arglist.vim @@ -1,5 +1,8 @@ " Test argument list commands +source shared.vim +source term_util.vim + func Reset_arglist() args a | %argd endfunc @@ -510,3 +513,42 @@ func Test_argdo() call assert_equal(['Xa.c', 'Xb.c', 'Xc.c'], l) bwipe Xa.c Xb.c Xc.c endfunc + +" Test for quiting Vim with unedited files in the argument list +func Test_quit_with_arglist() + if !CanRunVimInTerminal() + throw 'Skipped: cannot run vim in terminal' + endif + let buf = RunVimInTerminal('', {'rows': 6}) + call term_sendkeys(buf, ":set nomore\n") + call term_sendkeys(buf, ":args a b c\n") + call term_sendkeys(buf, ":quit\n") + call term_wait(buf) + call WaitForAssert({-> assert_match('^E173:', term_getline(buf, 6))}) + call StopVimInTerminal(buf) + + " Try :confirm quit with unedited files in arglist + let buf = RunVimInTerminal('', {'rows': 6}) + call term_sendkeys(buf, ":set nomore\n") + call term_sendkeys(buf, ":args a b c\n") + call term_sendkeys(buf, ":confirm quit\n") + call term_wait(buf) + call WaitForAssert({-> assert_match('^\[Y\]es, (N)o: *$', + \ term_getline(buf, 6))}) + call term_sendkeys(buf, "N") + call term_wait(buf) + call term_sendkeys(buf, ":confirm quit\n") + call WaitForAssert({-> assert_match('^\[Y\]es, (N)o: *$', + \ term_getline(buf, 6))}) + call term_sendkeys(buf, "Y") + call term_wait(buf) + call WaitForAssert({-> assert_equal("finished", term_getstatus(buf))}) + only! + " When this test fails, swap files are left behind which breaks subsequent + " tests + call delete('.a.swp') + call delete('.b.swp') + call delete('.c.swp') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim index 9d0d5b3e70..fdd8b0bef6 100644 --- a/src/nvim/testdir/test_assert.vim +++ b/src/nvim/testdir/test_assert.vim @@ -50,6 +50,26 @@ func Test_assert_equal() call remove(v:errors, 0) endfunc +func Test_assert_equal_dict() + call assert_equal(0, assert_equal(#{one: 1, two: 2}, #{two: 2, one: 1})) + + call assert_equal(1, assert_equal(#{one: 1, two: 2}, #{two: 2, one: 3})) + call assert_match("Expected {'one': 1} but got {'one': 3} - 1 equal item omitted", v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, assert_equal(#{one: 1, two: 2}, #{two: 22, one: 11})) + call assert_match("Expected {'one': 1, 'two': 2} but got {'one': 11, 'two': 22}", v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, assert_equal(#{}, #{two: 2, one: 1})) + call assert_match("Expected {} but got {'one': 1, 'two': 2}", v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, assert_equal(#{two: 2, one: 1}, #{})) + call assert_match("Expected {'one': 1, 'two': 2} but got {}", v:errors[0]) + call remove(v:errors, 0) +endfunc + func Test_assert_equalfile() call assert_equal(1, assert_equalfile('abcabc', 'xyzxyz')) call assert_match("E485: Can't read file abcabc", v:errors[0]) diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 1936832400..438851a0ad 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -2226,7 +2226,7 @@ func Test_autocmd_bufreadpre() " (even though the position will be invalid, this should make Vim reset the " cursor position in the other window. wincmd p - 1 + 1 " set cpo+=g " won't do anything, but try to set the cursor on an invalid lnum autocmd BufReadPre <buffer> :norm! 70gg " triggers BufReadPre, should not move the cursor in either window @@ -2241,8 +2241,11 @@ func Test_autocmd_bufreadpre() close close call delete('XAutocmdBufReadPre.txt') + " set cpo-=g endfunc +" FileChangedShell tested in test_filechanged.vim + " Tests for the following autocommands: " - FileWritePre writing a compressed file " - FileReadPost reading a compressed file @@ -2560,7 +2563,29 @@ func Test_BufWrite_lockmarks() call delete('Xtest2') endfunc -" FileChangedShell tested in test_filechanged.vim +" Test closing a window or editing another buffer from a FileChangedRO handler +" in a readonly buffer +func Test_FileChangedRO_winclose() + augroup FileChangedROTest + au! + autocmd FileChangedRO * quit + augroup END + new + set readonly + call assert_fails('normal i', 'E788:') + close + augroup! FileChangedROTest + + augroup FileChangedROTest + au! + autocmd FileChangedRO * edit Xfile + augroup END + new + set readonly + call assert_fails('normal i', 'E788:') + close + augroup! FileChangedROTest +endfunc func LogACmd() call add(g:logged, line('$')) diff --git a/src/nvim/testdir/test_buffer.vim b/src/nvim/testdir/test_buffer.vim index 02360b4e75..67be3e6747 100644 --- a/src/nvim/testdir/test_buffer.vim +++ b/src/nvim/testdir/test_buffer.vim @@ -105,9 +105,9 @@ func Test_buflist_browse() call assert_equal(b2, bufnr()) call assert_equal(1, line('.')) - brewind +/foo3 + brewind + call assert_equal(b1, bufnr()) - call assert_equal(3, line('.')) + call assert_equal(4, line('.')) blast +/baz2 call assert_equal(b3, bufnr()) @@ -146,6 +146,7 @@ endfunc func Test_bdelete_cmd() %bwipe! call assert_fails('bdelete 5', 'E516:') + call assert_fails('1,1bdelete 1 2', 'E488:') " Deleting a unlisted and unloaded buffer edit Xfile1 diff --git a/src/nvim/testdir/test_changelist.vim b/src/nvim/testdir/test_changelist.vim index ce77c1f3c7..3741f32e69 100644 --- a/src/nvim/testdir/test_changelist.vim +++ b/src/nvim/testdir/test_changelist.vim @@ -46,3 +46,5 @@ func Test_getchangelist() call delete('Xfile1.txt') call delete('Xfile2.txt') endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_charsearch.vim b/src/nvim/testdir/test_charsearch.vim index 6f09e85a42..683bcabe34 100644 --- a/src/nvim/testdir/test_charsearch.vim +++ b/src/nvim/testdir/test_charsearch.vim @@ -1,3 +1,4 @@ +" Test for character search commands - t, T, f, F, ; and , func Test_charsearch() enew! @@ -60,3 +61,16 @@ func Test_search_cmds() call assert_equal('ddd yee y', getline(6)) enew! endfunc + +" Test for character search in virtual edit mode with <Tab> +func Test_csearch_virtualedit() + new + set virtualedit=all + call setline(1, "a\tb") + normal! tb + call assert_equal([0, 1, 2, 6], getpos('.')) + set virtualedit& + close! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_clientserver.vim b/src/nvim/testdir/test_clientserver.vim index 922803438f..db62fe5fa6 100644 --- a/src/nvim/testdir/test_clientserver.vim +++ b/src/nvim/testdir/test_clientserver.vim @@ -39,6 +39,8 @@ func Test_client_server() call remote_send(name, ":let testvar = 'yes'\<CR>") call WaitFor('remote_expr("' . name . '", "exists(\"testvar\") ? testvar : \"\"", "", 1) == "yes"') call assert_equal('yes', remote_expr(name, "testvar", "", 2)) + call assert_fails("let x=remote_expr(name, '2+x')", 'E449:') + call assert_fails("let x=remote_expr('[], '2+2')", 'E116:') if has('unix') && has('gui') && !has('gui_running') " Running in a terminal and the GUI is available: Tell the server to open @@ -75,6 +77,7 @@ func Test_client_server() eval 'MYSELF'->remote_startserver() " May get MYSELF1 when running the test again. call assert_match('MYSELF', v:servername) + call assert_fails("call remote_startserver('MYSELF')", 'E941:') endif let g:testvar = 'myself' call assert_equal('myself', remote_expr(v:servername, 'testvar')) @@ -107,7 +110,12 @@ func Test_client_server() call job_stop(job, 'kill') endif endtry + + call assert_fails("let x=remote_peek([])", 'E730:') + call assert_fails("let x=remote_read('vim10')", 'E277:') endfunc " Uncomment this line to get a debugging log " call ch_logfile('channellog', 'w') + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 9885d356fd..276bb7fb71 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -603,12 +603,22 @@ func Test_cmdline_paste() call feedkeys(":\"one\<C-R>\<C-X>two\<CR>", 'xt') call assert_equal('"onetwo', @:) + " Test for pasting register containing CTRL-H using CTRL-R and CTRL-R CTRL-R let @a = "xy\<C-H>z" call feedkeys(":\"\<C-R>a\<CR>", 'xt') call assert_equal('"xz', @:) + call feedkeys(":\"\<C-R>\<C-R>a\<CR>", 'xt') + call assert_equal("\"xy\<C-H>z", @:) call feedkeys(":\"\<C-R>\<C-O>a\<CR>", 'xt') call assert_equal("\"xy\<C-H>z", @:) + " Test for pasting register containing CTRL-V using CTRL-R and CTRL-R CTRL-R + let @a = "xy\<C-V>z" + call feedkeys(":\"\<C-R>=@a\<CR>\<cr>", 'xt') + call assert_equal('"xyz', @:) + call feedkeys(":\"\<C-R>\<C-R>=@a\<CR>\<cr>", 'xt') + call assert_equal("\"xy\<C-V>z", @:) + call assert_beeps('call feedkeys(":\<C-R>=\<C-R>=\<Esc>", "xt")') bwipe! @@ -1268,6 +1278,10 @@ endfunc func Test_cmdwin_feedkeys() " This should not generate E488 call feedkeys("q:\<CR>", 'x') + " Using feedkeys with q: only should automatically close the cmd window + call feedkeys('q:', 'xt') + call assert_equal(1, winnr('$')) + call assert_equal('', getcmdwintype()) endfunc " Tests for the issues fixed in 7.4.441. @@ -1312,22 +1326,6 @@ func Test_cmdwin_autocmd() augroup! CmdWin endfunc -func Test_cmdwin_jump_to_win() - call assert_fails('call feedkeys("q:\<C-W>\<C-W>\<CR>", "xt")', 'E11:') - new - set modified - call assert_fails('call feedkeys("q/:qall\<CR>", "xt")', 'E162:') - close! - call feedkeys("q/:close\<CR>", "xt") - call assert_equal(1, winnr('$')) - call feedkeys("q/:exit\<CR>", "xt") - call assert_equal(1, winnr('$')) - - " opening command window twice should fail - call assert_beeps('call feedkeys("q:q:\<CR>\<CR>", "xt")') - call assert_equal(1, winnr('$')) -endfunc - func Test_cmdlineclear_tabenter() " See test/functional/legacy/cmdline_spec.lua CheckScreendump @@ -1365,6 +1363,22 @@ func Test_cmdline_expand_special() call delete('Xfile.java') endfunc +func Test_cmdwin_jump_to_win() + call assert_fails('call feedkeys("q:\<C-W>\<C-W>\<CR>", "xt")', 'E11:') + new + set modified + call assert_fails('call feedkeys("q/:qall\<CR>", "xt")', 'E162:') + close! + call feedkeys("q/:close\<CR>", "xt") + call assert_equal(1, winnr('$')) + call feedkeys("q/:exit\<CR>", "xt") + call assert_equal(1, winnr('$')) + + " opening command window twice should fail + call assert_beeps('call feedkeys("q:q:\<CR>\<CR>", "xt")') + call assert_equal(1, winnr('$')) +endfunc + " Test for backtick expression in the command line func Test_cmd_backtick() %argd @@ -1385,6 +1399,35 @@ func Test_cmdwin_tabpage() tabclose! endfunc +" Test for the :! command +func Test_cmd_bang() + if !has('unix') + return + endif + + let lines =<< trim [SCRIPT] + " Test for no previous command + call assert_fails('!!', 'E34:') + set nomore + " Test for cmdline expansion with :! + call setline(1, 'foo!') + silent !echo <cWORD> > Xfile.out + call assert_equal(['foo!'], readfile('Xfile.out')) + " Test for using previous command + silent !echo \! ! + call assert_equal(['! echo foo!'], readfile('Xfile.out')) + call writefile(v:errors, 'Xresult') + call delete('Xfile.out') + qall! + [SCRIPT] + call writefile(lines, 'Xscript') + if RunVim([], [], '--clean -S Xscript') + call assert_equal([], readfile('Xresult')) + endif + call delete('Xscript') + call delete('Xresult') +endfunc + " Test error: "E135: *Filter* Autocommands must not change current buffer" func Test_cmd_bang_E135() new @@ -1529,6 +1572,7 @@ endfunc func Test_cmdline_edit() let str = ":one two\<C-U>" let str ..= "one two\<C-W>\<C-W>" + let str ..= "four\<BS>\<C-H>\<Del>\<kDel>" let str ..= "\<Left>five\<Right>" let str ..= "\<Home>two " let str ..= "\<C-Left>one " @@ -1547,6 +1591,7 @@ func Test_cmdline_edit_rightleft() set rightleftcmd=search let str = "/one two\<C-U>" let str ..= "one two\<C-W>\<C-W>" + let str ..= "four\<BS>\<C-H>\<Del>\<kDel>" let str ..= "\<Right>five\<Left>" let str ..= "\<Home>two " let str ..= "\<C-Right>one " @@ -1574,6 +1619,63 @@ func Test_cmdline_expr() call assert_equal("\"e \<C-\>\<C-Y>", @:) endfunc +" Test for 'imcmdline' and 'imsearch' +" This test doesn't actually test the input method functionality. +func Test_cmdline_inputmethod() + new + call setline(1, ['', 'abc', '']) + set imcmdline + + call feedkeys(":\"abc\<CR>", 'xt') + call assert_equal("\"abc", @:) + call feedkeys(":\"\<C-^>abc\<C-^>\<CR>", 'xt') + call assert_equal("\"abc", @:) + call feedkeys("/abc\<CR>", 'xt') + call assert_equal([2, 1], [line('.'), col('.')]) + call feedkeys("/\<C-^>abc\<C-^>\<CR>", 'xt') + call assert_equal([2, 1], [line('.'), col('.')]) + + " set imsearch=2 + call cursor(1, 1) + call feedkeys("/abc\<CR>", 'xt') + call assert_equal([2, 1], [line('.'), col('.')]) + call cursor(1, 1) + call feedkeys("/\<C-^>abc\<C-^>\<CR>", 'xt') + call assert_equal([2, 1], [line('.'), col('.')]) + set imdisable + call feedkeys("/\<C-^>abc\<C-^>\<CR>", 'xt') + call assert_equal([2, 1], [line('.'), col('.')]) + set imdisable& + set imsearch& + + set imcmdline& + %bwipe! +endfunc + +" Test for recursively getting multiple command line inputs +func Test_cmdwin_multi_input() + call feedkeys(":\<C-R>=input('P: ')\<CR>\"cyan\<CR>\<CR>", 'xt') + call assert_equal('"cyan', @:) +endfunc + +" Test for using CTRL-_ in the command line with 'allowrevins' +func Test_cmdline_revins() + CheckNotMSWindows + CheckFeature rightleft + call feedkeys(":\"abc\<c-_>\<cr>", 'xt') + call assert_equal("\"abc\<c-_>", @:) + set allowrevins + call feedkeys(":\"abc\<c-_>xyz\<c-_>\<CR>", 'xt') + call assert_equal('"abcñèæ', @:) + set allowrevins& +endfunc + +" Test for typing UTF-8 composing characters in the command line +func Test_cmdline_composing_chars() + call feedkeys(":\"\<C-V>u3046\<C-V>u3099\<CR>", 'xt') + call assert_equal('"ゔ', @:) +endfunc + " Test for normal mode commands not supported in the cmd window func Test_cmdwin_blocked_commands() call assert_fails('call feedkeys("q:\<C-T>\<CR>", "xt")', 'E11:') @@ -1582,6 +1684,36 @@ func Test_cmdwin_blocked_commands() call assert_fails('call feedkeys("q:Q\<CR>", "xt")', 'E11:') call assert_fails('call feedkeys("q:Z\<CR>", "xt")', 'E11:') call assert_fails('call feedkeys("q:\<F1>\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>s\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>v\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>^\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>n\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>z\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>o\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>w\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>j\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>k\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>h\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>l\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>T\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>x\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>r\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>R\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>K\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>}\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>]\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>f\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>d\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-W>g\<CR>", "xt")', 'E11:') +endfunc + +" Close the Cmd-line window in insert mode using CTRL-C +func Test_cmdwin_insert_mode_close() + %bw! + let s = '' + exe "normal q:a\<C-C>let s='Hello'\<CR>" + call assert_equal('Hello', s) + call assert_equal(1, winnr('$')) endfunc " test that ";" works to find a match at the start of the first line diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim index c0c572ce65..3dc8710d63 100644 --- a/src/nvim/testdir/test_compiler.vim +++ b/src/nvim/testdir/test_compiler.vim @@ -68,5 +68,9 @@ func Test_compiler_completion() endfunc func Test_compiler_error() + let g:current_compiler = 'abc' call assert_fails('compiler doesnotexist', 'E666:') + call assert_equal('abc', g:current_compiler) + call assert_fails('compiler! doesnotexist', 'E666:') + unlet! g:current_compiler endfunc diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim index 7b6bc940d3..acc34e5e7c 100644 --- a/src/nvim/testdir/test_digraph.vim +++ b/src/nvim/testdir/test_digraph.vim @@ -466,10 +466,12 @@ endfunc func Test_digraph_cmndline() " Create digraph on commandline - " This is a hack, to let Vim create the digraph in commandline mode - let s = '' - exe "sil! norm! :let s.='\<c-k>Eu'\<cr>" - call assert_equal("€", s) + call feedkeys(":\"\<c-k>Eu\<cr>", 'xt') + call assert_equal('"€', @:) + + " Canceling a CTRL-K on the cmdline + call feedkeys(":\"a\<c-k>\<esc>b\<cr>", 'xt') + call assert_equal('"ab', @:) endfunc func Test_show_digraph() diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index 19b088959b..65194f49dd 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -286,7 +286,7 @@ func Test_edit_11() call cursor(2, 1) call feedkeys("i\<c-f>int c;\<esc>", 'tnix') call cursor(3, 1) - call feedkeys("i/* comment */", 'tnix') + call feedkeys("\<Insert>/* comment */", 'tnix') call assert_equal(['{', "\<tab>int c;", "/* comment */"], getline(1, '$')) " added changed cindentkeys slightly set cindent cinkeys+=*/ @@ -1617,6 +1617,22 @@ func Test_edit_startinsert() bwipe! endfunc +" Test for :startreplace and :startgreplace +func Test_edit_startreplace() + new + call setline(1, 'abc') + call feedkeys("l:startreplace\<CR>xyz\e", 'xt') + call assert_equal('axyz', getline(1)) + call feedkeys("0:startreplace!\<CR>abc\e", 'xt') + call assert_equal('axyzabc', getline(1)) + call setline(1, "a\tb") + call feedkeys("0l:startgreplace\<CR>xyz\e", 'xt') + call assert_equal("axyz\tb", getline(1)) + call feedkeys("0i\<C-R>=execute('startreplace')\<CR>12\e", 'xt') + call assert_equal("12axyz\tb", getline(1)) + close! +endfunc + func Test_edit_noesckeys() CheckNotGui new @@ -1637,6 +1653,58 @@ func Test_edit_noesckeys() " set esckeys endfunc +" Test for running an invalid ex command in insert mode using CTRL-O +" Note that vim has a hard-coded sleep of 3 seconds. So this test will take +" more than 3 seconds to complete. +func Test_edit_ctrl_o_invalid_cmd() + new + set showmode showcmd + let caught_e492 = 0 + try + call feedkeys("i\<C-O>:invalid\<CR>abc\<Esc>", "xt") + catch /E492:/ + let caught_e492 = 1 + endtry + call assert_equal(1, caught_e492) + call assert_equal('abc', getline(1)) + set showmode& showcmd& + close! +endfunc + +" Test for inserting text in a line with only spaces ('H' flag in 'cpoptions') +func Test_edit_cpo_H() + throw 'Skipped: Nvim does not support cpoptions flag "H"' + new + call setline(1, ' ') + normal! Ia + call assert_equal(' a', getline(1)) + set cpo+=H + call setline(1, ' ') + normal! Ia + call assert_equal(' a ', getline(1)) + set cpo-=H + close! +endfunc + +" Test for inserting tab in virtual replace mode ('L' flag in 'cpoptions') +func Test_edit_cpo_L() + new + call setline(1, 'abcdefghijklmnopqr') + exe "normal 0gR\<Tab>" + call assert_equal("\<Tab>ijklmnopqr", getline(1)) + set cpo+=L + set list + call setline(1, 'abcdefghijklmnopqr') + exe "normal 0gR\<Tab>" + call assert_equal("\<Tab>cdefghijklmnopqr", getline(1)) + set nolist + call setline(1, 'abcdefghijklmnopqr') + exe "normal 0gR\<Tab>" + call assert_equal("\<Tab>ijklmnopqr", getline(1)) + set cpo-=L + %bw! +endfunc + " Test for editing a directory func Test_edit_is_a_directory() CheckEnglish diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim index cff78620d1..b478332c79 100644 --- a/src/nvim/testdir/test_ex_mode.vim +++ b/src/nvim/testdir/test_ex_mode.vim @@ -64,6 +64,66 @@ func Test_ex_mode() let &encoding = encoding_save endfunc +" Test substitute confirmation prompt :%s/pat/str/c in Ex mode +func Test_Ex_substitute() + CheckRunVimInTerminal + let buf = RunVimInTerminal('', {'rows': 6}) + + call term_sendkeys(buf, ":call setline(1, ['foo foo', 'foo foo', 'foo foo'])\<CR>") + call term_sendkeys(buf, ":set number\<CR>") + call term_sendkeys(buf, "gQ") + call WaitForAssert({-> assert_match(':', term_getline(buf, 6))}, 1000) + + call term_sendkeys(buf, "%s/foo/bar/gc\<CR>") + call WaitForAssert({-> assert_match(' 1 foo foo', term_getline(buf, 5))}, + \ 1000) + call WaitForAssert({-> assert_match(' ^^^', term_getline(buf, 6))}, 1000) + call term_sendkeys(buf, "N\<CR>") + call term_wait(buf) + call WaitForAssert({-> assert_match(' ^^^', term_getline(buf, 6))}, 1000) + call term_sendkeys(buf, "n\<CR>") + call WaitForAssert({-> assert_match(' ^^^', term_getline(buf, 6))}, + \ 1000) + call term_sendkeys(buf, "y\<CR>") + + call term_sendkeys(buf, "q\<CR>") + call WaitForAssert({-> assert_match(':', term_getline(buf, 6))}, 1000) + + " Pressing enter in ex mode should print the current line + call term_sendkeys(buf, "\<CR>") + call WaitForAssert({-> assert_match(' 3 foo foo', + \ term_getline(buf, 5))}, 1000) + + call term_sendkeys(buf, ":vi\<CR>") + call WaitForAssert({-> assert_match('foo bar', term_getline(buf, 1))}, 1000) + + call term_sendkeys(buf, ":q!\n") + call StopVimInTerminal(buf) +endfunc + +" Test for displaying lines from an empty buffer in Ex mode +func Test_Ex_emptybuf() + new + call assert_fails('call feedkeys("Q\<CR>", "xt")', 'E749:') + call setline(1, "abc") + call assert_fails('call feedkeys("Q\<CR>", "xt")', 'E501:') + call assert_fails('call feedkeys("Q%d\<CR>", "xt")', 'E749:') + close! +endfunc + +" Test for the :open command +func Test_open_command() + throw 'Skipped: Nvim does not have :open' + new + call setline(1, ['foo foo', 'foo bar', 'foo baz']) + call feedkeys("Qopen\<CR>j", 'xt') + call assert_equal('foo bar', getline('.')) + call feedkeys("Qopen /bar/\<CR>", 'xt') + call assert_equal(5, col('.')) + call assert_fails('call feedkeys("Qopen /baz/\<CR>", "xt")', 'E479:') + close! +endfunc + " Test for :g/pat/visual to run vi commands in Ex mode " This used to hang Vim before 8.2.0274. func Test_Ex_global() @@ -81,6 +141,31 @@ func Test_Ex_escape_enter() call assert_equal("a\rb", l) endfunc +" Test for :append! command in Ex mode +func Test_Ex_append() + throw 'Skipped: Nvim only supports Vim Ex mode' + new + call setline(1, "\t abc") + call feedkeys("Qappend!\npqr\nxyz\n.\nvisual\n", 'xt') + call assert_equal(["\t abc", "\t pqr", "\t xyz"], getline(1, '$')) + close! +endfunc + +" In Ex-mode, backslashes at the end of a command should be halved. +func Test_Ex_echo_backslash() + throw 'Skipped: Nvim only supports Vim Ex mode' + " This test works only when the language is English + if v:lang != "C" && v:lang !~ '^[Ee]n' + return + endif + let bsl = '\\\\' + let bsl2 = '\\\' + call assert_fails('call feedkeys("Qecho " .. bsl .. "\nvisual\n", "xt")', + \ "E15: Invalid expression: \\\\") + call assert_fails('call feedkeys("Qecho " .. bsl2 .. "\nm\nvisual\n", "xt")', + \ "E15: Invalid expression: \\\nm") +endfunc + func Test_ex_mode_errors() " Not allowed to enter ex mode when text is locked au InsertCharPre <buffer> normal! gQ<CR> diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim index df2cf97633..6858efeaa9 100644 --- a/src/nvim/testdir/test_excmd.vim +++ b/src/nvim/testdir/test_excmd.vim @@ -22,6 +22,7 @@ func Test_range_error() call assert_fails(':\/echo 1', 'E481:') normal vv call assert_fails(":'<,'>echo 1", 'E481:') + call assert_fails(":\\xcenter", 'E10:') endfunc func Test_buffers_lastused() @@ -65,6 +66,10 @@ func Test_copy() 1,3copy 2 call assert_equal(['L1', 'L2', 'L1', 'L2', 'L3', 'L3', 'L4'], getline(1, 7)) + " Specifying a count before using : to run an ex-command + exe "normal! gg4:yank\<CR>" + call assert_equal("L1\nL2\nL1\nL2\n", @") + close! endfunc @@ -222,12 +227,24 @@ func Test_change_cmd() close! endfunc +" Test for the :language command +func Test_language_cmd() + CheckNotMSWindows " FIXME: why does this fail on Windows CI? + CheckNotBSD " FIXME: why does this fail on OpenBSD CI? + CheckFeature multi_lang + + call assert_fails('language ctype non_existing_lang', 'E197:') + call assert_fails('language time non_existing_lang', 'E197:') +endfunc + " Test for the :confirm command dialog func Test_confirm_cmd() CheckNotGui CheckRunVimInTerminal + call writefile(['foo1'], 'foo') call writefile(['bar1'], 'bar') + " Test for saving all the modified buffers let buf = RunVimInTerminal('', {'rows': 20}) call term_sendkeys(buf, ":set nomore\n") @@ -240,8 +257,10 @@ func Test_confirm_cmd() call WaitForAssert({-> assert_match('\[Y\]es, (N)o, Save (A)ll, (D)iscard All, (C)ancel: ', term_getline(buf, 20))}, 1000) call term_sendkeys(buf, "A") call StopVimInTerminal(buf) + call assert_equal(['foo2'], readfile('foo')) call assert_equal(['bar2'], readfile('bar')) + " Test for discarding all the changes to modified buffers let buf = RunVimInTerminal('', {'rows': 20}) call term_sendkeys(buf, ":set nomore\n") @@ -254,8 +273,10 @@ func Test_confirm_cmd() call WaitForAssert({-> assert_match('\[Y\]es, (N)o, Save (A)ll, (D)iscard All, (C)ancel: ', term_getline(buf, 20))}, 1000) call term_sendkeys(buf, "D") call StopVimInTerminal(buf) + call assert_equal(['foo2'], readfile('foo')) call assert_equal(['bar2'], readfile('bar')) + " Test for saving and discarding changes to some buffers let buf = RunVimInTerminal('', {'rows': 20}) call term_sendkeys(buf, ":set nomore\n") @@ -270,6 +291,7 @@ func Test_confirm_cmd() call WaitForAssert({-> assert_match('\[Y\]es, (N)o, (C)ancel: ', term_getline(buf, 20))}, 1000) call term_sendkeys(buf, "Y") call StopVimInTerminal(buf) + call assert_equal(['foo4'], readfile('foo')) call assert_equal(['bar2'], readfile('bar')) @@ -391,6 +413,11 @@ func Test_confirm_write_partial_file() call delete('Xscript') endfunc +" Test for the :print command +func Test_print_cmd() + call assert_fails('print', 'E749:') +endfunc + " Test for the :winsize command func Test_winsize_cmd() call assert_fails('winsize 1', 'E465:') @@ -399,6 +426,67 @@ func Test_winsize_cmd() " Actually changing the window size would be flaky. endfunc +" Test for the :redir command +func Test_redir_cmd() + call assert_fails('redir @@', 'E475:') + call assert_fails('redir abc', 'E475:') + if has('unix') + call mkdir('Xdir') + call assert_fails('redir > Xdir', 'E17:') + call delete('Xdir', 'd') + endif + if !has('bsd') + call writefile([], 'Xfile') + call setfperm('Xfile', 'r--r--r--') + call assert_fails('redir! > Xfile', 'E190:') + call delete('Xfile') + endif + + " Test for redirecting to a register + redir @q> | echon 'clean ' | redir END + redir @q>> | echon 'water' | redir END + call assert_equal('clean water', @q) + + " Test for redirecting to a variable + redir => color | echon 'blue ' | redir END + redir =>> color | echon 'sky' | redir END + call assert_equal('blue sky', color) +endfunc + +" Test for the :filetype command +func Test_filetype_cmd() + call assert_fails('filetype abc', 'E475:') +endfunc + +" Test for the :mode command +func Test_mode_cmd() + call assert_fails('mode abc', 'E359:') +endfunc + +" Test for the :sleep command +func Test_sleep_cmd() + call assert_fails('sleep x', 'E475:') +endfunc + +" Test for the :read command +func Test_read_cmd() + call writefile(['one'], 'Xfile') + new + call assert_fails('read', 'E32:') + edit Xfile + read + call assert_equal(['one', 'one'], getline(1, '$')) + close! + new + read Xfile + call assert_equal(['', 'one'], getline(1, '$')) + call deletebufline('', 1, '$') + call feedkeys("Qr Xfile\<CR>visual\<CR>", 'xt') + call assert_equal(['one'], getline(1, '$')) + close! + call delete('Xfile') +endfunc + " Test for running Ex commands when text is locked. " <C-\>e in the command line is used to lock the text func Test_run_excmd_with_text_locked() @@ -423,6 +511,107 @@ func Test_run_excmd_with_text_locked() call assert_fails("call feedkeys(\":\<C-R>=execute('bnext')\<CR>\", 'xt')", 'E565:') endfunc +" Test for the :verbose command +func Test_verbose_cmd() + call assert_equal([' verbose=1'], split(execute('verbose set vbs'), "\n")) + call assert_equal([' verbose=0'], split(execute('0verbose set vbs'), "\n")) + let l = execute("4verbose set verbose | set verbose") + call assert_equal([' verbose=4', ' verbose=0'], split(l, "\n")) +endfunc + +" Test for the :delete command and the related abbreviated commands +func Test_excmd_delete() + new + call setline(1, ['foo', "\tbar"]) + call assert_equal(['^Ibar$'], split(execute('dl'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal(['^Ibar$'], split(execute('dell'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal(['^Ibar$'], split(execute('delel'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal(['^Ibar$'], split(execute('deletl'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal(['^Ibar$'], split(execute('deletel'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal([' bar'], split(execute('dp'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal([' bar'], split(execute('dep'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal([' bar'], split(execute('delp'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal([' bar'], split(execute('delep'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal([' bar'], split(execute('deletp'), "\n")) + call setline(1, ['foo', "\tbar"]) + call assert_equal([' bar'], split(execute('deletep'), "\n")) + close! +endfunc + +" Test for commands that are blocked in a sandbox +func Sandbox_tests() + call assert_fails("call histadd(':', 'ls')", 'E48:') + call assert_fails("call mkdir('Xdir')", 'E48:') + call assert_fails("call rename('a', 'b')", 'E48:') + call assert_fails("call setbufvar(1, 'myvar', 1)", 'E48:') + call assert_fails("call settabvar(1, 'myvar', 1)", 'E48:') + call assert_fails("call settabwinvar(1, 1, 'myvar', 1)", 'E48:') + call assert_fails("call setwinvar(1, 'myvar', 1)", 'E48:') + call assert_fails("call timer_start(100, '')", 'E48:') + if has('channel') + call assert_fails("call prompt_setcallback(1, '')", 'E48:') + call assert_fails("call prompt_setinterrupt(1, '')", 'E48:') + call assert_fails("call prompt_setprompt(1, '')", 'E48:') + endif + call assert_fails("let $TESTVAR=1", 'E48:') + call assert_fails("call feedkeys('ivim')", 'E48:') + call assert_fails("source! Xfile", 'E48:') + call assert_fails("call delete('Xfile')", 'E48:') + call assert_fails("call writefile([], 'Xfile')", 'E48:') + call assert_fails('!ls', 'E48:') + " call assert_fails('shell', 'E48:') + call assert_fails('stop', 'E48:') + call assert_fails('exe "normal \<C-Z>"', 'E48:') + " set insertmode + " call assert_fails('call feedkeys("\<C-Z>", "xt")', 'E48:') + " set insertmode& + call assert_fails('suspend', 'E48:') + call assert_fails('call system("ls")', 'E48:') + call assert_fails('call systemlist("ls")', 'E48:') + if has('clientserver') + call assert_fails('let s=remote_expr("gvim", "2+2")', 'E48:') + if !has('win32') + " remote_foreground() doesn't thrown an error message on MS-Windows + call assert_fails('call remote_foreground("gvim")', 'E48:') + endif + call assert_fails('let s=remote_peek("gvim")', 'E48:') + call assert_fails('let s=remote_read("gvim")', 'E48:') + call assert_fails('let s=remote_send("gvim", "abc")', 'E48:') + call assert_fails('let s=server2client("gvim", "abc")', 'E48:') + endif + if has('terminal') + call assert_fails('terminal', 'E48:') + call assert_fails('call term_start("vim")', 'E48:') + call assert_fails('call term_dumpwrite(1, "Xfile")', 'E48:') + endif + if has('channel') + call assert_fails("call ch_logfile('chlog')", 'E48:') + call assert_fails("call ch_open('localhost:8765')", 'E48:') + endif + if has('job') + call assert_fails("call job_start('vim')", 'E48:') + endif + if has('unix') && has('libcall') + call assert_fails("echo libcall('libc.so', 'getenv', 'HOME')", 'E48:') + endif + if has('unix') + call assert_fails('cd `pwd`', 'E48:') + endif +endfunc + +func Test_sandbox() + sandbox call Sandbox_tests() +endfunc + func Test_not_break_expression_register() call setreg('=', '1+1') if 0 diff --git a/src/nvim/testdir/test_expand.vim b/src/nvim/testdir/test_expand.vim index 48dce25bb3..d86fea4f45 100644 --- a/src/nvim/testdir/test_expand.vim +++ b/src/nvim/testdir/test_expand.vim @@ -1,5 +1,7 @@ " Test for expanding file names +source shared.vim + func Test_with_directories() call mkdir('Xdir1') call mkdir('Xdir2') @@ -79,5 +81,48 @@ func Test_expandcmd() call assert_fails('call expandcmd("make <afile>")', 'E495:') enew call assert_fails('call expandcmd("make %")', 'E499:') - close + let $FOO="blue\tsky" + call setline(1, "$FOO") + call assert_equal("grep pat blue\tsky", expandcmd('grep pat <cfile>')) + unlet $FOO + close! +endfunc + +" Test for expanding <sfile>, <slnum> and <sflnum> outside of sourcing a script +func Test_source_sfile() + let lines =<< trim [SCRIPT] + :call assert_fails('echo expandcmd("<sfile>")', 'E498:') + :call assert_fails('echo expandcmd("<slnum>")', 'E842:') + :call assert_fails('echo expandcmd("<sflnum>")', 'E961:') + :call assert_fails('call expandcmd("edit <cfile>")', 'E446:') + :call assert_fails('call expandcmd("edit #")', 'E194:') + :call assert_fails('call expandcmd("edit #<2")', 'E684:') + :call assert_fails('call expandcmd("edit <cword>")', 'E348:') + :call assert_fails('call expandcmd("edit <cexpr>")', 'E348:') + :call assert_fails('autocmd User MyCmd echo "<sfile>"', 'E498:') + :call writefile(v:errors, 'Xresult') + :qall! + + [SCRIPT] + call writefile(lines, 'Xscript') + if RunVim([], [], '--clean -s Xscript') + call assert_equal([], readfile('Xresult')) + endif + call delete('Xscript') + call delete('Xresult') +endfunc + +" Test for expanding filenames multiple times in a command line +func Test_expand_filename_multicmd() + edit foo + call setline(1, 'foo!') + new + call setline(1, 'foo!') + new <cword> | new <cWORD> + call assert_equal(4, winnr('$')) + call assert_equal('foo!', bufname(winbufnr(1))) + call assert_equal('foo', bufname(winbufnr(2))) + %bwipe! endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim index 1b0f49ca51..c6e781a1ef 100644 --- a/src/nvim/testdir/test_filechanged.vim +++ b/src/nvim/testdir/test_filechanged.vim @@ -237,7 +237,7 @@ func Test_file_changed_dialog() sleep 2 silent !touch Xchanged_d let v:warningmsg = '' - checktime + checktime Xchanged_d call assert_equal('', v:warningmsg) call assert_equal(1, line('$')) call assert_equal('new line', getline(1)) diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 00ba548e97..dee04e66dc 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -757,6 +757,30 @@ func Test_setfiletype_completion() call assert_equal('"setfiletype java javacc javascript javascriptreact', @:) endfunc +" Test for ':filetype detect' command for a buffer without a file +func Test_emptybuf_ftdetect() + new + call setline(1, '#!/bin/sh') + call assert_equal('', &filetype) + filetype detect + call assert_equal('sh', &filetype) + close! +endfunc + +" Test for ':filetype indent on' and ':filetype indent off' commands +func Test_filetype_indent_off() + new Xtest.vim + filetype indent on + call assert_equal(1, g:did_indent_on) + call assert_equal(['filetype detection:ON plugin:OFF indent:ON'], + \ execute('filetype')->split("\n")) + filetype indent off + call assert_equal(0, exists('g:did_indent_on')) + call assert_equal(['filetype detection:ON plugin:OFF indent:OFF'], + \ execute('filetype')->split("\n")) + close +endfunc + """"""""""""""""""""""""""""""""""""""""""""""""" " Tests for specific extensions and filetypes. " Keep sorted. diff --git a/src/nvim/testdir/test_filter_cmd.vim b/src/nvim/testdir/test_filter_cmd.vim index d465e48c7b..dae164b11c 100644 --- a/src/nvim/testdir/test_filter_cmd.vim +++ b/src/nvim/testdir/test_filter_cmd.vim @@ -45,6 +45,14 @@ func Test_filter_fails() call assert_fails('filter /pat', 'E476:') call assert_fails('filter /pat/', 'E476:') call assert_fails('filter /pat/ asdf', 'E492:') + " Using assert_fails() causes E476 instead of E866. So use a try-catch. + let caught_e866 = 0 + try + filter /\@>b/ ls + catch /E866:/ + let caught_e866 = 1 + endtry + call assert_equal(1, caught_e866) call assert_fails('filter!', 'E471:') call assert_fails('filter! pat', 'E476:') diff --git a/src/nvim/testdir/test_findfile.vim b/src/nvim/testdir/test_findfile.vim index 1684c5d30a..0f4b30aec2 100644 --- a/src/nvim/testdir/test_findfile.vim +++ b/src/nvim/testdir/test_findfile.vim @@ -193,12 +193,14 @@ func Test_find_cmd() set path=.,./**/* call CreateFiles() cd Xdir1 + " Test for :find find foo call assert_equal('foo', expand('%:.')) 2find foo call assert_equal('Xdir2/foo', expand('%:.')) call assert_fails('3find foo', 'E347:') + " Test for :sfind enew sfind barfoo @@ -207,6 +209,7 @@ func Test_find_cmd() close call assert_fails('sfind baz', 'E345:') call assert_equal(2, winnr('$')) + " Test for :tabfind enew tabfind foobar @@ -215,7 +218,8 @@ func Test_find_cmd() tabclose call assert_fails('tabfind baz', 'E345:') call assert_equal(1, tabpagenr('$')) - " call chdir(save_dir) + + call chdir(save_dir) exe 'cd ' . save_dir call CleanFiles() let &path = save_path diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim index ad561baf4a..feddf85346 100644 --- a/src/nvim/testdir/test_global.vim +++ b/src/nvim/testdir/test_global.vim @@ -66,6 +66,18 @@ func Test_global_print() close! endfunc +" Test for global command with newline character +func Test_global_newline() + new + call setline(1, ['foo']) + exe "g/foo/s/f/h/\<NL>s/o$/w/" + call assert_equal('how', getline(1)) + call setline(1, ["foo\<NL>bar"]) + exe "g/foo/s/foo\\\<NL>bar/xyz/" + call assert_equal('xyz', getline(1)) + close! +endfunc + func Test_wrong_delimiter() call assert_fails('g x^bxd', 'E146:') endfunc diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim index ece8ccf215..e84726bbfc 100644 --- a/src/nvim/testdir/test_help_tagjump.vim +++ b/src/nvim/testdir/test_help_tagjump.vim @@ -125,6 +125,11 @@ func Test_help_tagjump() call assert_true(getline('.') =~ '\*/\\bar\*') helpclose + help \_$ + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*/\\_$\*') + helpclose + help CTRL-\_CTRL-N call assert_equal("help", &filetype) call assert_true(getline('.') =~ '\*CTRL-\\_CTRL-N\*') diff --git a/src/nvim/testdir/test_history.vim b/src/nvim/testdir/test_history.vim index 96006ac7e7..feb521e232 100644 --- a/src/nvim/testdir/test_history.vim +++ b/src/nvim/testdir/test_history.vim @@ -114,6 +114,7 @@ function Test_Search_history_window() bwipe! endfunc +" Test for :history command option completion function Test_history_completion() call feedkeys(":history \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"history / : = > ? @ all cmd debug expr input search', @:) @@ -122,8 +123,9 @@ endfunc " Test for increasing the 'history' option value func Test_history_size() let save_histsz = &history - call histdel(':') set history=10 + call histadd(':', 'ls') + call histdel(':') for i in range(1, 5) call histadd(':', 'cmd' .. i) endfor @@ -173,6 +175,53 @@ func Test_history_search() call assert_equal(['pat2', 'pat1', ''], g:pat) cunmap <F2> delfunc SavePat + + " Search for a pattern that is not present in the history + call assert_beeps('call feedkeys("/a1b2\<Up>\<CR>", "xt")') + + " Recall patterns with 'history' set to 0 + set history=0 + let @/ = 'abc' + let cmd = 'call feedkeys("/\<Up>\<Down>\<S-Up>\<S-Down>\<CR>", "xt")' + call assert_fails(cmd, 'E486:') + set history& + + " Recall patterns till the end of history + set history=4 + call histadd('/', 'pat') + call histdel('/') + call histadd('/', 'pat1') + call histadd('/', 'pat2') + call assert_beeps('call feedkeys("/\<Up>\<Up>\<Up>\<C-U>\<cr>", "xt")') + call assert_beeps('call feedkeys("/\<Down><cr>", "xt")') + + " Test for wrapping around the history list + for i in range(3, 7) + call histadd('/', 'pat' .. i) + endfor + let upcmd = "\<up>\<up>\<up>\<up>\<up>" + let downcmd = "\<down>\<down>\<down>\<down>\<down>" + try + call feedkeys("/" .. upcmd .. "\<cr>", 'xt') + catch /E486:/ + endtry + call assert_equal('pat4', @/) + try + call feedkeys("/" .. upcmd .. downcmd .. "\<cr>", 'xt') + catch /E486:/ + endtry + call assert_equal('pat4', @/) + + " Test for changing the search command separator in the history + call assert_fails('call feedkeys("/def/\<cr>", "xt")', 'E486:') + call assert_fails('call feedkeys("?\<up>\<cr>", "xt")', 'E486:') + call assert_equal('def?', histget('/', -1)) + + call assert_fails('call feedkeys("/ghi?\<cr>", "xt")', 'E486:') + call assert_fails('call feedkeys("?\<up>\<cr>", "xt")', 'E486:') + call assert_equal('ghi\?', histget('/', -1)) + + set history& endfunc " Test for making sure the key value is not stored in history diff --git a/src/nvim/testdir/test_increment.vim b/src/nvim/testdir/test_increment.vim index 12fe52f057..2559654f25 100644 --- a/src/nvim/testdir/test_increment.vim +++ b/src/nvim/testdir/test_increment.vim @@ -776,6 +776,14 @@ func Test_increment_empty_line() call setline(1, ['0', '0', '0', '0', '0', '0', '']) exe "normal Gvgg\<C-A>" call assert_equal(['1', '1', '1', '1', '1', '1', ''], getline(1, 7)) + + " Ctrl-A/Ctrl-X should do nothing in operator pending mode + %d + call setline(1, 'one two') + exe "normal! c\<C-A>l" + exe "normal! c\<C-X>l" + call assert_equal('one two', getline(1)) + bwipe! endfunc diff --git a/src/nvim/testdir/test_join.vim b/src/nvim/testdir/test_join.vim index ac6ef8f29f..1f7a0825a5 100644 --- a/src/nvim/testdir/test_join.vim +++ b/src/nvim/testdir/test_join.vim @@ -437,5 +437,11 @@ func Test_join_lines() call setline(1, ['a', 'b', '', 'c', 'd']) normal 5J call assert_equal('a b c d', getline(1)) + call setline(1, ['a', 'b', 'c']) + 2,2join + call assert_equal(['a', 'b', 'c'], getline(1, '$')) + call assert_equal(2, line('.')) + 2join + call assert_equal(['a', 'b c'], getline(1, '$')) bwipe! endfunc diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim index e2b7554797..a34a950526 100644 --- a/src/nvim/testdir/test_mapping.vim +++ b/src/nvim/testdir/test_mapping.vim @@ -643,6 +643,13 @@ func Test_map_error() map <expr> ,f abc call assert_fails('normal ,f', 'E121:') unmap <expr> ,f + + " Recursive use of :normal in a map + set maxmapdepth=100 + map gq :normal gq<CR> + call assert_fails('normal gq', 'E192:') + unmap gq + set maxmapdepth& endfunc " Test for <special> key mapping diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim index 608f9883c2..74e63d9d69 100644 --- a/src/nvim/testdir/test_marks.vim +++ b/src/nvim/testdir/test_marks.vim @@ -215,6 +215,10 @@ func Test_mark_error() call assert_fails('mark', 'E471:') call assert_fails('mark xx', 'E488:') call assert_fails('mark _', 'E191:') + call assert_beeps('normal! m~') + + call setpos("'k", [0, 100, 1, 0]) + call assert_fails("normal 'k", 'E19:') endfunc " Test for :lockmarks when pasting content @@ -241,6 +245,29 @@ func Test_marks_k_cmd() close! endfunc +" Test for file marks (A-Z) +func Test_file_mark() + new Xone + call setline(1, ['aaa', 'bbb']) + norm! G$mB + w! + new Xtwo + call setline(1, ['ccc', 'ddd']) + norm! GmD + w! + + enew + normal! `B + call assert_equal('Xone', bufname()) + call assert_equal([2, 3], [line('.'), col('.')]) + normal! 'D + call assert_equal('Xtwo', bufname()) + call assert_equal([2, 1], [line('.'), col('.')]) + + call delete('Xone') + call delete('Xtwo') +endfunc + " Test for the getmarklist() function func Test_getmarklist() new diff --git a/src/nvim/testdir/test_move.vim b/src/nvim/testdir/test_move.vim index f666a904b0..8c40369dbd 100644 --- a/src/nvim/testdir/test_move.vim +++ b/src/nvim/testdir/test_move.vim @@ -38,6 +38,7 @@ func Test_move() call assert_fails("move -100", 'E16:') call assert_fails("move +100", 'E16:') call assert_fails('move', 'E16:') + call assert_fails("move 'r", 'E20:') %bwipeout! endfunc diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index f146726b7b..2d5b66df26 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1,6 +1,8 @@ " Test for various Normal mode commands source shared.vim +source check.vim +source view_util.vim func Setup_NewWindow() 10new @@ -53,7 +55,7 @@ func OpfuncDummy(type, ...) let g:bufnr=bufnr('%') endfunc -fun! Test_normal00_optrans() +func Test_normal00_optrans() new call append(0, ['1 This is a simple test: abcd', '2 This is the second line', '3 this is the third line']) 1 @@ -95,6 +97,12 @@ func Test_normal01_keymodel() 50 call feedkeys("\<S-Up>y", 'tx') call assert_equal(['49', '5'], getreg(0, 0, 1)) + " Use the different Shift special keys + 50 + call feedkeys("\<S-Right>\<S-Left>\<S-Up>\<S-Down>\<S-Home>\<S-End>y", 'tx') + call assert_equal(['50'], getline("'<", "'>")) + call assert_equal(['50', ''], getreg(0, 0, 1)) + " Do not start visual mode when keymodel= set keymodel= 50 @@ -486,8 +494,8 @@ func Test_normal11_showcmd() bw! endfunc +" Test for nv_error and normal command errors func Test_normal12_nv_error() - " Test for nv_error 10new call setline(1, range(1,5)) " should not do anything, just beep @@ -497,6 +505,22 @@ func Test_normal12_nv_error() call assert_beeps("normal! g\<C-A>") call assert_beeps("normal! g\<C-X>") call assert_beeps("normal! g\<C-B>") + " call assert_beeps("normal! vQ\<Esc>") + call assert_beeps("normal! 2[[") + call assert_beeps("normal! 2]]") + call assert_beeps("normal! 2[]") + call assert_beeps("normal! 2][") + call assert_beeps("normal! 4[z") + call assert_beeps("normal! 4]z") + call assert_beeps("normal! 4[c") + call assert_beeps("normal! 4]c") + call assert_beeps("normal! 200%") + call assert_beeps("normal! %") + call assert_beeps("normal! 2{") + call assert_beeps("normal! 2}") + call assert_beeps("normal! r\<Right>") + call assert_beeps("normal! 8ry") + call assert_beeps('normal! "@') bw! endfunc @@ -658,6 +682,13 @@ func Test_normal16_z_scroll_hor() $put =lineB 1d + " Test for zl and zh with a count + norm! 0z10l + call assert_equal([11, 1], [col('.'), wincol()]) + norm! z4h + call assert_equal([11, 5], [col('.'), wincol()]) + normal! 2gg + " Test for zl 1 norm! 5zl @@ -780,6 +811,27 @@ func Test_normal17_z_scroll_hor2() bw! endfunc +" Test for H, M and L commands with folds +func Test_scroll_cmds() + 15new + call setline(1, range(1, 100)) + exe "normal! 30ggz\<CR>" + set foldenable + 33,36fold + 40,43fold + 46,49fold + let h = winheight(0) + " Top of the screen = 30 + " Folded lines = 9 + " Bottom of the screen = 30 + h + 9 - 1 + normal! 4L + call assert_equal(35 + h, line('.')) + normal! 4H + call assert_equal(33, line('.')) + set foldenable& + close! +endfunc + func Test_normal18_z_fold() " basic tests for foldopen/folddelete if !has("folding") @@ -789,6 +841,9 @@ func Test_normal18_z_fold() 50 setl foldenable fdm=marker foldlevel=5 + call assert_beeps('normal! zj') + call assert_beeps('normal! zk') + " Test for zF " First fold norm! 4zF @@ -1221,6 +1276,9 @@ func Test_normal22_zet() let a = readfile('Xfile_Test_normal22_zet') call assert_equal(['1', '2'], a) + " Unsupported Z command + call assert_beeps('normal! ZW') + " Nvim: This sometimes hangs the TSAN build. " for file in ['Xfile_Test_normal22_zet'] " call delete(file) @@ -1289,6 +1347,15 @@ func Test_normal23_K() call assert_match("man --pager=cat 'man'", a) endif + " Error cases + call setline(1, '#$#') + call assert_fails('normal! ggK', 'E349:') + call setline(1, '---') + call assert_fails('normal! ggv2lK', 'E349:') + call setline(1, ['abc', 'xyz']) + call assert_fails("normal! gg2lv2h\<C-]>", 'E426:') + call assert_beeps("normal! ggVjK") + " clean up let &keywordprg = k bw! @@ -1499,12 +1566,27 @@ func Test_normal28_parenthesis() norm! $d( call assert_equal(['With some sentences!', '', ' ', '', 'This is a long sentence', ''], getline(1, '$')) + " It is an error if a next sentence is not found + %d + call setline(1, '.SH') + call assert_beeps('normal )') + + " Jumping to a fold should open the fold + call setline(1, ['', '', 'one', 'two', 'three']) + set foldenable + 2,$fold + call feedkeys(')', 'xt') + call assert_equal(3, line('.')) + call assert_equal(1, foldlevel('.')) + call assert_equal(-1, foldclosed('.')) + set foldenable& + " clean up bw! endfunc -fun! Test_normal29_brace() - " basic test for { and } movements +" Test for { and } paragraph movements +func Test_normal29_brace() let text =<< trim [DATA] A paragraph begins after each empty line, and also at each of a set of paragraph macros, specified by the pairs of characters in the 'paragraphs' @@ -1657,12 +1739,24 @@ fun! Test_normal29_brace() " [DATA] " call assert_equal(expected, getline(1, '$')) + " Jumping to a fold should open the fold + " %d + " call setline(1, ['', 'one', 'two', '']) + " set foldenable + " 2,$fold + " call feedkeys('}', 'xt') + " call assert_equal(4, line('.')) + " call assert_equal(1, foldlevel('.')) + " call assert_equal(-1, foldclosed('.')) + " set foldenable& + " clean up set cpo-={ bw! endfunc -fun! Test_normal30_changecase() +" Test for ~ command +func Test_normal30_changecase() new call append(0, 'This is a simple test: äüöß') norm! 1ggVu @@ -1682,8 +1776,23 @@ fun! Test_normal30_changecase() norm! V~ call assert_equal('THIS IS A simple test: äüöss', getline('.')) - " Turkish ASCII turns to multi-byte. On some systems Turkish locale - " is available but toupper()/tolower() don't do the right thing. + " Test for changing case across lines using 'whichwrap' + call setline(1, ['aaaaaa', 'aaaaaa']) + normal! gg10~ + call assert_equal(['AAAAAA', 'aaaaaa'], getline(1, 2)) + set whichwrap+=~ + normal! gg10~ + call assert_equal(['aaaaaa', 'AAAAaa'], getline(1, 2)) + set whichwrap& + + " clean up + bw! +endfunc + +" Turkish ASCII turns to multi-byte. On some systems Turkish locale +" is available but toupper()/tolower() don't do the right thing. +func Test_normal_changecase_turkish() + new try lang tr_TR.UTF-8 set casemap= @@ -1727,21 +1836,11 @@ fun! Test_normal30_changecase() " can't use Turkish locale throw 'Skipped: Turkish locale not available' endtry - - call setline(1, ['aaaaaa', 'aaaaaa']) - normal! gg10~ - call assert_equal(['AAAAAA', 'aaaaaa'], getline(1, 2)) - set whichwrap+=~ - normal! gg10~ - call assert_equal(['aaaaaa', 'AAAAaa'], getline(1, 2)) - set whichwrap& - - " clean up - bw! + close! endfunc -fun! Test_normal31_r_cmd() - " Test for r command +" Test for r (replace) command +func Test_normal31_r_cmd() new call append(0, 'This is a simple test: abcd') exe "norm! 1gg$r\<cr>" @@ -1760,6 +1859,22 @@ fun! Test_normal31_r_cmd() exe "norm! 1gg05rf" call assert_equal('fffffis a', getline(1)) + " When replacing characters, copy characters from above and below lines + " using CTRL-Y and CTRL-E. + " Different code paths are used for utf-8 and latin1 encodings + set showmatch + for enc in ['latin1', 'utf-8'] + enew! + let &encoding = enc + call setline(1, [' {a}', 'xxxxxxxxxx', ' [b]']) + exe "norm! 2gg5r\<C-Y>l5r\<C-E>" + call assert_equal(' {a}x [b]x', getline(2)) + endfor + set showmatch& + + " r command should fail in operator pending mode + call assert_beeps('normal! cr') + " clean up set noautoindent bw! @@ -1783,7 +1898,7 @@ endfunc " Test for g`, g;, g,, g&, gv, gk, gj, gJ, g0, g^, g_, gm, g$, gM, g CTRL-G, " gi and gI commands -fun! Test_normal33_g_cmd2() +func Test_normal33_g_cmd2() if !has("jumplist") return endif @@ -1832,6 +1947,16 @@ fun! Test_normal33_g_cmd2() norm! g& call assert_equal(['11', '22', '33', '44', '55', '66', '77', '88', '9', '110', 'a', 'b', 'c', 'dd'], getline(1, '$')) + " Jumping to a fold using gg should open the fold + set foldenable + set foldopen+=jump + 5,8fold + call feedkeys('6gg', 'xt') + call assert_equal(1, foldlevel('.')) + call assert_equal(-1, foldclosed('.')) + set foldopen-=jump + set foldenable& + " Test for gv %d call append('$', repeat(['abcdefgh'], 8)) @@ -1975,6 +2100,10 @@ fun! Test_normal33_g_cmd2() call assert_equal('foo first line', getline(1)) set virtualedit& + " Test for aboring a g command using CTRL-\ CTRL-G + exe "normal! g\<C-\>\<C-G>" + call assert_equal('foo first line', getline('.')) + " clean up bw! endfunc @@ -1995,6 +2124,10 @@ func Test_g_ctrl_g() let a = execute(":norm! g\<c-g>") call assert_equal("\n--No lines in buffer--", a) + " Test for CTRL-G (same as :file) + let a = execute(":norm! \<c-g>") + call assert_equal("\n\n\"[No Name]\" --No lines in buffer--", a) + call setline(1, ['first line', 'second line']) " Test g CTRL-g with dos, mac and unix file type. @@ -2063,7 +2196,7 @@ func Test_g_ctrl_g() endfunc " Test for g8 -fun! Test_normal34_g_cmd3() +func Test_normal34_g_cmd3() new let a=execute(':norm! 1G0g8') call assert_equal("\nNUL", a) @@ -2113,7 +2246,7 @@ func Test_normal_8g8() endfunc " Test for g< -fun! Test_normal35_g_cmd4() +func Test_normal35_g_cmd4() " Cannot capture its output, " probably a bug, therefore, test disabled: throw "Skipped: output of g< can't be tested currently" @@ -2123,7 +2256,7 @@ fun! Test_normal35_g_cmd4() endfunc " Test for gp gP go -fun! Test_normal36_g_cmd5() +func Test_normal36_g_cmd5() new call append(0, 'abcdefghijklmnopqrstuvwxyz') set ff=unix @@ -2162,7 +2295,7 @@ fun! Test_normal36_g_cmd5() endfunc " Test for gt and gT -fun! Test_normal37_g_cmd6() +func Test_normal37_g_cmd6() tabnew 1.txt tabnew 2.txt tabnew 3.txt @@ -2189,7 +2322,7 @@ fun! Test_normal37_g_cmd6() endfunc " Test for <Home> and <C-Home> key -fun! Test_normal38_nvhome() +func Test_normal38_nvhome() new call setline(1, range(10)) $ @@ -2211,8 +2344,21 @@ fun! Test_normal38_nvhome() bw! endfunc +" Test for <End> and <C-End> keys +func Test_normal_nvend() + new + call setline(1, map(range(1, 10), '"line" .. v:val')) + exe "normal! \<End>" + call assert_equal(5, col('.')) + exe "normal! 4\<End>" + call assert_equal([4, 5], [line('.'), col('.')]) + exe "normal! \<C-End>" + call assert_equal([10, 6], [line('.'), col('.')]) + close! +endfunc + " Test for cw cW ce -fun! Test_normal39_cw() +func Test_normal39_cw() " Test for cw and cW on whitespace " and cpo+=w setting new @@ -2253,7 +2399,7 @@ fun! Test_normal39_cw() endfunc " Test for CTRL-\ commands -fun! Test_normal40_ctrl_bsl() +func Test_normal40_ctrl_bsl() new call append(0, 'here are some words') exe "norm! 1gg0a\<C-\>\<C-N>" @@ -2271,14 +2417,19 @@ fun! Test_normal40_ctrl_bsl() exe ":norm! \<c-\>\<c-n>dw" " set noim call assert_equal('are some words', getline(1)) - " call assert_false(&insertmode) + call assert_false(&insertmode) + call assert_beeps("normal! \<C-\>\<C-A>") + + " Using CTRL-\ CTRL-N in cmd window should close the window + call feedkeys("q:\<C-\>\<C-N>", 'xt') + call assert_equal('', getcmdwintype()) " clean up bw! endfunc " Test for <c-r>=, <c-r><c-r>= and <c-r><c-o>= in insert mode -fun! Test_normal41_insert_reg() +func Test_normal41_insert_reg() new set sts=2 sw=2 ts=8 tw=0 call append(0, ["aaa\tbbb\tccc", '', '', '']) @@ -2334,7 +2485,7 @@ func Test_normal42_halfpage() endfunc " Tests for text object aw -fun! Test_normal43_textobject1() +func Test_normal43_textobject1() new call append(0, ['foobar,eins,foobar', 'foo,zwei,foo ']) " diw @@ -2505,7 +2656,7 @@ func Test_normal49_counts() endfunc func Test_normal50_commandline() - if !has("timers") || !has("cmdline_hist") || !has("vertsplit") + if !has("timers") || !has("cmdline_hist") return endif func! DoTimerWork(id) @@ -2567,6 +2718,8 @@ func Test_normal52_rl() call assert_equal(19, col('.')) call feedkeys("\<right>", 'tx') call assert_equal(18, col('.')) + call feedkeys("\<left>", 'tx') + call assert_equal(19, col('.')) call feedkeys("\<s-right>", 'tx') call assert_equal(13, col('.')) call feedkeys("\<c-right>", 'tx') @@ -2690,6 +2843,8 @@ func Test_changelist() normal g; call assert_equal([2, 2], [line('.'), col('.')]) call assert_fails('normal g;', 'E662:') + new + call assert_fails('normal g;', 'E664:') %bwipe! let &ul = save_ul endfunc @@ -2736,6 +2891,10 @@ endfunc " Jumping to beginning and end of methods in Java-like languages func Test_java_motion() new + call assert_beeps('normal! [m') + call assert_beeps('normal! ]m') + call assert_beeps('normal! [M') + call assert_beeps('normal! ]M') a Piece of Java { @@ -2810,7 +2969,7 @@ Piece of Java close! endfunc -fun! Test_normal_gdollar_cmd() +func Test_normal_gdollar_cmd() if !has("jumplist") return endif @@ -2935,6 +3094,29 @@ func Test_normal_cpo_minus() close! endfunc +" Test for displaying dollar when changing text ('$' flag in 'cpoptions') +func Test_normal_cpo_dollar() + throw 'Skipped: use test/functional/legacy/cpoptions_spec.lua' + new + let g:Line = '' + func SaveFirstLine() + let g:Line = Screenline(1) + return '' + endfunc + inoremap <expr> <buffer> <F2> SaveFirstLine() + call test_override('redraw_flag', 1) + set cpo+=$ + call setline(1, 'one two three') + redraw! + exe "normal c2w\<F2>vim" + call assert_equal('one tw$ three', g:Line) + call assert_equal('vim three', getline(1)) + set cpo-=$ + call test_override('ALL', 0) + delfunc SaveFirstLine + %bw! +endfunc + " Test for using : to run a multi-line Ex command in operator pending mode func Test_normal_yank_with_excmd() new @@ -2975,12 +3157,28 @@ func Test_wincmd_with_count() endfunc " Test for 'b', 'B' 'ge' and 'gE' commands -func Test_backward_motion() +func Test_horiz_motion() + new normal! gg call assert_beeps('normal! b') call assert_beeps('normal! B') call assert_beeps('normal! gE') call assert_beeps('normal! ge') + " <S-Backspace> moves one word left and <C-Backspace> moves one WORD left + call setline(1, 'one ,two ,three') + exe "normal! $\<S-BS>" + call assert_equal(11, col('.')) + exe "normal! $\<C-BS>" + call assert_equal(10, col('.')) + close! +endfunc + +" Test for using a : command in operator pending mode +func Test_normal_colon_op() + new + call setline(1, ['one', 'two']) + call assert_beeps("normal! Gc:d\<CR>") + close! endfunc " Some commands like yy, cc, dd, >>, << and !! accept a count after diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 7b6cfa6bb4..2840378b97 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -290,9 +290,10 @@ func Test_set_errors() call assert_fails('set regexpengine=3', 'E474:') call assert_fails('set history=10001', 'E474:') call assert_fails('set numberwidth=21', 'E474:') - call assert_fails('set colorcolumn=-a') - call assert_fails('set colorcolumn=a') - call assert_fails('set colorcolumn=1,') + call assert_fails('set colorcolumn=-a', 'E474:') + call assert_fails('set colorcolumn=a', 'E474:') + call assert_fails('set colorcolumn=1,', 'E474:') + call assert_fails('set colorcolumn=1;', 'E474:') call assert_fails('set cmdheight=-1', 'E487:') call assert_fails('set cmdwinheight=-1', 'E487:') if has('conceal') @@ -343,9 +344,13 @@ func Test_set_errors() call assert_fails('set guicursor=i-ci,r-cr:h', 'E545:') call assert_fails('set guicursor=i-ci', 'E545:') call assert_fails('set guicursor=x', 'E545:') + call assert_fails('set guicursor=x:', 'E546:') call assert_fails('set guicursor=r-cr:horx', 'E548:') call assert_fails('set guicursor=r-cr:hor0', 'E549:') endif + if has('mouseshape') + call assert_fails('se mouseshape=i-r:x', 'E547:') + endif call assert_fails('set backupext=~ patchmode=~', 'E589:') call assert_fails('set winminheight=10 winheight=9', 'E591:') call assert_fails('set winminwidth=10 winwidth=9', 'E592:') @@ -736,7 +741,26 @@ func Test_buftype() call setline(1, ['L1']) set buftype=nowrite call assert_fails('write', 'E382:') - close! + + " for val in ['', 'nofile', 'nowrite', 'acwrite', 'quickfix', 'help', 'terminal', 'prompt', 'popup'] + for val in ['', 'nofile', 'nowrite', 'acwrite', 'quickfix', 'help', 'prompt'] + exe 'set buftype=' .. val + call writefile(['something'], 'XBuftype') + call assert_fails('write XBuftype', 'E13:', 'with buftype=' .. val) + endfor + + call delete('XBuftype') + bwipe! +endfunc + +" Test for the 'shell' option +func Test_shell() + throw 'Skipped: Nvim does not have :shell' + CheckUnix + let save_shell = &shell + set shell= + call assert_fails('shell', 'E91:') + let &shell = save_shell endfunc " Test for the 'shellquote' option diff --git a/src/nvim/testdir/test_plus_arg_edit.vim b/src/nvim/testdir/test_plus_arg_edit.vim index 64533e71cf..c52044d064 100644 --- a/src/nvim/testdir/test_plus_arg_edit.vim +++ b/src/nvim/testdir/test_plus_arg_edit.vim @@ -18,7 +18,7 @@ func Test_edit_bad() e! ++enc=utf8 Xfile call assert_equal('[?][?][???][??]', getline(1)) - e! ++enc=utf8 ++bad=_ Xfile + e! ++encoding=utf8 ++bad=_ Xfile call assert_equal('[_][_][___][__]', getline(1)) e! ++enc=utf8 ++bad=drop Xfile diff --git a/src/nvim/testdir/test_put.vim b/src/nvim/testdir/test_put.vim index a46ef8b3fe..97af3699a8 100644 --- a/src/nvim/testdir/test_put.vim +++ b/src/nvim/testdir/test_put.vim @@ -114,6 +114,16 @@ func Test_put_p_indent_visual() bwipe! endfunc +" Test for deleting all the contents of a buffer with a put +func Test_put_visual_delete_all_lines() + new + call setline(1, ['one', 'two', 'three']) + let @r = '' + normal! VG"rgp + call assert_equal(1, line('$')) + close! +endfunc + func Test_gp_with_count_leaves_cursor_at_end() new call setline(1, '<---->') diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 4f83c45770..5649652fd2 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -499,6 +499,7 @@ func Xtest_browse(cchar) \ 'RegularLine2'] Xfirst + call assert_fails('-5Xcc', 'E16:') call assert_fails('Xprev', 'E553') call assert_fails('Xpfile', 'E553') Xnfile @@ -2891,6 +2892,21 @@ func Test_vimgrep_incsearch() set noincsearch endfunc +" Test vimgrep with the last search pattern not set +func Test_vimgrep_with_no_last_search_pat() + let lines =<< trim [SCRIPT] + call assert_fails('vimgrep // *', 'E35:') + call writefile(v:errors, 'Xresult') + qall! + [SCRIPT] + call writefile(lines, 'Xscript') + if RunVim([], [], '--clean -S Xscript') + call assert_equal([], readfile('Xresult')) + endif + call delete('Xscript') + call delete('Xresult') +endfunc + " Test vimgrep without swap file func Test_vimgrep_without_swap_file() let lines =<< trim [SCRIPT] @@ -4400,6 +4416,20 @@ func Test_splitview() call assert_equal(0, getloclist(0, {'winid' : 0}).winid) new | only + " Using :split or :vsplit from a quickfix window should behave like a :new + " or a :vnew command + copen + split + call assert_equal(3, winnr('$')) + let l = getwininfo() + call assert_equal([0, 0, 1], [l[0].quickfix, l[1].quickfix, l[2].quickfix]) + close + copen + vsplit + let l = getwininfo() + call assert_equal([0, 0, 1], [l[0].quickfix, l[1].quickfix, l[2].quickfix]) + new | only + call delete('Xtestfile1') call delete('Xtestfile2') endfunc diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim index 45e60a6d44..82d250e8b3 100644 --- a/src/nvim/testdir/test_regexp_latin.vim +++ b/src/nvim/testdir/test_regexp_latin.vim @@ -122,7 +122,10 @@ endfunc " Tests for regexp patterns without multi-byte support. func Test_regexp_single_line_pat() " tl is a List of Lists with: - " regexp engine + " regexp engines to test + " 0 - test with 'regexpengine' values 0 and 1 + " 1 - test with 'regexpengine' values 0 and 2 + " 2 - test with 'regexpengine' values 0, 1 and 2 " regexp pattern " text to test the pattern on " expected match (optional) @@ -143,6 +146,8 @@ func Test_regexp_single_line_pat() call add(tl, [2, 'c*', 'abdef', '']) call add(tl, [2, 'bc\+', 'abccccdef', 'bcccc']) call add(tl, [2, 'bc\+', 'abdef']) " no match + " match newline character in a string + call add(tl, [2, 'o\nb', "foo\nbar", "o\nb"]) " operator \| call add(tl, [2, 'a\|ab', 'cabd', 'a']) " alternation is ordered @@ -566,6 +571,9 @@ func Test_regexp_single_line_pat() " Test \%V atom call add(tl, [2, '\%>70vGesamt', 'Jean-Michel Charlier & Victor Hubinon\Gesamtausgabe [Salleck] Buck Danny {Jean-Michel Charlier & Victor Hubinon}\Gesamtausgabe', 'Gesamt']) + " Test for ignoring case and matching repeated characters + call add(tl, [2, '\cb\+', 'aAbBbBcC', 'bBbB']) + " Run the tests for t in tl let re = t[0] @@ -625,6 +633,14 @@ endfunc " Tests for multi-line regexp patterns without multi-byte support. func Test_regexp_multiline_pat() + " tl is a List of Lists with: + " regexp engines to test + " 0 - test with 'regexpengine' values 0 and 1 + " 1 - test with 'regexpengine' values 0 and 2 + " 2 - test with 'regexpengine' values 0, 1 and 2 + " regexp pattern + " List with text to test the pattern on + " List with the expected match let tl = [] " back references @@ -634,6 +650,70 @@ func Test_regexp_multiline_pat() " line breaks call add(tl, [2, '\S.*\nx', ['abc', 'def', 'ghi', 'xjk', 'lmn'], ['abc', 'def', 'XXjk', 'lmn']]) + " Any single character or end-of-line + call add(tl, [2, '\_.\+', ['a', 'b', 'c'], ['XX']]) + " Any identifier or end-of-line + call add(tl, [2, '\_i\+', ['a', 'b', ';', '2'], ['XX;XX']]) + " Any identifier but excluding digits or end-of-line + call add(tl, [2, '\_I\+', ['a', 'b', ';', '2'], ['XX;XX2XX']]) + " Any keyword or end-of-line + call add(tl, [2, '\_k\+', ['a', 'b', '=', '2'], ['XX=XX']]) + " Any keyword but excluding digits or end-of-line + call add(tl, [2, '\_K\+', ['a', 'b', '=', '2'], ['XX=XX2XX']]) + " Any filename character or end-of-line + call add(tl, [2, '\_f\+', ['a', 'b', '.', '5'], ['XX']]) + " Any filename character but excluding digits or end-of-line + call add(tl, [2, '\_F\+', ['a', 'b', '.', '5'], ['XX5XX']]) + " Any printable character or end-of-line + call add(tl, [2, '\_p\+', ['a', 'b', '=', '4'], ['XX']]) + " Any printable character excluding digits or end-of-line + call add(tl, [2, '\_P\+', ['a', 'b', '=', '4'], ['XX4XX']]) + " Any whitespace character or end-of-line + call add(tl, [2, '\_s\+', [' ', ' ', 'a', 'b'], ['XXaXXbXX']]) + " Any non-whitespace character or end-of-line + call add(tl, [2, '\_S\+', [' ', ' ', 'a', 'b'], [' XX XX']]) + " Any decimal digit or end-of-line + call add(tl, [2, '\_d\+', ['1', 'a', '2', 'b', '3'], ['XXaXXbXX']]) + " Any non-decimal digit or end-of-line + call add(tl, [2, '\_D\+', ['1', 'a', '2', 'b', '3'], ['1XX2XX3XX']]) + " Any hexadecimal digit or end-of-line + call add(tl, [2, '\_x\+', ['1', 'a', 'g', '9', '8'], ['XXgXX']]) + " Any non-hexadecimal digit or end-of-line + call add(tl, [2, '\_X\+', ['1', 'a', 'g', '9', '8'], ['1XXaXX9XX8XX']]) + " Any octal digit or end-of-line + call add(tl, [2, '\_o\+', ['0', '7', '8', '9', '0'], ['XX8XX9XX']]) + " Any non-octal digit or end-of-line + call add(tl, [2, '\_O\+', ['0', '7', '8', '9', '0'], ['0XX7XX0XX']]) + " Any word character or end-of-line + call add(tl, [2, '\_w\+', ['A', 'B', '=', 'C', 'D'], ['XX=XX']]) + " Any non-word character or end-of-line + call add(tl, [2, '\_W\+', ['A', 'B', '=', 'C', 'D'], ['AXXBXXCXXDXX']]) + " Any head-of-word character or end-of-line + call add(tl, [2, '\_h\+', ['a', '1', 'b', '2', 'c'], ['XX1XX2XX']]) + " Any non-head-of-word character or end-of-line + call add(tl, [2, '\_H\+', ['a', '1', 'b', '2', 'c'], ['aXXbXXcXX']]) + " Any alphabetic character or end-of-line + call add(tl, [2, '\_a\+', ['a', '1', 'b', '2', 'c'], ['XX1XX2XX']]) + " Any non-alphabetic character or end-of-line + call add(tl, [2, '\_A\+', ['a', '1', 'b', '2', 'c'], ['aXXbXXcXX']]) + " Any lowercase character or end-of-line + call add(tl, [2, '\_l\+', ['a', 'A', 'b', 'B'], ['XXAXXBXX']]) + " Any non-lowercase character or end-of-line + call add(tl, [2, '\_L\+', ['a', 'A', 'b', 'B'], ['aXXbXX']]) + " Any uppercase character or end-of-line + call add(tl, [2, '\_u\+', ['a', 'A', 'b', 'B'], ['aXXbXX']]) + " Any non-uppercase character or end-of-line + call add(tl, [2, '\_U\+', ['a', 'A', 'b', 'B'], ['XXAXXBXX']]) + " Collection or end-of-line + call add(tl, [2, '\_[a-z]\+', ['a', 'A', 'b', 'B'], ['XXAXXBXX']]) + " start of line anywhere in the text + call add(tl, [2, 'one\zs\_s*\_^\zetwo', + \ ['', 'one', ' two', 'one', '', 'two'], + \ ['', 'one', ' two', 'oneXXtwo']]) + " end of line anywhere in the text + call add(tl, [2, 'one\zs\_$\_s*two', + \ ['', 'one', ' two', 'one', '', 'two'], ['', 'oneXX', 'oneXX']]) + " Check that \_[0-9] matching EOL does not break a following \> call add(tl, [2, '\<\(\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\.\)\{3\}\(25\_[0-5]\|2\_[0-4]\_[0-9]\|\_[01]\?\_[0-9]\_[0-9]\?\)\>', ['', 'localnet/192.168.0.1', ''], ['', 'localnet/XX', '']]) @@ -649,7 +729,7 @@ func Test_regexp_multiline_pat() let before = t[2] let after = t[3] for engine in [0, 1, 2] - if engine == 2 && re == 0 || engine == 1 && re ==1 + if engine == 2 && re == 0 || engine == 1 && re == 1 continue endif let ®expengine = engine @@ -697,9 +777,8 @@ func Test_lookbehind_across_line() bwipe! endfunc -" Check matching Visual area -func Test_matching_visual_area() - new +" Test for the \%V atom (match inside the visual area) +func Regex_Match_Visual_Area() call append(0, ['Visual:', 'thexe the thexethe', 'andaxand andaxand', \ 'oooxofor foroxooo', 'oooxofor foroxooo']) call cursor(1, 1) @@ -708,12 +787,22 @@ func Test_matching_visual_area() exe "normal jfx\<C-V>fxj:s/\\%Vo/O/g\<CR>" call assert_equal(['Visual:', 'thexE thE thExethe', 'AndAxAnd AndAxAnd', \ 'oooxOfOr fOrOxooo', 'oooxOfOr fOrOxooo', ''], getline(1, '$')) + %d +endfunc + +" Check matching Visual area +func Test_matching_visual_area() + new + set regexpengine=1 + call Regex_Match_Visual_Area() + set regexpengine=2 + call Regex_Match_Visual_Area() + set regexpengine& bwipe! endfunc " Check matching marks -func Test_matching_marks() - new +func Regex_Mark() call append(0, ['', '', '', 'Marks:', 'asdfSasdfsadfEasdf', 'asdfSas', \ 'dfsadfEasdf', '', '', '', '', '']) call cursor(4, 1) @@ -721,6 +810,15 @@ func Test_matching_marks() exe "normal jfSmsj0fEme:.-4,.+6s/.\\%>'s\\_.*\\%<'e../again/\<CR>" call assert_equal(['', '', '', 'Marks:', 'asdfhereasdf', 'asdfagainasdf', \ '', '', '', '', '', ''], getline(1, '$')) + %d +endfunc + +func Test_matching_marks() + new + set regexpengine=1 + call Regex_Mark() + set regexpengine=2 + call Regex_Mark() bwipe! endfunc @@ -761,8 +859,7 @@ func Test_matching_curpos() endfunc " Test for matching the start and end of a buffer -func Test_start_end_of_buffer_match() - new +func Regex_start_end_buffer() call setline(1, repeat(['vim edit'], 20)) /\%^ call assert_equal([0, 1, 1, 0], getpos('.')) @@ -772,6 +869,15 @@ func Test_start_end_of_buffer_match() call assert_equal([0, 20, 8, 0], getpos('.')) exe "normal 6gg/..\\%$\<CR>" call assert_equal([0, 20, 7, 0], getpos('.')) + %d +endfunc + +func Test_start_end_of_buffer_match() + new + set regexpengine=1 + call Regex_start_end_buffer() + set regexpengine=2 + call Regex_start_end_buffer() bwipe! endfunc @@ -784,10 +890,20 @@ endfunc " Check for detecting error func Test_regexp_error() - set regexpengine=2 - call assert_fails("call matchlist('x x', ' \\ze*')", 'E888:') - call assert_fails("call matchlist('x x', ' \\zs*')", 'E888:') - set re& + call assert_fails("call matchlist('x x', '\\%#=1 \\zs*')", 'E888:') + call assert_fails("call matchlist('x x', '\\%#=1 \\ze*')", 'E888:') + call assert_fails("call matchlist('x x', '\\%#=2 \\zs*')", 'E888:') + call assert_fails("call matchlist('x x', '\\%#=2 \\ze*')", 'E888:') + call assert_fails('exe "normal /\\%#=1\\%[x\\%[x]]\<CR>"', 'E369:') +endfunc + +" Test for using the last substitute string pattern (~) +func Test_regexp_last_subst_string() + new + s/bar/baz/e + call assert_equal(matchstr("foo\nbaz\nbar", "\\%#=1\~"), "baz") + call assert_equal(matchstr("foo\nbaz\nbar", "\\%#=2\~"), "baz") + close! endfunc " Check patterns matching cursor position. diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index b852cfd22f..abe28b77cd 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -416,6 +416,20 @@ func Test_put_reg_restart_mode() bwipe! endfunc +" Test for executing a register using :@ command +func Test_execute_register() + call setreg('r', []) + call assert_beeps('@r') + let i = 1 + let @q = 'let i+= 1' + @q + @ + call assert_equal(3, i) + + " cannot execute a register in operator pending mode + call assert_beeps('normal! c@r') +endfunc + " Test for getting register info func Test_get_reginfo() enew diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 454c956996..747fb0e384 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -19,9 +19,9 @@ func Test_search_cmdline() set noincsearch :1 call feedkeys("/foobar\<cr>", 'tx') - call feedkeys("/the\<cr>",'tx') + call feedkeys("/the\<cr>", 'tx') call assert_equal('the', @/) - call feedkeys("/thes\<C-P>\<C-P>\<cr>",'tx') + call feedkeys("/thes\<C-P>\<C-P>\<cr>", 'tx') call assert_equal('foobar', @/) " Test 2 @@ -655,10 +655,49 @@ func Test_search_cmdline7() bw! endfunc -" Tests for regexp with various magic settings -func Test_search_regexp() - enew! +func Test_search_cmdline8() + " Highlighting is cleared in all windows + " since hls applies to all windows + CheckOption incsearch + CheckFeature terminal + CheckNotGui + if has("win32") + throw "Skipped: Bug with sending <ESC> to terminal window not fixed yet" + endif + + let h = winheight(0) + if h < 3 + return + endif + " Prepare buffer text + let lines = ['abb vim vim vi', 'vimvivim'] + call writefile(lines, 'Xsearch.txt') + let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile', 'Xsearch.txt'], {'term_rows': 3}) + call WaitForAssert({-> assert_equal(lines, [term_getline(buf, 1), term_getline(buf, 2)])}) + + call term_sendkeys(buf, ":set incsearch hlsearch\<cr>") + call term_sendkeys(buf, ":14vsp\<cr>") + call term_sendkeys(buf, "/vim\<cr>") + call term_sendkeys(buf, "/b\<esc>") + call term_sendkeys(buf, "gg0") + call TermWait(buf, 250) + let screen_line = term_scrape(buf, 1) + let [a0,a1,a2,a3] = [screen_line[3].attr, screen_line[4].attr, + \ screen_line[18].attr, screen_line[19].attr] + call assert_notequal(a0, a1) + call assert_notequal(a0, a3) + call assert_notequal(a1, a2) + call assert_equal(a0, a2) + call assert_equal(a1, a3) + " clean up + call delete('Xsearch.txt') + + bwipe! +endfunc + +" Tests for regexp with various magic settings +func Run_search_regexp_magic_opt() put ='1 a aa abb abbccc' exe 'normal! /a*b\{2}c\+/e' . "\<CR>" call assert_equal([0, 2, 17, 0], getpos('.')) @@ -693,6 +732,18 @@ func Test_search_regexp() exe 'normal! /\V[ab]\(\[xy]\)\1' . "\<CR>" call assert_equal([0, 9, 7, 0], getpos('.')) + %d +endfunc + +func Test_search_regexp() + enew! + + set regexpengine=1 + call Run_search_regexp_magic_opt() + set regexpengine=2 + call Run_search_regexp_magic_opt() + set regexpengine& + set undolevels=100 put ='9 foobar' put ='' @@ -700,12 +751,12 @@ func Test_search_regexp() normal G exe 'normal! dv?bar?' . "\<CR>" call assert_equal('9 foo', getline('.')) - call assert_equal([0, 10, 5, 0], getpos('.')) - call assert_equal(10, line('$')) + call assert_equal([0, 2, 5, 0], getpos('.')) + call assert_equal(2, line('$')) normal u call assert_equal('9 foobar', getline('.')) - call assert_equal([0, 10, 6, 0], getpos('.')) - call assert_equal(11, line('$')) + call assert_equal([0, 2, 6, 0], getpos('.')) + call assert_equal(3, line('$')) set undolevels& enew! @@ -1433,7 +1484,7 @@ func Test_large_hex_chars2() endfunc func Test_one_error_msg() - " This was also giving an internal error + " This was also giving an internal error call assert_fails('call search(" \\((\\v[[=P=]]){185}+ ")', 'E871:') endfunc @@ -1478,6 +1529,20 @@ func Test_search_match_at_curpos() close! endfunc +" Test for error cases with the search() function +func Test_search_errors() + call assert_fails("call search('pat', [])", 'E730:') + call assert_fails("call search('pat', 'b', {})", 'E728:') + call assert_fails("call search('pat', 'b', 1, [])", 'E745:') + call assert_fails("call search('pat', 'ns')", 'E475:') + call assert_fails("call search('pat', 'mr')", 'E475:') + + new + call setline(1, ['foo', 'bar']) + call assert_fails('call feedkeys("/foo/;/bar/;\<CR>", "tx")', 'E386:') + bwipe! +endfunc + func Test_search_display_pattern() new call setline(1, ['foo', 'bar', 'foobar']) @@ -1541,6 +1606,132 @@ func Test_search_special() exe "norm /\x80PS" endfunc +" Test for command failures when the last search pattern is not set. +" Need to run this in a new vim instance where last search pattern is not set. +func Test_search_with_no_last_pat() + let lines =<< trim [SCRIPT] + call assert_fails("normal i\<C-R>/\e", 'E35:') + call assert_fails("exe '/'", 'E35:') + call assert_fails("exe '?'", 'E35:') + call assert_fails("/", 'E35:') + call assert_fails("?", 'E35:') + call assert_fails("normal n", 'E35:') + call assert_fails("normal N", 'E35:') + call assert_fails("normal gn", 'E35:') + call assert_fails("normal gN", 'E35:') + call assert_fails("normal cgn", 'E35:') + call assert_fails("normal cgN", 'E35:') + let p = [] + let p = @/ + call assert_equal('', p) + call assert_fails("normal :\<C-R>/", 'E35:') + call assert_fails("//p", 'E35:') + call assert_fails(";//p", 'E35:') + call assert_fails("??p", 'E35:') + call assert_fails(";??p", 'E35:') + call assert_fails('g//p', 'E476:') + call assert_fails('v//p', 'E476:') + call writefile(v:errors, 'Xresult') + qall! + [SCRIPT] + call writefile(lines, 'Xscript') + + if RunVim([], [], '--clean -S Xscript') + call assert_equal([], readfile('Xresult')) + endif + call delete('Xscript') + call delete('Xresult') +endfunc + +" Test for using tilde (~) atom in search. This should use the last used +" substitute pattern +func Test_search_tilde_pat() + let lines =<< trim [SCRIPT] + set regexpengine=1 + call assert_fails('exe "normal /~\<CR>"', 'E33:') + call assert_fails('exe "normal ?~\<CR>"', 'E33:') + set regexpengine=2 + call assert_fails('exe "normal /~\<CR>"', 'E383:') + call assert_fails('exe "normal ?~\<CR>"', 'E383:') + set regexpengine& + call writefile(v:errors, 'Xresult') + qall! + [SCRIPT] + call writefile(lines, 'Xscript') + if RunVim([], [], '--clean -S Xscript') + call assert_equal([], readfile('Xresult')) + endif + call delete('Xscript') + call delete('Xresult') +endfunc + +" Test for searching a pattern that is not present with 'nowrapscan' +func Test_search_pat_not_found() + new + set nowrapscan + let @/ = '1abcxyz2' + call assert_fails('normal n', 'E385:') + call assert_fails('normal N', 'E384:') + set wrapscan& + close +endfunc + +" Test for v:searchforward variable +func Test_searchforward_var() + new + call setline(1, ['foo', '', 'foo']) + call cursor(2, 1) + let @/ = 'foo' + let v:searchforward = 0 + normal N + call assert_equal(3, line('.')) + call cursor(2, 1) + let v:searchforward = 1 + normal N + call assert_equal(1, line('.')) + close! +endfunc + +" Test for invalid regular expressions +func Test_invalid_regexp() + set regexpengine=1 + call assert_fails("call search(repeat('\\(.\\)', 10))", 'E51:') + call assert_fails("call search('\\%(')", 'E53:') + call assert_fails("call search('\\(')", 'E54:') + call assert_fails("call search('\\)')", 'E55:') + call assert_fails("call search('x\\@#')", 'E59:') + call assert_fails('call search(''\v%(%(%(%(%(%(%(%(%(%(%(a){1}){1}){1}){1}){1}){1}){1}){1}){1}){1}){1}'')', 'E60:') + call assert_fails("call search('a\\+*')", 'E61:') + call assert_fails("call search('\\_m')", 'E63:') + call assert_fails("call search('\\+')", 'E64:') + call assert_fails("call search('\\1')", 'E65:') + call assert_fails("call search('\\z\\(\\)')", 'E66:') + call assert_fails("call search('\\z2')", 'E67:') + call assert_fails("call search('\\zx')", 'E68:') + call assert_fails("call search('\\%[ab')", 'E69:') + call assert_fails("call search('\\%[]')", 'E70:') + call assert_fails("call search('\\%a')", 'E71:') + call assert_fails("call search('ab\\%[\\(cd\\)]')", 'E369:') + call assert_fails("call search('ab\\%[\\%(cd\\)]')", 'E369:') + set regexpengine=2 + call assert_fails("call search('\\_')", 'E865:') + call assert_fails("call search('\\+')", 'E866:') + call assert_fails("call search('\\zx')", 'E867:') + call assert_fails("call search('\\%a')", 'E867:') + call assert_fails("call search('x\\@#')", 'E869:') + call assert_fails("call search(repeat('\\(.\\)', 10))", 'E872:') + call assert_fails("call search('\\_m')", 'E877:') + call assert_fails("call search('\\%(')", 'E53:') + call assert_fails("call search('\\(')", 'E54:') + call assert_fails("call search('\\)')", 'E55:') + call assert_fails("call search('\\z\\(\\)')", 'E66:') + call assert_fails("call search('\\%[ab')", 'E69:') + call assert_fails("call search('\\%[]')", 'E70:') + call assert_fails("call search('\\%9999999999999999999999999999v')", 'E951:') + set regexpengine& + call assert_fails("call search('\\%#=3ab')", 'E864:') +endfunc + " Test 'smartcase' with utf-8. func Test_search_smartcase_utf8() new diff --git a/src/nvim/testdir/test_smartindent.vim b/src/nvim/testdir/test_smartindent.vim index f3650a9ac4..e2d028e828 100644 --- a/src/nvim/testdir/test_smartindent.vim +++ b/src/nvim/testdir/test_smartindent.vim @@ -21,9 +21,7 @@ endfunc func Test_smartindent_has_no_effect() new exe "normal! i\<Tab>one\<Esc>" - set noautoindent - set smartindent - set indentexpr= + setlocal noautoindent smartindent indentexpr= exe "normal! Gotwo\<Esc>" call assert_equal("\ttwo", getline("$")) @@ -32,16 +30,13 @@ func Test_smartindent_has_no_effect() call assert_equal("three", getline("$")) delfunction! MyIndent - set autoindent& - set smartindent& - set indentexpr& bwipe! endfunc " Test for inserting '{' and '} with smartindent func Test_smartindent_braces() new - set smartindent shiftwidth=4 + setlocal smartindent shiftwidth=4 call setline(1, [' if (a)', "\tif (b)", "\t return 1"]) normal 2ggO{ normal 3ggA { @@ -57,7 +52,62 @@ func Test_smartindent_braces() \ "\t}", \ ' }' \ ], getline(1, '$')) - set si& sw& ai& + close! +endfunc + +" Test for adding a new line before and after comments with smartindent +func Test_si_add_line_around_comment() + new + setlocal smartindent shiftwidth=4 + call setline(1, [' A', '# comment1', '# comment2']) + exe "normal GoC\<Esc>2GOB" + call assert_equal([' A', ' B', '# comment1', '# comment2', ' C'], + \ getline(1, '$')) + close! +endfunc + +" After a C style comment, indent for a following line should line up with the +" line containing the start of the comment. +func Test_si_indent_after_c_comment() + new + setlocal smartindent shiftwidth=4 fo+=ro + exe "normal i\<C-t>/*\ncomment\n/\n#define FOOBAR\n75\<Esc>ggOabc" + normal 3jOcont + call assert_equal([' abc', ' /*', ' * comment', ' * cont', + \ ' */', '#define FOOBAR', ' 75'], getline(1, '$')) + close! +endfunc + +" Test for indenting a statement after a if condition split across lines +func Test_si_if_cond_split_across_lines() + new + setlocal smartindent shiftwidth=4 + exe "normal i\<C-t>if (cond1 &&\n\<C-t>cond2) {\ni = 10;\n}" + call assert_equal([' if (cond1 &&', "\t cond2) {", "\ti = 10;", + \ ' }'], getline(1, '$')) + close! +endfunc + +" Test for inserting lines before and after a one line comment +func Test_si_one_line_comment() + new + setlocal smartindent shiftwidth=4 + exe "normal i\<C-t>abc;\n\<C-t>/* comment */" + normal oi = 10; + normal kOj = 1; + call assert_equal([' abc;', "\tj = 1;", "\t/* comment */", "\ti = 10;"], + \ getline(1, '$')) + close! +endfunc + +" Test for smartindent with a comment continued across multiple lines +func Test_si_comment_line_continuation() + new + setlocal smartindent shiftwidth=4 + call setline(1, ['# com1', '# com2 \', ' contd', '# com3', ' xyz']) + normal ggOabc + call assert_equal([' abc', '# com1', '# com2 \', ' contd', '# com3', + \ ' xyz'], getline(1, '$')) close! endfunc diff --git a/src/nvim/testdir/test_sort.vim b/src/nvim/testdir/test_sort.vim index 540c73a772..9895ad754c 100644 --- a/src/nvim/testdir/test_sort.vim +++ b/src/nvim/testdir/test_sort.vim @@ -1489,6 +1489,22 @@ func Test_sort_last_search_pat() close! endfunc +" Test for :sort with no last search pattern +func Test_sort_with_no_last_search_pat() + let lines =<< trim [SCRIPT] + call setline(1, ['3b', '1c', '2a']) + call assert_fails('sort //', 'E35:') + call writefile(v:errors, 'Xresult') + qall! + [SCRIPT] + call writefile(lines, 'Xscript') + if RunVim([], [], '--clean -S Xscript') + call assert_equal([], readfile('Xresult')) + endif + call delete('Xscript') + call delete('Xresult') +endfunc + " Test for retaining marks across a :sort func Test_sort_with_marks() new diff --git a/src/nvim/testdir/test_source.vim b/src/nvim/testdir/test_source.vim index b8fe8422b3..ba6fd5ad95 100644 --- a/src/nvim/testdir/test_source.vim +++ b/src/nvim/testdir/test_source.vim @@ -57,3 +57,34 @@ func Test_different_script() call assert_fails('source XtwoScript', 'E121:') call delete('XtwoScript') endfunc + +" When sourcing a vim script, shebang should be ignored. +func Test_source_ignore_shebang() + call writefile(['#!./xyzabc', 'let g:val=369'], 'Xfile.vim') + source Xfile.vim + call assert_equal(g:val, 369) + call delete('Xfile.vim') +endfunc + +" Test for expanding <sfile> in a autocmd and for <slnum> and <sflnum> +func Test_source_autocmd_sfile() + let code =<< trim [CODE] + let g:SfileName = '' + augroup sfiletest + au! + autocmd User UserAutoCmd let g:Sfile = '<sfile>:t' + augroup END + doautocmd User UserAutoCmd + let g:Slnum = expand('<slnum>') + let g:Sflnum = expand('<sflnum>') + augroup! sfiletest + [CODE] + call writefile(code, 'Xscript.vim') + source Xscript.vim + call assert_equal('Xscript.vim', g:Sfile) + call assert_equal('7', g:Slnum) + call assert_equal('8', g:Sflnum) + call delete('Xscript.vim') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index 58f0760f48..7744c5bcca 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -827,6 +827,16 @@ func Test_spell_good_word_invalid() bwipe! endfunc +func Test_spell_good_word_slash() + " This caused E1280. + new + norm afoo / + 1 + norm zG + + bwipe! +endfunc + func LoadAffAndDic(aff_contents, dic_contents) throw 'skipped: Nvim does not support enc=latin1' set enc=latin1 diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index 619b63202a..f795d1c0cf 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -294,7 +294,7 @@ endfunc " Test for *:s%* on :substitute. func Test_sub_cmd_6() - throw "skipped: Nvim removed POSIX-related 'cpoptions' flags" + throw 'Skipped: Nvim does not support cpoptions flag "/"' set magic& set cpo+=/ @@ -808,6 +808,41 @@ func Test_sub_expand_text() close! endfunc +" Test for command failures when the last substitute pattern is not set. +func Test_sub_with_no_last_pat() + let lines =<< trim [SCRIPT] + call assert_fails('~', 'E33:') + call assert_fails('s//abc/g', 'E476:') + call assert_fails('s\/bar', 'E476:') + call assert_fails('s\&bar&', 'E476:') + call writefile(v:errors, 'Xresult') + qall! + [SCRIPT] + call writefile(lines, 'Xscript') + if RunVim([], [], '--clean -S Xscript') + call assert_equal([], readfile('Xresult')) + endif + + " Nvim does not support cpoptions flag "/"' + " let lines =<< trim [SCRIPT] + " set cpo+=/ + " call assert_fails('s/abc/%/', 'E33:') + " call writefile(v:errors, 'Xresult') + " qall! + " [SCRIPT] + " call writefile(lines, 'Xscript') + " if RunVim([], [], '--clean -S Xscript') + " call assert_equal([], readfile('Xresult')) + " endif + + call delete('Xscript') + call delete('Xresult') +endfunc + +func Test_substitute() + call assert_equal('a1a2a3a', substitute('123', '\zs', 'a', 'g')) +endfunc + func Test_submatch_list_concatenate() let pat = 'A\(.\)' let Rep = {-> string([submatch(0, 1)] + [[submatch(1)]])} diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim index b180f27685..923e1cbf50 100644 --- a/src/nvim/testdir/test_swap.vim +++ b/src/nvim/testdir/test_swap.vim @@ -278,7 +278,6 @@ func Test_swap_recover_ext() autocmd SwapExists * let v:swapchoice = 'r' augroup END - " Create a valid swapfile by editing a file with a special extension. split Xtest.scr call setline(1, ['one', 'two', 'three']) @@ -311,6 +310,46 @@ func Test_swap_recover_ext() augroup! test_swap_recover_ext endfunc +" Test for closing a split window automatically when a swap file is detected +" and 'Q' is selected in the confirmation prompt. +func Test_swap_split_win() + autocmd! SwapExists + augroup test_swap_splitwin + autocmd! + autocmd SwapExists * let v:swapchoice = 'q' + augroup END + + " Create a valid swapfile by editing a file with a special extension. + split Xtest.scr + call setline(1, ['one', 'two', 'three']) + write " file is written, not modified + write " write again to make sure the swapfile is created + " read the swapfile as a Blob + let swapfile_name = swapname('%') + let swapfile_bytes = readfile(swapfile_name, 'B') + + " Close and delete the file and recreate the swap file. + quit + call delete('Xtest.scr') + call writefile(swapfile_bytes, swapfile_name) + " Split edit the file again. This should fail to open the window + try + split Xtest.scr + catch + " E308 should be caught, not E306. + call assert_exception('E308:') " Original file may have been changed + endtry + call assert_equal(1, winnr('$')) + + call delete('Xtest.scr') + call delete(swapfile_name) + + augroup test_swap_splitwin + autocmd! + augroup END + augroup! test_swap_splitwin +endfunc + " Test for selecting 'q' in the attention prompt func Test_swap_prompt_splitwin() CheckRunVimInTerminal diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim index 51ab5c1022..9a115da8d3 100644 --- a/src/nvim/testdir/test_tabpage.vim +++ b/src/nvim/testdir/test_tabpage.vim @@ -139,7 +139,11 @@ function Test_tabpage() call assert_fails("tabmove -99", 'E474:') call assert_fails("tabmove -3+", 'E474:') call assert_fails("tabmove $3", 'E474:') + call assert_fails("%tabonly", 'E16:') 1tabonly! + tabnew + call assert_fails("-2tabmove", 'E474:') + tabonly! endfunc " Test autocommands @@ -607,6 +611,16 @@ func Test_tabpage_cmdheight() call delete('XTest_tabpage_cmdheight') endfunc +" Test for closing the tab page from a command window +func Test_tabpage_close_cmdwin() + tabnew + call feedkeys("q/:tabclose\<CR>\<Esc>", 'xt') + call assert_equal(2, tabpagenr('$')) + call feedkeys("q/:tabonly\<CR>\<Esc>", 'xt') + call assert_equal(2, tabpagenr('$')) + tabonly +endfunc + " Return the terminal key code for selecting a tab page from the tabline. This " sequence contains the following codes: a CSI (0x9b), KS_TABLINE (0xf0), " KS_FILLER (0x58) and then the tab page number. diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index 11e32067b2..b1746641ee 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -935,6 +935,11 @@ func Test_tag_multimatch() tag FIRST tnext call assert_equal(2, line('.')) + tlast + tprev + call assert_equal(2, line('.')) + tNext + call assert_equal(1, line('.')) set ignorecase& call delete('Xtags') @@ -1077,6 +1082,213 @@ Type number and <Enter> (q or empty cancels): %bwipe endfunc +" Test for :isearch, :ilist, :ijump and :isplit commands +" Test for [i, ]i, [I, ]I, [ CTRL-I, ] CTRL-I and CTRL-W i commands +func Test_inc_search() + new + call setline(1, ['1:foo', '2:foo', 'foo', '3:foo', '4:foo']) + call cursor(3, 1) + + " Test for [i and ]i + call assert_equal('1:foo', execute('normal [i')) + call assert_equal('2:foo', execute('normal 2[i')) + call assert_fails('normal 3[i', 'E387:') + call assert_equal('3:foo', execute('normal ]i')) + call assert_equal('4:foo', execute('normal 2]i')) + call assert_fails('normal 3]i', 'E389:') + + " Test for :isearch + call assert_equal('1:foo', execute('isearch foo')) + call assert_equal('3:foo', execute('isearch 4 /foo/')) + call assert_fails('isearch 3 foo', 'E387:') + call assert_equal('3:foo', execute('+1,$isearch foo')) + call assert_fails('1,.-1isearch 3 foo', 'E389:') + call assert_fails('isearch bar', 'E389:') + call assert_fails('isearch /foo/3', 'E488:') + + " Test for [I and ]I + call assert_equal([ + \ ' 1: 1 1:foo', + \ ' 2: 2 2:foo', + \ ' 3: 3 foo', + \ ' 4: 4 3:foo', + \ ' 5: 5 4:foo'], split(execute('normal [I'), "\n")) + call assert_equal([ + \ ' 1: 4 3:foo', + \ ' 2: 5 4:foo'], split(execute('normal ]I'), "\n")) + + " Test for :ilist + call assert_equal([ + \ ' 1: 1 1:foo', + \ ' 2: 2 2:foo', + \ ' 3: 3 foo', + \ ' 4: 4 3:foo', + \ ' 5: 5 4:foo'], split(execute('ilist foo'), "\n")) + call assert_equal([ + \ ' 1: 4 3:foo', + \ ' 2: 5 4:foo'], split(execute('+1,$ilist /foo/'), "\n")) + call assert_fails('ilist bar', 'E389:') + + " Test for [ CTRL-I and ] CTRL-I + exe "normal [\t" + call assert_equal([1, 3], [line('.'), col('.')]) + exe "normal 2j4[\t" + call assert_equal([4, 3], [line('.'), col('.')]) + call assert_fails("normal k3[\t", 'E387:') + call assert_fails("normal 6[\t", 'E389:') + exe "normal ]\t" + call assert_equal([4, 3], [line('.'), col('.')]) + exe "normal k2]\t" + call assert_equal([5, 3], [line('.'), col('.')]) + call assert_fails("normal 2k3]\t", 'E389:') + + " Test for :ijump + call cursor(3, 1) + ijump foo + call assert_equal([1, 3], [line('.'), col('.')]) + call cursor(3, 1) + ijump 4 /foo/ + call assert_equal([4, 3], [line('.'), col('.')]) + call cursor(3, 1) + call assert_fails('ijump 3 foo', 'E387:') + +,$ijump 2 foo + call assert_equal([5, 3], [line('.'), col('.')]) + call assert_fails('ijump bar', 'E389:') + + " Test for CTRL-W i + call cursor(3, 1) + wincmd i + call assert_equal([1, 3, 3], [line('.'), col('.'), winnr('$')]) + close + 5wincmd i + call assert_equal([5, 3, 3], [line('.'), col('.'), winnr('$')]) + close + call assert_fails('3wincmd i', 'E387:') + call assert_fails('6wincmd i', 'E389:') + + " Test for :isplit + isplit foo + call assert_equal([1, 3, 3], [line('.'), col('.'), winnr('$')]) + close + isplit 5 /foo/ + call assert_equal([5, 3, 3], [line('.'), col('.'), winnr('$')]) + close + call assert_fails('isplit 3 foo', 'E387:') + call assert_fails('isplit 6 foo', 'E389:') + call assert_fails('isplit bar', 'E389:') + + close! +endfunc + +" this was using a line from ml_get() freed by the regexp +func Test_isearch_copy_line() + new + norm o + norm 0 + 0norm o + sil! norm bc0 + sil! isearch \%') + bwipe! +endfunc + +" Test for :dsearch, :dlist, :djump and :dsplit commands +" Test for [d, ]d, [D, ]D, [ CTRL-D, ] CTRL-D and CTRL-W d commands +func Test_macro_search() + new + call setline(1, ['#define FOO 1', '#define FOO 2', '#define FOO 3', + \ '#define FOO 4', '#define FOO 5']) + call cursor(3, 9) + + " Test for [d and ]d + call assert_equal('#define FOO 1', execute('normal [d')) + call assert_equal('#define FOO 2', execute('normal 2[d')) + call assert_fails('normal 3[d', 'E387:') + call assert_equal('#define FOO 4', execute('normal ]d')) + call assert_equal('#define FOO 5', execute('normal 2]d')) + call assert_fails('normal 3]d', 'E388:') + + " Test for :dsearch + call assert_equal('#define FOO 1', execute('dsearch FOO')) + call assert_equal('#define FOO 5', execute('dsearch 5 /FOO/')) + call assert_fails('dsearch 3 FOO', 'E387:') + call assert_equal('#define FOO 4', execute('+1,$dsearch FOO')) + call assert_fails('1,.-1dsearch 3 FOO', 'E388:') + call assert_fails('dsearch BAR', 'E388:') + + " Test for [D and ]D + call assert_equal([ + \ ' 1: 1 #define FOO 1', + \ ' 2: 2 #define FOO 2', + \ ' 3: 3 #define FOO 3', + \ ' 4: 4 #define FOO 4', + \ ' 5: 5 #define FOO 5'], split(execute('normal [D'), "\n")) + call assert_equal([ + \ ' 1: 4 #define FOO 4', + \ ' 2: 5 #define FOO 5'], split(execute('normal ]D'), "\n")) + + " Test for :dlist + call assert_equal([ + \ ' 1: 1 #define FOO 1', + \ ' 2: 2 #define FOO 2', + \ ' 3: 3 #define FOO 3', + \ ' 4: 4 #define FOO 4', + \ ' 5: 5 #define FOO 5'], split(execute('dlist FOO'), "\n")) + call assert_equal([ + \ ' 1: 4 #define FOO 4', + \ ' 2: 5 #define FOO 5'], split(execute('+1,$dlist /FOO/'), "\n")) + call assert_fails('dlist BAR', 'E388:') + + " Test for [ CTRL-D and ] CTRL-D + exe "normal [\<C-D>" + call assert_equal([1, 9], [line('.'), col('.')]) + exe "normal 2j4[\<C-D>" + call assert_equal([4, 9], [line('.'), col('.')]) + call assert_fails("normal k3[\<C-D>", 'E387:') + call assert_fails("normal 6[\<C-D>", 'E388:') + exe "normal ]\<C-D>" + call assert_equal([4, 9], [line('.'), col('.')]) + exe "normal k2]\<C-D>" + call assert_equal([5, 9], [line('.'), col('.')]) + call assert_fails("normal 2k3]\<C-D>", 'E388:') + + " Test for :djump + call cursor(3, 9) + djump FOO + call assert_equal([1, 9], [line('.'), col('.')]) + call cursor(3, 9) + djump 4 /FOO/ + call assert_equal([4, 9], [line('.'), col('.')]) + call cursor(3, 9) + call assert_fails('djump 3 FOO', 'E387:') + +,$djump 2 FOO + call assert_equal([5, 9], [line('.'), col('.')]) + call assert_fails('djump BAR', 'E388:') + + " Test for CTRL-W d + call cursor(3, 9) + wincmd d + call assert_equal([1, 9, 3], [line('.'), col('.'), winnr('$')]) + close + 5wincmd d + call assert_equal([5, 9, 3], [line('.'), col('.'), winnr('$')]) + close + call assert_fails('3wincmd d', 'E387:') + call assert_fails('6wincmd d', 'E388:') + + " Test for :dsplit + dsplit FOO + call assert_equal([1, 9, 3], [line('.'), col('.'), winnr('$')]) + close + dsplit 5 /FOO/ + call assert_equal([5, 9, 3], [line('.'), col('.'), winnr('$')]) + close + call assert_fails('dsplit 3 FOO', 'E387:') + call assert_fails('dsplit 6 FOO', 'E388:') + call assert_fails('dsplit BAR', 'E388:') + + close! +endfunc + func Test_define_search() " this was accessing freed memory new @@ -1092,6 +1304,37 @@ func Test_define_search() bwipe! endfunc +" Test for [*, [/, ]* and ]/ +func Test_comment_search() + new + call setline(1, ['', '/*', ' *', ' *', ' */']) + normal! 4gg[/ + call assert_equal([2, 1], [line('.'), col('.')]) + normal! 3gg[* + call assert_equal([2, 1], [line('.'), col('.')]) + normal! 3gg]/ + call assert_equal([5, 3], [line('.'), col('.')]) + normal! 3gg]* + call assert_equal([5, 3], [line('.'), col('.')]) + %d + call setline(1, ['', '/*', ' *', ' *']) + call assert_beeps('normal! 3gg]/') + %d + call setline(1, ['', ' *', ' *', ' */']) + call assert_beeps('normal! 4gg[/') + %d + call setline(1, ' /* comment */') + normal! 15|[/ + call assert_equal(9, col('.')) + normal! 15|]/ + call assert_equal(21, col('.')) + call setline(1, ' comment */') + call assert_beeps('normal! 15|[/') + call setline(1, ' /* comment') + call assert_beeps('normal! 15|]/') + close! +endfunc + " Test for the 'taglength' option func Test_tag_length() set tags=Xtags diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim index 748af199b2..f0a0f894c3 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -1137,8 +1137,79 @@ func Test_whichwrap_multi_byte() bwipe! endfunc -func Test_substitute() - call assert_equal('a1a2a3a', substitute('123', '\zs', 'a', 'g')) +" Test for the 'f' flag in 'comments' (only the first line has the comment +" string) +func Test_firstline_comment() + new + setlocal comments=f:- fo+=ro + exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>" + call assert_equal(['A', '- B', ' C', ' D'], getline(1, '$')) + %d + setlocal comments=:- + exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>" + call assert_equal(['- A', '- B', '- C', '- D'], getline(1, '$')) + %bw! +endfunc + +" Test for the 'r' flag in 'comments' (right align comment) +func Test_comment_rightalign() + new + setlocal comments=sr:/***,m:**,ex-2:******/ fo+=ro + exe "normal i=\<C-C>o\t /***\nD\n/" + exe "normal 2GOA\<C-C>joB\<C-C>jOC\<C-C>joE\<C-C>GOF\<C-C>joG" + let expected =<< trim END + = + A + /*** + ** B + ** C + ** D + ** E + ** F + ******/ + G + END + call assert_equal(expected, getline(1, '$')) + %bw! +endfunc + +" Test for the 'b' flag in 'comments' +func Test_comment_blank() + new + setlocal comments=b:* fo+=ro + exe "normal i* E\nF\n\<BS>G\nH\<C-C>ggOC\<C-C>O\<BS>B\<C-C>OA\<C-C>2joD" + let expected =<< trim END + A + *B + * C + * D + * E + * F + *G + H + END + call assert_equal(expected, getline(1, '$')) + %bw! +endfunc + +" Test for the 'n' flag in comments +func Test_comment_nested() + new + setlocal comments=n:> fo+=ro + exe "normal i> B\nD\<C-C>ggOA\<C-C>joC\<C-C>Go\<BS>>>> F\nH" + exe "normal 5GOE\<C-C>6GoG" + let expected =<< trim END + > A + > B + > C + > D + >>>> E + >>>> F + >>>> G + >>>> H + END + call assert_equal(expected, getline(1, '$')) + %bw! endfunc " Test for 'a' and 'w' flags in 'formatoptions' diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index e5b4bc23e8..56a5ec96af 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -7,6 +7,10 @@ source shared.vim source term_util.vim source load.vim +func SetUp() + call timer_stopall() +endfunc + func MyHandler(timer) let g:val += 1 endfunc @@ -15,7 +19,7 @@ func MyHandlerWithLists(lists, timer) let x = string(a:lists) endfunc -func Test_oneshot() +func Test_timer_oneshot() let g:val = 0 let timer = timer_start(50, 'MyHandler') let slept = WaitFor('g:val == 1') @@ -27,7 +31,7 @@ func Test_oneshot() endif endfunc -func Test_repeat_three() +func Test_timer_repeat_three() let g:val = 0 let timer = timer_start(50, 'MyHandler', {'repeat': 3}) let slept = WaitFor('g:val == 3') @@ -39,8 +43,7 @@ func Test_repeat_three() endif endfunc -func Test_repeat_many() - call timer_stopall() +func Test_timer_repeat_many() let g:val = 0 let timer = timer_start(50, 'MyHandler', {'repeat': -1}) if has('mac') @@ -51,7 +54,7 @@ func Test_repeat_many() call assert_inrange((has('mac') ? 1 : 2), LoadAdjust(5), g:val) endfunc -func Test_with_partial_callback() +func Test_timer_with_partial_callback() let g:val = 0 let meow = {'one': 1} function meow.bite(...) @@ -68,13 +71,13 @@ func Test_with_partial_callback() endif endfunc -func Test_retain_partial() +func Test_timer_retain_partial() call timer_start(50, function('MyHandlerWithLists', [['a']])) - call garbagecollect() + call test_garbagecollect_now() sleep 100m endfunc -func Test_info() +func Test_timer_info() let id = timer_start(1000, 'MyHandler') let info = id->timer_info() call assert_equal(id, info[0]['id']) @@ -91,10 +94,11 @@ func Test_info() call timer_stop(id) call assert_equal([], timer_info(id)) + + call assert_fails('call timer_info("abc")', 'E39:') endfunc -func Test_stopall() - call timer_stopall() +func Test_timer_stopall() let id1 = timer_start(1000, 'MyHandler') let id2 = timer_start(2000, 'MyHandler') let info = timer_info() @@ -105,7 +109,7 @@ func Test_stopall() call assert_equal(0, len(info)) endfunc -func Test_paused() +func Test_timer_paused() let g:val = 0 let id = timer_start(50, 'MyHandler') @@ -129,6 +133,8 @@ func Test_paused() else call assert_inrange(0, 10, slept) endif + + call assert_fails('call timer_pause("abc", 1)', 'E39:') endfunc func StopMyself(timer) @@ -138,7 +144,7 @@ func StopMyself(timer) endif endfunc -func Test_delete_myself() +func Test_timer_delete_myself() let g:called = 0 let t = timer_start(10, 'StopMyself', {'repeat': -1}) call WaitForAssert({-> assert_equal(2, g:called)}) @@ -150,33 +156,45 @@ func StopTimer1(timer) let g:timer2 = 10->timer_start('StopTimer2') " avoid maxfuncdepth error call timer_pause(g:timer1, 1) - sleep 40m + sleep 20m endfunc func StopTimer2(timer) call timer_stop(g:timer1) endfunc -func Test_stop_in_callback() +func Test_timer_stop_in_callback() + call assert_equal(0, len(timer_info())) let g:timer1 = timer_start(10, 'StopTimer1') - sleep 40m + let slept = 0 + for i in range(10) + if len(timer_info()) == 0 + break + endif + sleep 10m + let slept += 10 + endfor + " This should take only 30 msec, but on Mac it's often longer + call assert_inrange(0, 50, slept) endfunc func StopTimerAll(timer) call timer_stopall() endfunc -func Test_stop_all_in_callback() - call timer_stopall() - let g:timer1 = timer_start(10, 'StopTimerAll') - let info = timer_info() - call assert_equal(1, len(info)) - if has('mac') - sleep 100m - endif - sleep 40m - let info = timer_info() - call assert_equal(0, len(info)) +func Test_timer_stop_all_in_callback() + call assert_equal(0, len(timer_info())) + call timer_start(10, 'StopTimerAll') + call assert_equal(1, len(timer_info())) + let slept = 0 + for i in range(10) + if len(timer_info()) == 0 + break + endif + sleep 10m + let slept += 10 + endfor + call assert_inrange(0, 30, slept) endfunc func FeedkeysCb(timer) @@ -189,7 +207,7 @@ func InputCb(timer) call Resume() endfunc -func Test_input_in_timer() +func Test_timer_input_in_timer() let g:val = '' call timer_start(10, 'InputCb') call Standby(1000) @@ -211,6 +229,10 @@ func Test_timer_errors() call WaitForAssert({-> assert_equal(3, g:call_count)}) sleep 50m call assert_equal(3, g:call_count) + + call assert_fails('call timer_start(100, "MyHandler", "abc")', 'E475:') + call assert_fails('call timer_start(100, [])', 'E921:') + call assert_fails('call timer_stop("abc")', 'E39:') endfunc func FuncWithCaughtError(timer) @@ -242,7 +264,7 @@ func Interrupt(timer) call nvim_input("\<C-C>") endfunc -func Test_peek_and_get_char() +func Test_timer_peek_and_get_char() if !has('unix') && !has('gui_running') return endif @@ -253,7 +275,7 @@ func Test_peek_and_get_char() eval intr->timer_stop() endfunc -func Test_getchar_zero() +func Test_timer_getchar_zero() if has('win32') && !has('gui_running') " Console: no low-level input return @@ -271,7 +293,7 @@ func Test_getchar_zero() call timer_stop(id) endfunc -func Test_ex_mode() +func Test_timer_ex_mode() " Function with an empty line. func Foo(...) @@ -282,9 +304,9 @@ func Test_ex_mode() call timer_stop(timer) endfunc -func Test_restore_count() +func Test_timer_restore_count() if !CanRunVimInTerminal() - return + throw 'Skipped: cannot run Vim in a terminal window' endif " Check that v:count is saved and restored, not changed by a timer. call writefile([ @@ -315,7 +337,7 @@ endfunc " Test that the garbage collector isn't triggered if a timer callback invokes " vgetc(). -func Test_nocatch_garbage_collect() +func Test_timer_nocatch_garbage_collect() " skipped: Nvim does not support test_garbagecollect_soon(), test_override() return " 'uptimetime. must be bigger than the timer timeout @@ -339,7 +361,7 @@ func Test_nocatch_garbage_collect() delfunc FeedChar endfunc -func Test_error_in_timer_callback() +func Test_timer_error_in_timer_callback() if !has('terminal') || (has('win32') && has('gui_running')) throw 'Skipped: cannot run Vim in a terminal window' endif @@ -374,6 +396,15 @@ func Test_error_in_timer_callback() exe buf .. 'bwipe!' endfunc +" Test for garbage collection when a timer is still running +func Test_timer_garbage_collect() + let timer = timer_start(1000, function('MyHandler'), {'repeat' : 10}) + call test_garbagecollect_now() + let l = timer_info(timer) + call assert_equal(function('MyHandler'), l[0].callback) + call timer_stop(timer) +endfunc + func Test_timer_invalid_callback() call assert_fails('call timer_start(0, "0")', 'E921') endfunc diff --git a/src/nvim/testdir/test_trycatch.vim b/src/nvim/testdir/test_trycatch.vim index 205ed095ea..598402fafe 100644 --- a/src/nvim/testdir/test_trycatch.vim +++ b/src/nvim/testdir/test_trycatch.vim @@ -1996,6 +1996,47 @@ func Test_reload_in_try_catch() call delete('Xreload') endfunc +" Test for errors with :catch, :throw, :finally {{{1 +func Test_try_catch_errors() + call assert_fails('throw |', 'E471:') + call assert_fails("throw \n ", 'E471:') + call assert_fails('catch abc', 'E603:') + call assert_fails('try | let i = 1| finally | catch | endtry', 'E604:') + call assert_fails('finally', 'E606:') + call assert_fails('try | finally | finally | endtry', 'E607:') + " v8.2.3486 has been ported, but v8.2.1183 hasn't, so E170 appears here. + " call assert_fails('try | for i in range(5) | endif | endtry', 'E580:') + call assert_fails('try | for i in range(5) | endif | endtry', 'E170:') + call assert_fails('try | while v:true | endtry', 'E170:') + call assert_fails('try | if v:true | endtry', 'E171:') +endfunc + +" Test for verbose messages with :try :catch, and :finally {{{1 +func Test_try_catch_verbose() + " This test works only when the language is English + if v:lang != "C" && v:lang !~ '^[Ee]n' + return + endif + + set verbose=14 + redir => msg + try + echo i + catch /E121:/ + finally + endtry + redir END + let expected = [ + \ 'Exception thrown: Vim(echo):E121: Undefined variable: i', + \ '', + \ 'Exception caught: Vim(echo):E121: Undefined variable: i', + \ '', + \ 'Exception finished: Vim(echo):E121: Undefined variable: i' + \ ] + call assert_equal(expected, split(msg, "\n")) + set verbose& +endfunc + " Test for using throw in a called function with following error {{{1 func Test_user_command_throw_in_function_call() let lines =<< trim END diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index af92328387..da8bf12318 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -299,6 +299,8 @@ func Test_undo_write() close! call delete('Xtest') bwipe! Xtest + + call assert_fails('earlier xyz', 'E475:') endfunc func Test_insert_expr() diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index f93eb6e274..1323288676 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1771,6 +1771,125 @@ func Test_function_defined_line() call delete('Xtest.vim') endfunc +" Test for missing :endif, :endfor, :endwhile and :endtry {{{1 +func Test_missing_end() + call writefile(['if 2 > 1', 'echo ">"'], 'Xscript') + call assert_fails('source Xscript', 'E171:') + call writefile(['for i in range(5)', 'echo i'], 'Xscript') + call assert_fails('source Xscript', 'E170:') + call writefile(['while v:true', 'echo "."'], 'Xscript') + call assert_fails('source Xscript', 'E170:') + call writefile(['try', 'echo "."'], 'Xscript') + call assert_fails('source Xscript', 'E600:') + call delete('Xscript') + + " Using endfor with :while + let caught_e732 = 0 + try + while v:true + endfor + catch /E732:/ + let caught_e732 = 1 + endtry + call assert_equal(1, caught_e732) + + " Using endwhile with :for + let caught_e733 = 0 + try + for i in range(1) + endwhile + catch /E733:/ + let caught_e733 = 1 + endtry + call assert_equal(1, caught_e733) + + " Missing 'in' in a :for statement + call assert_fails('for i range(1) | endfor', 'E690:') +endfunc + +" Test for deep nesting of if/for/while/try statements {{{1 +func Test_deep_nest() + if !CanRunVimInTerminal() + throw 'Skipped: cannot run vim in terminal' + endif + + let lines =<< trim [SCRIPT] + " Deep nesting of if ... endif + func Test1() + let @a = join(repeat(['if v:true'], 51), "\n") + let @a ..= "\n" + let @a ..= join(repeat(['endif'], 51), "\n") + @a + let @a = '' + endfunc + + " Deep nesting of for ... endfor + func Test2() + let @a = join(repeat(['for i in [1]'], 51), "\n") + let @a ..= "\n" + let @a ..= join(repeat(['endfor'], 51), "\n") + @a + let @a = '' + endfunc + + " Deep nesting of while ... endwhile + func Test3() + let @a = join(repeat(['while v:true'], 51), "\n") + let @a ..= "\n" + let @a ..= join(repeat(['endwhile'], 51), "\n") + @a + let @a = '' + endfunc + + " Deep nesting of try ... endtry + func Test4() + let @a = join(repeat(['try'], 51), "\n") + let @a ..= "\necho v:true\n" + let @a ..= join(repeat(['endtry'], 51), "\n") + @a + let @a = '' + endfunc + [SCRIPT] + call writefile(lines, 'Xscript') + + let buf = RunVimInTerminal('-S Xscript', {'rows': 6}) + + " Deep nesting of if ... endif + call term_sendkeys(buf, ":call Test1()\n") + call WaitForAssert({-> assert_match('^E579:', term_getline(buf, 5))}) + + " Deep nesting of for ... endfor + call term_sendkeys(buf, ":call Test2()\n") + call WaitForAssert({-> assert_match('^E585:', term_getline(buf, 5))}) + + " Deep nesting of while ... endwhile + call term_sendkeys(buf, ":call Test3()\n") + call WaitForAssert({-> assert_match('^E585:', term_getline(buf, 5))}) + + " Deep nesting of try ... endtry + call term_sendkeys(buf, ":call Test4()\n") + call WaitForAssert({-> assert_match('^E601:', term_getline(buf, 5))}) + + "let l = '' + "for i in range(1, 6) + " let l ..= term_getline(buf, i) . "\n" + "endfor + "call assert_report(l) + + call StopVimInTerminal(buf) + call delete('Xscript') +endfunc + +" Test for <sfile>, <slnum> in a function {{{1 +func Test_sfile_in_function() + func Xfunc() + call assert_match('..Test_sfile_in_function\[5]..Xfunc', expand('<sfile>')) + call assert_equal('2', expand('<slnum>')) + endfunc + call Xfunc() + delfunc Xfunc +endfunc + func Test_for_over_string() let res = '' for c in 'aéc̀d' diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 349c4fde50..f77765d415 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -284,6 +284,15 @@ func Test_virtual_replace2() call assert_equal(['abcd', \ 'efgh', \ 'ijkl'], getline(1, '$')) + + " Test for truncating spaces in a newly added line using 'autoindent' if + " characters are not added to that line. + %d_ + call setline(1, [' app', ' bee', ' cat']) + setlocal autoindent + exe "normal gg$gRt\n\nr" + call assert_equal([' apt', '', ' rat'], getline(1, '$')) + " clean up %d_ set bs&vim @@ -1169,8 +1178,8 @@ func Test_exclusive_selection() close! endfunc -" Test for starting visual mode with a count -" This test should be run withou any previous visual modes. So this should be +" Test for starting visual mode with a count. +" This test should be run without any previous visual modes. So this should be " run as a first test. func Test_AAA_start_visual_mode_with_count() new diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index d295e520c7..41b0cd874c 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -172,6 +172,35 @@ func Test_window_split_edit_bufnr() %bw! endfunc +func Test_window_split_no_room() + " N horizontal windows need >= 2*N + 1 lines: + " - 1 line + 1 status line in each window + " - 1 Ex command line + " + " 2*N + 1 <= &lines + " N <= (lines - 1)/2 + " + " Beyond that number of windows, E36: Not enough room is expected. + let hor_win_count = (&lines - 1)/2 + let hor_split_count = hor_win_count - 1 + for s in range(1, hor_split_count) | split | endfor + call assert_fails('split', 'E36:') + + " N vertical windows need >= 2*(N - 1) + 1 columns: + " - 1 column + 1 separator for each window (except last window) + " - 1 column for the last window which does not have separator + " + " 2*(N - 1) + 1 <= &columns + " 2*N - 1 <= &columns + " N <= (&columns + 1)/2 + let ver_win_count = (&columns + 1)/2 + let ver_split_count = ver_win_count - 1 + for s in range(1, ver_split_count) | vsplit | endfor + call assert_fails('vsplit', 'E36:') + + %bw! +endfunc + func Test_window_exchange() e Xa @@ -886,6 +915,155 @@ func Test_floatwin_splitmove() bwipe endfunc +" Test for the :only command +func Test_window_only() + new + set modified + new + call assert_fails('only', 'E445:') + only! + " Test for :only with a count + let wid = win_getid() + new + new + 3only + call assert_equal(1, winnr('$')) + call assert_equal(wid, win_getid()) + call assert_fails('close', 'E444:') + call assert_fails('%close', 'E16:') +endfunc + +" Test for errors with :wincmd +func Test_wincmd_errors() + call assert_fails('wincmd g', 'E474:') + call assert_fails('wincmd ab', 'E474:') +endfunc + +" Test for errors with :winpos +func Test_winpos_errors() + throw 'Skipped: Nvim does not have :winpos' + if !has("gui_running") && !has('win32') + call assert_fails('winpos', 'E188:') + endif + call assert_fails('winpos 10', 'E466:') +endfunc + +" Test for +cmd in a :split command +func Test_split_cmd() + split +set\ readonly + call assert_equal(1, &readonly) + call assert_equal(2, winnr('$')) + close +endfunc + +" Create maximum number of horizontally or vertically split windows and then +" run commands that create a new horizontally/vertically split window +func Run_noroom_for_newwindow_test(dir_arg) + let dir = (a:dir_arg == 'v') ? 'vert ' : '' + + " Open as many windows as possible + for i in range(500) + try + exe dir . 'new' + catch /E36:/ + break + endtry + endfor + + call writefile(['first', 'second', 'third'], 'Xfile1') + call writefile([], 'Xfile2') + call writefile([], 'Xfile3') + + " Argument list related commands + args Xfile1 Xfile2 Xfile3 + next + for cmd in ['sargument 2', 'snext', 'sprevious', 'sNext', 'srewind', + \ 'sfirst', 'slast'] + call assert_fails(dir .. cmd, 'E36:') + endfor + %argdelete + + " Buffer related commands + set modified + hide enew + for cmd in ['sbuffer Xfile1', 'sbnext', 'sbprevious', 'sbNext', 'sbrewind', + \ 'sbfirst', 'sblast', 'sball', 'sbmodified', 'sunhide'] + call assert_fails(dir .. cmd, 'E36:') + endfor + + " Window related commands + for cmd in ['split', 'split Xfile2', 'new', 'new Xfile3', 'sview Xfile1', + \ 'sfind runtest.vim'] + call assert_fails(dir .. cmd, 'E36:') + endfor + + " Help + call assert_fails(dir .. 'help', 'E36:') + call assert_fails(dir .. 'helpgrep window', 'E36:') + + " Command-line window + if a:dir_arg == 'h' + " Cmd-line window is always a horizontally split window + call assert_beeps('call feedkeys("q:\<CR>", "xt")') + endif + + " Quickfix and location list window + if has('quickfix') + cexpr '' + call assert_fails(dir .. 'copen', 'E36:') + lexpr '' + call assert_fails(dir .. 'lopen', 'E36:') + + " Preview window + call assert_fails(dir .. 'pedit Xfile2', 'E36:') + call setline(1, 'abc') + call assert_fails(dir .. 'psearch abc', 'E36:') + endif + + " Window commands (CTRL-W ^ and CTRL-W f) + if a:dir_arg == 'h' + call assert_fails('call feedkeys("\<C-W>^", "xt")', 'E36:') + call setline(1, 'Xfile1') + call assert_fails('call feedkeys("gg\<C-W>f", "xt")', 'E36:') + endif + enew! + + " Tag commands (:stag, :stselect and :stjump) + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "second\tXfile1\t2", + \ "third\tXfile1\t3",], + \ 'Xtags') + set tags=Xtags + call assert_fails(dir .. 'stag second', 'E36:') + call assert_fails('call feedkeys(":" .. dir .. "stselect second\n1\n", "xt")', 'E36:') + call assert_fails(dir .. 'stjump second', 'E36:') + call assert_fails(dir .. 'ptag second', 'E36:') + set tags& + call delete('Xtags') + + " :isplit and :dsplit + call setline(1, ['#define FOO 1', 'FOO']) + normal 2G + call assert_fails(dir .. 'isplit FOO', 'E36:') + call assert_fails(dir .. 'dsplit FOO', 'E36:') + + " terminal + if has('terminal') + call assert_fails(dir .. 'terminal', 'E36:') + endif + + %bwipe! + call delete('Xfile1') + call delete('Xfile2') + call delete('Xfile3') + only +endfunc + +func Test_split_cmds_with_no_room() + call Run_noroom_for_newwindow_test('h') + call Run_noroom_for_newwindow_test('v') +endfunc + func Test_window_resize() throw 'Skipped: Nvim supports cmdheight=0' " Vertical :resize (absolute, relative, min and max size). diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index b42665c9b5..bfbba1f793 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -206,6 +206,8 @@ func Test_write_errors() call assert_fails('1,2write', 'E140:') close! + call assert_fails('w > Xtest', 'E494:') + " Try to overwrite a directory if has('unix') call mkdir('Xdir1') diff --git a/src/nvim/testing.c b/src/nvim/testing.c index 9207ebe73b..69b687e44f 100644 --- a/src/nvim/testing.c +++ b/src/nvim/testing.c @@ -110,9 +110,13 @@ static void ga_concat_shorten_esc(garray_T *gap, const char_u *str) /// Fill "gap" with information about an assert error. static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_str, - typval_T *exp_tv, typval_T *got_tv, assert_type_T atype) + typval_T *exp_tv_arg, typval_T *got_tv_arg, assert_type_T atype) { char_u *tofree; + typval_T *exp_tv = exp_tv_arg; + typval_T *got_tv = got_tv_arg; + bool did_copy = false; + int omitted = 0; if (opt_msg_tv->v_type != VAR_UNKNOWN) { tofree = (char_u *)encode_tv2echo(opt_msg_tv, NULL); @@ -130,6 +134,55 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_s } if (exp_str == NULL) { + // When comparing dictionaries, drop the items that are equal, so that + // it's a lot easier to see what differs. + if (atype != ASSERT_NOTEQUAL + && exp_tv->v_type == VAR_DICT && got_tv->v_type == VAR_DICT + && exp_tv->vval.v_dict != NULL && got_tv->vval.v_dict != NULL) { + dict_T *exp_d = exp_tv->vval.v_dict; + dict_T *got_d = got_tv->vval.v_dict; + + did_copy = true; + exp_tv->vval.v_dict = tv_dict_alloc(); + got_tv->vval.v_dict = tv_dict_alloc(); + + int todo = (int)exp_d->dv_hashtab.ht_used; + for (const hashitem_T *hi = exp_d->dv_hashtab.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + dictitem_T *item2 = tv_dict_find(got_d, (const char *)hi->hi_key, -1); + if (item2 == NULL + || !tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &item2->di_tv, false, false)) { + // item of exp_d not present in got_d or values differ. + const size_t key_len = STRLEN(hi->hi_key); + tv_dict_add_tv(exp_tv->vval.v_dict, (const char *)hi->hi_key, key_len, + &TV_DICT_HI2DI(hi)->di_tv); + if (item2 != NULL) { + tv_dict_add_tv(got_tv->vval.v_dict, (const char *)hi->hi_key, key_len, + &item2->di_tv); + } + } else { + omitted++; + } + todo--; + } + } + + // Add items only present in got_d. + todo = (int)got_d->dv_hashtab.ht_used; + for (const hashitem_T *hi = got_d->dv_hashtab.ht_array; todo > 0; hi++) { + if (!HASHITEM_EMPTY(hi)) { + dictitem_T *item2 = tv_dict_find(exp_d, (const char *)hi->hi_key, -1); + if (item2 == NULL) { + // item of got_d not present in exp_d + const size_t key_len = STRLEN(hi->hi_key); + tv_dict_add_tv(got_tv->vval.v_dict, (const char *)hi->hi_key, key_len, + &TV_DICT_HI2DI(hi)->di_tv); + } + todo--; + } + } + } + tofree = (char_u *)encode_tv2string(exp_tv, NULL); ga_concat_shorten_esc(gap, tofree); xfree(tofree); @@ -148,6 +201,18 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_s tofree = (char_u *)encode_tv2string(got_tv, NULL); ga_concat_shorten_esc(gap, tofree); xfree(tofree); + + if (omitted != 0) { + char buf[100]; + vim_snprintf(buf, sizeof(buf), " - %d equal item%s omitted", omitted, + omitted == 1 ? "" : "s"); + ga_concat(gap, buf); + } + } + + if (did_copy) { + tv_clear(exp_tv); + tv_clear(got_tv); } } @@ -405,15 +470,15 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *const cmd = tv_get_string_chk(&argvars[0]); garray_T ga; int save_trylevel = trylevel; + const int called_emsg_before = called_emsg; // trylevel must be zero for a ":throw" command to be considered failed trylevel = 0; - called_emsg = false; suppress_errthrow = true; emsg_silent = true; do_cmdline_cmd(cmd); - if (!called_emsg) { + if (called_emsg == called_emsg_before) { prepare_assert_error(&ga); ga_concat(&ga, "command did not fail: "); assert_append_cmd_or_arg(&ga, argvars, cmd); @@ -438,7 +503,6 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) } trylevel = save_trylevel; - called_emsg = false; suppress_errthrow = false; emsg_silent = false; emsg_on_display = false; diff --git a/src/nvim/window.c b/src/nvim/window.c index 7895391697..cf10e635b6 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -331,6 +331,7 @@ newwindow: // move window to new tab page case 'T': + CHECK_CMDWIN; if (one_window(curwin)) { msg(_(m_onlyone)); } else { diff --git a/test/functional/legacy/arglist_spec.lua b/test/functional/legacy/arglist_spec.lua index fbb67f9c03..8379e426e0 100644 --- a/test/functional/legacy/arglist_spec.lua +++ b/test/functional/legacy/arglist_spec.lua @@ -1,8 +1,11 @@ -- Test argument list commands local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local clear, command, eq = helpers.clear, helpers.command, helpers.eq local eval, exc_exec, neq = helpers.eval, helpers.exc_exec, helpers.neq +local feed = helpers.feed +local pcall_err = helpers.pcall_err describe('argument list commands', function() before_each(clear) @@ -206,7 +209,6 @@ describe('argument list commands', function() command('%argd') end) - it('test for autocommand that redefines the argument list, when doing ":all"', function() command('autocmd BufReadPost Xxx2 next Xxx2 Xxx1') command("call writefile(['test file Xxx1'], 'Xxx1')") @@ -234,4 +236,45 @@ describe('argument list commands', function() command('argdelete Xxx*') command('bwipe! Xxx1 Xxx2 Xxx3') end) + + it('quitting Vim with unedited files in the argument list throws E173', function() + command('set nomore') + command('args a b c') + eq('Vim(quit):E173: 2 more files to edit', pcall_err(command, 'quit')) + end) + + it(':confirm quit with unedited files in arglist', function() + local screen = Screen.new(60, 6) + screen:attach() + command('set nomore') + command('args a b c') + feed(':confirm quit\n') + screen:expect([[ + | + ~ | + | + :confirm quit | + 2 more files to edit. Quit anyway? | + [Y]es, (N)o: ^ | + ]]) + feed('N') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + ~ | + | + ]]) + feed(':confirm quit\n') + screen:expect([[ + | + ~ | + | + :confirm quit | + 2 more files to edit. Quit anyway? | + [Y]es, (N)o: ^ | + ]]) + feed('Y') + end) end) diff --git a/test/functional/legacy/cpoptions_spec.lua b/test/functional/legacy/cpoptions_spec.lua new file mode 100644 index 0000000000..d2f382ec12 --- /dev/null +++ b/test/functional/legacy/cpoptions_spec.lua @@ -0,0 +1,34 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local command = helpers.command +local feed = helpers.feed + +before_each(clear) + +describe('cpoptions', function() + it('$', function() + local screen = Screen.new(30, 6) + screen:attach() + command('set cpo+=$') + command([[call setline(1, 'one two three')]]) + feed('c2w') + screen:expect([[ + ^one tw$ three | + ~ | + ~ | + ~ | + ~ | + -- INSERT -- | + ]]) + feed('vim<Esc>') + screen:expect([[ + vi^m three | + ~ | + ~ | + ~ | + ~ | + | + ]]) + end) +end) diff --git a/test/functional/legacy/ex_mode_spec.lua b/test/functional/legacy/ex_mode_spec.lua index 244b6bf00f..98f113bbd0 100644 --- a/test/functional/legacy/ex_mode_spec.lua +++ b/test/functional/legacy/ex_mode_spec.lua @@ -1,4 +1,5 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local clear = helpers.clear local command = helpers.command local eq = helpers.eq @@ -39,4 +40,86 @@ describe('Ex mode', function() test_ex_edit('\tm<C-T>n', '\tm<C-T>n') command('set wildchar&') end) + + it('substitute confirmation prompt', function() + command('set noincsearch nohlsearch inccommand=') + local screen = Screen.new(60, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, reverse = true}, -- MsgSeparator + [1] = {foreground = Screen.colors.Brown}, -- LineNr + [2] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + }) + screen:attach() + command([[call setline(1, ['foo foo', 'foo foo', 'foo foo'])]]) + command([[set number]]) + feed('gQ') + screen:expect([[ + {1: 1 }foo foo | + {1: 2 }foo foo | + {1: 3 }foo foo | + {0: }| + Entering Ex mode. Type "visual" to go to Normal mode. | + :^ | + ]]) + + feed('%s/foo/bar/gc<CR>') + screen:expect([[ + {1: 1 }foo foo | + {0: }| + Entering Ex mode. Type "visual" to go to Normal mode. | + :%s/foo/bar/gc | + {1: 1 }foo foo | + ^^^^ | + ]]) + feed('N<CR>') + screen:expect([[ + Entering Ex mode. Type "visual" to go to Normal mode. | + :%s/foo/bar/gc | + {1: 1 }foo foo | + ^^^N | + {1: 1 }foo foo | + ^^^^ | + ]]) + feed('n<CR>') + screen:expect([[ + {1: 1 }foo foo | + ^^^N | + {1: 1 }foo foo | + ^^^n | + {1: 1 }foo foo | + ^^^^ | + ]]) + feed('y<CR>') + + feed('q<CR>') + screen:expect([[ + {1: 1 }foo foo | + ^^^y | + {1: 2 }foo foo | + ^^^q | + {1: 2 }foo foo | + :^ | + ]]) + + -- Pressing enter in ex mode should print the current line + feed('<CR>') + screen:expect([[ + ^^^y | + {1: 2 }foo foo | + ^^^q | + {1: 2 }foo foo | + {1: 3 }foo foo | + :^ | + ]]) + + feed(':vi<CR>') + screen:expect([[ + {1: 1 }foo bar | + {1: 2 }foo foo | + {1: 3 }^foo foo | + {2:~ }| + {2:~ }| + | + ]]) + end) end) diff --git a/test/functional/legacy/excmd_spec.lua b/test/functional/legacy/excmd_spec.lua index 174f7d292e..6b3b265579 100644 --- a/test/functional/legacy/excmd_spec.lua +++ b/test/functional/legacy/excmd_spec.lua @@ -1,9 +1,15 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local clear = helpers.clear +local command = helpers.command local exec_lua = helpers.exec_lua +local feed = helpers.feed local meths = helpers.meths +local poke_eventloop = helpers.poke_eventloop +local read_file = helpers.read_file local source = helpers.source local eq = helpers.eq +local write_file = helpers.write_file local function sizeoflong() if not exec_lua('return pcall(require, "ffi")') then @@ -30,3 +36,153 @@ describe('Ex command', function() ]] end) end) + +it(':confirm command dialog', function() + local screen + + local function start_new() + clear() + screen = Screen.new(60, 20) + screen:attach() + end + + write_file('foo', 'foo1\n') + write_file('bar', 'bar1\n') + + -- Test for saving all the modified buffers + start_new() + command("set nomore") + command("new foo") + command("call setline(1, 'foo2')") + command("new bar") + command("call setline(1, 'bar2')") + command("wincmd b") + feed(':confirm qall\n') + screen:expect([[ + bar2 | + ~ | + ~ | + ~ | + ~ | + ~ | + bar [+] | + foo2 | + ~ | + ~ | + ~ | + ~ | + foo [+] | + | + ~ | + ~ | + | + :confirm qall | + Save changes to "bar"? | + [Y]es, (N)o, Save (A)ll, (D)iscard All, (C)ancel: ^ | + ]]) + feed('A') + poke_eventloop() + + eq('foo2\n', read_file('foo')) + eq('bar2\n', read_file('bar')) + + -- Test for discarding all the changes to modified buffers + start_new() + command("set nomore") + command("new foo") + command("call setline(1, 'foo3')") + command("new bar") + command("call setline(1, 'bar3')") + command("wincmd b") + feed(':confirm qall\n') + screen:expect([[ + bar3 | + ~ | + ~ | + ~ | + ~ | + ~ | + bar [+] | + foo3 | + ~ | + ~ | + ~ | + ~ | + foo [+] | + | + ~ | + ~ | + | + :confirm qall | + Save changes to "bar"? | + [Y]es, (N)o, Save (A)ll, (D)iscard All, (C)ancel: ^ | + ]]) + feed('D') + poke_eventloop() + + eq('foo2\n', read_file('foo')) + eq('bar2\n', read_file('bar')) + + -- Test for saving and discarding changes to some buffers + start_new() + command("set nomore") + command("new foo") + command("call setline(1, 'foo4')") + command("new bar") + command("call setline(1, 'bar4')") + command("wincmd b") + feed(':confirm qall\n') + screen:expect([[ + bar4 | + ~ | + ~ | + ~ | + ~ | + ~ | + bar [+] | + foo4 | + ~ | + ~ | + ~ | + ~ | + foo [+] | + | + ~ | + ~ | + | + :confirm qall | + Save changes to "bar"? | + [Y]es, (N)o, Save (A)ll, (D)iscard All, (C)ancel: ^ | + ]]) + feed('N') + screen:expect([[ + bar4 | + ~ | + ~ | + ~ | + ~ | + ~ | + bar [+] | + foo4 | + ~ | + ~ | + ~ | + ~ | + foo [+] | + | + | + :confirm qall | + Save changes to "bar"? | + | + Save changes to "foo"? | + [Y]es, (N)o, (C)ancel: ^ | + ]]) + feed('Y') + poke_eventloop() + + eq('foo4\n', read_file('foo')) + eq('bar2\n', read_file('bar')) + + os.remove('foo') + os.remove('bar') +end) diff --git a/test/functional/legacy/filechanged_spec.lua b/test/functional/legacy/filechanged_spec.lua index 6eb853d630..ecb861098c 100644 --- a/test/functional/legacy/filechanged_spec.lua +++ b/test/functional/legacy/filechanged_spec.lua @@ -62,7 +62,7 @@ describe('file changed dialog', function() sleep 2 silent !touch Xchanged_d let v:warningmsg = '' - checktime + checktime Xchanged_d call assert_equal('', v:warningmsg) call assert_equal(1, line('$')) call assert_equal('new line', getline(1)) diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index 22589fb107..e065a727f3 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -1848,8 +1848,7 @@ describe("'winhighlight' highlight", function() ]], unchanged=true} end) - - it('works local to the buffer', function() + it('works local to the window', function() insert("aa") command("split") command("setlocal winhl=Normal:Background1") @@ -2240,4 +2239,35 @@ describe("'winhighlight' highlight", function() | ]]} end) + + it('can override StatusLine and StatusLineNC', function() + command('set winhighlight=StatusLine:Background1,StatusLineNC:Background2') + command('split') + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + {1:[No Name] }| + | + {0:~ }| + {5:[No Name] }| + | + ]]) + end) + + it('can override WinBar and WinBarNC #19345', function() + command('setlocal winbar=foobar') + command('set winhighlight=WinBar:Background1,WinBarNC:Background2') + command('split') + screen:expect([[ + {1:foobar }| + ^ | + {0:~ }| + {3:[No Name] }| + {5:foobar }| + | + {4:[No Name] }| + | + ]]) + end) end) diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index e9c0e1b2a1..00f126a1f2 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -1196,6 +1196,53 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim {4:Press ENTER or type command to continue}^ | ]]} end) + + it('prints lines in Ex mode correctly with a burst of carriage returns #19341', function() + command('set number') + meths.buf_set_lines(0, 0, 0, true, {'aaa', 'bbb', 'ccc'}) + command('set display-=msgsep') + feed('gggQ<CR><CR>1<CR><CR>vi') + screen:expect([[ + Entering Ex mode. Type "visual" to go to Normal mode. | + {11: 2 }bbb | + {11: 3 }ccc | + :1 | + {11: 1 }aaa | + {11: 2 }bbb | + :vi^ | + ]]) + feed('<CR>') + screen:expect([[ + {11: 1 }aaa | + {11: 2 }^bbb | + {11: 3 }ccc | + {11: 4 } | + {1:~ }| + {1:~ }| + | + ]]) + command('set display+=msgsep') + feed('gggQ<CR><CR>1<CR><CR>vi') + screen:expect([[ + Entering Ex mode. Type "visual" to go to Normal mode. | + {11: 2 }bbb | + {11: 3 }ccc | + :1 | + {11: 1 }aaa | + {11: 2 }bbb | + :vi^ | + ]]) + feed('<CR>') + screen:expect([[ + {11: 1 }aaa | + {11: 2 }^bbb | + {11: 3 }ccc | + {11: 4 } | + {1:~ }| + {1:~ }| + | + ]]) + end) end) describe('ui/ext_messages', function() diff --git a/test/functional/ui/searchhl_spec.lua b/test/functional/ui/searchhl_spec.lua index c3b9af5e72..c5c88323a2 100644 --- a/test/functional/ui/searchhl_spec.lua +++ b/test/functional/ui/searchhl_spec.lua @@ -5,6 +5,7 @@ local command = helpers.command local feed_command = helpers.feed_command local eq = helpers.eq local eval = helpers.eval +local funcs = helpers.funcs local testprg = helpers.testprg describe('search highlighting', function() @@ -321,100 +322,101 @@ describe('search highlighting', function() end) it('works with incsearch', function() - feed_command('set hlsearch') - feed_command('set incsearch') + command('set hlsearch') + command('set incsearch') + command('set laststatus=0') insert([[ the first line - in a little file - ]]) + in a little file]]) + command('vsplit') feed("gg/li") screen:expect([[ - the first {3:li}ne | - in a {2:li}ttle file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the first {3:li}ne │the first {2:li}ne | + in a {2:li}ttle file │in a {2:li}ttle file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| /li^ | ]]) -- check that consecutive matches are caught by C-g/C-t feed("<C-g>") screen:expect([[ - the first {2:li}ne | - in a {3:li}ttle file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the first {2:li}ne │the first {2:li}ne | + in a {3:li}ttle file │in a {2:li}ttle file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| /li^ | ]]) feed("<C-t>") screen:expect([[ - the first {3:li}ne | - in a {2:li}ttle file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the first {3:li}ne │the first {2:li}ne | + in a {2:li}ttle file │in a {2:li}ttle file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| /li^ | ]]) feed("t") screen:expect([[ - the first line | - in a {3:lit}tle file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the first line │the first line | + in a {3:lit}tle file │in a {2:lit}tle file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| /lit^ | ]]) feed("<cr>") screen:expect([[ - the first line | - in a {2:^lit}tle file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the first line │the first line | + in a {2:^lit}tle file │in a {2:lit}tle file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| /lit | ]]) feed("/fir") screen:expect([[ - the {3:fir}st line | - in a little file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the {3:fir}st line │the {2:fir}st line | + in a little file │in a little file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| /fir^ | ]]) -- incsearch have priority over hlsearch feed("<esc>/ttle") screen:expect([[ - the first line | - in a li{3:ttle} file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the first line │the first line | + in a li{3:ttle} file │in a li{2:ttle} file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| /ttle^ | ]]) -- cancelling search resets to the old search term feed('<esc>') screen:expect([[ - the first line | - in a {2:^lit}tle file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the first line │the first line | + in a {2:^lit}tle file │in a {2:lit}tle file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| | ]]) eq('lit', eval('@/')) @@ -422,91 +424,78 @@ describe('search highlighting', function() -- cancelling inc search restores the hl state feed(':noh<cr>') screen:expect([[ - the first line | - in a ^little file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the first line │the first line | + in a ^little file │in a little file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| :noh | ]]) feed('/first') screen:expect([[ - the {3:first} line | - in a little file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the {3:first} line │the {2:first} line | + in a little file │in a little file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| /first^ | ]]) feed('<esc>') screen:expect([[ - the first line | - in a ^little file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the first line │the first line | + in a ^little file │in a little file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| | ]]) -- test that pressing C-g in an empty command line does not move the cursor - feed('/<C-g>') - screen:expect([[ - the first line | - in a little file | - | - {1:~ }| - {1:~ }| - {1:~ }| - /^ | - ]]) - - -- same, for C-t - feed('<ESC>') - screen:expect([[ - the first line | - in a ^little file | - | - {1:~ }| - {1:~ }| - {1:~ }| - | - ]]) - feed('/<C-t>') - screen:expect([[ - the first line | - in a little file | - | - {1:~ }| - {1:~ }| - {1:~ }| - /^ | - ]]) + feed('gg0') + command([[let @/ = 'i']]) + -- moves to next match of previous search pattern, just like /<cr> + feed('/<c-g><cr>') + eq({0, 1, 6, 0}, funcs.getpos('.')) + -- moves to next match of previous search pattern, just like /<cr> + feed('/<cr>') + eq({0, 1, 12, 0}, funcs.getpos('.')) + -- moves to next match of previous search pattern, just like /<cr> + feed('/<c-t><cr>') + eq({0, 2, 1, 0}, funcs.getpos('.')) -- 8.0.1304, test that C-g and C-t works with incsearch and empty pattern feed('<esc>/fi<CR>') + screen:expect([[ + the {2:fi}rst line │the {2:fi}rst line | + in a little {2:^fi}le │in a little {2:fi}le | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + /fi | + ]]) feed('//') screen:expect([[ - the {3:fi}rst line | - in a little {2:fi}le | - | - {1:~ }| - {1:~ }| - {1:~ }| + the {3:fi}rst line │the {2:fi}rst line | + in a little {2:fi}le │in a little {2:fi}le | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| //^ | ]]) - feed('<C-g>') screen:expect([[ - the {2:fi}rst line | - in a little {3:fi}le | - | - {1:~ }| - {1:~ }| - {1:~ }| + the {2:fi}rst line │the {2:fi}rst line | + in a little {3:fi}le │in a little {2:fi}le | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| //^ | ]]) end) diff --git a/test/functional/vimscript/input_spec.lua b/test/functional/vimscript/input_spec.lua index 14c02f9eb2..554d15e550 100644 --- a/test/functional/vimscript/input_spec.lua +++ b/test/functional/vimscript/input_spec.lua @@ -9,6 +9,7 @@ local source = helpers.source local command = helpers.command local exc_exec = helpers.exc_exec local nvim_async = helpers.nvim_async +local NIL = helpers.NIL local screen @@ -200,6 +201,15 @@ describe('input()', function() feed(':let var = input({"cancelreturn": "BAR"})<CR>') feed('<Esc>') eq('BAR', meths.get_var('var')) + feed(':let var = input({"cancelreturn": []})<CR>') + feed('<Esc>') + eq({}, meths.get_var('var')) + feed(':let var = input({"cancelreturn": v:false})<CR>') + feed('<Esc>') + eq(false, meths.get_var('var')) + feed(':let var = input({"cancelreturn": v:null})<CR>') + feed('<Esc>') + eq(NIL, meths.get_var('var')) end) it('supports default string', function() feed(':let var = input("", "DEF1")<CR>') @@ -220,8 +230,6 @@ describe('input()', function() eq('Vim(call):E730: using List as a String', exc_exec('call input({"prompt": []})')) eq('Vim(call):E730: using List as a String', - exc_exec('call input({"cancelreturn": []})')) - eq('Vim(call):E730: using List as a String', exc_exec('call input({"default": []})')) eq('Vim(call):E730: using List as a String', exc_exec('call input({"completion": []})')) @@ -418,8 +426,6 @@ describe('inputdialog()', function() eq('Vim(call):E730: using List as a String', exc_exec('call inputdialog({"prompt": []})')) eq('Vim(call):E730: using List as a String', - exc_exec('call inputdialog({"cancelreturn": []})')) - eq('Vim(call):E730: using List as a String', exc_exec('call inputdialog({"default": []})')) eq('Vim(call):E730: using List as a String', exc_exec('call inputdialog({"completion": []})')) |