Patch 9.0.0379
Problem:    Cleaning up after writefile() is a hassle.
Solution:   Add the 'D' flag to defer deleting the written file.  Very useful
            in tests.
Files:      runtime/doc/builtin.txt, src/filepath.c, src/userfunc.c,
            src/proto/userfunc.pro, src/vim9execute.c,
            src/proto/vim9execute.pro, src/vim9expr.c, src/vim9cmds.c,
            src/proto/vim9cmds.pro, src/testdir/test_writefile.vim


*** ../vim-9.0.0378/runtime/doc/builtin.txt     2022-08-28 18:52:06.667888932 
+0100
--- runtime/doc/builtin.txt     2022-09-04 14:07:13.182053418 +0100
***************
*** 10437,10470 ****
                When {object} is a |List| write it to file {fname}.  Each list
                item is separated with a NL.  Each list item must be a String
                or Number.
!               When {flags} contains "b" then binary mode is used: There will
!               not be a NL after the last list item.  An empty item at the
!               end does cause the last line in the file to end in a NL.
  
                When {object} is a |Blob| write the bytes to file {fname}
!               unmodified.
  
!               When {flags} contains "a" then append mode is used, lines are
!               appended to the file: >
                        :call writefile(["foo"], "event.log", "a")
                        :call writefile(["bar"], "event.log", "a")
  <
!               When {flags} contains "s" then fsync() is called after writing
!               the file.  This flushes the file to disk, if possible.  This
!               takes more time but avoids losing the file if the system
!               crashes.
!               When {flags} does not contain "S" or "s" then fsync() is
!               called if the 'fsync' option is set.
!               When {flags} contains "S" then fsync() is not called, even
!               when 'fsync' is set.
  
-               All NL characters are replaced with a NUL character.
-               Inserting CR characters needs to be done before passing {list}
-               to writefile().
                An existing file is overwritten, if possible.
                When the write fails -1 is returned, otherwise 0.  There is an
                error message if the file can't be created or when writing
                fails.
                Also see |readfile()|.
                To copy a file byte for byte: >
                        :let fl = readfile("foo", "b")
--- 10449,10491 ----
                When {object} is a |List| write it to file {fname}.  Each list
                item is separated with a NL.  Each list item must be a String
                or Number.
!               All NL characters are replaced with a NUL character.
!               Inserting CR characters needs to be done before passing {list}
!               to writefile().
  
                When {object} is a |Blob| write the bytes to file {fname}
!               unmodified, also when binary mode is not specified.
! 
!               {flags} must be a String.  These characters are recognized:
  
!               'b'  Binary mode is used: There will not be a NL after the
!                    last list item.  An empty item at the end does cause the
!                    last line in the file to end in a NL.
! 
!               'a'  Append mode is used, lines are appended to the file: >
                        :call writefile(["foo"], "event.log", "a")
                        :call writefile(["bar"], "event.log", "a")
  <
!               'D'  Delete the file when the current function ends.  This
!                    works like: >
!                       :defer delete({fname})
! <                  Fails when not in a function.  Also see |:defer|.
! 
!               's'  fsync() is called after writing the file.  This flushes
!                    the file to disk, if possible.  This takes more time but
!                    avoids losing the file if the system crashes.
! 
!               'S'  fsync() is not called, even when 'fsync' is set.
! 
!                    When {flags} does not contain "S" or "s" then fsync() is
!                    called if the 'fsync' option is set.
  
                An existing file is overwritten, if possible.
+ 
                When the write fails -1 is returned, otherwise 0.  There is an
                error message if the file can't be created or when writing
                fails.
+ 
                Also see |readfile()|.
                To copy a file byte for byte: >
                        :let fl = readfile("foo", "b")
