To: vim_dev@googlegroups.com Subject: Patch 9.0.0379 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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 *)_("") : 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 *)_("") : 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(¤t_funccal->fc_defer, sizeof(defer_T), 10); ! if (ga_grow(¤t_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(¤t_funccal->fc_defer, sizeof(defer_T), 10); ! if (ga_grow(¤t_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 -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///