mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 11:28:22 +00:00
vim-patch:9.0.0379: cleaning up after writefile() is a hassle
Problem: Cleaning up after writefile() is a hassle.
Solution: Add the 'D' flag to defer deleting the written file. Very useful
in tests.
806a273f3c
Co-authored-by: Bram Moolenaar <Bram@vim.org>
This commit is contained in:
@@ -9507,31 +9507,43 @@ writefile({object}, {fname} [, {flags}])
|
|||||||
When {object} is a |List| write it to file {fname}. Each list
|
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
|
item is separated with a NL. Each list item must be a String
|
||||||
or Number.
|
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" fsync() call is not used, with "s"
|
|
||||||
it is used, 'fsync' option applies by default. No fsync()
|
|
||||||
means that writefile() will finish faster, but writes may be
|
|
||||||
left in OS buffers and not yet written to disk. Such changes
|
|
||||||
will disappear if system crashes before OS does writing.
|
|
||||||
|
|
||||||
All NL characters are replaced with a NUL character.
|
All NL characters are replaced with a NUL character.
|
||||||
Inserting CR characters needs to be done before passing {list}
|
Inserting CR characters needs to be done before passing {list}
|
||||||
to writefile().
|
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.
|
An existing file is overwritten, if possible.
|
||||||
|
|
||||||
When the write fails -1 is returned, otherwise 0. There is an
|
When the write fails -1 is returned, otherwise 0. There is an
|
||||||
error message if the file can't be created or when writing
|
error message if the file can't be created or when writing
|
||||||
fails.
|
fails.
|
||||||
|
|
||||||
Also see |readfile()|.
|
Also see |readfile()|.
|
||||||
To copy a file byte for byte: >
|
To copy a file byte for byte: >
|
||||||
:let fl = readfile("foo", "b")
|
:let fl = readfile("foo", "b")
|
||||||
|
@@ -9296,6 +9296,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|||||||
|
|
||||||
bool binary = false;
|
bool binary = false;
|
||||||
bool append = false;
|
bool append = false;
|
||||||
|
bool defer = false;
|
||||||
bool do_fsync = !!p_fs;
|
bool do_fsync = !!p_fs;
|
||||||
bool mkdir_p = false;
|
bool mkdir_p = false;
|
||||||
if (argvars[2].v_type != VAR_UNKNOWN) {
|
if (argvars[2].v_type != VAR_UNKNOWN) {
|
||||||
@@ -9309,6 +9310,8 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|||||||
binary = true; break;
|
binary = true; break;
|
||||||
case 'a':
|
case 'a':
|
||||||
append = true; break;
|
append = true; break;
|
||||||
|
case 'D':
|
||||||
|
defer = true; break;
|
||||||
case 's':
|
case 's':
|
||||||
do_fsync = true; break;
|
do_fsync = true; break;
|
||||||
case 'S':
|
case 'S':
|
||||||
@@ -9328,6 +9331,12 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|||||||
if (fname == NULL) {
|
if (fname == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (defer && get_current_funccal() == NULL) {
|
||||||
|
semsg(_(e_str_not_inside_function), "defer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
FileDescriptor fp;
|
FileDescriptor fp;
|
||||||
int error;
|
int error;
|
||||||
if (*fname == NUL) {
|
if (*fname == NUL) {
|
||||||
@@ -9336,9 +9345,17 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|||||||
((append ? kFileAppend : kFileTruncate)
|
((append ? kFileAppend : kFileTruncate)
|
||||||
| (mkdir_p ? kFileMkDir : kFileCreate)
|
| (mkdir_p ? kFileMkDir : kFileCreate)
|
||||||
| kFileCreate), 0666)) != 0) {
|
| kFileCreate), 0666)) != 0) {
|
||||||
semsg(_("E482: Can't open file %s for writing: %s"),
|
semsg(_("E482: Can't open file %s for writing: %s"), fname, os_strerror(error));
|
||||||
fname, os_strerror(error));
|
|
||||||
} else {
|
} else {
|
||||||
|
if (defer) {
|
||||||
|
typval_T tv = {
|
||||||
|
.v_type = VAR_STRING,
|
||||||
|
.v_lock = VAR_UNLOCKED,
|
||||||
|
.vval.v_string = xstrdup(fname),
|
||||||
|
};
|
||||||
|
add_defer("delete", 1, &tv);
|
||||||
|
}
|
||||||
|
|
||||||
bool write_ok;
|
bool write_ok;
|
||||||
if (argvars[0].v_type == VAR_BLOB) {
|
if (argvars[0].v_type == VAR_BLOB) {
|
||||||
write_ok = write_blob(&fp, argvars[0].vval.v_blob);
|
write_ok = write_blob(&fp, argvars[0].vval.v_blob);
|
||||||
|
@@ -478,6 +478,7 @@ void emsg_funcname(const char *errmsg, const char *name)
|
|||||||
|
|
||||||
/// Get function arguments at "*arg" and advance it.
|
/// Get function arguments at "*arg" and advance it.
|
||||||
/// Return them in "*argvars[MAX_FUNC_ARGS + 1]" and the count in "argcount".
|
/// 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(char **arg, evalarg_T *const evalarg, int partial_argc,
|
static int get_func_arguments(char **arg, evalarg_T *const evalarg, int partial_argc,
|
||||||
typval_T *argvars, int *argcount)
|
typval_T *argvars, int *argcount)
|
||||||
{
|
{
|
||||||
@@ -3119,16 +3120,28 @@ static int ex_defer_inner(char *name, char **arg, evalarg_T *const evalarg)
|
|||||||
{
|
{
|
||||||
typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
|
typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
|
||||||
int argcount = 0; // number of arguments found
|
int argcount = 0; // number of arguments found
|
||||||
int ret = FAIL;
|
|
||||||
|
|
||||||
if (current_funccal == NULL) {
|
if (current_funccal == NULL) {
|
||||||
semsg(_(e_str_not_inside_function), "defer");
|
semsg(_(e_str_not_inside_function), "defer");
|
||||||
return FAIL;
|
return FAIL;
|
||||||
}
|
}
|
||||||
if (get_func_arguments(arg, evalarg, false, argvars, &argcount) == FAIL) {
|
if (get_func_arguments(arg, evalarg, false, argvars, &argcount) == FAIL) {
|
||||||
goto theend;
|
while (--argcount >= 0) {
|
||||||
|
tv_clear(&argvars[argcount]);
|
||||||
|
}
|
||||||
|
return FAIL;
|
||||||
}
|
}
|
||||||
|
add_defer(name, argcount, argvars);
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a deferred call for "name" with arguments "argvars[argcount]".
|
||||||
|
/// Consumes "argvars[]".
|
||||||
|
/// Caller must check that current_funccal is not NULL.
|
||||||
|
void add_defer(char *name, int argcount_arg, typval_T *argvars)
|
||||||
|
{
|
||||||
char *saved_name = xstrdup(name);
|
char *saved_name = xstrdup(name);
|
||||||
|
int argcount = argcount_arg;
|
||||||
|
|
||||||
if (current_funccal->fc_defer.ga_itemsize == 0) {
|
if (current_funccal->fc_defer.ga_itemsize == 0) {
|
||||||
ga_init(¤t_funccal->fc_defer, sizeof(defer_T), 10);
|
ga_init(¤t_funccal->fc_defer, sizeof(defer_T), 10);
|
||||||
@@ -3140,13 +3153,6 @@ static int ex_defer_inner(char *name, char **arg, evalarg_T *const evalarg)
|
|||||||
argcount--;
|
argcount--;
|
||||||
dr->dr_argvars[argcount] = argvars[argcount];
|
dr->dr_argvars[argcount] = argvars[argcount];
|
||||||
}
|
}
|
||||||
ret = OK;
|
|
||||||
|
|
||||||
theend:
|
|
||||||
while (--argcount >= 0) {
|
|
||||||
tv_clear(&argvars[argcount]);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invoked after a function has finished: invoke ":defer" functions.
|
/// Invoked after a function has finished: invoke ":defer" functions.
|
||||||
|
@@ -6250,28 +6250,6 @@ func Test_very_long_error_line()
|
|||||||
call setqflist([], 'f')
|
call setqflist([], 'f')
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
" The test depends on deferred delete and string interpolation, which haven't
|
|
||||||
" been ported, so override it with a rewrite that doesn't use these features.
|
|
||||||
func! Test_very_long_error_line()
|
|
||||||
let msg = repeat('abcdefghijklmn', 146)
|
|
||||||
let emsg = 'Xlonglines.c:1:' . msg
|
|
||||||
call writefile([msg, emsg], 'Xerror')
|
|
||||||
cfile Xerror
|
|
||||||
call delete('Xerror')
|
|
||||||
cwindow
|
|
||||||
call assert_equal('|| ' .. msg, getline(1))
|
|
||||||
call assert_equal('Xlonglines.c|1| ' .. msg, getline(2))
|
|
||||||
cclose
|
|
||||||
|
|
||||||
let l = execute('clist!')->split("\n")
|
|
||||||
call assert_equal([' 1: ' .. msg, ' 2 Xlonglines.c:1: ' .. msg], l)
|
|
||||||
|
|
||||||
let l = execute('cc')->split("\n")
|
|
||||||
call assert_equal(['(2 of 2): ' .. msg], l)
|
|
||||||
|
|
||||||
call setqflist([], 'f')
|
|
||||||
endfunc
|
|
||||||
|
|
||||||
" In the quickfix window, spaces at the beginning of an informational line
|
" In the quickfix window, spaces at the beginning of an informational line
|
||||||
" should not be removed but should be removed from an error line.
|
" should not be removed but should be removed from an error line.
|
||||||
func Test_info_line_with_space()
|
func Test_info_line_with_space()
|
||||||
|
@@ -924,19 +924,36 @@ endfunc
|
|||||||
" Test for ':write ++bin' and ':write ++nobin'
|
" Test for ':write ++bin' and ':write ++nobin'
|
||||||
func Test_write_binary_file()
|
func Test_write_binary_file()
|
||||||
" create a file without an eol/eof character
|
" create a file without an eol/eof character
|
||||||
call writefile(0z616161, 'Xfile1', 'b')
|
call writefile(0z616161, 'Xwbfile1', 'b')
|
||||||
new Xfile1
|
new Xwbfile1
|
||||||
write ++bin Xfile2
|
write ++bin Xwbfile2
|
||||||
write ++nobin Xfile3
|
write ++nobin Xwbfile3
|
||||||
call assert_equal(0z616161, readblob('Xfile2'))
|
call assert_equal(0z616161, readblob('Xwbfile2'))
|
||||||
if has('win32')
|
if has('win32')
|
||||||
call assert_equal(0z6161610D.0A, readblob('Xfile3'))
|
call assert_equal(0z6161610D.0A, readblob('Xwbfile3'))
|
||||||
else
|
else
|
||||||
call assert_equal(0z6161610A, readblob('Xfile3'))
|
call assert_equal(0z6161610A, readblob('Xwbfile3'))
|
||||||
endif
|
endif
|
||||||
call delete('Xfile1')
|
call delete('Xwbfile1')
|
||||||
call delete('Xfile2')
|
call delete('Xwbfile2')
|
||||||
call delete('Xfile3')
|
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
|
endfunc
|
||||||
|
|
||||||
" Check that buffer is written before triggering QuitPre
|
" Check that buffer is written before triggering QuitPre
|
||||||
|
Reference in New Issue
Block a user