*** ../vim-9.0.0378/src/filepath.c      2022-09-02 19:45:12.215205522 +0100
--- src/filepath.c      2022-09-04 15:09:27.530229465 +0100
***************
*** 2232,2237 ****
--- 2232,2238 ----
  {
      int               binary = FALSE;
      int               append = FALSE;
+     int               defer = FALSE;
  #ifdef HAVE_FSYNC
      int               do_fsync = p_fs;
  #endif
***************
*** 2285,2290 ****
--- 2286,2293 ----
            binary = TRUE;
        if (vim_strchr(arg2, 'a') != NULL)
            append = TRUE;
+       if (vim_strchr(arg2, 'D') != NULL)
+           defer = TRUE;
  #ifdef HAVE_FSYNC
        if (vim_strchr(arg2, 's') != NULL)
            do_fsync = TRUE;
***************
*** 2297,2333 ****
      if (fname == NULL)
        return;
  
      // Always open the file in binary mode, library functions have a mind of
      // their own about CR-LF conversion.
      if (*fname == NUL || (fd = mch_fopen((char *)fname,
                                      append ? APPENDBIN : WRITEBIN)) == NULL)
      {
!       semsg(_(e_cant_create_file_str), *fname == NUL ? (char_u *)_("<empty>") 
: fname);
        ret = -1;
      }
-     else if (blob)
-     {
-       if (write_blob(fd, blob) == FAIL)
-           ret = -1;
- #ifdef HAVE_FSYNC
-       else if (do_fsync)
-           // Ignore the error, the user wouldn't know what to do about it.
-           // May happen for a device.
-           vim_ignored = vim_fsync(fileno(fd));
- #endif
-       fclose(fd);
-     }
      else
      {
!       if (write_list(fd, list, binary) == FAIL)
!           ret = -1;
  #ifdef HAVE_FSYNC
!       else if (do_fsync)
!           // Ignore the error, the user wouldn't know what to do about it.
!           // May happen for a device.
!           vim_ignored = vim_fsync(fileno(fd));
  #endif
!       fclose(fd);
      }
  
      rettv->vval.v_number = ret;
--- 2300,2358 ----
      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.
      if (*fname == NUL || (fd = mch_fopen((char *)fname,
                                      append ? APPENDBIN : WRITEBIN)) == NULL)
      {
!       semsg(_(e_cant_create_file_str),
!                              *fname == NUL ? (char_u *)_("<empty>") : fname);
        ret = -1;
      }
      else
      {
!       if (defer)
!       {
!           typval_T tv;
! 
!           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)
!           {
!               ret = -1;
!               fclose(fd);
!               (void)mch_remove(fname);
!           }
!       }
! 
!       if (ret == 0)
!       {
!           if (blob)
!           {
!               if (write_blob(fd, blob) == FAIL)
!                   ret = -1;
!           }
!           else
!           {
!               if (write_list(fd, list, binary) == FAIL)
!                   ret = -1;
!           }
  #ifdef HAVE_FSYNC
!           if (ret == 0 && do_fsync)
!               // Ignore the error, the user wouldn't know what to do about
!               // it.  May happen for a device.
!               vim_ignored = vim_fsync(fileno(fd));
  #endif
!           fclose(fd);
!       }
      }
  
      rettv->vval.v_number = ret;
