Patch 9.0.0411
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.
Files:      runtime/doc/builtin.txt, src/filepath.c, src/userfunc.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


*** ../vim-9.0.0410/runtime/doc/builtin.txt     2022-09-04 15:40:31.812188115 
+0100
--- runtime/doc/builtin.txt     2022-09-07 20:49:25.656972204 +0100
***************
*** 6229,6236 ****
  mkdir({name} [, {path} [, {prot}]])
                Create directory {name}.
  
!               If {path} is "p" then intermediate directories are created as
!               necessary.  Otherwise it must be "".
  
                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
--- 6239,6264 ----
  mkdir({name} [, {path} [, {prot}]])
                Create directory {name}.
  
!               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
*** ../vim-9.0.0410/src/filepath.c      2022-09-04 15:40:31.812188115 +0100
--- src/filepath.c      2022-09-07 20:51:23.008694815 +0100
***************
*** 1428,1437 ****
  /*
   * Create the directory in which "dir" is located, and higher levels when
   * needed.
   * Return OK or FAIL.
   */
      static int
! mkdir_recurse(char_u *dir, int prot)
  {
      char_u    *p;
      char_u    *updir;
--- 1428,1439 ----
  /*
   * 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, char_u **created)
  {
      char_u    *p;
      char_u    *updir;
***************
*** 1449,1456 ****
        return FAIL;
      if (mch_isdir(updir))
        r = OK;
!     else if (mkdir_recurse(updir, prot) == OK)
        r = vim_mkdir_emsg(updir, prot);
      vim_free(updir);
      return r;
  }
--- 1451,1462 ----
        return FAIL;
      if (mch_isdir(updir))
        r = 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,1469 ****
--- 1470,1478 ----
      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,1498 ****
  
      if (argvars[1].v_type != VAR_UNKNOWN)
      {
        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)
        {
            if (mch_isdir(dir))
            {
--- 1495,1515 ----
  
      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;
        }
!       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,1509 ****
                rettv->vval.v_number = OK;
                return;
            }
!           mkdir_recurse(dir, prot);
        }
      }
      rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
  }
  
  /*
--- 1517,1549 ----
                rettv->vval.v_number = OK;
                return;
            }
!           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,2310 ****
      if (fname == NULL)
        return;
  
!     if (defer && !in_def_function() && get_current_funccal() == NULL)
!     {
!       semsg(_(e_str_not_inside_function), "defer");
        return;
-     }
  
      // Always open the file in binary mode, library functions have a mind of
      // their own about CR-LF conversion.
--- 2340,2347 ----
      if (fname == NULL)
        return;
  
!     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,2329 ****
  
            tv.v_type = VAR_STRING;
            tv.v_lock = 0;
!           tv.vval.v_string = vim_strsave(fname);
            if (tv.vval.v_string == NULL
                    || add_defer((char_u *)"delete", 1, &tv) == FAIL)
            {
--- 2360,2366 ----
  
            tv.v_type = VAR_STRING;
            tv.v_lock = 0;
!           tv.vval.v_string = FullName_save(fname, FALSE);
            if (tv.vval.v_string == NULL
                    || add_defer((char_u *)"delete", 1, &tv) == FAIL)
            {
*** ../vim-9.0.0410/src/userfunc.c      2022-09-07 17:28:05.849865176 +0100
--- src/userfunc.c      2022-09-07 20:43:14.877880964 +0100
***************
*** 5650,5655 ****
--- 5650,5670 ----
  }
  
  /*
+  * 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
*** ../vim-9.0.0410/src/proto/userfunc.pro      2022-09-07 17:28:05.849865176 
+0100
--- src/proto/userfunc.pro      2022-09-07 20:45:11.961587496 +0100
***************
*** 60,65 ****
--- 60,66 ----
  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);
*** ../vim-9.0.0410/src/testdir/test_autochdir.vim      2022-08-29 
11:02:55.227279554 +0100
--- src/testdir/test_autochdir.vim      2022-09-07 20:59:10.559616186 +0100
***************
*** 28,36 ****
  func Test_set_filename_other_window()
    let cwd = getcwd()
    call test_autochdir()
!   call mkdir('Xa')
!   call mkdir('Xb')
!   call mkdir('Xc')
    try
      args Xa/aaa.txt Xb/bbb.txt
      set acd
--- 28,36 ----
  func Test_set_filename_other_window()
    let cwd = getcwd()
    call test_autochdir()
!   call mkdir('Xa', 'R')
!   call mkdir('Xb', 'R')
!   call mkdir('Xc', 'R')
    try
      args Xa/aaa.txt Xb/bbb.txt
      set acd
***************
*** 45,53 ****
      bwipe! aaa.txt
      bwipe! bbb.txt
      bwipe! ccc.txt
-     call delete('Xa', 'rf')
-     call delete('Xb', 'rf')
-     call delete('Xc', 'rf')
    endtry
  endfunc
  
--- 45,50 ----
***************
*** 56,62 ****
    set acd
    call test_autochdir()
  
!   call mkdir('XacdDir')
    let winid = win_getid()
    new XacdDir/file
    call assert_match('testdir.XacdDir$', getcwd())
--- 53,59 ----
    set acd
    call test_autochdir()
  
!   call mkdir('XacdDir', 'R')
    let winid = win_getid()
    new XacdDir/file
    call assert_match('testdir.XacdDir$', getcwd())
***************
*** 68,74 ****
    bwipe!
    set noacd
    call chdir(cwd)
-   call delete('XacdDir', 'rf')
  endfunc
  
  func Test_verbose_pwd()
--- 65,70 ----
***************
*** 78,84 ****
    edit global.txt
    call assert_match('\[global\].*testdir$', execute('verbose pwd'))
  
!   call mkdir('Xautodir')
    split Xautodir/local.txt
    lcd Xautodir
    call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose 
pwd'))
--- 74,80 ----
    edit global.txt
    call assert_match('\[global\].*testdir$', execute('verbose pwd'))
  
!   call mkdir('Xautodir', 'R')
    split Xautodir/local.txt
    lcd Xautodir
    call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose 
pwd'))
***************
*** 112,118 ****
  
    bwipe!
    call chdir(cwd)
-   call delete('Xautodir', 'rf')
  endfunc
  
  func Test_multibyte()
--- 108,113 ----
*** ../vim-9.0.0410/src/testdir/test_autocmd.vim        2022-09-04 
21:28:15.192614789 +0100
--- src/testdir/test_autocmd.vim        2022-09-07 21:01:38.499280388 +0100
***************
*** 707,720 ****
    call assert_equal('++', g:val)
  
    " Also get BufEnter when editing a directory
!   call mkdir('Xbufenterdir')
    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
--- 707,719 ----
    call assert_equal('++', g:val)
  
    " Also get BufEnter when editing a directory
!   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
    au! BufEnter
  
    " Editing a "nofile" buffer doesn't read the file but does trigger BufEnter
***************
*** 1902,1912 ****
    new
    file Xbufwritecmd
    set buftype=acwrite
!   call mkdir('Xbufwritecmd')
    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!
--- 1901,1910 ----
    new
    file Xbufwritecmd
    set buftype=acwrite
!   call mkdir('Xbufwritecmd', 'D')
    write
    " BufWriteCmd should be triggered even if a directory has the same name
    call assert_equal(1, g:written)
    unlet g:written
    au! BufWriteCmd
    bwipe!
***************
*** 2710,2716 ****
  endfunc
  
  func Test_autocmd_in_try_block()
!   call mkdir('Xintrydir')
    au BufEnter * let g:fname = expand('%')
    try
      edit Xintrydir/
--- 2708,2714 ----
  endfunc
  
  func Test_autocmd_in_try_block()
!   call mkdir('Xintrydir', 'R')
    au BufEnter * let g:fname = expand('%')
    try
      edit Xintrydir/
***************
*** 2719,2725 ****
  
    unlet g:fname
    au! BufEnter
-   call delete('Xintrydir', 'rf')
  endfunc
  
  func Test_autocmd_SafeState()
--- 2717,2722 ----
*** ../vim-9.0.0410/src/testdir/test_eval_stuff.vim     2022-09-02 
21:55:45.499049444 +0100
--- src/testdir/test_eval_stuff.vim     2022-09-07 21:25:00.072137930 +0100
***************
*** 44,49 ****
--- 44,107 ----
    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
*** ../vim-9.0.0410/src/testdir/test_writefile.vim      2022-09-04 
21:29:41.556536007 +0100
--- src/testdir/test_writefile.vim      2022-09-07 21:10:21.038104305 +0100
***************
*** 950,955 ****
--- 950,968 ----
    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
*** ../vim-9.0.0410/src/version.c       2022-09-07 20:01:13.323141838 +0100
--- src/version.c       2022-09-07 21:28:56.075611230 +0100
***************
*** 705,706 ****
--- 705,708 ----
  {   /* Add new patch number below this line */
+ /**/
+     411,
  /**/

-- 
How To Keep A Healthy Level Of Insanity:
1. At lunch time, sit in your parked car with sunglasses on and point
   a hair dryer at passing cars. See if they slow down.

 /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/20220907203344.D776C1C04B1%40moolenaar.net.

Raspunde prin e-mail lui