changeset 30150:0fe61fa4e5d1 v9.0.0411

patch 9.0.0411: only created files can be cleaned up with one call Commit: https://github.com/vim/vim/commit/6f14da15ac900589f2f413d77898b9bff3b31ece Author: Bram Moolenaar <Bram@vim.org> Date: Wed Sep 7 21:30:44 2022 +0100 patch 9.0.0411: only created files can be cleaned up with one call Problem: Only created files can be cleaned up with one call. Solution: Add flags to mkdir() to delete with a deferred function. Expand the writefile() name to a full path to handle changing directory.
author Bram Moolenaar <Bram@vim.org>
date Wed, 07 Sep 2022 22:45:04 +0200
parents 58dd17222e56
children c51f0fe92cf5
files runtime/doc/builtin.txt src/filepath.c src/proto/userfunc.pro src/testdir/test_autochdir.vim src/testdir/test_autocmd.vim src/testdir/test_eval_stuff.vim src/testdir/test_writefile.vim src/userfunc.c src/version.c
diffstat 9 files changed, 105 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -6239,8 +6239,26 @@ min({expr})	Return the minimum value of 
 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
--- a/src/filepath.c
+++ b/src/filepath.c
@@ -1428,10 +1428,12 @@ f_isabsolutepath(typval_T *argvars, typv
 /*
  * 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 *ret
     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 *ret
 
     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 *ret
 		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 
     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 
 
 	    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)
 	    {
--- 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);
--- 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()
--- 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()
index 3669cfd40b24aad092ae59b3cf49bd8aef15d5fb..0081d89a52a488d644f570d314bce2336251c0b0
GIT binary patch
literal 26019
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
--- 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
--- 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
--- 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,