From a9d01da6616a702ec093b0abc75e097368889524 Mon Sep 17 00:00:00 2001 From: Hirohito Higashi Date: Sat, 4 Apr 2026 08:27:46 +0000 Subject: [PATCH 01/29] patch 9.2.0292: E340 internal error when using method call on void value Problem: E340 internal error when using method call on void value (Peter Kenny) Solution: Check for void value (Hirohito Higashi) Using a method call on a void return value (e.g. "echo F()->empty()" where F() returns void) caused an internal error E340. Now it properly reports E1031 or E1186 depending on the context. Changes: - eval.c: check for void value before -> method call at runtime - vim9expr.c: check for void type before -> method call at compile time - vim9execute.c: check for void value in builtin function arguments and in ISN_STORE fixes: #19897 closes: #19912 Signed-off-by: Hirohito Higashi Signed-off-by: Christian Brabandt --- src/eval.c | 8 +- src/testdir/test_vim9_builtin.vim | 4 +- src/testdir/test_vim9_expr.vim | 2 +- src/testdir/test_vim9_func.vim | 140 ++++++++++++++++++++++++++++++ src/version.c | 2 + src/vim9execute.c | 16 +++- src/vim9expr.c | 7 ++ 7 files changed, 174 insertions(+), 5 deletions(-) diff --git a/src/eval.c b/src/eval.c index e8b9cc8303..6f7c9960a2 100644 --- a/src/eval.c +++ b/src/eval.c @@ -7566,7 +7566,13 @@ handle_subscript( *arg = skipwhite(p + 2); else *arg = p + 2; - if (VIM_ISWHITE(**arg)) + if (ret == OK && evaluate && rettv->v_type == VAR_VOID) + { + if (verbose) + emsg(_(e_cannot_use_void_value)); + ret = FAIL; + } + else if (VIM_ISWHITE(**arg)) { emsg(_(e_no_white_space_allowed_before_parenthesis)); ret = FAIL; diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim index 4cc5656289..d8f69eff3e 100644 --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -402,7 +402,7 @@ enddef def Test_bufload() assert_fails('bufload([])', 'E1220:') - bufload('')->assert_equal(0) + bufload('') enddef def Test_bufloaded() @@ -647,7 +647,7 @@ def Test_ch_logfile() else assert_fails('ch_logfile(true)', 'E1174:') assert_fails('ch_logfile("foo", true)', 'E1174:') - ch_logfile('', '')->assert_equal(0) + ch_logfile('', '') v9.CheckSourceDefAndScriptFailure(['ch_logfile(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1']) v9.CheckSourceDefAndScriptFailure(['ch_logfile("a", true)'], ['E1013: Argument 2: type mismatch, expected string but got bool', 'E1174: String required for argument 2']) diff --git a/src/testdir/test_vim9_expr.vim b/src/testdir/test_vim9_expr.vim index ab0b02a6d1..bd0dca335f 100644 --- a/src/testdir/test_vim9_expr.vim +++ b/src/testdir/test_vim9_expr.vim @@ -3996,7 +3996,7 @@ def Test_expr9_method_call() enddef RetVoid()->byteidx(3) END - v9.CheckDefExecFailure(lines, 'E1013:') + v9.CheckDefExecFailure(lines, 'E1031: Cannot use void value') lines =<< trim END const SetList = [function('len')] diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim index 343c2f777e..f4cd1edaab 100644 --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -4864,4 +4864,144 @@ if has('perl') endif +def Test_void_method_chain() + #### Case 1: Echo, method chain source is void #### + # outside def: runtime error + var lines =<< trim END + vim9script + var Fn1a: func = (): void => { + } + echo Fn1a()->empty() + END + v9.CheckScriptFailure(lines, 'E1031: Cannot use void value') + + # inside def, compile-time error (known void return) + lines =<< trim END + vim9script + def Fn1b(): void + enddef + def TestFunc() + echo Fn1b()->empty() + enddef + defcompile TestFunc + END + v9.CheckScriptFailure(lines, 'E1031: Cannot use void value') + + # inside def, runtime error (untyped func) + lines =<< trim END + vim9script + def TestFunc() + var Fn1c: func = (): void => { + } + echo Fn1c()->empty() + enddef + TestFunc() + END + v9.CheckScriptFailure(lines, 'E1031: Cannot use void value') + + # inside def, compile-time error (func(): void) + lines =<< trim END + vim9script + def TestFunc() + var Fn1d: func(): void = () => { + } + echo Fn1d()->empty() + enddef + defcompile TestFunc + END + v9.CheckScriptFailure(lines, 'E1031: Cannot use void value') + + #### Case 2: Echo, method chain destination is void #### + # outside def: runtime error + lines =<< trim END + vim9script + var Fn2a: func = (s: string): void => { + } + echo "x"->Fn2a() + END + v9.CheckScriptFailure(lines, 'E1186: Expression does not result in a value: "x"->Fn2a()') + + # inside def, compile-time error (known void return) + lines =<< trim END + vim9script + def Fn2b(s: string): void + enddef + def TestFunc() + echo "x"->Fn2b() + enddef + defcompile TestFunc + END + v9.CheckScriptFailure(lines, 'E1186: Expression does not result in a value: "x"->Fn2b()') + + # inside def, runtime error (untyped func) + lines =<< trim END + vim9script + def TestFunc() + var Fn2c: func = (s: string): void => { + } + echo "x"->Fn2c() + enddef + TestFunc() + END + v9.CheckScriptFailure(lines, 'E1031: Cannot use void value') + + # inside def, compile-time error (func(string): void) + lines =<< trim END + vim9script + def TestFunc() + var Fn2d: func(string): void = (s: string): void => { + } + echo "x"->Fn2d() + enddef + defcompile TestFunc + END + v9.CheckScriptFailure(lines, 'E1186: Expression does not result in a value: "x"->Fn2d()') + + #### Case 3: Assignment, RHS is void #### + # outside def: runtime error + lines =<< trim END + vim9script + var Fn3a: func = (): void => { + } + var x = Fn3a() + END + v9.CheckScriptFailure(lines, 'E1031: Cannot use void value') + + # inside def, compile-time error (known void return) + lines =<< trim END + vim9script + def Fn3b(): void + enddef + def TestFunc() + var x = Fn3b() + enddef + defcompile TestFunc + END + v9.CheckScriptFailure(lines, 'E1031: Cannot use void value') + + # inside def, runtime error (untyped func) + lines =<< trim END + vim9script + def TestFunc() + var Fn3c: func = (): void => { + } + var x = Fn3c() + enddef + TestFunc() + END + v9.CheckScriptFailure(lines, 'E1031: Cannot use void value') + + # inside def, compile-time error (func(): void) + lines =<< trim END + vim9script + def TestFunc() + var Fn3d: func(): void = () => { + } + var x = Fn3d() + enddef + defcompile TestFunc + END + v9.CheckScriptFailure(lines, 'E1031: Cannot use void value') +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/version.c b/src/version.c index 25442abc61..c9db272bbb 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 292, /**/ 291, /**/ diff --git a/src/vim9execute.c b/src/vim9execute.c index f7d0cc3c3c..1bc25ed98e 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -1412,6 +1412,17 @@ call_bfunc(int func_idx, int argcount, ectx_T *ectx) if (call_prepare(argcount, argvars, ectx) == FAIL) return FAIL; + + // Check for void value being passed as an argument. + for (idx = 0; idx < argcount; ++idx) + if (argvars[idx].v_type == VAR_VOID) + { + emsg(_(e_cannot_use_void_value)); + for (idx = 0; idx < argcount; ++idx) + clear_tv(&argvars[idx]); + return FAIL; + } + ectx->ec_where.wt_func_name = internal_func_name(func_idx); // Call the builtin function. Set "current_ectx" so that when it @@ -4307,8 +4318,11 @@ exec_instructions(ectx_T *ectx) case ISN_STORE: --ectx->ec_stack.ga_len; tv = STACK_TV_VAR(iptr->isn_arg.number); - if (check_typval_is_value(STACK_TV_BOT(0)) == FAIL) + if (check_typval_is_value(STACK_TV_BOT(0)) == FAIL + || STACK_TV_BOT(0)->v_type == VAR_VOID) { + if (STACK_TV_BOT(0)->v_type == VAR_VOID) + emsg(_(e_cannot_use_void_value)); clear_tv(STACK_TV_BOT(0)); goto on_error; } diff --git a/src/vim9expr.c b/src/vim9expr.c index e12c87edd0..0344976c9d 100644 --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -2568,6 +2568,13 @@ compile_subscript( return FAIL; ppconst->pp_is_const = FALSE; + type = get_type_on_stack(cctx, 0); + if (type->tt_type == VAR_VOID) + { + emsg(_(e_cannot_use_void_value)); + return FAIL; + } + // Apply the '!', '-' and '+' first: // -1.0->func() works like (-1.0)->func() if (compile_leader(cctx, TRUE, start_leader, end_leader) == FAIL) From bc182ae56eb71b94738aaa3bd607c32f584fc200 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 4 Apr 2026 08:32:33 +0000 Subject: [PATCH 02/29] patch 9.2.0293: :packadd may lead to heap-buffer-overflow Problem: :packadd may lead to heap-buffer-overflow when all entries in 'runtimepath' have the same length (after 9.2.0291). Solution: Check for comma after current entry properly (zeertzjq). related: #19854 closes: #19911 Signed-off-by: zeertzjq Signed-off-by: Christian Brabandt --- src/scriptfile.c | 2 +- src/testdir/test_packadd.vim | 7 +++++++ src/version.c | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/scriptfile.c b/src/scriptfile.c index 6df5781a73..df90fe7711 100644 --- a/src/scriptfile.c +++ b/src/scriptfile.c @@ -885,7 +885,7 @@ add_pack_dir_to_rtp(char_u *fname) buf.length = (size_t)copy_option_part(&entry, buf.string, MAXPATHL, ","); // keep track of p_rtp length as we go to make the STRLEN() below have less work to do - p_rtp_len += (*(p_rtp + buf.length) == ',') ? buf.length + 1 : buf.length; + p_rtp_len += (*(cur_entry + buf.length) == ',') ? buf.length + 1 : buf.length; if ((p = (char_u *)strstr((char *)buf.string, "after")) != NULL && p > buf.string diff --git a/src/testdir/test_packadd.vim b/src/testdir/test_packadd.vim index cd7126a9db..6a368762a9 100644 --- a/src/testdir/test_packadd.vim +++ b/src/testdir/test_packadd.vim @@ -26,6 +26,13 @@ func Test_packadd() " plugdir should be inserted before plugdir/after call assert_match('^nosuchdir,' . s:plugdir . ',', &rtp) + " This used to cause heep-buffer-overflow + " All existing entries in 'rtp' have the same length here + let &rtp = 'Xfoodir,Xbardir,Xbazdir' + packadd mytest + " plugdir should be inserted after the existing directories + call assert_match('^Xfoodir,Xbardir,Xbazdir,' .. s:plugdir .. ',', &rtp) + set rtp& let rtp = &rtp filetype on diff --git a/src/version.c b/src/version.c index c9db272bbb..50474f4ac5 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 293, /**/ 292, /**/ From bd8b6c6b05d0e177f2567d24da584adf171470b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 4 Apr 2026 08:35:52 +0000 Subject: [PATCH 03/29] CI: Bump codecov/codecov-action Bumps the github-actions group with 1 update in the / directory: [codecov/codecov-action](https://github.com/codecov/codecov-action). Updates `codecov/codecov-action` from 5 to 6 - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5...v6) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... closes: #19910 Signed-off-by: dependabot[bot] Signed-off-by: Christian Brabandt --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a3f2f6f0f..5f09f06ee2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -405,7 +405,7 @@ jobs: - name: Codecov timeout-minutes: 20 if: matrix.coverage - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v6 with: flags: linux,${{ matrix.features }}-${{ matrix.compiler }}-${{ join(matrix.extra, '-') }} token: ${{ secrets.CODECOV_TOKEN }} @@ -846,7 +846,7 @@ jobs: - name: Codecov timeout-minutes: 20 if: matrix.coverage - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v6 with: directory: src flags: windows,${{ matrix.toolchain }}-${{ matrix.arch }}-${{ matrix.features }} From 22db4a3c57a9921ba164a905f84120cfa5dbccfe Mon Sep 17 00:00:00 2001 From: Yee Cheng Chin Date: Sat, 4 Apr 2026 08:37:47 +0000 Subject: [PATCH 04/29] patch 9.2.0294: if_lua: lua interface does not work with lua 5.5 Problem: if_lua: lua interface does not work with lua 5.5 (Lyderic Landry) Solution: Use the new lua API `luaL_openselectedlibs()` (Yee Cheng Chin) Lua 5.5 removed the API function `openlibs` with `openselectedlibs`, with `luaL_openlibs` replaced by a macro that just calls the new `luaL_openselectedlibs` in the headers (see lua/lua@d738c8d18). This broke Vim's dynamic Lua build as we try to redefine `luaL_openlibs` ourselves and also this function can no longer be loaded from the lib. Update the code to use the new API call instead to fix the issue. fixes: #19814 closes: #19842 closes: #19909 Signed-off-by: Yee Cheng Chin Signed-off-by: Christian Brabandt --- src/if_lua.c | 18 +++++++++++++++++- src/version.c | 2 ++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/if_lua.c b/src/if_lua.c index 7321af2680..fc2e392f2d 100644 --- a/src/if_lua.c +++ b/src/if_lua.c @@ -215,7 +215,11 @@ static void luaV_call_lua_func_free(void *state); # define luaopen_os dll_luaopen_os # define luaopen_package dll_luaopen_package # define luaopen_debug dll_luaopen_debug -# define luaL_openlibs dll_luaL_openlibs +# if LUA_VERSION_NUM >= 505 +# define luaL_openselectedlibs dll_luaL_openselectedlibs +# else +# define luaL_openlibs dll_luaL_openlibs +# endif // lauxlib # if LUA_VERSION_NUM <= 501 @@ -331,7 +335,11 @@ int (*dll_luaopen_io) (lua_State *L); int (*dll_luaopen_os) (lua_State *L); int (*dll_luaopen_package) (lua_State *L); int (*dll_luaopen_debug) (lua_State *L); +# if LUA_VERSION_NUM >= 505 +void (*dll_luaL_openselectedlibs) (lua_State *L, int load, int preload); +# else void (*dll_luaL_openlibs) (lua_State *L); +# endif typedef void **luaV_function; typedef struct { @@ -439,7 +447,11 @@ static const luaV_Reg luaV_dll[] = { {"luaopen_os", (luaV_function) &dll_luaopen_os}, {"luaopen_package", (luaV_function) &dll_luaopen_package}, {"luaopen_debug", (luaV_function) &dll_luaopen_debug}, +# if LUA_VERSION_NUM >= 505 + {"luaL_openselectedlibs", (luaV_function) &dll_luaL_openselectedlibs}, +# else {"luaL_openlibs", (luaV_function) &dll_luaL_openlibs}, +# endif {NULL, NULL} }; @@ -2570,7 +2582,11 @@ luaopen_vim(lua_State *L) luaV_newstate(void) { lua_State *L = luaL_newstate(); +# if LUA_VERSION_NUM >= 505 + luaL_openselectedlibs(L, ~0, 0); // core libs +# else luaL_openlibs(L); // core libs +# endif lua_pushcfunction(L, luaopen_vim); // vim lua_call(L, 0, 0); return L; diff --git a/src/version.c b/src/version.c index 50474f4ac5..41d345cbae 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 294, /**/ 293, /**/ From 08bd9114c17e1dc8fb234cd5362e77db8be3aad5 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 4 Apr 2026 08:50:46 +0000 Subject: [PATCH 05/29] patch 9.2.0295: 'showcmd' shows wrong Visual block size with 'linebreak' Problem: 'showcmd' shows wrong Visual block size with 'linebreak' after end char (after 7.4.467). Solution: Exclude 'linebreak' from end position. Also fix confusing test function names. closes: #19908 Signed-off-by: zeertzjq Signed-off-by: Christian Brabandt --- src/normal.c | 2 +- ...Test_visual_block_hl_with_linebreak_1.dump | 4 ++-- src/testdir/test_listlbr.vim | 22 +++++++++++++++---- src/testdir/test_listlbr_utf8.vim | 5 +++-- src/version.c | 2 ++ 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/normal.c b/src/normal.c index e7be757576..c29fb6c25a 100644 --- a/src/normal.c +++ b/src/normal.c @@ -1653,7 +1653,7 @@ clear_showcmd(void) curwin->w_p_sbr = empty_option; #endif getvcols(curwin, &curwin->w_cursor, &VIsual, - &leftcol, &rightcol, 0); + &leftcol, &rightcol, GETVCOL_END_EXCL_LBR); #ifdef FEAT_LINEBREAK p_sbr = saved_sbr; curwin->w_p_sbr = saved_w_sbr; diff --git a/src/testdir/dumps/Test_visual_block_hl_with_linebreak_1.dump b/src/testdir/dumps/Test_visual_block_hl_with_linebreak_1.dump index a1825a9ee2..69bbb16772 100644 --- a/src/testdir/dumps/Test_visual_block_hl_with_linebreak_1.dump +++ b/src/testdir/dumps/Test_visual_block_hl_with_linebreak_1.dump @@ -2,5 +2,5 @@ |f+0#0000001#a8a8a8255|o@1> +0#0000000#ffffff0@16||+1&&|~+0#4040ff13&| @52 |x+0#0000000&@19||+1&&|~+0#4040ff13&| @52 |~| @18||+1#0000000&|~+0#4040ff13&| @52 -|<+3#0000000&|a|m|e|]| |[|+|]| |2|,|4| @3|A|l@1| |[+1&&|N|o| |N|a|m|e|]| @26|0|,|0|-|1| @9|A|l@1 -|-+2&&@1| |V|I|S|U|A|L| |B|L|O|C|K| |-@1| +0&&@45|2|x|2|0| @6 +|~| @18||+1#0000000&|~+0#4040ff13&| @52 +|-+2#0000000&@1| |V|I|S|U|A|L| |B|L|O|C|K| |-@1| +0&&@28|2|x|4| @6|2|,|4| @10|A|l@1| diff --git a/src/testdir/test_listlbr.vim b/src/testdir/test_listlbr.vim index 7430ec626c..434bef3609 100644 --- a/src/testdir/test_listlbr.vim +++ b/src/testdir/test_listlbr.vim @@ -195,14 +195,28 @@ func Test_linebreak_reset_restore() call StopVimInTerminal(buf) endfunc -func Test_virtual_block() +func Test_visual_block() call s:test_windows('setl sbr=+') call setline(1, [ \ "REMOVE: this not", \ "REMOVE: aaaaaaaaaaaaa", \ ]) + set showcmd showcmdloc=tabline showtabline=2 tabline=%S + if has('gui') + set guioptions-=e + endif exe "norm! 1/^REMOVE:" - exe "norm! 0\jf x" + exe "norm! 0\jf " + let lines = s:screen_lines([1, 4], winwidth(0)) + let expect = [ +\ "2x8 ", +\ "REMOVE: this not ", +\ "REMOVE: ", +\ "+aaaaaaaaaaaaa ", +\ ] + call s:compare_lines(expect, lines) + norm! x + set showcmd& showcmdloc& showtabline& tabline& guioptions& $put let lines = s:screen_lines([1, 4], winwidth(0)) let expect = [ @@ -215,7 +229,7 @@ func Test_virtual_block() call s:close_windows() endfunc -func Test_virtual_block_and_vbA() +func Test_visual_block_and_vbA() call s:test_windows() call setline(1, "long line: " . repeat("foobar ", 40) . "TARGET at end") exe "norm! $3B\eAx\" @@ -236,7 +250,7 @@ func Test_virtual_block_and_vbA() call s:close_windows() endfunc -func Test_virtual_char_and_block() +func Test_visual_char_and_block() call s:test_windows() call setline(1, "1111-1111-1111-11-1111-1111-1111") exe "norm! 0f-lv3lc2222\bgj." diff --git a/src/testdir/test_listlbr_utf8.vim b/src/testdir/test_listlbr_utf8.vim index bde1a49580..5bac070b95 100644 --- a/src/testdir/test_listlbr_utf8.vim +++ b/src/testdir/test_listlbr_utf8.vim @@ -412,11 +412,12 @@ func Test_visual_block_hl_with_linebreak() setlocal nolinebreak call setline(1, [repeat('x', 15), repeat('x', 10), repeat('x', 10)]) call prop_type_add('test', {}) - call prop_add(2, 5, #{text: "foo: ",type: "test"}) - call prop_add(3, 5, #{text: "bar: ",type: "test"}) + call prop_add(2, 5, #{text: "foo: ", type: "test"}) + call prop_add(3, 5, #{text: "bar: ", type: "test"}) exe "normal! gg02l\2j2l" endfunc + set laststatus=0 showcmd ruler " FIXME: clipboard=autoselect sometimes changes Visual highlight set clipboard= END diff --git a/src/version.c b/src/version.c index 41d345cbae..f0f21363c6 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 295, /**/ 294, /**/ From 18cd55dbc4b97f06360905aa3efce5ab5d9bcad8 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 4 Apr 2026 08:55:59 +0000 Subject: [PATCH 06/29] patch 9.2.0296: Redundant and incorrect integer pointer casts in drawline.c Problem: Currently `colnr_T` and `int` and the same type, so casting `int *` to `colnr_T *` is redundant. Additionally, even if they are changed to different types in the future, these casts are incorrect as they won't work on big-endian platforms. Solution: Remove the casts. Also fix two cases of passing false instead of 0 to an integer argument (zeertzjq). related: #19672 closes: #19907 Signed-off-by: zeertzjq Signed-off-by: Christian Brabandt --- src/drawline.c | 13 +++++-------- src/ops.c | 4 ++-- src/version.c | 2 ++ 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/drawline.c b/src/drawline.c index b9b92a1e9e..e420442563 100644 --- a/src/drawline.c +++ b/src/drawline.c @@ -1418,8 +1418,7 @@ win_line( wlv.fromcol = 0; else { - getvvcol(wp, top, (colnr_T *)&wlv.fromcol, - NULL, NULL, 0); + getvvcol(wp, top, &wlv.fromcol, NULL, NULL, 0); if (gchar_pos(top) == NUL) wlv.tocol = wlv.fromcol + 1; } @@ -1437,12 +1436,10 @@ win_line( { pos = *bot; if (*p_sel == 'e') - getvvcol(wp, &pos, (colnr_T *)&wlv.tocol, - NULL, NULL, 0); + getvvcol(wp, &pos, &wlv.tocol, NULL, NULL, 0); else { - getvvcol(wp, &pos, NULL, NULL, - (colnr_T *)&wlv.tocol, 0); + getvvcol(wp, &pos, NULL, NULL, &wlv.tocol, 0); ++wlv.tocol; } } @@ -1481,14 +1478,14 @@ win_line( { if (lnum == curwin->w_cursor.lnum) getvcol(curwin, &(curwin->w_cursor), - (colnr_T *)&wlv.fromcol, NULL, NULL, 0); + &wlv.fromcol, NULL, NULL, 0); else wlv.fromcol = 0; if (lnum == curwin->w_cursor.lnum + search_match_lines) { pos.lnum = lnum; pos.col = search_match_endcol; - getvcol(curwin, &pos, (colnr_T *)&wlv.tocol, NULL, NULL, 0); + getvcol(curwin, &pos, &wlv.tocol, NULL, NULL, 0); } else wlv.tocol = MAXCOL; diff --git a/src/ops.c b/src/ops.c index 140cdc8c58..6ea50c912e 100644 --- a/src/ops.c +++ b/src/ops.c @@ -2620,7 +2620,7 @@ charwise_block_prep( startcol = start.col; if (virtual_op) { - getvcol(curwin, &start, &cs, NULL, &ce, false); + getvcol(curwin, &start, &cs, NULL, &ce, 0); if (ce != cs && start.coladd > 0) { // Part of a tab selected -- but don't @@ -2639,7 +2639,7 @@ charwise_block_prep( endcol = end.col; if (virtual_op) { - getvcol(curwin, &end, &cs, NULL, &ce, false); + getvcol(curwin, &end, &cs, NULL, &ce, 0); if (p[endcol] == NUL || (cs + end.coladd < ce // Don't add space for double-wide // char; endcol will be on last byte diff --git a/src/version.c b/src/version.c index f0f21363c6..49445781ee 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 296, /**/ 295, /**/ From 77e7a40af2cab8c0f89a33553af42428b20af233 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sat, 4 Apr 2026 09:04:34 +0000 Subject: [PATCH 07/29] patch 9.2.0297: libvterm: can improve CSI overflow code Problem: libvterm: can improve CSI overflow code Solution: Handle overflow cases better (Yasuhiro Matsumoto) closes: #19903 Signed-off-by: Yasuhiro Matsumoto Signed-off-by: Christian Brabandt --- src/libvterm/src/parser.c | 16 ++++++++++------ src/testdir/test_terminal3.vim | 5 +++-- src/version.c | 2 ++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/libvterm/src/parser.c b/src/libvterm/src/parser.c index e167e0cb1a..2ca422f4a2 100644 --- a/src/libvterm/src/parser.c +++ b/src/libvterm/src/parser.c @@ -230,12 +230,16 @@ size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len) case CSI_ARGS: /* Numerical value of argument */ if(c >= '0' && c <= '9') { - if(vt->parser.v.csi.args[vt->parser.v.csi.argi] == CSI_ARG_MISSING) - vt->parser.v.csi.args[vt->parser.v.csi.argi] = 0; - if(vt->parser.v.csi.args[vt->parser.v.csi.argi] < (CSI_ARG_MISSING - 9) / 10) { - vt->parser.v.csi.args[vt->parser.v.csi.argi] *= 10; - vt->parser.v.csi.args[vt->parser.v.csi.argi] += c - '0'; - } + long arg_max = CSI_ARG_MISSING - 1; + long *arg = &vt->parser.v.csi.args[vt->parser.v.csi.argi]; + int digit = c - '0'; + + if(*arg == CSI_ARG_MISSING) + *arg = 0; + if(*arg > (arg_max - digit) / 10) + *arg = arg_max; + else + *arg = *arg * 10 + digit; break; } if(c == ':') { diff --git a/src/testdir/test_terminal3.vim b/src/testdir/test_terminal3.vim index cdfa189068..04c7c925e3 100644 --- a/src/testdir/test_terminal3.vim +++ b/src/testdir/test_terminal3.vim @@ -1232,8 +1232,9 @@ endfunc " Test that CSI sequences with more than CSI_ARGS_MAX arguments do not crash func Test_terminal_csi_args_overflow() CheckExecutable printf - let buf = term_start([&shell, &shellcmdflag, - \ 'printf "\033[' . repeat('1;', 49) . '1m"']) + let seq = "\033[" .. repeat('1;', 49) .. '1m' + let seq ..= "\033[1111111111111111111m" + let buf = term_start([&shell, &shellcmdflag, 'printf "' .. seq .. '"']) " If we get here without a crash, the fix works call assert_equal('running', term_getstatus(buf)) diff --git a/src/version.c b/src/version.c index 49445781ee..abd3f91c0f 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 297, /**/ 296, /**/ From 3c79e33aeb0c26534c749a35d35a331613104d57 Mon Sep 17 00:00:00 2001 From: Hirohito Higashi Date: Sat, 4 Apr 2026 09:09:13 +0000 Subject: [PATCH 08/29] patch 9.2.0298: Some internal variables are not modified Problem: Some internal variables are not modified Solution: Add const qualifier to static table data (Hirohito Higashi). Several static arrays that are never modified at runtime were missing the const qualifier. Add const to move them from .data to .rodata section. closes: #19901 Signed-off-by: Hirohito Higashi Signed-off-by: Christian Brabandt --- src/crypt.c | 2 +- src/ex_docmd.c | 2 +- src/hardcopy.c | 2 +- src/memline.c | 2 +- src/pty.c | 12 ++++++------ src/regexp.c | 4 ++-- src/regexp_bt.c | 2 +- src/regexp_nfa.c | 2 +- src/term.c | 6 +++--- src/version.c | 2 ++ 10 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/crypt.c b/src/crypt.c index 794594f910..2fade5db9d 100644 --- a/src/crypt.c +++ b/src/crypt.c @@ -342,7 +342,7 @@ sodium_enabled(int verbose) #endif #define CRYPT_MAGIC_LEN 12 // cannot change -static char crypt_magic_head[] = "VimCrypt~"; +static const char crypt_magic_head[] = "VimCrypt~"; /* * Return int value for crypt method name. diff --git a/src/ex_docmd.c b/src/ex_docmd.c index 7dfc1d56c9..7834f2bdbd 100644 --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -2734,7 +2734,7 @@ ex_errmsg(char *msg, char_u *arg) * The "+" string used in place of an empty command in Ex mode. * This string is used in pointer comparison. */ -static char exmode_plus[] = "+"; +static const char exmode_plus[] = "+"; /* * Handle a range without a command. diff --git a/src/hardcopy.c b/src/hardcopy.c index 5333116049..55d4384182 100644 --- a/src/hardcopy.c +++ b/src/hardcopy.c @@ -1394,7 +1394,7 @@ static int prt_use_courier; static int prt_in_ascii; static int prt_half_width; static char *prt_ascii_encoding; -static char_u prt_hexchar[] = "0123456789abcdef"; +static const char_u prt_hexchar[] = "0123456789abcdef"; static void prt_write_file_raw_len(char_u *buffer, int bytes) diff --git a/src/memline.c b/src/memline.c index 35b1fae230..886f08f973 100644 --- a/src/memline.c +++ b/src/memline.c @@ -69,7 +69,7 @@ typedef struct pointer_entry PTR_EN; // block/line-count pair #define BLOCK0_ID1_C4 's' // block 0 id 1 'cm' 4 #if defined(FEAT_CRYPT) -static int id1_codes[] = { +static const int id1_codes[] = { BLOCK0_ID1_C0, // CRYPT_M_ZIP BLOCK0_ID1_C1, // CRYPT_M_BF BLOCK0_ID1_C2, // CRYPT_M_BF2 diff --git a/src/pty.c b/src/pty.c index 55bbf94d54..c615785c7e 100644 --- a/src/pty.c +++ b/src/pty.c @@ -354,15 +354,15 @@ mch_openpty(char **ttyn) #ifndef PTY_DONE # ifdef hpux -static char PtyProto[] = "/dev/ptym/ptyXY"; -static char TtyProto[] = "/dev/pty/ttyXY"; +static const char PtyProto[] = "/dev/ptym/ptyXY"; +static const char TtyProto[] = "/dev/pty/ttyXY"; # else # ifdef __HAIKU__ -static char PtyProto[] = "/dev/pt/XY"; -static char TtyProto[] = "/dev/tt/XY"; +static const char PtyProto[] = "/dev/pt/XY"; +static const char TtyProto[] = "/dev/tt/XY"; # else -static char PtyProto[] = "/dev/ptyXY"; -static char TtyProto[] = "/dev/ttyXY"; +static const char PtyProto[] = "/dev/ptyXY"; +static const char TtyProto[] = "/dev/ttyXY"; # endif # endif diff --git a/src/regexp.c b/src/regexp.c index 6ac74467f9..54fadc899e 100644 --- a/src/regexp.c +++ b/src/regexp.c @@ -377,7 +377,7 @@ static int reg_strict; // "[abc" is illegal */ // META[] is used often enough to justify turning it into a table. -static char_u META_flags[] = { +static const char_u META_flags[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // % & ( ) * + . @@ -2908,7 +2908,7 @@ static regengine_T nfa_regengine = static int regexp_engine = 0; #ifdef DEBUG -static char_u regname[][30] = { +static const char_u regname[][30] = { "AUTOMATIC Regexp Engine", "BACKTRACKING Regexp Engine", "NFA Regexp Engine" diff --git a/src/regexp_bt.c b/src/regexp_bt.c index 063510c643..a2877fc8de 100644 --- a/src/regexp_bt.c +++ b/src/regexp_bt.c @@ -253,7 +253,7 @@ static int one_exactly = FALSE; // only do one char for EXACTLY // When making changes to classchars also change nfa_classcodes. static char_u *classchars = (char_u *)".iIkKfFpPsSdDxXoOwWhHaAlLuU"; -static int classcodes[] = { +static const int classcodes[] = { ANY, IDENT, SIDENT, KWORD, SKWORD, FNAME, SFNAME, PRINT, SPRINT, WHITE, NWHITE, DIGIT, NDIGIT, diff --git a/src/regexp_nfa.c b/src/regexp_nfa.c index b17d4d3493..610c6bd678 100644 --- a/src/regexp_nfa.c +++ b/src/regexp_nfa.c @@ -233,7 +233,7 @@ enum }; // Keep in sync with classchars. -static int nfa_classcodes[] = { +static const int nfa_classcodes[] = { NFA_ANY, NFA_IDENT, NFA_SIDENT, NFA_KWORD,NFA_SKWORD, NFA_FNAME, NFA_SFNAME, NFA_PRINT, NFA_SPRINT, NFA_WHITE, NFA_NWHITE, NFA_DIGIT, NFA_NDIGIT, diff --git a/src/term.c b/src/term.c index bdf81bd615..da63f8a845 100644 --- a/src/term.c +++ b/src/term.c @@ -689,7 +689,7 @@ static tcap_entry_T builtin_sync_output[] = { /* * List of DECRQM modes that Vim supports */ -static int dec_modes[] = { +static const int dec_modes[] = { 2026, // Synchronized output #ifdef UNIX 2048 // In-band terminal resize events @@ -7827,11 +7827,11 @@ swap_tcap(void) #if (defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))) || defined(FEAT_TERMINAL) -static int cube_value[] = { +static const int cube_value[] = { 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF }; -static int grey_ramp[] = { +static const int grey_ramp[] = { 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76, 0x80, 0x8A, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE }; diff --git a/src/version.c b/src/version.c index abd3f91c0f..e3dee3b3a5 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 298, /**/ 297, /**/ From 5943c57173e78ce5b5d82d3e908542b010a31134 Mon Sep 17 00:00:00 2001 From: Carlo Klapproth <6682561+elcarlosIII@users.noreply.github.com> Date: Sat, 4 Apr 2026 09:22:50 +0000 Subject: [PATCH 09/29] runtime(zathurarc): Update page-padding, wrap the zathurarcOption keywords page-padding was split in page-v-padding and page-h-padding closes: #19899 Signed-off-by: Carlo Klapproth <6682561+elcarlosIII@users.noreply.github.com> Signed-off-by: Christian Brabandt --- runtime/syntax/zathurarc.vim | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/runtime/syntax/zathurarc.vim b/runtime/syntax/zathurarc.vim index 32997c2d2d..3e35522e96 100644 --- a/runtime/syntax/zathurarc.vim +++ b/runtime/syntax/zathurarc.vim @@ -4,6 +4,7 @@ " Documentation: https://pwmt.org/projects/zathura/documentation/ " Upstream: https://github.com/Freed-Wu/zathurarc.vim " Latest Revision: 2024-09-16 +" 2026 Apr 04 by Vim project: add page-v-padding and page-h-padding if exists('b:current_syntax') finish @@ -22,7 +23,33 @@ syntax region zathurarcString start=`'` skip=`\\'` end=`'` syntax keyword zathurarcMode normal fullscreen presentation index syntax keyword zathurarcBoolean true false syntax keyword zathurarcCommand include map set unmap -syntax keyword zathurarcOption abort-clear-search adjust-open advance-pages-per-row completion-bg completion-fg completion-group-bg completion-group-fg completion-highlight-bg completion-highlight-fg continuous-hist-save database dbus-raise-window dbus-service default-bg default-fg double-click-follow exec-command filemonitor first-page-column font guioptions highlight-active-color highlight-color highlight-fg highlight-transparency incremental-search index-active-bg index-active-fg index-bg index-fg inputbar-bg inputbar-fg link-hadjust link-zoom n-completion-items notification-bg notification-error-bg notification-error-fg notification-fg notification-warning-bg notification-warning-fg page-cache-size page-padding page-right-to-left page-thumbnail-size pages-per-row recolor recolor-darkcolor recolor-keephue recolor-lightcolor recolor-reverse-video render-loading render-loading-bg render-loading-fg sandbox scroll-full-overlap scroll-hstep scroll-page-aware scroll-step scroll-wrap search-hadjust selection-clipboard selection-notification show-directories show-hidden show-recent statusbar-basename statusbar-bg statusbar-fg statusbar-h-padding statusbar-home-tilde statusbar-page-percent statusbar-v-padding synctex synctex-editor-command vertical-center window-height window-icon window-icon-document window-title-basename window-title-home-tilde window-title-page window-width zoom-center zoom-max zoom-min zoom-step +syntax keyword zathurarcOption abort-clear-search adjust-open advance-pages-per-row +syntax keyword zathurarcOption completion-bg completion-fg completion-group-bg +syntax keyword zathurarcOption completion-group-fg completion-highlight-bg +syntax keyword zathurarcOption completion-highlight-fg continuous-hist-save database +syntax keyword zathurarcOption dbus-raise-window dbus-service default-bg default-fg +syntax keyword zathurarcOption double-click-follow exec-command filemonitor +syntax keyword zathurarcOption first-page-column font guioptions highlight-active-color +syntax keyword zathurarcOption highlight-color highlight-fg highlight-transparency +syntax keyword zathurarcOption incremental-search index-active-bg index-active-fg index-bg +syntax keyword zathurarcOption index-fg inputbar-bg inputbar-fg link-hadjust link-zoom +syntax keyword zathurarcOption n-completion-items notification-bg notification-error-bg +syntax keyword zathurarcOption notification-error-fg notification-fg +syntax keyword zathurarcOption notification-warning-bg notification-warning-fg +syntax keyword zathurarcOption page-cache-size page-h-padding page-v-padding +syntax keyword zathurarcOption page-right-to-left page-thumbnail-size pages-per-row recolor +syntax keyword zathurarcOption recolor-darkcolor recolor-keephue recolor-lightcolor +syntax keyword zathurarcOption recolor-reverse-video render-loading render-loading-bg +syntax keyword zathurarcOption render-loading-fg sandbox scroll-full-overlap scroll-hstep +syntax keyword zathurarcOption scroll-page-aware scroll-step scroll-wrap search-hadjust +syntax keyword zathurarcOption selection-clipboard selection-notification show-directories +syntax keyword zathurarcOption show-hidden show-recent statusbar-basename statusbar-bg +syntax keyword zathurarcOption statusbar-fg statusbar-h-padding statusbar-home-tilde +syntax keyword zathurarcOption statusbar-page-percent statusbar-v-padding synctex +syntax keyword zathurarcOption synctex-editor-command vertical-center window-height +syntax keyword zathurarcOption window-icon window-icon-document window-title-basename +syntax keyword zathurarcOption window-title-home-tilde window-title-page window-width +syntax keyword zathurarcOption zoom-center zoom-max zoom-min zoom-step highlight default link zathurarcComment Comment highlight default link zathurarcNumber Number From 46f530e517bd1b59acc2eb0d2aa76d02e54ca9fe Mon Sep 17 00:00:00 2001 From: Christian Brabandt Date: Sun, 5 Apr 2026 15:58:00 +0000 Subject: [PATCH 10/29] patch 9.2.0299: runtime(zip): may write using absolute paths Problem: runtime(zip): may write using absolute paths (syndicate) Solution: Detect this case and abort on Unix, warn in the documentation about possible issues Signed-off-by: Christian Brabandt --- runtime/autoload/zip.vim | 8 ++++++++ runtime/doc/pi_zip.txt | 6 +++++- src/testdir/samples/evil.zip | Bin 413 -> 562 bytes src/testdir/test_plugin_zip.vim | 19 +++++++++++++++++++ src/version.c | 2 ++ 5 files changed, 34 insertions(+), 1 deletion(-) diff --git a/runtime/autoload/zip.vim b/runtime/autoload/zip.vim index 1ce9cfc2f7..f4482fd7fc 100644 --- a/runtime/autoload/zip.vim +++ b/runtime/autoload/zip.vim @@ -21,6 +21,7 @@ " 2026 Feb 08 by Vim Project: use system() instead of :! " 2026 Mar 08 by Vim Project: Make ZipUpdatePS() check for powershell " 2026 Apr 01 by Vim Project: Detect more path traversal attacks +" 2026 Apr 05 by Vim Project: Detect more path traversal attacks " License: Vim License (see vim's :help license) " Copyright: Copyright (C) 2005-2019 Charles E. Campbell {{{1 " Permission is hereby granted to use and distribute this code, @@ -395,9 +396,16 @@ fun! zip#Write(fname) if has("unix") let zipfile = substitute(a:fname,'zipfile://\(.\{-}\)::[^\\].*$','\1','') let fname = substitute(a:fname,'zipfile://.\{-}::\([^\\].*\)$','\1','') + " fname should not start with a leading slash to avoid writing anywhere into the system + if fname =~ '^/' + call s:Mess('Error', "***error*** (zip#Write) Path Traversal Attack detected, not writing!") + call s:ChgDir(curdir,s:WARNING,"(zip#Write) unable to return to ".curdir."!") + return + endif else let zipfile = substitute(a:fname,'^.\{-}zipfile://\(.\{-}\)::[^\\].*$','\1','') let fname = substitute(a:fname,'^.\{-}zipfile://.\{-}::\([^\\].*\)$','\1','') + " TODO: what to check on MS-Windows to avoid writing absolute paths? endif if fname =~ '^[.]\{1,2}/' let gnu_cmd = g:zip_zipcmd . ' -d ' . s:Escape(fnamemodify(zipfile,":p"),0) . ' ' . s:Escape(fname,0) diff --git a/runtime/doc/pi_zip.txt b/runtime/doc/pi_zip.txt index b1bc7fd7dc..e9294b4059 100644 --- a/runtime/doc/pi_zip.txt +++ b/runtime/doc/pi_zip.txt @@ -1,4 +1,4 @@ -*pi_zip.txt* For Vim version 9.2. Last change: 2026 Feb 14 +*pi_zip.txt* For Vim version 9.2. Last change: 2026 Apr 05 +====================+ | Zip File Interface | @@ -33,6 +33,10 @@ Copyright: Copyright (C) 2005-2015 Charles E Campbell *zip-copyright* also write to the file. Currently, one may not make a new file in zip archives via the plugin. + The zip plugin tries to detect some common path traversal attack + patterns, but it may not catch all possible cases. Please be very + careful when using this plugin with untrusted input. + COMMANDS~ *zip-x* x : extract a listed file when the cursor is atop it diff --git a/src/testdir/samples/evil.zip b/src/testdir/samples/evil.zip index 17cffadf934580090ebe2b3d3876edec14767658..8361710b9bd032d8c4ec094bef18f5884f893fa2 100644 GIT binary patch delta 178 zcmbQsyoqJPUQcEg5e5)&sB4XBm=%#P2xNn>3=r#=L=+JBv$6+ zC#L9?RFo9tC+nsa<>%@q7A0q7mZfq{+cz`!68%QZL O5Q;G}Ft7nJ0|Nj+rz%ze delta 31 gcmdnQGM9P6-pL${XGB=p7=VBY2+siN-5?GF0D5Kxwg3PC diff --git a/src/testdir/test_plugin_zip.vim b/src/testdir/test_plugin_zip.vim index 53b6120834..6d77643482 100644 --- a/src/testdir/test_plugin_zip.vim +++ b/src/testdir/test_plugin_zip.vim @@ -296,3 +296,22 @@ def g:Test_zip_fname_evil_path2() assert_match('zipfile://.*::.*tmp/foobar', @%) bw! enddef + +def g:Test_zip_fname_evil_path3() + CheckNotMSWindows + # needed for writing the zip file + CheckExecutable zip + + CopyZipFile("evil.zip") + defer delete("X.zip") + e X.zip + + :1 + var fname = 'payload.txt' + search('\V' .. fname) + exe "normal \" + :w! + var mess = execute(':mess') + assert_match('Path Traversal Attack', mess) + bw! +enddef diff --git a/src/version.c b/src/version.c index e3dee3b3a5..4d0ec69447 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 299, /**/ 298, /**/ From 9e041457a54ea04d009814422353f45138062dcd Mon Sep 17 00:00:00 2001 From: Christian Brabandt Date: Sun, 5 Apr 2026 16:08:12 +0000 Subject: [PATCH 11/29] patch 9.2.0300: The vimball plugin needs some love Problem: The vimball plugin needs some love (syndicate) Solution: Clean-up, refactor and update the plugin, in particular, catch path traversal attacks This change does the following - Clean up Indentation and remove calls to Decho - Increase minimum Vim version to 7.4 for mkdir() - Use mkdir() consistently - Update Metadata Header - Remove check for fnameescape() - Catch path traversal attacks - Add vimball basic tests - Remove mentioning of g:vimball_mkdir in documentation closes: #19921 Signed-off-by: Christian Brabandt --- runtime/autoload/vimball.vim | 266 ++++++++-------------------- runtime/doc/pi_vimball.txt | 17 +- runtime/doc/tags | 1 - src/testdir/Make_all.mak | 2 + src/testdir/test_plugin_vimball.vim | 85 +++++++++ src/version.c | 2 + 6 files changed, 164 insertions(+), 209 deletions(-) create mode 100644 src/testdir/test_plugin_vimball.vim diff --git a/runtime/autoload/vimball.vim b/runtime/autoload/vimball.vim index 6456984411..fb4df5eb66 100644 --- a/runtime/autoload/vimball.vim +++ b/runtime/autoload/vimball.vim @@ -6,6 +6,7 @@ " GetLatestVimScripts: 1502 1 :AutoInstall: vimball.vim " Last Change: " 2025 Feb 28 by Vim Project: add support for bzip3 (#16755) +" 2026 Apr 05 by Vim Project: Detect Path Traversal Attacks " Copyright: (c) 2004-2011 by Charles E. Campbell " The VIM LICENSE applies to Vimball.vim, and Vimball.txt " (see |copyright|) except use "Vimball" instead of "Vim". @@ -18,15 +19,14 @@ if &cp || exists("g:loaded_vimball") finish endif let g:loaded_vimball = "v37" -if v:version < 702 +if v:version < 704 echohl WarningMsg - echo "***warning*** this version of vimball needs vim 7.2" + echo "***warning*** this version of vimball needs vim 7.4" echohl Normal finish endif let s:keepcpo= &cpo set cpo&vim -"DechoTabOn " ===================================================================== " Constants: {{{1 @@ -47,20 +47,6 @@ if !exists("s:USAGE") let g:netrw_cygwin= 0 endif endif - - " set up g:vimball_mkdir if the mkdir() call isn't defined - if !exists("*mkdir") - if exists("g:netrw_local_mkdir") - let g:vimball_mkdir= g:netrw_local_mkdir - elseif executable("mkdir") - let g:vimball_mkdir= "mkdir" - elseif executable("makedir") - let g:vimball_mkdir= "makedir" - endif - if !exists(g:vimball_mkdir) - call vimball#ShowMesg(s:WARNING,"(vimball) g:vimball_mkdir undefined") - endif - endif endif " ===================================================================== @@ -81,7 +67,6 @@ endif " filesize " [file] fun! vimball#MkVimball(line1,line2,writelevel,...) range -" call Dfunc("MkVimball(line1=".a:line1." line2=".a:line2." writelevel=".a:writelevel." vimballname<".a:1.">) a:0=".a:0) if a:1 =~ '\.vim$' || a:1 =~ '\.txt$' let vbname= substitute(a:1,'\.\a\{3}$','.vmb','') else @@ -90,15 +75,12 @@ fun! vimball#MkVimball(line1,line2,writelevel,...) range if vbname !~ '\.vmb$' let vbname= vbname.'.vmb' endif -" call Decho("vbname<".vbname.">") if !a:writelevel && a:1 =~ '[\/]' call vimball#ShowMesg(s:ERROR,"(MkVimball) vimball name<".a:1."> should not include slashes; use ! to insist") -" call Dret("MkVimball : vimball name<".a:1."> should not include slashes") return endif if !a:writelevel && filereadable(vbname) call vimball#ShowMesg(s:ERROR,"(MkVimball) file<".vbname."> exists; use ! to insist") -" call Dret("MkVimball : file<".vbname."> already exists; use ! to insist") return endif @@ -120,17 +102,14 @@ fun! vimball#MkVimball(line1,line2,writelevel,...) range " record current tab, initialize while loop index let curtabnr = tabpagenr() let linenr = a:line1 -" call Decho("curtabnr=".curtabnr) while linenr <= a:line2 let svfile = getline(linenr) -" call Decho("svfile<".svfile.">") if !filereadable(svfile) call vimball#ShowMesg(s:ERROR,"unable to read file<".svfile.">") - call s:ChgDir(curdir) - call vimball#RestoreSettings() -" call Dret("MkVimball") + call s:ChgDir(curdir) + call vimball#RestoreSettings() return endif @@ -145,20 +124,18 @@ fun! vimball#MkVimball(line1,line2,writelevel,...) range let lastline= line("$") + 1 if lastline == 2 && getline("$") == "" - call setline(1,'" Vimball Archiver by Charles E. Campbell') - call setline(2,'UseVimball') - call setline(3,'finish') - let lastline= line("$") + 1 + call setline(1,'" Vimball Archiver by Charles E. Campbell') + call setline(2,'UseVimball') + call setline(3,'finish') + let lastline= line("$") + 1 endif call setline(lastline ,substitute(svfile,'$',' [[[1','')) call setline(lastline+1,0) " write the file from the tab -" call Decho("exe $r ".fnameescape(svfile)) exe "$r ".fnameescape(svfile) call setline(lastline+1,line("$") - lastline - 1) -" call Decho("lastline=".lastline." line$=".line("$")) " restore to normal tab exe "tabn ".curtabnr @@ -170,13 +147,10 @@ fun! vimball#MkVimball(line1,line2,writelevel,...) range call s:ChgDir(curdir) setlocal ff=unix if a:writelevel -" call Decho("exe w! ".fnameescape(vbname)) exe "w! ".fnameescape(vbname) else -" call Decho("exe w ".fnameescape(vbname)) exe "w ".fnameescape(vbname) endif -" call Decho("Vimball<".vbname."> created") echo "Vimball<".vbname."> created" " remove the evidence @@ -187,7 +161,6 @@ fun! vimball#MkVimball(line1,line2,writelevel,...) range " restore options call vimball#RestoreSettings() -" call Dret("MkVimball") endfun " --------------------------------------------------------------------- @@ -195,17 +168,9 @@ endfun " (invoked the the UseVimball command embedded in " vimballs' prologue) fun! vimball#Vimball(really,...) -" call Dfunc("vimball#Vimball(really=".a:really.") a:0=".a:0) - - if v:version < 701 || (v:version == 701 && !exists('*fnameescape')) - echoerr "your vim is missing the fnameescape() function (pls upgrade to vim 7.2 or later)" -" call Dret("vimball#Vimball : needs 7.1 with patch 299 or later") - return - endif if getline(1) !~ '^" Vimball Archiver' echoerr "(Vimball) The current file does not appear to be a Vimball!" -" call Dret("vimball#Vimball") return endif @@ -215,7 +180,6 @@ fun! vimball#Vimball(really,...) let vimballfile = expand("%:tr") " set up vimball tab -" call Decho("setting up vimball tab") tabnew sil! file Vimball let vbtabnr= tabpagenr() @@ -227,21 +191,18 @@ fun! vimball#Vimball(really,...) " If, however, the user did not specify a full path, set the home to be below the current directory let home= expand(a:1) if has("win32") || has("win95") || has("win64") || has("win16") - if home !~ '^\a:[/\\]' - let home= getcwd().'/'.a:1 - endif + if home !~ '^\a:[/\\]' + let home= getcwd().'/'.a:1 + endif elseif home !~ '^/' - let home= getcwd().'/'.a:1 + let home= getcwd().'/'.a:1 endif else let home= vimball#VimballHome() endif -" call Decho("home<".home.">") " save current directory and remove older same-named vimball, if any let curdir = getcwd() -" call Decho("home<".home.">") -" call Decho("curdir<".curdir.">") call s:ChgDir(home) let s:ok_unablefind= 1 @@ -260,56 +221,48 @@ fun! vimball#Vimball(really,...) endif " apportion vimball contents to various files -" call Decho("exe tabn ".curtabnr) exe "tabn ".curtabnr -" call Decho("linenr=".linenr." line$=".line("$")) while 1 < linenr && linenr < line("$") let fname = substitute(getline(linenr),'\t\[\[\[1$','','') let fname = substitute(fname,'\\','/','g') + let fname = resolve(simplify(fname)) let fsize = substitute(getline(linenr+1),'^\(\d\+\).\{-}$','\1','')+0 let fenc = substitute(getline(linenr+1),'^\d\+\s*\(\S\{-}\)$','\1','') let filecnt = filecnt + 1 -" call Decho("fname<".fname."> fsize=".fsize." filecnt=".filecnt. " fenc=".fenc) + if fname =~ '\.\.' + echomsg "(Vimball) Path Traversal Attack detected, aborting..." + exe "tabn ".curtabnr + bw! Vimball + call s:ChgDir(curdir) + return + endif if a:really echomsg "extracted <".fname.">: ".fsize." lines" else echomsg "would extract <".fname.">: ".fsize." lines" endif -" call Decho("using L#".linenr.": will extract file<".fname.">") -" call Decho("using L#".(linenr+1).": fsize=".fsize) " Allow AsNeeded/ directory to take place of plugin/ directory " when AsNeeded/filename is filereadable or was present in VimballRecord if fname =~ '\ instead of <".fname.">") - let fname= anfname - endif + let anfname= substitute(fname,'\)") let fnamebuf= substitute(fname,'\\','/','g') - let dirpath = substitute(home,'\\','/','g') -" call Decho("init: fnamebuf<".fnamebuf.">") -" call Decho("init: dirpath <".dirpath.">") + let dirpath = substitute(home,'\\','/','g') while fnamebuf =~ '/' let dirname = dirpath."/".substitute(fnamebuf,'/.*$','','') - let dirpath = dirname + let dirpath = dirname let fnamebuf = substitute(fnamebuf,'^.\{-}/\(.*\)$','\1','') -" call Decho("dirname<".dirname.">") -" call Decho("dirpath<".dirpath.">") if !isdirectory(dirname) -" call Decho("making <".dirname.">") - if exists("g:vimball_mkdir") - call system(g:vimball_mkdir." ".shellescape(dirname)) - else - call mkdir(dirname) - endif - call s:RecordInVar(home,"rmdir('".dirname."')") + call mkdir(dirname) + call s:RecordInVar(home,"rmdir('".dirname."')") endif endwhile endif @@ -319,13 +272,11 @@ fun! vimball#Vimball(really,...) " (skip over path/filename and qty-lines) let linenr = linenr + 2 let lastline = linenr + fsize - 1 -" call Decho("exe ".linenr.",".lastline."yank a") " no point in handling a zero-length file if lastline >= linenr exe "silent ".linenr.",".lastline."yank a" " copy "a" buffer into tab -" call Decho('copy "a buffer into tab#'.vbtabnr) exe "tabn ".vbtabnr setlocal ma sil! %d @@ -336,38 +287,31 @@ fun! vimball#Vimball(really,...) " write tab to file if a:really let fnamepath= home."/".fname -" call Decho("exe w! ".fnameescape(fnamepath)) - if fenc != "" - exe "silent w! ++enc=".fnameescape(fenc)." ".fnameescape(fnamepath) - else - exe "silent w! ".fnameescape(fnamepath) - endif - echo "wrote ".fnameescape(fnamepath) - call s:RecordInVar(home,"call delete('".fnamepath."')") + if fenc != "" + exe "silent w! ++enc=".fnameescape(fenc)." ".fnameescape(fnamepath) + else + exe "silent w! ".fnameescape(fnamepath) + endif + echo "wrote ".fnameescape(fnamepath) + call s:RecordInVar(home,"call delete('".fnamepath."')") endif " return to tab with vimball -" call Decho("exe tabn ".curtabnr) exe "tabn ".curtabnr " set up help if it's a doc/*.txt file -" call Decho("didhelp<".didhelp."> fname<".fname.">") if a:really && didhelp == "" && fname =~ 'doc/[^/]\+\.\(txt\|..x\)$' - let didhelp= substitute(fname,'^\(.*\") + let didhelp= substitute(fname,'^\(.*\") if didhelp != "" let htpath= home."/".didhelp -" call Decho("exe helptags ".htpath) exe "helptags ".fnameescape(htpath) echo "did helptags" endif @@ -388,8 +332,6 @@ fun! vimball#Vimball(really,...) exe "sil! tabc! ".vbtabnr call vimball#RestoreSettings() call s:ChgDir(curdir) - -" call Dret("vimball#Vimball") endfun " --------------------------------------------------------------------- @@ -399,23 +341,18 @@ endfun " Usage: RmVimball (assume current file is a vimball; remove) " RmVimball vimballname fun! vimball#RmVimball(...) -" call Dfunc("vimball#RmVimball() a:0=".a:0) if exists("g:vimball_norecord") -" call Dret("vimball#RmVimball : (g:vimball_norecord)") return endif if a:0 == 0 let curfile= expand("%:tr") -" call Decho("case a:0=0: curfile<".curfile."> (used expand(%:tr))") else if a:1 =~ '[\/]' call vimball#ShowMesg(s:USAGE,"RmVimball vimballname [path]") -" call Dret("vimball#RmVimball : suspect a:1<".a:1.">") return endif let curfile= a:1 -" call Decho("case a:0=".a:0.": curfile<".curfile.">") endif if curfile =~ '\.vmb$' let curfile= substitute(curfile,'\.vmb','','') @@ -428,75 +365,60 @@ fun! vimball#RmVimball(...) let home= vimball#VimballHome() endif let curdir = getcwd() -" call Decho("home <".home.">") -" call Decho("curfile<".curfile.">") -" call Decho("curdir <".curdir.">") call s:ChgDir(home) if filereadable(".VimballRecord") -" call Decho(".VimballRecord is readable") -" call Decho("curfile<".curfile.">") keepalt keepjumps 1split sil! keepalt keepjumps e .VimballRecord let keepsrch= @/ -" call Decho('search for ^\M'.curfile.'.\m: ') -" call Decho('search for ^\M'.curfile.'.\m{vba|vmb}: ') -" call Decho('search for ^\M'.curfile.'\m[-0-9.]*\.{vba|vmb}: ') if search('^\M'.curfile."\m: ".'cw') - let foundit= 1 + let foundit= 1 elseif search('^\M'.curfile.".\mvmb: ",'cw') - let foundit= 2 + let foundit= 2 elseif search('^\M'.curfile.'\m[-0-9.]*\.vmb: ','cw') - let foundit= 2 + let foundit= 2 elseif search('^\M'.curfile.".\mvba: ",'cw') - let foundit= 1 + let foundit= 1 elseif search('^\M'.curfile.'\m[-0-9.]*\.vba: ','cw') - let foundit= 1 + let foundit= 1 else let foundit = 0 endif if foundit - if foundit == 1 - let exestring = substitute(getline("."),'^\M'.curfile.'\m\S\{-}\.vba: ','','') - else - let exestring = substitute(getline("."),'^\M'.curfile.'\m\S\{-}\.vmb: ','','') - endif + if foundit == 1 + let exestring = substitute(getline("."),'^\M'.curfile.'\m\S\{-}\.vba: ','','') + else + let exestring = substitute(getline("."),'^\M'.curfile.'\m\S\{-}\.vmb: ','','') + endif let s:VBRstring= substitute(exestring,'call delete(','','g') let s:VBRstring= substitute(s:VBRstring,"[')]",'','g') -" call Decho("exe ".exestring) - sil! keepalt keepjumps exe exestring - sil! keepalt keepjumps d - let exestring= strlen(substitute(exestring,'call delete(.\{-})|\=',"D","g")) -" call Decho("exestring<".exestring.">") - echomsg "removed ".exestring." files" + sil! keepalt keepjumps exe exestring + sil! keepalt keepjumps d + let exestring= strlen(substitute(exestring,'call delete(.\{-})|\=',"D","g")) + echomsg "removed ".exestring." files" else let s:VBRstring= '' - let curfile = substitute(curfile,'\.vmb','','') -" call Decho("unable to find <".curfile."> in .VimballRecord") - if !exists("s:ok_unablefind") + let curfile = substitute(curfile,'\.vmb','','') + if !exists("s:ok_unablefind") call vimball#ShowMesg(s:WARNING,"(RmVimball) unable to find <".curfile."> in .VimballRecord") - endif + endif endif sil! keepalt keepjumps g/^\s*$/d sil! keepalt keepjumps wq! let @/= keepsrch endif call s:ChgDir(curdir) - -" call Dret("vimball#RmVimball") endfun " --------------------------------------------------------------------- " vimball#Decompress: attempts to automatically decompress vimballs {{{2 fun! vimball#Decompress(fname,...) -" call Dfunc("Decompress(fname<".a:fname.">) a:0=".a:0) - " decompression: if expand("%") =~ '.*\.gz' && executable("gunzip") " handle *.gz with gunzip silent exe "!gunzip ".shellescape(a:fname) if v:shell_error != 0 - call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) gunzip may have failed with <".a:fname.">") + call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) gunzip may have failed with <".a:fname.">") endif let fname= substitute(a:fname,'\.gz$','','') exe "e ".escape(fname,' \') @@ -506,7 +428,7 @@ fun! vimball#Decompress(fname,...) " handle *.gz with gzip -d silent exe "!gzip -d ".shellescape(a:fname) if v:shell_error != 0 - call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "gzip -d" may have failed with <'.a:fname.">") + call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "gzip -d" may have failed with <'.a:fname.">") endif let fname= substitute(a:fname,'\.gz$','','') exe "e ".escape(fname,' \') @@ -516,7 +438,7 @@ fun! vimball#Decompress(fname,...) " handle *.bz2 with bunzip2 silent exe "!bunzip2 ".shellescape(a:fname) if v:shell_error != 0 - call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) bunzip2 may have failed with <".a:fname.">") + call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) bunzip2 may have failed with <".a:fname.">") endif let fname= substitute(a:fname,'\.bz2$','','') exe "e ".escape(fname,' \') @@ -526,7 +448,7 @@ fun! vimball#Decompress(fname,...) " handle *.bz2 with bzip2 -d silent exe "!bzip2 -d ".shellescape(a:fname) if v:shell_error != 0 - call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "bzip2 -d" may have failed with <'.a:fname.">") + call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "bzip2 -d" may have failed with <'.a:fname.">") endif let fname= substitute(a:fname,'\.bz2$','','') exe "e ".escape(fname,' \') @@ -536,7 +458,7 @@ fun! vimball#Decompress(fname,...) " handle *.bz3 with bunzip3 silent exe "!bunzip3 ".shellescape(a:fname) if v:shell_error != 0 - call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) bunzip3 may have failed with <".a:fname.">") + call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) bunzip3 may have failed with <".a:fname.">") endif let fname= substitute(a:fname,'\.bz3$','','') exe "e ".escape(fname,' \') @@ -546,7 +468,7 @@ fun! vimball#Decompress(fname,...) " handle *.bz3 with bzip3 -d silent exe "!bzip3 -d ".shellescape(a:fname) if v:shell_error != 0 - call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "bzip3 -d" may have failed with <'.a:fname.">") + call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "bzip3 -d" may have failed with <'.a:fname.">") endif let fname= substitute(a:fname,'\.bz3$','','') exe "e ".escape(fname,' \') @@ -556,7 +478,7 @@ fun! vimball#Decompress(fname,...) " handle *.zip with unzip silent exe "!unzip ".shellescape(a:fname) if v:shell_error != 0 - call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) unzip may have failed with <".a:fname.">") + call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) unzip may have failed with <".a:fname.">") endif let fname= substitute(a:fname,'\.zip$','','') exe "e ".escape(fname,' \') @@ -564,14 +486,11 @@ fun! vimball#Decompress(fname,...) endif if a:0 == 0| setlocal noma bt=nofile fmr=[[[,]]] fdm=marker | endif - -" call Dret("Decompress") endfun " --------------------------------------------------------------------- " vimball#ShowMesg: {{{2 fun! vimball#ShowMesg(level,msg) -" call Dfunc("vimball#ShowMesg(level=".a:level." msg<".a:msg.">)") let rulerkeep = &ruler let showcmdkeep = &showcmd @@ -596,13 +515,10 @@ fun! vimball#ShowMesg(level,msg) let &ruler = rulerkeep let &showcmd = showcmdkeep - -" call Dret("vimball#ShowMesg") endfun " ===================================================================== " s:ChgDir: change directory (in spite of Windoze) {{{2 fun! s:ChgDir(newdir) -" call Dfunc("ChgDir(newdir<".a:newdir.">)") if (has("win32") || has("win95") || has("win64") || has("win16")) try exe 'silent cd '.fnameescape(substitute(a:newdir,'/','\\','g')) @@ -618,33 +534,22 @@ fun! s:ChgDir(newdir) exe 'silent cd '.fnameescape(a:newdir) endtry endif -" call Dret("ChgDir : curdir<".getcwd().">") endfun " --------------------------------------------------------------------- " s:RecordInVar: record a un-vimball command in the .VimballRecord file {{{2 fun! s:RecordInVar(home,cmd) -" call Dfunc("RecordInVar(home<".a:home."> cmd<".a:cmd.">)") - if a:cmd =~ '^rmdir' -" if !exists("s:recorddir") -" let s:recorddir= substitute(a:cmd,'^rmdir',"call s:Rmdir",'') -" else -" let s:recorddir= s:recorddir."|".substitute(a:cmd,'^rmdir',"call s:Rmdir",'') -" endif - elseif !exists("s:recordfile") + if !exists("s:recordfile") let s:recordfile= a:cmd else let s:recordfile= s:recordfile."|".a:cmd endif -" call Dret("RecordInVar : s:recordfile<".(exists("s:recordfile")? s:recordfile : "")."> s:recorddir<".(exists("s:recorddir")? s:recorddir : "").">") endfun " --------------------------------------------------------------------- " s:RecordInFile: {{{2 fun! s:RecordInFile(home) -" call Dfunc("s:RecordInFile()") if exists("g:vimball_norecord") -" call Dret("s:RecordInFile : g:vimball_norecord") return endif @@ -654,22 +559,19 @@ fun! s:RecordInFile(home) keepalt keepjumps 1split let cmd= expand("%:tr").": " -" call Decho("cmd<".cmd.">") sil! keepalt keepjumps e .VimballRecord setlocal ma $ if exists("s:recordfile") && exists("s:recorddir") - let cmd= cmd.s:recordfile."|".s:recorddir + let cmd= cmd.s:recordfile."|".s:recorddir elseif exists("s:recorddir") - let cmd= cmd.s:recorddir + let cmd= cmd.s:recorddir elseif exists("s:recordfile") - let cmd= cmd.s:recordfile + let cmd= cmd.s:recordfile else -" call Dret("s:RecordInFile : neither recordfile nor recorddir exist") - return + return endif -" call Decho("cmd<".cmd.">") " put command into buffer, write .VimballRecord `file keepalt keepjumps put=cmd @@ -678,35 +580,28 @@ fun! s:RecordInFile(home) call s:ChgDir(curdir) if exists("s:recorddir") -" call Decho("unlet s:recorddir<".s:recorddir.">") - unlet s:recorddir + unlet s:recorddir endif if exists("s:recordfile") -" call Decho("unlet s:recordfile<".s:recordfile.">") - unlet s:recordfile + unlet s:recordfile endif - else -" call Decho("s:record[file|dir] doesn't exist") endif - -" call Dret("s:RecordInFile") endfun " --------------------------------------------------------------------- " vimball#VimballHome: determine/get home directory path (usually from rtp) {{{2 fun! vimball#VimballHome() -" call Dfunc("vimball#VimballHome()") if exists("g:vimball_home") let home= g:vimball_home else " go to vim plugin home for home in split(&rtp,',') + [''] if isdirectory(home) && filewritable(home) | break | endif - let basehome= substitute(home,'[/\\]\.vim$','','') + let basehome= substitute(home,'[/\\]\.vim$','','') if isdirectory(basehome) && filewritable(basehome) - let home= basehome."/.vim" - break - endif + let home= basehome."/.vim" + break + endif endfor if home == "" " just pick the first directory @@ -717,18 +612,9 @@ fun! vimball#VimballHome() endif endif " insure that the home directory exists -" call Decho("picked home<".home.">") if !isdirectory(home) - if exists("g:vimball_mkdir") -" call Decho("home<".home."> isn't a directory -- making it now with g:vimball_mkdir<".g:vimball_mkdir.">") -" call Decho("system(".g:vimball_mkdir." ".shellescape(home).")") - call system(g:vimball_mkdir." ".shellescape(home)) - else -" call Decho("home<".home."> isn't a directory -- making it now with mkdir()") - call mkdir(home) - endif + call mkdir(home) endif -" call Dret("vimball#VimballHome <".home.">") return home endfun @@ -758,13 +644,11 @@ fun! vimball#SaveSettings() endif " vimballs should be in unix format setlocal ff=unix -" call Dret("SaveSettings") endfun " --------------------------------------------------------------------- " vimball#RestoreSettings: {{{2 fun! vimball#RestoreSettings() -" call Dfunc("RestoreSettings()") let @a = s:regakeep if exists("+acd") let &acd = s:acdkeep @@ -780,14 +664,12 @@ fun! vimball#RestoreSettings() let &l:ff = s:ffkeep if s:makeep[0] != 0 " restore mark a -" call Decho("restore mark-a: makeep=".string(makeep)) call setpos("'a",s:makeep) endif if exists("+acd") unlet s:acdkeep endif unlet s:regakeep s:eikeep s:fenkeep s:hidkeep s:ickeep s:repkeep s:vekeep s:makeep s:lzkeep s:pmkeep s:ffkeep -" call Dret("RestoreSettings") endfun let &cpo = s:keepcpo @@ -795,4 +677,4 @@ unlet s:keepcpo " --------------------------------------------------------------------- " Modelines: {{{1 -" vim: fdm=marker +" vim: fdm=marker et diff --git a/runtime/doc/pi_vimball.txt b/runtime/doc/pi_vimball.txt index fdf7173068..07fc68f7eb 100644 --- a/runtime/doc/pi_vimball.txt +++ b/runtime/doc/pi_vimball.txt @@ -1,4 +1,4 @@ -*pi_vimball.txt* For Vim version 9.2. Last change: 2026 Feb 14 +*pi_vimball.txt* For Vim version 9.2. Last change: 2026 Apr 05 ---------------- Vimball Archiver @@ -93,21 +93,6 @@ MAKING A VIMBALL *:MkVimball* make. -MAKING DIRECTORIES VIA VIMBALLS *g:vimball_mkdir* - - First, the |mkdir()| command is tried (not all systems support it). - - If it doesn't exist, then if g:vimball_mkdir doesn't exist, it is set - as follows: > - |g:netrw_localmkdir|, if it exists - "mkdir" , if it is executable - "makedir" , if it is executable - Otherwise , it is undefined. -< One may explicitly specify the directory making command using - g:vimball_mkdir. This command is used to make directories that - are needed as indicated by the vimball. - - CONTROLLING THE VIMBALL EXTRACTION DIRECTORY *g:vimball_home* You may override the use of the 'runtimepath' by specifying a diff --git a/runtime/doc/tags b/runtime/doc/tags index 2e9fc21ed4..c0b6ace109 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -8038,7 +8038,6 @@ g:vim_indent_cont indent.txt /*g:vim_indent_cont* g:vim_json_conceal syntax.txt /*g:vim_json_conceal* g:vim_json_warnings syntax.txt /*g:vim_json_warnings* g:vimball_home pi_vimball.txt /*g:vimball_home* -g:vimball_mkdir pi_vimball.txt /*g:vimball_mkdir* g:vimsyn_comment_strings syntax.txt /*g:vimsyn_comment_strings* g:vimsyn_embed syntax.txt /*g:vimsyn_embed* g:vimsyn_folding syntax.txt /*g:vimsyn_folding* diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak index 28a5ec663a..c0e4e68860 100644 --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -255,6 +255,7 @@ NEW_TESTS = \ test_plugin_termdebug \ test_plugin_tohtml \ test_plugin_tutor \ + test_plugin_vimball \ test_plugin_zip \ test_plus_arg_edit \ test_popup \ @@ -531,6 +532,7 @@ NEW_TESTS_RES = \ test_plugin_termdebug.res \ test_plugin_tohtml.res \ test_plugin_tutor.res \ + test_plugin_vimball.res \ test_plugin_zip.res \ test_plus_arg_edit.res \ test_popup.res \ diff --git a/src/testdir/test_plugin_vimball.vim b/src/testdir/test_plugin_vimball.vim new file mode 100644 index 0000000000..2d5b4ba768 --- /dev/null +++ b/src/testdir/test_plugin_vimball.vim @@ -0,0 +1,85 @@ +" Test for the vimball plugin + +let s:testdir = expand("