]> granicus.if.org Git - vim/commitdiff
patch 9.0.0411: only created files can be cleaned up with one call v9.0.0411
authorBram Moolenaar <Bram@vim.org>
Wed, 7 Sep 2022 20:30:44 +0000 (21:30 +0100)
committerBram Moolenaar <Bram@vim.org>
Wed, 7 Sep 2022 20:30:44 +0000 (21:30 +0100)
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.

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

index 1de0e40ad159a7c36413cdf6d91ef57366370b3f..12d62a6760a32db139929800da75011d64b3c148 100644 (file)
@@ -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
index c72e4285f360e88dbea03bc26f1fbce420222193..373a784f648789045cdc3bd4192e459aed0a8a3b 100644 (file)
@@ -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)
            {
index 555830ef65c0ac6d03b089098a8f05f4ac4442eb..e5543f389d39811caf603685b3f0aa386c096b2d 100644 (file)
@@ -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);
index 332de8f2bf7dbfeb0f4fec536b4b96ba0110833e..eb402539ffb265847783aaa8cf4a4361d6cd369f 100644 (file)
@@ -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()
index 028e3d22e70c9169bc026af5b4373f30b5eb85f3..669089df2d5937afa8c84ed8294b5f5e8497d20b 100644 (file)
@@ -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 100644 (file)
@@ -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
index dddc5c9539c15d74a957e2fbbc9740df666d7632..f2b8aba57fc29496ed0f2d388baea504f7719dbe 100644 (file)
@@ -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
index fea5b7980315535b9bcdb3cc113e76da44ff0ebc..7b6034aaff6dab60bfd0cfe00fa53b2bbb71e696 100644 (file)
@@ -5649,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[]".
index 0d6cc8951025f9f84210f36fbc07da66aef091f6..78a1ae0965ea2cbea9461e7903047e05bcaa08b2 100644 (file)
@@ -703,6 +703,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    411,
 /**/
     410,
 /**/