aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2023-04-16 11:07:48 +0800
committerzeertzjq <zeertzjq@outlook.com>2023-04-16 15:04:41 +0800
commit0167649ce4071e60d985b65f3f9408ffb21cb58c (patch)
tree0a6e9e3e31fc47d02de3561a7c063417ea3b45d2
parentb75634e55ee4cdfee7917b29f39e3ca1307cb059 (diff)
downloadrneovim-0167649ce4071e60d985b65f3f9408ffb21cb58c.tar.gz
rneovim-0167649ce4071e60d985b65f3f9408ffb21cb58c.tar.bz2
rneovim-0167649ce4071e60d985b65f3f9408ffb21cb58c.zip
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. https://github.com/vim/vim/commit/806a273f3c84ecd475913d901890bb1929be9a0a Co-authored-by: Bram Moolenaar <Bram@vim.org>
-rw-r--r--runtime/doc/builtin.txt40
-rw-r--r--src/nvim/eval/funcs.c21
-rw-r--r--src/nvim/eval/userfunc.c24
-rw-r--r--test/old/testdir/test_quickfix.vim22
-rw-r--r--test/old/testdir/test_writefile.vim37
5 files changed, 87 insertions, 57 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index cb05718ab9..adb3164429 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -9507,31 +9507,43 @@ writefile({object}, {fname} [, {flags}])
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.
+ 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.
+ unmodified, also when binary mode is not specified.
- When {flags} contains "a" then append mode is used, lines are
- appended to the file: >
+ {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")
<
- 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.
+ '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.
- 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")
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 99e511a7a4..66fd663e5e 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -9296,6 +9296,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
bool binary = false;
bool append = false;
+ bool defer = false;
bool do_fsync = !!p_fs;
bool mkdir_p = false;
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;
case 'a':
append = true; break;
+ case 'D':
+ defer = true; break;
case 's':
do_fsync = true; break;
case 'S':
@@ -9328,6 +9331,12 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (fname == NULL) {
return;
}
+
+ if (defer && get_current_funccal() == NULL) {
+ semsg(_(e_str_not_inside_function), "defer");
+ return;
+ }
+
FileDescriptor fp;
int error;
if (*fname == NUL) {
@@ -9336,9 +9345,17 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
((append ? kFileAppend : kFileTruncate)
| (mkdir_p ? kFileMkDir : kFileCreate)
| kFileCreate), 0666)) != 0) {
- semsg(_("E482: Can't open file %s for writing: %s"),
- fname, os_strerror(error));
+ semsg(_("E482: Can't open file %s for writing: %s"), fname, os_strerror(error));
} 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;
if (argvars[0].v_type == VAR_BLOB) {
write_ok = write_blob(&fp, argvars[0].vval.v_blob);
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index 9853622ee0..4b9bc1fdec 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -478,6 +478,7 @@ void emsg_funcname(const char *errmsg, const char *name)
/// 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(char **arg, evalarg_T *const evalarg, int partial_argc,
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
int argcount = 0; // number of arguments found
- int ret = FAIL;
if (current_funccal == NULL) {
semsg(_(e_str_not_inside_function), "defer");
return 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);
+ int argcount = argcount_arg;
if (current_funccal->fc_defer.ga_itemsize == 0) {
ga_init(&current_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--;
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.
diff --git a/test/old/testdir/test_quickfix.vim b/test/old/testdir/test_quickfix.vim
index 838c4b1c15..f720b6e42d 100644
--- a/test/old/testdir/test_quickfix.vim
+++ b/test/old/testdir/test_quickfix.vim
@@ -6250,28 +6250,6 @@ func Test_very_long_error_line()
call setqflist([], 'f')
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
" should not be removed but should be removed from an error line.
func Test_info_line_with_space()
diff --git a/test/old/testdir/test_writefile.vim b/test/old/testdir/test_writefile.vim
index 6019cee193..5e6428ded8 100644
--- a/test/old/testdir/test_writefile.vim
+++ b/test/old/testdir/test_writefile.vim
@@ -924,19 +924,36 @@ endfunc
" Test for ':write ++bin' and ':write ++nobin'
func Test_write_binary_file()
" create a file without an eol/eof character
- call writefile(0z616161, 'Xfile1', 'b')
- new Xfile1
- write ++bin Xfile2
- write ++nobin Xfile3
- call assert_equal(0z616161, readblob('Xfile2'))
+ call writefile(0z616161, 'Xwbfile1', 'b')
+ new Xwbfile1
+ write ++bin Xwbfile2
+ write ++nobin Xwbfile3
+ call assert_equal(0z616161, readblob('Xwbfile2'))
if has('win32')
- call assert_equal(0z6161610D.0A, readblob('Xfile3'))
+ call assert_equal(0z6161610D.0A, readblob('Xwbfile3'))
else
- call assert_equal(0z6161610A, readblob('Xfile3'))
+ call assert_equal(0z6161610A, readblob('Xwbfile3'))
endif
- call delete('Xfile1')
- call delete('Xfile2')
- call delete('Xfile3')
+ call delete('Xwbfile1')
+ call delete('Xwbfile2')
+ 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