diff options
-rw-r--r-- | runtime/doc/builtin.txt | 22 | ||||
-rw-r--r-- | src/filepath.c | 55 | ||||
-rw-r--r-- | src/proto/userfunc.pro | 1 | ||||
-rw-r--r-- | src/testdir/test_autochdir.vim | 15 | ||||
-rw-r--r-- | src/testdir/test_autocmd.vim | 9 | ||||
-rw-r--r-- | src/testdir/test_eval_stuff.vim | 58 | ||||
-rw-r--r-- | src/testdir/test_writefile.vim | 13 | ||||
-rw-r--r-- | src/userfunc.c | 15 | ||||
-rw-r--r-- | src/version.c | 2 |
9 files changed, 163 insertions, 27 deletions
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/src/filepath.c b/src/filepath.c index c72e4285f3..373a784f64 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/proto/userfunc.pro b/src/proto/userfunc.pro index 555830ef65..e5543f389d 100644 --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -60,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/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 028e3d22e7..669089df2d 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_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/userfunc.c b/src/userfunc.c index fea5b79803..7b6034aaff 100644 --- a/src/userfunc.c +++ b/src/userfunc.c @@ -5650,6 +5650,21 @@ ex_defer_inner( } /* + * 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[]". * Caller must check that in_def_function() returns TRUE or current_funccal is diff --git a/src/version.c b/src/version.c index 0d6cc89510..78a1ae0965 100644 --- a/src/version.c +++ b/src/version.c @@ -704,6 +704,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 411, +/**/ 410, /**/ 409, |