*** ../vim-9.0.0378/src/userfunc.c      2022-09-04 13:41:31.183585396 +0100
--- src/userfunc.c      2022-09-04 15:13:05.557815371 +0100
***************
*** 1728,1733 ****
--- 1728,1734 ----
  /*
   * Get function arguments at "*arg" and advance it.
   * Return them in "*argvars[MAX_FUNC_ARGS + 1]" and the count in "argcount".
+  * On failure FAIL is returned but the "argvars[argcount]" are still set.
   */
      static int
  get_func_arguments(
***************
*** 5570,5578 ****
  {
      typval_T  argvars[MAX_FUNC_ARGS + 1];     // vars for arguments
      int               argcount = 0;                   // number of arguments 
found
-     defer_T   *dr;
-     int               ret = FAIL;
-     char_u    *saved_name;
  
      if (current_funccal == NULL)
      {
--- 5571,5576 ----
***************
*** 5580,5602 ****
        return FAIL;
      }
      if (get_func_arguments(arg, evalarg, FALSE, argvars, &argcount) == FAIL)
!       goto theend;
!     saved_name = vim_strsave(name);
!     if (saved_name == NULL)
!       goto theend;
  
!     if (current_funccal->fc_defer.ga_itemsize == 0)
!       ga_init2(&current_funccal->fc_defer, sizeof(defer_T), 10);
!     if (ga_grow(&current_funccal->fc_defer, 1) == FAIL)
        goto theend;
!     dr = ((defer_T *)current_funccal->fc_defer.ga_data)
!                                         + current_funccal->fc_defer.ga_len++;
!     dr->dr_name = saved_name;
!     dr->dr_argcount = argcount;
!     while (argcount > 0)
      {
!       --argcount;
!       dr->dr_argvars[argcount] = argvars[argcount];
      }
      ret = OK;
  
--- 5578,5628 ----
        return FAIL;
      }
      if (get_func_arguments(arg, evalarg, FALSE, argvars, &argcount) == FAIL)
!     {
!       while (--argcount >= 0)
!           clear_tv(&argvars[argcount]);
!       return FAIL;
!     }
!     return add_defer(name, argcount, argvars);
! }
  
! /*
!  * 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
!  * not NULL.
!  * Returns OK or FAIL.
!  */
!     int
! add_defer(char_u *name, int argcount_arg, typval_T *argvars)
! {
!     char_u    *saved_name = vim_strsave(name);
!     int               argcount = argcount_arg;
!     defer_T   *dr;
!     int               ret = FAIL;
! 
!     if (saved_name == NULL)
        goto theend;
!     if (in_def_function())
!     {
!       if (add_defer_function(saved_name, argcount, argvars) == OK)
!           argcount = 0;
!     }
!     else
      {
!       if (current_funccal->fc_defer.ga_itemsize == 0)
!           ga_init2(&current_funccal->fc_defer, sizeof(defer_T), 10);
!       if (ga_grow(&current_funccal->fc_defer, 1) == FAIL)
!           goto theend;
!       dr = ((defer_T *)current_funccal->fc_defer.ga_data)
!                                         + current_funccal->fc_defer.ga_len++;
!       dr->dr_name = saved_name;
!       dr->dr_argcount = argcount;
!       while (argcount > 0)
!       {
!           --argcount;
!           dr->dr_argvars[argcount] = argvars[argcount];
!       }
      }
      ret = OK;
  
*** ../vim-9.0.0378/src/proto/userfunc.pro      2022-09-03 21:35:50.184158219 
+0100
--- src/proto/userfunc.pro      2022-09-04 14:16:37.328845269 +0100
***************
*** 58,63 ****
--- 58,64 ----
  void func_ref(char_u *name);
  void func_ptr_ref(ufunc_T *fp);
  void ex_return(exarg_T *eap);
+ int add_defer(char_u *name, int argcount_arg, typval_T *argvars);
  void handle_defer(void);
  void ex_call(exarg_T *eap);
  int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv);
*** ../vim-9.0.0378/src/vim9execute.c   2022-09-04 11:42:18.107231195 +0100
--- src/vim9execute.c   2022-09-04 15:21:33.333542279 +0100
***************
*** 845,885 ****
      return FALSE;
  }
  
  /*
!  * Handle ISN_DEFER.  Stack has a function reference and "argcount" arguments.
!  * The local variable that lists deferred functions is "var_idx".
!  * Returns OK or FAIL.
   */
