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(¤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 -- [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.