diff options
139 files changed, 5685 insertions, 1125 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index d9067b05e3..530095c40f 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -10,7 +10,7 @@ freebsd_task: timeout_in: 30m install_script: - pkg update -f - - pkg install -y cmake gmake ninja pkgconf unzip wget gettext python libffi git + - pkg install -y cmake gmake ninja unzip wget gettext python git build_deps_script: - gmake deps build_script: diff --git a/.github/scripts/install_deps.sh b/.github/scripts/install_deps.sh index bb99873267..32591eb8da 100755 --- a/.github/scripts/install_deps.sh +++ b/.github/scripts/install_deps.sh @@ -18,7 +18,7 @@ done os=$(uname -s) if [[ $os == Linux ]]; then $SUDO apt-get update - $SUDO apt-get install -y build-essential cmake curl gettext ninja-build pkg-config unzip + $SUDO apt-get install -y build-essential cmake curl gettext ninja-build unzip if [[ -n $TEST ]]; then $SUDO apt-get install -y locales-all cpanminus fi diff --git a/cmake.deps/CMakeLists.txt b/cmake.deps/CMakeLists.txt index 20cb8f31f6..3d835064ba 100644 --- a/cmake.deps/CMakeLists.txt +++ b/cmake.deps/CMakeLists.txt @@ -135,81 +135,23 @@ endif() include(ExternalProject) set_directory_properties(PROPERTIES EP_PREFIX "${DEPS_BUILD_DIR}") -set(LIBUV_URL https://github.com/libuv/libuv/archive/62c2374a8c005ce9e42088965f8f8af2532c177b.tar.gz) -set(LIBUV_SHA256 c7e89137da65a1cb550ba96b892dfeeabea982bf33b9237bcf9bbcd90f2e70a1) - -set(MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/c-6.0.0/msgpack-c-6.0.0.tar.gz) -set(MSGPACK_SHA256 3654f5e2c652dc52e0a993e270bb57d5702b262703f03771c152bba51602aeba) - -# https://github.com/LuaJIT/LuaJIT/tree/v2.1 -set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/224129a8e64bfa219d35cd03055bf03952f167f6.tar.gz) -set(LUAJIT_SHA256 a9bcd9e646e2b188e1d7e3fb594e04c61dda3b332dfd0378d41be19c1eae9d09) - -set(LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz) -set(LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333) - -set(LUAROCKS_URL https://github.com/luarocks/luarocks/archive/v3.9.2.tar.gz) -set(LUAROCKS_SHA256 a0b36cd68586cd79966d0106bb2e5a4f5523327867995fd66bee4237062b3e3b) - -set(UNIBILIUM_URL https://github.com/neovim/unibilium/archive/d72c3598e7ac5d1ebf86ee268b8b4ed95c0fa628.tar.gz) -set(UNIBILIUM_SHA256 9c4747c862ab5e3076dcf8fa8f0ea7a6b50f20ec5905618b9536655596797487) - -set(LIBTERMKEY_URL https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/libtermkey/0.22-1/libtermkey_0.22.orig.tar.gz) -set(LIBTERMKEY_SHA256 6945bd3c4aaa83da83d80a045c5563da4edd7d0374c62c0d35aec09eb3014600) - -set(LIBVTERM_URL https://launchpad.net/libvterm/trunk/v0.3/+download/libvterm-0.3.1.tar.gz) -set(LIBVTERM_SHA256 25a8ad9c15485368dfd0a8a9dca1aec8fea5c27da3fa74ec518d5d3787f0c397) - -set(LUV_URL https://github.com/luvit/luv/archive/093a977b82077591baefe1e880d37dfa2730bd54.tar.gz) -set(LUV_SHA256 222b38b6425f0926218e14e7da81481fdde6f9660c1feac25a53e6fb52e886e6) - -set(LPEG_URL http://www.inf.puc-rio.br/~roberto/lpeg/lpeg-1.0.2.tar.gz) -set(LPEG_SHA256 48d66576051b6c78388faad09b70493093264588fcd0f258ddaab1cdd4a15ffe) - -set(LUA_COMPAT53_URL https://github.com/keplerproject/lua-compat-5.3/archive/v0.9.tar.gz) -set(LUA_COMPAT53_SHA256 ad05540d2d96a48725bb79a1def35cf6652a4e2ec26376e2617c8ce2baa6f416) - -# Windows only: cat.exe tee.exe xxd.exe -set(CAT_URL https://github.com/neovim/deps/raw/21c5e8bdda33521a6ed497b315e03265a2785cbc/opt/cat.exe) -set(CAT_SHA256 93b8d307bb15af3968920bdea3beb869a49d166f9164853c58a4e6ffdcae61c6) -set(TEE_URL https://github.com/neovim/deps/raw/21c5e8bdda33521a6ed497b315e03265a2785cbc/opt/tee.exe) -set(TEE_SHA256 950eea4e17fa3a7e89fa2c55374037b5797b3f1a54fea1304634884ab42ec14d) -set(XXD_URL https://github.com/neovim/deps/raw/21c5e8bdda33521a6ed497b315e03265a2785cbc/opt/xxd.exe) -set(XXD_SHA256 7a581e3882d28161cc52850f9a11d634b3eaf2c029276f093c1ed4c90e45a10c) - -set(WINGUI_URL https://github.com/equalsraf/neovim-qt/releases/download/v0.2.17/neovim-qt.zip) -set(WINGUI_SHA256 502e386eef677c2c2e0c11d8cbb27f3e12b4d96818369417e8da4129c4580c25) - -set(WIN32YANK_X86_64_URL https://github.com/equalsraf/win32yank/releases/download/v0.1.1/win32yank-x64.zip) -set(WIN32YANK_X86_64_SHA256 247c9a05b94387a884b49d3db13f806b1677dfc38020f955f719be6902260cd6) - -set(GETTEXT_URL https://ftp.gnu.org/pub/gnu/gettext/gettext-0.20.1.tar.gz) -set(GETTEXT_SHA256 66415634c6e8c3fa8b71362879ec7575e27da43da562c798a8a2f223e6e47f5c) - -set(LIBICONV_URL https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.15.tar.gz) -set(LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178) - -set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/v0.20.2.tar.gz) -set(TREESITTER_C_SHA256 af66fde03feb0df4faf03750102a0d265b007e5d957057b6b293c13116a70af2 ) -set(TREESITTER_LUA_URL https://github.com/MunifTanjim/tree-sitter-lua/archive/v0.0.14.tar.gz) -set(TREESITTER_LUA_SHA256 930d0370dc15b66389869355c8e14305b9ba7aafd36edbfdb468c8023395016d) -set(TREESITTER_VIM_URL https://github.com/neovim/tree-sitter-vim/archive/v0.3.0.tar.gz) -set(TREESITTER_VIM_SHA256 403acec3efb7cdb18ff3d68640fc823502a4ffcdfbb71cec3f98aa786c21cbe2) -set(TREESITTER_VIMDOC_URL https://github.com/neovim/tree-sitter-vimdoc/archive/v2.0.0.tar.gz) -set(TREESITTER_VIMDOC_SHA256 1ff8f4afd3a9599dd4c3ce87c155660b078c1229704d1a254433e33794b8f274) -set(TREESITTER_QUERY_URL https://github.com/nvim-treesitter/tree-sitter-query/archive/v0.1.0.tar.gz) -set(TREESITTER_QUERY_SHA256 e2b806f80e8bf1c4f4e5a96248393fe6622fc1fc6189d6896d269658f67f914c) -set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/321a652626c63bfea3ea320083a4b14863b80270.tar.gz) -set(TREESITTER_SHA256 8f780289d9524a680e548d891c07dab025241fffdea86f8b55921e4024a84757) - -if(USE_EXISTING_SRC_DIR) - get_cmake_property(VARS VARIABLES) - foreach (VAR ${VARS}) - if(VAR MATCHES "^.*URL$") - unset(${VAR}) +file(READ deps.txt DEPENDENCIES) +STRING(REGEX REPLACE "\n" ";" DEPENDENCIES "${DEPENDENCIES}") +foreach(dep ${DEPENDENCIES}) + STRING(REGEX REPLACE " " ";" dep "${dep}") + list(GET dep 0 name) + list(GET dep 1 value) + + if(name MATCHES "^.*URL$") + mark_as_advanced(${name}) + if(NOT USE_EXISTING_SRC_DIR) + set(${name} ${value} CACHE STRING "") endif() - endforeach() -endif() + elseif(name MATCHES "^.*SHA256$") + set(${name} ${value}) + endif() + +endforeach() if(USE_BUNDLED_UNIBILIUM) include(BuildUnibilium) diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt new file mode 100644 index 0000000000..0f0cf894dc --- /dev/null +++ b/cmake.deps/deps.txt @@ -0,0 +1,64 @@ +LIBUV_URL https://github.com/libuv/libuv/archive/62c2374a8c005ce9e42088965f8f8af2532c177b.tar.gz +LIBUV_SHA256 c7e89137da65a1cb550ba96b892dfeeabea982bf33b9237bcf9bbcd90f2e70a1 + +MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/c-6.0.0/msgpack-c-6.0.0.tar.gz +MSGPACK_SHA256 3654f5e2c652dc52e0a993e270bb57d5702b262703f03771c152bba51602aeba + +LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/224129a8e64bfa219d35cd03055bf03952f167f6.tar.gz +LUAJIT_SHA256 a9bcd9e646e2b188e1d7e3fb594e04c61dda3b332dfd0378d41be19c1eae9d09 + +LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz +LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333 + +LUAROCKS_URL https://github.com/luarocks/luarocks/archive/v3.9.2.tar.gz +LUAROCKS_SHA256 a0b36cd68586cd79966d0106bb2e5a4f5523327867995fd66bee4237062b3e3b + +UNIBILIUM_URL https://github.com/neovim/unibilium/archive/d72c3598e7ac5d1ebf86ee268b8b4ed95c0fa628.tar.gz +UNIBILIUM_SHA256 9c4747c862ab5e3076dcf8fa8f0ea7a6b50f20ec5905618b9536655596797487 + +LIBTERMKEY_URL https://github.com/neovim/deps/raw/master/opt/libtermkey-0.22.tar.gz +LIBTERMKEY_SHA256 6945bd3c4aaa83da83d80a045c5563da4edd7d0374c62c0d35aec09eb3014600 + +LIBVTERM_URL https://github.com/neovim/deps/raw/master/opt/libvterm-0.3.1.tar.gz +LIBVTERM_SHA256 25a8ad9c15485368dfd0a8a9dca1aec8fea5c27da3fa74ec518d5d3787f0c397 + +LUV_URL https://github.com/luvit/luv/archive/093a977b82077591baefe1e880d37dfa2730bd54.tar.gz +LUV_SHA256 222b38b6425f0926218e14e7da81481fdde6f9660c1feac25a53e6fb52e886e6 + +LPEG_URL https://github.com/neovim/deps/raw/master/opt/lpeg-1.0.2.tar.gz +LPEG_SHA256 48d66576051b6c78388faad09b70493093264588fcd0f258ddaab1cdd4a15ffe + +LUA_COMPAT53_URL https://github.com/keplerproject/lua-compat-5.3/archive/v0.9.tar.gz +LUA_COMPAT53_SHA256 ad05540d2d96a48725bb79a1def35cf6652a4e2ec26376e2617c8ce2baa6f416 + +CAT_URL https://github.com/neovim/deps/raw/21c5e8bdda33521a6ed497b315e03265a2785cbc/opt/cat.exe +CAT_SHA256 93b8d307bb15af3968920bdea3beb869a49d166f9164853c58a4e6ffdcae61c6 +TEE_URL https://github.com/neovim/deps/raw/21c5e8bdda33521a6ed497b315e03265a2785cbc/opt/tee.exe +TEE_SHA256 950eea4e17fa3a7e89fa2c55374037b5797b3f1a54fea1304634884ab42ec14d +XXD_URL https://github.com/neovim/deps/raw/21c5e8bdda33521a6ed497b315e03265a2785cbc/opt/xxd.exe +XXD_SHA256 7a581e3882d28161cc52850f9a11d634b3eaf2c029276f093c1ed4c90e45a10c + +WINGUI_URL https://github.com/equalsraf/neovim-qt/releases/download/v0.2.17/neovim-qt.zip +WINGUI_SHA256 502e386eef677c2c2e0c11d8cbb27f3e12b4d96818369417e8da4129c4580c25 + +WIN32YANK_X86_64_URL https://github.com/equalsraf/win32yank/releases/download/v0.1.1/win32yank-x64.zip +WIN32YANK_X86_64_SHA256 247c9a05b94387a884b49d3db13f806b1677dfc38020f955f719be6902260cd6 + +GETTEXT_URL https://ftp.gnu.org/pub/gnu/gettext/gettext-0.20.1.tar.gz +GETTEXT_SHA256 66415634c6e8c3fa8b71362879ec7575e27da43da562c798a8a2f223e6e47f5c + +LIBICONV_URL https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.15.tar.gz +LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178 + +TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/v0.20.2.tar.gz +TREESITTER_C_SHA256 af66fde03feb0df4faf03750102a0d265b007e5d957057b6b293c13116a70af2 +TREESITTER_LUA_URL https://github.com/MunifTanjim/tree-sitter-lua/archive/v0.0.14.tar.gz +TREESITTER_LUA_SHA256 930d0370dc15b66389869355c8e14305b9ba7aafd36edbfdb468c8023395016d +TREESITTER_VIM_URL https://github.com/neovim/tree-sitter-vim/archive/v0.3.0.tar.gz +TREESITTER_VIM_SHA256 403acec3efb7cdb18ff3d68640fc823502a4ffcdfbb71cec3f98aa786c21cbe2 +TREESITTER_VIMDOC_URL https://github.com/neovim/tree-sitter-vimdoc/archive/v2.0.0.tar.gz +TREESITTER_VIMDOC_SHA256 1ff8f4afd3a9599dd4c3ce87c155660b078c1229704d1a254433e33794b8f274 +TREESITTER_QUERY_URL https://github.com/nvim-treesitter/tree-sitter-query/archive/v0.1.0.tar.gz +TREESITTER_QUERY_SHA256 e2b806f80e8bf1c4f4e5a96248393fe6622fc1fc6189d6896d269658f67f914c +TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/321a652626c63bfea3ea320083a4b14863b80270.tar.gz +TREESITTER_SHA256 8f780289d9524a680e548d891c07dab025241fffdea86f8b55921e4024a84757 diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index b37ac117f3..c2dc5ddd5b 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -464,6 +464,8 @@ sign_unplacelist({list}) List unplace a list of signs simplify({filename}) String simplify filename as much as possible sin({expr}) Float sine of {expr} sinh({expr}) Float hyperbolic sine of {expr} +slice({expr}, {start} [, {end}]) String, List or Blob + slice of a String, List or Blob sockconnect({mode}, {address} [, {opts}]) Number Connects to socket sort({list} [, {func} [, {dict}]]) @@ -484,7 +486,7 @@ str2list({expr} [, {utf8}]) List convert each character of {expr} to str2nr({expr} [, {base} [, {quoted}]]) Number convert String to Number strcharlen({expr}) Number character length of the String {expr} -strcharpart({str}, {start} [, {len}]) +strcharpart({str}, {start} [, {len} [, {skipcc}]]) String {len} characters of {str} at character {start} strchars({expr} [, {skipcc}]) Number character count of the String {expr} @@ -7773,6 +7775,19 @@ sinh({expr}) *sinh()* Can also be used as a |method|: > Compute()->sinh() +slice({expr}, {start} [, {end}]) *slice()* + Similar to using a |slice| "expr[start : end]", but "end" is + used exclusive. And for a string the indexes are used as + character indexes instead of byte indexes. + Also, composing characters are not counted. + When {end} is omitted the slice continues to the last item. + When {end} is -1 the last item is omitted. + Returns an empty value if {start} or {end} are invalid. + + Can also be used as a |method|: > + GetList()->slice(offset) + + sockconnect({mode}, {address} [, {opts}]) *sockconnect()* Connect a socket to an address. If {mode} is "pipe" then {address} should be the path of a local domain socket (on @@ -8115,12 +8130,16 @@ strcharlen({string}) *strcharlen()* GetText()->strcharlen() -strcharpart({src}, {start} [, {len}]) *strcharpart()* +strcharpart({src}, {start} [, {len} [, {skipcc}]]) *strcharpart()* Like |strpart()| but using character index and length instead - of byte index and length. Composing characters are counted - separately. + of byte index and length. + When {skipcc} is omitted or zero, composing characters are + counted separately. + When {skipcc} set to 1, Composing characters are ignored, + similar to |slice()|. When a character index is used where a character does not - exist it is assumed to be one character. For example: > + exist it is omitted and counted as one character. For + example: > strcharpart('abc', -1, 2) < results in 'a'. diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 0c18fd5b4e..05fdf2f5bb 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -270,6 +270,9 @@ similar to -1. > :let shortlist = mylist[2:2] " List with one item: [3] :let otherlist = mylist[:] " make a copy of the List +Notice that the last index is inclusive. If you prefer using an exclusive +index use the |slice()| method. + If the first index is beyond the last item of the List or the second item is before the first item, the result is an empty list. There is no error message. @@ -1152,6 +1155,8 @@ text column numbers start with one! Example, to get the byte under the cursor: > :let c = getline(".")[col(".") - 1] +Index zero gives the first byte. Careful: text column numbers start with one! + If the length of the String is less than the index, the result is an empty String. A negative index always results in an empty string (reason: backward compatibility). Use [-1:] to get the last byte. @@ -1176,6 +1181,9 @@ In legacy Vim script the indexes are byte indexes. This doesn't recognize multibyte encodings, see |byteidx()| for computing the indexes. If expr8 is a Number it is first converted to a String. +The item at index expr1b is included, it is inclusive. For an exclusive index +use the |slice()| function. + If expr1a is omitted zero is used. If expr1b is omitted the length of the string minus one is used. diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 01edbe7cbd..e07bfc2209 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -2131,27 +2131,6 @@ start({cmd}, {cmd_args}, {dispatchers}, {extra_spawn_params}) ============================================================================== -Lua module: vim.lsp.sync *lsp-sync* - - *vim.lsp.sync.compute_diff()* -compute_diff({___MissingCloseParenHere___}) - Returns the range table for the difference between prev and curr lines - - Parameters: ~ - • {prev_lines} (table) list of lines - • {curr_lines} (table) list of lines - • {firstline} (integer) line to begin search for first difference - • {lastline} (integer) line to begin search in old_lines for - last difference - • {new_lastline} (integer) line to begin search in new_lines for - last difference - • {offset_encoding} (string) encoding requested by language server - - Return: ~ - (table) TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentContentChangeEvent - - -============================================================================== Lua module: vim.lsp.protocol *lsp-protocol* *vim.lsp.protocol.make_client_capabilities()* diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index f33cffa22e..bc357ac534 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -49,6 +49,9 @@ iterators |luaref-in|. • Added |vim.treesitter.query.omnifunc()| for treesitter query files (set by default). +• |'smoothscroll'| option to scroll by screen line rather than by text line +when |'wrap'| is set. + ============================================================================== CHANGED FEATURES *news-changed* diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index ba73d79cd3..c87b6f1835 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -4254,7 +4254,7 @@ A jump table for the options with a short description can be found at |Q_op|. Warning: Setting this option can make pending mappings to be aborted when the mouse is moved. - *'mousescroll'* + *'mousescroll'* *E5080* 'mousescroll' string (default "ver:3,hor:6") global This option controls the number of lines / columns to scroll by when @@ -5652,6 +5652,16 @@ A jump table for the options with a short description can be found at |Q_op|. option. Also see |ins-expandtab|. When 'expandtab' is not set, the number of spaces is minimized by using <Tab>s. + *'smoothscroll'* *'sms'* *'nosmoothscroll'* *'nosms'* +'smoothscroll' 'sms' boolean (default off) + local to window + Scrolling works with screen lines. When 'wrap' is set and the first + line in the window wraps part of it may not be visible, as if it is + above the window. "<<<" is displayed at the start of the first line, + highlighted with |hl-NonText|. + NOTE: only partly implemented, currently works with CTRL-E, CTRL-Y + and scrolling with the mouse. + *'softtabstop'* *'sts'* 'softtabstop' 'sts' number (default 0) local to buffer @@ -5696,6 +5706,7 @@ A jump table for the options with a short description can be found at |Q_op|. Name of the word list file where words are added for the |zg| and |zw| commands. It must end in ".{encoding}.add". You need to include the path, otherwise the file is placed in the current directory. + The path may include characters from 'isfname', space, comma and '@'. *E765* It may also be a comma-separated list of names. A count before the |zg| and |zw| commands can be used to access each. This allows using diff --git a/runtime/doc/pi_health.txt b/runtime/doc/pi_health.txt index 266c021ecc..bcc933d8b2 100644 --- a/runtime/doc/pi_health.txt +++ b/runtime/doc/pi_health.txt @@ -12,7 +12,7 @@ any other environment conditions that a plugin might care about. Nvim ships with healthchecks for configuration, performance, python support, ruby support, clipboard support, and more. -To run all healthchecks, use: > +To run all healthchecks, use: >vim :checkhealth < @@ -32,17 +32,17 @@ Commands *health-commands* :che[ckhealth] {plugins} Run healthcheck(s) for one or more plugins. E.g. to run only - the standard Nvim healthcheck: > + the standard Nvim healthcheck: >vim :checkhealth nvim < To run the healthchecks for the "foo" and "bar" plugins (assuming they are on 'runtimepath' and they have implemented - the Lua `require("foo.health").check()` interface): > + the Lua `require("foo.health").check()` interface): >vim :checkhealth foo bar < To run healthchecks for Lua submodules, use dot notation or "*" to refer to all submodules. For example Nvim provides - `vim.lsp` and `vim.treesitter`: > + `vim.lsp` and `vim.treesitter`: >vim :checkhealth vim.lsp vim.treesitter :checkhealth vim* < @@ -100,7 +100,7 @@ All such health modules must return a Lua table containing a `check()` function. Copy this sample code into `lua/foo/health.lua`, replacing "foo" in the path -with your plugin name: > +with your plugin name: >lua local M = {} diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index 952f0064e6..c166ecd79d 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -870,6 +870,7 @@ Short explanation of each option: *option-list* 'smartcase' 'scs' no ignore case when pattern has uppercase 'smartindent' 'si' smart autoindenting for C programs 'smarttab' 'sta' use 'shiftwidth' when inserting <Tab> +'smoothscroll' 'sms' scroll by screen lines when 'wrap' is set 'softtabstop' 'sts' number of spaces that <Tab> uses while editing 'spell' enable spell checking 'spellcapcheck' 'spc' pattern to locate end of a sentence diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 8e1b72eadc..9075d60b1b 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -630,6 +630,8 @@ String manipulation: *string-functions* submatch() get a specific match in ":s" and substitute() strpart() get part of a string using byte index strcharpart() get part of a string using char index + slice() take a slice of a string, using char index in + Vim9 script strgetchar() get character from a string using char index expand() expand special keywords expandcmd() expand a command like done for `:edit` @@ -659,6 +661,7 @@ List manipulation: *list-functions* filter() remove selected items from a List map() change each List item reduce() reduce a List to a value + slice() take a slice of a List sort() sort a List reverse() reverse the order of a List or Blob uniq() remove copies of repeated adjacent items diff --git a/runtime/lua/editorconfig.lua b/runtime/lua/editorconfig.lua index 5188c13284..a4024e5e7a 100644 --- a/runtime/lua/editorconfig.lua +++ b/runtime/lua/editorconfig.lua @@ -189,6 +189,7 @@ local function parse(filepath, dir) end elseif key ~= nil and val ~= nil then if key == 'root' then + assert(val == 'true' or val == 'false', 'root must be either "true" or "false"') opts.root = val == 'true' elseif pat and pat:match_str(filepath) then opts[key] = val diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 4fafc4e2e2..5c799b23f2 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -637,6 +637,7 @@ local extension = { nse = 'lua', rockspec = 'lua', lua = 'lua', + luau = 'luau', lrc = 'lyrics', m = function(path, bufnr) return require('vim.filetype.detect').m(bufnr) diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index 03532c33b7..376cac19a7 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -1,7 +1,8 @@ local api = vim.api +local bit = require('bit') local handlers = require('vim.lsp.handlers') local util = require('vim.lsp.util') -local bit = require('bit') +local uv = vim.loop --- @class STTokenRange --- @field line integer line number 0-based @@ -94,15 +95,39 @@ end --- ---@private ---@return STTokenRange[] -local function tokens_to_ranges(data, bufnr, client) +local function tokens_to_ranges(data, bufnr, client, request) local legend = client.server_capabilities.semanticTokensProvider.legend local token_types = legend.tokenTypes local token_modifiers = legend.tokenModifiers + local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) local ranges = {} + local start = uv.hrtime() + local ms_to_ns = 1000 * 1000 + local yield_interval_ns = 5 * ms_to_ns + local co, is_main = coroutine.running() + local line local start_char = 0 for i = 1, #data, 5 do + -- if this function is called from the main coroutine, let it run to completion with no yield + if not is_main then + local elapsed_ns = uv.hrtime() - start + + if elapsed_ns > yield_interval_ns then + vim.schedule(function() + coroutine.resume(co, util.buf_versions[bufnr]) + end) + if request.version ~= coroutine.yield() then + -- request became stale since the last time the coroutine ran. + -- abandon it by yielding without a way to resume + coroutine.yield() + end + + start = uv.hrtime() + end + end + local delta_line = data[i] line = line and line + delta_line or delta_line local delta_start = data[i + 1] @@ -113,11 +138,17 @@ local function tokens_to_ranges(data, bufnr, client) local modifiers = modifiers_from_number(data[i + 4], token_modifiers) ---@private - local function _get_byte_pos(char_pos) - return util._get_line_byte_from_position(bufnr, { - line = line, - character = char_pos, - }, client.offset_encoding) + local function _get_byte_pos(col) + if col > 0 then + local buf_line = lines[line + 1] or '' + local ok, result + ok, result = pcall(util._str_byteindex_enc, buf_line, col, client.offset_encoding) + if ok then + return result + end + return math.min(#buf_line, col) + end + return col end local start_col = _get_byte_pos(start_char) @@ -280,7 +311,7 @@ function STHighlighter:send_request() local c = vim.lsp.get_client_by_id(ctx.client_id) local highlighter = STHighlighter.active[ctx.bufnr] if not err and c and highlighter then - highlighter:process_response(response, c, version) + coroutine.wrap(STHighlighter.process_response)(highlighter, response, c, version) end end, self.bufnr) @@ -315,11 +346,9 @@ function STHighlighter:process_response(response, client, version) return end - -- reset active request - state.active_request = {} - -- skip nil responses if response == nil then + state.active_request = {} return end @@ -347,12 +376,19 @@ function STHighlighter:process_response(response, client, version) tokens = response.data end - -- Update the state with the new results + -- convert token list to highlight ranges + -- this could yield and run over multiple event loop iterations + local highlights = tokens_to_ranges(tokens, self.bufnr, client, state.active_request) + + -- reset active request + state.active_request = {} + + -- update the state with the new results local current_result = state.current_result current_result.version = version current_result.result_id = response.resultId current_result.tokens = tokens - current_result.highlights = tokens_to_ranges(tokens, self.bufnr, client) + current_result.highlights = highlights current_result.namespace_cleared = false -- redraw all windows displaying buffer diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 31af2afb0b..8274361f6d 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -253,12 +253,17 @@ local function get_lines(bufnr, rows) ---@private local function buf_lines() local lines = {} - for _, row in pairs(rows) do + for _, row in ipairs(rows) do lines[row] = (api.nvim_buf_get_lines(bufnr, row, row + 1, false) or { '' })[1] end return lines end + -- use loaded buffers if available + if vim.fn.bufloaded(bufnr) == 1 then + return buf_lines() + end + local uri = vim.uri_from_bufnr(bufnr) -- load the buffer if this is not a file uri @@ -268,11 +273,6 @@ local function get_lines(bufnr, rows) return buf_lines() end - -- use loaded buffers if available - if vim.fn.bufloaded(bufnr) == 1 then - return buf_lines() - end - local filename = api.nvim_buf_get_name(bufnr) -- get the data from the file diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 7df93d1b2e..f6425d7cb9 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -1,3 +1,5 @@ +local ts = vim.treesitter + local Range = require('vim.treesitter._range') local api = vim.api @@ -32,15 +34,58 @@ function FoldInfo:invalidate_range(srow, erow) end end +--- Efficiently remove items from middle of a list a list. +--- +--- Calling table.remove() in a loop will re-index the tail of the table on +--- every iteration, instead this function will re-index the table exactly +--- once. +--- +--- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524 +--- +---@param t any[] +---@param first integer +---@param last integer +local function list_remove(t, first, last) + local n = #t + for i = 0, n - first do + t[first + i] = t[last + 1 + i] + t[last + 1 + i] = nil + end +end + ---@package ---@param srow integer ---@param erow integer function FoldInfo:remove_range(srow, erow) - for i = erow - 1, srow, -1 do - table.remove(self.levels, i + 1) - table.remove(self.levels0, i + 1) - table.remove(self.start_counts, i + 1) - table.remove(self.stop_counts, i + 1) + list_remove(self.levels, srow + 1, erow) + list_remove(self.levels0, srow + 1, erow) + list_remove(self.start_counts, srow + 1, erow) + list_remove(self.stop_counts, srow + 1, erow) +end + +--- Efficiently insert items into the middle of a list. +--- +--- Calling table.insert() in a loop will re-index the tail of the table on +--- every iteration, instead this function will re-index the table exactly +--- once. +--- +--- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524 +--- +---@param t any[] +---@param first integer +---@param last integer +---@param v any +local function list_insert(t, first, last, v) + local n = #t + + -- Shift table forward + for i = n - first, 0, -1 do + t[last + 1 + i] = t[first + i] + end + + -- Fill in new values + for i = first, last do + t[i] = v end end @@ -48,12 +93,10 @@ end ---@param srow integer ---@param erow integer function FoldInfo:add_range(srow, erow) - for i = srow, erow - 1 do - table.insert(self.levels, i + 1, '-1') - table.insert(self.levels0, i + 1, -1) - table.insert(self.start_counts, i + 1, nil) - table.insert(self.stop_counts, i + 1, nil) - end + list_insert(self.levels, srow + 1, erow, '-1') + list_insert(self.levels0, srow + 1, erow, -1) + list_insert(self.start_counts, srow + 1, erow, nil) + list_insert(self.stop_counts, srow + 1, erow, nil) end ---@package @@ -90,21 +133,41 @@ local function trim_level(level) return level end +--- If a parser doesn't have any ranges explicitly set, treesitter will +--- return a range with end_row and end_bytes with a value of UINT32_MAX, +--- so clip end_row to the max buffer line. +--- +--- TODO(lewis6991): Handle this generally +--- +--- @param bufnr integer +--- @param erow integer? +--- @return integer +local function normalise_erow(bufnr, erow) + local max_erow = api.nvim_buf_line_count(bufnr) - 1 + return math.min(erow or max_erow, max_erow) +end + ---@param bufnr integer ---@param info TS.FoldInfo ---@param srow integer? ---@param erow integer? local function get_folds_levels(bufnr, info, srow, erow) srow = srow or 0 - erow = erow or api.nvim_buf_line_count(bufnr) + erow = normalise_erow(bufnr, erow) info:invalidate_range(srow, erow) local prev_start = -1 local prev_stop = -1 - vim.treesitter.get_parser(bufnr):for_each_tree(function(tree, ltree) - local query = vim.treesitter.query.get(ltree:lang(), 'folds') + local parser = ts.get_parser(bufnr) + + if not parser:is_valid() then + return + end + + parser:for_each_tree(function(tree, ltree) + local query = ts.query.get(ltree:lang(), 'folds') if not query then return end @@ -112,9 +175,9 @@ local function get_folds_levels(bufnr, info, srow, erow) -- erow in query is end-exclusive local q_erow = erow and erow + 1 or -1 - for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow or 0, q_erow) do + for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow, q_erow) do if query.captures[id] == 'fold' then - local range = vim.treesitter.get_range(node, bufnr, metadata[id]) + local range = ts.get_range(node, bufnr, metadata[id]) local start, _, stop, stop_col = Range.unpack4(range) if stop_col == 0 then @@ -184,13 +247,25 @@ local function recompute_folds() vim._foldupdate() end +--- Schedule a function only if bufnr is loaded +---@param bufnr integer +---@param fn function +local function schedule_if_loaded(bufnr, fn) + vim.schedule(function() + if not api.nvim_buf_is_loaded(bufnr) then + return + end + fn() + end) +end + ---@param bufnr integer ---@param foldinfo TS.FoldInfo ---@param tree_changes Range4[] local function on_changedtree(bufnr, foldinfo, tree_changes) -- For some reason, queries seem to use the old buffer state in on_bytes. -- Get around this by scheduling and manually updating folds. - vim.schedule(function() + schedule_if_loaded(bufnr, function() for _, change in ipairs(tree_changes) do local srow, _, erow = Range.unpack4(change) get_folds_levels(bufnr, foldinfo, srow, erow) @@ -212,7 +287,7 @@ local function on_bytes(bufnr, foldinfo, start_row, old_row, new_row) foldinfo:remove_range(end_row_new, end_row_old) elseif new_row > old_row then foldinfo:add_range(start_row, end_row_new) - vim.schedule(function() + schedule_if_loaded(bufnr, function() get_folds_levels(bufnr, foldinfo, start_row, end_row_new) recompute_folds() end) @@ -226,7 +301,7 @@ function M.foldexpr(lnum) lnum = lnum or vim.v.lnum local bufnr = api.nvim_get_current_buf() - if not vim.treesitter._has_parser(bufnr) or not lnum then + if not ts._has_parser(bufnr) or not lnum then return '0' end @@ -234,7 +309,7 @@ function M.foldexpr(lnum) foldinfos[bufnr] = FoldInfo.new() get_folds_levels(bufnr, foldinfos[bufnr]) - local parser = vim.treesitter.get_parser(bufnr) + local parser = ts.get_parser(bufnr) parser:register_cbs({ on_changedtree = function(tree_changes) on_changedtree(bufnr, foldinfos[bufnr], tree_changes) diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 19cea32367..1adf6759fa 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -262,7 +262,7 @@ function LanguageTree:parse() tcall(self._parser.parse, self._parser, self._trees[i], self._source, true) -- Pass ranges if this is an initial parse - local cb_changes = self._trees[i] and tree_changes or ranges + local cb_changes = self._trees[i] and tree_changes or tree:included_ranges(true) self:_do_callback('changedtree', cb_changes, tree) self._trees[i] = tree diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 0d10ac4758..b7b9c61123 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -305,6 +305,9 @@ call <SID>Header(gettext("displaying text")) call <SID>AddOption("scroll", gettext("number of lines to scroll for CTRL-U and CTRL-D")) call append("$", "\t" .. s:local_to_window) call <SID>OptionL("scr") +call <SID>AddOption("smoothscroll", gettext("scroll by screen line")) +call append("$", "\t" .. s:local_to_window) +call <SID>BinOptionL("sms") call <SID>AddOption("scrolloff", gettext("number of screen lines to show around the cursor")) call append("$", " \tset so=" . &so) call <SID>AddOption("wrap", gettext("long lines wrap")) diff --git a/runtime/syntax/checkhealth.vim b/runtime/syntax/checkhealth.vim index 4b0ce75a54..ea6555f005 100644 --- a/runtime/syntax/checkhealth.vim +++ b/runtime/syntax/checkhealth.vim @@ -11,15 +11,10 @@ unlet! b:current_syntax syn case match -syn keyword healthError ERROR[:] -syn keyword healthWarning WARNING[:] -syn keyword healthSuccess OK[:] +syn keyword DiagnosticError ERROR[:] +syn keyword DiagnosticWarning WARNING[:] +syn keyword DiagnosticOk OK[:] syn match helpSectionDelim "^======*\n.*$" syn match healthHeadingChar "=" conceal cchar=─ contained containedin=helpSectionDelim -hi def link healthError Error -hi def link healthWarning WarningMsg -hi def healthSuccess guibg=#5fff00 guifg=#080808 ctermbg=82 ctermfg=232 -hi def link healthHelp Identifier - let b:current_syntax = "checkhealth" diff --git a/scripts/bump_deps.lua b/scripts/bump_deps.lua index f980e800cf..6a049d136a 100755 --- a/scripts/bump_deps.lua +++ b/scripts/bump_deps.lua @@ -63,6 +63,7 @@ local function rm_file_if_present(path_to_file) end local nvim_src_dir = vim.fn.getcwd() +local deps_file = nvim_src_dir .. '/' .. 'cmake.deps/deps.txt' local temp_dir = nvim_src_dir .. '/tmp' run({ 'mkdir', '-p', temp_dir }) @@ -127,26 +128,24 @@ end local function write_cmakelists_line(symbol, kind, value) require_executable('sed') - local cmakelists_path = nvim_src_dir .. '/' .. 'cmake.deps/CMakeLists.txt' run_die({ 'sed', '-i', '-e', - 's/set(' + 's/' .. symbol .. '_' .. kind .. '.*$' - .. '/set(' + .. '/' .. symbol .. '_' .. kind .. ' ' .. value - .. ')' .. '/', - cmakelists_path, - }, 'Failed to write ' .. cmakelists_path) + deps_file, + }, 'Failed to write ' .. deps_file) end local function explicit_create_branch(dep) @@ -181,8 +180,6 @@ local function update_cmakelists(dependency, archive, comment) verify_branch(dependency.name) - local changed_file = nvim_src_dir .. '/' .. 'cmake.deps/CMakeLists.txt' - p('Updating ' .. dependency.name .. ' to ' .. archive.url .. '\n') write_cmakelists_line(dependency.symbol, 'URL', archive.url:gsub('/', '\\/')) write_cmakelists_line(dependency.symbol, 'SHA256', archive.sha) @@ -190,7 +187,7 @@ local function update_cmakelists(dependency, archive, comment) { 'git', 'commit', - changed_file, + deps_file, '-m', commit_prefix .. 'bump ' .. dependency.name .. ' to ' .. comment, }, @@ -201,10 +198,9 @@ end local function verify_cmakelists_committed() require_executable('git') - local cmakelists_path = nvim_src_dir .. '/' .. 'cmake.deps/CMakeLists.txt' run_die( - { 'git', 'diff', '--quiet', 'HEAD', '--', cmakelists_path }, - cmakelists_path .. ' has uncommitted changes' + { 'git', 'diff', '--quiet', 'HEAD', '--', deps_file }, + deps_file .. ' has uncommitted changes' ) end diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index e2ab70eca2..1bddd3aa8b 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -927,6 +927,9 @@ local function gen_css(fname) /* Tag pseudo-header common in :help docs. */ .help-tag-right { color: var(--tag-color); + margin-left: auto; + margin-right: 0; + float: right; } h1 .help-tag, h2 .help-tag, h3 .help-tag { font-size: smaller; diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py index d0686e92bc..e9209e219e 100755 --- a/scripts/gen_vimdoc.py +++ b/scripts/gen_vimdoc.py @@ -222,7 +222,6 @@ CONFIG = { 'util.lua', 'log.lua', 'rpc.lua', - 'sync.lua', 'protocol.lua', ], 'files': [ diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c index 284c94a712..74348052b0 100644 --- a/src/nvim/arglist.c +++ b/src/nvim/arglist.c @@ -63,6 +63,8 @@ typedef struct { # include "arglist.c.generated.h" #endif +static const char e_window_layout_changed_unexpectedly[] + = N_("E249: Window layout changed unexpectedly"); static const char e_cannot_change_arglist_recursively[] = N_("E1156: Cannot change the argument list recursively"); @@ -976,7 +978,7 @@ static void arg_all_open_windows(arg_all_state_T *aall, int count) aall->new_curwin = wp; aall->new_curtab = curtab; } else if (wp->w_frame->fr_parent != curwin->w_frame->fr_parent) { - emsg(_("E249: window layout changed unexpectedly")); + emsg(_(e_window_layout_changed_unexpectedly)); i = count; break; } else { diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index b5109b4b21..2d5d8e262b 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -56,6 +56,9 @@ # include "autocmd.c.generated.h" #endif +static const char e_autocommand_nesting_too_deep[] + = N_("E218: Autocommand nesting too deep"); + // Naming Conventions: // - general autocmd behavior start with au_ // - AutoCmd start with aucmd_ @@ -1589,7 +1592,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force // Allow nesting of autocommands, but restrict the depth, because it's // possible to create an endless loop. if (nesting == 10) { - emsg(_("E218: autocommand nesting too deep")); + emsg(_(e_autocommand_nesting_too_deep)); goto BYPASS_AU; } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 02226f3cc4..ce8ee21882 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -192,6 +192,8 @@ typedef struct { #define w_p_rlc w_onebuf_opt.wo_rlc // 'rightleftcmd' long wo_scr; #define w_p_scr w_onebuf_opt.wo_scr // 'scroll' + int wo_sms; +#define w_p_sms w_onebuf_opt.wo_sms // 'smoothscroll' int wo_spell; #define w_p_spell w_onebuf_opt.wo_spell // 'spell' int wo_cuc; @@ -1163,11 +1165,12 @@ struct window_S { bool w_botfill; // true when filler lines are actually // below w_topline (at end of file) bool w_old_botfill; // w_botfill at last redraw - colnr_T w_leftcol; // window column number of the left most + colnr_T w_leftcol; // screen column number of the left most // character in the window; used when // 'wrap' is off - colnr_T w_skipcol; // starting column when a single line - // doesn't fit in the window + colnr_T w_skipcol; // starting screen column for the first + // line in the window; used when 'wrap' is + // on; does not include win_col_off() // six fields that are only used when there is a WinScrolled autocommand linenr_T w_last_topline; ///< last known value for w_topline @@ -1220,6 +1223,7 @@ struct window_S { int w_valid; pos_T w_valid_cursor; // last known position of w_cursor, used to adjust w_valid colnr_T w_valid_leftcol; // last known w_leftcol + colnr_T w_valid_skipcol; // last known w_skipcol bool w_viewport_invalid; linenr_T w_viewport_last_topline; // topline when the viewport was last updated diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c index de56c5c717..84c1276b8b 100644 --- a/src/nvim/bufwrite.c +++ b/src/nvim/bufwrite.c @@ -49,7 +49,16 @@ #include "nvim/undo.h" #include "nvim/vim.h" -static char *err_readonly = "is read-only (cannot override: \"W\" in 'cpoptions')"; +static const char *err_readonly = "is read-only (cannot override: \"W\" in 'cpoptions')"; +static const char e_patchmode_cant_touch_empty_original_file[] + = N_("E206: Patchmode: can't touch empty original file"); +static const char e_write_error_conversion_failed_make_fenc_empty_to_override[] + = N_("E513: Write error, conversion failed (make 'fenc' empty to override)"); +static const char e_write_error_conversion_failed_in_line_nr_make_fenc_empty_to_override[] + = N_("E513: Write error, conversion failed in line %" PRIdLINENR + " (make 'fenc' empty to override)"); +static const char e_write_error_file_system_full[] + = N_("E514: Write error (file system full?)"); static const char e_no_matching_autocommands_for_buftype_str_buffer[] = N_("E676: No matching autocommands for buftype=%s buffer"); @@ -1064,7 +1073,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if (buf->b_ml.ml_mfp == NULL) { // This can happen during startup when there is a stray "w" in the // vimrc file. - emsg(_(e_emptybuf)); + emsg(_(e_empty_buffer)); return FAIL; } @@ -1685,20 +1694,18 @@ restore_backup: if (err.msg == NULL) { if (write_info.bw_conv_error) { if (write_info.bw_conv_error_lnum == 0) { - err = set_err(_("E513: write error, conversion failed " - "(make 'fenc' empty to override)")); + err = set_err(_(e_write_error_conversion_failed_make_fenc_empty_to_override)); } else { err = set_err(xmalloc(300)); err.alloc = true; vim_snprintf(err.msg, 300, // NOLINT(runtime/printf) - _("E513: write error, conversion failed in line %" PRIdLINENR - " (make 'fenc' empty to override)"), + _(e_write_error_conversion_failed_in_line_nr_make_fenc_empty_to_override), write_info.bw_conv_error_lnum); } } else if (got_int) { err = set_err(_(e_interr)); } else { - err = set_err(_("E514: write error (file system full?)")); + err = set_err(_(e_write_error_file_system_full)); } } @@ -1837,7 +1844,7 @@ restore_backup: || (empty_fd = os_open(org, O_CREAT | O_EXCL | O_NOFOLLOW, perm < 0 ? 0666 : (perm & 0777))) < 0) { - emsg(_("E206: patchmode: can't touch empty original file")); + emsg(_(e_patchmode_cant_touch_empty_original_file)); } else { close(empty_fd); } diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 3d4e0abda9..8cae831881 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -858,8 +858,10 @@ bool vim_iswordp_buf(const char *const p, buf_T *const buf) return vim_iswordc_buf(c, buf); } -/// Check that "c" is a valid file-name character. +/// Check that "c" is a valid file-name character as specified with the +/// 'isfname' option. /// Assume characters above 0x100 are valid (multi-byte). +/// To be used for commands like "gf". /// /// @param c character to check bool vim_isfilec(int c) @@ -868,6 +870,14 @@ bool vim_isfilec(int c) return c >= 0x100 || (c > 0 && (g_chartab[c] & CT_FNAME_CHAR)); } +/// Check if "c" is a valid file-name character, including characters left +/// out of 'isfname' to make "gf" work, such as comma, space, '@', etc. +bool vim_is_fname_char(int c) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return vim_isfilec(c) || c == ',' || c == ' ' || c == '@'; +} + /// Check that "c" is a valid file-name character or a wildcard character /// Assume characters above 0x100 are valid (multi-byte). /// Explicitly interpret ']' as a wildcard character as path_has_wildcard("]") diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index 6d90b32545..8ba0b2ffb3 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -115,7 +115,7 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a col = wcol; if ((addspaces || finetune) && !VIsual_active) { - curwin->w_curswant = linetabsize(line) + one_more; + curwin->w_curswant = linetabsize_str(line) + one_more; if (curwin->w_curswant > 0) { curwin->w_curswant--; } @@ -129,7 +129,7 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a && curwin->w_width_inner != 0 && wcol >= (colnr_T)width && width > 0) { - csize = linetabsize(line); + csize = linetabsize_str(line); if (csize > 0) { csize--; } @@ -439,23 +439,27 @@ void adjust_cursor_col(void) } } -/// When curwin->w_leftcol has changed, adjust the cursor position. +/// Set "curwin->w_leftcol" to "leftcol". +/// Adjust the cursor position if needed. /// /// @return true if the cursor was moved. -bool leftcol_changed(void) +bool set_leftcol(colnr_T leftcol) { - // TODO(hinidu): I think it should be colnr_T or int, but p_siso is long. - // Perhaps we can change p_siso to int. - int64_t lastcol; - colnr_T s, e; - bool retval = false; + // Return quickly when there is no change. + if (curwin->w_leftcol == leftcol) { + return false; + } + curwin->w_leftcol = leftcol; changed_cline_bef_curs(); - lastcol = curwin->w_leftcol + curwin->w_width_inner - curwin_col_off() - 1; + // TODO(hinidu): I think it should be colnr_T or int, but p_siso is long. + // Perhaps we can change p_siso to int. + int64_t lastcol = curwin->w_leftcol + curwin->w_width_inner - curwin_col_off() - 1; validate_virtcol(); + bool retval = false; // If the cursor is right or left of the screen, move it to last or first - // character. + // visible character. long siso = get_sidescrolloff_value(curwin); if (curwin->w_virtcol > (colnr_T)(lastcol - siso)) { retval = true; @@ -468,6 +472,7 @@ bool leftcol_changed(void) // If the start of the character under the cursor is not on the screen, // advance the cursor one more char. If this fails (last char of the // line) adjust the scrolling. + colnr_T s, e; getvvcol(curwin, &curwin->w_cursor, &s, NULL, &e); if (e > (colnr_T)lastcol) { retval = true; diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c index f21e632036..428f9f28e4 100644 --- a/src/nvim/cursor_shape.c +++ b/src/nvim/cursor_shape.c @@ -26,6 +26,8 @@ # include "cursor_shape.c.generated.h" #endif +static const char e_digit_expected[] = N_("E548: Digit expected"); + /// Handling of cursor and mouse pointer shapes in various modes. cursorentry_T shape_table[SHAPE_IDX_COUNT] = { // Values are set by 'guicursor' and 'mouseshape'. @@ -101,7 +103,7 @@ Array mode_style_array(Arena *arena) /// @param what SHAPE_CURSOR or SHAPE_MOUSE ('mouseshape') /// /// @returns error message for an illegal option, NULL otherwise. -char *parse_shape_opt(int what) +const char *parse_shape_opt(int what) { char *colonp; char *commap; @@ -194,7 +196,7 @@ char *parse_shape_opt(int what) if (len != 0) { p += len; if (!ascii_isdigit(*p)) { - return N_("E548: digit expected"); + return e_digit_expected; } int n = getdigits_int(&p, false, 0); if (len == 3) { // "ver" or "hor" diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 9bdf6a8255..e6cdf3d60d 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -168,7 +168,7 @@ static void margin_columns_win(win_T *wp, int *left_col, int *right_col) return; } - width1 = wp->w_width - cur_col_off; + width1 = wp->w_width_inner - cur_col_off; width2 = width1 + win_col_off2(wp); *left_col = 0; @@ -595,19 +595,23 @@ static int get_line_number_attr(win_T *wp, winlinevars_T *wlv) static void handle_lnum_col(win_T *wp, winlinevars_T *wlv, int num_signs, int sign_idx, int sign_num_attr, int sign_cul_attr) { + bool has_cpo_n = vim_strchr(p_cpo, CPO_NUMCOL) != NULL; + if ((wp->w_p_nu || wp->w_p_rnu) - && (wlv->row == wlv->startrow + wlv->filler_lines - || vim_strchr(p_cpo, CPO_NUMCOL) == NULL)) { - // If 'signcolumn' is set to 'number' and a sign is present - // in "lnum", then display the sign instead of the line - // number. + && (wlv->row == wlv->startrow + wlv->filler_lines || !has_cpo_n) + // there is no line number in a wrapped line when "n" is in + // 'cpoptions', but 'breakindent' assumes it anyway. + && !((has_cpo_n && !wp->w_p_bri) && wp->w_skipcol > 0 && wlv->lnum == wp->w_topline)) { + // If 'signcolumn' is set to 'number' and a sign is present in "lnum", + // then display the sign instead of the line number. if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u' && num_signs > 0) { get_sign_display_info(true, wp, wlv, sign_idx, sign_cul_attr); } else { // Draw the line number (empty space after wrapping). - if (wlv->row == wlv->startrow + wlv->filler_lines) { + if (wlv->row == wlv->startrow + wlv->filler_lines + && (wp->w_skipcol == 0 || wlv->row > 0 || (wp->w_p_nu && wp->w_p_rnu))) { get_line_number_str(wp, wlv->lnum, wlv->extra, sizeof(wlv->extra)); - if (wp->w_skipcol > 0) { + if (wp->w_skipcol > 0 && wlv->startrow == 0) { for (wlv->p_extra = wlv->extra; *wlv->p_extra == ' '; wlv->p_extra++) { *wlv->p_extra = '-'; } @@ -754,7 +758,7 @@ static void handle_breakindent(win_T *wp, winlinevars_T *wlv) wlv->n_extra = 0; } } - if (wp->w_skipcol > 0 && wp->w_p_wrap && wp->w_briopt_sbr) { + if (wp->w_skipcol > 0 && wlv->startrow == 0 && wp->w_p_wrap && wp->w_briopt_sbr) { wlv->need_showbreak = false; } // Correct end of highlighted area for 'breakindent', @@ -804,7 +808,7 @@ static void handle_showbreak_and_filler(win_T *wp, winlinevars_T *wlv) wlv->c_final = NUL; wlv->n_extra = (int)strlen(sbr); wlv->char_attr = win_hl_attr(wp, HLF_AT); - if (wp->w_skipcol == 0 || !wp->w_p_wrap) { + if (wp->w_skipcol == 0 || wlv->startrow != 0 || !wp->w_p_wrap) { wlv->need_showbreak = false; } wlv->vcol_sbr = wlv->vcol + mb_charlen(sbr); @@ -1379,7 +1383,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the // first character to be displayed. if (wp->w_p_wrap) { - v = wp->w_skipcol; + v = startrow == 0 ? wp->w_skipcol : 0; } else { v = wp->w_leftcol; } @@ -2595,7 +2599,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (c == NUL) { // Highlight 'cursorcolumn' & 'colorcolumn' past end of the line. if (wp->w_p_wrap) { - v = wp->w_skipcol; + v = wlv.startrow == 0 ? wp->w_skipcol : 0; } else { v = wp->w_leftcol; } diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index b5e516005b..ec5163f37a 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -1447,6 +1447,26 @@ static void win_update(win_T *wp, DecorProviders *providers) init_search_hl(wp, &screen_search_hl); + // Make sure skipcol is valid, it depends on various options and the window + // width. + if (wp->w_skipcol > 0) { + int w = 0; + int width1 = wp->w_width_inner - win_col_off(wp); + int width2 = width1 + win_col_off2(wp); + int add = width1; + + while (w < wp->w_skipcol) { + if (w > 0) { + add = width2; + } + w += add; + } + if (w != wp->w_skipcol) { + // always round down, the higher value may not be valid + wp->w_skipcol = w - add; + } + } + // Force redraw when width of 'number' or 'relativenumber' column // changes. int nrwidth = (wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc) ? number_width(wp) : 0; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 0fb1102f4f..2078fc4251 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -193,7 +193,7 @@ static void insert_enter(InsertState *s) } } - Insstart_textlen = (colnr_T)linetabsize(get_cursor_line_ptr()); + Insstart_textlen = linetabsize_str(get_cursor_line_ptr()); Insstart_blank_vcol = MAXCOL; if (!did_ai) { @@ -2251,7 +2251,7 @@ int stop_arrow(void) // right, except when nothing was inserted yet. update_Insstart_orig = false; } - Insstart_textlen = (colnr_T)linetabsize(get_cursor_line_ptr()); + Insstart_textlen = linetabsize_str(get_cursor_line_ptr()); if (u_save_cursor() == OK) { arrow_used = false; @@ -2449,6 +2449,7 @@ void beginline(int flags) } curwin->w_set_curswant = true; } + adjust_skipcol(); } // oneright oneleft cursor_down cursor_up @@ -2490,6 +2491,7 @@ int oneright(void) curwin->w_cursor.col += l; curwin->w_set_curswant = true; + adjust_skipcol(); return OK; } @@ -2538,6 +2540,7 @@ int oneleft(void) // if the character on the left of the current cursor is a multi-byte // character, move to its first byte mb_adjust_cursor(); + adjust_skipcol(); return OK; } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 1dfa853fd3..13299b0253 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -88,10 +88,15 @@ static const char *e_missbrac = N_("E111: Missing ']'"); static const char *e_list_end = N_("E697: Missing end of List ']': %s"); -static const char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary"); +static const char *e_cannot_slice_dictionary + = N_("E719: Cannot slice a Dictionary"); +static const char e_cannot_index_special_variable[] + = N_("E909: Cannot index a special variable"); static const char *e_nowhitespace = N_("E274: No white space allowed before parenthesis"); static const char *e_write2 = N_("E80: Error while writing: %s"); +static const char e_variable_nested_too_deep_for_making_copy[] + = N_("E698: Variable nested too deep for making a copy"); static const char *e_string_list_or_blob_required = N_("E1098: String, List or Blob required"); static const char e_expression_too_recursive_str[] = N_("E1169: Expression too recursive: %s"); static const char e_dot_can_only_be_used_on_dictionary_str[] @@ -1438,7 +1443,7 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const if (*p == ':') { if (lp->ll_tv->v_type == VAR_DICT) { if (!quiet) { - emsg(_(e_dictrange)); + emsg(_(e_cannot_slice_dictionary)); } tv_clear(&var1); return NULL; @@ -1501,18 +1506,16 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const // g: dictionary). Disallow overwriting a builtin function. if (rettv != NULL && lp->ll_dict->dv_scope != 0) { char prevval; - int wrong; - if (len != -1) { prevval = key[len]; key[len] = NUL; } else { prevval = 0; // Avoid compiler warning. } - wrong = ((lp->ll_dict->dv_scope == VAR_DEF_SCOPE - && tv_is_func(*rettv) - && var_wrong_func_name(key, lp->ll_di == NULL)) - || !valid_varname(key)); + bool wrong = ((lp->ll_dict->dv_scope == VAR_DEF_SCOPE + && tv_is_func(*rettv) + && var_wrong_func_name(key, lp->ll_di == NULL)) + || !valid_varname(key)); if (len != -1) { key[len] = prevval; } @@ -1610,7 +1613,7 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const if (lp->ll_li == NULL) { tv_clear(&var2); if (!quiet) { - semsg(_(e_listidx), (int64_t)lp->ll_n1); + semsg(_(e_list_index_out_of_range_nr), (int64_t)lp->ll_n1); } return NULL; } @@ -1626,7 +1629,7 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const listitem_T *ni = tv_list_find(lp->ll_list, (int)lp->ll_n2); if (ni == NULL) { if (!quiet) { - semsg(_(e_listidx), (int64_t)lp->ll_n2); + semsg(_(e_list_index_out_of_range_nr), (int64_t)lp->ll_n2); } return NULL; } @@ -1639,7 +1642,7 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const } if (lp->ll_n2 < lp->ll_n1) { if (!quiet) { - semsg(_(e_listidx), (int64_t)lp->ll_n2); + semsg(_(e_list_index_out_of_range_nr), (int64_t)lp->ll_n2); } return NULL; } @@ -1689,7 +1692,7 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool lp->ll_n2 = tv_blob_len(lp->ll_blob) - 1; } - if (tv_blob_set_range(lp->ll_blob, (int)lp->ll_n1, (int)lp->ll_n2, rettv) == FAIL) { + if (tv_blob_set_range(lp->ll_blob, lp->ll_n1, lp->ll_n2, rettv) == FAIL) { return; } } else { @@ -2362,7 +2365,17 @@ int eval0(char *arg, typval_T *rettv, exarg_T *eap, evalarg_T *const evalarg) semsg(_(e_invexpr2), arg); } } - ret = FAIL; + + if (eap != NULL && p != NULL) { + // Some of the expression may not have been consumed. + // Only execute a next command if it cannot be a "||" operator. + // The next command may be "catch". + char *nextcmd = check_nextcmd(p); + if (nextcmd != NULL && *nextcmd != '|') { + eap->nextcmd = nextcmd; + } + } + return FAIL; } if (eap != NULL) { @@ -3469,39 +3482,12 @@ static int eval_index(char **arg, typval_T *rettv, evalarg_T *const evalarg, boo const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE); bool empty1 = false; bool empty2 = false; - ptrdiff_t len = -1; - int range = false; + bool range = false; const char *key = NULL; + ptrdiff_t keylen = -1; - switch (rettv->v_type) { - case VAR_FUNC: - case VAR_PARTIAL: - if (verbose) { - emsg(_("E695: Cannot index a Funcref")); - } + if (check_can_index(rettv, evaluate, verbose) == FAIL) { return FAIL; - case VAR_FLOAT: - if (verbose) { - emsg(_(e_float_as_string)); - } - return FAIL; - case VAR_BOOL: - case VAR_SPECIAL: - if (verbose) { - emsg(_("E909: Cannot index a special variable")); - } - return FAIL; - case VAR_UNKNOWN: - if (evaluate) { - return FAIL; - } - FALLTHROUGH; - case VAR_STRING: - case VAR_NUMBER: - case VAR_LIST: - case VAR_DICT: - case VAR_BLOB: - break; } typval_T var1 = TV_INITIAL_VALUE; @@ -3509,11 +3495,11 @@ static int eval_index(char **arg, typval_T *rettv, evalarg_T *const evalarg, boo if (**arg == '.') { // dict.name key = *arg + 1; - for (len = 0; eval_isdictc(key[len]); len++) {} - if (len == 0) { + for (keylen = 0; eval_isdictc(key[keylen]); keylen++) {} + if (keylen == 0) { return FAIL; } - *arg = skipwhite(key + len); + *arg = skipwhite(key + keylen); } else { // something[idx] // @@ -3565,195 +3551,197 @@ static int eval_index(char **arg, typval_T *rettv, evalarg_T *const evalarg, boo } if (evaluate) { - int n2 = 0; - int n1 = 0; - if (!empty1 && rettv->v_type != VAR_DICT && !tv_is_luafunc(rettv)) { - n1 = (int)tv_get_number(&var1); + int res = eval_index_inner(rettv, range, + empty1 ? NULL : &var1, empty2 ? NULL : &var2, false, + key, keylen, verbose); + if (!empty1) { tv_clear(&var1); } if (range) { - if (empty2) { - n2 = -1; - } else { - n2 = (int)tv_get_number(&var2); - tv_clear(&var2); - } + tv_clear(&var2); } + return res; + } + return OK; +} - switch (rettv->v_type) { - case VAR_NUMBER: - case VAR_STRING: { - const char *const s = tv_get_string(rettv); - char *v; - len = (ptrdiff_t)strlen(s); - if (range) { - // The resulting variable is a substring. If the indexes - // are out of range the result is empty. - if (n1 < 0) { - n1 = (int)len + n1; - if (n1 < 0) { - n1 = 0; - } - } - if (n2 < 0) { - n2 = (int)len + n2; - } else if (n2 >= len) { - n2 = (int)len; - } - if (n1 >= len || n2 < 0 || n1 > n2) { - v = NULL; - } else { - v = xmemdupz(s + n1, (size_t)n2 - (size_t)n1 + 1); - } - } else { - // The resulting variable is a string of a single - // character. If the index is too big or negative the - // result is empty. - if (n1 >= len || n1 < 0) { - v = NULL; - } else { - v = xmemdupz(s + n1, 1); - } +/// Check if "rettv" can have an [index] or [sli:ce] +static int check_can_index(typval_T *rettv, bool evaluate, bool verbose) +{ + switch (rettv->v_type) { + case VAR_FUNC: + case VAR_PARTIAL: + if (verbose) { + emsg(_("E695: Cannot index a Funcref")); + } + return FAIL; + case VAR_FLOAT: + if (verbose) { + emsg(_(e_using_float_as_string)); + } + return FAIL; + case VAR_BOOL: + case VAR_SPECIAL: + if (verbose) { + emsg(_(e_cannot_index_special_variable)); + } + return FAIL; + case VAR_UNKNOWN: + if (evaluate) { + emsg(_(e_cannot_index_special_variable)); + return FAIL; + } + FALLTHROUGH; + case VAR_STRING: + case VAR_NUMBER: + case VAR_LIST: + case VAR_DICT: + case VAR_BLOB: + break; + } + return OK; +} + +/// slice() function +void f_slice(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + if (check_can_index(argvars, true, false) == OK) { + tv_copy(argvars, rettv); + eval_index_inner(rettv, true, argvars + 1, + argvars[2].v_type == VAR_UNKNOWN ? NULL : argvars + 2, + true, NULL, 0, false); + } +} + +/// Apply index or range to "rettv". +/// +/// @param var1 the first index, NULL for [:expr]. +/// @param var2 the second index, NULL for [expr] and [expr: ] +/// @param exclusive true for slice(): second index is exclusive, use character +/// index for string. +/// Alternatively, "key" is not NULL, then key[keylen] is the dict index. +static int eval_index_inner(typval_T *rettv, bool is_range, typval_T *var1, typval_T *var2, + bool exclusive, const char *key, ptrdiff_t keylen, bool verbose) +{ + varnumber_T n1 = 0; + varnumber_T n2 = 0; + if (var1 != NULL && rettv->v_type != VAR_DICT) { + n1 = tv_get_number(var1); + } + + if (is_range) { + if (rettv->v_type == VAR_DICT) { + if (verbose) { + emsg(_(e_cannot_slice_dictionary)); } - tv_clear(rettv); - rettv->v_type = VAR_STRING; - rettv->vval.v_string = v; - break; + return FAIL; } - case VAR_BLOB: - len = tv_blob_len(rettv->vval.v_blob); - if (range) { - // The resulting variable is a sub-blob. If the indexes - // are out of range the result is empty. - if (n1 < 0) { - n1 = (int)len + n1; - if (n1 < 0) { - n1 = 0; - } - } - if (n2 < 0) { - n2 = (int)len + n2; - } else if (n2 >= len) { - n2 = (int)len - 1; - } - if (n1 >= len || n2 < 0 || n1 > n2) { - tv_clear(rettv); - rettv->v_type = VAR_BLOB; - rettv->vval.v_blob = NULL; - } else { - blob_T *const blob = tv_blob_alloc(); - ga_grow(&blob->bv_ga, n2 - n1 + 1); - blob->bv_ga.ga_len = n2 - n1 + 1; - for (long i = n1; i <= n2; i++) { - tv_blob_set(blob, (int)(i - n1), tv_blob_get(rettv->vval.v_blob, (int)i)); - } - tv_clear(rettv); - tv_blob_set_ret(rettv, blob); - } + if (var2 != NULL) { + n2 = tv_get_number(var2); + } else { + n2 = VARNUMBER_MAX; + } + } + + switch (rettv->v_type) { + case VAR_BOOL: + case VAR_SPECIAL: + case VAR_FUNC: + case VAR_FLOAT: + case VAR_PARTIAL: + case VAR_UNKNOWN: + break; // Not evaluating, skipping over subscript + + case VAR_NUMBER: + case VAR_STRING: { + const char *const s = tv_get_string(rettv); + char *v; + int len = (int)strlen(s); + if (exclusive) { + if (is_range) { + v = string_slice(s, n1, n2, exclusive); } else { - // The resulting variable is a byte value. - // If the index is too big or negative that is an error. - if (n1 < 0) { - n1 = (int)len + n1; - } - if (n1 < len && n1 >= 0) { - const int v = (int)tv_blob_get(rettv->vval.v_blob, n1); - tv_clear(rettv); - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = v; - } else { - semsg(_(e_blobidx), (int64_t)n1); - } + v = char_from_string(s, n1); } - break; - case VAR_LIST: - len = tv_list_len(rettv->vval.v_list); + } else if (is_range) { + // The resulting variable is a substring. If the indexes + // are out of range the result is empty. if (n1 < 0) { - n1 = (int)len + n1; - } - if (!empty1 && (n1 < 0 || n1 >= len)) { - // For a range we allow invalid values and return an empty - // list. A list index out of range is an error. - if (!range) { - if (verbose) { - semsg(_(e_listidx), (int64_t)n1); - } - return FAIL; + n1 = len + n1; + if (n1 < 0) { + n1 = 0; } - n1 = (int)len; } - if (range) { - list_T *l; - listitem_T *item; - - if (n2 < 0) { - n2 = (int)len + n2; - } else if (n2 >= len) { - n2 = (int)len - 1; - } - if (!empty2 && (n2 < 0 || n2 + 1 < n1)) { - n2 = -1; - } - l = tv_list_alloc(n2 - n1 + 1); - item = tv_list_find(rettv->vval.v_list, n1); - while (n1++ <= n2) { - tv_list_append_tv(l, TV_LIST_ITEM_TV(item)); - item = TV_LIST_ITEM_NEXT(rettv->vval.v_list, item); - } - tv_clear(rettv); - tv_list_set_ret(rettv, l); + if (n2 < 0) { + n2 = len + n2; + } else if (n2 >= len) { + n2 = len; + } + if (exclusive) { + n2--; + } + if (n1 >= len || n2 < 0 || n1 > n2) { + v = NULL; } else { - tv_copy(TV_LIST_ITEM_TV(tv_list_find(rettv->vval.v_list, (int)n1)), &var1); - tv_clear(rettv); - *rettv = var1; + v = xmemdupz(s + n1, (size_t)n2 - (size_t)n1 + 1); } - break; - case VAR_DICT: { - if (range) { - if (verbose) { - emsg(_(e_dictrange)); - } - if (len == -1) { - tv_clear(&var1); - } - return FAIL; + } else { + // The resulting variable is a string of a single + // character. If the index is too big or negative the + // result is empty. + if (n1 >= len || n1 < 0) { + v = NULL; + } else { + v = xmemdupz(s + n1, 1); } + } + tv_clear(rettv); + rettv->v_type = VAR_STRING; + rettv->vval.v_string = v; + break; + } - if (len == -1) { - key = tv_get_string_chk(&var1); - if (key == NULL) { - tv_clear(&var1); - return FAIL; - } - } + case VAR_BLOB: + tv_blob_slice_or_index(rettv->vval.v_blob, is_range, n1, n2, exclusive, rettv); + break; - dictitem_T *const item = tv_dict_find(rettv->vval.v_dict, key, len); + case VAR_LIST: + if (var1 == NULL) { + n1 = 0; + } + if (var2 == NULL) { + n2 = VARNUMBER_MAX; + } + if (tv_list_slice_or_index(rettv->vval.v_list, + is_range, n1, n2, exclusive, rettv, verbose) == FAIL) { + return FAIL; + } + break; - if (item == NULL && verbose) { - semsg(_(e_dictkey), key); - } - if (len == -1) { - tv_clear(&var1); - } - if (item == NULL || tv_is_luafunc(&item->di_tv)) { + case VAR_DICT: { + if (key == NULL) { + key = tv_get_string_chk(var1); + if (key == NULL) { return FAIL; } + } - tv_copy(&item->di_tv, &var1); - tv_clear(rettv); - *rettv = var1; - break; + dictitem_T *const item = tv_dict_find(rettv->vval.v_dict, key, keylen); + + if (item == NULL && verbose) { + semsg(_(e_dictkey), key); } - case VAR_BOOL: - case VAR_SPECIAL: - case VAR_FUNC: - case VAR_FLOAT: - case VAR_PARTIAL: - case VAR_UNKNOWN: - break; // Not evaluating, skipping over subscript + if (item == NULL || tv_is_luafunc(&item->di_tv)) { + return FAIL; } - } + typval_T tmp; + tv_copy(&item->di_tv, &tmp); + tv_clear(rettv); + *rettv = tmp; + break; + } + } return OK; } @@ -7269,6 +7257,88 @@ int check_luafunc_name(const char *const str, const bool paren) return (int)(p - str); } +/// Return the character "str[index]" where "index" is the character index. If +/// "index" is out of range NULL is returned. +char *char_from_string(const char *str, varnumber_T index) +{ + size_t nbyte = 0; + varnumber_T nchar = index; + + if (str == NULL || index < 0) { + return NULL; + } + size_t slen = strlen(str); + while (nchar > 0 && nbyte < slen) { + nbyte += (size_t)utf_ptr2len(str + nbyte); + nchar--; + } + if (nbyte >= slen) { + return NULL; + } + return xstrnsave(str + nbyte, (size_t)utf_ptr2len(str + nbyte)); +} + +/// Get the byte index for character index "idx" in string "str" with length +/// "str_len". +/// If going over the end return "str_len". +/// If "idx" is negative count from the end, -1 is the last character. +/// When going over the start return -1. +static ssize_t char_idx2byte(const char *str, size_t str_len, varnumber_T idx) +{ + varnumber_T nchar = idx; + size_t nbyte = 0; + + if (nchar >= 0) { + while (nchar > 0 && nbyte < str_len) { + nbyte += (size_t)utf_ptr2len(str + nbyte); + nchar--; + } + } else { + nbyte = str_len; + while (nchar < 0 && nbyte > 0) { + nbyte--; + nbyte -= (size_t)utf_head_off(str, str + nbyte); + nchar++; + } + if (nchar < 0) { + return -1; + } + } + return (ssize_t)nbyte; +} + +/// Return the slice "str[first:last]" using character indexes. +/// +/// @param exclusive true for slice(). +/// +/// Return NULL when the result is empty. +char *string_slice(const char *str, varnumber_T first, varnumber_T last, bool exclusive) +{ + if (str == NULL) { + return NULL; + } + size_t slen = strlen(str); + ssize_t start_byte = char_idx2byte(str, slen, first); + if (start_byte < 0) { + start_byte = 0; // first index very negative: use zero + } + ssize_t end_byte; + if ((last == -1 && !exclusive) || last == VARNUMBER_MAX) { + end_byte = (ssize_t)slen; + } else { + end_byte = char_idx2byte(str, slen, last); + if (!exclusive && end_byte >= 0 && end_byte < (ssize_t)slen) { + // end index is inclusive + end_byte += utf_ptr2len(str + end_byte); + } + } + + if (start_byte >= (ssize_t)slen || end_byte <= start_byte) { + return NULL; + } + return xstrnsave(str + start_byte, (size_t)(end_byte - start_byte)); +} + /// Handle: /// - expr[expr], expr[expr:expr] subscript /// - ".name" lookup @@ -7632,7 +7702,7 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c int ret = OK; if (recurse >= DICT_MAXNEST) { - emsg(_("E698: variable nested too deep for making a copy")); + emsg(_(e_variable_nested_too_deep_for_making_copy)); return FAIL; } recurse++; diff --git a/src/nvim/eval.h b/src/nvim/eval.h index e9cdb108a8..5fbfc4702c 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -229,16 +229,6 @@ typedef struct { Callback callback; } timer_T; -/// Type of assert_* check being performed -typedef enum { - ASSERT_EQUAL, - ASSERT_NOTEQUAL, - ASSERT_MATCH, - ASSERT_NOTMATCH, - ASSERT_INRANGE, - ASSERT_OTHER, -} assert_type_T; - /// types for expressions. typedef enum { EXPR_UNKNOWN = 0, diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 09705148d0..d9c7208c02 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -371,6 +371,7 @@ return { simplify={args=1, base=1}, sin={args=1, base=1, float_func="sin"}, sinh={args=1, base=1, float_func="sinh"}, + slice={args={2, 3}, base=1}, sockconnect={args={2,3}}, sort={args={1, 3}, base=1}, soundfold={args=1, base=1}, @@ -385,7 +386,7 @@ return { str2list={args={1, 2}, base=1}, str2nr={args={1, 3}, base=1}, strcharlen={args=1, base=1}, - strcharpart={args={2, 3}, base=1, fast=true}, + strcharpart={args={2, 4}, base=1, fast=true}, strchars={args={1, 2}, base=1}, strdisplaywidth={args={1, 2}, base=1}, strftime={args={1, 2}, base=1}, diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index 9caea2fef1..7668fb129f 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -20,7 +20,8 @@ # include "eval/executor.c.generated.h" // IWYU pragma: export #endif -char *e_listidx = N_("E684: list index out of range: %" PRId64); +char *e_list_index_out_of_range_nr + = N_("E684: List index out of range: %" PRId64); /// Handle tv1 += tv2, -=, *=, /=, %=, .= /// diff --git a/src/nvim/eval/executor.h b/src/nvim/eval/executor.h index 3d789f76a5..42abf77f4a 100644 --- a/src/nvim/eval/executor.h +++ b/src/nvim/eval/executor.h @@ -3,7 +3,7 @@ #include "nvim/eval/typval.h" -extern char *e_listidx; +extern char *e_list_index_out_of_range_nr; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/executor.h.generated.h" diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index f898063fb0..927c1b3d5c 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -148,6 +148,8 @@ PRAGMA_DIAG_POP static const char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob"); static const char *e_invalwindow = N_("E957: Invalid window number"); +static const char e_invalid_submatch_number_nr[] + = N_("E935: Invalid submatch number: %d"); static const char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value"); static const char e_missing_function_argument[] = N_("E1132: Missing function argument"); @@ -928,7 +930,7 @@ static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (!error) { li = tv_list_find(l, (int)idx); if (li == NULL) { - semsg(_(e_listidx), idx); + semsg(_(e_list_index_out_of_range_nr), idx); } } } @@ -1902,7 +1904,7 @@ static void extend(typval_T *argvars, typval_T *rettv, char *arg_errmsg, bool is } else { item = tv_list_find(l1, (int)before); if (item == NULL) { - semsg(_(e_listidx), (int64_t)before); + semsg(_(e_list_index_out_of_range_nr), (int64_t)before); return; } } @@ -3729,7 +3731,7 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (before != tv_list_len(l)) { item = tv_list_find(l, (int)before); if (item == NULL) { - semsg(_(e_listidx), before); + semsg(_(e_list_index_out_of_range_nr), before); l = NULL; } } @@ -8027,7 +8029,7 @@ static void f_submatch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } if (no < 0 || no >= NSUBEXP) { - semsg(_("E935: invalid submatch number: %d"), no); + semsg(_(e_invalid_submatch_number_nr), no); return; } int retList = 0; diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index cf355a22fa..5755178b18 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -40,6 +40,10 @@ # include "eval/typval.c.generated.h" #endif +static const char e_variable_nested_too_deep_for_unlock[] + = N_("E743: Variable nested too deep for (un)lock"); +static const char e_using_invalid_value_as_string[] + = N_("E908: Using an invalid value as a String"); static const char e_string_required_for_argument_nr[] = N_("E1174: String required for argument %d"); static const char e_non_empty_string_required_for_argument_nr[] @@ -52,6 +56,10 @@ static const char e_list_required_for_argument_nr[] = N_("E1211: List required for argument %d"); static const char e_bool_required_for_argument_nr[] = N_("E1212: Bool required for argument %d"); +static const char e_float_or_number_required_for_argument_nr[] + = N_("E1219: Float or Number required for argument %d"); +static const char e_string_or_number_required_for_argument_nr[] + = N_("E1220: String or Number required for argument %d"); static const char e_string_or_list_required_for_argument_nr[] = N_("E1222: String or List required for argument %d"); static const char e_list_or_blob_required_for_argument_nr[] @@ -765,6 +773,64 @@ int tv_list_concat(list_T *const l1, list_T *const l2, typval_T *const tv) return OK; } +static list_T *tv_list_slice(list_T *ol, varnumber_T n1, varnumber_T n2) +{ + list_T *l = tv_list_alloc(n2 - n1 + 1); + listitem_T *item = tv_list_find(ol, (int)n1); + for (; n1 <= n2; n1++) { + tv_list_append_tv(l, TV_LIST_ITEM_TV(item)); + item = TV_LIST_ITEM_NEXT(rettv->vval.v_list, item); + } + return l; +} + +int tv_list_slice_or_index(list_T *list, bool range, varnumber_T n1_arg, varnumber_T n2_arg, + bool exclusive, typval_T *rettv, bool verbose) +{ + int len = tv_list_len(rettv->vval.v_list); + varnumber_T n1 = n1_arg; + varnumber_T n2 = n2_arg; + + if (n1 < 0) { + n1 = len + n1; + } + if (n1 < 0 || n1 >= len) { + // For a range we allow invalid values and return an empty list. + // A list index out of range is an error. + if (!range) { + if (verbose) { + semsg(_(e_list_index_out_of_range_nr), (int64_t)n1); + } + return FAIL; + } + n1 = len; + } + if (range) { + if (n2 < 0) { + n2 = len + n2; + } else if (n2 >= len) { + n2 = len - (exclusive ? 0 : 1); + } + if (exclusive) { + n2--; + } + if (n2 < 0 || n2 + 1 < n1) { + n2 = -1; + } + list_T *l = tv_list_slice(rettv->vval.v_list, n1, n2); + tv_clear(rettv); + tv_list_set_ret(rettv, l); + } else { + // copy the item to "var1" to avoid that freeing the list makes it + // invalid. + typval_T var1; + tv_copy(TV_LIST_ITEM_TV(tv_list_find(rettv->vval.v_list, (int)n1)), &var1); + tv_clear(rettv); + *rettv = var1; + } + return OK; +} + typedef struct { char *s; char *tofree; @@ -927,7 +993,7 @@ void tv_list_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) if (error) { // Type error: do nothing, errmsg already given. } else if ((item = tv_list_find(l, (int)idx)) == NULL) { - semsg(_(e_listidx), idx); + semsg(_(e_list_index_out_of_range_nr), idx); } else { if (argvars[2].v_type == VAR_UNKNOWN) { // Remove one item, return its value. @@ -941,7 +1007,7 @@ void tv_list_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) if (error) { // Type error: do nothing. } else if ((item2 = tv_list_find(l, (int)end)) == NULL) { - semsg(_(e_listidx), end); + semsg(_(e_list_index_out_of_range_nr), end); } else { int cnt = 0; @@ -1515,7 +1581,7 @@ const char *tv_list_find_str(list_T *const l, const int n) { const listitem_T *const li = tv_list_find(l, n); if (li == NULL) { - semsg(_(e_listidx), (int64_t)n); + semsg(_(e_list_index_out_of_range_nr), (int64_t)n); return NULL; } return tv_get_string(TV_LIST_ITEM_TV(li)); @@ -2718,6 +2784,79 @@ bool tv_blob_equal(const blob_T *const b1, const blob_T *const b2) return true; } +/// Returns a slice of "blob" from index "n1" to "n2" in "rettv". The length of +/// the blob is "len". Returns an empty blob if the indexes are out of range. +static int tv_blob_slice(const blob_T *blob, int len, varnumber_T n1, varnumber_T n2, + bool exclusive, typval_T *rettv) +{ + // The resulting variable is a sub-blob. If the indexes + // are out of range the result is empty. + if (n1 < 0) { + n1 = len + n1; + if (n1 < 0) { + n1 = 0; + } + } + if (n2 < 0) { + n2 = len + n2; + } else if (n2 >= len) { + n2 = len - (exclusive ? 0 : 1); + } + if (exclusive) { + n2--; + } + if (n1 >= len || n2 < 0 || n1 > n2) { + tv_clear(rettv); + rettv->v_type = VAR_BLOB; + rettv->vval.v_blob = NULL; + } else { + blob_T *const new_blob = tv_blob_alloc(); + ga_grow(&new_blob->bv_ga, (int)(n2 - n1 + 1)); + new_blob->bv_ga.ga_len = (int)(n2 - n1 + 1); + for (int i = (int)n1; i <= (int)n2; i++) { + tv_blob_set(new_blob, i - (int)n1, tv_blob_get(rettv->vval.v_blob, i)); + } + tv_clear(rettv); + tv_blob_set_ret(rettv, new_blob); + } + + return OK; +} + +/// Return the byte value in "blob" at index "idx" in "rettv". If the index is +/// too big or negative that is an error. The length of the blob is "len". +static int tv_blob_index(const blob_T *blob, int len, varnumber_T idx, typval_T *rettv) +{ + // The resulting variable is a byte value. + // If the index is too big or negative that is an error. + if (idx < 0) { + idx = len + idx; + } + if (idx < len && idx >= 0) { + const int v = (int)tv_blob_get(rettv->vval.v_blob, (int)idx); + tv_clear(rettv); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = v; + } else { + semsg(_(e_blobidx), idx); + return FAIL; + } + + return OK; +} + +int tv_blob_slice_or_index(const blob_T *blob, int is_range, varnumber_T n1, varnumber_T n2, + bool exclusive, typval_T *rettv) +{ + int len = tv_blob_len(rettv->vval.v_blob); + + if (is_range) { + return tv_blob_slice(blob, len, n1, n2, exclusive, rettv); + } else { + return tv_blob_index(blob, len, n1, rettv); + } +} + /// Check if "n1" is a valid index for a blob with length "bloblen". int tv_blob_check_index(int bloblen, varnumber_T n1, bool quiet) { @@ -2745,14 +2884,14 @@ int tv_blob_check_range(int bloblen, varnumber_T n1, varnumber_T n2, bool quiet) /// Set bytes "n1" to "n2" (inclusive) in "dest" to the value of "src". /// Caller must make sure "src" is a blob. /// Returns FAIL if the number of bytes does not match. -int tv_blob_set_range(blob_T *dest, int n1, int n2, typval_T *src) +int tv_blob_set_range(blob_T *dest, varnumber_T n1, varnumber_T n2, typval_T *src) { if (n2 - n1 + 1 != tv_blob_len(src->vval.v_blob)) { emsg(_("E972: Blob value does not have the right number of bytes")); return FAIL; } - for (int il = n1, ir = 0; il <= n2; il++) { + for (int il = (int)n1, ir = 0; il <= (int)n2; il++) { tv_blob_set(dest, il, tv_blob_get(src->vval.v_blob, ir++)); } return OK; @@ -3450,7 +3589,7 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock, const boo static int recurse = 0; if (recurse >= DICT_MAXNEST) { - emsg(_("E743: variable nested too deep for (un)lock")); + emsg(_(e_variable_nested_too_deep_for_unlock)); return; } if (deep == 0) { @@ -3800,16 +3939,16 @@ bool tv_check_num(const typval_T *const tv) return false; } -#define FUNC_ERROR "E729: using Funcref as a String" +#define FUNC_ERROR "E729: Using a Funcref as a String" static const char *const str_errors[] = { [VAR_PARTIAL]= N_(FUNC_ERROR), [VAR_FUNC]= N_(FUNC_ERROR), - [VAR_LIST]= N_("E730: using List as a String"), - [VAR_DICT]= N_("E731: using Dictionary as a String"), - [VAR_FLOAT]= e_float_as_string, - [VAR_BLOB]= N_("E976: using Blob as a String"), - [VAR_UNKNOWN]= e_inval_string, + [VAR_LIST]= N_("E730: Using a List as a String"), + [VAR_DICT]= N_("E731: Using a Dictionary as a String"), + [VAR_FLOAT]= e_using_float_as_string, + [VAR_BLOB]= N_("E976: Using a Blob as a String"), + [VAR_UNKNOWN]= e_using_invalid_value_as_string, }; #undef FUNC_ERROR @@ -4029,6 +4168,17 @@ int tv_check_for_opt_number_arg(const typval_T *const args, const int idx) || tv_check_for_number_arg(args, idx) != FAIL) ? OK : FAIL; } +/// Give an error and return FAIL unless "args[idx]" is a float or a number. +int tv_check_for_float_or_nr_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (args[idx].v_type != VAR_FLOAT && args[idx].v_type != VAR_NUMBER) { + semsg(_(e_float_or_number_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + /// Give an error and return FAIL unless "args[idx]" is a bool. int tv_check_for_bool_arg(const typval_T *const args, const int idx) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE @@ -4109,6 +4259,18 @@ int tv_check_for_opt_dict_arg(const typval_T *const args, const int idx) || tv_check_for_dict_arg(args, idx) != FAIL) ? OK : FAIL; } +/// Give an error and return FAIL unless "args[idx]" is a string or +/// a number. +int tv_check_for_string_or_number_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (args[idx].v_type != VAR_STRING && args[idx].v_type != VAR_NUMBER) { + semsg(_(e_string_or_number_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + /// Give an error and return FAIL unless "args[idx]" is a string or a list. int tv_check_for_string_or_list_arg(const typval_T *const args, const int idx) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE @@ -4120,6 +4282,14 @@ int tv_check_for_string_or_list_arg(const typval_T *const args, const int idx) return OK; } +/// Check for an optional string or list argument at 'idx' +int tv_check_for_opt_string_or_list_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + return (args[idx].v_type == VAR_UNKNOWN + || tv_check_for_string_or_list_arg(args, idx) != FAIL) ? OK : FAIL; +} + /// Give an error and return FAIL unless "args[idx]" is a string /// or a function reference. int tv_check_for_string_or_func_arg(const typval_T *const args, const int idx) diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index 1f5a6eaec4..fbb5e8d10c 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -79,6 +79,8 @@ static const char *e_funcref = N_("E718: Funcref required"); static const char *e_nofunc = N_("E130: Unknown function: %s"); static const char e_function_list_was_modified[] = N_("E454: Function list was modified"); +static const char e_function_nesting_too_deep[] + = N_("E1058: Function nesting too deep"); static const char e_no_white_space_allowed_before_str_str[] = N_("E1068: No white space allowed before '%s': %s"); static const char e_missing_heredoc_end_marker_str[] @@ -2528,7 +2530,7 @@ void ex_function(exarg_T *eap) xfree(trans_function_name(&p, true, 0, NULL, NULL)); if (*skipwhite(p) == '(') { if (nesting == MAX_FUNC_NESTING - 1) { - emsg(_("E1058: function nesting too deep")); + emsg(_(e_function_nesting_too_deep)); } else { nesting++; indent += 2; @@ -2587,19 +2589,13 @@ void ex_function(exarg_T *eap) // Check for ":let v =<< [trim] EOF" // and ":let [a, b] =<< [trim] EOF" - arg = skipwhite(skiptowhite(p)); - if (*arg == '[') { - arg = vim_strchr(arg, ']'); - } - if (arg != NULL) { - arg = skipwhite(skiptowhite(arg)); - if (arg[0] == '=' - && arg[1] == '<' - && arg[2] == '<' - && (p[0] == 'l' - && p[1] == 'e' - && (!ASCII_ISALNUM(p[2]) - || (p[2] == 't' && !ASCII_ISALNUM(p[3]))))) { + arg = p; + if (checkforcmd(&arg, "let", 2)) { + while (vim_strchr("$@&", *arg) != NULL) { + arg++; + } + arg = skipwhite(find_name_end(arg, NULL, NULL, FNE_INCL_BR)); + if (arg[0] == '=' && arg[1] == '<' && arg[2] == '<') { p = skipwhite(arg + 3); while (true) { if (strncmp(p, "trim", 4) == 0) { diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index de6c4bab60..64177d13e8 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -52,6 +52,10 @@ static const char *e_letunexp = N_("E18: Unexpected characters in :let"); static const char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s"); +static const char e_setting_str_to_value_with_wrong_type[] + = N_("E963: Setting %s to value with wrong type"); +static const char e_cannot_use_heredoc_here[] + = N_("E991: Cannot use =<< here"); /// Evaluate one Vim expression {expr} in string "p" and append the /// resulting string to "gap". "p" points to the opening "{". @@ -169,7 +173,7 @@ list_T *heredoc_get(exarg_T *eap, char *cmd, bool script_get) char dot[] = "."; if (eap->getline == NULL) { - emsg(_("E991: cannot use =<< here")); + emsg(_(e_cannot_use_heredoc_here)); return NULL; } @@ -1457,7 +1461,7 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, } return; } else if (v->di_tv.v_type != tv->v_type) { - semsg(_("E963: setting %s to value with wrong type"), name); + semsg(_(e_setting_str_to_value_with_wrong_type), name); return; } } diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index 9976c56879..30c295de46 100644 --- a/src/nvim/eval/window.c +++ b/src/nvim/eval/window.c @@ -98,6 +98,7 @@ win_T *win_id2wp(int id) } /// Return the window and tab pointer of window "id". +/// Returns NULL when not found. win_T *win_id2wp_tp(int id, tabpage_T **tpp) { FOR_ALL_TAB_WINDOWS(tp, wp) { diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index f276e8ae24..dbfd1088d2 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -126,6 +126,9 @@ typedef struct { # include "ex_cmds.c.generated.h" #endif +static const char e_non_numeric_argument_to_z[] + = N_("E144: Non-numeric argument to :z"); + /// ":ascii" and "ga" implementation void do_ascii(const exarg_T *const eap) { @@ -346,10 +349,8 @@ static int linelen(int *has_tab) last > first && ascii_iswhite(last[-1]); last--) {} char save = *last; *last = NUL; - // Get line length. - len = linetabsize(line); - // Check for embedded TAB. - if (has_tab != NULL) { + len = linetabsize_str(line); // Get line length. + if (has_tab != NULL) { // Check for embedded TAB. *has_tab = vim_strchr(first, TAB) != NULL; } *last = save; @@ -2954,7 +2955,7 @@ void ex_z(exarg_T *eap) if (*x != 0) { if (!ascii_isdigit(*x)) { - emsg(_("E144: non-numeric argument to :z")); + emsg(_(e_non_numeric_argument_to_z)); return; } bigness = atol(x); diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index deaa24452b..40259767ff 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -53,6 +53,9 @@ # include "ex_cmds2.c.generated.h" #endif +static const char e_compiler_not_supported_str[] + = N_("E666: Compiler not supported: %s"); + void ex_ruby(exarg_T *eap) { script_host_execute("ruby", eap); @@ -731,7 +734,7 @@ void ex_compiler(exarg_T *eap) // Try lua compiler snprintf(buf, bufsize, "compiler/%s.lua", eap->arg); if (source_runtime(buf, DIP_ALL) == FAIL) { - semsg(_("E666: compiler not supported: %s"), eap->arg); + semsg(_(e_compiler_not_supported_str), eap->arg); } } xfree(buf); diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 0e6d4bba1b..adb17f2cfd 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -87,12 +87,22 @@ static const char e_ambiguous_use_of_user_defined_command[] = N_("E464: Ambiguous use of user-defined command"); +static const char e_no_call_stack_to_substitute_for_stack[] + = N_("E489: No call stack to substitute for \"<stack>\""); static const char e_not_an_editor_command[] = N_("E492: Not an editor command"); +static const char e_no_autocommand_file_name_to_substitute_for_afile[] + = N_("E495: No autocommand file name to substitute for \"<afile>\""); +static const char e_no_autocommand_buffer_number_to_substitute_for_abuf[] + = N_("E496: No autocommand buffer number to substitute for \"<abuf>\""); +static const char e_no_autocommand_match_name_to_substitute_for_amatch[] + = N_("E497: No autocommand match name to substitute for \"<amatch>\""); static const char e_no_source_file_name_to_substitute_for_sfile[] - = N_("E498: no :source file name to substitute for \"<sfile>\""); -static const char e_no_call_stack_to_substitute_for_stack[] - = N_("E489: no call stack to substitute for \"<stack>\""); + = N_("E498: No :source file name to substitute for \"<sfile>\""); +static const char e_no_line_number_to_use_for_slnum[] + = N_("E842: No line number to use for \"<slnum>\""); +static const char e_no_line_number_to_use_for_sflnum[] + = N_("E961: No line number to use for \"<sflnum>\""); static const char e_no_script_file_name_to_substitute_for_script[] = N_("E1274: No script file name to substitute for \"<script>\""); @@ -220,7 +230,7 @@ void do_exmode(void) if ((prev_line != curwin->w_cursor.lnum || changedtick != buf_get_changedtick(curbuf)) && !ex_no_reprint) { if (curbuf->b_ml.ml_flags & ML_EMPTY) { - emsg(_(e_emptybuf)); + emsg(_(e_empty_buffer)); } else { if (ex_pressedreturn) { // Make sure the message overwrites the right line and isn't throttled. @@ -238,7 +248,7 @@ void do_exmode(void) } } else if (ex_pressedreturn && !ex_no_reprint) { // must be at EOF if (curbuf->b_ml.ml_flags & ML_EMPTY) { - emsg(_(e_emptybuf)); + emsg(_(e_empty_buffer)); } else { emsg(_("E501: At end-of-file")); } @@ -278,7 +288,7 @@ static void msg_verbose_cmd(linenr_T lnum, char *cmd) /// Execute a simple command line. Used for translated commands like "*". int do_cmdline_cmd(const char *cmd) { - return do_cmdline((char *)cmd, NULL, NULL, DOCMD_NOWAIT|DOCMD_KEYTYPED); + return do_cmdline((char *)cmd, NULL, NULL, DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED); } /// do_cmdline(): execute one Ex command line @@ -1852,7 +1862,8 @@ static bool skip_cmd(const exarg_T *eap) /// Execute one Ex command. /// -/// If 'sourcing' is true, the command will be included in the error message. +/// If "flags" has DOCMD_VERBOSE, the command will be included in the error +/// message. /// /// 1. skip comment lines and leading space /// 2. handle command modifiers @@ -4900,7 +4911,7 @@ static void ex_exit(exarg_T *eap) static void ex_print(exarg_T *eap) { if (curbuf->b_ml.ml_flags & ML_EMPTY) { - emsg(_(e_emptybuf)); + emsg(_(e_empty_buffer)); } else { for (; !got_int; os_breakcheck()) { print_line(eap->line1, @@ -6894,7 +6905,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum } result = autocmd_fname; if (result == NULL) { - *errormsg = _("E495: no autocommand file name to substitute for \"<afile>\""); + *errormsg = _(e_no_autocommand_file_name_to_substitute_for_afile); return NULL; } result = path_try_shorten_fname(result); @@ -6902,7 +6913,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum case SPEC_ABUF: // buffer number for autocommand if (autocmd_bufnr <= 0) { - *errormsg = _("E496: no autocommand buffer number to substitute for \"<abuf>\""); + *errormsg = _(e_no_autocommand_buffer_number_to_substitute_for_abuf); return NULL; } snprintf(strbuf, sizeof(strbuf), "%d", autocmd_bufnr); @@ -6912,7 +6923,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum case SPEC_AMATCH: // match name for autocommand result = autocmd_match; if (result == NULL) { - *errormsg = _("E497: no autocommand match name to substitute for \"<amatch>\""); + *errormsg = _(e_no_autocommand_match_name_to_substitute_for_amatch); return NULL; } break; @@ -6944,7 +6955,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum case SPEC_SLNUM: // line in file for ":so" command if (SOURCING_NAME == NULL || SOURCING_LNUM == 0) { - *errormsg = _("E842: no line number to use for \"<slnum>\""); + *errormsg = _(e_no_line_number_to_use_for_slnum); return NULL; } snprintf(strbuf, sizeof(strbuf), "%" PRIdLINENR, SOURCING_LNUM); @@ -6953,7 +6964,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum case SPEC_SFLNUM: // line in script file if (current_sctx.sc_lnum + SOURCING_LNUM == 0) { - *errormsg = _("E961: no line number to use for \"<sflnum>\""); + *errormsg = _(e_no_line_number_to_use_for_sflnum); return NULL; } snprintf(strbuf, sizeof(strbuf), "%" PRIdLINENR, diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 375bf37664..1b150ef75d 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -38,6 +38,9 @@ # include "ex_eval.c.generated.h" #endif +static const char e_multiple_else[] = N_("E583: Multiple :else"); +static const char e_multiple_finally[] = N_("E607: Multiple :finally"); + // Exception handling terms: // // :try ":try" command ─┐ @@ -873,7 +876,7 @@ void ex_else(exarg_T *eap) skip = true; } else if (cstack->cs_flags[cstack->cs_idx] & CSF_ELSE) { if (eap->cmdidx == CMD_else) { - eap->errmsg = _("E583: multiple :else"); + eap->errmsg = _(e_multiple_else); return; } eap->errmsg = _("E584: :elseif after :else"); @@ -1426,7 +1429,7 @@ void ex_finally(exarg_T *eap) if (cstack->cs_flags[idx] & CSF_FINALLY) { // Give up for a multiple ":finally" and ignore it. - eap->errmsg = _("E607: multiple :finally"); + eap->errmsg = _(e_multiple_finally); return; } rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 5018c9268b..af2ec3356f 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -504,6 +504,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat } validate_cursor(); + // May redraw the status line to show the cursor position. if (p_ru && (curwin->w_status_height > 0 || global_stl_height() > 0)) { curwin->w_redr_status = true; @@ -598,6 +599,7 @@ static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool magic_overruled = s->magic_overruled_save; validate_cursor(); // needed for TAB + status_redraw_all(); redraw_all_later(UPD_SOME_VALID); if (call_update_screen) { update_screen(); diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 9c112f2344..1bfbe2e634 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -182,7 +182,8 @@ typedef struct ff_search_ctx_T { # include "file_search.c.generated.h" #endif -static const char e_pathtoolong[] = N_("E854: path too long for completion"); +static const char e_path_too_long_for_completion[] + = N_("E854: Path too long for completion"); /// Initialization routine for vim_findfile(). /// @@ -395,7 +396,7 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i len = 0; while (*wc_part != NUL) { if (len + 5 >= MAXPATHL) { - emsg(_(e_pathtoolong)); + emsg(_(e_path_too_long_for_completion)); break; } if (strncmp(wc_part, "**", 2) == 0) { @@ -438,7 +439,7 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i // create an absolute path if (strlen(search_ctx->ffsc_start_dir) + strlen(search_ctx->ffsc_fix_path) + 3 >= MAXPATHL) { - emsg(_(e_pathtoolong)); + emsg(_(e_path_too_long_for_completion)); goto error_return; } STRCPY(ff_expand_buffer, search_ctx->ffsc_start_dir); diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua index 7d531bc228..e93e9a8d02 100644 --- a/src/nvim/generators/gen_eval.lua +++ b/src/nvim/generators/gen_eval.lua @@ -17,6 +17,7 @@ hashpipe:write([[ #include "nvim/cmdexpand.h" #include "nvim/cmdhist.h" #include "nvim/digraph.h" +#include "nvim/eval.h" #include "nvim/eval/buffer.h" #include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" diff --git a/src/nvim/generators/gen_vimvim.lua b/src/nvim/generators/gen_vimvim.lua index 09a7cab0c6..29355d3cda 100644 --- a/src/nvim/generators/gen_vimvim.lua +++ b/src/nvim/generators/gen_vimvim.lua @@ -112,13 +112,12 @@ for _, au in ipairs(auevents.events) do w(' ' .. au) end end -for au, _ in pairs(auevents.aliases) do - if not auevents.nvim_specific[au] then - if lld.line_length > 850 then - w('\n' .. vimau_start) - end - w(' ' .. au) +for _, au in pairs(auevents.aliases) do + if lld.line_length > 850 then + w('\n' .. vimau_start) end + -- au[1] is aliased to au[2] + w(' ' .. au[1]) end local nvimau_start = 'syn keyword nvimAutoEvent contained ' diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 057043b225..a0e8350287 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -133,6 +133,8 @@ static size_t last_recorded_len = 0; // number of last recorded chars # include "getchar.c.generated.h" #endif +static const char e_recursive_mapping[] = N_("E223: Recursive mapping"); + // Free and clear a buffer. void free_buff(buffheader_T *buf) { @@ -832,23 +834,23 @@ bool noremap_keys(void) return KeyNoremap & (RM_NONE|RM_SCRIPT); } -// Insert a string in position 'offset' in the typeahead buffer (for "@r" -// and ":normal" command, vgetorpeek() and check_termcode()) -// -// If noremap is REMAP_YES, new string can be mapped again. -// If noremap is REMAP_NONE, new string cannot be mapped again. -// If noremap is REMAP_SKIP, first char of new string cannot be mapped again, -// but abbreviations are allowed. -// If noremap is REMAP_SCRIPT, new string cannot be mapped again, except for -// script-local mappings. -// If noremap is > 0, that many characters of the new string cannot be mapped. -// -// If nottyped is true, the string does not return KeyTyped (don't use when -// offset is non-zero!). -// -// If silent is true, cmd_silent is set when the characters are obtained. -// -// return FAIL for failure, OK otherwise +/// Insert a string in position "offset" in the typeahead buffer (for "@r" +/// and ":normal" command, vgetorpeek() and check_termcode()) +/// +/// If "noremap" is REMAP_YES, new string can be mapped again. +/// If "noremap" is REMAP_NONE, new string cannot be mapped again. +/// If "noremap" is REMAP_SKIP, first char of new string cannot be mapped again, +/// but abbreviations are allowed. +/// If "noremap" is REMAP_SCRIPT, new string cannot be mapped again, except for +/// script-local mappings. +/// If "noremap" is > 0, that many characters of the new string cannot be mapped. +/// +/// If "nottyped" is true, the string does not return KeyTyped (don't use when +/// "offset" is non-zero!). +/// +/// If "silent" is true, cmd_silent is set when the characters are obtained. +/// +/// @return FAIL for failure, OK otherwise int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent) { uint8_t *s1, *s2; @@ -1349,8 +1351,8 @@ void before_blocking(void) } } -/// updatescript() is called when a character can be written to the script file -/// or when we have waited some time for a character (c == 0). +/// updatescript() is called when a character can be written to the script +/// file or when we have waited some time for a character (c == 0). /// /// All the changed memfiles are synced if c == 0 or when the number of typed /// characters reaches 'updatecount' and 'updatecount' is non-zero. @@ -2135,7 +2137,7 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth) // Put the replacement string in front of mapstr. // The depth check catches ":map x y" and ":map y x". if (++*mapdepth >= p_mmd) { - emsg(_("E223: recursive mapping")); + emsg(_(e_recursive_mapping)); if (State & MODE_CMDLINE) { redrawcmdline(); } else { diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 159bfc202f..698f4a98c7 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -968,7 +968,6 @@ EXTERN const char e_scroll[] INIT(= N_("E49: Invalid scroll size")); EXTERN const char e_shellempty[] INIT(= N_("E91: 'shell' option is empty")); EXTERN const char e_signdata[] INIT(= N_("E255: Couldn't read in sign data!")); EXTERN const char e_swapclose[] INIT(= N_("E72: Close error on swap file")); -EXTERN const char e_tagstack[] INIT(= N_("E73: tag stack empty")); EXTERN const char e_toocompl[] INIT(= N_("E74: Command too complex")); EXTERN const char e_longname[] INIT(= N_("E75: Name too long")); EXTERN const char e_toomsbra[] INIT(= N_("E76: Too many [")); @@ -983,8 +982,7 @@ EXTERN const char e_write[] INIT(= N_("E80: Error while writing")); EXTERN const char e_zerocount[] INIT(= N_("E939: Positive count required")); EXTERN const char e_usingsid[] INIT(= N_("E81: Using <SID> not in a script context")); EXTERN const char e_missingparen[] INIT(= N_("E107: Missing parentheses: %s")); -EXTERN const char e_maxmempat[] INIT(= N_("E363: pattern uses more memory than 'maxmempattern'")); -EXTERN const char e_emptybuf[] INIT(= N_("E749: empty buffer")); +EXTERN const char e_empty_buffer[] INIT(= N_("E749: Empty buffer")); EXTERN const char e_nobufnr[] INIT(= N_("E86: Buffer %" PRId64 " does not exist")); EXTERN const char e_str_not_inside_function[] INIT(= N_("E193: %s not inside a function")); @@ -1000,8 +998,7 @@ EXTERN const char e_autocmd_close[] INIT(= N_("E813: Cannot close autocmd window EXTERN const char e_listarg[] INIT(= N_("E686: Argument of %s must be a List")); EXTERN const char e_unsupportedoption[] INIT(= N_("E519: Option not supported")); EXTERN const char e_fnametoolong[] INIT(= N_("E856: Filename too long")); -EXTERN const char e_float_as_string[] INIT(= N_("E806: using Float as a String")); -EXTERN const char e_inval_string[] INIT(= N_("E908: using an invalid value as a String")); +EXTERN const char e_using_float_as_string[] INIT(= N_("E806: Using a Float as a String")); EXTERN const char e_cannot_edit_other_buf[] INIT(= N_("E788: Not allowed to edit another buffer now")); EXTERN const char e_using_number_as_bool_nr[] INIT(= N_("E1023: Using a Number as a Bool: %d")); EXTERN const char e_not_callable_type_str[] INIT(= N_("E1085: Not a callable type: %s")); diff --git a/src/nvim/grid.c b/src/nvim/grid.c index 7745daf69a..037606c38f 100644 --- a/src/nvim/grid.c +++ b/src/nvim/grid.c @@ -22,7 +22,7 @@ #include "nvim/highlight.h" #include "nvim/log.h" #include "nvim/message.h" -#include "nvim/option_defs.h" +#include "nvim/option.h" #include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -503,6 +503,7 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle int col = 0; bool redraw_next; // redraw_this for next character bool clear_next = false; + bool topline = row == 0; int char_cells; // 1: normal char // 2: occupies two display cells int start_dirty = -1, end_dirty = 0; @@ -529,6 +530,30 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle max_off_from = linebuf_size; max_off_to = grid->line_offset[row] + (size_t)grid->cols; + // Take care of putting "<<<" on the first line for 'smoothscroll'. + if (topline && wp->w_skipcol > 0 + // do not overwrite the 'showbreak' text with "<<<" + && *get_showbreak_value(wp) == NUL + // do not overwrite the 'listchars' "precedes" text with "<<<" + && !(wp->w_p_list && wp->w_p_lcs_chars.prec != 0)) { + int off = 0; + int skip = 0; + if (wp->w_p_nu && wp->w_p_rnu) { + // do not overwrite the line number, change "123 text" to + // "123>>>xt". + while (skip < wp->w_width_inner && ascii_isdigit(*linebuf_char[off])) { + off++; + skip++; + } + } + + for (int i = 0; i < 3 && i + skip < wp->w_width_inner; i++) { + schar_from_ascii(linebuf_char[off], '<'); + linebuf_attr[off] = HL_ATTR(HLF_AT); + off++; + } + } + if (rlflag) { // Clear rest first, because it's left of the text. if (clear_width > 0) { diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index 3ab8a3bb88..a0f0a947b8 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -119,6 +119,17 @@ enum { # include "highlight_group.c.generated.h" #endif +static const char e_highlight_group_name_not_found_str[] + = N_("E411: Highlight group not found: %s"); +static const char e_group_has_settings_highlight_link_ignored[] + = N_("E414: Group has settings, highlight link ignored"); +static const char e_unexpected_equal_sign_str[] + = N_("E415: Unexpected equal sign: %s"); +static const char e_missing_equal_sign_str_2[] + = N_("E416: Missing equal sign: %s"); +static const char e_missing_argument_str[] + = N_("E417: Missing argument: %s"); + #define hl_table ((HlGroup *)((highlight_ga.ga_data))) // The default highlight groups. These are compiled-in for fast startup and @@ -920,7 +931,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (!doclear && !dolink && ends_excmd((uint8_t)(*linep))) { int id = syn_name2id_len(line, (size_t)(name_end - line)); if (id == 0) { - semsg(_("E411: highlight group not found: %s"), line); + semsg(_(e_highlight_group_name_not_found_str), line); } else { highlight_list_one(id); } @@ -976,7 +987,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (to_id > 0 && !forceit && !init && hl_has_settings(from_id - 1, dodefault)) { if (SOURCING_NAME == NULL && !dodefault) { - emsg(_("E414: group has settings, highlight link ignored")); + emsg(_(e_group_has_settings_highlight_link_ignored)); } } else if (hlgroup->sg_link != to_id || hlgroup->sg_script_ctx.sc_sid != current_sctx.sc_sid @@ -1054,7 +1065,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) while (!ends_excmd((uint8_t)(*linep))) { const char *key_start = linep; if (*linep == '=') { - semsg(_("E415: unexpected equal sign: %s"), key_start); + semsg(_(e_unexpected_equal_sign_str), key_start); error = true; break; } @@ -1087,7 +1098,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) // Check for the equal sign. if (*linep != '=') { - semsg(_("E416: missing equal sign: %s"), key_start); + semsg(_(e_missing_equal_sign_str_2), key_start); error = true; break; } @@ -1108,7 +1119,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) linep = skiptowhite(linep); } if (linep == arg_start) { - semsg(_("E417: missing argument: %s"), key_start); + semsg(_(e_missing_argument_str), key_start); error = true; break; } diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index c1d02e89b8..c5449bd66e 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -65,6 +65,17 @@ static mapblock_T *(maphash[MAX_MAPHASH]) = { 0 }; # include "mapping.c.generated.h" #endif +static const char e_global_abbreviation_already_exists_for_str[] + = N_("E224: Global abbreviation already exists for %s"); +static const char e_global_mapping_already_exists_for_str[] + = N_("E225: Global mapping already exists for %s"); +static const char e_abbreviation_already_exists_for_str[] + = N_("E226: Abbreviation already exists for %s"); +static const char e_mapping_already_exists_for_str[] + = N_("E227: Mapping already exists for %s"); +static const char e_entries_missing_in_mapset_dict_argument[] + = N_("E460: Entries missing in mapset() dict argument"); + /// Get the start of the hashed map list for "state" and first character "c". mapblock_T *get_maphash_list(int state, int c) { @@ -645,10 +656,9 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, && mp->m_keylen == len && strncmp(mp->m_keys, lhs, (size_t)len) == 0) { if (is_abbrev) { - semsg(_("E224: global abbreviation already exists for %s"), - mp->m_keys); + semsg(_(e_global_abbreviation_already_exists_for_str), mp->m_keys); } else { - semsg(_("E225: global mapping already exists for %s"), mp->m_keys); + semsg(_(e_global_mapping_already_exists_for_str), mp->m_keys); } retval = 5; goto theend; @@ -761,9 +771,9 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, break; } else if (args->unique) { if (is_abbrev) { - semsg(_("E226: abbreviation already exists for %s"), p); + semsg(_(e_abbreviation_already_exists_for_str), p); } else { - semsg(_("E227: mapping already exists for %s"), p); + semsg(_(e_mapping_already_exists_for_str), p); } retval = 5; goto theend; @@ -2226,7 +2236,7 @@ void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) api_free_object(callback_obj); } if (lhs == NULL || lhsraw == NULL || orig_rhs == NULL) { - emsg(_("E460: entries missing in mapset() dict argument")); + emsg(_(e_entries_missing_in_mapset_dict_argument)); api_free_luaref(rhs_lua); return; } diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c index 968fa455bf..bd8314c679 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -70,6 +70,8 @@ # include "memfile.c.generated.h" #endif +static const char e_block_was_not_locked[] = N_("E293: Block was not locked"); + /// Open a new or existing memory block file. /// /// @param fname Name of file to use. @@ -335,7 +337,7 @@ void mf_put(memfile_T *mfp, bhdr_T *hp, bool dirty, bool infile) unsigned flags = hp->bh_flags; if ((flags & BH_LOCKED) == 0) { - iemsg(_("E293: block was not locked")); + iemsg(_(e_block_was_not_locked)); } flags &= ~BH_LOCKED; if (dirty) { diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 5dffd5e933..0815ba411c 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -248,7 +248,23 @@ typedef enum { # include "memline.c.generated.h" #endif -static char e_warning_pointer_block_corrupted[] +static const char e_ml_get_invalid_lnum_nr[] + = N_("E315: ml_get: Invalid lnum: %" PRId64); +static const char e_ml_get_cannot_find_line_nr_in_buffer_nr_str[] + = N_("E316: ml_get: Cannot find line %" PRId64 "in buffer %d %s"); +static const char e_pointer_block_id_wrong[] + = N_("E317: Pointer block id wrong"); +static const char e_pointer_block_id_wrong_two[] + = N_("E317: Pointer block id wrong 2"); +static const char e_pointer_block_id_wrong_three[] + = N_("E317: Pointer block id wrong 3"); +static const char e_pointer_block_id_wrong_four[] + = N_("E317: Pointer block id wrong 4"); +static const char e_line_number_out_of_range_nr_past_the_end[] + = N_("E322: Line number out of range: %" PRId64 " past the end"); +static const char e_line_count_wrong_in_block_nr[] + = N_("E323: Line count wrong in block %" PRId64); +static const char e_warning_pointer_block_corrupted[] = N_("E1364: Warning: Pointer block corrupted"); #if __has_feature(address_sanitizer) @@ -1826,7 +1842,7 @@ char *ml_get_buf(buf_T *buf, linenr_T lnum, bool will_change) // Avoid giving this message for a recursive call, may happen when // the GUI redraws part of the text. recursive++; - siemsg(_("E315: ml_get: invalid lnum: %" PRId64), (int64_t)lnum); + siemsg(_(e_ml_get_invalid_lnum_nr), (int64_t)lnum); recursive--; } ml_flush_line(buf); @@ -1861,7 +1877,7 @@ errorret: recursive++; get_trans_bufname(buf); shorten_dir(NameBuff); - siemsg(_("E316: ml_get: cannot find line %" PRId64 " in buffer %d %s"), + siemsg(_(e_ml_get_cannot_find_line_nr_in_buffer_nr_str), (int64_t)lnum, buf->b_fnum, NameBuff); recursive--; } @@ -2228,7 +2244,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo } PTR_BL *pp = hp->bh_data; // must be pointer block if (pp->pb_id != PTR_ID) { - iemsg(_("E317: pointer block id wrong 3")); + iemsg(_(e_pointer_block_id_wrong_three)); mf_put(mfp, hp, false, false); return FAIL; } @@ -2531,7 +2547,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) } PTR_BL *pp = hp->bh_data; // must be pointer block if (pp->pb_id != PTR_ID) { - iemsg(_("E317: pointer block id wrong 4")); + iemsg(_(e_pointer_block_id_wrong_four)); mf_put(mfp, hp, false, false); return FAIL; } @@ -2897,7 +2913,7 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) pp = (PTR_BL *)(dp); // must be pointer block if (pp->pb_id != PTR_ID) { - iemsg(_("E317: pointer block id wrong")); + iemsg(_(e_pointer_block_id_wrong)); goto error_block; } @@ -2935,10 +2951,10 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) } if (idx >= (int)pp->pb_count) { // past the end: something wrong! if (lnum > buf->b_ml.ml_line_count) { - siemsg(_("E322: line number out of range: %" PRId64 " past the end"), + siemsg(_(e_line_number_out_of_range_nr_past_the_end), (int64_t)lnum - buf->b_ml.ml_line_count); } else { - siemsg(_("E323: line count wrong in block %" PRId64), bnum); + siemsg(_(e_line_count_wrong_in_block_nr), bnum); } goto error_block; } @@ -3008,7 +3024,7 @@ static void ml_lineadd(buf_T *buf, int count) PTR_BL *pp = hp->bh_data; // must be pointer block if (pp->pb_id != PTR_ID) { mf_put(mfp, hp, false, false); - iemsg(_("E317: pointer block id wrong 2")); + iemsg(_(e_pointer_block_id_wrong_two)); break; } pp->pb_pointer[ip->ip_index].pe_line_count += count; diff --git a/src/nvim/message.c b/src/nvim/message.c index e8e2d57e41..63bcf3e069 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1250,6 +1250,7 @@ void wait_return(int redraw) || c == K_MOUSEDOWN || c == K_MOUSEUP || c == K_MOUSEMOVE); os_breakcheck(); + // Avoid that the mouse-up event causes visual mode to start. if (c == K_LEFTMOUSE || c == K_MIDDLEMOUSE || c == K_RIGHTMOUSE || c == K_X1MOUSE || c == K_X2MOUSE) { diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index e03eec184f..8189fde83c 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -223,7 +223,7 @@ static int get_fpos_of_mouse(pos_T *mpos) } // winpos and height may change in win_enter()! - if (winrow + wp->w_winbar_height >= wp->w_height) { // In (or below) status line + if (winrow >= wp->w_height_inner) { // In (or below) status line return IN_STATUS_LINE; } @@ -231,7 +231,7 @@ static int get_fpos_of_mouse(pos_T *mpos) return MOUSE_WINBAR; } - if (wincol >= wp->w_width) { // In vertical separator line + if (wincol >= wp->w_width_inner) { // In vertical separator line return IN_SEP_LINE; } @@ -557,10 +557,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) if (VIsual_active) { // set MOUSE_MAY_STOP_VIS if we are outside the selection // or the current window (might have false negative here) - if (mouse_row < curwin->w_winrow - || mouse_row > (curwin->w_winrow + curwin->w_height)) { - jump_flags = MOUSE_MAY_STOP_VIS; - } else if (m_pos_flag != IN_BUFFER) { + if (m_pos_flag != IN_BUFFER) { jump_flags = MOUSE_MAY_STOP_VIS; } else { if (VIsual_mode == 'V') { @@ -1413,9 +1410,22 @@ bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump) } else { row -= win_get_fill(win, lnum); } - count = plines_win_nofill(win, lnum, true); + count = plines_win_nofill(win, lnum, false); } else { - count = plines_win(win, lnum, true); + count = plines_win(win, lnum, false); + } + + if (win->w_skipcol > 0 && lnum == win->w_topline) { + // Adjust for 'smoothscroll' clipping the top screen lines. + // A similar formula is used in curs_columns(). + int width1 = win->w_width_inner - win_col_off(win); + int skip_lines = 0; + if (win->w_skipcol > width1) { + skip_lines = (win->w_skipcol - width1) / (width1 + win_col_off2(win)) + 1; + } else if (win->w_skipcol > 0) { + skip_lines = 1; + } + count -= skip_lines; } if (count > row) { @@ -1439,8 +1449,11 @@ bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump) col = off; } col += row * (win->w_width_inner - off); - // add skip column (for long wrapping line) - col += win->w_skipcol; + + // Add skip column for the topline. + if (lnum == win->w_topline) { + col += win->w_skipcol; + } } if (!win->w_p_wrap) { @@ -1651,8 +1664,6 @@ bool mouse_scroll_horiz(int dir) return false; } - curwin->w_leftcol = (colnr_T)leftcol; - // When the line of the cursor is too short, move the cursor to the // longest visible line. if (!virtual_active() @@ -1661,7 +1672,7 @@ bool mouse_scroll_horiz(int dir) curwin->w_cursor.col = 0; } - return leftcol_changed(); + return set_leftcol(leftcol); } /// Adjusts the clicked column position when 'conceallevel' > 0 diff --git a/src/nvim/move.c b/src/nvim/move.c index b749d07d15..c93c94f8c5 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -57,6 +57,43 @@ typedef struct { # include "move.c.generated.h" #endif +/// Reduce "n" for the screen lines skipped with "wp->w_skipcol". +static int adjust_plines_for_skipcol(win_T *wp, int n) +{ + if (wp->w_skipcol == 0) { + return n; + } + + int off = 0; + int width = wp->w_width_inner - win_col_off(wp); + if (wp->w_skipcol >= width) { + off++; + int skip = wp->w_skipcol - width; + width -= win_col_off2(wp); + while (skip >= width) { + off++; + skip -= width; + } + } + wp->w_valid &= ~VALID_WROW; + return n - off; +} + +/// Return how many lines "lnum" will take on the screen, taking into account +/// whether it is the first line, whether w_skipcol is non-zero and limiting to +/// the window height. +static int plines_correct_topline(win_T *wp, linenr_T lnum, linenr_T *nextp, bool *foldedp) +{ + int n = plines_win_full(wp, lnum, nextp, foldedp, true, false); + if (lnum == wp->w_topline) { + n = adjust_plines_for_skipcol(wp, n); + } + if (n > wp->w_height_inner) { + return wp->w_height_inner; + } + return n; +} + // Compute wp->w_botline for the current wp->w_topline. Can be called after // wp->w_topline changed. static void comp_botline(win_T *wp) @@ -78,7 +115,7 @@ static void comp_botline(win_T *wp) for (; lnum <= wp->w_buffer->b_ml.ml_line_count; lnum++) { linenr_T last = lnum; bool folded; - int n = plines_win_full(wp, lnum, &last, &folded, true); + int n = plines_correct_topline(wp, lnum, &last, &folded); if (lnum <= wp->w_cursor.lnum && last >= wp->w_cursor.lnum) { wp->w_cline_row = done; wp->w_cline_height = n; @@ -127,6 +164,51 @@ static void redraw_for_cursorcolumn(win_T *wp) } } +/// Calculates how much overlap the smoothscroll marker "<<<" overlaps with +/// buffer text for curwin. +/// Parameter "extra2" should be the padding on the 2nd line, not the first +/// line. +/// Returns the number of columns of overlap with buffer text, excluding the +/// extra padding on the ledge. +static int smoothscroll_marker_overlap(win_T *wp, int extra2) +{ + // We don't draw the <<< marker when in showbreak mode, thus no need to + // account for it. See grid_put_linebuf(). + if (*get_showbreak_value(wp) != NUL) { + return 0; + } + return extra2 > 3 ? 0 : 3 - extra2; +} + +/// Calculates the skipcol offset for window "wp" given how many +/// physical lines we want to scroll down. +static int skipcol_from_plines(win_T *wp, int plines_off) +{ + int width1 = wp->w_width_inner - win_col_off(wp); + + int skipcol = 0; + if (plines_off > 0) { + skipcol += width1; + } + if (plines_off > 1) { + skipcol += (width1 + win_col_off2(wp)) * (plines_off - 1); + } + return skipcol; +} + +/// Set wp->s_skipcol to zero and redraw later if needed. +static void reset_skipcol(win_T *wp) +{ + if (wp->w_skipcol != 0) { + wp->w_skipcol = 0; + + // Should use the least expensive way that displays all that changed. + // UPD_NOT_VALID is too expensive, UPD_REDRAW_TOP does not redraw + // enough when the top line gets another screen line. + redraw_later(wp, UPD_SOME_VALID); + } +} + // Update curwin->w_topline to move the cursor onto the screen. void update_topline(win_T *wp) { @@ -178,7 +260,7 @@ void update_topline(win_T *wp) bool check_topline = false; // If the cursor is above or near the top of the window, scroll the window // to show the line the cursor is in, with 'scrolloff' context. - if (wp->w_topline > 1) { + if (wp->w_topline > 1 || wp->w_skipcol > 0) { // If the cursor is above topline, scrolling is always needed. // If the cursor is far below topline and there is no folding, // scrolling down is never needed. @@ -186,6 +268,17 @@ void update_topline(win_T *wp) check_topline = true; } else if (check_top_offset()) { check_topline = true; + } else if (wp->w_skipcol > 0 && wp->w_cursor.lnum == wp->w_topline) { + colnr_T vcol; + + // Check that the cursor position is visible. Add columns for the + // smoothscroll marker "<<<" displayed in the top-left if needed. + getvvcol(wp, &wp->w_cursor, &vcol, NULL, NULL); + int smoothscroll_overlap = smoothscroll_marker_overlap(wp, + win_col_off(wp) - win_col_off2(wp)); + if (wp->w_skipcol + smoothscroll_overlap > vcol) { + check_topline = true; + } } } // Check if there are more filler lines than allowed. @@ -314,12 +407,9 @@ void update_topline(win_T *wp) if (wp->w_topline != old_topline || wp->w_topfill != old_topfill) { dollar_vcol = -1; - if (wp->w_skipcol != 0) { - wp->w_skipcol = 0; - redraw_later(wp, UPD_NOT_VALID); - } else { - redraw_later(wp, UPD_VALID); - } + redraw_later(wp, UPD_VALID); + reset_skipcol(wp); + // May need to set w_skipcol when cursor in w_topline. if (wp->w_cursor.lnum == wp->w_topline) { validate_cursor(); @@ -392,7 +482,15 @@ void check_cursor_moved(win_T *wp) |VALID_CHEIGHT|VALID_CROW|VALID_TOPLINE); wp->w_valid_cursor = wp->w_cursor; wp->w_valid_leftcol = wp->w_leftcol; + wp->w_valid_skipcol = wp->w_skipcol; wp->w_viewport_invalid = true; + } else if (wp->w_skipcol != wp->w_valid_skipcol) { + wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL + |VALID_CHEIGHT|VALID_CROW + |VALID_BOTLINE|VALID_BOTLINE_AP); + wp->w_valid_cursor = wp->w_cursor; + wp->w_valid_leftcol = wp->w_leftcol; + wp->w_valid_skipcol = wp->w_skipcol; } else if (wp->w_cursor.col != wp->w_valid_cursor.col || wp->w_leftcol != wp->w_valid_leftcol || wp->w_cursor.coladd != @@ -564,7 +662,7 @@ static void curs_rows(win_T *wp) } else { linenr_T last = lnum; bool folded; - int n = plines_win_full(wp, lnum, &last, &folded, false); + int n = plines_correct_topline(wp, lnum, &last, &folded); lnum = last + 1; if (folded && lnum > wp->w_cursor.lnum) { break; @@ -581,7 +679,7 @@ static void curs_rows(win_T *wp) && (!wp->w_lines[i].wl_valid || wp->w_lines[i].wl_lnum != wp->w_cursor.lnum))) { wp->w_cline_height = plines_win_full(wp, wp->w_cursor.lnum, NULL, - &wp->w_cline_folded, true); + &wp->w_cline_folded, true, true); } else if (i > wp->w_lines_valid) { // a line that is too long to fit on the last screen line wp->w_cline_height = 0; @@ -628,7 +726,7 @@ void validate_cheight(void) curwin->w_cline_height = plines_win_full(curwin, curwin->w_cursor.lnum, NULL, &curwin->w_cline_folded, - true); + true, true); curwin->w_valid |= VALID_CHEIGHT; } @@ -679,8 +777,8 @@ int curwin_col_off(void) } // Return the difference in column offset for the second screen line of a -// wrapped line. It's 8 if 'number' or 'relativenumber' is on and 'n' is in -// 'cpoptions'. +// wrapped line. It's positive if 'number' or 'relativenumber' is on and 'n' +// is in 'cpoptions'. int win_col_off2(win_T *wp) { if ((wp->w_p_nu || wp->w_p_rnu) && vim_strchr(p_cpo, CPO_NUMCOL) != NULL) { @@ -694,19 +792,14 @@ int curwin_col_off2(void) return win_col_off2(curwin); } -// Compute curwin->w_wcol and curwin->w_virtcol. -// Also updates curwin->w_wrow and curwin->w_cline_row. -// Also updates curwin->w_leftcol. +// Compute wp->w_wcol and wp->w_virtcol. +// Also updates wp->w_wrow and wp->w_cline_row. +// Also updates wp->w_leftcol. // @param may_scroll when true, may scroll horizontally void curs_columns(win_T *wp, int may_scroll) { - int n; - int width = 0; colnr_T startcol; colnr_T endcol; - colnr_T prev_skipcol; - long so = get_scrolloff_value(wp); - long siso = get_sidescrolloff_value(wp); // First make sure that w_topline is valid (after moving the cursor). update_topline(wp); @@ -736,8 +829,11 @@ void curs_columns(win_T *wp, int may_scroll) // Now compute w_wrow, counting screen lines from w_cline_row. wp->w_wrow = wp->w_cline_row; - int textwidth = wp->w_width_inner - extra; - if (textwidth <= 0) { + int n; + int width1 = wp->w_width_inner - extra; // text width for first screen line + int width2 = 0; // text width for second and later screen line + bool did_sub_skipcol = false; + if (width1 <= 0) { // No room for text, put cursor in last char of window. // If not wrapping, the last non-empty line. wp->w_wcol = wp->w_width_inner - 1; @@ -747,13 +843,28 @@ void curs_columns(win_T *wp, int may_scroll) wp->w_wrow = wp->w_height_inner - 1 - wp->w_empty_rows; } } else if (wp->w_p_wrap && wp->w_width_inner != 0) { - width = textwidth + win_col_off2(wp); + width2 = width1 + win_col_off2(wp); + + // skip columns that are not visible + if (wp->w_cursor.lnum == wp->w_topline + && wp->w_skipcol > 0 + && wp->w_wcol >= wp->w_skipcol) { + // Deduct by multiples of width2. This allows the long line wrapping + // formula below to correctly calculate the w_wcol value when wrapping. + if (wp->w_skipcol <= width1) { + wp->w_wcol -= width2; + } else { + wp->w_wcol -= width2 * (((wp->w_skipcol - width1) / width2) + 1); + } + + did_sub_skipcol = true; + } // long line wrapping, adjust wp->w_wrow if (wp->w_wcol >= wp->w_width_inner) { // this same formula is used in validate_cursor_col() - n = (wp->w_wcol - wp->w_width_inner) / width + 1; - wp->w_wcol -= n * width; + n = (wp->w_wcol - wp->w_width_inner) / width2 + 1; + wp->w_wcol -= n * width2; wp->w_wrow += n; // When cursor wraps to first char of next line in Insert @@ -775,6 +886,7 @@ void curs_columns(win_T *wp, int may_scroll) // If Cursor is right of the screen, scroll leftwards // If we get closer to the edge than 'sidescrolloff', scroll a little // extra + long siso = get_sidescrolloff_value(wp); assert(siso <= INT_MAX); int off_left = startcol - wp->w_leftcol - (int)siso; int off_right = @@ -785,8 +897,8 @@ void curs_columns(win_T *wp, int may_scroll) // When far off or not enough room on either side, put cursor in // middle of window. int new_leftcol; - if (p_ss == 0 || diff >= textwidth / 2 || off_right >= off_left) { - new_leftcol = wp->w_wcol - extra - textwidth / 2; + if (p_ss == 0 || diff >= width1 / 2 || off_right >= off_left) { + new_leftcol = curwin->w_wcol - extra - width1 / 2; } else { if (diff < p_ss) { assert(p_ss <= INT_MAX); @@ -823,9 +935,9 @@ void curs_columns(win_T *wp, int may_scroll) wp->w_wrow += win_get_fill(wp, wp->w_cursor.lnum); } - prev_skipcol = wp->w_skipcol; - int plines = 0; + long so = get_scrolloff_value(wp); + colnr_T prev_skipcol = wp->w_skipcol; if ((wp->w_wrow >= wp->w_height_inner || ((prev_skipcol > 0 || wp->w_wrow + so >= wp->w_height_inner) @@ -833,7 +945,7 @@ void curs_columns(win_T *wp, int may_scroll) >= wp->w_height_inner)) && wp->w_height_inner != 0 && wp->w_cursor.lnum == wp->w_topline - && width > 0 + && width2 > 0 && wp->w_width_inner != 0) { // Cursor past end of screen. Happens with a single line that does // not fit on screen. Find a skipcol to show the text around the @@ -842,7 +954,7 @@ void curs_columns(win_T *wp, int may_scroll) // 2: Less than "p_so" lines below // 3: both of them extra = 0; - if (wp->w_skipcol + so * width > wp->w_virtcol) { + if (wp->w_skipcol + so * width2 > wp->w_virtcol) { extra = 1; } // Compute last display line of the buffer line that we want at the @@ -857,13 +969,13 @@ void curs_columns(win_T *wp, int may_scroll) } else { n = plines; } - if ((colnr_T)n >= wp->w_height_inner + wp->w_skipcol / width - so) { + if ((colnr_T)n >= wp->w_height_inner + wp->w_skipcol / width2 - so) { extra += 2; } - if (extra == 3 || plines <= so * 2) { + if (extra == 3 || wp->w_height_inner <= so * 2) { // not enough room for 'scrolloff', put cursor in the middle - n = wp->w_virtcol / width; + n = wp->w_virtcol / width2; if (n > wp->w_height_inner / 2) { n -= wp->w_height_inner / 2; } else { @@ -873,51 +985,58 @@ void curs_columns(win_T *wp, int may_scroll) if (n > plines - wp->w_height_inner + 1) { n = plines - wp->w_height_inner + 1; } - wp->w_skipcol = n * width; + wp->w_skipcol = n * width2; } else if (extra == 1) { // less than 'scrolloff' lines above, decrease skipcol assert(so <= INT_MAX); - extra = (wp->w_skipcol + (int)so * width - wp->w_virtcol - + width - 1) / width; + extra = (wp->w_skipcol + (int)so * width2 - wp->w_virtcol + width2 - 1) / width2; if (extra > 0) { - if ((colnr_T)(extra * width) > wp->w_skipcol) { - extra = wp->w_skipcol / width; + if ((colnr_T)(extra * width2) > wp->w_skipcol) { + extra = wp->w_skipcol / width2; } - wp->w_skipcol -= extra * width; + wp->w_skipcol -= extra * width2; } } else if (extra == 2) { // less than 'scrolloff' lines below, increase skipcol - endcol = (n - wp->w_height_inner + 1) * width; + endcol = (n - wp->w_height_inner + 1) * width2; while (endcol > wp->w_virtcol) { - endcol -= width; + endcol -= width2; } if (endcol > wp->w_skipcol) { wp->w_skipcol = endcol; } } - wp->w_wrow -= wp->w_skipcol / width; + // adjust w_wrow for the changed w_skipcol + if (did_sub_skipcol) { + wp->w_wrow -= (wp->w_skipcol - prev_skipcol) / width2; + } else { + wp->w_wrow -= wp->w_skipcol / width2; + } + if (wp->w_wrow >= wp->w_height_inner) { // small window, make sure cursor is in it extra = wp->w_wrow - wp->w_height_inner + 1; - wp->w_skipcol += extra * width; + wp->w_skipcol += extra * width2; wp->w_wrow -= extra; } // extra could be either positive or negative - extra = ((int)prev_skipcol - (int)wp->w_skipcol) / width; + extra = (prev_skipcol - wp->w_skipcol) / width2; win_scroll_lines(wp, 0, extra); - } else { + } else if (!wp->w_p_sms) { wp->w_skipcol = 0; } if (prev_skipcol != wp->w_skipcol) { - redraw_later(wp, UPD_NOT_VALID); + redraw_later(wp, UPD_SOME_VALID); } - redraw_for_cursorcolumn(curwin); + redraw_for_cursorcolumn(wp); - // now w_leftcol is valid, avoid check_cursor_moved() thinking otherwise + // now w_leftcol and w_skipcol are valid, avoid check_cursor_moved() + // thinking otherwise wp->w_valid_leftcol = wp->w_leftcol; + wp->w_valid_skipcol = wp->w_skipcol; wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL; } @@ -966,18 +1085,18 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp, // similar to what is done in validate_cursor_col() colnr_T col = scol; col += off; - int width = wp->w_width - off + win_col_off2(wp); + int width = wp->w_width_inner - off + win_col_off2(wp); // long line wrapping, adjust row - if (wp->w_p_wrap && col >= (colnr_T)wp->w_width && width > 0) { + if (wp->w_p_wrap && col >= (colnr_T)wp->w_width_inner && width > 0) { // use same formula as what is used in curs_columns() - rowoff = visible_row ? ((col - wp->w_width) / width + 1) : 0; + rowoff = visible_row ? ((col - wp->w_width_inner) / width + 1) : 0; col -= rowoff * width; } col -= wp->w_leftcol; - if (col >= 0 && col < wp->w_width && row + rowoff <= wp->w_height) { + if (col >= 0 && col < wp->w_width_inner && row + rowoff <= wp->w_height_inner) { coloff = col - scol + (local ? 0 : wp->w_wincol + wp->w_wincol_off) + 1; row += local ? 0 : wp->w_winrow + wp->w_winrow_off; } else { @@ -1064,32 +1183,69 @@ void f_virtcol2col(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) bool scrolldown(long line_count, int byfold) { int done = 0; // total # of physical lines done + int width1 = 0; + int width2 = 0; + bool do_sms = curwin->w_p_wrap && curwin->w_p_sms; + + if (do_sms) { + width1 = curwin->w_width_inner - curwin_col_off(); + width2 = width1 + curwin_col_off2(); + } // Make sure w_topline is at the first of a sequence of folded lines. (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL); validate_cursor(); // w_wrow needs to be valid - while (line_count-- > 0) { + for (long todo = line_count; todo > 0; todo--) { if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline) && curwin->w_topfill < curwin->w_height_inner - 1) { curwin->w_topfill++; done++; } else { - if (curwin->w_topline == 1) { + // break when at the very top + if (curwin->w_topline == 1 && (!do_sms || curwin->w_skipcol < width1)) { break; } - curwin->w_topline--; - curwin->w_topfill = 0; - // A sequence of folded lines only counts for one logical line - linenr_T first; - if (hasFolding(curwin->w_topline, &first, NULL)) { - done++; - if (!byfold) { - line_count -= curwin->w_topline - first - 1; + if (do_sms && curwin->w_skipcol >= width1) { + // scroll a screen line down + if (curwin->w_skipcol >= width1 + width2) { + curwin->w_skipcol -= width2; + } else { + curwin->w_skipcol -= width1; } - curwin->w_botline -= curwin->w_topline - first; - curwin->w_topline = first; + redraw_later(curwin, UPD_NOT_VALID); + done++; } else { - done += plines_win_nofill(curwin, curwin->w_topline, true); + // scroll a text line down + curwin->w_topline--; + curwin->w_skipcol = 0; + curwin->w_topfill = 0; + // A sequence of folded lines only counts for one logical line + linenr_T first; + if (hasFolding(curwin->w_topline, &first, NULL)) { + done++; + if (!byfold) { + todo -= curwin->w_topline - first - 1; + } + curwin->w_botline -= curwin->w_topline - first; + curwin->w_topline = first; + } else { + if (do_sms) { + int size = (int)win_linetabsize(curwin, curwin->w_topline, + ml_get(curwin->w_topline), (colnr_T)MAXCOL); + if (size > width1) { + curwin->w_skipcol = width1; + size -= width1; + redraw_later(curwin, UPD_NOT_VALID); + } + while (size > width2) { + curwin->w_skipcol += width2; + size -= width2; + } + done++; + } else { + done += plines_win_nofill(curwin, curwin->w_topline, true); + } + } } } curwin->w_botline--; // approximate w_botline @@ -1134,9 +1290,39 @@ bool scrolldown(long line_count, int byfold) foldAdjustCursor(); coladvance(curwin->w_curswant); } + + if (curwin->w_cursor.lnum == curwin->w_topline && do_sms) { + long so = get_scrolloff_value(curwin); + long scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2; + + // make sure the cursor is in the visible text + validate_virtcol(); + long col = curwin->w_virtcol - curwin->w_skipcol + scrolloff_cols; + int row = 0; + if (col >= width1) { + col -= width1; + row++; + } + if (col > width2 && width2 > 0) { + row += (int)col / width2; + col = col % width2; + } + if (row >= curwin->w_height_inner) { + curwin->w_curswant = curwin->w_virtcol - (row - curwin->w_height_inner + 1) * width2; + coladvance(curwin->w_curswant); + } + } return moved; } +/// Return TRUE if scrollup() will scroll by screen line rather than text line. +static int scrolling_screenlines(bool byfold) +{ + return (curwin->w_p_wrap && curwin->w_p_sms) + || (byfold && hasAnyFolding(curwin)) + || (curwin->w_p_diff && !curwin->w_p_wrap); +} + /// Scroll the current window up by "line_count" logical lines. "CTRL-E" /// /// @param line_count number of lines to scroll @@ -1145,28 +1331,68 @@ bool scrollup(long line_count, int byfold) { linenr_T topline = curwin->w_topline; linenr_T botline = curwin->w_botline; + int do_sms = curwin->w_p_wrap && curwin->w_p_sms; - if ((byfold && hasAnyFolding(curwin)) - || win_may_fill(curwin)) { - // count each sequence of folded lines as one logical line - linenr_T lnum = curwin->w_topline; - while (line_count--) { + if (scrolling_screenlines(byfold) || win_may_fill(curwin)) { + int width1 = curwin->w_width_inner - curwin_col_off(); + int width2 = width1 + curwin_col_off2(); + unsigned size = 0; + linenr_T prev_topline = curwin->w_topline; + + if (do_sms) { + size = linetabsize(curwin, curwin->w_topline); + } + + // diff mode: first consume "topfill" + // 'smoothscroll': increase "w_skipcol" until it goes over the end of + // the line, then advance to the next line. + // folding: count each sequence of folded lines as one logical line. + for (long todo = line_count; todo > 0; todo--) { if (curwin->w_topfill > 0) { curwin->w_topfill--; } else { + linenr_T lnum = curwin->w_topline; if (byfold) { + // for a closed fold: go to the last line in the fold (void)hasFolding(lnum, NULL, &lnum); } - if (lnum >= curbuf->b_ml.ml_line_count) { - break; + if (lnum == curwin->w_topline && do_sms) { + // 'smoothscroll': increase "w_skipcol" until it goes over + // the end of the line, then advance to the next line. + int add = curwin->w_skipcol > 0 ? width2 : width1; + curwin->w_skipcol += add; + if ((unsigned)curwin->w_skipcol >= size) { + if (lnum == curbuf->b_ml.ml_line_count) { + // at the last screen line, can't scroll further + curwin->w_skipcol -= add; + break; + } + lnum++; + } + } else { + if (lnum >= curbuf->b_ml.ml_line_count) { + break; + } + lnum++; + } + + if (lnum > curwin->w_topline) { + // approximate w_botline + curwin->w_botline += lnum - curwin->w_topline; + curwin->w_topline = lnum; + curwin->w_topfill = win_get_fill(curwin, lnum); + curwin->w_skipcol = 0; + if (todo > 1 && do_sms) { + size = linetabsize(curwin, curwin->w_topline); + } } - lnum++; - curwin->w_topfill = win_get_fill(curwin, lnum); } } - // approximate w_botline - curwin->w_botline += lnum - curwin->w_topline; - curwin->w_topline = lnum; + + if (curwin->w_topline == prev_topline) { + // need to redraw even though w_topline didn't change + redraw_later(curwin, UPD_NOT_VALID); + } } else { curwin->w_topline += (linenr_T)line_count; curwin->w_botline += (linenr_T)line_count; // approximate w_botline @@ -1194,12 +1420,117 @@ bool scrollup(long line_count, int byfold) coladvance(curwin->w_curswant); } - bool moved = topline != curwin->w_topline - || botline != curwin->w_botline; + if (curwin->w_cursor.lnum == curwin->w_topline && do_sms && curwin->w_skipcol > 0) { + int col_off = curwin_col_off(); + int col_off2 = curwin_col_off2(); + + int width1 = curwin->w_width_inner - col_off; + int width2 = width1 + col_off2; + int extra2 = col_off - col_off2; + long so = get_scrolloff_value(curwin); + long scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2; + int space_cols = (curwin->w_height_inner - 1) * width2; + + // If we have non-zero scrolloff, just ignore the <<< marker as we are + // going past it anyway. + int smoothscroll_overlap = scrolloff_cols != 0 ? 0 : + smoothscroll_marker_overlap(curwin, extra2); + + // Make sure the cursor is in a visible part of the line, taking + // 'scrolloff' into account, but using screen lines. + // If there are not enough screen lines put the cursor in the middle. + if (scrolloff_cols > space_cols / 2) { + scrolloff_cols = space_cols / 2; + } + validate_virtcol(); + if (curwin->w_virtcol < curwin->w_skipcol + smoothscroll_overlap + scrolloff_cols) { + colnr_T col = curwin->w_virtcol; + + if (col < width1) { + col += width1; + } + while (col < curwin->w_skipcol + smoothscroll_overlap + scrolloff_cols) { + col += width2; + } + curwin->w_curswant = col; + coladvance(curwin->w_curswant); + + // validate_virtcol() marked various things as valid, but after + // moving the cursor they need to be recomputed + curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL); + } + } + + bool moved = topline != curwin->w_topline || botline != curwin->w_botline; return moved; } +/// Called after changing the cursor column: make sure that curwin->w_skipcol is +/// valid for 'smoothscroll'. +void adjust_skipcol(void) +{ + if (!curwin->w_p_wrap || !curwin->w_p_sms || curwin->w_cursor.lnum != curwin->w_topline) { + return; + } + + int width1 = curwin->w_width_inner - curwin_col_off(); + if (width1 <= 0) { + return; // no text will be displayed + } + int width2 = width1 + curwin_col_off2(); + long so = get_scrolloff_value(curwin); + long scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2; + bool scrolled = false; + + validate_cheight(); + if (curwin->w_cline_height == curwin->w_height_inner + // w_cline_height may be capped at w_height_inner, check there aren't + // actually more lines. + && plines_win(curwin, curwin->w_cursor.lnum, false) <= curwin->w_height_inner) { + // the line just fits in the window, don't scroll + reset_skipcol(curwin); + return; + } + + validate_virtcol(); + while (curwin->w_skipcol > 0 + && curwin->w_virtcol < curwin->w_skipcol + 3 + scrolloff_cols) { + // scroll a screen line down + if (curwin->w_skipcol >= width1 + width2) { + curwin->w_skipcol -= width2; + } else { + curwin->w_skipcol -= width1; + } + redraw_later(curwin, UPD_NOT_VALID); + scrolled = true; + validate_virtcol(); + } + if (scrolled) { + return; // don't scroll in the other direction now + } + long col = curwin->w_virtcol - curwin->w_skipcol + scrolloff_cols; + int row = 0; + if (col >= width1) { + col -= width1; + row++; + } + if (col > width2) { + row += (int)col / width2; + col = col % width2; + } + if (row >= curwin->w_height_inner) { + if (curwin->w_skipcol == 0) { + curwin->w_skipcol += width1; + row--; + } + if (row >= curwin->w_height_inner) { + curwin->w_skipcol += (row - curwin->w_height_inner) * width2; + } + redraw_later(curwin, UPD_NOT_VALID); + } +} + /// Don't end up with too many filler lines in the window. /// /// @param down when true scroll down when not enough space @@ -1316,7 +1647,8 @@ void scrollup_clamp(void) // a (wrapped) text line. Uses and sets "lp->fill". // Returns the height of the added line in "lp->height". // Lines above the first one are incredibly high: MAXCOL. -static void topline_back(win_T *wp, lineoff_T *lp) +// When "winheight" is true limit to window height. +static void topline_back_winheight(win_T *wp, lineoff_T *lp, int winheight) { if (lp->fill < win_get_fill(wp, lp->lnum)) { // Add a filler line @@ -1331,11 +1663,16 @@ static void topline_back(win_T *wp, lineoff_T *lp) // Add a closed fold lp->height = 1; } else { - lp->height = plines_win_nofill(wp, lp->lnum, true); + lp->height = plines_win_nofill(wp, lp->lnum, winheight); } } } +static void topline_back(win_T *wp, lineoff_T *lp) +{ + topline_back_winheight(wp, lp, true); +} + // Add one line below "lp->lnum". This can be a filler line, a closed fold or // a (wrapped) text line. Uses and sets "lp->fill". // Returns the height of the added line in "lp->height". @@ -1388,12 +1725,9 @@ static void topline_botline(lineoff_T *lp) // If "always" is true, always set topline (for "zt"). void scroll_cursor_top(int min_scroll, int always) { - int scrolled = 0; - linenr_T top; // just above displayed lines - linenr_T bot; // just below displayed lines linenr_T old_topline = curwin->w_topline; + int old_skipcol = curwin->w_skipcol; linenr_T old_topfill = curwin->w_topfill; - linenr_T new_topline; int off = (int)get_scrolloff_value(curwin); if (mouse_dragging > 0) { @@ -1406,11 +1740,14 @@ void scroll_cursor_top(int min_scroll, int always) // - moved at least 'scrolljump' lines and // - at least 'scrolloff' lines above and below the cursor validate_cheight(); + int scrolled = 0; int used = curwin->w_cline_height; // includes filler lines above if (curwin->w_cursor.lnum < curwin->w_topline) { scrolled = used; } + linenr_T top; // just above displayed lines + linenr_T bot; // just below displayed lines if (hasFolding(curwin->w_cursor.lnum, &top, &bot)) { top--; bot++; @@ -1418,7 +1755,7 @@ void scroll_cursor_top(int min_scroll, int always) top = curwin->w_cursor.lnum - 1; bot = curwin->w_cursor.lnum + 1; } - new_topline = top + 1; + linenr_T new_topline = top + 1; // "used" already contains the number of filler lines above, don't add it // again. @@ -1431,6 +1768,15 @@ void scroll_cursor_top(int min_scroll, int always) int i = hasFolding(top, &top, NULL) ? 1 // count one logical line for a sequence of folded lines : plines_win_nofill(curwin, top, true); + if (top < curwin->w_topline) { + scrolled += i; + } + + // If scrolling is needed, scroll at least 'sj' lines. + if ((new_topline >= curwin->w_topline || scrolled > min_scroll) && extra >= off) { + break; + } + used += i; if (extra + i <= off && bot < curbuf->b_ml.ml_line_count) { if (hasFolding(bot, NULL, &bot)) { @@ -1443,15 +1789,6 @@ void scroll_cursor_top(int min_scroll, int always) if (used > curwin->w_height_inner) { break; } - if (top < curwin->w_topline) { - scrolled += i; - } - - // If scrolling is needed, scroll at least 'sj' lines. - if ((new_topline >= curwin->w_topline || scrolled > min_scroll) - && extra >= off) { - break; - } extra += i; new_topline = top; @@ -1466,7 +1803,7 @@ void scroll_cursor_top(int min_scroll, int always) scroll_cursor_halfway(false, false); } else { // If "always" is false, only adjust topline to a lower value, higher - // value may happen with wrapping lines + // value may happen with wrapping lines. if (new_topline < curwin->w_topline || always) { curwin->w_topline = new_topline; } @@ -1481,7 +1818,13 @@ void scroll_cursor_top(int min_scroll, int always) } } check_topfill(curwin, false); + // TODO(vim): if the line doesn't fit may optimize w_skipcol + if (curwin->w_topline == curwin->w_cursor.lnum + && curwin->w_skipcol >= curwin->w_cursor.col) { + reset_skipcol(curwin); + } if (curwin->w_topline != old_topline + || curwin->w_skipcol != old_skipcol || curwin->w_topfill != old_topfill) { curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); @@ -1519,20 +1862,18 @@ void set_empty_rows(win_T *wp, int used) void scroll_cursor_bot(int min_scroll, int set_topbot) { int used; - int scrolled = 0; - int extra = 0; lineoff_T loff; - lineoff_T boff; - int fill_below_window; - linenr_T old_topline = curwin->w_topline; - int old_topfill = curwin->w_topfill; - linenr_T old_botline = curwin->w_botline; - int old_valid = curwin->w_valid; + linenr_T old_topline = curwin->w_topline; + int old_skipcol = curwin->w_skipcol; + int old_topfill = curwin->w_topfill; + linenr_T old_botline = curwin->w_botline; + int old_valid = curwin->w_valid; int old_empty_rows = curwin->w_empty_rows; - linenr_T cln = curwin->w_cursor.lnum; // Cursor Line Number - long so = get_scrolloff_value(curwin); + linenr_T cln = curwin->w_cursor.lnum; // Cursor Line Number if (set_topbot) { + bool set_skipcol = false; + used = 0; curwin->w_botline = cln + 1; loff.fill = 0; @@ -1540,9 +1881,24 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) curwin->w_topline > 1; curwin->w_topline = loff.lnum) { loff.lnum = curwin->w_topline; - topline_back(curwin, &loff); - if (loff.height == MAXCOL - || used + loff.height > curwin->w_height_inner) { + topline_back_winheight(curwin, &loff, false); + if (loff.height == MAXCOL) { + break; + } + if (used + loff.height > curwin->w_height_inner) { + if (curwin->w_p_sms && curwin->w_p_wrap) { + // 'smoothscroll' and 'wrap' are set. The above line is + // too long to show in its entirety, so we show just a part + // of it. + if (used < curwin->w_height_inner) { + int plines_offset = used + loff.height - curwin->w_height_inner; + used = curwin->w_height_inner; + curwin->w_topfill = loff.fill; + curwin->w_topline = loff.lnum; + curwin->w_skipcol = skipcol_from_plines(curwin, plines_offset); + set_skipcol = true; + } + } break; } used += loff.height; @@ -1551,8 +1907,15 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) set_empty_rows(curwin, used); curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; if (curwin->w_topline != old_topline - || curwin->w_topfill != old_topfill) { + || curwin->w_topfill != old_topfill + || set_skipcol + || curwin->w_skipcol != 0) { curwin->w_valid &= ~(VALID_WROW|VALID_CROW); + if (set_skipcol) { + redraw_later(curwin, UPD_NOT_VALID); + } else { + reset_skipcol(curwin); + } } } else { validate_botline(curwin); @@ -1561,16 +1924,50 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) // The lines of the cursor line itself are always used. used = plines_win_nofill(curwin, cln, true); - // If the cursor is below botline, we will at least scroll by the height - // of the cursor line. Correct for empty lines, which are really part of - // botline. + int scrolled = 0; + int min_scrolled = 1; + // If the cursor is on or below botline, we will at least scroll by the + // height of the cursor line, which is "used". Correct for empty lines, + // which are really part of botline. if (cln >= curwin->w_botline) { scrolled = used; if (cln == curwin->w_botline) { scrolled -= curwin->w_empty_rows; } + min_scrolled = scrolled; + if (curwin->w_p_sms && curwin->w_p_wrap) { + // 'smoothscroll' and 'wrap' are set + if (cln > curwin->w_botline) { + // add screen lines below w_botline + for (linenr_T lnum = curwin->w_botline + 1; lnum <= cln; lnum++) { + min_scrolled += plines_win_nofill(curwin, lnum, true); + } + } + + // Calculate how many screen lines the current top line of window + // occupies. If it is occupying more than the entire window, we + // need to scroll the additional clipped lines to scroll past the + // top line before we can move on to the other lines. + int top_plines = plines_win_nofill(curwin, curwin->w_topline, false); + int skip_lines = 0; + int width1 = curwin->w_width_inner - curwin_col_off(); + int width2 = width1 + curwin_col_off2(); + // similar formula is used in curs_columns() + if (curwin->w_skipcol > width1) { + skip_lines += (curwin->w_skipcol - width1) / width2 + 1; + } else if (curwin->w_skipcol > 0) { + skip_lines = 1; + } + + top_plines -= skip_lines; + if (top_plines > curwin->w_height_inner) { + scrolled += (top_plines - curwin->w_height_inner); + min_scrolled += (top_plines - curwin->w_height_inner); + } + } } + lineoff_T boff; // Stop counting lines to scroll when // - hitting start of the file // - scrolled nothing or at least 'sj' lines @@ -1582,9 +1979,10 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) } loff.fill = 0; boff.fill = 0; - fill_below_window = win_get_fill(curwin, curwin->w_botline) - - curwin->w_filler_rows; + int fill_below_window = win_get_fill(curwin, curwin->w_botline) - curwin->w_filler_rows; + int extra = 0; + long so = get_scrolloff_value(curwin); while (loff.lnum > 1) { // Stop when scrolled nothing or at least "min_scroll", found "extra" // context for 'scrolloff' and counted all lines below the window. @@ -1670,13 +2068,27 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) if (line_count >= curwin->w_height_inner && line_count > min_scroll) { scroll_cursor_halfway(false, true); } else { - scrollup(line_count, true); + // With 'smoothscroll' scroll at least the height of the cursor line, + // unless it would move the cursor. + if (curwin->w_p_wrap && curwin->w_p_sms && line_count < min_scrolled + && (curwin->w_cursor.lnum < curwin->w_topline + || (curwin->w_virtcol - curwin->w_skipcol >= + curwin->w_width_inner - curwin_col_off()))) { + line_count = min_scrolled; + } + if (line_count > 0) { + if (scrolling_screenlines(true)) { + scrollup(scrolled, true); // TODO(vim): + } else { + scrollup(line_count, true); + } + } } // If topline didn't change we need to restore w_botline and w_empty_rows // (we changed them). // If topline did change, update_screen() will set botline. - if (curwin->w_topline == old_topline && set_topbot) { + if (curwin->w_topline == old_topline && curwin->w_skipcol == old_skipcol && set_topbot) { curwin->w_botline = old_botline; curwin->w_empty_rows = old_empty_rows; curwin->w_valid = old_valid; @@ -1691,27 +2103,65 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) /// void scroll_cursor_halfway(bool atend, bool prefer_above) { - int above = 0; - int topfill = 0; - int below = 0; - lineoff_T loff; - lineoff_T boff; linenr_T old_topline = curwin->w_topline; - - loff.lnum = boff.lnum = curwin->w_cursor.lnum; + lineoff_T loff = { .lnum = curwin->w_cursor.lnum }; + lineoff_T boff = { .lnum = curwin->w_cursor.lnum }; (void)hasFolding(loff.lnum, &loff.lnum, &boff.lnum); int used = plines_win_nofill(curwin, loff.lnum, true); loff.fill = 0; boff.fill = 0; linenr_T topline = loff.lnum; + colnr_T skipcol = 0; + bool set_skipcol = false; + + int half_height = 0; + bool smooth_scroll = false; + if (curwin->w_p_sms && curwin->w_p_wrap) { + // 'smoothscroll' and 'wrap' are set + smooth_scroll = true; + half_height = (curwin->w_height_inner - used) / 2; + used = 0; + } + int topfill = 0; while (topline > 1) { + // If using smoothscroll, we can precisely scroll to the + // exact point where the cursor is halfway down the screen. + if (smooth_scroll) { + topline_back_winheight(curwin, &loff, false); + if (loff.height == MAXCOL) { + break; + } else { + used += loff.height; + } + if (used > half_height) { + if (used - loff.height < half_height) { + int plines_offset = used - half_height; + loff.height -= plines_offset; + used = half_height; + + topline = loff.lnum; + topfill = loff.fill; + skipcol = skipcol_from_plines(curwin, plines_offset); + set_skipcol = true; + } + break; + } + topline = loff.lnum; + topfill = loff.fill; + continue; + } + + // If not using smoothscroll, we have to iteratively find how many + // lines to scroll down to roughly fit the cursor. // This may not be right in the middle if the lines' // physical height > 1 (e.g. 'wrap' is on). // Depending on "prefer_above" we add a line above or below first. // Loop twice to avoid duplicating code. bool done = false; + int above = 0; + int below = 0; for (int round = 1; round <= 2; round++) { if (prefer_above ? (round == 2 && below < above) @@ -1757,8 +2207,15 @@ void scroll_cursor_halfway(bool atend, bool prefer_above) } } - if (!hasFolding(topline, &curwin->w_topline, NULL)) { + if (!hasFolding(topline, &curwin->w_topline, NULL) + && (curwin->w_topline != topline || set_skipcol || curwin->w_skipcol != 0)) { curwin->w_topline = topline; + if (set_skipcol) { + curwin->w_skipcol = skipcol; + redraw_later(curwin, UPD_NOT_VALID); + } else { + reset_skipcol(curwin); + } } curwin->w_topfill = topfill; if (old_topline > curwin->w_topline + curwin->w_height_inner) { @@ -1809,6 +2266,16 @@ void cursor_correct(void) return; } + if (curwin->w_p_sms && !curwin->w_p_wrap) { + // 'smoothscroll is active + if (curwin->w_cline_height == curwin->w_height_inner) { + // The cursor line just fits in the window, don't scroll. + reset_skipcol(curwin); + return; + } + // TODO(vim): If the cursor line doesn't fit in the window then only adjust w_skipcol. + } + // Narrow down the area where the cursor can be put by taking lines from // the top and the bottom until: // - the desired context lines are found @@ -1861,9 +2328,10 @@ void cursor_correct(void) curwin->w_viewport_invalid = true; } -// move screen 'count' pages up or down and update screen -// -// return FAIL for failure, OK otherwise +/// Move screen "count" pages up ("dir" is BACKWARD) or down ("dir" is FORWARD) +/// and update the screen. +/// +/// @return FAIL for failure, OK otherwise. int onepage(Direction dir, long count) { long n; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 5a5286905f..890fa0f80a 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -106,6 +106,8 @@ static int VIsual_mode_orig = NUL; // saved Visual mode # include "normal.c.generated.h" #endif +static const char e_changelist_is_empty[] = N_("E664: Changelist is empty"); + static inline void normal_state_init(NormalState *s) { memset(s, 0, sizeof(NormalState)); @@ -2158,9 +2160,8 @@ void check_scrollbind(linenr_T topline_diff, long leftcol_diff) } // do the horizontal scroll - if (want_hor && curwin->w_leftcol != tgt_leftcol) { - curwin->w_leftcol = tgt_leftcol; - leftcol_changed(); + if (want_hor) { + (void)set_leftcol(tgt_leftcol); } } @@ -2432,7 +2433,7 @@ bool find_decl(char *ptr, size_t len, bool locally, bool thisblock, int flags_ar /// @return true if able to move cursor, false otherwise. static bool nv_screengo(oparg_T *oap, int dir, long dist) { - int linelen = linetabsize(get_cursor_line_ptr()); + int linelen = linetabsize_str(get_cursor_line_ptr()); bool retval = true; bool atend = false; int col_off1; // margin offset for first screen line @@ -2494,7 +2495,7 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) retval = false; break; } - linelen = linetabsize(get_cursor_line_ptr()); + linelen = linetabsize_str(get_cursor_line_ptr()); if (linelen > width1) { int w = (((linelen - width1 - 1) / width2) + 1) * width2; assert(curwin->w_curswant <= INT_MAX - w); @@ -2525,7 +2526,7 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) if (curwin->w_curswant >= width1) { curwin->w_curswant -= width2; } - linelen = linetabsize(get_cursor_line_ptr()); + linelen = linetabsize_str(get_cursor_line_ptr()); } } } @@ -2566,6 +2567,8 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) if (atend) { curwin->w_curswant = MAXCOL; // stick in the last column } + adjust_skipcol(); + return retval; } @@ -2633,6 +2636,7 @@ static void nv_scroll_line(cmdarg_T *cap) void scroll_redraw(int up, long count) { linenr_T prev_topline = curwin->w_topline; + int prev_skipcol = curwin->w_skipcol; int prev_topfill = curwin->w_topfill; linenr_T prev_lnum = curwin->w_cursor.lnum; @@ -2640,7 +2644,7 @@ void scroll_redraw(int up, long count) scrollup(count, true) : scrolldown(count, true); - if (get_scrolloff_value(curwin)) { + if (get_scrolloff_value(curwin) > 0) { // Adjust the cursor position for 'scrolloff'. Mark w_topline as // valid, otherwise the screen jumps back at the end of the file. cursor_correct(); @@ -2651,6 +2655,7 @@ void scroll_redraw(int up, long count) // we get stuck at one position. Don't move the cursor up if the // first line of the buffer is already on the screen while (curwin->w_topline == prev_topline + && curwin->w_skipcol == prev_skipcol && curwin->w_topfill == prev_topfill) { if (up) { if (curwin->w_cursor.lnum > prev_lnum @@ -2890,27 +2895,21 @@ static void nv_zet(cmdarg_T *cap) case 'h': case K_LEFT: if (!curwin->w_p_wrap) { - if ((colnr_T)cap->count1 > curwin->w_leftcol) { - curwin->w_leftcol = 0; - } else { - curwin->w_leftcol -= (colnr_T)cap->count1; - } - leftcol_changed(); + (void)set_leftcol((colnr_T)cap->count1 > curwin->w_leftcol + ? 0 : curwin->w_leftcol - (colnr_T)cap->count1); } break; - // "zL" - scroll screen left half-page + // "zL" - scroll window left half-page case 'L': cap->count1 *= curwin->w_width_inner / 2; FALLTHROUGH; - // "zl" - scroll screen to the left + // "zl" - scroll window to the left if not wrapping case 'l': case K_RIGHT: if (!curwin->w_p_wrap) { - // scroll the window left - curwin->w_leftcol += (colnr_T)cap->count1; - leftcol_changed(); + (void)set_leftcol(curwin->w_leftcol + (colnr_T)cap->count1); } break; @@ -4966,7 +4965,7 @@ static void nv_pcmark(cmdarg_T *cap) move_res = nv_mark_move_to(cap, flags, fm); } else if (cap->cmdchar == 'g') { if (curbuf->b_changelistlen == 0) { - emsg(_("E664: changelist is empty")); + emsg(_(e_changelist_is_empty)); } else if (cap->count1 < 0) { emsg(_("E662: At start of changelist")); } else { @@ -5349,7 +5348,7 @@ static void nv_g_dollar_cmd(cmdarg_T *cap) colnr_T vcol; getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol); - if (vcol >= curwin->w_leftcol + curwin->w_width - col_off) { + if (vcol >= curwin->w_leftcol + curwin->w_width_inner - col_off) { curwin->w_cursor.col--; } } @@ -5493,7 +5492,7 @@ static void nv_g_cmd(cmdarg_T *cap) case 'M': oap->motion_type = kMTCharWise; oap->inclusive = false; - i = linetabsize(get_cursor_line_ptr()); + i = linetabsize_str(get_cursor_line_ptr()); if (cap->count0 > 0 && cap->count0 <= 100) { coladvance((colnr_T)(i * cap->count0 / 100)); } else { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 89fe9b464d..bb66bb5731 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -96,6 +96,9 @@ struct block_def { # include "ops.c.generated.h" #endif +static const char e_search_pattern_and_expression_register_may_not_contain_two_or_more_lines[] + = N_("E883: Search pattern and expression register may not contain two or more lines"); + // Flags for third item in "opchars". #define OPF_LINES 1 // operator always works on lines #define OPF_CHANGE 2 // operator changes text @@ -5043,8 +5046,7 @@ void write_reg_contents_lst(int name, char **strings, bool must_append, MotionTy if (strings[0] == NULL) { s = ""; } else if (strings[1] != NULL) { - emsg(_("E883: search pattern and expression register may not " - "contain two or more lines")); + emsg(_(e_search_pattern_and_expression_register_may_not_contain_two_or_more_lines)); return; } write_reg_contents_ex(name, s, -1, must_append, yank_type, block_len); @@ -5503,7 +5505,7 @@ void cursor_pos_info(dict_T *dict) validate_virtcol(); col_print(buf1, sizeof(buf1), (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1); - col_print(buf2, sizeof(buf2), (int)strlen(p), linetabsize(p)); + col_print(buf2, sizeof(buf2), (int)strlen(p), linetabsize_str(p)); if (char_count_cursor == byte_count_cursor && char_count == byte_count) { diff --git a/src/nvim/option.c b/src/nvim/option.c index 3264d80a2f..a977fc4f86 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2621,6 +2621,19 @@ static const char *did_set_showtabline(optset_T *args FUNC_ATTR_UNUSED) return NULL; } +/// Process the updated 'smoothscroll' option value. +static const char *did_set_smoothscroll(optset_T *args FUNC_ATTR_UNUSED) +{ + win_T *win = (win_T *)args->os_win; + if (win->w_p_sms) { + return NULL; + } + + win->w_skipcol = 0; + changed_line_abv_curs_win(win); + return NULL; +} + /// Process the new 'foldlevel' option value. static const char *did_set_foldlevel(optset_T *args FUNC_ATTR_UNUSED) { @@ -4417,6 +4430,8 @@ static char *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win) return (char *)&(win->w_p_rlc); case PV_SCROLL: return (char *)&(win->w_p_scr); + case PV_SMS: + return (char *)&(win->w_p_sms); case PV_WRAP: return (char *)&(win->w_p_wrap); case PV_LBR: @@ -4648,6 +4663,7 @@ void copy_winopt(winopt_T *from, winopt_T *to) to->wo_briopt = copy_option_val(from->wo_briopt); to->wo_scb = from->wo_scb; to->wo_scb_save = from->wo_scb_save; + to->wo_sms = from->wo_sms; to->wo_crb = from->wo_crb; to->wo_crb_save = from->wo_crb_save; to->wo_spell = from->wo_spell; diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 40e77550aa..944cc583b3 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -949,6 +949,7 @@ enum { WV_RLC, WV_SCBIND, WV_SCROLL, + WV_SMS, WV_SISO, WV_SO, WV_SPELL, diff --git a/src/nvim/options.lua b/src/nvim/options.lua index e028fbb6a6..c4a85969c0 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2012,6 +2012,15 @@ return { defaults={if_true=0} }, { + full_name='smoothscroll', abbreviation='sms', + short_desc=N_("scroll by screen lines when 'wrap' is set"), + type='bool', scope={'window'}, + pv_name='p_sms', + redraw={'current_window'}, + defaults={if_true=0}, + cb='did_set_smoothscroll' + }, + { full_name='scrollback', abbreviation='scbk', short_desc=N_("lines to scroll with CTRL-U and CTRL-D"), type='number', scope={'buffer'}, diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 96d383bbb1..1c75d5bd03 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -61,8 +61,10 @@ static const char e_unclosed_expression_sequence[] = N_("E540: Unclosed expression sequence"); +static const char e_comma_required[] + = N_("E536: Comma required"); static const char e_unbalanced_groups[] - = N_("E542: unbalanced groups"); + = N_("E542: Unbalanced groups"); static const char e_backupext_and_patchmode_are_equal[] = N_("E589: 'backupext' and 'patchmode' are equal"); static const char e_showbreak_contains_unprintable_or_wide_character[] @@ -514,7 +516,7 @@ const char *did_set_mousescroll(optset_T *args FUNC_ATTR_UNUSED) // Verify that only digits follow the colon. for (size_t i = 4; i < length; i++) { if (!ascii_isdigit(string[i])) { - return N_("E548: digit expected"); + return N_("E5080: Digit expected"); } } @@ -1743,7 +1745,7 @@ const char *did_set_foldmarker(optset_T *args) char *p = vim_strchr(*varp, ','); if (p == NULL) { - return N_("E536: comma required"); + return e_comma_required; } if (p == *varp || p[1] == NUL) { diff --git a/src/nvim/plines.c b/src/nvim/plines.c index b2a4ac710d..3e69e547cb 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -189,10 +189,11 @@ int plines_win_col(win_T *wp, linenr_T lnum, long column) /// @param[out] nextp if not NULL, the line after a fold /// @param[out] foldedp if not NULL, whether lnum is on a fold /// @param[in] cache whether to use the window's cache for folds +/// @param[in] winheight when true limit to window height /// /// @return the total number of screen lines int plines_win_full(win_T *wp, linenr_T lnum, linenr_T *const nextp, bool *const foldedp, - const bool cache) + const bool cache, const bool winheight) { bool folded = hasFoldingWin(wp, lnum, NULL, nextp, cache, NULL); if (foldedp) { @@ -201,9 +202,9 @@ int plines_win_full(win_T *wp, linenr_T lnum, linenr_T *const nextp, bool *const if (folded) { return 1; } else if (lnum == wp->w_topline) { - return plines_win_nofill(wp, lnum, true) + wp->w_topfill; + return plines_win_nofill(wp, lnum, winheight) + wp->w_topfill; } - return plines_win(wp, lnum, true); + return plines_win(wp, lnum, winheight); } int plines_m_win(win_T *wp, linenr_T first, linenr_T last) @@ -212,7 +213,7 @@ int plines_m_win(win_T *wp, linenr_T first, linenr_T last) while (first <= last) { linenr_T next = first; - count += plines_win_full(wp, first, &next, NULL, false); + count += plines_win_full(wp, first, &next, NULL, false, true); first = next + 1; } return count; @@ -243,12 +244,12 @@ int win_chartabsize(win_T *wp, char *p, colnr_T col) /// @param s /// /// @return Number of characters the string will take on the screen. -int linetabsize(char *s) +int linetabsize_str(char *s) { return linetabsize_col(0, s); } -/// Like linetabsize(), but "s" starts at column "startcol". +/// Like linetabsize_str(), but "s" starts at column "startcol". /// /// @param startcol /// @param s @@ -265,7 +266,7 @@ int linetabsize_col(int startcol, char *s) return cts.cts_vcol; } -/// Like linetabsize(), but for a given window instead of the current one. +/// Like linetabsize_str(), but for a given window instead of the current one. /// /// @param wp /// @param line @@ -284,6 +285,13 @@ unsigned win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len) return (unsigned)cts.cts_vcol; } +/// Return the number of cells line "lnum" of window "wp" will take on the +/// screen, taking into account the size of a tab and text properties. +unsigned linetabsize(win_T *wp, linenr_T lnum) +{ + return win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum, false), (colnr_T)MAXCOL); +} + /// Prepare the structure passed to chartabsize functions. /// /// "line" is the start of the line, "ptr" is the first relevant character. diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index 3246ef2c71..d404aa9647 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -133,8 +133,6 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i validate_cursor_col(); int above_row = 0; int below_row = cmdline_row; - int row_off = 0; - int col_off = 0; // wildoptions=pum if (State == MODE_CMDLINE) { @@ -145,7 +143,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i // anchor position: the start of the completed word pum_win_row = curwin->w_wrow; if (pum_rl) { - cursor_col = curwin->w_width - curwin->w_wcol - 1; + cursor_col = curwin->w_width_inner - curwin->w_wcol - 1; } else { cursor_col = curwin->w_wcol; } @@ -153,10 +151,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i pum_anchor_grid = (int)curwin->w_grid.target->handle; pum_win_row += curwin->w_grid.row_offset; cursor_col += curwin->w_grid.col_offset; - if (ui_has(kUIMultigrid)) { - row_off = curwin->w_winrow; - col_off = curwin->w_wincol; - } else if (curwin->w_grid.target != &default_grid) { + if (!ui_has(kUIMultigrid) && curwin->w_grid.target != &default_grid) { pum_anchor_grid = (int)default_grid.handle; pum_win_row += curwin->w_winrow; cursor_col += curwin->w_wincol; @@ -202,6 +197,21 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i } } + int min_row = 0; + int min_col = 0; + int max_col = Columns; + int win_start_col = curwin->w_wincol; + int win_end_col = W_ENDCOL(curwin); + if (!(State & MODE_CMDLINE) && ui_has(kUIMultigrid)) { + above_row -= curwin->w_winrow; + below_row = MAX(below_row - curwin->w_winrow, curwin->w_grid.rows); + min_row = -curwin->w_winrow; + min_col = -curwin->w_wincol; + max_col = MAX(Columns - curwin->w_wincol, curwin->w_grid.cols); + win_start_col = 0; + win_end_col = curwin->w_grid.cols; + } + // Figure out the size and position of the pum. if (size < PUM_DEF_HEIGHT) { pum_height = size; @@ -215,8 +225,8 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i // Put the pum below "pum_win_row" if possible. // If there are few lines decide on where there is more room. - if (pum_win_row + row_off + 2 >= below_row - pum_height - && pum_win_row + row_off - above_row > (below_row - above_row) / 2) { + if (pum_win_row + 2 >= below_row - pum_height + && pum_win_row - above_row > (below_row - above_row) / 2) { // pum above "pum_win_row" pum_above = true; @@ -232,12 +242,12 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i } } - if (pum_win_row + row_off >= size + context_lines) { + if (pum_win_row - min_row >= size + context_lines) { pum_row = pum_win_row - size - context_lines; pum_height = size; } else { - pum_row = -row_off; - pum_height = pum_win_row + row_off - context_lines; + pum_row = min_row; + pum_height = pum_win_row - min_row - context_lines; } if (p_ph > 0 && pum_height > p_ph) { @@ -262,8 +272,8 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i } pum_row = pum_win_row + context_lines; - if (size > below_row - row_off - pum_row) { - pum_height = below_row - row_off - pum_row; + if (size > below_row - pum_row) { + pum_height = below_row - pum_row; } else { pum_height = size; } @@ -279,9 +289,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i } // If there is a preview window above avoid drawing over it. - if (pvwin != NULL && pum_row + row_off < above_row && pum_height > above_row) { - pum_row = above_row - row_off; - pum_height = pum_win_row + row_off - above_row; + if (pvwin != NULL && pum_row < above_row && pum_height > above_row) { + pum_row = above_row; + pum_height = pum_win_row - above_row; } pum_array = array; @@ -307,19 +317,19 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i def_width = max_width; } - if (((cursor_col + col_off < Columns - p_pw - || cursor_col + col_off < Columns - max_width) && !pum_rl) - || (pum_rl && (cursor_col + col_off > p_pw - || cursor_col + col_off > max_width))) { + if (((cursor_col < max_col - p_pw + || cursor_col < max_col - max_width) && !pum_rl) + || (pum_rl && (cursor_col - min_col > p_pw + || cursor_col - min_col > max_width))) { // align pum with "cursor_col" pum_col = cursor_col; // start with the maximum space available if (pum_rl) { - pum_width = pum_col + col_off - pum_scrollbar + 1; + pum_width = pum_col - min_col - pum_scrollbar + 1; } else { - assert(Columns - col_off - pum_col - pum_scrollbar >= 0); - pum_width = Columns - col_off - pum_col - pum_scrollbar; + assert(max_col - pum_col - pum_scrollbar >= 0); + pum_width = max_col - pum_col - pum_scrollbar; } if (pum_width > max_width + pum_kind_width + pum_extra_width + 1 @@ -331,42 +341,42 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i if (pum_width < p_pw) { pum_width = (int)p_pw; } - } else if (((cursor_col + col_off > p_pw - || cursor_col + col_off > max_width) && !pum_rl) - || (pum_rl && (cursor_col + col_off < Columns - p_pw - || cursor_col + col_off < Columns - max_width))) { + } else if (((cursor_col - min_col > p_pw + || cursor_col - min_col > max_width) && !pum_rl) + || (pum_rl && (cursor_col < max_col - p_pw + || cursor_col < max_col - max_width))) { // align pum edge with "cursor_col" - if (pum_rl && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1) { + if (pum_rl && win_end_col < max_width + pum_scrollbar + 1) { pum_col = cursor_col + max_width + pum_scrollbar + 1; - if (pum_col + col_off >= Columns) { - pum_col = Columns - col_off - 1; + if (pum_col >= max_col) { + pum_col = max_col - 1; } } else if (!pum_rl) { - if (curwin->w_wincol > Columns - max_width - pum_scrollbar + if (win_start_col > max_col - max_width - pum_scrollbar && max_width <= p_pw) { // use full width to end of the screen - pum_col = Columns - col_off - max_width - pum_scrollbar; - if (pum_col + col_off < 0) { - pum_col = -col_off; + pum_col = max_col - max_width - pum_scrollbar; + if (pum_col < min_col) { + pum_col = min_col; } } } if (pum_rl) { - pum_width = pum_col + col_off - pum_scrollbar + 1; + pum_width = pum_col - min_col - pum_scrollbar + 1; } else { - pum_width = Columns - col_off - pum_col - pum_scrollbar; + pum_width = max_col - pum_col - pum_scrollbar; } if (pum_width < p_pw) { pum_width = (int)p_pw; if (pum_rl) { - if (pum_width > pum_col + col_off) { - pum_width = pum_col + col_off; + if (pum_width > pum_col - min_col) { + pum_width = pum_col - min_col; } } else { - if (pum_width >= Columns - col_off - pum_col) { - pum_width = Columns - col_off - pum_col - 1; + if (pum_width >= max_col - pum_col) { + pum_width = max_col - pum_col - 1; } } } else if (pum_width > max_width + pum_kind_width + pum_extra_width + 1 @@ -377,26 +387,23 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i } } } - } else if (Columns < def_width) { + } else if (max_col - min_col < def_width) { // not enough room, will use what we have if (pum_rl) { - assert(Columns - col_off - 1 >= INT_MIN); - pum_col = Columns - col_off - 1; + pum_col = max_col - 1; } else { - pum_col = -col_off; + pum_col = min_col; } - pum_width = Columns - 1; + pum_width = max_col - min_col - 1; } else { if (max_width > p_pw) { // truncate max_width = (int)p_pw; } - if (pum_rl) { - pum_col = max_width - col_off - 1; + pum_col = min_col + max_width - 1; } else { - assert(Columns - max_width >= 0); - pum_col = Columns - col_off - max_width; + pum_col = max_col - max_width; } pum_width = max_width - pum_scrollbar; } @@ -435,18 +442,26 @@ void pum_redraw(void) int col_off = 0; bool extra_space = false; if (pum_rl) { - col_off = pum_width; - if (pum_col < curwin->w_wincol + curwin->w_width - 1) { + col_off = pum_width - 1; + assert(!(State & MODE_CMDLINE)); + int win_end_col = ui_has(kUIMultigrid) ? curwin->w_grid.cols : W_ENDCOL(curwin); + if (pum_col < win_end_col - 1) { grid_width += 1; extra_space = true; } - } else if (pum_col > 0) { - grid_width += 1; - col_off = 1; - extra_space = true; + } else { + int min_col = (!(State & MODE_CMDLINE) && ui_has(kUIMultigrid)) ? -curwin->w_wincol : 0; + if (pum_col > min_col) { + grid_width += 1; + col_off = 1; + extra_space = true; + } } if (pum_scrollbar > 0) { grid_width++; + if (pum_rl) { + col_off++; + } } grid_assign_handle(&pum_grid); @@ -974,42 +989,44 @@ void pum_set_event_info(dict_T *dict) static void pum_position_at_mouse(int min_width) { - int row_off = 0; - int col_off = 0; + int min_row = 0; + int max_row = Rows; + int max_col = Columns; if (mouse_grid > 1) { win_T *wp = get_win_by_grid_handle(mouse_grid); if (wp != NULL) { - row_off = wp->w_winrow; - col_off = wp->w_wincol; + min_row = -wp->w_winrow; + max_row = MAX(Rows - wp->w_winrow, wp->w_grid.rows); + max_col = MAX(Columns - wp->w_wincol, wp->w_grid.cols); } } pum_anchor_grid = mouse_grid; - if (Rows - row_off - mouse_row > pum_size) { + if (max_row - mouse_row > pum_size) { // Enough space below the mouse row. pum_above = false; pum_row = mouse_row + 1; - if (pum_height > Rows - row_off - pum_row) { - pum_height = Rows - row_off - pum_row; + if (pum_height > max_row - pum_row) { + pum_height = max_row - pum_row; } } else { // Show above the mouse row, reduce height if it does not fit. pum_above = true; pum_row = mouse_row - pum_size; - if (pum_row + row_off < 0) { - pum_height += pum_row + row_off; - pum_row = -row_off; + if (pum_row < min_row) { + pum_height += pum_row - min_row; + pum_row = min_row; } } - if (Columns - col_off - mouse_col >= pum_base_width - || Columns - col_off - mouse_col > min_width) { + if (max_col - mouse_col >= pum_base_width + || max_col - mouse_col > min_width) { // Enough space to show at mouse column. pum_col = mouse_col; } else { // Not enough space, right align with window. - pum_col = Columns - col_off - (pum_base_width > min_width ? min_width : pum_base_width); + pum_col = max_col - (pum_base_width > min_width ? min_width : pum_base_width); } - pum_width = Columns - col_off - pum_col; + pum_width = max_col - pum_col; if (pum_width > pum_base_width + 1) { pum_width = pum_base_width + 1; } diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index deb183f5c0..d08af2de6c 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -100,10 +100,20 @@ static int toggle_Magic(int x) return (semsg((m), (c) ? "" : "\\", (a)), rc_did_emsg = true, (void *)NULL) #define EMSG2_RET_FAIL(m, c) \ return (semsg((m), (c) ? "" : "\\"), rc_did_emsg = true, FAIL) -#define EMSG_ONE_RET_NULL EMSG2_RET_NULL(_("E369: invalid item in %s%%[]"), reg_magic == MAGIC_ALL) +#define EMSG_ONE_RET_NULL EMSG2_RET_NULL(_(e_invalid_item_in_str_brackets), reg_magic == MAGIC_ALL) #define MAX_LIMIT (32767L << 16L) +static const char e_invalid_character_after_str_at[] + = N_("E59: Invalid character after %s@"); +static const char e_invalid_use_of_underscore[] + = N_("E63: Invalid use of \\_"); +static const char e_pattern_uses_more_memory_than_maxmempattern[] + = N_("E363: Pattern uses more memory than 'maxmempattern'"); +static const char e_invalid_item_in_str_brackets[] + = N_("E369: Invalid item in %s%%[]"); +static const char e_missing_delimiter_after_search_pattern_str[] + = N_("E654: Missing delimiter after search pattern: %s"); static const char e_missingbracket[] = N_("E769: Missing ] after %s["); static const char e_reverse_range[] = N_("E944: Reverse range in character class"); static const char e_large_class[] = N_("E945: Range too large in character class"); @@ -491,7 +501,7 @@ char *skip_regexp_err(char *startp, int delim, int magic) char *p = skip_regexp(startp, delim, magic); if (*p != delim) { - semsg(_("E654: missing delimiter after search pattern: %s"), startp); + semsg(_(e_missing_delimiter_after_search_pattern_str), startp); return NULL; } return p; diff --git a/src/nvim/regexp_bt.c b/src/nvim/regexp_bt.c index 08b97b60ba..0e1ea9d3b0 100644 --- a/src/nvim/regexp_bt.c +++ b/src/nvim/regexp_bt.c @@ -1748,7 +1748,7 @@ static uint8_t *regatom(int *flagp) case Magic('U'): p = (uint8_t *)vim_strchr((char *)classchars, no_Magic(c)); if (p == NULL) { - EMSG_RET_NULL(_("E63: invalid use of \\_")); + EMSG_RET_NULL(_(e_invalid_use_of_underscore)); } // When '.' is followed by a composing char ignore the dot, so that // the composing char is matched here. @@ -2531,7 +2531,7 @@ static uint8_t *regpiece(int *flagp) } } if (lop == END) { - EMSG2_RET_NULL(_("E59: invalid character after %s@"), + EMSG2_RET_NULL(_(e_invalid_character_after_str_at), reg_magic == MAGIC_ALL); } // Look behind must match with behind_pos. @@ -3436,7 +3436,7 @@ static regitem_T *regstack_push(regstate_T state, uint8_t *scan) regitem_T *rp; if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) { - emsg(_(e_maxmempat)); + emsg(_(e_pattern_uses_more_memory_than_maxmempattern)); return NULL; } ga_grow(®stack, sizeof(regitem_T)); @@ -4402,7 +4402,7 @@ static bool regmatch(uint8_t *scan, proftime_T *tm, int *timed_out) // follows. The code is below. Parameters are stored in // a regstar_T on the regstack. if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) { - emsg(_(e_maxmempat)); + emsg(_(e_pattern_uses_more_memory_than_maxmempattern)); status = RA_FAIL; } else { ga_grow(®stack, sizeof(regstar_T)); @@ -4439,7 +4439,7 @@ static bool regmatch(uint8_t *scan, proftime_T *tm, int *timed_out) case NOBEHIND: // Need a bit of room to store extra positions. if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) { - emsg(_(e_maxmempat)); + emsg(_(e_pattern_uses_more_memory_than_maxmempattern)); status = RA_FAIL; } else { ga_grow(®stack, sizeof(regbehind_T)); diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 08927fe83d..7d58a87ab7 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -4927,7 +4927,7 @@ skip_add: const size_t newsize = (size_t)newlen * sizeof(nfa_thread_T); if ((long)(newsize >> 10) >= p_mmp) { - emsg(_(e_maxmempat)); + emsg(_(e_pattern_uses_more_memory_than_maxmempattern)); depth--; return NULL; } @@ -5218,7 +5218,7 @@ static regsubs_T *addstate_here(nfa_list_T *l, nfa_state_T *state, regsubs_T *su const size_t newsize = (size_t)newlen * sizeof(nfa_thread_T); if ((long)(newsize >> 10) >= p_mmp) { - emsg(_(e_maxmempat)); + emsg(_(e_pattern_uses_more_memory_than_maxmempattern)); return NULL; } nfa_thread_T *const newl = xmalloc(newsize); diff --git a/src/nvim/search.c b/src/nvim/search.c index 094476a5ee..8f1e9148d4 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -59,6 +59,11 @@ # include "search.c.generated.h" #endif +static const char e_search_hit_top_without_match_for_str[] + = N_("E384: Search hit TOP without match for: %s"); +static const char e_search_hit_bottom_without_match_for_str[] + = N_("E385: Search hit BOTTOM without match for: %s"); + // This file contains various searching-related routines. These fall into // three groups: // 1. string searches (for /, ?, n, and N) @@ -943,11 +948,9 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, if (p_ws) { semsg(_(e_patnotf2), mr_pattern); } else if (lnum == 0) { - semsg(_("E384: search hit TOP without match for: %s"), - mr_pattern); + semsg(_(e_search_hit_top_without_match_for_str), mr_pattern); } else { - semsg(_("E385: search hit BOTTOM without match for: %s"), - mr_pattern); + semsg(_(e_search_hit_bottom_without_match_for_str), mr_pattern); } } return FAIL; diff --git a/src/nvim/spell.c b/src/nvim/spell.c index d1d1b9180f..84875261f1 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -3621,7 +3621,7 @@ bool valid_spellfile(const char *val) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { for (const char *s = val; *s != NUL; s++) { - if (!vim_isfilec((uint8_t)(*s)) && *s != ',' && *s != ' ') { + if (!vim_is_fname_char((uint8_t)(*s))) { return false; } } diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index b93dd55042..5e4a429cc7 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -323,6 +323,10 @@ enum { }; static const char *e_spell_trunc = N_("E758: Truncated spell file"); +static const char e_error_while_reading_sug_file_str[] + = N_("E782: Error while reading .sug file: %s"); +static const char e_duplicate_char_in_map_entry[] + = N_("E783: Duplicate char in MAP entry"); static const char *e_illegal_character_in_word = N_("E1280: Illegal character in word"); static const char *e_afftrailing = N_("Trailing text in %s line %d: %s"); static const char *e_affname = N_("Affix name too long in %s line %d: %s"); @@ -956,7 +960,7 @@ void suggest_load_files(void) if (spell_read_tree(fd, &slang->sl_sbyts, NULL, &slang->sl_sidxs, false, 0) != 0) { someerror: - semsg(_("E782: error while reading .sug file: %s"), + semsg(_(e_error_while_reading_sug_file_str), slang->sl_fname); slang_clear_sug(slang); goto nextone; @@ -4971,9 +4975,12 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang) spin->si_sugtree = true; // Go through the whole case-folded tree, soundfold each word and put it - // in the trie. + // in the trie. Bail out if the tree is empty. byts = slang->sl_fbyts; idxs = slang->sl_fidxs; + if (byts == NULL || idxs == NULL) { + return FAIL; + } arridx[0] = 0; curi[0] = 1; @@ -5857,7 +5864,7 @@ static void set_map_str(slang_T *lp, char *map) } else { // This should have been checked when generating the .spl // file. - emsg(_("E783: duplicate char in MAP entry")); + emsg(_(e_duplicate_char_in_map_entry)); xfree(b); } } else { diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 61e00f85dc..5231ec0841 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -1834,12 +1834,26 @@ void f_strcharpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) const size_t slen = strlen(p); int nbyte = 0; + varnumber_T skipcc = false; bool error = false; varnumber_T nchar = tv_get_number_chk(&argvars[1], &error); if (!error) { + if (argvars[2].v_type != VAR_UNKNOWN + && argvars[3].v_type != VAR_UNKNOWN) { + skipcc = tv_get_bool(&argvars[3]); + if (skipcc < 0 || skipcc > 1) { + semsg(_(e_using_number_as_bool_nr), skipcc); + return; + } + } + if (nchar > 0) { while (nchar > 0 && (size_t)nbyte < slen) { - nbyte += utf_ptr2len(p + nbyte); + if (skipcc) { + nbyte += utfc_ptr2len(p + nbyte); + } else { + nbyte += utf_ptr2len(p + nbyte); + } nchar--; } } else { @@ -1855,7 +1869,11 @@ void f_strcharpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) if (off < 0) { len += 1; } else { - len += utf_ptr2len(p + off); + if (skipcc) { + len += utfc_ptr2len(p + off); + } else { + len += utf_ptr2len(p + off); + } } charlen--; } diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 840cd60f13..d237972e40 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -59,6 +59,12 @@ static bool did_syntax_onoff = false; #define SPO_COUNT 7 static const char e_illegal_arg[] = N_("E390: Illegal argument: %s"); +static const char e_contains_argument_not_accepted_here[] + = N_("E395: Contains argument not accepted here"); +static const char e_invalid_cchar_value[] + = N_("E844: Invalid cchar value"); +static const char e_trailing_char_after_rsb_str_str[] + = N_("E890: Trailing char after ']': %s]%s"); // The patterns that are being searched for are stored in a syn_pattern. // A match item consists of one pattern. @@ -3874,7 +3880,7 @@ static char *get_syn_options(char *arg, syn_opt_arg_T *opt, int *conceal_char, i if (flagtab[fidx].argtype == 1) { if (!opt->has_cont_list) { - emsg(_("E395: contains argument not accepted here")); + emsg(_(e_contains_argument_not_accepted_here)); return NULL; } if (get_id_list(&arg, 8, &opt->cont_list, skip) == FAIL) { @@ -3893,7 +3899,7 @@ static char *get_syn_options(char *arg, syn_opt_arg_T *opt, int *conceal_char, i *conceal_char = utf_ptr2char(arg + 6); arg += utfc_ptr2len(arg + 6) - 1; if (!vim_isprintc_strict(*conceal_char)) { - emsg(_("E844: invalid cchar value")); + emsg(_(e_invalid_cchar_value)); return NULL; } arg = skipwhite(arg + 7); @@ -4111,8 +4117,7 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) } if (p[1] == ']') { if (p[2] != NUL) { - semsg(_("E890: trailing char after ']': %s]%s"), - kw, &p[2]); + semsg(_(e_trailing_char_after_rsb_str_str), kw, &p[2]); goto error; } kw = p + 1; diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 2ee1a0335b..ff16a10d8e 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -188,10 +188,18 @@ typedef struct { # include "tag.c.generated.h" #endif -static const char *bottommsg = N_("E555: at bottom of tag stack"); -static const char *topmsg = N_("E556: at top of tag stack"); -static const char *recurmsg = N_("E986: cannot modify the tag stack within tagfunc"); -static const char *tfu_inv_ret_msg = N_("E987: invalid return value from tagfunc"); +static const char e_tag_stack_empty[] + = N_("E73: Tag stack empty"); +static const char e_tag_not_found_str[] + = N_("E426: Tag not found: %s"); +static const char e_at_bottom_of_tag_stack[] + = N_("E555: At bottom of tag stack"); +static const char e_at_top_of_tag_stack[] + = N_("E556: At top of tag stack"); +static const char e_cannot_modify_tag_stack_within_tagfunc[] + = N_("E986: Cannot modify the tag stack within tagfunc"); +static const char e_invalid_return_value_from_tagfunc[] + = N_("E987: Invalid return value from tagfunc"); static const char e_window_unexpectedly_close_while_searching_for_tags[] = N_("E1299: Window unexpectedly closed while searching for tags"); @@ -304,7 +312,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose) static int flags; if (tfu_in_use) { - emsg(_(recurmsg)); + emsg(_(e_cannot_modify_tag_stack_within_tagfunc)); return; } @@ -391,14 +399,14 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose) if (g_do_tagpreview != 0 ? ptag_entry.tagname == NULL : tagstacklen == 0) { // empty stack - emsg(_(e_tagstack)); + emsg(_(e_tag_stack_empty)); goto end_do_tag; } if (type == DT_POP) { // go to older position const bool old_KeyTyped = KeyTyped; if ((tagstackidx -= count) < 0) { - emsg(_(bottommsg)); + emsg(_(e_at_bottom_of_tag_stack)); if (tagstackidx + count == 0) { // We did [num]^T from the bottom of the stack tagstackidx = 0; @@ -408,7 +416,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose) // way to the bottom now. tagstackidx = 0; } else if (tagstackidx >= tagstacklen) { // count == 0? - emsg(_(topmsg)); + emsg(_(e_at_top_of_tag_stack)); goto end_do_tag; } @@ -457,10 +465,10 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose) // go to the last one. Don't store the cursor // position. tagstackidx = tagstacklen - 1; - emsg(_(topmsg)); + emsg(_(e_at_top_of_tag_stack)); save_pos = false; } else if (tagstackidx < 0) { // must have been count == 0 - emsg(_(bottommsg)); + emsg(_(e_at_bottom_of_tag_stack)); tagstackidx = 0; goto end_do_tag; } @@ -638,7 +646,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose) if (num_matches <= 0) { if (verbose) { - semsg(_("E426: tag not found: %s"), name); + semsg(_(e_tag_not_found_str), name); } g_do_tagpreview = 0; } else { @@ -1281,7 +1289,7 @@ static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flag } if (rettv.v_type != VAR_LIST || !rettv.vval.v_list) { tv_clear(&rettv); - emsg(_(tfu_inv_ret_msg)); + emsg(_(e_invalid_return_value_from_tagfunc)); return FAIL; } taglist = rettv.vval.v_list; @@ -1295,7 +1303,7 @@ static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flag int name_only = flags & TAG_NAMES; if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT) { - emsg(_(tfu_inv_ret_msg)); + emsg(_(e_invalid_return_value_from_tagfunc)); break; } @@ -1341,7 +1349,7 @@ static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flag } if (!res_name || !res_fname || !res_cmd) { - emsg(_(tfu_inv_ret_msg)); + emsg(_(e_invalid_return_value_from_tagfunc)); break; } @@ -3560,7 +3568,7 @@ int set_tagstack(win_T *wp, const dict_T *d, int action) // not allowed to alter the tag stack entries from inside tagfunc if (tfu_in_use) { - emsg(_(recurmsg)); + emsg(_(e_cannot_modify_tag_stack_within_tagfunc)); return FAIL; } diff --git a/src/nvim/testing.c b/src/nvim/testing.c index 907940f64a..430d6713ff 100644 --- a/src/nvim/testing.c +++ b/src/nvim/testing.c @@ -30,16 +30,26 @@ #include "nvim/types.h" #include "nvim/vim.h" +/// Type of assert_* check being performed +typedef enum { + ASSERT_EQUAL, + ASSERT_NOTEQUAL, + ASSERT_MATCH, + ASSERT_NOTMATCH, + ASSERT_FAILS, + ASSERT_OTHER, +} assert_type_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "testing.c.generated.h" #endif static const char e_assert_fails_second_arg[] - = N_("E856: assert_fails() second argument must be a string or a list with one or two strings"); + = N_("E856: \"assert_fails()\" second argument must be a string or a list with one or two strings"); static const char e_assert_fails_fourth_argument[] - = N_("E1115: assert_fails() fourth argument must be a number"); + = N_("E1115: \"assert_fails()\" fourth argument must be a number"); static const char e_assert_fails_fifth_argument[] - = N_("E1116: assert_fails() fifth argument must be a string"); + = N_("E1116: \"assert_fails()\" fifth argument must be a string"); static const char e_calling_test_garbagecollect_now_while_v_testing_is_not_set[] = N_("E1142: Calling test_garbagecollect_now() while v:testing is not set"); @@ -121,7 +131,7 @@ static void ga_concat_shorten_esc(garray_T *gap, const char *str) for (const char *p = str; *p != NUL; p++) { int same_len = 1; const char *s = p; - const int c = mb_ptr2char_adv(&s); + const int c = mb_cptr2char_adv(&s); const int clen = (int)(s - p); while (*s != NUL && c == utf_ptr2char(s)) { same_len++; @@ -142,7 +152,7 @@ static void ga_concat_shorten_esc(garray_T *gap, const char *str) } /// Fill "gap" with information about an assert error. -static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char *exp_str, +static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, const char *exp_str, typval_T *exp_tv_arg, typval_T *got_tv_arg, assert_type_T atype) { char *tofree; @@ -220,7 +230,13 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char *exp_str ga_concat_shorten_esc(gap, tofree); xfree(tofree); } else { + if (atype == ASSERT_FAILS) { + ga_concat(gap, "'"); + } ga_concat_shorten_esc(gap, exp_str); + if (atype == ASSERT_FAILS) { + ga_concat(gap, "'"); + } } if (atype != ASSERT_NOTEQUAL) { @@ -500,11 +516,21 @@ void f_assert_exception(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) /// "assert_fails(cmd [, error [, msg]])" function void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - const char *const cmd = tv_get_string_chk(&argvars[0]); garray_T ga; - int save_trylevel = trylevel; + const int save_trylevel = trylevel; const int called_emsg_before = called_emsg; const char *wrong_arg_msg = NULL; + char *tofree = NULL; + + if (tv_check_for_string_or_number_arg(argvars, 0) == FAIL + || tv_check_for_opt_string_or_list_arg(argvars, 1) == FAIL + || (argvars[1].v_type != VAR_UNKNOWN + && (argvars[2].v_type != VAR_UNKNOWN + && (tv_check_for_opt_number_arg(argvars, 3) == FAIL + || (argvars[3].v_type != VAR_UNKNOWN + && tv_check_for_opt_string_arg(argvars, 4) == FAIL))))) { + return; + } // trylevel must be zero for a ":throw" command to be considered failed trylevel = 0; @@ -512,7 +538,13 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) in_assert_fails = true; no_wait_return++; + const char *const cmd = tv_get_string_chk(&argvars[0]); do_cmdline_cmd(cmd); + + // reset here for any errors reported below + trylevel = save_trylevel; + suppress_errthrow = false; + if (called_emsg == called_emsg_before) { prepare_assert_error(&ga); ga_concat(&ga, "command did not fail: "); @@ -523,6 +555,7 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } else if (argvars[1].v_type != VAR_UNKNOWN) { char buf[NUMBUFLEN]; const char *expected; + const char *expected_str = NULL; bool error_found = false; int error_found_index = 1; char *actual = emsg_assert_fails_msg == NULL ? "[unknown]" : emsg_assert_fails_msg; @@ -538,14 +571,23 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } const typval_T *tv = TV_LIST_ITEM_TV(tv_list_first(list)); expected = tv_get_string_buf_chk(tv, buf); + if (expected == NULL) { + goto theend; + } if (!pattern_match(expected, actual, false)) { error_found = true; + expected_str = expected; } else if (tv_list_len(list) == 2) { + // make a copy, an error in pattern_match() may free it + tofree = actual = xstrdup(get_vim_var_str(VV_ERRMSG)); tv = TV_LIST_ITEM_TV(tv_list_last(list)); - actual = get_vim_var_str(VV_ERRMSG); expected = tv_get_string_buf_chk(tv, buf); + if (expected == NULL) { + goto theend; + } if (!pattern_match(expected, actual, false)) { error_found = true; + expected_str = expected; } } } else { @@ -589,8 +631,8 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) actual_tv.v_type = VAR_STRING; actual_tv.vval.v_string = actual; } - fill_assert_error(&ga, &argvars[2], NULL, - &argvars[error_found_index], &actual_tv, ASSERT_OTHER); + fill_assert_error(&ga, &argvars[2], expected_str, + &argvars[error_found_index], &actual_tv, ASSERT_FAILS); ga_concat(&ga, ": "); assert_append_cmd_or_arg(&ga, argvars, cmd); assert_error(&ga); @@ -612,6 +654,7 @@ theend: msg_reset_scroll(); lines_left = Rows; XFREE_CLEAR(emsg_assert_fails_msg); + xfree(tofree); set_vim_var_string(VV_ERRMSG, NULL, 0); if (wrong_arg_msg != NULL) { emsg(_(wrong_arg_msg)); @@ -639,16 +682,9 @@ static int assert_inrange(typval_T *argvars) if (factual < flower || factual > fupper) { garray_T ga; prepare_assert_error(&ga); - if (argvars[3].v_type != VAR_UNKNOWN) { - char *const tofree = encode_tv2string(&argvars[3], NULL); - ga_concat(&ga, tofree); - xfree(tofree); - } else { - char msg[80]; - vim_snprintf(msg, sizeof(msg), "Expected range %g - %g, but got %g", - flower, fupper, factual); - ga_concat(&ga, msg); - } + char expected_str[200]; + vim_snprintf(expected_str, sizeof(expected_str), "range %g - %g,", flower, fupper); + fill_assert_error(&ga, &argvars[3], expected_str, NULL, &argvars[2], ASSERT_OTHER); assert_error(&ga); ga_clear(&ga); return 1; @@ -664,13 +700,11 @@ static int assert_inrange(typval_T *argvars) if (actual < lower || actual > upper) { garray_T ga; prepare_assert_error(&ga); - - char msg[55]; - vim_snprintf(msg, sizeof(msg), + char expected_str[200]; + vim_snprintf(expected_str, sizeof(expected_str), "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",", lower, upper); // -V576 - fill_assert_error(&ga, &argvars[3], msg, NULL, &argvars[2], - ASSERT_INRANGE); + fill_assert_error(&ga, &argvars[3], expected_str, NULL, &argvars[2], ASSERT_OTHER); assert_error(&ga); ga_clear(&ga); return 1; @@ -682,6 +716,13 @@ static int assert_inrange(typval_T *argvars) /// "assert_inrange(lower, upper[, msg])" function void f_assert_inrange(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { + if (tv_check_for_float_or_nr_arg(argvars, 0) == FAIL + || tv_check_for_float_or_nr_arg(argvars, 1) == FAIL + || tv_check_for_float_or_nr_arg(argvars, 2) == FAIL + || tv_check_for_opt_string_arg(argvars, 3) == FAIL) { + return; + } + rettv->vval.v_number = assert_inrange(argvars); } diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index b267027d02..2de1467511 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -977,7 +977,7 @@ void tui_grid_clear(TUIData *tui, Integer g) UGrid *grid = &tui->grid; ugrid_clear(grid); kv_size(tui->invalid_regions) = 0; - clear_region(tui, 0, grid->height, 0, grid->width, 0); + clear_region(tui, 0, tui->height, 0, tui->width, 0); } void tui_grid_cursor_goto(TUIData *tui, Integer grid, Integer row, Integer col) diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 64c16ed192..35f46e512d 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -137,6 +137,13 @@ typedef struct { # include "undo.c.generated.h" #endif +static const char e_undo_list_corrupt[] + = N_("E439: Undo list corrupt"); +static const char e_undo_line_missing[] + = N_("E440: Undo line missing"); +static const char e_write_error_in_undo_file_str[] + = N_("E829: Write error in undo file: %s"); + // used in undo_end() to report number of added and deleted lines static long u_newcount, u_oldcount; @@ -1340,7 +1347,7 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, write_error: fclose(fp); if (!write_ok) { - semsg(_("E829: write error in undo file: %s"), file_name); + semsg(_(e_write_error_in_undo_file_str), file_name); } if (buf->b_ffname != NULL) { @@ -2809,7 +2816,7 @@ static void u_unch_branch(u_header_T *uhp) static u_entry_T *u_get_headentry(buf_T *buf) { if (buf->b_u_newhead == NULL || buf->b_u_newhead->uh_entry == NULL) { - iemsg(_("E439: undo list corrupt")); + iemsg(_(e_undo_list_corrupt)); return NULL; } return buf->b_u_newhead->uh_entry; @@ -2832,7 +2839,7 @@ static void u_getbot(buf_T *buf) linenr_T extra = buf->b_ml.ml_line_count - uep->ue_lcount; uep->ue_bot = uep->ue_top + (linenr_T)uep->ue_size + 1 + extra; if (uep->ue_bot < 1 || uep->ue_bot > buf->b_ml.ml_line_count) { - iemsg(_("E440: undo line missing")); + iemsg(_(e_undo_line_missing)); uep->ue_bot = uep->ue_top + 1; // assume all lines deleted, will // get all the old lines back // without deleting the current diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c index b18318b5ff..11f2620aaa 100644 --- a/src/nvim/usercmd.c +++ b/src/nvim/usercmd.c @@ -44,10 +44,12 @@ garray_T ucmds = { 0, 0, sizeof(ucmd_T), 4, NULL }; -static const char e_complete_used_without_allowing_arguments[] - = N_("E1208: -complete used without allowing arguments"); +static const char e_argument_required_for_str[] + = N_("E179: Argument required for %s"); static const char e_no_such_user_defined_command_str[] = N_("E184: No such user-defined command: %s"); +static const char e_complete_used_without_allowing_arguments[] + = N_("E1208: -complete used without allowing arguments"); static const char e_no_such_user_defined_command_in_current_buffer_str[] = N_("E1237: No such user-defined command in current buffer: %s"); @@ -808,7 +810,7 @@ invalid_count: } } else if (STRNICMP(attr, "complete", attrlen) == 0) { if (val == NULL) { - emsg(_("E179: argument required for -complete")); + semsg(_(e_argument_required_for_str), "-complete"); return FAIL; } @@ -819,7 +821,7 @@ invalid_count: } else if (STRNICMP(attr, "addr", attrlen) == 0) { *argt |= EX_RANGE; if (val == NULL) { - emsg(_("E179: argument required for -addr")); + semsg(_(e_argument_required_for_str), "-addr"); return FAIL; } if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL) { diff --git a/src/nvim/window.c b/src/nvim/window.c index 2e037aa699..a15be27f74 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -670,6 +670,8 @@ void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err) tabpage_T *tab = win_find_tabpage(win); + // no redrawing and don't set the window title + RedrawingDisabled++; if (noautocmd) { block_autocmds(); } @@ -699,6 +701,7 @@ void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err) if (noautocmd) { unblock_autocmds(); } + RedrawingDisabled--; } /// Create a new float. diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index af6fbf092a..c81b6e90cc 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -361,6 +361,12 @@ describe('API', function() eq('', eval('v:errmsg')) -- v:errmsg was not updated. eq('', eval('v:exception')) end) + + it('gives E493 instead of prompting on backwards range', function() + command('split') + eq('Vim(windo):E493: Backwards range given: 2,1windo echo', + pcall_err(command, '2,1windo echo')) + end) end) describe('nvim_command_output', function() diff --git a/test/functional/editor/mode_insert_spec.lua b/test/functional/editor/mode_insert_spec.lua index 6f16b4e685..12f450520a 100644 --- a/test/functional/editor/mode_insert_spec.lua +++ b/test/functional/editor/mode_insert_spec.lua @@ -86,7 +86,7 @@ describe('insert-mode', function() {0:~ }| {4: }| ={2:{}} | - {5:E731: using Dictionary as a String} | + {5:E731: Using a Dictionary as a String} | {6:Press ENTER or type command to continue}^ | ]]) feed('<CR>') diff --git a/test/functional/ex_cmds/excmd_spec.lua b/test/functional/ex_cmds/excmd_spec.lua index 14cc2b8387..a92329ede5 100644 --- a/test/functional/ex_cmds/excmd_spec.lua +++ b/test/functional/ex_cmds/excmd_spec.lua @@ -11,20 +11,24 @@ describe('Ex cmds', function() clear() end) + local function check_excmd_err(cmd, err) + eq(err .. ': ' .. cmd, pcall_err(command, cmd)) + end + it('handle integer overflow from user-input #5555', function() command(':9999999999999999999999999999999999999999') command(':later 9999999999999999999999999999999999999999') command(':echo expand("#<9999999999999999999999999999999999999999")') command(':lockvar 9999999999999999999999999999999999999999') command(':winsize 9999999999999999999999999999999999999999 9999999999999999999999999999999999999999') - eq('Vim(tabnext):E475: Invalid argument: 9999999999999999999999999999999999999999', - pcall_err(command, ':tabnext 9999999999999999999999999999999999999999')) - eq('Vim(Next):E939: Positive count required', - pcall_err(command, ':N 9999999999999999999999999999999999999999')) + check_excmd_err(':tabnext 9999999999999999999999999999999999999999', + 'Vim(tabnext):E475: Invalid argument: 9999999999999999999999999999999999999999') + check_excmd_err(':N 9999999999999999999999999999999999999999', + 'Vim(Next):E939: Positive count required') + check_excmd_err(':bdelete 9999999999999999999999999999999999999999', + 'Vim(bdelete):E939: Positive count required') eq('Vim(menu):E329: No menu "9999999999999999999999999999999999999999"', pcall_err(command, ':menu 9999999999999999999999999999999999999999')) - eq('Vim(bdelete):E939: Positive count required', - pcall_err(command, ':bdelete 9999999999999999999999999999999999999999')) assert_alive() end) diff --git a/test/functional/ex_cmds/highlight_spec.lua b/test/functional/ex_cmds/highlight_spec.lua index 45764e6719..958dd99226 100644 --- a/test/functional/ex_cmds/highlight_spec.lua +++ b/test/functional/ex_cmds/highlight_spec.lua @@ -24,7 +24,7 @@ describe(':highlight', function() end) it('invalid group name', function() - eq('Vim(highlight):E411: highlight group not found: foo', + eq('Vim(highlight):E411: Highlight group not found: foo', exc_exec("highlight foo")) end) diff --git a/test/functional/legacy/conceal_spec.lua b/test/functional/legacy/conceal_spec.lua index 429cf9dc03..6aaa93f886 100644 --- a/test/functional/legacy/conceal_spec.lua +++ b/test/functional/legacy/conceal_spec.lua @@ -474,6 +474,39 @@ describe('Conceal', function() ]]) end) + -- oldtest: Test_conceal_linebreak() + it('with linebreak', function() + local screen = Screen.new(75, 8) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + }) + screen:attach() + exec([[ + let &wrap = v:true + let &conceallevel = 2 + let &concealcursor = 'nc' + let &linebreak = v:true + let &showbreak = '+ ' + let line = 'a`a`a`a`' + \ .. 'a'->repeat(&columns - 15) + \ .. ' b`b`' + \ .. 'b'->repeat(&columns - 10) + \ .. ' cccccc' + eval ['x'->repeat(&columns), '', line]->setline(1) + syntax region CodeSpan matchgroup=Delimiter start=/\z(`\+\)/ end=/\z1/ concealends + ]]) + screen:expect([[ + ^xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| + | + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | + {0:+ }bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb | + {0:+ }cccccc | + {0:~ }| + {0:~ }| + | + ]]) + end) + -- Tests for correct display (cursor column position) with +conceal and tabulators. -- oldtest: Test_conceal_cursor_pos() it('cursor and column position with conceal and tabulators', function() diff --git a/test/functional/legacy/display_spec.lua b/test/functional/legacy/display_spec.lua index f9b78f5dcd..f1cd8d1aac 100644 --- a/test/functional/legacy/display_spec.lua +++ b/test/functional/legacy/display_spec.lua @@ -194,4 +194,51 @@ describe('display', function() it('display "lastline" works correctly with multibyte fillchar', function() run_test_display_lastline(true) end) + + -- oldtest: Test_display_long_lastline() + it('display "lastline" shows correct text when end of wrapped line is deleted', function() + local screen = Screen.new(35, 14) + screen:attach() + exec([[ + set display=lastline scrolloff=5 + call setline(1, [ + \'aaaaa'->repeat(100), + \'bbbbb '->repeat(7) .. 'ccccc '->repeat(7) .. 'ddddd '->repeat(7) + \]) + ]]) + feed('482|') + screen:expect([[ + <<<aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaa^aaaaaaaaa| + aaaaaaaaaa | + | + ]]) + feed('D') + screen:expect([[ + <<<aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaaaaaaa^a | + bbbbb bbbbb bbbbb bbbbb bbbbb bb@@@| + | + ]]) + end) end) diff --git a/test/functional/legacy/edit_spec.lua b/test/functional/legacy/edit_spec.lua index a0d8ca63a2..186bf395cc 100644 --- a/test/functional/legacy/edit_spec.lua +++ b/test/functional/legacy/edit_spec.lua @@ -95,7 +95,7 @@ describe('edit', function() {0:~ }| {4: }| ={2:{}} | - {5:E731: using Dictionary as a String} | + {5:E731: Using a Dictionary as a String} | {6:Press ENTER or type command to continue}^ | ]]) diff --git a/test/functional/legacy/eval_spec.lua b/test/functional/legacy/eval_spec.lua index b5e45a86c1..c531c59fd1 100644 --- a/test/functional/legacy/eval_spec.lua +++ b/test/functional/legacy/eval_spec.lua @@ -613,15 +613,15 @@ describe('eval', function() Executing call setreg(1, 2, 3, 4) Vim(call):E118: Too many arguments for function: setreg Executing call setreg([], 2) - Vim(call):E730: using List as a String + Vim(call):E730: Using a List as a String Executing call setreg(1, 2, []) - Vim(call):E730: using List as a String + Vim(call):E730: Using a List as a String Executing call setreg("/", ["1", "2"]) - Vim(call):E883: search pattern and expression register may not contain two or more lines + Vim(call):E883: Search pattern and expression register may not contain two or more lines Executing call setreg("=", ["1", "2"]) - Vim(call):E883: search pattern and expression register may not contain two or more lines + Vim(call):E883: Search pattern and expression register may not contain two or more lines Executing call setreg(1, ["", "", [], ""]) - Vim(call):E730: using List as a String]]) + Vim(call):E730: Using a List as a String]]) end) it('function name not starting with a capital', function() diff --git a/test/functional/legacy/glob2regpat_spec.lua b/test/functional/legacy/glob2regpat_spec.lua index 029d95206e..1771f12f85 100644 --- a/test/functional/legacy/glob2regpat_spec.lua +++ b/test/functional/legacy/glob2regpat_spec.lua @@ -8,7 +8,7 @@ describe('glob2regpat()', function() before_each(clear) it('handles invalid input', function() - eq('Vim(call):E806: using Float as a String', + eq('Vim(call):E806: Using a Float as a String', exc_exec('call glob2regpat(1.33)')) end) it('returns ^$ for empty input', function() diff --git a/test/functional/legacy/scroll_opt_spec.lua b/test/functional/legacy/scroll_opt_spec.lua new file mode 100644 index 0000000000..8af23d2c26 --- /dev/null +++ b/test/functional/legacy/scroll_opt_spec.lua @@ -0,0 +1,825 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local exec = helpers.exec +local feed = helpers.feed + +before_each(clear) + +describe('smoothscroll', function() + local screen + + before_each(function() + screen = Screen.new(40, 12) + screen:attach() + end) + + -- oldtest: Test_CtrlE_CtrlY_stop_at_end() + it('disabled does not break <C-E> and <C-Y> stop at end', function() + exec([[ + enew + call setline(1, ['one', 'two']) + set number + ]]) + feed('<C-Y>') + screen:expect({any = " 1 ^one"}) + feed('<C-E><C-E><C-E>') + screen:expect({any = " 2 ^two"}) + end) + + -- oldtest: Test_smoothscroll_CtrlE_CtrlY() + it('works with <C-E> and <C-E>', function() + exec([[ + call setline(1, [ 'line one', 'word '->repeat(20), 'line three', 'long word '->repeat(7), 'line', 'line', 'line', ]) + set smoothscroll scrolloff=5 + :5 + ]]) + local s1 = [[ + word word word word word word word word | + word word word word word word word word | + word word word word | + line three | + long word long word long word long word | + long word long word long word | + ^line | + line | + line | + ~ | + ~ | + | + ]] + local s2 = [[ + <<<d word word word word word word word | + word word word word | + line three | + long word long word long word long word | + long word long word long word | + ^line | + line | + line | + ~ | + ~ | + ~ | + | + ]] + local s3 = [[ + <<<d word word word | + line three | + long word long word long word long word | + long word long word long word | + ^line | + line | + line | + ~ | + ~ | + ~ | + ~ | + | + ]] + local s4 = [[ + line three | + long word long word long word long word | + long word long word long word | + line | + line | + ^line | + ~ | + ~ | + ~ | + ~ | + ~ | + | + ]] + local s5 = [[ + <<<d word word word | + line three | + long word long word long word long word | + long word long word long word | + line | + line | + ^line | + ~ | + ~ | + ~ | + ~ | + | + ]] + local s6 = [[ + <<<d word word word word word word word | + word word word word | + line three | + long word long word long word long word | + long word long word long word | + line | + line | + ^line | + ~ | + ~ | + ~ | + | + ]] + local s7 = [[ + word word word word word word word word | + word word word word word word word word | + word word word word | + line three | + long word long word long word long word | + long word long word long word | + line | + line | + ^line | + ~ | + ~ | + | + ]] + local s8 = [[ + line one | + word word word word word word word word | + word word word word word word word word | + word word word word | + line three | + long word long word long word long word | + long word long word long word | + line | + line | + ^line | + ~ | + | + ]] + feed('<C-E>') + screen:expect(s1) + feed('<C-E>') + screen:expect(s2) + feed('<C-E>') + screen:expect(s3) + feed('<C-E>') + screen:expect(s4) + feed('<C-Y>') + screen:expect(s5) + feed('<C-Y>') + screen:expect(s6) + feed('<C-Y>') + screen:expect(s7) + feed('<C-Y>') + screen:expect(s8) + exec('set foldmethod=indent') + -- move the cursor so we can reuse the same dumps + feed('5G<C-E>') + screen:expect(s1) + feed('<C-E>') + screen:expect(s2) + feed('7G<C-Y>') + screen:expect(s7) + feed('<C-Y>') + screen:expect(s8) + end) + + -- oldtest: Test_smoothscroll_number() + it("works 'number' and 'cpo'+=n", function() + exec([[ + call setline(1, [ 'one ' .. 'word '->repeat(20), 'two ' .. 'long word '->repeat(7), 'line', 'line', 'line', ]) + set smoothscroll scrolloff=5 + set splitkeep=topline + set number cpo+=n + :3 + func g:DoRel() + set number relativenumber scrolloff=0 + :%del + call setline(1, [ 'one', 'very long text '->repeat(12), 'three', ]) + exe "normal 2Gzt\<C-E>" + endfunc + ]]) + screen:expect([[ + 1 one word word word word word word wo| + rd word word word word word word word wo| + rd word word word word word | + 2 two long word long word long word lo| + ng word long word long word long word | + 3 ^line | + 4 line | + 5 line | + ~ | + ~ | + ~ | + | + ]]) + feed('<C-E>') + screen:expect([[ + <<<word word word word word word word wo| + rd word word word word word | + 2 two long word long word long word lo| + ng word long word long word long word | + 3 ^line | + 4 line | + 5 line | + ~ | + ~ | + ~ | + ~ | + | + ]]) + feed('<C-E>') + screen:expect([[ + <<<word word word word word | + 2 two long word long word long word lo| + ng word long word long word long word | + 3 ^line | + 4 line | + 5 line | + ~ | + ~ | + ~ | + ~ | + ~ | + | + ]]) + exec('set cpo-=n') + screen:expect([[ + <<< d word word word word word word | + 2 two long word long word long word lo| + ng word long word long word long wor| + d | + 3 ^line | + 4 line | + 5 line | + ~ | + ~ | + ~ | + ~ | + | + ]]) + feed('<C-Y>') + screen:expect([[ + <<< rd word word word word word word wor| + d word word word word word word | + 2 two long word long word long word lo| + ng word long word long word long wor| + d | + 3 ^line | + 4 line | + 5 line | + ~ | + ~ | + ~ | + | + ]]) + feed('<C-Y>') + screen:expect([[ + 1 one word word word word word word wo| + rd word word word word word word wor| + d word word word word word word | + 2 two long word long word long word lo| + ng word long word long word long wor| + d | + 3 ^line | + 4 line | + 5 line | + ~ | + ~ | + | + ]]) + exec('botright split') + feed('gg') + screen:expect([[ + 1 one word word word word word word wo| + rd word word word word word word wor| + d word word word word word word | + 2 two long word long word long word@@@| + [No Name] [+] | + 1 ^one word word word word word word wo| + rd word word word word word word wor| + d word word word word word word | + 2 two long word long word long word lo| + ng word long word long word long @@@| + [No Name] [+] | + | + ]]) + feed('<C-E>') + screen:expect([[ + 1 one word word word word word word wo| + rd word word word word word word wor| + d word word word word word word | + 2 two long word long word long word@@@| + [No Name] [+] | + <<< rd word word word word word word wor| + d word word word word word word^ | + 2 two long word long word long word lo| + ng word long word long word long wor| + d | + [No Name] [+] | + | + ]]) + feed('<C-E>') + screen:expect([[ + 1 one word word word word word word wo| + rd word word word word word word wor| + d word word word word word word | + 2 two long word long word long word@@@| + [No Name] [+] | + <<< d word word word word word word^ | + 2 two long word long word long word lo| + ng word long word long word long wor| + d | + 3 line | + [No Name] [+] | + | + ]]) + exec('close') + exec('call DoRel()') + screen:expect([[ + 2<<<^ong text very long text very long te| + xt very long text very long text ver| + y long text very long text very long| + text very long text very long text | + 1 three | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + --No lines in buffer-- | + ]]) + end) + + -- oldtest: Test_smoothscroll_list() + it("works with list mode", function() + screen:try_resize(40, 8) + exec([[ + set smoothscroll scrolloff=0 + set list + call setline(1, [ 'one', 'very long text '->repeat(12), 'three', ]) + exe "normal 2Gzt\<C-E>" + ]]) + screen:expect([[ + <<<t very long text very long text very | + ^long text very long text very long text | + very long text very long text very long | + text very long text- | + three | + ~ | + ~ | + | + ]]) + exec('set listchars+=precedes:#') + screen:expect([[ + #ext very long text very long text very | + ^long text very long text very long text | + very long text very long text very long | + text very long text- | + three | + ~ | + ~ | + | + ]]) + end) + + -- oldtest: Test_smoothscroll_diff_mode() + it("works with diff mode", function() + screen:try_resize(40, 8) + exec([[ + let text = 'just some text here' + call setline(1, text) + set smoothscroll + diffthis + new + call setline(1, text) + set smoothscroll + diffthis + ]]) + screen:expect([[ + - ^just some text here | + ~ | + ~ | + [No Name] [+] | + - just some text here | + ~ | + [No Name] [+] | + | + ]]) + feed('<C-Y>') + screen:expect_unchanged() + feed('<C-E>') + screen:expect_unchanged() + end) + + -- oldtest: Test_smoothscroll_wrap_scrolloff_zero() + it("works with zero 'scrolloff'", function() + screen:try_resize(40, 8) + exec([[ + call setline(1, ['Line' .. (' with some text'->repeat(7))]->repeat(7)) + set smoothscroll scrolloff=0 display= + :3 + ]]) + screen:expect([[ + <<<h some text with some text | + Line with some text with some text with | + some text with some text with some text | + with some text with some text | + ^Line with some text with some text with | + some text with some text with some text | + with some text with some text | + | + ]]) + feed('j') + screen:expect_unchanged() + -- moving cursor down - whole bottom line shows + feed('<C-E>j') + screen:expect_unchanged() + feed('G') + screen:expect_unchanged() + -- moving cursor up right after the >>> marker - no need to show whole line + feed('2gj3l2k') + screen:expect([[ + <<<^h some text with some text | + Line with some text with some text with | + some text with some text with some text | + with some text with some text | + Line with some text with some text with | + some text with some text with some text | + with some text with some text | + | + ]]) + -- moving cursor up where the >>> marker is - whole top line shows + feed('2j02k') + screen:expect([[ + ^Line with some text with some text with | + some text with some text with some text | + with some text with some text | + Line with some text with some text with | + some text with some text with some text | + with some text with some text | + @ | + | + ]]) + end) + + -- oldtest: Test_smoothscroll_wrap_long_line() + it("adjusts the cursor position in a long line", function() + screen:try_resize(40, 6) + exec([[ + call setline(1, ['one', 'two', 'Line' .. (' with lots of text'->repeat(30)) .. ' end', 'four']) + set smoothscroll scrolloff=0 + normal 3G10|zt + ]]) + -- scrolling up, cursor moves screen line down + screen:expect([[ + Line with^ lots of text with lots of text| + with lots of text with lots of text wit| + h lots of text with lots of text with lo| + ts of text with lots of text with lots o| + f text with lots of text with lots of te| + | + ]]) + feed('<C-E>') + screen:expect([[ + <<<th lot^s of text with lots of text wit| + h lots of text with lots of text with lo| + ts of text with lots of text with lots o| + f text with lots of text with lots of te| + xt with lots of text with lots of text w| + | + ]]) + feed('5<C-E>') + screen:expect([[ + <<< lots ^of text with lots of text with | + lots of text with lots of text with lots| + of text with lots of text with lots of | + text with lots of text with lots of text| + with lots of text with lots of text wit| + | + ]]) + -- scrolling down, cursor moves screen line up + feed('5<C-Y>') + screen:expect([[ + <<<th lots of text with lots of text wit| + h lots of text with lots of text with lo| + ts of text with lots of text with lots o| + f text with lots of text with lots of te| + xt with l^ots of text with lots of text w| + | + ]]) + feed('<C-Y>') + screen:expect([[ + Line with lots of text with lots of text| + with lots of text with lots of text wit| + h lots of text with lots of text with lo| + ts of text with lots of text with lots o| + f text wi^th lots of text with lots of te| + | + ]]) + -- 'scrolloff' set to 1, scrolling up, cursor moves screen line down + exec('set scrolloff=1') + feed('10|<C-E>') + screen:expect([[ + <<<th lots of text with lots of text wit| + h lots of^ text with lots of text with lo| + ts of text with lots of text with lots o| + f text with lots of text with lots of te| + xt with lots of text with lots of text w| + | + ]]) + -- 'scrolloff' set to 1, scrolling down, cursor moves screen line up + feed('<C-E>gjgj<C-Y>') + screen:expect([[ + <<<th lots of text with lots of text wit| + h lots of text with lots of text with lo| + ts of text with lots of text with lots o| + f text wi^th lots of text with lots of te| + xt with lots of text with lots of text w| + | + ]]) + -- 'scrolloff' set to 2, scrolling up, cursor moves screen line down + exec('set scrolloff=2') + feed('10|<C-E>') + screen:expect([[ + <<<th lots of text with lots of text wit| + h lots of text with lots of text with lo| + ts of tex^t with lots of text with lots o| + f text with lots of text with lots of te| + xt with lots of text with lots of text w| + | + ]]) + -- 'scrolloff' set to 2, scrolling down, cursor moves screen line up + feed('<C-E>gj<C-Y>') + screen:expect_unchanged() + -- 'scrolloff' set to 0, move cursor down one line. Cursor should move properly, + -- and since this is a really long line, it will be put on top of the screen. + exec('set scrolloff=0') + feed('0j') + screen:expect([[ + <<<of text with lots of text with lots o| + f text with lots of text end | + ^four | + ~ | + ~ | + | + ]]) + -- Test zt/zz/zb that they work properly when a long line is above it + feed('zb') + screen:expect([[ + <<<th lots of text with lots of text wit| + h lots of text with lots of text with lo| + ts of text with lots of text with lots o| + f text with lots of text end | + ^four | + | + ]]) + feed('zz') + screen:expect([[ + <<<of text with lots of text with lots o| + f text with lots of text end | + ^four | + ~ | + ~ | + | + ]]) + feed('zt') + screen:expect([[ + ^four | + ~ | + ~ | + ~ | + ~ | + | + ]]) + -- Repeat the step and move the cursor down again. + -- This time, use a shorter long line that is barely long enough to span more + -- than one window. Note that the cursor is at the bottom this time because + -- Vim prefers to do so if we are scrolling a few lines only. + exec("call setline(1, ['one', 'two', 'Line' .. (' with lots of text'->repeat(10)) .. ' end', 'four'])") + feed('3Gztj') + screen:expect([[ + <<<th lots of text with lots of text wit| + h lots of text with lots of text with lo| + ts of text with lots of text with lots o| + f text with lots of text end | + ^four | + | + ]]) + -- Repeat the step but this time start it when the line is smooth-scrolled by + -- one line. This tests that the offset calculation is still correct and + -- still end up scrolling down to the next line with cursor at bottom of + -- screen. + feed('3Gzt<C-E>j') + screen:expect([[ + <<<th lots of text with lots of text wit| + h lots of text with lots of text with lo| + ts of text with lots of text with lots o| + f text with lots of text end | + fou^r | + | + ]]) + end) + + -- oldtest: Test_smoothscroll_one_long_line() + it("scrolls correctly when moving the cursor", function() + screen:try_resize(40, 6) + exec([[ + call setline(1, 'with lots of text '->repeat(7)) + set smoothscroll scrolloff=0 + ]]) + local s1 = [[ + ^with lots of text with lots of text with| + lots of text with lots of text with lot| + s of text with lots of text with lots of| + text | + ~ | + | + ]] + screen:expect(s1) + feed('<C-E>') + screen:expect([[ + <<<ts of text with lots of text with lot| + ^s of text with lots of text with lots of| + text | + ~ | + ~ | + | + ]]) + feed('0') + screen:expect(s1) + end) + + -- oldtest: Test_smoothscroll_long_line_showbreak() + it("cursor is not one screen line too far down", function() + screen:try_resize(40, 6) + -- a line that spans four screen lines + exec("call setline(1, 'with lots of text in one line '->repeat(6))") + exec('set smoothscroll scrolloff=0 showbreak=+++\\ ') + local s1 = [[ + ^with lots of text in one line with lots | + +++ of text in one line with lots of tex| + +++ t in one line with lots of text in o| + +++ ne line with lots of text in one lin| + +++ e with lots of text in one line | + | + ]] + screen:expect(s1) + feed('<C-E>') + screen:expect([[ + +++ ^of text in one line with lots of tex| + +++ t in one line with lots of text in o| + +++ ne line with lots of text in one lin| + +++ e with lots of text in one line | + ~ | + | + ]]) + feed('0') + screen:expect(s1) + end) + + -- oldtest: Test_smoothscroll_zero_width() + it("does not divide by zero with a narrow window", function() + screen:try_resize(12, 2) + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.Brown}, + [2] = {foreground = Screen.colors.Blue1, bold = true}, + }) + exec([[ + call setline(1, ['a'->repeat(100)]) + set wrap smoothscroll number laststatus=0 + wincmd v + wincmd v + wincmd v + wincmd v + ]]) + screen:expect([[ + {1: 1^ }│{1: }│{1: }│{1: }│{1: }| + | + ]]) + feed('llllllllll<C-W>o') + screen:expect([[ + {2:<<<}{1: }aa^aaaaaa| + | + ]]) + end) + + it("works with virt_lines above and below", function() + screen:try_resize(55, 7) + exec([=[ + call setline(1, ['Line' .. (' with some text'->repeat(7))]->repeat(3)) + set smoothscroll + let ns = nvim_create_namespace('') + call nvim_buf_set_extmark(0, ns, 0, 0, {'virt_lines':[[['virt_below1']]]}) + call nvim_buf_set_extmark(0, ns, 1, 0, {'virt_lines':[[['virt_above1']]],'virt_lines_above':1}) + call nvim_buf_set_extmark(0, ns, 1, 0, {'virt_lines':[[['virt_below2']]]}) + call nvim_buf_set_extmark(0, ns, 2, 0, {'virt_lines':[[['virt_above2']]],'virt_lines_above':1}) + norm ggL + ]=]) + screen:expect([[ + Line with some text with some text with some text with | + some text with some text with some text with some text | + virt_below1 | + virt_above1 | + ^Line with some text with some text with some text with | + some text with some text with some text with some text | + | + ]]) + feed('<C-E>') + screen:expect([[ + <<<e text with some text with some text with some text | + virt_below1 | + virt_above1 | + ^Line with some text with some text with some text with | + some text with some text with some text with some text | + virt_below2 | + | + ]]) + feed('<C-E>') + screen:expect([[ + virt_below1 | + virt_above1 | + ^Line with some text with some text with some text with | + some text with some text with some text with some text | + virt_below2 | + virt_above2 | + | + ]]) + feed('<C-E>') + screen:expect([[ + virt_above1 | + ^Line with some text with some text with some text with | + some text with some text with some text with some text | + virt_below2 | + virt_above2 | + Line with some text with some text with some text wi@@@| + | + ]]) + feed('<C-E>') + screen:expect([[ + ^Line with some text with some text with some text with | + some text with some text with some text with some text | + virt_below2 | + virt_above2 | + Line with some text with some text with some text with | + some text with some text with some text with some text | + | + ]]) + feed('<C-E>') + screen:expect([[ + <<<e text with some text with some text with some tex^t | + virt_below2 | + virt_above2 | + Line with some text with some text with some text with | + some text with some text with some text with some text | + ~ | + | + ]]) + end) + + it('<<< marker shows with tabline, winbar and splits', function() + screen:try_resize(40, 12) + exec([[ + call setline(1, ['Line' .. (' with some text'->repeat(7))]->repeat(7)) + set smoothscroll scrolloff=0 + norm sj + ]]) + screen:expect([[ + <<<e text with some text with some text | + with some text with some text | + Line with some text with some text with | + some text with some text with some text | + with some text with some text | + [No Name] [+] | + <<<e text with some text with some text | + ^with some text with some text | + Line with some text with some text with | + some text with some text with some te@@@| + [No Name] [+] | + | + ]]) + exec('set showtabline=2') + feed('<C-E>') + screen:expect([[ + 2+ [No Name] | + <<<e text with some text with some text | + with some text with some text | + Line with some text with some text with | + some text with some text with some text | + with some text with some text | + [No Name] [+] | + <<<e text with some text with some text | + ^with some text with some text | + Line with some text with some text wi@@@| + [No Name] [+] | + | + ]]) + exec('set winbar=winbar') + feed('<C-w>k<C-E>') + screen:expect([[ + 2+ [No Name] | + winbar | + <<<e text with some text with some text | + ^with some text with some text | + Line with some text with some text with | + some text with some text with some te@@@| + [No Name] [+] | + winbar | + <<<e text with some text with some text | + with some text with some text | + [No Name] [+] | + | + ]]) + end) +end) diff --git a/test/functional/lua/commands_spec.lua b/test/functional/lua/commands_spec.lua index 0dc6c19fa1..fca619348d 100644 --- a/test/functional/lua/commands_spec.lua +++ b/test/functional/lua/commands_spec.lua @@ -198,7 +198,7 @@ describe(':luado command', function() end) it('works correctly when changing lines out of range', function() curbufmeths.set_lines(0, 1, false, {"ABC", "def", "gHi"}) - eq('Vim(luado):E322: line number out of range: 1 past the end', + eq('Vim(luado):E322: Line number out of range: 1 past the end', pcall_err(command, '2,$luado vim.api.nvim_command("%d") return linenr')) eq({''}, curbufmeths.get_lines(0, -1, false)) end) @@ -214,7 +214,7 @@ describe(':luado command', function() end) it('fails in sandbox when needed', function() curbufmeths.set_lines(0, 1, false, {"ABC", "def", "gHi"}) - eq('Vim(luado):E48: Not allowed in sandbox', + eq('Vim(luado):E48: Not allowed in sandbox: sandbox luado runs = (runs or 0) + 1', pcall_err(command, 'sandbox luado runs = (runs or 0) + 1')) eq(NIL, funcs.luaeval('runs')) end) diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index 4242b6e493..fcf313785a 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -892,8 +892,8 @@ describe('stdpath()', function() end) it('on non-strings', function() - eq('Vim(call):E731: using Dictionary as a String', exc_exec('call stdpath({"eris": 23})')) - eq('Vim(call):E730: using List as a String', exc_exec('call stdpath([23])')) + eq('Vim(call):E731: Using a Dictionary as a String', exc_exec('call stdpath({"eris": 23})')) + eq('Vim(call):E730: Using a List as a String', exc_exec('call stdpath([23])')) end) end) end) diff --git a/test/functional/options/mousescroll_spec.lua b/test/functional/options/mousescroll_spec.lua index 5bff45a836..38a9692792 100644 --- a/test/functional/options/mousescroll_spec.lua +++ b/test/functional/options/mousescroll_spec.lua @@ -20,7 +20,7 @@ end describe("'mousescroll'", function() local invalid_arg = 'Vim(set):E474: Invalid argument: mousescroll=' - local digit_expected = 'Vim(set):E548: digit expected: mousescroll=' + local digit_expected = 'Vim(set):E5080: Digit expected: mousescroll=' local function should_fail(val, errorstr) eq(errorstr..val, exc_exec('set mousescroll='..val)) diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index 926e12ec32..488a213a9e 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -130,8 +130,8 @@ describe('health.vim', function() local screen = Screen.new(50, 12) screen:attach() screen:set_default_attr_ids({ - Ok = { foreground = Screen.colors.Grey3, background = 6291200 }, - Error = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, + Ok = { foreground = Screen.colors.LightGreen }, + Error = { foreground = Screen.colors.Red }, Heading = { foreground = tonumber('0x6a0dad') }, Bar = { foreground = Screen.colors.LightGrey, background = Screen.colors.DarkGrey }, }) diff --git a/test/functional/terminal/api_spec.lua b/test/functional/terminal/api_spec.lua index 724791343d..93641fc576 100644 --- a/test/functional/terminal/api_spec.lua +++ b/test/functional/terminal/api_spec.lua @@ -66,10 +66,10 @@ describe('api', function() screen:expect([[ [tui] insert-mode | - [socket 1] this is more t{4: }| - han 25 columns {4: }| - [socket 2] input{1: } {4: }| - {4:~ }| + [socket 1] this is more t | + han 25 columns | + [socket 2] input{1: } | + {4:~ } | {3:-- INSERT --} | {3:-- TERMINAL --} | ]]) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index f366c8a6d9..b69867af89 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -2445,6 +2445,19 @@ describe("TUI as a client", function() {3:-- TERMINAL --} | ]]} + -- grid smaller than containing terminal window is cleared properly + feed_data(":call setline(1,['a'->repeat(&columns)]->repeat(&lines))\n") + feed_data("0:set lines=2\n") + screen_server:expect{grid=[[ + {1:a}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {5:[No Name] [+] }| + | + | + | + | + {3:-- TERMINAL --} | + ]]} + feed_data(":q!\n") server_super:close() diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index 783246c6e4..636f571641 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -854,11 +854,11 @@ describe('Ex commands coloring', function() :# | {ERR:Error detected while processing :} | {ERR:E605: Exception not caught: 42} | - {ERR:E749: empty buffer} | + {ERR:E749: Empty buffer} | {PE:Press ENTER or type command to continue}^ | ]]) feed('<CR>') - eq('Error detected while processing :\nE605: Exception not caught: 42\nE749: empty buffer', + eq('Error detected while processing :\nE605: Exception not caught: 42\nE749: Empty buffer', exec_capture('messages')) end) it('errors out when failing to get callback', function() diff --git a/test/functional/ui/diff_spec.lua b/test/functional/ui/diff_spec.lua index dbdf3823ec..0f551e3044 100644 --- a/test/functional/ui/diff_spec.lua +++ b/test/functional/ui/diff_spec.lua @@ -1325,6 +1325,7 @@ it('win_update redraws lines properly', function() ]]} end) +-- oldtest: Test_diff_rnu() it('diff updates line numbers below filler lines', function() clear() local screen = Screen.new(40, 14) @@ -1401,6 +1402,7 @@ it('diff updates line numbers below filler lines', function() ]]) end) +-- oldtest: Test_diff_with_scroll_and_change() it('Align the filler lines when changing text in diff mode', function() clear() local screen = Screen.new(40, 20) diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index a67db78cbe..28f489783b 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -1720,7 +1720,7 @@ describe("'inccommand' and :cnoremap", function() local function refresh(case, visual) clear() - screen = visual and Screen.new(50,10) or nil + screen = visual and Screen.new(80,10) or nil common_setup(screen, case, default_text) end diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index d9b9cf9f1b..e55804e29f 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -790,7 +790,7 @@ describe('ui/mouse/input', function() feed('<C-LeftMouse><0,0>') screen:expect([[ {6:E433: No tags file} | - {6:E426: tag not found: test}| + {6:E426: Tag not found: test}| {6:ing} | {7:Press ENTER or type comma}| {7:nd to continue}^ | @@ -1861,5 +1861,16 @@ describe('ui/mouse/input', function() feed('<Down><CR>') eq({1, 9}, meths.win_get_cursor(0)) eq('ran away', funcs.getreg('"')) + + -- Test for right click inside visual selection at bottom of window with winbar + command('setlocal winbar=WINBAR') + feed('2yyP') + funcs.setreg('"', '') + feed('G$vbb') + meths.input_mouse('right', 'press', '', 0, 4, 61) + meths.input_mouse('right', 'release', '', 0, 4, 61) + feed('<Down><CR>') + eq({4, 20}, meths.win_get_cursor(0)) + eq('the moon', funcs.getreg('"')) end) end) diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua index 2525314b8e..4c04bcb54e 100644 --- a/test/functional/ui/multigrid_spec.lua +++ b/test/functional/ui/multigrid_spec.lua @@ -37,6 +37,10 @@ describe('ext_multigrid', function() [18] = {bold = true, foreground = Screen.colors.Magenta}, [19] = {foreground = Screen.colors.Brown}, [20] = {background = Screen.colors.LightGrey}, + [21] = {background = Screen.colors.LightMagenta}, + [22] = {background = Screen.colors.LightMagenta, bold = true, foreground = Screen.colors.Blue}, + [23] = {background = Screen.colors.Grey90}, + [24] = {background = Screen.colors.Grey}, }) end) @@ -884,7 +888,6 @@ describe('ext_multigrid', function() it('gets written till grid width', function() insert(('a'):rep(60).."\n") - screen:expect{grid=[[ ## grid 1 [2:-----------------------------------------------------]| @@ -927,8 +930,95 @@ describe('ext_multigrid', function() ]]} end) + it('"g$" works correctly with double-width characters and no wrapping', function() + command('set nowrap') + insert(('a'):rep(58) .. ('哦'):rep(3)) + feed('0') + screen:expect{grid=[[ + ## grid 1 + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa哦| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ]]} + feed('g$') + screen:expect{grid=[[ + ## grid 1 + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^哦| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ]]} + end) + it('wraps with grid width', function() - insert(('b'):rep(80).."\n") + insert(('b'):rep(160).."\n") screen:expect{grid=[[ ## grid 1 [2:-----------------------------------------------------]| @@ -947,7 +1037,8 @@ describe('ext_multigrid', function() [3:-----------------------------------------------------]| ## grid 2 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| - bbbbbbbbbbbbbbbbbbbb | + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb | ^ | {1:~ }| {1:~ }| @@ -965,6 +1056,47 @@ describe('ext_multigrid', function() {1:~ }| {1:~ }| {1:~ }| + ## grid 3 + | + ]]} + feed('2gk') + command('setlocal cursorline cursorlineopt=screenline') + screen:expect{grid=[[ + ## grid 1 + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + {23:^bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb}| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb | + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| {1:~ }| ## grid 3 | @@ -1060,6 +1192,255 @@ describe('ext_multigrid', function() | ]]} end) + + it('anchored float window "bufpos"', function() + insert(('c'):rep(1111)) + screen:expect{grid=[[ + ## grid 1 + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccc^c | + {1:~ }| + ## grid 3 + | + ]]} + local float_buf = meths.create_buf(false, false) + meths.open_win(float_buf, false, { + relative = 'win', + win = curwin(), + bufpos = {0, 1018}, + anchor = 'SE', + width = 5, + height = 5, + }) + screen:expect{grid=[[ + ## grid 1 + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc| + cccccccccccccccccccccccccccccc^c | + {1:~ }| + ## grid 3 + | + ## grid 4 + {21: }| + {22:~ }| + {22:~ }| + {22:~ }| + {22:~ }| + ]], float_pos={ + [4] = {{id = 1001}, "SE", 2, 16, 58, true, 50}; + }} + end) + + it('completion popup position', function() + insert(('\n'):rep(14) .. ('foo bar '):rep(7)) + feed('A<C-X><C-N>') + screen:expect{grid=[[ + ## grid 1 + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + | + | + | + | + | + | + | + | + | + | + | + | + | + | + foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + {7:-- Keyword Local completion (^N^P) }{15:match 1 of 2} | + ## grid 4 + {24: foo}| + {21: bar}| + ]], float_pos={ + [4] = {{id = -1}, "NW", 2, 15, 55, false, 100}; + }} + feed('<C-E><Esc>') + + command('setlocal rightleft') + feed('o<C-X><C-N>') + screen:expect{grid=[[ + ## grid 1 + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + | + | + | + | + | + | + | + | + | + | + | + | + | + | + rab oof rab oof rab oof rab oof rab oof rab oof rab oof| + ^ oof| + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + ## grid 3 + {7:-- Keyword Local completion (^N^P) }{15:match 1 of 2} | + ## grid 4 + {24: oof}| + {21: rab}| + ]], float_pos={ + [4] = {{id = -1}, "NW", 2, 16, 45, false, 100}; + }} + feed('<C-E><Esc>') + + command('set wildoptions+=pum') + feed(':sign un<Tab>') + screen:expect{grid=[[ + ## grid 1 + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + | + | + | + | + | + | + | + | + | + | + | + | + | + | + rab oof rab oof rab oof rab oof rab oof rab oof rab oof| + | + {1: ~}| + {1: ~}| + {1: ~}| + {1: ~}| + ## grid 3 + :sign undefine^ | + ## grid 4 + {24: undefine }| + {21: unplace }| + ]], float_pos={ + [4] = {{id = -1}, "SW", 1, 13, 5, false, 250}; + }} + end) end) it('multiline messages scroll over windows', function() @@ -2003,7 +2384,7 @@ describe('ext_multigrid', function() {1:~ }| ]]} - meths.input_mouse('left', 'press', '', 1,8, 26) + meths.input_mouse('left', 'press', '', 1, 8, 26) poke_eventloop() meths.input_mouse('left', 'drag', '', 1, 6, 30) screen:expect{grid=[[ @@ -2044,6 +2425,625 @@ describe('ext_multigrid', function() {1:~ }| {1:~ }| ]]} + + command('aunmenu PopUp | vmenu PopUp.Copy y') + + funcs.setreg('"', '') + meths.input_mouse('left', 'press', '2', 2, 1, 6) + screen:expect{grid=[[ + ## grid 1 + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {12:[No Name] [+] }| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + {12:[No Name] [+] }{11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + some text | + to be {20:clicke}^d | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + {7:-- VISUAL --} | + ## grid 4 + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmo | + {1:~ }| + ## grid 5 + some text | + to be {20:clicked} | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]]} + meths.input_mouse('right', 'press', '', 2, 1, 6) + meths.input_mouse('right', 'release', '', 2, 1, 6) + screen:expect{grid=[[ + ## grid 1 + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {12:[No Name] [+] }| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + {12:[No Name] [+] }{11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + some text | + to be {20:clicke}^d | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + {7:-- VISUAL --} | + ## grid 4 + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmo | + {1:~ }| + ## grid 5 + some text | + to be {20:clicked} | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 6 + {21: Copy }| + ]], float_pos={ + [6] = {{id = -1}, "NW", 2, 2, 5, false, 250}; + }} + feed('<Down><CR>') + screen:expect{grid=[[ + ## grid 1 + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {12:[No Name] [+] }| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + {12:[No Name] [+] }{11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + some text | + to be ^clicked | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 4 + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmo | + {1:~ }| + ## grid 5 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]]} + eq('clicked', funcs.getreg('"')) + + funcs.setreg('"', '') + meths.input_mouse('left', 'press', '2', 4, 0, 64) + screen:expect{grid=[[ + ## grid 1 + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] [+] }| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + {12:[No Name] [+] [No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + {7:-- VISUAL --} | + ## grid 4 + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do {20:eiusm}^o | + {1:~ }| + ## grid 5 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]]} + meths.input_mouse('right', 'press', '', 4, 0, 64) + meths.input_mouse('right', 'release', '', 4, 0, 64) + screen:expect{grid=[[ + ## grid 1 + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] [+] }| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + {12:[No Name] [+] [No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + {7:-- VISUAL --} | + ## grid 4 + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do {20:eiusm}^o | + {1:~ }| + ## grid 5 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 6 + {21: Copy }| + ]], float_pos={ + [6] = {{id = -1}, "NW", 4, 1, 63, false, 250}; + }} + feed('<Down><CR>') + screen:expect{grid=[[ + ## grid 1 + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] [+] }| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + {12:[No Name] [+] [No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 4 + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do ^eiusmo | + {1:~ }| + ## grid 5 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]]} + eq('eiusmo', funcs.getreg('"')) + + command('wincmd J') + screen:try_resize_grid(4, 7, 10) + screen:expect{grid=[[ + ## grid 1 + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + {12:[No Name] [+] [No Name] [+] }| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 4 + Lorem i| + psum do| + lor sit| + amet, | + consect| + etur ad| + ipiscin| + g elit,| + sed do| + ^eiusmo| + ## grid 5 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + ]]} + + funcs.setreg('"', '') + meths.input_mouse('left', 'press', '2', 4, 9, 1) + screen:expect{grid=[[ + ## grid 1 + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + {12:[No Name] [+] [No Name] [+] }| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + {7:-- VISUAL --} | + ## grid 4 + Lorem i| + psum do| + lor sit| + amet, | + consect| + etur ad| + ipiscin| + g elit,| + sed do| + {20:eiusm}^o| + ## grid 5 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + ]]} + meths.input_mouse('right', 'press', '', 4, 9, 1) + meths.input_mouse('right', 'release', '', 4, 9, 1) + screen:expect{grid=[[ + ## grid 1 + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + {12:[No Name] [+] [No Name] [+] }| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + {7:-- VISUAL --} | + ## grid 4 + Lorem i| + psum do| + lor sit| + amet, | + consect| + etur ad| + ipiscin| + g elit,| + sed do| + {20:eiusm}^o| + ## grid 5 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 6 + {21: Copy }| + ]], float_pos={ + [6] = {{id = -1}, "SW", 4, 9, 0, false, 250}; + }} + feed('<Down><CR>') + screen:expect{grid=[[ + ## grid 1 + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + {12:[No Name] [+] [No Name] [+] }| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 4 + Lorem i| + psum do| + lor sit| + amet, | + consect| + etur ad| + ipiscin| + g elit,| + sed do| + ^eiusmo| + ## grid 5 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + ]]} + eq('eiusmo', funcs.getreg('"')) + + screen:try_resize_grid(4, 7, 11) + screen:expect{grid=[[ + ## grid 1 + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + {12:[No Name] [+] [No Name] [+] }| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 4 + ^Lorem i| + psum do| + lor sit| + amet, | + consect| + etur ad| + ipiscin| + g elit,| + sed do| + eiusmo| + {1:~ }| + ## grid 5 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + ]]} + + funcs.setreg('"', '') + meths.input_mouse('left', 'press', '2', 4, 9, 1) + screen:expect{grid=[[ + ## grid 1 + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + {12:[No Name] [+] [No Name] [+] }| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + {7:-- VISUAL --} | + ## grid 4 + Lorem i| + psum do| + lor sit| + amet, | + consect| + etur ad| + ipiscin| + g elit,| + sed do| + {20:eiusm}^o| + {1:~ }| + ## grid 5 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + ]]} + meths.input_mouse('right', 'press', '', 4, 9, 1) + meths.input_mouse('right', 'release', '', 4, 9, 1) + screen:expect{grid=[[ + ## grid 1 + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + {12:[No Name] [+] [No Name] [+] }| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + {7:-- VISUAL --} | + ## grid 4 + Lorem i| + psum do| + lor sit| + amet, | + consect| + etur ad| + ipiscin| + g elit,| + sed do| + {20:eiusm}^o| + {1:~ }| + ## grid 5 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 6 + {21: Copy }| + ]], float_pos={ + [6] = {{id = -1}, "NW", 4, 10, 0, false, 250}; + }} + feed('<Down><CR>') + screen:expect{grid=[[ + ## grid 1 + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + {12:[No Name] [+] [No Name] [+] }| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 4 + Lorem i| + psum do| + lor sit| + amet, | + consect| + etur ad| + ipiscin| + g elit,| + sed do| + ^eiusmo| + {1:~ }| + ## grid 5 + some text | + to be clicked | + {1:~ }| + {1:~ }| + {1:~ }| + ]]} + eq('eiusmo', funcs.getreg('"')) end) it('supports mouse drag with mouse=a', function() diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 0b71e12b6f..76038472bd 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -5,6 +5,7 @@ local clear, feed = helpers.clear, helpers.feed local source = helpers.source local insert = helpers.insert local meths = helpers.meths +local async_meths = helpers.async_meths local command = helpers.command local funcs = helpers.funcs local eq = helpers.eq @@ -1978,6 +1979,54 @@ describe('builtin popupmenu', function() {2:-- }{5:match 1 of 4} | ]]) end + + feed('\n<c-x><c-n>') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:-----------]│[2:--------------------]| + [4:-----------]│[2:--------------------]| + [4:-----------]│[2:--------------------]| + [4:-----------]│[2:--------------------]| + [4:-----------]│[2:--------------------]| + [4:-----------]│[2:--------------------]| + {3:<Name] [+] }{4:[No Name] [+] }| + [3:--------------------------------]| + ## grid 2 + aaa aab aac | + bbb aaa | + c aaabcdef ccc aaa | + aaa^ | + {1:~ }| + {1:~ }| + ## grid 3 + {2:-- }{5:match 1 of 6} | + ## grid 4 + aaa aab aac| + bbb aaa | + c aaabcdef | + ccc aaa | + aaa | + {1:~ }| + ## grid 5 + {s: aaa }{c: }| + {n: aab }{s: }| + {n: aac }{s: }| + ]], float_pos={ + [5] = {{id = -1}, "NW", 2, 4, -1, false, 100}; + }} + else + screen:expect([[ + aaa aab aac│aaa aab aac | + bbb aaa │bbb aaa | + c aaabcdef │c aaabcdef ccc aaa | + ccc aaa │aaa^ | + aaa {s: aaa }{c: }{1: }| + {1:~ }{n: aab }{s: }{1: }| + {3:<Name] [+] }{n: aac }{s: }{4: }| + {2:-- }{5:match 1 of 6} | + ]]) + end end) if not multigrid then @@ -2396,7 +2445,7 @@ describe('builtin popupmenu', function() -- can't draw the pum, but check we don't crash screen:try_resize(12,2) screen:expect([[ - text^ | + {1:<<<}t^ | {2:-- INSERT -} | ]]) @@ -2488,10 +2537,10 @@ describe('builtin popupmenu', function() funcs.complete(16, {'word', 'choice', 'text', 'thing'}) screen:expect([[ ^ tfelthgir emos| - {1: }{n: drow}{1: ~}| - {1: }{n: eciohc}{1: ~}| - {1: }{n: txet}{1: ~}| - {1: }{n: gniht}{1: ~}| + {1: }{n: drow }{1: ~}| + {1: }{n: eciohc }{1: ~}| + {1: }{n: txet }{1: ~}| + {1: }{n: gniht }{1: ~}| {1: ~}| {1: ~}| {1: ~}| @@ -2512,10 +2561,10 @@ describe('builtin popupmenu', function() feed('<c-n>') screen:expect([[ ^ drow tfelthgir emos| - {1: }{s: drow}{1: ~}| - {1: }{n: eciohc}{1: ~}| - {1: }{n: txet}{1: ~}| - {1: }{n: gniht}{1: ~}| + {1: }{s: drow }{1: ~}| + {1: }{n: eciohc }{1: ~}| + {1: }{n: txet }{1: ~}| + {1: }{n: gniht }{1: ~}| {1: ~}| {1: ~}| {1: ~}| @@ -2609,10 +2658,11 @@ describe('builtin popupmenu', function() end it('with rightleft vsplits', function() - screen:try_resize(40, 8) + screen:try_resize(40, 6) command('set rightleft') command('rightbelow vsplit') - command("set completeopt+=noinsert,noselect") + command('set completeopt+=noinsert,noselect') + command('set pumheight=2') feed('isome rightleft ') funcs.complete(16, {'word', 'choice', 'text', 'thing'}) if multigrid then @@ -2622,8 +2672,6 @@ describe('builtin popupmenu', function() [2:-------------------]│[4:--------------------]| [2:-------------------]│[4:--------------------]| [2:-------------------]│[4:--------------------]| - [2:-------------------]│[4:--------------------]| - [2:-------------------]│[4:--------------------]| {3:[No Name] [+] }{4:[No Name] [+] }| [3:----------------------------------------]| ## grid 2 @@ -2631,8 +2679,6 @@ describe('builtin popupmenu', function() {1: ~}| {1: ~}| {1: ~}| - {1: ~}| - {1: ~}| ## grid 3 {2:-- INSERT --} | ## grid 4 @@ -2640,28 +2686,134 @@ describe('builtin popupmenu', function() {1: ~}| {1: ~}| {1: ~}| - {1: ~}| - {1: ~}| ## grid 5 - {n: drow}| - {n: eciohc}| - {n: txet}| - {n: gniht}| + {c: }{n: drow }| + {s: }{n: eciohc }| ]], float_pos={ [5] = {{id = -1}, "NW", 4, 1, -11, false, 100}; }} else screen:expect([[ tfelthgir emos│ ^ tfelthgir emos| - {1: }{n: drow}{1: ~}| - {1: }{n: eciohc}{1: ~}| - {1: }{n: txet}{1: ~}| - {1: }{n: gniht}{1: ~}| + {1: }{c: }{n: drow }{1: ~}| + {1: }{s: }{n: eciohc }{1: ~}| {1: ~}│{1: ~}| {3:[No Name] [+] }{4:[No Name] [+] }| {2:-- INSERT --} | ]]) end + feed('<C-E><CR>') + funcs.complete(1, {'word', 'choice', 'text', 'thing'}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:-------------------]│[4:--------------------]| + [2:-------------------]│[4:--------------------]| + [2:-------------------]│[4:--------------------]| + [2:-------------------]│[4:--------------------]| + {3:[No Name] [+] }{4:[No Name] [+] }| + [3:----------------------------------------]| + ## grid 2 + tfelthgir emos| + | + {1: ~}| + {1: ~}| + ## grid 3 + {2:-- INSERT --} | + ## grid 4 + tfelthgir emos| + ^ | + {1: ~}| + {1: ~}| + ## grid 5 + {c: }{n: drow}| + {s: }{n: eciohc}| + ]], float_pos={ + [5] = {{id = -1}, "NW", 4, 2, 4, false, 100}; + }} + else + screen:expect([[ + tfelthgir emos│ tfelthgir emos| + │ ^ | + {1: ~}│{1: }{c: }{n: drow}| + {1: ~}│{1: }{s: }{n: eciohc}| + {3:[No Name] [+] }{4:[No Name] [+] }| + {2:-- INSERT --} | + ]]) + end + feed('<C-E>') + async_meths.call_function('input', {'', '', 'sign'}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:-------------------]│[4:--------------------]| + [2:-------------------]│[4:--------------------]| + [2:-------------------]│[4:--------------------]| + [2:-------------------]│[4:--------------------]| + {3:[No Name] [+] }{4:[No Name] [+] }| + [3:----------------------------------------]| + ## grid 2 + tfelthgir emos| + | + {1: ~}| + {1: ~}| + ## grid 3 + ^ | + ## grid 4 + tfelthgir emos| + | + {1: ~}| + {1: ~}| + ]]} + else + screen:expect([[ + tfelthgir emos│ tfelthgir emos| + │ | + {1: ~}│{1: ~}| + {1: ~}│{1: ~}| + {3:[No Name] [+] }{4:[No Name] [+] }| + ^ | + ]]) + end + command('set wildoptions+=pum') + feed('<Tab>') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:-------------------]│[4:--------------------]| + [2:-------------------]│[4:--------------------]| + [2:-------------------]│[4:--------------------]| + [2:-------------------]│[4:--------------------]| + {3:[No Name] [+] }{4:[No Name] [+] }| + [3:----------------------------------------]| + ## grid 2 + tfelthgir emos| + | + {1: ~}| + {1: ~}| + ## grid 3 + define^ | + ## grid 4 + tfelthgir emos| + | + {1: ~}| + {1: ~}| + ## grid 5 + {s:define }{c: }| + {n:jump }{s: }| + ]], float_pos={ + [5] = {{id = -1}, "SW", 1, 5, 0, false, 250}; + }} + else + screen:expect([[ + tfelthgir emos│ tfelthgir emos| + │ | + {1: ~}│{1: ~}| + {s:define }{c: }{1: ~}│{1: ~}| + {n:jump }{s: }{3: }{4:[No Name] [+] }| + define^ | + ]]) + end end) if not multigrid then @@ -4357,6 +4509,79 @@ describe('builtin popupmenu', function() ]]) end eq('foo', meths.get_var('menustr')) + + command('setlocal winbar=WINBAR') + if multigrid then + meths.input_mouse('right', 'press', '', 6, 1, 14) + screen:expect({grid=[[ + ## grid 1 + [2:--------------------------------]| + [2:--------------------------------]| + {3:[No Name] [+] }| + [5:---------------]│[6:----------------]| + [5:---------------]│[6:----------------]| + [3:--------------------------------]| + ## grid 2 + popup menu test | + {1:~ }| + ## grid 3 + :let g:menustr = 'foo' | + ## grid 4 + {n: foo}| + {n: bar}| + {n: baz}| + ## grid 5 + popup menu test| + {1:~ }| + ## grid 6 + {2:WINBAR }| + ^popup menu test | + ]], float_pos={[4] = {{id = -1}, "SW", 6, 1, 12, false, 250}}}) + else + feed('<RightMouse><30,4>') + screen:expect([[ + popup menu test | + {1:~ }{n: foo}| + {3:[No Name] [+] }{n: bar}| + popup menu test│{2:WINBAR }{n: baz}| + {1:~ }│^popup menu test | + :let g:menustr = 'foo' | + ]]) + end + if multigrid then + meths.input_mouse('left', 'press', '', 4, 1, 2) + screen:expect({grid=[[ + ## grid 1 + [2:--------------------------------]| + [2:--------------------------------]| + {3:[No Name] [+] }| + [5:---------------]│[6:----------------]| + [5:---------------]│[6:----------------]| + [3:--------------------------------]| + ## grid 2 + popup menu test | + {1:~ }| + ## grid 3 + :let g:menustr = 'bar' | + ## grid 5 + popup menu test| + {1:~ }| + ## grid 6 + {2:WINBAR }| + ^popup menu test | + ]]}) + else + feed('<LeftMouse><31,2>') + screen:expect([[ + popup menu test | + {1:~ }| + {3:[No Name] [+] }| + popup menu test│{2:WINBAR }| + {1:~ }│^popup menu test | + :let g:menustr = 'bar' | + ]]) + end + eq('bar', meths.get_var('menustr')) end) if not multigrid then diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index 439021ad87..6b05bd01c2 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local spawn, set_session, clear = helpers.spawn, helpers.set_session, helpers.clear local feed, command = helpers.feed, helpers.command +local curwin = helpers.curwin local insert = helpers.insert local eq = helpers.eq local eval = helpers.eval @@ -189,6 +190,52 @@ local function screen_tests(linegrid) eq(expected, screen.title) end) end) + + it('setting the buffer of another window using RPC', function() + local oldwin = curwin().id + command('split') + meths.win_set_buf(oldwin, buf2) + command('redraw!') + screen:expect(function() + eq(expected, screen.title) + end) + end) + + it('setting the buffer of another window using Lua callback', function() + local oldwin = curwin().id + command('split') + exec_lua(string.format([[ + vim.schedule(function() + vim.api.nvim_win_set_buf(%d, %d) + end) + ]], oldwin, buf2)) + command('redraw!') + screen:expect(function() + eq(expected, screen.title) + end) + end) + + it('creating a floating window using RPC', function() + meths.open_win(buf2, false, { + relative = 'editor', width = 5, height = 5, row = 0, col = 0, + }) + command('redraw!') + screen:expect(function() + eq(expected, screen.title) + end) + end) + + it('creating a floating window using Lua callback', function() + exec_lua(string.format([[ + vim.api.nvim_open_win(%d, false, { + relative = 'editor', width = 5, height = 5, row = 0, col = 0, + }) + ]], buf2)) + command('redraw!') + screen:expect(function() + eq(expected, screen.title) + end) + end) end) end) diff --git a/test/functional/vimscript/execute_spec.lua b/test/functional/vimscript/execute_spec.lua index 5fe3d787cb..a9a4ad4811 100644 --- a/test/functional/vimscript/execute_spec.lua +++ b/test/functional/vimscript/execute_spec.lua @@ -4,6 +4,7 @@ local eval = helpers.eval local clear = helpers.clear local source = helpers.source local exc_exec = helpers.exc_exec +local pcall_err = helpers.pcall_err local funcs = helpers.funcs local Screen = require('test.functional.ui.screen') local command = helpers.command @@ -93,17 +94,17 @@ describe('execute()', function() it('captures errors', function() local ret ret = exc_exec('call execute(0.0)') - eq('Vim(call):E806: using Float as a String', ret) + eq('Vim(call):E806: Using a Float as a String', ret) ret = exc_exec('call execute(v:_null_dict)') - eq('Vim(call):E731: using Dictionary as a String', ret) + eq('Vim(call):E731: Using a Dictionary as a String', ret) ret = exc_exec('call execute(function("tr"))') - eq('Vim(call):E729: using Funcref as a String', ret) + eq('Vim(call):E729: Using a Funcref as a String', ret) ret = exc_exec('call execute(["echo 42", 0.0, "echo 44"])') - eq('Vim:E806: using Float as a String', ret) + eq('Vim:E806: Using a Float as a String', ret) ret = exc_exec('call execute(["echo 42", v:_null_dict, "echo 44"])') - eq('Vim:E731: using Dictionary as a String', ret) + eq('Vim:E731: Using a Dictionary as a String', ret) ret = exc_exec('call execute(["echo 42", function("tr"), "echo 44"])') - eq('Vim:E729: using Funcref as a String', ret) + eq('Vim:E729: Using a Funcref as a String', ret) end) it('captures output with highlights', function() @@ -284,6 +285,14 @@ describe('execute()', function() eq('42', eval('g:mes')) end) + it('gives E493 instead of prompting on backwards range for ""', function() + command('split') + eq('Vim(windo):E493: Backwards range given: 2,1windo echo', + pcall_err(funcs.execute, '2,1windo echo', '')) + eq('Vim(windo):E493: Backwards range given: 2,1windo echo', + pcall_err(funcs.execute, {'2,1windo echo'}, '')) + end) + it('captures but does not display output for "silent"', function() local screen = Screen.new(40, 5) screen:attach() @@ -322,10 +331,10 @@ describe('execute()', function() it('propagates errors for "" and "silent"', function() local ret ret = exc_exec('call execute(0.0, "")') - eq('Vim(call):E806: using Float as a String', ret) + eq('Vim(call):E806: Using a Float as a String', ret) ret = exc_exec('call execute(v:_null_dict, "silent")') - eq('Vim(call):E731: using Dictionary as a String', ret) + eq('Vim(call):E731: Using a Dictionary as a String', ret) ret = exc_exec('call execute("echo add(1, 1)", "")') eq('Vim(echo):E897: List or Blob required', ret) diff --git a/test/functional/vimscript/input_spec.lua b/test/functional/vimscript/input_spec.lua index f50b39c2c5..d1643a799a 100644 --- a/test/functional/vimscript/input_spec.lua +++ b/test/functional/vimscript/input_spec.lua @@ -222,17 +222,17 @@ describe('input()', function() eq('DEF2', meths.get_var('var')) end) it('errors out on invalid inputs', function() - eq('Vim(call):E730: using List as a String', + eq('Vim(call):E730: Using a List as a String', exc_exec('call input([])')) - eq('Vim(call):E730: using List as a String', + eq('Vim(call):E730: Using a List as a String', exc_exec('call input("", [])')) - eq('Vim(call):E730: using List as a String', + eq('Vim(call):E730: Using a List as a String', exc_exec('call input("", "", [])')) - eq('Vim(call):E730: using List as a String', + eq('Vim(call):E730: Using a List as a String', exc_exec('call input({"prompt": []})')) - eq('Vim(call):E730: using List as a String', + eq('Vim(call):E730: Using a List as a String', exc_exec('call input({"default": []})')) - eq('Vim(call):E730: using List as a String', + eq('Vim(call):E730: Using a List as a String', exc_exec('call input({"completion": []})')) eq('Vim(call):E5050: {opts} must be the only argument', exc_exec('call input({}, "default")')) @@ -418,17 +418,17 @@ describe('inputdialog()', function() eq('DEF2', meths.get_var('var')) end) it('errors out on invalid inputs', function() - eq('Vim(call):E730: using List as a String', + eq('Vim(call):E730: Using a List as a String', exc_exec('call inputdialog([])')) - eq('Vim(call):E730: using List as a String', + eq('Vim(call):E730: Using a List as a String', exc_exec('call inputdialog("", [])')) - eq('Vim(call):E730: using List as a String', + eq('Vim(call):E730: Using a List as a String', exc_exec('call inputdialog("", "", [])')) - eq('Vim(call):E730: using List as a String', + eq('Vim(call):E730: Using a List as a String', exc_exec('call inputdialog({"prompt": []})')) - eq('Vim(call):E730: using List as a String', + eq('Vim(call):E730: Using a List as a String', exc_exec('call inputdialog({"default": []})')) - eq('Vim(call):E730: using List as a String', + eq('Vim(call):E730: Using a List as a String', exc_exec('call inputdialog({"completion": []})')) eq('Vim(call):E5050: {opts} must be the only argument', exc_exec('call inputdialog({}, "default")')) @@ -512,13 +512,13 @@ describe('confirm()', function() eq(1, meths.get_var('a')) end - eq('Vim(call):E730: using List as a String', + eq('Vim(call):E730: Using a List as a String', pcall_err(command, 'call confirm([])')) - eq('Vim(call):E730: using List as a String', + eq('Vim(call):E730: Using a List as a String', pcall_err(command, 'call confirm("Are you sure?", [])')) eq('Vim(call):E745: Using a List as a Number', pcall_err(command, 'call confirm("Are you sure?", "&Yes\n&No\n", [])')) - eq('Vim(call):E730: using List as a String', + eq('Vim(call):E730: Using a List as a String', pcall_err(command, 'call confirm("Are you sure?", "&Yes\n&No\n", 0, [])')) end) diff --git a/test/functional/vimscript/map_functions_spec.lua b/test/functional/vimscript/map_functions_spec.lua index ba1b4d7a76..2c8fe69428 100644 --- a/test/functional/vimscript/map_functions_spec.lua +++ b/test/functional/vimscript/map_functions_spec.lua @@ -246,9 +246,9 @@ describe('mapset()', function() end) it('does not leak memory if lhs is missing', function() - eq('Vim:E460: entries missing in mapset() dict argument', + eq('Vim:E460: Entries missing in mapset() dict argument', pcall_err(exec_lua, [[vim.fn.mapset('n', false, {rhs = 'foo'})]])) - eq('Vim:E460: entries missing in mapset() dict argument', + eq('Vim:E460: Entries missing in mapset() dict argument', pcall_err(exec_lua, [[vim.fn.mapset('n', false, {callback = function() end})]])) end) end) diff --git a/test/functional/vimscript/null_spec.lua b/test/functional/vimscript/null_spec.lua index 1153baac46..4ba5dd6b45 100644 --- a/test/functional/vimscript/null_spec.lua +++ b/test/functional/vimscript/null_spec.lua @@ -65,7 +65,7 @@ describe('NULL', function() -- Correct behaviour null_expr_test('can be indexed with error message for empty list', 'L[0]', - 'E684: list index out of range: 0', nil) + 'E684: List index out of range: 0', nil) null_expr_test('can be splice-indexed', 'L[:]', 0, {}) null_expr_test('is not locked', 'islocked("v:_null_list")', 0, 0) null_test('is accepted by :for', 'for x in L|throw x|endfor', 0) @@ -80,7 +80,7 @@ describe('NULL', function() null_expr_test('can be copied', 'copy(L)', 0, {}) null_expr_test('can be deepcopied', 'deepcopy(L)', 0, {}) null_expr_test('does not crash when indexed', 'L[1]', - 'E684: list index out of range: 1', nil) + 'E684: List index out of range: 1', nil) null_expr_test('does not crash call()', 'call("arglistid", L)', 0, 0) null_expr_test('does not crash col()', 'col(L)', 0, 0) null_expr_test('does not crash virtcol()', 'virtcol(L)', 0, 0) diff --git a/test/functional/vimscript/writefile_spec.lua b/test/functional/vimscript/writefile_spec.lua index c816efd37b..521a4eb2b1 100644 --- a/test/functional/vimscript/writefile_spec.lua +++ b/test/functional/vimscript/writefile_spec.lua @@ -145,13 +145,13 @@ describe('writefile()', function() pcall_err(command, ('call writefile(%s, "%s", "b")'):format(arg, fname))) end for _, args in ipairs({'[], %s, "b"', '[], "' .. fname .. '", %s'}) do - eq('Vim(call):E806: using Float as a String', + eq('Vim(call):E806: Using a Float as a String', pcall_err(command, ('call writefile(%s)'):format(args:format('0.0')))) - eq('Vim(call):E730: using List as a String', + eq('Vim(call):E730: Using a List as a String', pcall_err(command, ('call writefile(%s)'):format(args:format('[]')))) - eq('Vim(call):E731: using Dictionary as a String', + eq('Vim(call):E731: Using a Dictionary as a String', pcall_err(command, ('call writefile(%s)'):format(args:format('{}')))) - eq('Vim(call):E729: using Funcref as a String', + eq('Vim(call):E729: Using a Funcref as a String', pcall_err(command, ('call writefile(%s)'):format(args:format('function("tr")')))) end eq('Vim(call):E5060: Unknown flag: «»', diff --git a/test/old/testdir/test_alot.vim b/test/old/testdir/test_alot.vim index 4a22315b9f..2a959f0834 100644 --- a/test/old/testdir/test_alot.vim +++ b/test/old/testdir/test_alot.vim @@ -17,7 +17,6 @@ source test_global.vim source test_move.vim source test_put.vim source test_reltime.vim -source test_scroll_opt.vim source test_searchpos.vim source test_set.vim source test_shift.vim diff --git a/test/old/testdir/test_assert.vim b/test/old/testdir/test_assert.vim index 4386492339..57d11d0e3a 100644 --- a/test/old/testdir/test_assert.vim +++ b/test/old/testdir/test_assert.vim @@ -1,5 +1,8 @@ " Test that the methods used for testing work. +source check.vim +source term_util.vim + func Test_assert_false() call assert_equal(0, assert_false(0)) call assert_equal(0, assert_false(v:false)) @@ -53,6 +56,14 @@ func Test_assert_equal() call assert_equal("\b\e\f\n\t\r\\\x01\x7f", 'x') call assert_match('Expected ''\\b\\e\\f\\n\\t\\r\\\\\\x01\\x7f'' but got ''x''', v:errors[0]) call remove(v:errors, 0) + + " many composing characters are handled properly + call setline(1, ' ') + norm 100gr݀ + call assert_equal(1, getline(1)) + call assert_match("Expected 1 but got '.* occurs 100 times]'", v:errors[0]) + call remove(v:errors, 0) + bwipe! endfunc func Test_assert_equal_dict() @@ -220,11 +231,11 @@ func Test_assert_fail_fails() call remove(v:errors, 0) call assert_equal(1, assert_fails('xxx', ['E9876'])) - call assert_match("Expected \\['E9876'\\] but got 'E492:", v:errors[0]) + call assert_match("Expected 'E9876' but got 'E492:", v:errors[0]) call remove(v:errors, 0) call assert_equal(1, assert_fails('xxx', ['E492:', 'E9876'])) - call assert_match("Expected \\['E492:', 'E9876'\\] but got 'E492:", v:errors[0]) + call assert_match("Expected 'E9876' but got 'E492:", v:errors[0]) call remove(v:errors, 0) call assert_equal(1, assert_fails('echo', '', 'echo command')) @@ -240,35 +251,77 @@ func Test_assert_fail_fails() catch let exp = v:exception endtry - call assert_match("E856: assert_fails() second argument", exp) + call assert_match("E856: \"assert_fails()\" second argument", exp) try call assert_equal(1, assert_fails('xxx', ['1', '2', '3'])) catch let exp = v:exception endtry - call assert_match("E856: assert_fails() second argument", exp) + call assert_match("E856: \"assert_fails()\" second argument", exp) + + try + call assert_equal(1, assert_fails('xxx', v:_null_list)) + catch + let exp = v:exception + endtry + call assert_match("E856: \"assert_fails()\" second argument", exp) + + try + call assert_equal(1, assert_fails('xxx', [])) + catch + let exp = v:exception + endtry + call assert_match("E856: \"assert_fails()\" second argument", exp) try call assert_equal(1, assert_fails('xxx', #{one: 1})) catch let exp = v:exception endtry - call assert_match("E856: assert_fails() second argument", exp) + call assert_match("E1222: String or List required for argument 2", exp) + + try + call assert_equal(0, assert_fails('xxx', [#{one: 1}])) + catch + let exp = v:exception + endtry + call assert_match("E731: Using a Dictionary as a String", exp) + + let exp = '' + try + call assert_equal(0, assert_fails('xxx', ['E492', #{one: 1}])) + catch + let exp = v:exception + endtry + call assert_match("E731: Using a Dictionary as a String", exp) try call assert_equal(1, assert_fails('xxx', 'E492', '', 'burp')) catch let exp = v:exception endtry - call assert_match("E1115: assert_fails() fourth argument must be a number", exp) + call assert_match("E1210: Number required for argument 4", exp) try call assert_equal(1, assert_fails('xxx', 'E492', '', 54, 123)) catch let exp = v:exception endtry - call assert_match("E1116: assert_fails() fifth argument must be a string", exp) + call assert_match("E1174: String required for argument 5", exp) + + call assert_equal(1, assert_fails('c0', ['', '\(.\)\1'])) + call assert_match("Expected '\\\\\\\\(.\\\\\\\\)\\\\\\\\1' but got 'E939: Positive count required: c0': c0", v:errors[0]) + call remove(v:errors, 0) + + " Test for matching the line number and the script name in an error message + call writefile(['', 'call Xnonexisting()'], 'Xassertfails.vim', 'D') + call assert_fails('source Xassertfails.vim', 'E117:', '', 10) + call assert_match("Expected 10 but got 2", v:errors[0]) + call remove(v:errors, 0) + call assert_fails('source Xassertfails.vim', 'E117:', '', 2, 'Xabc') + call assert_match("Expected 'Xabc' but got .*Xassertfails.vim", v:errors[0]) + call remove(v:errors, 0) endfunc func Test_assert_fails_in_try_block() @@ -277,6 +330,23 @@ func Test_assert_fails_in_try_block() endtry endfunc +" Test that assert_fails() in a timer does not cause a hit-enter prompt. +" Requires using a terminal, in regular tests the hit-enter prompt won't be +" triggered. +func Test_assert_fails_in_timer() + CheckRunVimInTerminal + + let buf = RunVimInTerminal('', {'rows': 6}) + let cmd = ":call timer_start(0, {-> assert_fails('call', 'E471:')})" + call term_sendkeys(buf, cmd) + call WaitForAssert({-> assert_equal(cmd, term_getline(buf, 6))}) + call term_sendkeys(buf, "\<CR>") + call TermWait(buf, 100) + call assert_match('E471: Argument required', term_getline(buf, 6)) + + call StopVimInTerminal(buf) +endfunc + func Test_assert_beeps() new call assert_equal(0, assert_beeps('normal h')) @@ -293,6 +363,12 @@ func Test_assert_beeps() bwipe endfunc +func Test_assert_nobeep() + call assert_equal(1, assert_nobeep('normal! cr')) + call assert_match("command did beep: normal! cr", v:errors[0]) + call remove(v:errors, 0) +endfunc + func Test_assert_inrange() call assert_equal(0, assert_inrange(7, 7, 7)) call assert_equal(0, assert_inrange(5, 7, 5)) @@ -314,21 +390,32 @@ func Test_assert_inrange() call assert_fails('call assert_inrange(1, 1)', 'E119:') - if has('float') - call assert_equal(0, assert_inrange(7.0, 7, 7)) - call assert_equal(0, assert_inrange(7, 7.0, 7)) - call assert_equal(0, assert_inrange(7, 7, 7.0)) - call assert_equal(0, assert_inrange(5, 7, 5.0)) - call assert_equal(0, assert_inrange(5, 7, 6.0)) - call assert_equal(0, assert_inrange(5, 7, 7.0)) - - call assert_equal(1, assert_inrange(5, 7, 4.0)) - call assert_match("Expected range 5.0 - 7.0, but got 4.0", v:errors[0]) - call remove(v:errors, 0) - call assert_equal(1, assert_inrange(5, 7, 8.0)) - call assert_match("Expected range 5.0 - 7.0, but got 8.0", v:errors[0]) - call remove(v:errors, 0) - endif + call assert_equal(0, assert_inrange(7.0, 7, 7)) + call assert_equal(0, assert_inrange(7, 7.0, 7)) + call assert_equal(0, assert_inrange(7, 7, 7.0)) + call assert_equal(0, assert_inrange(5, 7, 5.0)) + call assert_equal(0, assert_inrange(5, 7, 6.0)) + call assert_equal(0, assert_inrange(5, 7, 7.0)) + + call assert_equal(1, assert_inrange(5, 7, 4.0)) + call assert_match("Expected range 5.0 - 7.0, but got 4.0", v:errors[0]) + call remove(v:errors, 0) + call assert_equal(1, assert_inrange(5, 7, 8.0)) + call assert_match("Expected range 5.0 - 7.0, but got 8.0", v:errors[0]) + call remove(v:errors, 0) + + " Use a custom message + call assert_equal(1, assert_inrange(5, 7, 8, "Higher")) + call assert_match("Higher: Expected range 5 - 7, but got 8", v:errors[0]) + call remove(v:errors, 0) + call assert_equal(1, assert_inrange(5, 7, 8.0, "Higher")) + call assert_match("Higher: Expected range 5.0 - 7.0, but got 8.0", v:errors[0]) + call remove(v:errors, 0) + + " Invalid arguments + call assert_fails("call assert_inrange([], 2, 3)", 'E1219:') + call assert_fails("call assert_inrange(1, [], 3)", 'E1219:') + call assert_fails("call assert_inrange(1, 2, [])", 'E1219:') endfunc func Test_assert_with_msg() @@ -366,6 +453,21 @@ func Test_mouse_position() let &mouse = save_mouse endfunc +" Test for the test_alloc_fail() function +func Test_test_alloc_fail() + throw 'Skipped: Nvim does not support test_alloc_fail()' + call assert_fails('call test_alloc_fail([], 1, 1)', 'E474:') + call assert_fails('call test_alloc_fail(10, [], 1)', 'E474:') + call assert_fails('call test_alloc_fail(10, 1, [])', 'E474:') + call assert_fails('call test_alloc_fail(999999, 1, 1)', 'E474:') +endfunc + +" Test for the test_option_not_set() function +func Test_test_option_not_set() + throw 'Skipped: Nvim does not support test_option_not_set()' + call assert_fails('call test_option_not_set("Xinvalidopt")', 'E475:') +endfunc + " Must be last. func Test_zz_quit_detected() " Verify that if a test function ends Vim the test script detects this. diff --git a/test/old/testdir/test_blockedit.vim b/test/old/testdir/test_blockedit.vim index 7b56b1554f..e0cfe11af0 100644 --- a/test/old/testdir/test_blockedit.vim +++ b/test/old/testdir/test_blockedit.vim @@ -18,6 +18,7 @@ endfunc func Test_blockinsert_autoindent() new let lines =<< trim END + vim9script var d = { a: () => 0, b: () => 0, @@ -28,40 +29,42 @@ func Test_blockinsert_autoindent() filetype plugin indent on setlocal sw=2 et ft=vim setlocal indentkeys+=: - exe "norm! 2Gf)\<c-v>2jA: asdf\<esc>" + exe "norm! 3Gf)\<c-v>2jA: asdf\<esc>" let expected =<< trim END + vim9script var d = { a: (): asdf => 0, b: (): asdf => 0, c: (): asdf => 0, } END - call assert_equal(expected, getline(1, 5)) + call assert_equal(expected, getline(1, 6)) " insert on the next column should do exactly the same :%dele call setline(1, lines) - exe "norm! 2Gf)l\<c-v>2jI: asdf\<esc>" - call assert_equal(expected, getline(1, 5)) + exe "norm! 3Gf)l\<c-v>2jI: asdf\<esc>" + call assert_equal(expected, getline(1, 6)) :%dele call setline(1, lines) setlocal sw=8 noet - exe "norm! 2Gf)\<c-v>2jA: asdf\<esc>" + exe "norm! 3Gf)\<c-v>2jA: asdf\<esc>" let expected =<< trim END + vim9script var d = { a: (): asdf => 0, b: (): asdf => 0, c: (): asdf => 0, } END - call assert_equal(expected, getline(1, 5)) + call assert_equal(expected, getline(1, 6)) " insert on the next column should do exactly the same :%dele call setline(1, lines) - exe "norm! 2Gf)l\<c-v>2jI: asdf\<esc>" - call assert_equal(expected, getline(1, 5)) + exe "norm! 3Gf)l\<c-v>2jI: asdf\<esc>" + call assert_equal(expected, getline(1, 6)) filetype off bwipe! diff --git a/test/old/testdir/test_breakindent.vim b/test/old/testdir/test_breakindent.vim index 0d1753182e..f6c0e32adf 100644 --- a/test/old/testdir/test_breakindent.vim +++ b/test/old/testdir/test_breakindent.vim @@ -87,7 +87,7 @@ func Test_breakindent02_vartabs() endif " simple breakindent test with showbreak set call s:test_windows('setl briopt=min:0 sbr=>> vts=4') - let lines = s:screen_lines(line('.'),8) + let lines = s:screen_lines(line('.'), 8) let expect = [ \ " abcd", \ " >>qr", @@ -100,7 +100,7 @@ endfunc func Test_breakindent03() " simple breakindent test with showbreak set and briopt including sbr call s:test_windows('setl briopt=sbr,min:0 sbr=++') - let lines = s:screen_lines(line('.'),8) + let lines = s:screen_lines(line('.'), 8) let expect=[ \ " abcd", \ "++ qrst", @@ -117,7 +117,7 @@ func Test_breakindent03_vartabs() return endif call s:test_windows('setl briopt=sbr,min:0 sbr=++ vts=4') - let lines = s:screen_lines(line('.'),8) + let lines = s:screen_lines(line('.'), 8) let expect = [ \ " abcd", \ "++ qrst", @@ -132,7 +132,7 @@ func Test_breakindent04() " breakindent set with min width 18 set sbr=<<< call s:test_windows('setl sbr=NONE briopt=min:18') - let lines = s:screen_lines(line('.'),8) + let lines = s:screen_lines(line('.'), 8) let expect = [ \ " abcd", \ " qrstuv", @@ -150,7 +150,7 @@ func Test_breakindent04_vartabs() return endif call s:test_windows('setl sbr= briopt=min:18 vts=4') - let lines = s:screen_lines(line('.'),8) + let lines = s:screen_lines(line('.'), 8) let expect = [ \ " abcd", \ " qrstuv", @@ -583,7 +583,7 @@ func Test_breakindent16() redraw! let lines = s:screen_lines(1,10) let expect = [ - \ " 789012", + \ "<<< 789012", \ " 345678", \ " 901234", \ ] @@ -611,7 +611,7 @@ func Test_breakindent16_vartabs() redraw! let lines = s:screen_lines(1,10) let expect = [ - \ " 789012", + \ "<<< 789012", \ " 345678", \ " 901234", \ ] @@ -711,25 +711,25 @@ endfunc func Test_breakindent20_cpo_n_nextpage() let s:input = "" call s:test_windows('setl breakindent briopt=min:14 cpo+=n number') - call setline(1, repeat('a', 200)) + call setline(1, repeat('abcdefghijklmnopqrst', 10)) norm! 1gg redraw! let lines = s:screen_lines(1, 20) let expect = [ - \ " 1 aaaaaaaaaaaaaaaa", - \ " aaaaaaaaaaaaaaaa", - \ " aaaaaaaaaaaaaaaa", + \ " 1 abcdefghijklmnop", + \ " qrstabcdefghijkl", + \ " mnopqrstabcdefgh", \ ] call s:compare_lines(expect, lines) " Scroll down one screen line setl scrolloff=5 - norm! 5gj + norm! 6gj redraw! let lines = s:screen_lines(1, 20) let expect = [ - \ "--1 aaaaaaaaaaaaaaaa", - \ " aaaaaaaaaaaaaaaa", - \ " aaaaaaaaaaaaaaaa", + \ "<<< qrstabcdefghijkl", + \ " mnopqrstabcdefgh", + \ " ijklmnopqrstabcd", \ ] call s:compare_lines(expect, lines) @@ -737,18 +737,18 @@ func Test_breakindent20_cpo_n_nextpage() norm! 1gg let lines = s:screen_lines(1, 20) let expect = [ - \ " 1 aaaaaaaaaaaaaaaa", - \ " aaaaaaaaaaaaaa", - \ " aaaaaaaaaaaaaa", + \ " 1 abcdefghijklmnop", + \ " qrstabcdefghij", + \ " klmnopqrstabcd", \ ] call s:compare_lines(expect, lines) " Scroll down one screen line - norm! 5gj + norm! 6gj let lines = s:screen_lines(1, 20) let expect = [ - \ "--1 aaaaaaaaaaaaaa", - \ " aaaaaaaaaaaaaa", - \ " aaaaaaaaaaaaaa", + \ "<<< qrstabcdefghij", + \ " klmnopqrstabcd", + \ " efghijklmnopqr", \ ] call s:compare_lines(expect, lines) diff --git a/test/old/testdir/test_conceal.vim b/test/old/testdir/test_conceal.vim index e3b8f767b8..63e17d8f2f 100644 --- a/test/old/testdir/test_conceal.vim +++ b/test/old/testdir/test_conceal.vim @@ -188,6 +188,32 @@ func Test_conceal_resize_term() call StopVimInTerminal(buf) endfunc +func Test_conceal_linebreak() + CheckScreendump + + let code =<< trim [CODE] + vim9script + &wrap = true + &conceallevel = 2 + &concealcursor = 'nc' + &linebreak = true + &showbreak = '+ ' + var line: string = 'a`a`a`a`' + .. 'a'->repeat(&columns - 15) + .. ' b`b`' + .. 'b'->repeat(&columns - 10) + .. ' cccccc' + ['x'->repeat(&columns), '', line]->setline(1) + syntax region CodeSpan matchgroup=Delimiter start=/\z(`\+\)/ end=/\z1/ concealends + [CODE] + call writefile(code, 'XTest_conceal_linebreak', 'D') + let buf = RunVimInTerminal('-S XTest_conceal_linebreak', {'rows': 8}) + call VerifyScreenDump(buf, 'Test_conceal_linebreak_1', {}) + + " clean up + call StopVimInTerminal(buf) +endfunc + " Tests for correct display (cursor column position) with +conceal and " tabulators. Need to run this test in a separate Vim instance. Otherwise the " screen is not updated (lazy redraw) and the cursor position is wrong. diff --git a/test/old/testdir/test_diffmode.vim b/test/old/testdir/test_diffmode.vim index 0049398776..ac90aaaa02 100644 --- a/test/old/testdir/test_diffmode.vim +++ b/test/old/testdir/test_diffmode.vim @@ -1605,6 +1605,21 @@ func Test_diff_scroll() call delete('Xright') endfunc +" This was scrolling too many lines. +func Test_diff_scroll_wrap_on() + 20new + 40vsplit + call setline(1, map(range(1, 9), 'repeat(v:val, 200)')) + setlocal number diff so=0 + redraw + normal! jj + call assert_equal(1, winsaveview().topline) + normal! j + call assert_equal(2, winsaveview().topline) + bwipe! + bwipe! +endfunc + " This was trying to update diffs for a buffer being closed func Test_diff_only() silent! lfile diff --git a/test/old/testdir/test_display.vim b/test/old/testdir/test_display.vim index b642f39c9f..f27a8362a9 100644 --- a/test/old/testdir/test_display.vim +++ b/test/old/testdir/test_display.vim @@ -478,5 +478,26 @@ func Test_display_lastline() call assert_fails(':set fillchars=lastline:〇', 'E474:') endfunc +func Test_display_long_lastline() + CheckScreendump + + let lines =<< trim END + set display=lastline + call setline(1, [ + \'aaaaa'->repeat(100), + \'bbbbb '->repeat(7) .. 'ccccc '->repeat(7) .. 'ddddd '->repeat(7) + \]) + END + + call writefile(lines, 'XdispLongline', 'D') + let buf = RunVimInTerminal('-S XdispLongline', #{rows: 14, cols: 35}) + + call term_sendkeys(buf, "482|") + call VerifyScreenDump(buf, 'Test_display_long_line_1', {}) + call term_sendkeys(buf, "D") + call VerifyScreenDump(buf, 'Test_display_long_line_2', {}) + + call StopVimInTerminal(buf) +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_expand.vim b/test/old/testdir/test_expand.vim index 4f5bb67d21..cd537f4ea1 100644 --- a/test/old/testdir/test_expand.vim +++ b/test/old/testdir/test_expand.vim @@ -141,7 +141,7 @@ func Test_source_sfile() if RunVim([], [], '--clean -s Xscript') call assert_equal([ \ 'E1274: No script file name to substitute for "<script>"', - \ 'E498: no :source file name to substitute for "<sfile>"'], + \ 'E498: No :source file name to substitute for "<sfile>"'], \ readfile('Xresult')) endif call delete('Xscript') diff --git a/test/old/testdir/test_expr_utf8.vim b/test/old/testdir/test_expr_utf8.vim index fad725d2e5..c6d2e4ed7e 100644 --- a/test/old/testdir/test_expr_utf8.vim +++ b/test/old/testdir/test_expr_utf8.vim @@ -31,4 +31,14 @@ func Test_strcharpart() call assert_equal('a', strcharpart('àxb', 0, 1)) call assert_equal('̀', strcharpart('àxb', 1, 1)) call assert_equal('x', strcharpart('àxb', 2, 1)) + + + call assert_equal('a', strcharpart('àxb', 0, 1, 0)) + call assert_equal('à', strcharpart('àxb', 0, 1, 1)) + call assert_equal('x', strcharpart('àxb', 1, 1, 1)) + + call assert_fails("let v = strcharpart('abc', 0, 0, [])", 'E745:') + call assert_fails("let v = strcharpart('abc', 0, 0, 2)", 'E1023:') endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_fileformat.vim b/test/old/testdir/test_fileformat.vim index 8d727a68c4..e12b1d1a9a 100644 --- a/test/old/testdir/test_fileformat.vim +++ b/test/old/testdir/test_fileformat.vim @@ -73,7 +73,12 @@ func Test_fileformats() call s:concat_files('XXMac', 'XXEol', 'XXMacEol') call s:concat_files('XXUxDs', 'XXMac', 'XXUxDsMc') - new + " The :bwipe commands below cause us to get back to the current buffer. + " Avoid stray errors for various 'fileformat' values which may cause a + " modeline to be misinterpreted by wiping the buffer and editing a new one. + only! + bwipe! + enew " Test 1: try reading and writing with 'fileformats' empty set fileformats= diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index 789430fc84..2d7a24090f 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -346,6 +346,7 @@ let s:filename_checks = { \ 'lsl': ['file.lsl'], \ 'lss': ['file.lss'], \ 'lua': ['file.lua', 'file.rockspec', 'file.nse', '.luacheckrc', '.busted'], + \ 'luau': ['file.luau'], \ 'lynx': ['lynx.cfg'], \ 'lyrics': ['file.lrc'], \ 'm3build': ['m3makefile', 'm3overrides'], diff --git a/test/old/testdir/test_let.vim b/test/old/testdir/test_let.vim index 0d84164274..bf119bdeab 100644 --- a/test/old/testdir/test_let.vim +++ b/test/old/testdir/test_let.vim @@ -338,7 +338,43 @@ func Test_let_heredoc_fails() call assert_report('No exception thrown') catch /E488:/ catch - call assert_report("Caught exception: " .. v:exception) + call assert_report('Caught exception: ' .. v:exception) + endtry + + try + let &commentstring =<< trim TEXT + change + insert + append + TEXT + call assert_report('No exception thrown') + catch /E730:/ + catch + call assert_report('Caught exception: ' .. v:exception) + endtry + + try + let $SOME_ENV_VAR =<< trim TEXT + change + insert + append + TEXT + call assert_report('No exception thrown') + catch /E730:/ + catch + call assert_report('Caught exception: ' .. v:exception) + endtry + + try + let @r =<< trim TEXT + change + insert + append + TEXT + call assert_report('No exception thrown') + catch /E730:/ + catch + call assert_report('Caught exception: ' .. v:exception) endtry let text =<< trim END @@ -504,6 +540,32 @@ E z END call assert_equal([' x', ' \y', ' z'], [a, b, c]) + + " unpack assignment without whitespace + let[a,b,c]=<<END +change +insert +append +END + call assert_equal(['change', 'insert', 'append'], [a, b, c]) + + " curly braces name and list slice assignment + let foo_3_bar = ['', '', ''] + let foo_{1 + 2}_bar[ : ] =<< END +change +insert +append +END + call assert_equal(['change', 'insert', 'append'], foo_3_bar) + + " dictionary key containing brackets and spaces + let d = {'abc] 123': 'baz'} + let d[d['abc] 123'] .. '{'] =<< END +change +insert +append +END + call assert_equal(['change', 'insert', 'append'], d['baz{']) endfunc " Test for evaluating Vim expressions in a heredoc using {expr} diff --git a/test/old/testdir/test_listdict.vim b/test/old/testdir/test_listdict.vim index 0ff3582da9..11dade18f3 100644 --- a/test/old/testdir/test_listdict.vim +++ b/test/old/testdir/test_listdict.vim @@ -1,5 +1,7 @@ " Tests for the List and Dict types +source vim9.vim + func TearDown() " Run garbage collection after every test call test_garbagecollect_now() @@ -37,6 +39,23 @@ func Test_list_slice() let l[:1] += [1, 2] let l[2:] -= [1] call assert_equal([2, 4, 2], l) + + let lines =<< trim END + VAR l = [1, 2] + call assert_equal([1, 2], l[:]) + call assert_equal([2], l[-1 : -1]) + call assert_equal([1, 2], l[-2 : -1]) + END + call CheckLegacyAndVim9Success(lines) + + let l = [1, 2] + call assert_equal([], l[-3 : -1]) + + let lines =<< trim END + var l = [1, 2] + assert_equal([1, 2], l[-3 : -1]) + END + call CheckDefAndScriptSuccess(lines) endfunc " List identity diff --git a/test/old/testdir/test_listlbr.vim b/test/old/testdir/test_listlbr.vim index a746779e73..2e66fd4ccb 100644 --- a/test/old/testdir/test_listlbr.vim +++ b/test/old/testdir/test_listlbr.vim @@ -223,7 +223,7 @@ func Test_virtual_block_and_vbA() exe "norm! $3B\<C-v>eAx\<Esc>" let lines = s:screen_lines([1, 10], winwidth(0)) let expect = [ -\ "foobar foobar ", +\ "<<<bar foobar ", \ "foobar foobar ", \ "foobar foobar ", \ "foobar foobar ", diff --git a/test/old/testdir/test_listlbr_utf8.vim b/test/old/testdir/test_listlbr_utf8.vim index df1ed78119..15b248964f 100644 --- a/test/old/testdir/test_listlbr_utf8.vim +++ b/test/old/testdir/test_listlbr_utf8.vim @@ -266,7 +266,7 @@ func Test_chinese_char_on_wrap_column() norm! $ redraw! let expect=[ -\ '中aaaaaaaaaaaaaaaaa>', +\ '<<<aaaaaaaaaaaaaaaa>', \ '中aaaaaaaaaaaaaaaaa>', \ '中aaaaaaaaaaaaaaaaa>', \ '中aaaaaaaaaaaaaaaaa>', diff --git a/test/old/testdir/test_normal.vim b/test/old/testdir/test_normal.vim index fe8611d527..330a16dffb 100644 --- a/test/old/testdir/test_normal.vim +++ b/test/old/testdir/test_normal.vim @@ -250,9 +250,10 @@ func Test_normal_formatexpr_returns_nonzero() setlocal formatexpr=Format() normal VGgq call assert_equal(['one two'], getline(1, '$')) + setlocal formatexpr= delfunc Format - close! + bwipe! endfunc " Test for using a script-local function for 'formatexpr' @@ -1329,7 +1330,7 @@ func Test_vert_scroll_cmds() call assert_equal(15, line('w$')) set foldenable& - close! + bwipe! endfunc func Test_scroll_in_ex_mode() @@ -2350,7 +2351,7 @@ func Test_normal_section() call assert_equal(2, line('.')) call assert_equal(-1, foldclosedend(line('.'))) - close! + bwipe! endfunc " Test for changing case using u, U, gu, gU and ~ (tilde) commands @@ -2447,7 +2448,8 @@ func Test_normal_changecase_turkish() " can't use Turkish locale throw 'Skipped: Turkish locale not available' endtry - close! + + bwipe! endfunc " Test for r (replace) command @@ -2524,7 +2526,6 @@ endfunc " Test for g`, g;, g,, g&, gv, gk, gj, gJ, g0, g^, g_, gm, g$, gM, g CTRL-G, " gi and gI commands func Test_normal33_g_cmd2() - CheckFeature jumplist call Setup_NewWindow() " Test for g` clearjumps @@ -2982,7 +2983,8 @@ func Test_normal_nvend() call assert_equal([4, 5], [line('.'), col('.')]) exe "normal! \<C-End>" call assert_equal([10, 6], [line('.'), col('.')]) - close! + + bwipe! endfunc " Test for cw cW ce @@ -3479,12 +3481,11 @@ func Test_java_motion() call assert_equal([7, 8, 15], [line('.'), col('.'), virtcol('.')]) call assert_equal(-1, foldclosedend(7)) - close! + bwipe! endfunc " Tests for g cmds func Test_normal_gdollar_cmd() - CheckFeature jumplist call Setup_NewWindow() " Make long lines that will wrap %s/$/\=repeat(' foobar', 10)/ @@ -3595,7 +3596,8 @@ func Test_normal_yank_with_excmd() let @a = '' call feedkeys("\"ay:if v:true\<CR>normal l\<CR>endif\<CR>", 'xt') call assert_equal('f', @a) - close! + + bwipe! endfunc " Test for supplying a count to a normal-mode command across a cursorhold call @@ -3617,7 +3619,8 @@ func Test_normal_cursorhold_with_count() au! augroup END au! normalcHoldTest - close! + + bwipe! delfunc s:cHold endfunc @@ -3641,7 +3644,8 @@ func Test_horiz_motion() call assert_equal(11, col('.')) exe "normal! $\<C-BS>" call assert_equal(10, col('.')) - close! + + bwipe! endfunc " Test for using a : command in operator pending mode @@ -3649,7 +3653,7 @@ func Test_normal_colon_op() new call setline(1, ['one', 'two']) call assert_beeps("normal! Gc:d\<CR>") - close! + bwipe! endfunc " Test for d and D commands @@ -3674,7 +3678,7 @@ func Test_normal_delete_cmd() call assert_fails('normal D', 'E21:') call assert_fails('normal d$', 'E21:') - close! + bwipe! endfunc " Test for deleting or changing characters across lines with 'whichwrap' @@ -3694,7 +3698,8 @@ func Test_normal_op_across_lines() call setline(1, ['one two', 'three four']) exe "norm! $3x" call assert_equal(['one twhree four'], getline(1, '$')) - close! + + bwipe! set whichwrap& endfunc @@ -3732,23 +3737,54 @@ func Test_normal_word_move() normal 3Gyb call assert_equal("two\n ", @") - close! + bwipe! endfunc " Test for 'scrolloff' with a long line that doesn't fit in the screen -func Test_normal_scroloff() +func Test_normal_scrolloff() 10new - 80vnew - call setline(1, repeat('a', 1000)) + 60vnew + call setline(1, ' 1 ' .. repeat('a', 57) + \ .. ' 2 ' .. repeat('b', 57) + \ .. ' 3 ' .. repeat('c', 57) + \ .. ' 4 ' .. repeat('d', 57) + \ .. ' 5 ' .. repeat('e', 57) + \ .. ' 6 ' .. repeat('f', 57) + \ .. ' 7 ' .. repeat('g', 57) + \ .. ' 8 ' .. repeat('h', 57) + \ .. ' 9 ' .. repeat('i', 57) + \ .. '10 ' .. repeat('j', 57) + \ .. '11 ' .. repeat('k', 57) + \ .. '12 ' .. repeat('l', 57) + \ .. '13 ' .. repeat('m', 57) + \ .. '14 ' .. repeat('n', 57) + \ .. '15 ' .. repeat('o', 57) + \ .. '16 ' .. repeat('p', 57) + \ .. '17 ' .. repeat('q', 57) + \ .. '18 ' .. repeat('r', 57) + \ .. '19 ' .. repeat('s', 57) + \ .. '20 ' .. repeat('t', 57) + \ .. '21 ' .. repeat('u', 57) + \ .. '22 ' .. repeat('v', 57) + \ .. '23 ' .. repeat('w', 57) + \ .. '24 ' .. repeat('x', 57) + \ .. '25 ' .. repeat('y', 57) + \ .. '26 ' .. repeat('z', 57) + \ ) set scrolloff=10 normal gg10gj - call assert_equal(8, winline()) + call assert_equal(6, winline()) normal 10gj - call assert_equal(10, winline()) + call assert_equal(6, winline()) normal 10gk - call assert_equal(3, winline()) + call assert_equal(6, winline()) + normal 0 + call assert_equal(1, winline()) + normal $ + call assert_equal(10, winline()) + set scrolloff& - close! + bwipe! endfunc " Test for vertical scrolling with CTRL-F and CTRL-B with a long line @@ -3768,7 +3804,8 @@ func Test_normal_vert_scroll_longline() exe "normal \<C-B>\<C-B>" call assert_equal(5, line('.')) call assert_equal(5, winline()) - close! + + bwipe! endfunc " Test for jumping in a file using % @@ -3781,7 +3818,8 @@ func Test_normal_percent_jump() call feedkeys('50%', 'xt') call assert_equal(50, line('.')) call assert_equal(-1, foldclosedend(50)) - close! + + bwipe! endfunc " Test for << and >> commands to shift text by 'shiftwidth' @@ -3874,24 +3912,25 @@ func Test_mouse_shape_after_failed_change() CheckCanRunGui let lines =<< trim END + vim9script set mouseshape+=o:busy setlocal nomodifiable - let g:mouse_shapes = [] - - func SaveMouseShape(timer) - let g:mouse_shapes += [getmouseshape()] - endfunc - - func SaveAndQuit(timer) - call writefile(g:mouse_shapes, 'Xmouseshapes') - quit - endfunc + var mouse_shapes = [] - call timer_start(50, {_ -> feedkeys('c')}) - call timer_start(100, 'SaveMouseShape') - call timer_start(150, {_ -> feedkeys('c')}) - call timer_start(200, 'SaveMouseShape') - call timer_start(250, 'SaveAndQuit') + feedkeys('c') + timer_start(50, (_) => { + mouse_shapes += [getmouseshape()] + timer_start(50, (_) => { + feedkeys('c') + timer_start(50, (_) => { + mouse_shapes += [getmouseshape()] + timer_start(50, (_) => { + writefile(mouse_shapes, 'Xmouseshapes') + quit + }) + }) + }) + }) END call writefile(lines, 'Xmouseshape.vim', 'D') call RunVim([], [], "-g -S Xmouseshape.vim") diff --git a/test/old/testdir/test_number.vim b/test/old/testdir/test_number.vim index 521b0cf706..cf777fd918 100644 --- a/test/old/testdir/test_number.vim +++ b/test/old/testdir/test_number.vim @@ -138,7 +138,7 @@ func Test_number_with_linewrap1() call s:validate_cursor() let lines = s:screen_lines(1, 3) let expect = [ -\ "--1 aaaa", +\ "<<< aaaa", \ " aaaa", \ " aaaa", \ ] diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim index f101f550d1..cf8bf1903f 100644 --- a/test/old/testdir/test_options.vim +++ b/test/old/testdir/test_options.vim @@ -721,7 +721,7 @@ func Test_backupskip() let &backupskip = backupskip endfunc -func Test_copy_winopt() +func Test_buf_copy_winopt() set hidden " Test copy option from current buffer in window @@ -775,6 +775,108 @@ func Test_copy_winopt() set hidden& endfunc +func Test_split_copy_options() + let values = [ + \['cursorbind', 1, 0], + \['fillchars', '"vert:-"', '"' .. &fillchars .. '"'], + \['list', 1, 0], + \['listchars', '"space:-"', '"' .. &listchars .. '"'], + \['number', 1, 0], + \['relativenumber', 1, 0], + \['scrollbind', 1, 0], + \['smoothscroll', 1, 0], + \['virtualedit', '"block"', '"' .. &virtualedit .. '"'], + "\ ['wincolor', '"Search"', '"' .. &wincolor .. '"'], + \['wrap', 0, 1], + \] + if has('linebreak') + let values += [ + \['breakindent', 1, 0], + \['breakindentopt', '"min:5"', '"' .. &breakindentopt .. '"'], + \['linebreak', 1, 0], + \['numberwidth', 7, 4], + \['showbreak', '"++"', '"' .. &showbreak .. '"'], + \] + endif + if has('rightleft') + let values += [ + \['rightleft', 1, 0], + \['rightleftcmd', '"search"', '"' .. &rightleftcmd .. '"'], + \] + endif + if has('statusline') + let values += [ + \['statusline', '"---%f---"', '"' .. &statusline .. '"'], + \] + endif + if has('spell') + let values += [ + \['spell', 1, 0], + \] + endif + if has('syntax') + let values += [ + \['cursorcolumn', 1, 0], + \['cursorline', 1, 0], + \['cursorlineopt', '"screenline"', '"' .. &cursorlineopt .. '"'], + \['colorcolumn', '"+1"', '"' .. &colorcolumn .. '"'], + \] + endif + if has('diff') + let values += [ + \['diff', 1, 0], + \] + endif + if has('conceal') + let values += [ + \['concealcursor', '"nv"', '"' .. &concealcursor .. '"'], + \['conceallevel', '3', &conceallevel], + \] + endif + if has('terminal') + let values += [ + \['termwinkey', '"<C-X>"', '"' .. &termwinkey .. '"'], + \['termwinsize', '"10x20"', '"' .. &termwinsize .. '"'], + \] + endif + if has('folding') + let values += [ + \['foldcolumn', '"5"', &foldcolumn], + \['foldenable', 0, 1], + \['foldexpr', '"2 + 3"', '"' .. &foldexpr .. '"'], + \['foldignore', '"+="', '"' .. &foldignore .. '"'], + \['foldlevel', 4, &foldlevel], + \['foldmarker', '">>,<<"', '"' .. &foldmarker .. '"'], + \['foldmethod', '"marker"', '"' .. &foldmethod .. '"'], + \['foldminlines', 3, &foldminlines], + \['foldnestmax', 17, &foldnestmax], + \['foldtext', '"closed"', '"' .. &foldtext .. '"'], + \] + endif + if has('signs') + let values += [ + \['signcolumn', '"number"', '"' .. &signcolumn .. '"'], + \] + endif + + " set options to non-default value + for item in values + exe $"let &{item[0]} = {item[1]}" + endfor + + " check values are set in new window + split + for item in values + exe $'call assert_equal({item[1]}, &{item[0]}, "{item[0]}")' + endfor + + " restore + close + for item in values + exe $"let &{item[0]} = {item[1]}" + endfor +endfunc + func Test_shortmess_F() new call assert_match('\[No Name\]', execute('file')) @@ -792,8 +894,6 @@ endfunc func Test_shortmess_F2() e file1 e file2 - " Accommodate Nvim default. - set shortmess-=F call assert_match('file1', execute('bn', '')) call assert_match('file2', execute('bn', '')) set shortmess+=F @@ -811,12 +911,12 @@ func Test_shortmess_F2() " call assert_false(test_getvalue('need_fileinfo')) call assert_true(empty(execute('bn', ''))) " call assert_false(test_getvalue('need_fileinfo')) - " Accommodate Nvim default. - set shortmess-=F + set shortmess-=F " Accommodate Nvim default. call assert_match('file1', execute('bn', '')) call assert_match('file2', execute('bn', '')) bwipe bwipe + " call assert_fails('call test_getvalue("abc")', 'E475:') endfunc func Test_local_scrolloff() diff --git a/test/old/testdir/test_scroll_opt.vim b/test/old/testdir/test_scroll_opt.vim index 64f4ced470..8402fa51e2 100644 --- a/test/old/testdir/test_scroll_opt.vim +++ b/test/old/testdir/test_scroll_opt.vim @@ -1,4 +1,8 @@ -" Test for reset 'scroll' +" Test for reset 'scroll' and 'smoothscroll' + +source check.vim +source screendump.vim +source mouse.vim func Test_reset_scroll() let scr = &l:scroll @@ -51,4 +55,563 @@ func Test_scolloff_even_line_count() bwipe! endfunc +func Test_CtrlE_CtrlY_stop_at_end() + enew + call setline(1, ['one', 'two']) + set number + exe "normal \<C-Y>" + call assert_equal([" 1 one "], ScreenLines(1, 10)) + exe "normal \<C-E>\<C-E>\<C-E>" + call assert_equal([" 2 two "], ScreenLines(1, 10)) + + bwipe! + set nonumber +endfunc + +func Test_smoothscroll_CtrlE_CtrlY() + CheckScreendump + + let lines =<< trim END + vim9script + setline(1, [ + 'line one', + 'word '->repeat(20), + 'line three', + 'long word '->repeat(7), + 'line', + 'line', + 'line', + ]) + set smoothscroll + :5 + END + call writefile(lines, 'XSmoothScroll', 'D') + let buf = RunVimInTerminal('-S XSmoothScroll', #{rows: 12, cols: 40}) + + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_smoothscroll_1', {}) + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_smoothscroll_2', {}) + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_smoothscroll_3', {}) + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_smoothscroll_4', {}) + + call term_sendkeys(buf, "\<C-Y>") + call VerifyScreenDump(buf, 'Test_smoothscroll_5', {}) + call term_sendkeys(buf, "\<C-Y>") + call VerifyScreenDump(buf, 'Test_smoothscroll_6', {}) + call term_sendkeys(buf, "\<C-Y>") + call VerifyScreenDump(buf, 'Test_smoothscroll_7', {}) + call term_sendkeys(buf, "\<C-Y>") + call VerifyScreenDump(buf, 'Test_smoothscroll_8', {}) + + if has('folding') + call term_sendkeys(buf, ":set foldmethod=indent\<CR>") + " move the cursor so we can reuse the same dumps + call term_sendkeys(buf, "5G") + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_smoothscroll_1', {}) + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_smoothscroll_2', {}) + call term_sendkeys(buf, "7G") + call term_sendkeys(buf, "\<C-Y>") + call VerifyScreenDump(buf, 'Test_smoothscroll_7', {}) + call term_sendkeys(buf, "\<C-Y>") + call VerifyScreenDump(buf, 'Test_smoothscroll_8', {}) + endif + + call StopVimInTerminal(buf) +endfunc + +func Test_smoothscroll_number() + CheckScreendump + + let lines =<< trim END + vim9script + setline(1, [ + 'one ' .. 'word '->repeat(20), + 'two ' .. 'long word '->repeat(7), + 'line', + 'line', + 'line', + ]) + set smoothscroll + set splitkeep=topline + set number cpo+=n + :3 + + def g:DoRel() + set number relativenumber scrolloff=0 + :%del + setline(1, [ + 'one', + 'very long text '->repeat(12), + 'three', + ]) + exe "normal 2Gzt\<C-E>" + enddef + END + call writefile(lines, 'XSmoothNumber', 'D') + let buf = RunVimInTerminal('-S XSmoothNumber', #{rows: 12, cols: 40}) + + call VerifyScreenDump(buf, 'Test_smooth_number_1', {}) + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_smooth_number_2', {}) + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_smooth_number_3', {}) + + call term_sendkeys(buf, ":set cpo-=n\<CR>") + call VerifyScreenDump(buf, 'Test_smooth_number_4', {}) + call term_sendkeys(buf, "\<C-Y>") + call VerifyScreenDump(buf, 'Test_smooth_number_5', {}) + call term_sendkeys(buf, "\<C-Y>") + call VerifyScreenDump(buf, 'Test_smooth_number_6', {}) + + call term_sendkeys(buf, ":botright split\<CR>gg") + call VerifyScreenDump(buf, 'Test_smooth_number_7', {}) + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_smooth_number_8', {}) + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_smooth_number_9', {}) + call term_sendkeys(buf, ":close\<CR>") + + call term_sendkeys(buf, ":call DoRel()\<CR>") + call VerifyScreenDump(buf, 'Test_smooth_number_10', {}) + + call StopVimInTerminal(buf) +endfunc + +func Test_smoothscroll_list() + CheckScreendump + + let lines =<< trim END + vim9script + set smoothscroll scrolloff=0 + set list + setline(1, [ + 'one', + 'very long text '->repeat(12), + 'three', + ]) + exe "normal 2Gzt\<C-E>" + END + call writefile(lines, 'XSmoothList', 'D') + let buf = RunVimInTerminal('-S XSmoothList', #{rows: 8, cols: 40}) + + call VerifyScreenDump(buf, 'Test_smooth_list_1', {}) + + call term_sendkeys(buf, ":set listchars+=precedes:#\<CR>") + call VerifyScreenDump(buf, 'Test_smooth_list_2', {}) + + call StopVimInTerminal(buf) +endfunc + +func Test_smoothscroll_diff_mode() + CheckScreendump + + let lines =<< trim END + vim9script + var text = 'just some text here' + setline(1, text) + set smoothscroll + diffthis + new + setline(1, text) + set smoothscroll + diffthis + END + call writefile(lines, 'XSmoothDiff', 'D') + let buf = RunVimInTerminal('-S XSmoothDiff', #{rows: 8}) + + call VerifyScreenDump(buf, 'Test_smooth_diff_1', {}) + call term_sendkeys(buf, "\<C-Y>") + call VerifyScreenDump(buf, 'Test_smooth_diff_1', {}) + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_smooth_diff_1', {}) + + call StopVimInTerminal(buf) +endfunc + +func Test_smoothscroll_wrap_scrolloff_zero() + CheckScreendump + + let lines =<< trim END + vim9script + setline(1, ['Line' .. (' with some text'->repeat(7))]->repeat(7)) + set smoothscroll scrolloff=0 + :3 + END + call writefile(lines, 'XSmoothWrap', 'D') + let buf = RunVimInTerminal('-S XSmoothWrap', #{rows: 8, cols: 40}) + + call VerifyScreenDump(buf, 'Test_smooth_wrap_1', {}) + + " moving cursor down - whole bottom line shows + call term_sendkeys(buf, "j") + call VerifyScreenDump(buf, 'Test_smooth_wrap_2', {}) + + call term_sendkeys(buf, "\<C-E>j") + call VerifyScreenDump(buf, 'Test_smooth_wrap_3', {}) + + call term_sendkeys(buf, "G") + call VerifyScreenDump(buf, 'Test_smooth_wrap_4', {}) + + " moving cursor up right after the >>> marker - no need to show whole line + call term_sendkeys(buf, "2gj3l2k") + call VerifyScreenDump(buf, 'Test_smooth_wrap_5', {}) + + " moving cursor up where the >>> marker is - whole top line shows + call term_sendkeys(buf, "2j02k") + call VerifyScreenDump(buf, 'Test_smooth_wrap_6', {}) + + call StopVimInTerminal(buf) +endfunc + +func Test_smoothscroll_wrap_long_line() + CheckScreendump + + let lines =<< trim END + vim9script + setline(1, ['one', 'two', 'Line' .. (' with lots of text'->repeat(30)) .. ' end', 'four']) + set smoothscroll scrolloff=0 + normal 3G10|zt + END + call writefile(lines, 'XSmoothWrap', 'D') + let buf = RunVimInTerminal('-S XSmoothWrap', #{rows: 6, cols: 40}) + call VerifyScreenDump(buf, 'Test_smooth_long_1', {}) + + " scrolling up, cursor moves screen line down + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_smooth_long_2', {}) + call term_sendkeys(buf, "5\<C-E>") + call VerifyScreenDump(buf, 'Test_smooth_long_3', {}) + + " scrolling down, cursor moves screen line up + call term_sendkeys(buf, "5\<C-Y>") + call VerifyScreenDump(buf, 'Test_smooth_long_4', {}) + call term_sendkeys(buf, "\<C-Y>") + call VerifyScreenDump(buf, 'Test_smooth_long_5', {}) + + " 'scrolloff' set to 1, scrolling up, cursor moves screen line down + call term_sendkeys(buf, ":set scrolloff=1\<CR>") + call term_sendkeys(buf, "10|\<C-E>") + call VerifyScreenDump(buf, 'Test_smooth_long_6', {}) + + " 'scrolloff' set to 1, scrolling down, cursor moves screen line up + call term_sendkeys(buf, "\<C-E>") + call term_sendkeys(buf, "gjgj") + call term_sendkeys(buf, "\<C-Y>") + call VerifyScreenDump(buf, 'Test_smooth_long_7', {}) + + " 'scrolloff' set to 2, scrolling up, cursor moves screen line down + call term_sendkeys(buf, ":set scrolloff=2\<CR>") + call term_sendkeys(buf, "10|\<C-E>") + call VerifyScreenDump(buf, 'Test_smooth_long_8', {}) + + " 'scrolloff' set to 2, scrolling down, cursor moves screen line up + call term_sendkeys(buf, "\<C-E>") + call term_sendkeys(buf, "gj") + call term_sendkeys(buf, "\<C-Y>") + call VerifyScreenDump(buf, 'Test_smooth_long_9', {}) + + " 'scrolloff' set to 0, move cursor down one line. + " Cursor should move properly, and since this is a really long line, it will + " be put on top of the screen. + call term_sendkeys(buf, ":set scrolloff=0\<CR>") + call term_sendkeys(buf, "0j") + call VerifyScreenDump(buf, 'Test_smooth_long_10', {}) + + " Test zt/zz/zb that they work properly when a long line is above it + call term_sendkeys(buf, "zb") + call VerifyScreenDump(buf, 'Test_smooth_long_11', {}) + call term_sendkeys(buf, "zz") + call VerifyScreenDump(buf, 'Test_smooth_long_12', {}) + call term_sendkeys(buf, "zt") + call VerifyScreenDump(buf, 'Test_smooth_long_13', {}) + + " Repeat the step and move the cursor down again. + " This time, use a shorter long line that is barely long enough to span more + " than one window. Note that the cursor is at the bottom this time because + " Vim prefers to do so if we are scrolling a few lines only. + call term_sendkeys(buf, ":call setline(1, ['one', 'two', 'Line' .. (' with lots of text'->repeat(10)) .. ' end', 'four'])\<CR>") + call term_sendkeys(buf, "3Gzt") + call term_sendkeys(buf, "j") + call VerifyScreenDump(buf, 'Test_smooth_long_14', {}) + + " Repeat the step but this time start it when the line is smooth-scrolled by + " one line. This tests that the offset calculation is still correct and + " still end up scrolling down to the next line with cursor at bottom of + " screen. + call term_sendkeys(buf, "3Gzt") + call term_sendkeys(buf, "\<C-E>j") + call VerifyScreenDump(buf, 'Test_smooth_long_15', {}) + + call StopVimInTerminal(buf) +endfunc + +func Test_smoothscroll_one_long_line() + CheckScreendump + + let lines =<< trim END + vim9script + setline(1, 'with lots of text '->repeat(7)) + set smoothscroll scrolloff=0 + END + call writefile(lines, 'XSmoothOneLong', 'D') + let buf = RunVimInTerminal('-S XSmoothOneLong', #{rows: 6, cols: 40}) + call VerifyScreenDump(buf, 'Test_smooth_one_long_1', {}) + + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_smooth_one_long_2', {}) + + call term_sendkeys(buf, "0") + call VerifyScreenDump(buf, 'Test_smooth_one_long_1', {}) + + call StopVimInTerminal(buf) +endfunc + +func Test_smoothscroll_long_line_showbreak() + CheckScreendump + + let lines =<< trim END + vim9script + # a line that spans four screen lines + setline(1, 'with lots of text in one line '->repeat(6)) + set smoothscroll scrolloff=0 showbreak=+++\ + END + call writefile(lines, 'XSmoothLongShowbreak', 'D') + let buf = RunVimInTerminal('-S XSmoothLongShowbreak', #{rows: 6, cols: 40}) + call VerifyScreenDump(buf, 'Test_smooth_long_showbreak_1', {}) + + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_smooth_long_showbreak_2', {}) + + call term_sendkeys(buf, "0") + call VerifyScreenDump(buf, 'Test_smooth_long_showbreak_1', {}) + + call StopVimInTerminal(buf) +endfunc + +func s:check_col_calc(win_col, win_line, buf_col) + call assert_equal(a:win_col, wincol()) + call assert_equal(a:win_line, winline()) + call assert_equal(a:buf_col, col('.')) +endfunc + +" Test that if the current cursor is on a smooth scrolled line, we correctly +" reposition it. Also check that we don't miscalculate the values by checking +" the consistency between wincol() and col('.') as they are calculated +" separately in code. +func Test_smoothscroll_cursor_position() + call NewWindow(10, 20) + setl smoothscroll wrap + call setline(1, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + + call s:check_col_calc(1, 1, 1) + exe "normal \<C-E>" + + " Move down another line to avoid blocking the <<< display + call s:check_col_calc(1, 2, 41) + exe "normal \<C-Y>" + call s:check_col_calc(1, 3, 41) + + normal gg3l + exe "normal \<C-E>" + + " Move down only 1 line when we are out of the range of the <<< display + call s:check_col_calc(4, 1, 24) + exe "normal \<C-Y>" + call s:check_col_calc(4, 2, 24) + normal ggg$ + exe "normal \<C-E>" + call s:check_col_calc(20, 1, 40) + exe "normal \<C-Y>" + call s:check_col_calc(20, 2, 40) + normal gg + + " Test number, where we have indented lines + setl number + call s:check_col_calc(5, 1, 1) + exe "normal \<C-E>" + + " Move down only 1 line when the <<< display is on the number column + call s:check_col_calc(5, 1, 17) + exe "normal \<C-Y>" + call s:check_col_calc(5, 2, 17) + normal ggg$ + exe "normal \<C-E>" + call s:check_col_calc(20, 1, 32) + exe "normal \<C-Y>" + call s:check_col_calc(20, 2, 32) + normal gg + + setl numberwidth=1 + + " Move down another line when numberwidth is too short to cover the whole + " <<< display + call s:check_col_calc(3, 1, 1) + exe "normal \<C-E>" + call s:check_col_calc(3, 2, 37) + exe "normal \<C-Y>" + call s:check_col_calc(3, 3, 37) + normal ggl + + " Only move 1 line down when we are just past the <<< display + call s:check_col_calc(4, 1, 2) + exe "normal \<C-E>" + call s:check_col_calc(4, 1, 20) + exe "normal \<C-Y>" + call s:check_col_calc(4, 2, 20) + normal gg + setl numberwidth& + + " Test number + showbreak, so test that the additional indentation works + setl number showbreak=+++ + call s:check_col_calc(5, 1, 1) + exe "normal \<C-E>" + call s:check_col_calc(8, 1, 17) + exe "normal \<C-Y>" + call s:check_col_calc(8, 2, 17) + normal gg + + " Test number + cpo+=n mode, where wrapped lines aren't indented + setl number cpo+=n showbreak= + call s:check_col_calc(5, 1, 1) + exe "normal \<C-E>" + call s:check_col_calc(1, 2, 37) + exe "normal \<C-Y>" + call s:check_col_calc(1, 3, 37) + normal gg + + bwipe! +endfunc + +func Test_smoothscroll_cursor_scrolloff() + call NewWindow(10, 20) + setl smoothscroll wrap + setl scrolloff=3 + + " 120 chars are 6 screen lines + call setline(1, "abcdefghijklmnopqrstABCDEFGHIJKLMNOPQRSTabcdefghijklmnopqrstABCDEFGHIJKLMNOPQRSTabcdefghijklmnopqrstABCDEFGHIJKLMNOPQRST") + call setline(2, "below") + + call s:check_col_calc(1, 1, 1) + + " CTRL-E shows "<<<DEFG...", cursor move four lines down + exe "normal \<C-E>" + call s:check_col_calc(1, 4, 81) + + " cursor on start of second line, "gk" moves into first line, skipcol doesn't + " change + exe "normal G0gk" + call s:check_col_calc(1, 5, 101) + + " move cursor left one window width worth, scrolls one screen line + exe "normal 20h" + call s:check_col_calc(1, 5, 81) + + " move cursor left one window width worth, scrolls one screen line + exe "normal 20h" + call s:check_col_calc(1, 4, 61) + + " cursor on last line, "gk" should not cause a scroll + set scrolloff=0 + normal G0 + call s:check_col_calc(1, 7, 1) + normal gk + call s:check_col_calc(1, 6, 101) + + bwipe! +endfunc + + +" Test that mouse picking is still accurate when we have smooth scrolled lines +func Test_smoothscroll_mouse_pos() + CheckNotGui + CheckUnix + + let save_mouse = &mouse + "let save_term = &term + "let save_ttymouse = &ttymouse + set mouse=a "term=xterm ttymouse=xterm2 + + call NewWindow(10, 20) + setl smoothscroll wrap + " First line will wrap to 3 physical lines. 2nd/3rd lines are short lines. + call setline(1, ["abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "line 2", "line 3"]) + + func s:check_mouse_click(row, col, buf_row, buf_col) + call MouseLeftClick(a:row, a:col) + + call assert_equal(a:col, wincol()) + call assert_equal(a:row, winline()) + call assert_equal(a:buf_row, line('.')) + call assert_equal(a:buf_col, col('.')) + endfunc + + " Check that clicking without scroll works first. + call s:check_mouse_click(3, 5, 1, 45) + call s:check_mouse_click(4, 1, 2, 1) + call s:check_mouse_click(4, 6, 2, 6) + call s:check_mouse_click(5, 1, 3, 1) + call s:check_mouse_click(5, 6, 3, 6) + + " Smooth scroll, and checks that this didn't mess up mouse clicking + exe "normal \<C-E>" + call s:check_mouse_click(2, 5, 1, 45) + call s:check_mouse_click(3, 1, 2, 1) + call s:check_mouse_click(3, 6, 2, 6) + call s:check_mouse_click(4, 1, 3, 1) + call s:check_mouse_click(4, 6, 3, 6) + + exe "normal \<C-E>" + call s:check_mouse_click(1, 5, 1, 45) + call s:check_mouse_click(2, 1, 2, 1) + call s:check_mouse_click(2, 6, 2, 6) + call s:check_mouse_click(3, 1, 3, 1) + call s:check_mouse_click(3, 6, 3, 6) + + " Make a new first line 11 physical lines tall so it's taller than window + " height, to test overflow calculations with really long lines wrapping. + normal gg + call setline(1, "12345678901234567890"->repeat(11)) + exe "normal 6\<C-E>" + call s:check_mouse_click(5, 1, 1, 201) + call s:check_mouse_click(6, 1, 2, 1) + call s:check_mouse_click(7, 1, 3, 1) + + let &mouse = save_mouse + "let &term = save_term + "let &ttymouse = save_ttymouse +endfunc + +" this was dividing by zero +func Test_smoothscrol_zero_width() + CheckScreendump + + let lines =<< trim END + winsize 0 0 + vsplit + vsplit + vsplit + vsplit + vsplit + sil norm H + set wrap + set smoothscroll + set number + END + call writefile(lines, 'XSmoothScrollZero', 'D') + let buf = RunVimInTerminal('-u NONE -i NONE -n -m -X -Z -e -s -S XSmoothScrollZero', #{rows: 6, cols: 60, wait_for_ruler: 0}) + call TermWait(buf, 3000) + call VerifyScreenDump(buf, 'Test_smoothscroll_zero_1', {}) + + call term_sendkeys(buf, ":sil norm \<C-V>\<C-W>\<C-V>\<C-N>\<CR>") + call VerifyScreenDump(buf, 'Test_smoothscroll_zero_2', {}) + + call StopVimInTerminal(buf) +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_search.vim b/test/old/testdir/test_search.vim index 885043accf..37909d0afe 100644 --- a/test/old/testdir/test_search.vim +++ b/test/old/testdir/test_search.vim @@ -377,9 +377,9 @@ func Test_searchpairpos() endfunc func Test_searchpair_errors() - call assert_fails("call searchpair([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: using List as a String') - call assert_fails("call searchpair('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String') - call assert_fails("call searchpair('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String') + call assert_fails("call searchpair([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: Using a List as a String') + call assert_fails("call searchpair('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: Using a Funcref as a String') + call assert_fails("call searchpair('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: Using a Dictionary as a String') call assert_fails("call searchpair('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags') call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99') call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100') @@ -388,9 +388,9 @@ func Test_searchpair_errors() endfunc func Test_searchpairpos_errors() - call assert_fails("call searchpairpos([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: using List as a String') - call assert_fails("call searchpairpos('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String') - call assert_fails("call searchpairpos('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String') + call assert_fails("call searchpairpos([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: Using a List as a String') + call assert_fails("call searchpairpos('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: Using a Funcref as a String') + call assert_fails("call searchpairpos('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: Using a Dictionary as a String') call assert_fails("call searchpairpos('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags') call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99') call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100') diff --git a/test/old/testdir/test_selectmode.vim b/test/old/testdir/test_selectmode.vim index d1afd5204f..dd341d99d1 100644 --- a/test/old/testdir/test_selectmode.vim +++ b/test/old/testdir/test_selectmode.vim @@ -166,8 +166,11 @@ func Test_term_mouse_multiple_clicks_to_select_mode() let save_term = &term " let save_ttymouse = &ttymouse " call test_override('no_query_mouse', 1) - " set mouse=a term=xterm mousetime=200 - set mouse=a mousetime=200 + + " 'mousetime' must be sufficiently large, or else the test is flaky when + " using a ssh connection with X forwarding; i.e. ssh -X. + " set mouse=a term=xterm mousetime=1000 + set mouse=a mousetime=1000 set selectmode=mouse new diff --git a/test/old/testdir/test_spellfile.vim b/test/old/testdir/test_spellfile.vim index dbffbafed9..4d2a6cf35f 100644 --- a/test/old/testdir/test_spellfile.vim +++ b/test/old/testdir/test_spellfile.vim @@ -1063,4 +1063,24 @@ func Test_mkspellmem_opt() call assert_fails('set mkspellmem=1000,50,0', 'E474:') endfunc +" 'spellfile' accepts '@' on top of 'isfname'. +func Test_spellfile_allow_at_character() + call mkdir('Xtest/the foo@bar,dir', 'p') + let &spellfile = './Xtest/the foo@bar,dir/Xspellfile.add' + let &spellfile = '' + call delete('Xtest', 'rf') +endfunc + +" this was using a NULL pointer +func Test_mkspell_empty_dic() + call writefile(['1'], 'XtestEmpty.dic') + call writefile(['SOFOFROM abcd', 'SOFOTO ABCD', 'SAL CIA X'], 'XtestEmpty.aff') + mkspell! XtestEmpty.spl XtestEmpty + + call delete('XtestEmpty.dic') + call delete('XtestEmpty.aff') + call delete('XtestEmpty.spl') +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_termcodes.vim b/test/old/testdir/test_termcodes.vim index b9b207191e..a00ea02286 100644 --- a/test/old/testdir/test_termcodes.vim +++ b/test/old/testdir/test_termcodes.vim @@ -680,8 +680,11 @@ func Test_term_mouse_multiple_clicks_to_visually_select() let save_term = &term " let save_ttymouse = &ttymouse " call test_override('no_query_mouse', 1) - " set mouse=a term=xterm mousetime=200 - set mouse=a mousetime=200 + + " 'mousetime' must be sufficiently large, or else the test is flaky when + " using a ssh connection with X forwarding; i.e. ssh -X (issue #7563). + " set mouse=a term=xterm mousetime=600 + set mouse=a mousetime=600 new for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec diff --git a/test/old/testdir/test_trycatch.vim b/test/old/testdir/test_trycatch.vim index ef20e03126..d60b793f1b 100644 --- a/test/old/testdir/test_trycatch.vim +++ b/test/old/testdir/test_trycatch.vim @@ -38,7 +38,7 @@ func T25_F() if loops == 2 try Xpath 'f' . loops - finally + final Xpath 'g' . loops endtry endif @@ -50,19 +50,20 @@ func T25_F() Xpath 'i' endfunc +" Also try using "fina" and "final" and "finall" as abbraviations. func T25_G() if 1 try Xpath 'A' call T25_F() Xpath 'B' - finally + fina Xpath 'C' endtry else try Xpath 'D' - finally + finall Xpath 'E' endtry endif @@ -2220,6 +2221,61 @@ func Test_BufEnter_exception() %bwipe! endfunc +" Test for using try/catch when lines are joined by "|" or "\n" {{{1 +func Test_try_catch_nextcmd() + func Throw() + throw "Failure" + endfunc + + let lines =<< trim END + try + let s:x = Throw() + catch + let g:caught = 1 + endtry + END + + let g:caught = 0 + call execute(lines) + call assert_equal(1, g:caught) + + let g:caught = 0 + call execute(join(lines, '|')) + call assert_equal(1, g:caught) + + let g:caught = 0 + call execute(join(lines, "\n")) + call assert_equal(1, g:caught) + + unlet g:caught + delfunc Throw +endfunc + +" Test for using try/catch in a user command with a failing expression {{{1 +func Test_user_command_try_catch() + let lines =<< trim END + function s:throw() abort + throw 'error' + endfunction + + command! Execute + \ try + \ | let s:x = s:throw() + \ | catch + \ | let g:caught = 'caught' + \ | endtry + + let g:caught = 'no' + Execute + call assert_equal('caught', g:caught) + END + call writefile(lines, 'XtestTryCatch') + source XtestTryCatch + + call delete('XtestTryCatch') + unlet g:caught +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/test/old/testdir/test_vimscript.vim b/test/old/testdir/test_vimscript.vim index 68ce493927..c8085cc396 100644 --- a/test/old/testdir/test_vimscript.vim +++ b/test/old/testdir/test_vimscript.vim @@ -3087,7 +3087,7 @@ func Test_nested_if_else_errors() endif END call writefile(code, 'Xtest') - call AssertException(['source Xtest'], 'Vim(else):E583: multiple :else') + call AssertException(['source Xtest'], 'Vim(else):E583: Multiple :else') " :elseif after :else let code =<< trim END @@ -7035,6 +7035,122 @@ func Test_unlet_env() call assert_equal('', $TESTVAR) endfunc +func Test_refcount() + throw 'Skipped: Nvim does not support test_refcount()' + " Immediate values + call assert_equal(-1, test_refcount(1)) + call assert_equal(-1, test_refcount('s')) + call assert_equal(-1, test_refcount(v:true)) + call assert_equal(0, test_refcount([])) + call assert_equal(0, test_refcount({})) + call assert_equal(0, test_refcount(0zff)) + call assert_equal(0, test_refcount({-> line('.')})) + call assert_equal(-1, test_refcount(0.1)) + if has('job') + call assert_equal(0, test_refcount(job_start([&shell, &shellcmdflag, 'echo .']))) + endif + + " No refcount types + let x = 1 + call assert_equal(-1, test_refcount(x)) + let x = 's' + call assert_equal(-1, test_refcount(x)) + let x = v:true + call assert_equal(-1, test_refcount(x)) + let x = 0.1 + call assert_equal(-1, test_refcount(x)) + + " Check refcount + let x = [] + call assert_equal(1, test_refcount(x)) + + let x = {} + call assert_equal(1, x->test_refcount()) + + let x = 0zff + call assert_equal(1, test_refcount(x)) + + let X = {-> line('.')} + call assert_equal(1, test_refcount(X)) + let Y = X + call assert_equal(2, test_refcount(X)) + + if has('job') + let job = job_start([&shell, &shellcmdflag, 'echo .']) + call assert_equal(1, test_refcount(job)) + call assert_equal(1, test_refcount(job_getchannel(job))) + call assert_equal(1, test_refcount(job)) + endif + + " Function arguments, copying and unassigning + func ExprCheck(x, i) + let i = a:i + 1 + call assert_equal(i, test_refcount(a:x)) + let Y = a:x + call assert_equal(i + 1, test_refcount(a:x)) + call assert_equal(test_refcount(a:x), test_refcount(Y)) + let Y = 0 + call assert_equal(i, test_refcount(a:x)) + endfunc + call ExprCheck([], 0) + call ExprCheck({}, 0) + call ExprCheck(0zff, 0) + call ExprCheck({-> line('.')}, 0) + if has('job') + call ExprCheck(job, 1) + call ExprCheck(job_getchannel(job), 1) + call job_stop(job) + endif + delfunc ExprCheck + + " Regarding function + func Func(x) abort + call assert_equal(2, test_refcount(function('Func'))) + call assert_equal(0, test_refcount(funcref('Func'))) + endfunc + call assert_equal(1, test_refcount(function('Func'))) + call assert_equal(0, test_refcount(function('Func', [1]))) + call assert_equal(0, test_refcount(funcref('Func'))) + call assert_equal(0, test_refcount(funcref('Func', [1]))) + let X = function('Func') + let Y = X + call assert_equal(1, test_refcount(X)) + let X = function('Func', [1]) + let Y = X + call assert_equal(2, test_refcount(X)) + let X = funcref('Func') + let Y = X + call assert_equal(2, test_refcount(X)) + let X = funcref('Func', [1]) + let Y = X + call assert_equal(2, test_refcount(X)) + unlet X + unlet Y + call Func(1) + delfunc Func + + " Function with dict + func DictFunc() dict + call assert_equal(3, test_refcount(self)) + endfunc + let d = {'Func': function('DictFunc')} + call assert_equal(1, test_refcount(d)) + call assert_equal(0, test_refcount(d.Func)) + call d.Func() + unlet d + delfunc DictFunc + + if has('channel') + call assert_equal(-1, test_refcount(test_null_job())) + call assert_equal(-1, test_refcount(test_null_channel())) + endif + call assert_equal(-1, test_refcount(test_null_function())) + call assert_equal(-1, test_refcount(test_null_partial())) + call assert_equal(-1, test_refcount(test_null_blob())) + call assert_equal(-1, test_refcount(test_null_list())) + call assert_equal(-1, test_refcount(test_null_dict())) +endfunc + " Test for missing :endif, :endfor, :endwhile and :endtry {{{1 func Test_missing_end() call writefile(['if 2 > 1', 'echo ">"'], 'Xscript') diff --git a/test/old/testdir/test_visual.vim b/test/old/testdir/test_visual.vim index d10a946200..04282416bb 100644 --- a/test/old/testdir/test_visual.vim +++ b/test/old/testdir/test_visual.vim @@ -935,7 +935,7 @@ func Test_visual_block_mode() endfunc func Test_visual_force_motion_feedkeys() - onoremap <expr> i- execute('let g:mode = mode(1)') + onoremap <expr> i- execute('let g:mode = mode(1)')->slice(0, 0) call feedkeys('dvi-', 'x') call assert_equal('nov', g:mode) call feedkeys('di-', 'x') diff --git a/test/old/testdir/test_window_cmd.vim b/test/old/testdir/test_window_cmd.vim index f938203736..f18d1719c0 100644 --- a/test/old/testdir/test_window_cmd.vim +++ b/test/old/testdir/test_window_cmd.vim @@ -1734,7 +1734,7 @@ func Test_splitkeep_options() " let &t_WS = save_WS endfunc -function Test_splitkeep_cmdwin_cursor_position() +func Test_splitkeep_cmdwin_cursor_position() set splitkeep=screen call setline(1, range(&lines)) @@ -1759,9 +1759,9 @@ function Test_splitkeep_cmdwin_cursor_position() %bwipeout! set splitkeep& -endfunction +endfunc -function Test_splitkeep_misc() +func Test_splitkeep_misc() set splitkeep=screen set splitbelow @@ -1794,7 +1794,7 @@ function Test_splitkeep_misc() set splitkeep& endfunc -function Test_splitkeep_callback() +func Test_splitkeep_callback() CheckScreendump let lines =<< trim END set splitkeep=screen @@ -1827,7 +1827,7 @@ function Test_splitkeep_callback() call StopVimInTerminal(buf) endfunc -function Test_splitkeep_fold() +func Test_splitkeep_fold() CheckScreendump let lines =<< trim END @@ -1857,9 +1857,9 @@ function Test_splitkeep_fold() call VerifyScreenDump(buf, 'Test_splitkeep_fold_4', {}) call StopVimInTerminal(buf) -endfunction +endfunc -function Test_splitkeep_status() +func Test_splitkeep_status() CheckScreendump let lines =<< trim END @@ -1877,9 +1877,9 @@ function Test_splitkeep_status() call VerifyScreenDump(buf, 'Test_splitkeep_status_1', {}) call StopVimInTerminal(buf) -endfunction +endfunc -function Test_new_help_window_on_error() +func Test_new_help_window_on_error() help change.txt execute "normal! /CTRL-@\<CR>" silent! execute "normal! \<C-W>]" @@ -1889,7 +1889,26 @@ function Test_new_help_window_on_error() call assert_equal(wincount, winnr('$')) call assert_equal(expand("<cword>"), "'mod'") -endfunction +endfunc + +func Test_smoothscroll_in_zero_width_window() + let save_lines = &lines + let save_columns = &columns + + winsize 0 24 + set cpo+=n + exe "noremap 0 \<C-W>n\<C-W>L" + norm 000000 + set number smoothscroll + exe "norm \<C-Y>" + + only! + let &lines = save_lines + let &columns = save_columns + set cpo-=n + unmap 0 + set nonumber nosmoothscroll +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index ecee773ba8..cde5a731cf 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -1461,18 +1461,18 @@ describe('typval.c', function() itp('fails with error message when index is out of range', function() local l = list(int(1), int(2), int(3), int(4), int(5)) - eq(nil, tv_list_find_str(l, -6, 'E684: list index out of range: -6')) - eq(nil, tv_list_find_str(l, 5, 'E684: list index out of range: 5')) + eq(nil, tv_list_find_str(l, -6, 'E684: List index out of range: -6')) + eq(nil, tv_list_find_str(l, 5, 'E684: List index out of range: 5')) end) itp('fails with error message on invalid types', function() local l = list(1, empty_list, {}) - eq('', tv_list_find_str(l, 0, 'E806: using Float as a String')) - eq('', tv_list_find_str(l, 1, 'E730: using List as a String')) - eq('', tv_list_find_str(l, 2, 'E731: using Dictionary as a String')) - eq('', tv_list_find_str(l, -1, 'E731: using Dictionary as a String')) - eq('', tv_list_find_str(l, -2, 'E730: using List as a String')) - eq('', tv_list_find_str(l, -3, 'E806: using Float as a String')) + eq('', tv_list_find_str(l, 0, 'E806: Using a Float as a String')) + eq('', tv_list_find_str(l, 1, 'E730: Using a List as a String')) + eq('', tv_list_find_str(l, 2, 'E731: Using a Dictionary as a String')) + eq('', tv_list_find_str(l, -1, 'E731: Using a Dictionary as a String')) + eq('', tv_list_find_str(l, -2, 'E730: Using a List as a String')) + eq('', tv_list_find_str(l, -3, 'E806: Using a Float as a String')) end) end) end) @@ -1745,7 +1745,7 @@ describe('typval.c', function() itp('works', function() local d = ffi.gc(dict({test={}}), nil) eq('', ffi.string(check_emsg(function() return lib.tv_dict_get_string(d, 'test', false) end, - 'E731: using Dictionary as a String'))) + 'E731: Using a Dictionary as a String'))) d = ffi.gc(dict({tes=int(42), t=44, te='43', xx=int(45)}), nil) alloc_log:clear() local dis = dict_items(d) @@ -1766,7 +1766,7 @@ describe('typval.c', function() eq(s43, dis.te.di_tv.vval.v_string) alloc_log:check({}) eq('', ffi.string(check_emsg(function() return lib.tv_dict_get_string(d, 't', false) end, - 'E806: using Float as a String'))) + 'E806: Using a Float as a String'))) end) itp('allocates a string copy when requested', function() local function tv_dict_get_string_alloc(d, key, emsg) @@ -1785,14 +1785,14 @@ describe('typval.c', function() return s_ret end local d = ffi.gc(dict({test={}}), nil) - eq('', tv_dict_get_string_alloc(d, 'test', 'E731: using Dictionary as a String')) + eq('', tv_dict_get_string_alloc(d, 'test', 'E731: Using a Dictionary as a String')) d = ffi.gc(dict({tes=int(42), t=44, te='43', xx=int(45)}), nil) alloc_log:clear() eq(nil, tv_dict_get_string_alloc(d, 'test')) eq('42', tv_dict_get_string_alloc(d, 'tes')) eq('45', tv_dict_get_string_alloc(d, 'xx')) eq('43', tv_dict_get_string_alloc(d, 'te')) - eq('', tv_dict_get_string_alloc(d, 't', 'E806: using Float as a String')) + eq('', tv_dict_get_string_alloc(d, 't', 'E806: Using a Float as a String')) end) end) describe('get_string_buf()', function() @@ -1827,7 +1827,7 @@ describe('typval.c', function() s, r, b = tv_dict_get_string_buf(d, 'test') neq(r, b) eq('tset', s) - s, r, b = tv_dict_get_string_buf(d, 't', nil, 'E806: using Float as a String') + s, r, b = tv_dict_get_string_buf(d, 't', nil, 'E806: Using a Float as a String') neq(r, b) eq('', s) s, r, b = tv_dict_get_string_buf(d, 'te') @@ -1870,7 +1870,7 @@ describe('typval.c', function() neq(r, b) neq(r, def) eq('tset', s) - s, r, b, def = tv_dict_get_string_buf_chk(d, 'test', 1, nil, nil, 'E806: using Float as a String') + s, r, b, def = tv_dict_get_string_buf_chk(d, 'test', 1, nil, nil, 'E806: Using a Float as a String') neq(r, b) neq(r, def) eq(nil, s) @@ -2831,14 +2831,14 @@ describe('typval.c', function() alloc_log:clear() for _, v in ipairs({ {lib.VAR_NUMBER, nil}, - {lib.VAR_FLOAT, 'E806: using Float as a String'}, - {lib.VAR_PARTIAL, 'E729: using Funcref as a String'}, - {lib.VAR_FUNC, 'E729: using Funcref as a String'}, - {lib.VAR_LIST, 'E730: using List as a String'}, - {lib.VAR_DICT, 'E731: using Dictionary as a String'}, + {lib.VAR_FLOAT, 'E806: Using a Float as a String'}, + {lib.VAR_PARTIAL, 'E729: Using a Funcref as a String'}, + {lib.VAR_FUNC, 'E729: Using a Funcref as a String'}, + {lib.VAR_LIST, 'E730: Using a List as a String'}, + {lib.VAR_DICT, 'E731: Using a Dictionary as a String'}, {lib.VAR_BOOL, nil}, {lib.VAR_SPECIAL, nil}, - {lib.VAR_UNKNOWN, 'E908: using an invalid value as a String'}, + {lib.VAR_UNKNOWN, 'E908: Using an invalid value as a String'}, }) do local typ = v[1] local emsg = v[2] @@ -2986,15 +2986,15 @@ describe('typval.c', function() for _, v in ipairs({ {lib.VAR_NUMBER, {v_number=42}, nil, '42'}, {lib.VAR_STRING, {v_string=to_cstr('100500')}, nil, '100500'}, - {lib.VAR_FLOAT, {v_float=42.53}, 'E806: using Float as a String', ''}, - {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: using Funcref as a String', ''}, - {lib.VAR_FUNC, {v_string=NULL}, 'E729: using Funcref as a String', ''}, - {lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', ''}, - {lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', ''}, + {lib.VAR_FLOAT, {v_float=42.53}, 'E806: Using a Float as a String', ''}, + {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: Using a Funcref as a String', ''}, + {lib.VAR_FUNC, {v_string=NULL}, 'E729: Using a Funcref as a String', ''}, + {lib.VAR_LIST, {v_list=NULL}, 'E730: Using a List as a String', ''}, + {lib.VAR_DICT, {v_dict=NULL}, 'E731: Using a Dictionary as a String', ''}, {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'v:null'}, {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'v:true'}, {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'v:false'}, - {lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', ''}, + {lib.VAR_UNKNOWN, nil, 'E908: Using an invalid value as a String', ''}, }) do -- Using to_cstr in place of Neovim allocated string, cannot -- tv_clear() that. @@ -3030,15 +3030,15 @@ describe('typval.c', function() for _, v in ipairs({ {lib.VAR_NUMBER, {v_number=42}, nil, '42'}, {lib.VAR_STRING, {v_string=to_cstr('100500')}, nil, '100500'}, - {lib.VAR_FLOAT, {v_float=42.53}, 'E806: using Float as a String', nil}, - {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: using Funcref as a String', nil}, - {lib.VAR_FUNC, {v_string=NULL}, 'E729: using Funcref as a String', nil}, - {lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', nil}, - {lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', nil}, + {lib.VAR_FLOAT, {v_float=42.53}, 'E806: Using a Float as a String', nil}, + {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: Using a Funcref as a String', nil}, + {lib.VAR_FUNC, {v_string=NULL}, 'E729: Using a Funcref as a String', nil}, + {lib.VAR_LIST, {v_list=NULL}, 'E730: Using a List as a String', nil}, + {lib.VAR_DICT, {v_dict=NULL}, 'E731: Using a Dictionary as a String', nil}, {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'v:null'}, {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'v:true'}, {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'v:false'}, - {lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', nil}, + {lib.VAR_UNKNOWN, nil, 'E908: Using an invalid value as a String', nil}, }) do -- Using to_cstr, cannot free with tv_clear local tv = ffi.gc(typvalt(v[1], v[2]), nil) @@ -3072,15 +3072,15 @@ describe('typval.c', function() for _, v in ipairs({ {lib.VAR_NUMBER, {v_number=42}, nil, '42'}, {lib.VAR_STRING, {v_string=to_cstr('100500')}, nil, '100500'}, - {lib.VAR_FLOAT, {v_float=42.53}, 'E806: using Float as a String', ''}, - {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: using Funcref as a String', ''}, - {lib.VAR_FUNC, {v_string=NULL}, 'E729: using Funcref as a String', ''}, - {lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', ''}, - {lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', ''}, + {lib.VAR_FLOAT, {v_float=42.53}, 'E806: Using a Float as a String', ''}, + {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: Using a Funcref as a String', ''}, + {lib.VAR_FUNC, {v_string=NULL}, 'E729: Using a Funcref as a String', ''}, + {lib.VAR_LIST, {v_list=NULL}, 'E730: Using a List as a String', ''}, + {lib.VAR_DICT, {v_dict=NULL}, 'E731: Using a Dictionary as a String', ''}, {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'v:null'}, {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'v:true'}, {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'v:false'}, - {lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', ''}, + {lib.VAR_UNKNOWN, nil, 'E908: Using an invalid value as a String', ''}, }) do -- Using to_cstr, cannot free with tv_clear local tv = ffi.gc(typvalt(v[1], v[2]), nil) @@ -3115,15 +3115,15 @@ describe('typval.c', function() for _, v in ipairs({ {lib.VAR_NUMBER, {v_number=42}, nil, '42'}, {lib.VAR_STRING, {v_string=to_cstr('100500')}, nil, '100500'}, - {lib.VAR_FLOAT, {v_float=42.53}, 'E806: using Float as a String', nil}, - {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: using Funcref as a String', nil}, - {lib.VAR_FUNC, {v_string=NULL}, 'E729: using Funcref as a String', nil}, - {lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', nil}, - {lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', nil}, + {lib.VAR_FLOAT, {v_float=42.53}, 'E806: Using a Float as a String', nil}, + {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: Using a Funcref as a String', nil}, + {lib.VAR_FUNC, {v_string=NULL}, 'E729: Using a Funcref as a String', nil}, + {lib.VAR_LIST, {v_list=NULL}, 'E730: Using a List as a String', nil}, + {lib.VAR_DICT, {v_dict=NULL}, 'E731: Using a Dictionary as a String', nil}, {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'v:null'}, {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'v:true'}, {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'v:false'}, - {lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', nil}, + {lib.VAR_UNKNOWN, nil, 'E908: Using an invalid value as a String', nil}, }) do -- Using to_cstr, cannot free with tv_clear local tv = ffi.gc(typvalt(v[1], v[2]), nil) |