!     static int
! add_defer_func(int var_idx, int argcount, ectx_T *ectx)
  {
      typval_T  *defer_tv = STACK_TV_VAR(var_idx);
      list_T    *defer_l;
-     typval_T  *func_tv;
      list_T    *l;
-     int               i;
      typval_T  listval;
  
      if (defer_tv->v_type != VAR_LIST)
      {
        // first time, allocate the list
        if (rettv_list_alloc(defer_tv) == FAIL)
!           return FAIL;
      }
      defer_l = defer_tv->vval.v_list;
  
      l = list_alloc_with_items(argcount + 1);
      if (l == NULL)
!       return FAIL;
      listval.v_type = VAR_LIST;
      listval.vval.v_list = l;
      listval.v_lock = 0;
      if (list_insert_tv(defer_l, &listval, defer_l->lv_first) == FAIL)
      {
        vim_free(l);
!       return FAIL;
      }
  
      func_tv = STACK_TV_BOT(-argcount - 1);
      // TODO: check type is a funcref
      list_set_item(l, 0, func_tv);
--- 845,915 ----
      return FALSE;
  }
  
+ // Ugly static to avoid passing the execution context around through many
+ // layers.
+ static ectx_T *current_ectx = NULL;
+ 
  /*
!  * Return TRUE if currently executing a :def function.
!  * Can be used by builtin functions only.
   */
!     int
! in_def_function(void)
! {
!     return current_ectx != NULL;
! }
! 
! /*
!  * Add an entry for a deferred function call to the currently executing
!  * function.
!  * Return the list or NULL when failed.
!  */
!     static list_T *
! add_defer_item(int var_idx, int argcount, ectx_T *ectx)
  {
      typval_T  *defer_tv = STACK_TV_VAR(var_idx);
      list_T    *defer_l;
      list_T    *l;
      typval_T  listval;
  
      if (defer_tv->v_type != VAR_LIST)
      {
        // first time, allocate the list
        if (rettv_list_alloc(defer_tv) == FAIL)
!           return NULL;
      }
      defer_l = defer_tv->vval.v_list;
  
      l = list_alloc_with_items(argcount + 1);
      if (l == NULL)
!       return NULL;
      listval.v_type = VAR_LIST;
      listval.vval.v_list = l;
      listval.v_lock = 0;
      if (list_insert_tv(defer_l, &listval, defer_l->lv_first) == FAIL)
      {
        vim_free(l);
!       return NULL;
      }
  
+     return l;
+ }
+ 
+ /*
+  * Handle ISN_DEFER.  Stack has a function reference and "argcount" arguments.
+  * The local variable that lists deferred functions is "var_idx".
+  * Returns OK or FAIL.
+  */
+     static int
+ defer_command(int var_idx, int argcount, ectx_T *ectx)
+ {
+     list_T    *l = add_defer_item(var_idx, argcount, ectx);
+     int               i;
+     typval_T  *func_tv;
+ 
+     if (l == NULL)
+       return FAIL;
+ 
      func_tv = STACK_TV_BOT(-argcount - 1);
      // TODO: check type is a funcref
      list_set_item(l, 0, func_tv);
***************
*** 891,896 ****
--- 921,963 ----
  }
  
  /*
+  * Add a deferred function "name" with one argument "arg_tv".
+  * Consumes "name", also on failure.
+  * Only to be called when in_def_function() returns TRUE.
+  */
+     int
+ add_defer_function(char_u *name, int argcount, typval_T *argvars)
+ {
+     dfunc_T   *dfunc = ((dfunc_T *)def_functions.ga_data)
+                                                 + current_ectx->ec_dfunc_idx;
+     list_T    *l;
+     typval_T  func_tv;
+     int               i;
+ 
+     if (dfunc->df_defer_var_idx == 0)
+     {
+       iemsg("df_defer_var_idx is zero");
+       vim_free(func_tv.vval.v_string);
+       return FAIL;
+     }
+     func_tv.v_type = VAR_FUNC;
+     func_tv.v_lock = 0;
+     func_tv.vval.v_string = name;
+ 
+     l = add_defer_item(dfunc->df_defer_var_idx - 1, 1, current_ectx);
+     if (l == NULL)
+     {
+       vim_free(func_tv.vval.v_string);
+       return FAIL;
+     }
+ 
+     list_set_item(l, 0, &func_tv);
+     for (i = 0; i < argcount; ++i)
+       list_set_item(l, i + 1, argvars + i);
+     return OK;
+ }
+ 
+ /*
   * Invoked when returning from a function: Invoke any deferred calls.
   */
      static void
