diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 1de0e40ad1..12d62a6760 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -6239,8 +6239,26 @@ min({expr}) Return the minimum value of all items in {expr}. Example: > mkdir({name} [, {path} [, {prot}]]) Create directory {name}. - If {path} is "p" then intermediate directories are created as - necessary. Otherwise it must be "". + If {path} contains "p" then intermediate directories are + created as necessary. Otherwise it must be "". + + If {path} contains "D" then {name} is deleted at the end of + the current function, as with: > + defer delete({name}, 'd') +< + If {path} contains "R" then {name} is deleted recursively at + the end of the current function, as with: > + defer delete({name}, 'rf') +< Note that when {name} has more than one part and "p" is used + some directories may already exist. Only the first one that + is created and what it contains is scheduled to be deleted. + E.g. when using: > + call mkdir('subdir/tmp/autoload', 'pR') +< and "subdir" already exists then "subdir/tmp" will be + scheduled for deletion, like with: > + defer delete('subdir/tmp', 'rf') +< Note that if scheduling the defer fails the directory is not + deleted. This should only happen when out of memory. If {prot} is given it is used to set the protection bits of the new directory. The default is 0o755 (rwxr-xr-x: r/w for diff --git a/runtime/filetype.vim b/runtime/filetype.vim index c7670d08ca..e79bbba35e 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -940,7 +940,7 @@ au BufNewFile,BufRead *.java,*.jav setf java au BufNewFile,BufRead *.jj,*.jjt setf javacc " JavaScript, ECMAScript, ES module script, CommonJS script -au BufNewFile,BufRead *.js,*.javascript,*.es,*.mjs,*.cjs setf javascript +au BufNewFile,BufRead *.js,*.jsm,*.javascript,*.es,*.mjs,*.cjs setf javascript " JavaScript with React au BufNewFile,BufRead *.jsx setf javascriptreact diff --git a/src/charset.c b/src/charset.c index b26ebb96c7..d26a696c74 100644 --- a/src/charset.c +++ b/src/charset.c @@ -943,14 +943,13 @@ vim_isprintc_strict(int c) init_chartabsize_arg( chartabsize_T *cts, win_T *wp, - linenr_T lnum, + linenr_T lnum UNUSED, colnr_T col, char_u *line, char_u *ptr) { CLEAR_POINTER(cts); cts->cts_win = wp; - cts->cts_lnum = lnum; cts->cts_vcol = col; cts->cts_line = line; cts->cts_ptr = ptr; diff --git a/src/dict.c b/src/dict.c index 874b8231da..fa54c60c41 100644 --- a/src/dict.c +++ b/src/dict.c @@ -911,13 +911,15 @@ eval_dict(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int literal) int vim9script = in_vim9script(); int had_comma; - // First check if it's not a curly-braces thing: {expr}. + // First check if it's not a curly-braces expression: {expr}. // Must do this without evaluating, otherwise a function may be called // twice. Unfortunately this means we need to call eval1() twice for the // first item. - // But {} is an empty Dictionary. + // "{}" is an empty Dictionary. + // "#{abc}" is never a curly-braces expression. if (!vim9script && *curly_expr != '}' + && !literal && eval1(&curly_expr, &tv, NULL) == OK && *skipwhite(curly_expr) == '}') return NOTDONE; diff --git a/src/eval.c b/src/eval.c index 5ec5b8ae7b..9891473813 100644 --- a/src/eval.c +++ b/src/eval.c @@ -263,9 +263,17 @@ eval_expr_typval(typval_T *expr, typval_T *argv, int argc, typval_T *rettv) if (partial->pt_func != NULL && partial->pt_func->uf_def_status != UF_NOT_COMPILED) { - // FIXME: should create a funccal and link it in current_funccal. - if (call_def_function(partial->pt_func, argc, argv, - partial, NULL, rettv) == FAIL) + funccall_T *fc = create_funccal(partial->pt_func, rettv); + int r; + + if (fc == NULL) + return FAIL; + + // Shortcut to call a compiled function without overhead. + r = call_def_function(partial->pt_func, argc, argv, + DEF_USE_PT_ARGV, partial, fc, rettv); + remove_funccal(); + if (r == FAIL) return FAIL; } else diff --git a/src/filepath.c b/src/filepath.c index 3094d91270..43216bea23 100644 --- a/src/filepath.c +++ b/src/filepath.c @@ -1428,10 +1428,12 @@ f_isabsolutepath(typval_T *argvars, typval_T *rettv) /* * Create the directory in which "dir" is located, and higher levels when * needed. + * Set "created" to the full name of the first created directory. It will be + * NULL until that happens. * Return OK or FAIL. */ static int -mkdir_recurse(char_u *dir, int prot) +mkdir_recurse(char_u *dir, int prot, char_u **created) { char_u *p; char_u *updir; @@ -1449,8 +1451,12 @@ mkdir_recurse(char_u *dir, int prot) return FAIL; if (mch_isdir(updir)) r = OK; - else if (mkdir_recurse(updir, prot) == OK) + else if (mkdir_recurse(updir, prot, created) == OK) + { r = vim_mkdir_emsg(updir, prot); + if (r == OK && created != NULL && *created == NULL) + *created = FullName_save(updir, FALSE); + } vim_free(updir); return r; } @@ -1464,6 +1470,9 @@ f_mkdir(typval_T *argvars, typval_T *rettv) char_u *dir; char_u buf[NUMBUFLEN]; int prot = 0755; + int defer = FALSE; + int defer_recurse = FALSE; + char_u *created = NULL; rettv->vval.v_number = FAIL; if (check_restricted() || check_secure()) @@ -1486,13 +1495,21 @@ f_mkdir(typval_T *argvars, typval_T *rettv) if (argvars[1].v_type != VAR_UNKNOWN) { + char_u *arg2; + if (argvars[2].v_type != VAR_UNKNOWN) { prot = (int)tv_get_number_chk(&argvars[2], NULL); if (prot == -1) return; } - if (STRCMP(tv_get_string(&argvars[1]), "p") == 0) + arg2 = tv_get_string(&argvars[1]); + defer = vim_strchr(arg2, 'D') != NULL; + defer_recurse = vim_strchr(arg2, 'R') != NULL; + if ((defer || defer_recurse) && !can_add_defer()) + return; + + if (vim_strchr(arg2, 'p') != NULL) { if (mch_isdir(dir)) { @@ -1500,10 +1517,33 @@ f_mkdir(typval_T *argvars, typval_T *rettv) rettv->vval.v_number = OK; return; } - mkdir_recurse(dir, prot); + mkdir_recurse(dir, prot, defer || defer_recurse ? &created : NULL); } } rettv->vval.v_number = vim_mkdir_emsg(dir, prot); + + // Handle "D" and "R": deferred deletion of the created directory. + if (rettv->vval.v_number == OK + && created == NULL && (defer || defer_recurse)) + created = FullName_save(dir, FALSE); + if (created != NULL) + { + typval_T tv[2]; + + tv[0].v_type = VAR_STRING; + tv[0].v_lock = 0; + tv[0].vval.v_string = created; + tv[1].v_type = VAR_STRING; + tv[1].v_lock = 0; + tv[1].vval.v_string = vim_strsave( + (char_u *)(defer_recurse ? "rf" : "d")); + if (tv[0].vval.v_string == NULL || tv[1].vval.v_string == NULL + || add_defer((char_u *)"delete", 2, tv) == FAIL) + { + vim_free(tv[0].vval.v_string); + vim_free(tv[1].vval.v_string); + } + } } /* @@ -2300,11 +2340,8 @@ f_writefile(typval_T *argvars, typval_T *rettv) if (fname == NULL) return; - if (defer && !in_def_function() && get_current_funccal() == NULL) - { - semsg(_(e_str_not_inside_function), "defer"); + if (defer && !can_add_defer()) return; - } // Always open the file in binary mode, library functions have a mind of // their own about CR-LF conversion. @@ -2323,7 +2360,7 @@ f_writefile(typval_T *argvars, typval_T *rettv) tv.v_type = VAR_STRING; tv.v_lock = 0; - tv.vval.v_string = vim_strsave(fname); + tv.vval.v_string = FullName_save(fname, FALSE); if (tv.vval.v_string == NULL || add_defer((char_u *)"delete", 1, &tv) == FAIL) { diff --git a/src/gui_w32.c b/src/gui_w32.c index 20b0a5f64e..8ae0cd069b 100644 --- a/src/gui_w32.c +++ b/src/gui_w32.c @@ -8649,6 +8649,7 @@ test_gui_w32_sendevent(dict_T *args) inputs[0].ki.wVk = vkCode; if (STRICMP(event, "keyup") == 0) inputs[0].ki.dwFlags = KEYEVENTF_KEYUP; + (void)SetForegroundWindow(s_hwnd); SendInput(ARRAYSIZE(inputs), inputs, sizeof(INPUT)); } else diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro index 4fbbe86c99..e5543f389d 100644 --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -21,6 +21,8 @@ int funcdepth_increment(void); void funcdepth_decrement(void); int funcdepth_get(void); void funcdepth_restore(int depth); +funccall_T *create_funccal(ufunc_T *fp, typval_T *rettv); +void remove_funccal(void); int check_user_func_argcount(ufunc_T *fp, int argcount); int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv, funcexe_T *funcexe, dict_T *selfdict); void save_funccal(funccal_entry_T *entry); @@ -58,6 +60,7 @@ void func_ptr_unref(ufunc_T *fp); void func_ref(char_u *name); void func_ptr_ref(ufunc_T *fp); void ex_return(exarg_T *eap); +int can_add_defer(void); int add_defer(char_u *name, int argcount_arg, typval_T *argvars); void invoke_all_defer(void); void ex_call(exarg_T *eap); diff --git a/src/proto/vim9execute.pro b/src/proto/vim9execute.pro index bb3d414e7a..b8360c5e65 100644 --- a/src/proto/vim9execute.pro +++ b/src/proto/vim9execute.pro @@ -15,7 +15,7 @@ typval_T *lookup_debug_var(char_u *name); int may_break_in_function(ufunc_T *ufunc); int exe_typval_instr(typval_T *tv, typval_T *rettv); char_u *exe_substitute_instr(void); -int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, partial_T *partial, funccall_T *funccal, typval_T *rettv); +int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, int flags, partial_T *partial, funccall_T *funccal, typval_T *rettv); void unwind_def_callstack(ectx_T *ectx); void may_invoke_defer_funcs(ectx_T *ectx); void set_context_in_disassemble_cmd(expand_T *xp, char_u *arg); diff --git a/src/regexp_bt.c b/src/regexp_bt.c index cf484e0bab..e3e7a18016 100644 --- a/src/regexp_bt.c +++ b/src/regexp_bt.c @@ -3444,7 +3444,7 @@ regmatch( linenr_T lnum = rex.reg_firstlnum + rex.lnum; long_u vcol = 0; - if (lnum > 0 && lnum <= wp->w_buffer->b_ml.ml_line_count) + if (lnum >= 0 && lnum <= wp->w_buffer->b_ml.ml_line_count) vcol = (long_u)win_linetabsize(wp, lnum, rex.line, (colnr_T)(rex.input - rex.line)); if (!re_num_cmp(vcol + 1, scan)) diff --git a/src/regexp_nfa.c b/src/regexp_nfa.c index f8717f5b1a..b398489272 100644 --- a/src/regexp_nfa.c +++ b/src/regexp_nfa.c @@ -6778,7 +6778,7 @@ nfa_regmatch( linenr_T lnum = rex.reg_firstlnum + rex.lnum; long_u vcol = 0; - if (lnum > 0 + if (lnum >= 0 && lnum <= wp->w_buffer->b_ml.ml_line_count) vcol = (long_u)win_linetabsize(wp, lnum, rex.line, col); diff --git a/src/structs.h b/src/structs.h index 6dc456da24..dba772d3b2 100644 --- a/src/structs.h +++ b/src/structs.h @@ -4618,7 +4618,6 @@ typedef struct { // Argument for lbr_chartabsize(). typedef struct { win_T *cts_win; - linenr_T cts_lnum; // zero when not using text properties char_u *cts_line; // start of the line char_u *cts_ptr; // current position in line #ifdef FEAT_PROP_POPUP diff --git a/src/testdir/test_assert.vim b/src/testdir/test_assert.vim index 01e7718bb6..5d4abaa8a9 100644 --- a/src/testdir/test_assert.vim +++ b/src/testdir/test_assert.vim @@ -275,6 +275,21 @@ func Test_assert_fail_fails() endtry 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 @@ -289,8 +304,8 @@ func Test_assert_fail_fails() endtry 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 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) endfunc diff --git a/src/testdir/test_autochdir.vim b/src/testdir/test_autochdir.vim index 332de8f2bf..eb402539ff 100644 --- a/src/testdir/test_autochdir.vim +++ b/src/testdir/test_autochdir.vim @@ -28,9 +28,9 @@ endfunc func Test_set_filename_other_window() let cwd = getcwd() call test_autochdir() - call mkdir('Xa') - call mkdir('Xb') - call mkdir('Xc') + call mkdir('Xa', 'R') + call mkdir('Xb', 'R') + call mkdir('Xc', 'R') try args Xa/aaa.txt Xb/bbb.txt set acd @@ -45,9 +45,6 @@ func Test_set_filename_other_window() bwipe! aaa.txt bwipe! bbb.txt bwipe! ccc.txt - call delete('Xa', 'rf') - call delete('Xb', 'rf') - call delete('Xc', 'rf') endtry endfunc @@ -56,7 +53,7 @@ func Test_acd_win_execute() set acd call test_autochdir() - call mkdir('XacdDir') + call mkdir('XacdDir', 'R') let winid = win_getid() new XacdDir/file call assert_match('testdir.XacdDir$', getcwd()) @@ -68,7 +65,6 @@ func Test_acd_win_execute() bwipe! set noacd call chdir(cwd) - call delete('XacdDir', 'rf') endfunc func Test_verbose_pwd() @@ -78,7 +74,7 @@ func Test_verbose_pwd() edit global.txt call assert_match('\[global\].*testdir$', execute('verbose pwd')) - call mkdir('Xautodir') + call mkdir('Xautodir', 'R') split Xautodir/local.txt lcd Xautodir call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd')) @@ -112,7 +108,6 @@ func Test_verbose_pwd() bwipe! call chdir(cwd) - call delete('Xautodir', 'rf') endfunc func Test_multibyte() diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim index 532f7b537b..d1bd661206 100644 --- a/src/testdir/test_autocmd.vim +++ b/src/testdir/test_autocmd.vim @@ -707,14 +707,13 @@ func Test_BufEnter() call assert_equal('++', g:val) " Also get BufEnter when editing a directory - call mkdir('Xbufenterdir') + call mkdir('Xbufenterdir', 'D') split Xbufenterdir call assert_equal('+++', g:val) " On MS-Windows we can't edit the directory, make sure we wipe the right " buffer. bwipe! Xbufenterdir - call delete('Xbufenterdir', 'd') au! BufEnter " Editing a "nofile" buffer doesn't read the file but does trigger BufEnter @@ -1902,11 +1901,10 @@ func Test_BufWriteCmd() new file Xbufwritecmd set buftype=acwrite - call mkdir('Xbufwritecmd') + call mkdir('Xbufwritecmd', 'D') write " BufWriteCmd should be triggered even if a directory has the same name call assert_equal(1, g:written) - call delete('Xbufwritecmd', 'd') unlet g:written au! BufWriteCmd bwipe! @@ -2710,7 +2708,7 @@ func Test_throw_in_BufWritePre() endfunc func Test_autocmd_in_try_block() - call mkdir('Xintrydir') + call mkdir('Xintrydir', 'R') au BufEnter * let g:fname = expand('%') try edit Xintrydir/ @@ -2719,7 +2717,6 @@ func Test_autocmd_in_try_block() unlet g:fname au! BufEnter - call delete('Xintrydir', 'rf') endfunc func Test_autocmd_SafeState() diff --git a/src/testdir/test_eval_stuff.vim b/src/testdir/test_eval_stuff.vim index 3669cfd40b..0081d89a52 100644 --- a/src/testdir/test_eval_stuff.vim +++ b/src/testdir/test_eval_stuff.vim @@ -44,6 +44,64 @@ func Test_mkdir_p() call assert_fails('call mkdir("abc", [], [])', 'E745:') endfunc +func DoMkdirDel(name) + call mkdir(a:name, 'pD') + call assert_true(isdirectory(a:name)) +endfunc + +func DoMkdirDelAddFile(name) + call mkdir(a:name, 'pD') + call assert_true(isdirectory(a:name)) + call writefile(['text'], a:name .. '/file') +endfunc + +func DoMkdirDelRec(name) + call mkdir(a:name, 'pR') + call assert_true(isdirectory(a:name)) +endfunc + +func DoMkdirDelRecAddFile(name) + call mkdir(a:name, 'pR') + call assert_true(isdirectory(a:name)) + call writefile(['text'], a:name .. '/file') +endfunc + +func Test_mkdir_defer_del() + " Xtopdir/tmp is created thus deleted, not Xtopdir itself + call mkdir('Xtopdir', 'R') + call DoMkdirDel('Xtopdir/tmp') + call assert_true(isdirectory('Xtopdir')) + call assert_false(isdirectory('Xtopdir/tmp')) + + " Deletion fails because "tmp" contains "sub" + call DoMkdirDel('Xtopdir/tmp/sub') + call assert_true(isdirectory('Xtopdir')) + call assert_true(isdirectory('Xtopdir/tmp')) + call delete('Xtopdir/tmp', 'rf') + + " Deletion fails because "tmp" contains "file" + call DoMkdirDelAddFile('Xtopdir/tmp') + call assert_true(isdirectory('Xtopdir')) + call assert_true(isdirectory('Xtopdir/tmp')) + call assert_true(filereadable('Xtopdir/tmp/file')) + call delete('Xtopdir/tmp', 'rf') + + " Xtopdir/tmp is created thus deleted, not Xtopdir itself + call DoMkdirDelRec('Xtopdir/tmp') + call assert_true(isdirectory('Xtopdir')) + call assert_false(isdirectory('Xtopdir/tmp')) + + " Deletion works even though "tmp" contains "sub" + call DoMkdirDelRec('Xtopdir/tmp/sub') + call assert_true(isdirectory('Xtopdir')) + call assert_false(isdirectory('Xtopdir/tmp')) + + " Deletion works even though "tmp" contains "file" + call DoMkdirDelRecAddFile('Xtopdir/tmp') + call assert_true(isdirectory('Xtopdir')) + call assert_false(isdirectory('Xtopdir/tmp')) +endfunc + func Test_line_continuation() let array = [5, "\ ignore this diff --git a/src/testdir/test_filetype.vim b/src/testdir/test_filetype.vim index 551602fbe1..9d2677c7fc 100644 --- a/src/testdir/test_filetype.vim +++ b/src/testdir/test_filetype.vim @@ -275,7 +275,7 @@ let s:filename_checks = { \ 'jam': ['file.jpl', 'file.jpr', 'JAM-file.file', 'JAM.file', 'Prl-file.file', 'Prl.file'], \ 'java': ['file.java', 'file.jav'], \ 'javacc': ['file.jj', 'file.jjt'], - \ 'javascript': ['file.js', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs'], + \ 'javascript': ['file.js', 'file.jsm', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs'], \ 'javascript.glimmer': ['file.gjs'], \ 'javascriptreact': ['file.jsx'], \ 'jess': ['file.clp'], diff --git a/src/testdir/test_gui.vim b/src/testdir/test_gui.vim index c2b0815e89..d196c8edf2 100644 --- a/src/testdir/test_gui.vim +++ b/src/testdir/test_gui.vim @@ -1614,12 +1614,15 @@ func Test_gui_CTRL_SHIFT_V() endfunc func Test_gui_dialog_file() + " make sure the file does not exist, otherwise a dialog makes Vim hang + call delete('Xdialfile') + let lines =<< trim END file Xdialfile normal axxx confirm qa END - call writefile(lines, 'Xlines') + call writefile(lines, 'Xlines', 'D') let prefix = '!' if has('win32') let prefix = '!start ' @@ -1636,7 +1639,6 @@ func Test_gui_dialog_file() call delete('Xdialog') call delete('Xdialfile') - call delete('Xlines') endfunc " Test for sending low level key presses diff --git a/src/testdir/test_listdict.vim b/src/testdir/test_listdict.vim index bf3117bbbd..63ba47fabe 100644 --- a/src/testdir/test_listdict.vim +++ b/src/testdir/test_listdict.vim @@ -321,6 +321,10 @@ func Test_dict() " allow key starting with number at the start, not a curly expression call assert_equal({'1foo': 77}, #{1foo: 77}) + + " #{expr} is not a curly expression + let x = 'x' + call assert_equal(#{g: x}, #{g:x}) endfunc " This was allowed in legacy Vim script diff --git a/src/testdir/test_regexp_latin.vim b/src/testdir/test_regexp_latin.vim index bc08439d15..6f3ffe23ff 100644 --- a/src/testdir/test_regexp_latin.vim +++ b/src/testdir/test_regexp_latin.vim @@ -30,11 +30,13 @@ endfunc func Test_equivalence_re1() set re=1 call s:equivalence_test() + set re=0 endfunc func Test_equivalence_re2() set re=2 call s:equivalence_test() + set re=0 endfunc func Test_recursive_substitute() @@ -67,6 +69,7 @@ func Test_eow_with_optional() let actual = matchlist('abc def', '\(abc\>\)\?\s*\(def\)') call assert_equal(expected, actual) endfor + set re=0 endfunc func Test_backref() @@ -1141,4 +1144,14 @@ def Test_compare_columns() prop_type_delete('name') enddef +def Test_compare_column_matchstr() + enew + set re=1 + call assert_equal('aaa', matchstr('aaaaaaaaaaaaaaaaaaaa', '.*\%<5v')) + set re=2 + call assert_equal('aaa', matchstr('aaaaaaaaaaaaaaaaaaaa', '.*\%<5v')) + set re=0 +enddef + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_user_func.vim b/src/testdir/test_user_func.vim index ecf90b61ab..6b17534406 100644 --- a/src/testdir/test_user_func.vim +++ b/src/testdir/test_user_func.vim @@ -625,6 +625,29 @@ func Test_defer_quitall() call assert_false(filereadable('XQuitallTwo')) endfunc +func Test_defer_quitall_in_expr_func() + let lines =<< trim END + def DefIndex(idx: number, val: string): bool + call writefile([idx .. ': ' .. val], 'Xentry' .. idx, 'D') + if val == 'b' + qa! + endif + return val == 'c' + enddef + + def Test_defer_in_funcref() + assert_equal(2, indexof(['a', 'b', 'c'], funcref('g:DefIndex'))) + enddef + call Test_defer_in_funcref() + END + call writefile(lines, 'XdeferQuitallExpr', 'D') + let res = system(GetVimCommandClean() .. ' -X -S XdeferQuitallExpr') + call assert_equal(0, v:shell_error) + call assert_false(filereadable('Xentry0')) + call assert_false(filereadable('Xentry1')) + call assert_false(filereadable('Xentry2')) +endfunc + func FuncIndex(idx, val) call writefile([a:idx .. ': ' .. a:val], 'Xentry' .. a:idx, 'D') return a:val == 'c' @@ -635,6 +658,11 @@ def DefIndex(idx: number, val: string): bool return val == 'c' enddef +def DefIndexXtra(xtra: string, idx: number, val: string): bool + call writefile([idx .. ': ' .. val], 'Xentry' .. idx, 'D') + return val == 'c' +enddef + def Test_defer_in_funcref() assert_equal(2, indexof(['a', 'b', 'c'], function('g:FuncIndex'))) assert_false(filereadable('Xentry0')) @@ -655,6 +683,16 @@ def Test_defer_in_funcref() assert_false(filereadable('Xentry0')) assert_false(filereadable('Xentry1')) assert_false(filereadable('Xentry2')) + + assert_equal(2, indexof(['a', 'b', 'c'], function(g:DefIndexXtra, ['xtra']))) + assert_false(filereadable('Xentry0')) + assert_false(filereadable('Xentry1')) + assert_false(filereadable('Xentry2')) + + assert_equal(2, indexof(['a', 'b', 'c'], funcref(g:DefIndexXtra, ['xtra']))) + assert_false(filereadable('Xentry0')) + assert_false(filereadable('Xentry1')) + assert_false(filereadable('Xentry2')) enddef diff --git a/src/testdir/test_window_cmd.vim b/src/testdir/test_window_cmd.vim index 44fd646392..e38c8a8107 100644 --- a/src/testdir/test_window_cmd.vim +++ b/src/testdir/test_window_cmd.vim @@ -1617,4 +1617,19 @@ func Test_window_alloc_failure() tabonly endfunc +func Test_win_equal_last_status() + let save_lines = &lines + set lines=20 + set splitbelow + set laststatus=0 + + split | split | quit + call assert_equal(winheight(1), winheight(2)) + + let &lines = save_lines + set splitbelow& + set laststatus& +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/testdir/test_writefile.vim b/src/testdir/test_writefile.vim index dddc5c9539..f2b8aba57f 100644 --- a/src/testdir/test_writefile.vim +++ b/src/testdir/test_writefile.vim @@ -950,6 +950,19 @@ func Test_write_with_deferred_delete() call assert_equal('', glob('XdefdeferDelete')) endfunc +func DoWriteFile() + call writefile(['text'], 'Xthefile', 'D') + cd .. +endfunc + +func Test_write_defer_delete_chdir() + let dir = getcwd() + call DoWriteFile() + call assert_notequal(dir, getcwd()) + call chdir(dir) + call assert_equal('', glob('Xthefile')) +endfunc + " Check that buffer is written before triggering QuitPre func Test_wq_quitpre_autocommand() edit Xsomefile diff --git a/src/testing.c b/src/testing.c index 8c682cf6bc..fb5c9ab4cc 100644 --- a/src/testing.c +++ b/src/testing.c @@ -616,6 +616,11 @@ f_assert_fails(typval_T *argvars, typval_T *rettv) in_assert_fails = TRUE; 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); @@ -654,6 +659,8 @@ f_assert_fails(typval_T *argvars, typval_T *rettv) CHECK_LIST_MATERIALIZE(list); tv = &list->lv_first->li_tv; expected = tv_get_string_buf_chk(tv, buf); + if (expected == NULL) + goto theend; if (!pattern_match(expected, actual, FALSE)) { error_found = TRUE; @@ -667,6 +674,8 @@ f_assert_fails(typval_T *argvars, typval_T *rettv) { tv = &list->lv_u.mat.lv_last->li_tv; expected = tv_get_string_buf_chk(tv, buf); + if (expected == NULL) + goto theend; if (!pattern_match(expected, actual, FALSE)) { error_found = TRUE; diff --git a/src/userfunc.c b/src/userfunc.c index 76ab6b9d86..7b6034aaff 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -2580,6 +2580,40 @@ funcdepth_restore(int depth) funcdepth = depth; } +/* + * Allocate a funccall_T, link it in current_funccal and fill in "fp" and + * "rettv". + * Must be followed by one call to remove_funccal() or cleanup_function_call(). + * Returns NULL when allocation fails. + */ + funccall_T * +create_funccal(ufunc_T *fp, typval_T *rettv) +{ + funccall_T *fc = ALLOC_CLEAR_ONE(funccall_T); + + if (fc == NULL) + return NULL; + fc->fc_caller = current_funccal; + current_funccal = fc; + fc->fc_func = fp; + func_ptr_ref(fp); + fc->fc_rettv = rettv; + return fc; +} + +/* + * To be called when returning from a compiled function; restores + * current_funccal. + */ + void +remove_funccal() +{ + funccall_T *fc = current_funccal; + + current_funccal = fc->fc_caller; + free_funccal(fc); +} + /* * Call a user function. */ @@ -2627,20 +2661,15 @@ call_user_func( line_breakcheck(); // check for CTRL-C hit - fc = ALLOC_CLEAR_ONE(funccall_T); + fc = create_funccal(fp, rettv); if (fc == NULL) return; - fc->fc_caller = current_funccal; - current_funccal = fc; - fc->fc_func = fp; - fc->fc_rettv = rettv; fc->fc_level = ex_nesting_level; // Check if this function has a breakpoint. fc->fc_breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0); fc->fc_dbg_tick = debug_tick; // Set up fields for closure. ga_init2(&fc->fc_ufuncs, sizeof(ufunc_T *), 1); - func_ptr_ref(fp); if (fp->uf_def_status != UF_NOT_COMPILED) { @@ -2653,7 +2682,7 @@ call_user_func( profile_may_start_func(&profile_info, fp, caller); #endif sticky_cmdmod_flags = 0; - call_def_function(fp, argcount, argvars, funcexe->fe_partial, + call_def_function(fp, argcount, argvars, 0, funcexe->fe_partial, fc, rettv); funcdepth_decrement(); #ifdef FEAT_PROFILE @@ -2661,8 +2690,7 @@ call_user_func( || (caller != NULL && caller->uf_profiling))) profile_may_end_func(&profile_info, fp, caller); #endif - current_funccal = fc->fc_caller; - free_funccal(fc); + remove_funccal(); sticky_cmdmod_flags = save_sticky_cmdmod_flags; return; } @@ -5621,6 +5649,21 @@ ex_defer_inner( return add_defer(name, argcount, argvars); } +/* + * Return TRUE if currently inside a function call. + * Give an error message and return FALSE when not. + */ + int +can_add_defer(void) +{ + if (!in_def_function() && get_current_funccal() == NULL) + { + semsg(_(e_str_not_inside_function), "defer"); + return FALSE; + } + return TRUE; +} + /* * Add a deferred call for "name" with arguments "argvars[argcount]". * Consumes "argvars[]". diff --git a/src/version.c b/src/version.c index 8a58359823..8c58c4911d 100644 --- a/src/version.c +++ b/src/version.c @@ -718,6 +718,32 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 412, +/**/ + 411, +/**/ + 410, +/**/ + 409, +/**/ + 408, +/**/ + 407, +/**/ + 406, +/**/ + 405, +/**/ + 404, +/**/ + 403, +/**/ + 402, +/**/ + 401, +/**/ + 400, /**/ 399, /**/ diff --git a/src/vim9.h b/src/vim9.h index 22a3f3990e..9d72b96464 100644 --- a/src/vim9.h +++ b/src/vim9.h @@ -759,3 +759,5 @@ typedef enum { #define TVTT_DO_MEMBER 1 #define TVTT_MORE_SPECIFIC 2 // get most specific type for member +// flags for call_def_function() +#define DEF_USE_PT_ARGV 1 // use the partial arguments diff --git a/src/vim9execute.c b/src/vim9execute.c index 46251545ed..30ff7a7755 100644 --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -5272,16 +5272,21 @@ call_def_function( ufunc_T *ufunc, int argc_arg, // nr of arguments typval_T *argv, // arguments + int flags, // DEF_ flags partial_T *partial, // optional partial for context funccall_T *funccal, typval_T *rettv) // return value { ectx_T ectx; // execution context int argc = argc_arg; + int partial_argc = partial == NULL + || (flags & DEF_USE_PT_ARGV) == 0 + ? 0 : partial->pt_argc; + int total_argc = argc + partial_argc; typval_T *tv; int idx; int ret = FAIL; - int defcount = ufunc->uf_args.ga_len - argc; + int defcount = ufunc->uf_args.ga_len - total_argc; sctx_T save_current_sctx = current_sctx; int did_emsg_before = did_emsg_cumul + did_emsg; int save_suppress_errthrow = suppress_errthrow; @@ -5345,14 +5350,14 @@ call_def_function( ectx.ec_did_emsg_before = did_emsg_before; ++ex_nesting_level; - idx = argc - ufunc->uf_args.ga_len; + idx = total_argc - ufunc->uf_args.ga_len; if (idx > 0 && ufunc->uf_va_name == NULL) { semsg(NGETTEXT(e_one_argument_too_many, e_nr_arguments_too_many, - idx), idx); + idx), idx); goto failed_early; } - idx = argc - ufunc->uf_args.ga_len + ufunc->uf_def_args.ga_len; + idx = total_argc - ufunc->uf_args.ga_len + ufunc->uf_def_args.ga_len; if (idx < 0) { semsg(NGETTEXT(e_one_argument_too_few, e_nr_arguments_too_few, @@ -5360,15 +5365,19 @@ call_def_function( goto failed_early; } - // Put arguments on the stack, but no more than what the function expects. - // A lambda can be called with more arguments than it uses. - for (idx = 0; idx < argc + // Put values from the partial and arguments on the stack, but no more than + // what the function expects. A lambda can be called with more arguments + // than it uses. + for (idx = 0; idx < total_argc && (ufunc->uf_va_name != NULL || idx < ufunc->uf_args.ga_len); ++idx) { + int argv_idx = idx - partial_argc; + + tv = idx < partial_argc ? partial->pt_argv + idx : argv + argv_idx; if (idx >= ufunc->uf_args.ga_len - ufunc->uf_def_args.ga_len - && argv[idx].v_type == VAR_SPECIAL - && argv[idx].vval.v_number == VVAL_NONE) + && tv->v_type == VAR_SPECIAL + && tv->vval.v_number == VVAL_NONE) { // Use the default value. STACK_TV_BOT(0)->v_type = VAR_UNKNOWN; @@ -5377,10 +5386,10 @@ call_def_function( { if (ufunc->uf_arg_types != NULL && idx < ufunc->uf_args.ga_len && check_typval_arg_type( - ufunc->uf_arg_types[idx], &argv[idx], - NULL, idx + 1) == FAIL) + ufunc->uf_arg_types[idx], tv, + NULL, argv_idx + 1) == FAIL) goto failed_early; - copy_tv(&argv[idx], STACK_TV_BOT(0)); + copy_tv(tv, STACK_TV_BOT(0)); } ++ectx.ec_stack.ga_len; } diff --git a/src/window.c b/src/window.c index cb45f6d509..8daab2f5f0 100644 --- a/src/window.c +++ b/src/window.c @@ -2718,6 +2718,14 @@ win_close(win_T *win, int free_buf) // using the window. check_cursor(); } + + /* + * If last window has a status line now and we don't want one, remove the + * status line. Do this before win_equal(), because it may change the + * height of a window + */ + last_status(FALSE); + if (p_ea && (*p_ead == 'b' || *p_ead == dir)) // If the frame of the closed window contains the new current window, // only resize that frame. Otherwise resize all windows. @@ -2747,12 +2755,6 @@ win_close(win_T *win, int free_buf) --dont_parse_messages; #endif - /* - * If last window has a status line now and we don't want one, - * remove the status line. - */ - last_status(FALSE); - // After closing the help window, try restoring the window layout from // before it was opened. if (help_window)