***************
*** 1068,1077 ****
      return OK;
  }
  
- // Ugly global to avoid passing the execution context around through many
- // layers.
- static ectx_T *current_ectx = NULL;
- 
  /*
   * Call a builtin function by index.
   */
--- 1135,1140 ----
***************
*** 3748,3754 ****
  
            // :defer func(arg)
            case ISN_DEFER:
!               if (add_defer_func(iptr->isn_arg.defer.defer_var_idx,
                             iptr->isn_arg.defer.defer_argcount, ectx) == FAIL)
                    goto on_error;
                break;
--- 3811,3817 ----
  
            // :defer func(arg)
            case ISN_DEFER:
!               if (defer_command(iptr->isn_arg.defer.defer_var_idx,
                             iptr->isn_arg.defer.defer_argcount, ectx) == FAIL)
                    goto on_error;
                break;
***************
*** 5121,5127 ****
  }
  
  /*
!  * Execute the instructions from a VAR_INSTR typeval and put the result in
   * "rettv".
   * Return OK or FAIL.
   */
--- 5184,5190 ----
  }
  
  /*
!  * Execute the instructions from a VAR_INSTR typval and put the result in
   * "rettv".
   * Return OK or FAIL.
   */
*** ../vim-9.0.0378/src/proto/vim9execute.pro   2022-06-27 23:15:29.000000000 
+0100
--- src/proto/vim9execute.pro   2022-09-04 15:14:56.129607845 +0100
***************
*** 3,8 ****
--- 3,10 ----
  void update_has_breakpoint(ufunc_T *ufunc);
  void funcstack_check_refcount(funcstack_T *funcstack);
  int set_ref_in_funcstacks(int copyID);
+ int in_def_function(void);
+ int add_defer_function(char_u *name, int argcount, typval_T *argvars);
  char_u *char_from_string(char_u *str, varnumber_T index);
  char_u *string_slice(char_u *str, varnumber_T first, varnumber_T last, int 
exclusive);
  int fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx);
*** ../vim-9.0.0378/src/vim9expr.c      2022-09-03 21:35:50.184158219 +0100
--- src/vim9expr.c      2022-09-04 15:31:00.088976462 +0100
***************
*** 833,838 ****
--- 833,846 ----
                }
            }
  
+           if (STRCMP(name, "writefile") == 0 && argcount > 2)
+           {
+               // May have the "D" flag, reserve a variable for a deferred
+               // function call.
+               if (get_defer_var_idx(cctx) == 0)
+                   idx = -1;
+           }
+ 
            if (idx >= 0)
                res = generate_BCALL(cctx, idx, argcount, argcount_init == 1);
        }
*** ../vim-9.0.0378/src/vim9cmds.c      2022-09-03 21:35:50.184158219 +0100
--- src/vim9cmds.c      2022-09-04 15:30:24.825020294 +0100
***************
*** 1685,1690 ****
--- 1685,1711 ----
  }
  
  /*
+  * Get the local variable index for deferred function calls.
+  * Reserve it when not done already.
+  * Returns zero for failure.
+  */
+     int
+ get_defer_var_idx(cctx_T *cctx)
+ {
+     dfunc_T   *dfunc = ((dfunc_T *)def_functions.ga_data)
+                                              + cctx->ctx_ufunc->uf_dfunc_idx;
+     if (dfunc->df_defer_var_idx == 0)
+     {
+       lvar_T *lvar = reserve_local(cctx, (char_u *)"@defer@", 7,
+                                                           TRUE, &t_list_any);
+       if (lvar == NULL)
+           return 0;
+       dfunc->df_defer_var_idx = lvar->lv_idx + 1;
+     }
+     return dfunc->df_defer_var_idx;
+ }
+ 
+ /*
   * Compile "defer func(arg)".
   */
      char_u *
***************
*** 1693,1699 ****
      char_u    *p;
      char_u    *arg = arg_start;
      int               argcount = 0;
!     dfunc_T   *dfunc;
      type_T    *type;
      int               func_idx;
  
--- 1714,1720 ----
      char_u    *p;
      char_u    *arg = arg_start;
      int               argcount = 0;
!     int               defer_var_idx;
      type_T    *type;
      int               func_idx;
  
***************
*** 1730,1745 ****
  
      // TODO: check argument count with "type"
  
!     dfunc = ((dfunc_T *)def_functions.ga_data) + 
cctx->ctx_ufunc->uf_dfunc_idx;
!     if (dfunc->df_defer_var_idx == 0)
!     {
!       lvar_T *lvar = reserve_local(cctx, (char_u *)"@defer@", 7,
!                                                           TRUE, &t_list_any);
!       if (lvar == NULL)
!           return NULL;
!       dfunc->df_defer_var_idx = lvar->lv_idx + 1;
!     }
!     if (generate_DEFER(cctx, dfunc->df_defer_var_idx - 1, argcount) == FAIL)
        return NULL;
  
      return skipwhite(arg);
--- 1751,1760 ----
  
      // TODO: check argument count with "type"
  
!     defer_var_idx = get_defer_var_idx(cctx);
!     if (defer_var_idx == 0)
!       return NULL;
!     if (generate_DEFER(cctx, defer_var_idx - 1, argcount) == FAIL)
        return NULL;
  
      return skipwhite(arg);
*** ../vim-9.0.0378/src/proto/vim9cmds.pro      2022-09-03 21:35:50.184158219 
+0100
--- src/proto/vim9cmds.pro      2022-09-04 15:31:31.892936215 +0100
***************
*** 21,26 ****
--- 21,27 ----
  char_u *compile_endtry(char_u *arg, cctx_T *cctx);
  char_u *compile_throw(char_u *arg, cctx_T *cctx);
  char_u *compile_eval(char_u *arg, cctx_T *cctx);
+ int get_defer_var_idx(cctx_T *cctx);
  char_u *compile_defer(char_u *arg_start, cctx_T *cctx);
  char_u *compile_mult_expr(char_u *arg, int cmdidx, cctx_T *cctx);
  char_u *compile_put(char_u *arg, exarg_T *eap, cctx_T *cctx);
*** ../vim-9.0.0378/src/testdir/test_writefile.vim      2022-09-02 
21:55:45.511049444 +0100
--- src/testdir/test_writefile.vim      2022-09-04 14:32:33.051411843 +0100
***************
*** 933,938 ****
--- 933,955 ----
    call delete('Xwbfile3')
  endfunc
  
+ func DoWriteDefer()
+   call writefile(['some text'], 'XdeferDelete', 'D')
+   call assert_equal(['some text'], readfile('XdeferDelete'))
+ endfunc
+ 
+ def DefWriteDefer()
+   writefile(['some text'], 'XdefdeferDelete', 'D')
+   assert_equal(['some text'], readfile('XdefdeferDelete'))
+ enddef
+ 
+ func Test_write_with_deferred_delete()
+   call DoWriteDefer()
+   call assert_equal('', glob('XdeferDelete'))
+   call DefWriteDefer()
+   call assert_equal('', glob('XdefdeferDelete'))
+ endfunc
+ 
  " Check that buffer is written before triggering QuitPre
  func Test_wq_quitpre_autocommand()
    edit Xsomefile
*** ../vim-9.0.0378/src/version.c       2022-09-04 13:45:10.763423705 +0100
--- src/version.c       2022-09-04 15:36:18.296551656 +0100
***************
*** 705,706 ****
--- 705,708 ----
  {   /* Add new patch number below this line */
+ /**/
+     379,
  /**/

-- 
It's totally unfair to suggest - as many have - that engineers are socially
inept.  Engineers simply have different objectives when it comes to social
interaction.
                                (Scott Adams - The Dilbert principle)

 /// 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/20220904144114.121801C0CE4%40moolenaar.net.

Raspunde prin e-mail lui