mirror of
https://github.com/neovim/neovim.git
synced 2026-09-14 09:52:11 +00:00
Merge pull request #23119 from zeertzjq/vim-9.0.0370
vim-patch:9.0.{0370,0379,0390,0397,0411,1446}: :defer and deferred delete
This commit is contained in:
@@ -5572,8 +5572,24 @@ mkdir({name} [, {flags} [, {prot}]])
|
||||
When {flags} is present it must be a string. An empty string
|
||||
has no effect.
|
||||
|
||||
If {flags} is "p" then intermediate directories are created as
|
||||
necessary.
|
||||
If {flags} contains "p" then intermediate directories are
|
||||
created as necessary.
|
||||
|
||||
If {flags} contains "D" then {name} is deleted at the end of
|
||||
the current function, as with: >
|
||||
defer delete({name}, 'd')
|
||||
<
|
||||
If {flags} contains "R" then {name} is deleted recursively at
|
||||
the end of the current function, as with: >
|
||||
defer delete({name}, 'rf')
|
||||
< Note that when {name} has more than one part and "p" is used
|
||||
some directories may already exist. Only the first one that
|
||||
is created and what it contains is scheduled to be deleted.
|
||||
E.g. when using: >
|
||||
call mkdir('subdir/tmp/autoload', 'pR')
|
||||
< and "subdir" already exists then "subdir/tmp" will be
|
||||
scheduled for deletion, like with: >
|
||||
defer delete('subdir/tmp', 'rf')
|
||||
|
||||
If {prot} is given it is used to set the protection bits of
|
||||
the new directory. The default is 0o755 (rwxr-xr-x: r/w for
|
||||
@@ -9507,31 +9523,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.
|
||||
|
||||
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.
|
||||
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")
|
||||
|
||||
@@ -350,10 +350,68 @@ A function can also be called as part of evaluating an expression or when it
|
||||
is used as a method: >
|
||||
let x = GetList()
|
||||
let y = GetList()->Filter()
|
||||
<
|
||||
==============================================================================
|
||||
|
||||
3. Cleaning up in a function ~
|
||||
*:defer*
|
||||
:defer {func}({args}) Call {func} when the current function is done.
|
||||
{args} are evaluated here.
|
||||
|
||||
Quite often a command in a function has a global effect, which must be undone
|
||||
when the function finishes. Handling this in all kinds of situations can be a
|
||||
hassle. Especially when an unexpected error is encountered. This can be done
|
||||
with `try` / `finally` blocks, but this gets complicated when there is more
|
||||
than one.
|
||||
|
||||
A much simpler solution is using `defer`. It schedules a function call when
|
||||
the function is returning, no matter if there is an error. Example: >
|
||||
func Filter(text) abort
|
||||
call writefile(a:text, 'Tempfile')
|
||||
call system('filter < Tempfile > Outfile')
|
||||
call Handle('Outfile')
|
||||
call delete('Tempfile')
|
||||
call delete('Outfile')
|
||||
endfunc
|
||||
|
||||
Here 'Tempfile' and 'Outfile' will not be deleted if something causes the
|
||||
function to abort. `:defer` can be used to avoid that: >
|
||||
func Filter(text) abort
|
||||
call writefile(a:text, 'Tempfile')
|
||||
defer delete('Tempfile')
|
||||
defer delete('Outfile')
|
||||
call system('filter < Tempfile > Outfile')
|
||||
call Handle('Outfile')
|
||||
endfunc
|
||||
|
||||
Note that deleting "Outfile" is scheduled before calling `system()`, since it
|
||||
can be created even when `system()` fails.
|
||||
|
||||
The deferred functions are called in reverse order, the last one added is
|
||||
executed first. A useless example: >
|
||||
func Useless() abort
|
||||
for s in range(3)
|
||||
defer execute('echomsg "number ' .. s .. '"')
|
||||
endfor
|
||||
endfunc
|
||||
|
||||
Now `:messages` shows:
|
||||
number 2
|
||||
number 1
|
||||
number 0
|
||||
|
||||
Any return value of the deferred function is discarded. The function cannot
|
||||
be followed by anything, such as "->func" or ".member". Currently `:defer
|
||||
GetArg()->TheFunc()` does not work, it may work in a later version.
|
||||
|
||||
Errors are reported but do not cause aborting execution of deferred functions.
|
||||
|
||||
No range is accepted. The function can be a partial with extra arguments, but
|
||||
not with a dictionary. *E1300*
|
||||
|
||||
==============================================================================
|
||||
3. Automatically loading functions ~
|
||||
|
||||
4. Automatically loading functions ~
|
||||
*autoload-functions*
|
||||
When using many or large functions, it's possible to automatically define them
|
||||
only when they are used. There are two methods: with an autocommand and with
|
||||
|
||||
@@ -4892,6 +4892,9 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
*path_tail_with_sep((char *)dir) = NUL;
|
||||
}
|
||||
|
||||
bool defer = false;
|
||||
bool defer_recurse = false;
|
||||
char *created = NULL;
|
||||
if (argvars[1].v_type != VAR_UNKNOWN) {
|
||||
if (argvars[2].v_type != VAR_UNKNOWN) {
|
||||
prot = (int)tv_get_number_chk(&argvars[2], NULL);
|
||||
@@ -4899,9 +4902,17 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (strcmp(tv_get_string(&argvars[1]), "p") == 0) {
|
||||
const char *arg2 = tv_get_string(&argvars[1]);
|
||||
defer = vim_strchr(arg2, 'D') != NULL;
|
||||
defer_recurse = vim_strchr(arg2, 'R') != NULL;
|
||||
if ((defer || defer_recurse) && !can_add_defer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vim_strchr(arg2, 'p') != NULL) {
|
||||
char *failed_dir;
|
||||
int ret = os_mkdir_recurse(dir, prot, &failed_dir);
|
||||
int ret = os_mkdir_recurse(dir, prot, &failed_dir,
|
||||
defer || defer_recurse ? &created : NULL);
|
||||
if (ret != 0) {
|
||||
semsg(_(e_mkdir), failed_dir, os_strerror(ret));
|
||||
xfree(failed_dir);
|
||||
@@ -4909,10 +4920,27 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
return;
|
||||
}
|
||||
rettv->vval.v_number = OK;
|
||||
return;
|
||||
}
|
||||
}
|
||||
rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
|
||||
if (rettv->vval.v_number == FAIL) {
|
||||
rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
|
||||
}
|
||||
|
||||
// Handle "D" and "R": deferred deletion of the created directory.
|
||||
if (rettv->vval.v_number == OK
|
||||
&& created == NULL && (defer || defer_recurse)) {
|
||||
created = FullName_save(dir, false);
|
||||
}
|
||||
if (created != NULL) {
|
||||
typval_T tv[2];
|
||||
tv[0].v_type = VAR_STRING;
|
||||
tv[0].v_lock = VAR_UNLOCKED;
|
||||
tv[0].vval.v_string = created;
|
||||
tv[1].v_type = VAR_STRING;
|
||||
tv[1].v_lock = VAR_UNLOCKED;
|
||||
tv[1].vval.v_string = xstrdup(defer_recurse ? "rf" : "d");
|
||||
add_defer("delete", 2, tv);
|
||||
}
|
||||
}
|
||||
|
||||
/// "mode()" function
|
||||
@@ -9296,6 +9324,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 +9338,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 +9359,11 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
if (fname == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (defer && !can_add_defer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileDescriptor fp;
|
||||
int error;
|
||||
if (*fname == NUL) {
|
||||
@@ -9336,9 +9372,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 = FullName_save(fname, false),
|
||||
};
|
||||
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);
|
||||
|
||||
@@ -299,6 +299,7 @@ struct funccall_S {
|
||||
linenr_T breakpoint; ///< Next line with breakpoint or zero.
|
||||
int dbg_tick; ///< debug_tick when breakpoint was set.
|
||||
int level; ///< Top nesting level of executed function.
|
||||
garray_T fc_defer; ///< Functions to be called on return.
|
||||
proftime_T prof_child; ///< Time spent in a child.
|
||||
funccall_T *caller; ///< Calling function or NULL; or next funccal in
|
||||
///< list pointed to by previous_funccal.
|
||||
|
||||
@@ -53,6 +53,13 @@
|
||||
# include "eval/userfunc.c.generated.h"
|
||||
#endif
|
||||
|
||||
/// structure used as item in "fc_defer"
|
||||
typedef struct {
|
||||
char *dr_name; ///< function name, allocated
|
||||
typval_T dr_argvars[MAX_FUNC_ARGS + 1];
|
||||
int dr_argcount;
|
||||
} defer_T;
|
||||
|
||||
static hashtab_T func_hashtab;
|
||||
|
||||
// Used by get_func_tv()
|
||||
@@ -74,6 +81,8 @@ static const char e_no_white_space_allowed_before_str_str[]
|
||||
= N_("E1068: No white space allowed before '%s': %s");
|
||||
static const char e_missing_heredoc_end_marker_str[]
|
||||
= N_("E1145: Missing heredoc end marker: %s");
|
||||
static const char e_cannot_use_partial_with_dictionary_for_defer[]
|
||||
= N_("E1300: Cannot use a partial with dictionary for :defer");
|
||||
|
||||
void func_init(void)
|
||||
{
|
||||
@@ -469,7 +478,43 @@ void emsg_funcname(const char *errmsg, const char *name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate a variable for the result of a function.
|
||||
/// 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)
|
||||
{
|
||||
char *argp = *arg;
|
||||
int ret = OK;
|
||||
|
||||
// Get the arguments.
|
||||
while (*argcount < MAX_FUNC_ARGS - partial_argc) {
|
||||
argp = skipwhite(argp + 1); // skip the '(' or ','
|
||||
|
||||
if (*argp == ')' || *argp == ',' || *argp == NUL) {
|
||||
break;
|
||||
}
|
||||
if (eval1(&argp, &argvars[*argcount], evalarg) == FAIL) {
|
||||
ret = FAIL;
|
||||
break;
|
||||
}
|
||||
(*argcount)++;
|
||||
if (*argp != ',') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
argp = skipwhite(argp);
|
||||
if (*argp == ')') {
|
||||
argp++;
|
||||
} else {
|
||||
ret = FAIL;
|
||||
}
|
||||
*arg = argp;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Call a function and put the result in "rettv".
|
||||
///
|
||||
/// @param name name of the function
|
||||
/// @param len length of "name" or -1 to use strlen()
|
||||
@@ -480,34 +525,16 @@ void emsg_funcname(const char *errmsg, const char *name)
|
||||
int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, evalarg_T *const evalarg,
|
||||
funcexe_T *funcexe)
|
||||
{
|
||||
char *argp;
|
||||
int ret = OK;
|
||||
typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
|
||||
int argcount = 0; // number of arguments found
|
||||
const bool evaluate = evalarg == NULL ? false : (evalarg->eval_flags & EVAL_EVALUATE);
|
||||
|
||||
// Get the arguments.
|
||||
argp = *arg;
|
||||
while (argcount < MAX_FUNC_ARGS
|
||||
- (funcexe->fe_partial == NULL ? 0 : funcexe->fe_partial->pt_argc)) {
|
||||
argp = skipwhite(argp + 1); // skip the '(' or ','
|
||||
if (*argp == ')' || *argp == ',' || *argp == NUL) {
|
||||
break;
|
||||
}
|
||||
if (eval1(&argp, &argvars[argcount], evalarg) == FAIL) {
|
||||
ret = FAIL;
|
||||
break;
|
||||
}
|
||||
argcount++;
|
||||
if (*argp != ',') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (*argp == ')') {
|
||||
argp++;
|
||||
} else {
|
||||
ret = FAIL;
|
||||
}
|
||||
char *argp = *arg;
|
||||
int ret = get_func_arguments(&argp, evalarg,
|
||||
(funcexe->fe_partial == NULL
|
||||
? 0
|
||||
: funcexe->fe_partial->pt_argc),
|
||||
argvars, &argcount);
|
||||
|
||||
if (ret == OK) {
|
||||
int i = 0;
|
||||
@@ -1148,6 +1175,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
|
||||
DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
|
||||
}
|
||||
|
||||
// Invoke functions added with ":defer".
|
||||
handle_defer_one(current_funccal);
|
||||
|
||||
RedrawingDisabled--;
|
||||
|
||||
// when the function was aborted because of an error, return -1
|
||||
@@ -1544,7 +1574,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t
|
||||
int argv_base = 0;
|
||||
partial_T *partial = funcexe->fe_partial;
|
||||
|
||||
// Initialize rettv so that it is safe for caller to invoke clear_tv(rettv)
|
||||
// Initialize rettv so that it is safe for caller to invoke tv_clear(rettv)
|
||||
// even when call_func() returns FAIL.
|
||||
rettv->v_type = VAR_UNKNOWN;
|
||||
|
||||
@@ -3033,7 +3063,156 @@ void ex_return(exarg_T *eap)
|
||||
clear_evalarg(&evalarg, eap);
|
||||
}
|
||||
|
||||
/// Lower level implementation of "call". Only called when not skipping.
|
||||
static int ex_call_inner(exarg_T *eap, char *name, char **arg, char *startarg,
|
||||
const funcexe_T *const funcexe_init, evalarg_T *const evalarg)
|
||||
{
|
||||
bool doesrange;
|
||||
bool failed = false;
|
||||
|
||||
for (linenr_T lnum = eap->line1; lnum <= eap->line2; lnum++) {
|
||||
if (eap->addr_count > 0) {
|
||||
if (lnum > curbuf->b_ml.ml_line_count) {
|
||||
// If the function deleted lines or switched to another buffer
|
||||
// the line number may become invalid.
|
||||
emsg(_(e_invrange));
|
||||
break;
|
||||
}
|
||||
curwin->w_cursor.lnum = lnum;
|
||||
curwin->w_cursor.col = 0;
|
||||
curwin->w_cursor.coladd = 0;
|
||||
}
|
||||
*arg = startarg;
|
||||
|
||||
funcexe_T funcexe = *funcexe_init;
|
||||
funcexe.fe_doesrange = &doesrange;
|
||||
typval_T rettv;
|
||||
rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this
|
||||
if (get_func_tv(name, -1, &rettv, arg, evalarg, &funcexe) == FAIL) {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle a function returning a Funcref, Dictionary or List.
|
||||
if (handle_subscript((const char **)arg, &rettv, &EVALARG_EVALUATE, true) == FAIL) {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tv_clear(&rettv);
|
||||
if (doesrange) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Stop when immediately aborting on error, or when an interrupt
|
||||
// occurred or an exception was thrown but not caught.
|
||||
// get_func_tv() returned OK, so that the check for trailing
|
||||
// characters below is executed.
|
||||
if (aborting()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return failed;
|
||||
}
|
||||
|
||||
/// Core part of ":defer func(arg)". "arg" points to the "(" and is advanced.
|
||||
///
|
||||
/// @return FAIL or OK.
|
||||
static int ex_defer_inner(char *name, char **arg, const partial_T *const partial,
|
||||
evalarg_T *const evalarg)
|
||||
{
|
||||
typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
|
||||
int partial_argc = 0; // number of partial arguments
|
||||
int argcount = 0; // number of arguments found
|
||||
|
||||
if (current_funccal == NULL) {
|
||||
semsg(_(e_str_not_inside_function), "defer");
|
||||
return FAIL;
|
||||
}
|
||||
if (partial != NULL) {
|
||||
if (partial->pt_dict != NULL) {
|
||||
emsg(_(e_cannot_use_partial_with_dictionary_for_defer));
|
||||
return FAIL;
|
||||
}
|
||||
if (partial->pt_argc > 0) {
|
||||
partial_argc = partial->pt_argc;
|
||||
for (int i = 0; i < partial_argc; i++) {
|
||||
tv_copy(&partial->pt_argv[i], &argvars[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
int r = get_func_arguments(arg, evalarg, false, argvars + partial_argc, &argcount);
|
||||
argcount += partial_argc;
|
||||
if (r == FAIL) {
|
||||
while (--argcount >= 0) {
|
||||
tv_clear(&argvars[argcount]);
|
||||
}
|
||||
return FAIL;
|
||||
}
|
||||
add_defer(name, argcount, argvars);
|
||||
return OK;
|
||||
}
|
||||
|
||||
/// Return true if currently inside a function call.
|
||||
/// Give an error message and return FALSE when not.
|
||||
bool can_add_defer(void)
|
||||
{
|
||||
if (get_current_funccal() == NULL) {
|
||||
semsg(_(e_str_not_inside_function), "defer");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 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(¤t_funccal->fc_defer, sizeof(defer_T), 10);
|
||||
}
|
||||
defer_T *dr = GA_APPEND_VIA_PTR(defer_T, ¤t_funccal->fc_defer);
|
||||
dr->dr_name = saved_name;
|
||||
dr->dr_argcount = argcount;
|
||||
while (argcount > 0) {
|
||||
argcount--;
|
||||
dr->dr_argvars[argcount] = argvars[argcount];
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked after a function has finished: invoke ":defer" functions.
|
||||
static void handle_defer_one(funccall_T *funccal)
|
||||
{
|
||||
for (int idx = funccal->fc_defer.ga_len - 1; idx >= 0; idx--) {
|
||||
defer_T *dr = ((defer_T *)funccal->fc_defer.ga_data) + idx;
|
||||
funcexe_T funcexe = { .fe_evaluate = true };
|
||||
typval_T rettv;
|
||||
rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this
|
||||
call_func(dr->dr_name, -1, &rettv, dr->dr_argcount, dr->dr_argvars, &funcexe);
|
||||
tv_clear(&rettv);
|
||||
xfree(dr->dr_name);
|
||||
for (int i = dr->dr_argcount - 1; i >= 0; i--) {
|
||||
tv_clear(&dr->dr_argvars[i]);
|
||||
}
|
||||
}
|
||||
ga_clear(&funccal->fc_defer);
|
||||
}
|
||||
|
||||
/// Called when exiting: call all defer functions.
|
||||
void invoke_all_defer(void)
|
||||
{
|
||||
for (funccall_T *funccal = current_funccal; funccal != NULL; funccal = funccal->caller) {
|
||||
handle_defer_one(funccal);
|
||||
}
|
||||
}
|
||||
|
||||
/// ":1,25call func(arg1, arg2)" function call.
|
||||
/// ":defer func(arg1, arg2)" deferred function call.
|
||||
void ex_call(exarg_T *eap)
|
||||
{
|
||||
char *arg = eap->arg;
|
||||
@@ -3041,9 +3220,6 @@ void ex_call(exarg_T *eap)
|
||||
char *name;
|
||||
char *tofree;
|
||||
int len;
|
||||
typval_T rettv;
|
||||
linenr_T lnum;
|
||||
bool doesrange;
|
||||
bool failed = false;
|
||||
funcdict_T fudi;
|
||||
partial_T *partial = NULL;
|
||||
@@ -3051,6 +3227,7 @@ void ex_call(exarg_T *eap)
|
||||
|
||||
fill_evalarg_from_eap(&evalarg, eap, eap->skip);
|
||||
if (eap->skip) {
|
||||
typval_T rettv;
|
||||
// trans_function_name() doesn't work well when skipping, use eval0()
|
||||
// instead to skip to any following command, e.g. for:
|
||||
// :if 0 | call dict.foo().bar() | endif.
|
||||
@@ -3089,59 +3266,24 @@ void ex_call(exarg_T *eap)
|
||||
// Skip white space to allow ":call func ()". Not good, but required for
|
||||
// backward compatibility.
|
||||
startarg = skipwhite(arg);
|
||||
rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this.
|
||||
|
||||
if (*startarg != '(') {
|
||||
semsg(_(e_missingparen), eap->arg);
|
||||
goto end;
|
||||
}
|
||||
|
||||
lnum = eap->line1;
|
||||
for (; lnum <= eap->line2; lnum++) {
|
||||
if (eap->addr_count > 0) { // -V560
|
||||
if (lnum > curbuf->b_ml.ml_line_count) {
|
||||
// If the function deleted lines or switched to another buffer
|
||||
// the line number may become invalid.
|
||||
emsg(_(e_invrange));
|
||||
break;
|
||||
}
|
||||
curwin->w_cursor.lnum = lnum;
|
||||
curwin->w_cursor.col = 0;
|
||||
curwin->w_cursor.coladd = 0;
|
||||
}
|
||||
if (eap->cmdidx == CMD_defer) {
|
||||
arg = startarg;
|
||||
|
||||
failed = ex_defer_inner(name, &arg, partial, &evalarg) == FAIL;
|
||||
} else {
|
||||
funcexe_T funcexe = FUNCEXE_INIT;
|
||||
funcexe.fe_firstline = eap->line1;
|
||||
funcexe.fe_lastline = eap->line2;
|
||||
funcexe.fe_doesrange = &doesrange;
|
||||
funcexe.fe_evaluate = true;
|
||||
funcexe.fe_partial = partial;
|
||||
funcexe.fe_selfdict = fudi.fd_dict;
|
||||
funcexe.fe_firstline = eap->line1;
|
||||
funcexe.fe_lastline = eap->line2;
|
||||
funcexe.fe_found_var = found_var;
|
||||
if (get_func_tv(name, -1, &rettv, &arg, &evalarg, &funcexe) == FAIL) {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle a function returning a Funcref, Dictionary or List.
|
||||
if (handle_subscript((const char **)&arg, &rettv, &EVALARG_EVALUATE, true) == FAIL) {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
tv_clear(&rettv);
|
||||
if (doesrange) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Stop when immediately aborting on error, or when an interrupt
|
||||
// occurred or an exception was thrown but not caught.
|
||||
// get_func_tv() returned OK, so that the check for trailing
|
||||
// characters below is executed.
|
||||
if (aborting()) {
|
||||
break;
|
||||
}
|
||||
funcexe.fe_evaluate = true;
|
||||
failed = ex_call_inner(eap, name, &arg, startarg, &funcexe, &evalarg);
|
||||
}
|
||||
|
||||
// When inside :try we need to check for following "| catch" or "| endtry".
|
||||
|
||||
@@ -714,6 +714,18 @@ module.cmds = {
|
||||
addr_type='ADDR_OTHER',
|
||||
func='ex_debuggreedy',
|
||||
},
|
||||
{
|
||||
command='def',
|
||||
flags=bit.bor(EXTRA, BANG, SBOXOK, CMDWIN, LOCK_OK),
|
||||
addr_type='ADDR_NONE',
|
||||
func='ex_ni',
|
||||
},
|
||||
{
|
||||
command='defer',
|
||||
flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, CMDWIN, LOCK_OK),
|
||||
addr_type='ADDR_NONE',
|
||||
func='ex_call',
|
||||
},
|
||||
{
|
||||
command='delcommand',
|
||||
flags=bit.bor(BANG, NEEDARG, WORD1, TRLBAR, CMDWIN, LOCK_OK),
|
||||
|
||||
@@ -1966,7 +1966,7 @@ void rewind_conditionals(cstack_T *cstack, int idx, int cond_type, int *cond_lev
|
||||
/// Handle ":endfunction" when not after a ":function"
|
||||
void ex_endfunction(exarg_T *eap)
|
||||
{
|
||||
emsg(_("E193: :endfunction not inside a function"));
|
||||
semsg(_(e_str_not_inside_function), ":endfunction");
|
||||
}
|
||||
|
||||
/// @return true if the string "p" looks like a ":while" or ":for" command.
|
||||
|
||||
@@ -2536,7 +2536,7 @@ static int buf_write_make_backup(char *fname, bool append, FileInfo *file_info_o
|
||||
if (*dirp == NUL && !os_isdir(IObuff)) {
|
||||
int ret;
|
||||
char *failed_dir;
|
||||
if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir)) != 0) {
|
||||
if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir, NULL)) != 0) {
|
||||
semsg(_("E303: Unable to create directory \"%s\" for backup file: %s"),
|
||||
failed_dir, os_strerror(ret));
|
||||
xfree(failed_dir);
|
||||
@@ -2679,7 +2679,7 @@ nobackup:
|
||||
if (*dirp == NUL && !os_isdir(IObuff)) {
|
||||
int ret;
|
||||
char *failed_dir;
|
||||
if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir)) != 0) {
|
||||
if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir, NULL)) != 0) {
|
||||
semsg(_("E303: Unable to create directory \"%s\" for backup file: %s"),
|
||||
failed_dir, os_strerror(ret));
|
||||
xfree(failed_dir);
|
||||
|
||||
@@ -986,6 +986,8 @@ EXTERN const char e_maxmempat[] INIT(= N_("E363: pattern uses more memory than '
|
||||
EXTERN const char e_emptybuf[] INIT(= N_("E749: empty buffer"));
|
||||
EXTERN const char e_nobufnr[] INIT(= N_("E86: Buffer %" PRId64 " does not exist"));
|
||||
|
||||
EXTERN const char e_str_not_inside_function[] INIT(= N_("E193: %s not inside a function"));
|
||||
|
||||
EXTERN const char e_invalpat[] INIT(= N_("E682: Invalid search pattern or delimiter"));
|
||||
EXTERN const char e_bufloaded[] INIT(= N_("E139: File is loaded in another buffer"));
|
||||
EXTERN const char e_notset[] INIT(= N_("E764: Option '%s' is not set"));
|
||||
|
||||
@@ -76,7 +76,7 @@ static void log_path_init(void)
|
||||
char *failed_dir = NULL;
|
||||
bool log_dir_failure = false;
|
||||
if (!os_isdir(loghome)) {
|
||||
log_dir_failure = (os_mkdir_recurse(loghome, 0700, &failed_dir) != 0);
|
||||
log_dir_failure = (os_mkdir_recurse(loghome, 0700, &failed_dir, NULL) != 0);
|
||||
}
|
||||
XFREE_CLEAR(loghome);
|
||||
// Invalid $NVIM_LOG_FILE or failed to expand; fall back to default.
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "nvim/eval.h"
|
||||
#include "nvim/eval/typval.h"
|
||||
#include "nvim/eval/typval_defs.h"
|
||||
#include "nvim/eval/userfunc.h"
|
||||
#include "nvim/event/multiqueue.h"
|
||||
#include "nvim/event/stream.h"
|
||||
#include "nvim/ex_cmds.h"
|
||||
@@ -693,6 +694,9 @@ void getout(int exitval)
|
||||
// Position the cursor on the last screen line, below all the text
|
||||
ui_cursor_goto(Rows - 1, 0);
|
||||
|
||||
// Invoked all deferred functions in the function stack.
|
||||
invoke_all_defer();
|
||||
|
||||
// Optionally print hashtable efficiency.
|
||||
hash_debug_results();
|
||||
|
||||
|
||||
@@ -3442,7 +3442,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
|
||||
} else if (!*found_existing_dir && **dirp == NUL) {
|
||||
int ret;
|
||||
char *failed_dir;
|
||||
if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir)) != 0) {
|
||||
if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir, NULL)) != 0) {
|
||||
semsg(_("E303: Unable to create directory \"%s\" for swap file, "
|
||||
"recovery impossible: %s"),
|
||||
failed_dir, os_strerror(ret));
|
||||
|
||||
@@ -937,10 +937,13 @@ int os_mkdir(const char *path, int32_t mode)
|
||||
/// the name of the directory which os_mkdir_recurse
|
||||
/// failed to create. I.e. it will contain dir or any
|
||||
/// of the higher level directories.
|
||||
/// @param[out] created Set to the full name of the first created directory.
|
||||
/// It will be NULL until that happens.
|
||||
///
|
||||
/// @return `0` for success, libuv error code for failure.
|
||||
int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_dir)
|
||||
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_dir,
|
||||
char **const created)
|
||||
FUNC_ATTR_NONNULL_ARG(1, 3) FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
// Get end of directory name in "dir".
|
||||
// We're done when it's "/" or "c:/".
|
||||
@@ -975,6 +978,8 @@ int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_di
|
||||
if ((ret = os_mkdir(curdir, mode)) != 0) {
|
||||
*failed_dir = curdir;
|
||||
return ret;
|
||||
} else if (created != NULL && *created == NULL) {
|
||||
*created = FullName_save(curdir, false);
|
||||
}
|
||||
}
|
||||
xfree(curdir);
|
||||
@@ -1002,7 +1007,7 @@ int os_file_mkdir(char *fname, int32_t mode)
|
||||
*tail = NUL;
|
||||
int r;
|
||||
char *failed_dir;
|
||||
if (((r = os_mkdir_recurse(fname, mode, &failed_dir)) < 0)) {
|
||||
if (((r = os_mkdir_recurse(fname, mode, &failed_dir, NULL)) < 0)) {
|
||||
semsg(_(e_mkdir), failed_dir, os_strerror(r));
|
||||
xfree(failed_dir);
|
||||
}
|
||||
|
||||
@@ -3042,7 +3042,7 @@ shada_write_file_nomerge: {}
|
||||
if (!os_isdir(fname)) {
|
||||
int ret;
|
||||
char *failed_dir;
|
||||
if ((ret = os_mkdir_recurse(fname, 0700, &failed_dir)) != 0) {
|
||||
if ((ret = os_mkdir_recurse(fname, 0700, &failed_dir, NULL)) != 0) {
|
||||
semsg(_(SERR "Failed to create directory %s "
|
||||
"for writing ShaDa file: %s"),
|
||||
failed_dir, os_strerror(ret));
|
||||
|
||||
@@ -705,7 +705,7 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading)
|
||||
// Last directory in the list does not exist, create it.
|
||||
int ret;
|
||||
char *failed_dir;
|
||||
if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir)) != 0) {
|
||||
if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir, NULL)) != 0) {
|
||||
semsg(_("E5003: Unable to create directory \"%s\" for undo file: %s"),
|
||||
failed_dir, os_strerror(ret));
|
||||
xfree(failed_dir);
|
||||
|
||||
@@ -30,9 +30,9 @@ func Test_set_filename_other_window()
|
||||
CheckFunction test_autochdir
|
||||
let cwd = getcwd()
|
||||
call test_autochdir()
|
||||
call mkdir('Xa')
|
||||
call mkdir('Xb')
|
||||
call mkdir('Xc')
|
||||
call mkdir('Xa', 'R')
|
||||
call mkdir('Xb', 'R')
|
||||
call mkdir('Xc', 'R')
|
||||
try
|
||||
args Xa/aaa.txt Xb/bbb.txt
|
||||
set acd
|
||||
@@ -44,9 +44,6 @@ func Test_set_filename_other_window()
|
||||
finally
|
||||
set noacd
|
||||
call chdir(cwd)
|
||||
call delete('Xa', 'rf')
|
||||
call delete('Xb', 'rf')
|
||||
call delete('Xc', 'rf')
|
||||
bwipe! aaa.txt
|
||||
bwipe! bbb.txt
|
||||
bwipe! ccc.txt
|
||||
@@ -59,10 +56,10 @@ func Test_acd_win_execute()
|
||||
set acd
|
||||
call test_autochdir()
|
||||
|
||||
call mkdir('Xfile')
|
||||
call mkdir('XacdDir', 'R')
|
||||
let winid = win_getid()
|
||||
new Xfile/file
|
||||
call assert_match('testdir.Xfile$', getcwd())
|
||||
new XacdDir/file
|
||||
call assert_match('testdir.XacdDir$', getcwd())
|
||||
cd ..
|
||||
call assert_match('testdir$', getcwd())
|
||||
call win_execute(winid, 'echo')
|
||||
@@ -71,7 +68,6 @@ func Test_acd_win_execute()
|
||||
bwipe!
|
||||
set noacd
|
||||
call chdir(cwd)
|
||||
call delete('Xfile', 'rf')
|
||||
endfunc
|
||||
|
||||
func Test_verbose_pwd()
|
||||
@@ -82,7 +78,7 @@ func Test_verbose_pwd()
|
||||
edit global.txt
|
||||
call assert_match('\[global\].*testdir$', execute('verbose pwd'))
|
||||
|
||||
call mkdir('Xautodir')
|
||||
call mkdir('Xautodir', 'R')
|
||||
split Xautodir/local.txt
|
||||
lcd Xautodir
|
||||
call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd'))
|
||||
@@ -116,7 +112,6 @@ func Test_verbose_pwd()
|
||||
|
||||
bwipe!
|
||||
call chdir(cwd)
|
||||
call delete('Xautodir', 'rf')
|
||||
endfunc
|
||||
|
||||
func Test_multibyte()
|
||||
|
||||
@@ -918,14 +918,13 @@ func Test_BufEnter()
|
||||
call assert_equal('++', g:val)
|
||||
|
||||
" Also get BufEnter when editing a directory
|
||||
call mkdir('Xdir')
|
||||
split Xdir
|
||||
call mkdir('Xbufenterdir', 'D')
|
||||
split Xbufenterdir
|
||||
call assert_equal('+++', g:val)
|
||||
|
||||
" On MS-Windows we can't edit the directory, make sure we wipe the right
|
||||
" buffer.
|
||||
bwipe! Xdir
|
||||
call delete('Xdir', 'd')
|
||||
bwipe! Xbufenterdir
|
||||
au! BufEnter
|
||||
|
||||
" Editing a "nofile" buffer doesn't read the file but does trigger BufEnter
|
||||
@@ -2186,11 +2185,10 @@ func Test_BufWriteCmd()
|
||||
new
|
||||
file Xbufwritecmd
|
||||
set buftype=acwrite
|
||||
call mkdir('Xbufwritecmd')
|
||||
call mkdir('Xbufwritecmd', 'D')
|
||||
write
|
||||
" BufWriteCmd should be triggered even if a directory has the same name
|
||||
call assert_equal(1, g:written)
|
||||
call delete('Xbufwritecmd', 'd')
|
||||
unlet g:written
|
||||
au! BufWriteCmd
|
||||
bwipe!
|
||||
@@ -2947,16 +2945,15 @@ func Test_throw_in_BufWritePre()
|
||||
endfunc
|
||||
|
||||
func Test_autocmd_in_try_block()
|
||||
call mkdir('Xdir')
|
||||
call mkdir('Xintrydir', 'R')
|
||||
au BufEnter * let g:fname = expand('%')
|
||||
try
|
||||
edit Xdir/
|
||||
edit Xintrydir/
|
||||
endtry
|
||||
call assert_match('Xdir', g:fname)
|
||||
call assert_match('Xintrydir', g:fname)
|
||||
|
||||
unlet g:fname
|
||||
au! BufEnter
|
||||
call delete('Xdir', 'rf')
|
||||
endfunc
|
||||
|
||||
func Test_autocmd_CmdWinEnter()
|
||||
|
||||
@@ -36,12 +36,70 @@ func Test_mkdir_p()
|
||||
endtry
|
||||
" 'p' doesn't suppress real errors
|
||||
call writefile([], 'Xfile')
|
||||
call assert_fails('call mkdir("Xfile", "p")', 'E739')
|
||||
call assert_fails('call mkdir("Xfile", "p")', 'E739:')
|
||||
call delete('Xfile')
|
||||
call delete('Xmkdir', 'rf')
|
||||
call assert_equal(0, mkdir(v:_null_string))
|
||||
call assert_fails('call mkdir([])', 'E730')
|
||||
call assert_fails('call mkdir("abc", [], [])', 'E745')
|
||||
call assert_fails('call mkdir([])', 'E730:')
|
||||
call assert_fails('call mkdir("abc", [], [])', 'E745:')
|
||||
endfunc
|
||||
|
||||
func DoMkdirDel(name)
|
||||
call mkdir(a:name, 'pD')
|
||||
call assert_true(isdirectory(a:name))
|
||||
endfunc
|
||||
|
||||
func DoMkdirDelAddFile(name)
|
||||
call mkdir(a:name, 'pD')
|
||||
call assert_true(isdirectory(a:name))
|
||||
call writefile(['text'], a:name .. '/file')
|
||||
endfunc
|
||||
|
||||
func DoMkdirDelRec(name)
|
||||
call mkdir(a:name, 'pR')
|
||||
call assert_true(isdirectory(a:name))
|
||||
endfunc
|
||||
|
||||
func DoMkdirDelRecAddFile(name)
|
||||
call mkdir(a:name, 'pR')
|
||||
call assert_true(isdirectory(a:name))
|
||||
call writefile(['text'], a:name .. '/file')
|
||||
endfunc
|
||||
|
||||
func Test_mkdir_defer_del()
|
||||
" Xtopdir/tmp is created thus deleted, not Xtopdir itself
|
||||
call mkdir('Xtopdir', 'R')
|
||||
call DoMkdirDel('Xtopdir/tmp')
|
||||
call assert_true(isdirectory('Xtopdir'))
|
||||
call assert_false(isdirectory('Xtopdir/tmp'))
|
||||
|
||||
" Deletion fails because "tmp" contains "sub"
|
||||
call DoMkdirDel('Xtopdir/tmp/sub')
|
||||
call assert_true(isdirectory('Xtopdir'))
|
||||
call assert_true(isdirectory('Xtopdir/tmp'))
|
||||
call delete('Xtopdir/tmp', 'rf')
|
||||
|
||||
" Deletion fails because "tmp" contains "file"
|
||||
call DoMkdirDelAddFile('Xtopdir/tmp')
|
||||
call assert_true(isdirectory('Xtopdir'))
|
||||
call assert_true(isdirectory('Xtopdir/tmp'))
|
||||
call assert_true(filereadable('Xtopdir/tmp/file'))
|
||||
call delete('Xtopdir/tmp', 'rf')
|
||||
|
||||
" Xtopdir/tmp is created thus deleted, not Xtopdir itself
|
||||
call DoMkdirDelRec('Xtopdir/tmp')
|
||||
call assert_true(isdirectory('Xtopdir'))
|
||||
call assert_false(isdirectory('Xtopdir/tmp'))
|
||||
|
||||
" Deletion works even though "tmp" contains "sub"
|
||||
call DoMkdirDelRec('Xtopdir/tmp/sub')
|
||||
call assert_true(isdirectory('Xtopdir'))
|
||||
call assert_false(isdirectory('Xtopdir/tmp'))
|
||||
|
||||
" Deletion works even though "tmp" contains "file"
|
||||
call DoMkdirDelRecAddFile('Xtopdir/tmp')
|
||||
call assert_true(isdirectory('Xtopdir'))
|
||||
call assert_false(isdirectory('Xtopdir/tmp'))
|
||||
endfunc
|
||||
|
||||
func Test_line_continuation()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -532,4 +532,106 @@ func Test_funcdef_alloc_failure()
|
||||
bw!
|
||||
endfunc
|
||||
|
||||
func AddDefer(arg1, ...)
|
||||
call extend(g:deferred, [a:arg1])
|
||||
if a:0 == 1
|
||||
call extend(g:deferred, [a:1])
|
||||
endif
|
||||
endfunc
|
||||
|
||||
func WithDeferTwo()
|
||||
call extend(g:deferred, ['in Two'])
|
||||
for nr in range(3)
|
||||
defer AddDefer('Two' .. nr)
|
||||
endfor
|
||||
call extend(g:deferred, ['end Two'])
|
||||
endfunc
|
||||
|
||||
func WithDeferOne()
|
||||
call extend(g:deferred, ['in One'])
|
||||
call writefile(['text'], 'Xfuncdefer')
|
||||
defer delete('Xfuncdefer')
|
||||
defer AddDefer('One')
|
||||
call WithDeferTwo()
|
||||
call extend(g:deferred, ['end One'])
|
||||
endfunc
|
||||
|
||||
func WithPartialDefer()
|
||||
call extend(g:deferred, ['in Partial'])
|
||||
let Part = funcref('AddDefer', ['arg1'])
|
||||
defer Part("arg2")
|
||||
call extend(g:deferred, ['end Partial'])
|
||||
endfunc
|
||||
|
||||
func Test_defer()
|
||||
let g:deferred = []
|
||||
call WithDeferOne()
|
||||
|
||||
call assert_equal(['in One', 'in Two', 'end Two', 'Two2', 'Two1', 'Two0', 'end One', 'One'], g:deferred)
|
||||
unlet g:deferred
|
||||
|
||||
call assert_equal('', glob('Xfuncdefer'))
|
||||
|
||||
call assert_fails('defer delete("Xfuncdefer")->Another()', 'E488:')
|
||||
call assert_fails('defer delete("Xfuncdefer").member', 'E488:')
|
||||
|
||||
let g:deferred = []
|
||||
call WithPartialDefer()
|
||||
call assert_equal(['in Partial', 'end Partial', 'arg1', 'arg2'], g:deferred)
|
||||
unlet g:deferred
|
||||
|
||||
let Part = funcref('AddDefer', ['arg1'], {})
|
||||
call assert_fails('defer Part("arg2")', 'E1300:')
|
||||
endfunc
|
||||
|
||||
func DeferLevelTwo()
|
||||
call writefile(['text'], 'XDeleteTwo', 'D')
|
||||
throw 'someerror'
|
||||
endfunc
|
||||
|
||||
" def DeferLevelOne()
|
||||
func DeferLevelOne()
|
||||
call writefile(['text'], 'XDeleteOne', 'D')
|
||||
call g:DeferLevelTwo()
|
||||
" enddef
|
||||
endfunc
|
||||
|
||||
func Test_defer_throw()
|
||||
let caught = 'no'
|
||||
try
|
||||
call DeferLevelOne()
|
||||
catch /someerror/
|
||||
let caught = 'yes'
|
||||
endtry
|
||||
call assert_equal('yes', caught)
|
||||
call assert_false(filereadable('XDeleteOne'))
|
||||
call assert_false(filereadable('XDeleteTwo'))
|
||||
endfunc
|
||||
|
||||
func Test_defer_quitall()
|
||||
let lines =<< trim END
|
||||
" vim9script
|
||||
func DeferLevelTwo()
|
||||
call writefile(['text'], 'XQuitallTwo', 'D')
|
||||
qa!
|
||||
endfunc
|
||||
|
||||
" def DeferLevelOne()
|
||||
func DeferLevelOne()
|
||||
call writefile(['text'], 'XQuitallOne', 'D')
|
||||
call DeferLevelTwo()
|
||||
" enddef
|
||||
endfunc
|
||||
|
||||
" DeferLevelOne()
|
||||
call DeferLevelOne()
|
||||
END
|
||||
call writefile(lines, 'XdeferQuitall', 'D')
|
||||
let res = system(GetVimCommandClean() .. ' -X -S XdeferQuitall')
|
||||
call assert_equal(0, v:shell_error)
|
||||
call assert_false(filereadable('XQuitallOne'))
|
||||
call assert_false(filereadable('XQuitallTwo'))
|
||||
endfunc
|
||||
|
||||
|
||||
" vim: shiftwidth=2 sts=2 expandtab
|
||||
|
||||
@@ -924,19 +924,49 @@ 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
|
||||
|
||||
func DoWriteFile()
|
||||
call writefile(['text'], 'Xthefile', 'D')
|
||||
cd ..
|
||||
endfunc
|
||||
|
||||
func Test_write_defer_delete_chdir()
|
||||
let dir = getcwd()
|
||||
call DoWriteFile()
|
||||
call assert_notequal(dir, getcwd())
|
||||
call chdir(dir)
|
||||
call assert_equal('', glob('Xthefile'))
|
||||
endfunc
|
||||
|
||||
" Check that buffer is written before triggering QuitPre
|
||||
|
||||
@@ -17,6 +17,7 @@ local OK = helpers.OK
|
||||
local FAIL = helpers.FAIL
|
||||
local NULL = helpers.NULL
|
||||
local mkdir = helpers.mkdir
|
||||
local endswith = helpers.endswith
|
||||
|
||||
local NODE_NORMAL = 0
|
||||
local NODE_WRITABLE = 1
|
||||
@@ -748,12 +749,17 @@ describe('fs.c', function()
|
||||
|
||||
local function os_mkdir_recurse(path, mode)
|
||||
local failed_str = ffi.new('char *[1]', {nil})
|
||||
local ret = fs.os_mkdir_recurse(path, mode, failed_str)
|
||||
local str = failed_str[0]
|
||||
if str ~= nil then
|
||||
str = ffi.string(str)
|
||||
local created_str = ffi.new('char *[1]', {nil})
|
||||
local ret = fs.os_mkdir_recurse(path, mode, failed_str, created_str)
|
||||
local failed_dir = failed_str[0]
|
||||
if failed_dir ~= nil then
|
||||
failed_dir = ffi.string(failed_dir)
|
||||
end
|
||||
return ret, str
|
||||
local created_dir = created_str[0]
|
||||
if created_dir ~= nil then
|
||||
created_dir = ffi.string(created_dir)
|
||||
end
|
||||
return ret, failed_dir, created_dir
|
||||
end
|
||||
|
||||
describe('os_mkdir', function()
|
||||
@@ -774,33 +780,37 @@ describe('fs.c', function()
|
||||
describe('os_mkdir_recurse', function()
|
||||
itp('returns zero when given an already existing directory', function()
|
||||
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
|
||||
local ret, failed_str = os_mkdir_recurse('unit-test-directory', mode)
|
||||
local ret, failed_dir, created_dir = os_mkdir_recurse('unit-test-directory', mode)
|
||||
eq(0, ret)
|
||||
eq(nil, failed_str)
|
||||
eq(nil, failed_dir)
|
||||
eq(nil, created_dir)
|
||||
end)
|
||||
|
||||
itp('fails to create a directory where there is a file', function()
|
||||
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
|
||||
local ret, failed_str = os_mkdir_recurse(
|
||||
local ret, failed_dir, created_dir = os_mkdir_recurse(
|
||||
'unit-test-directory/test.file', mode)
|
||||
neq(0, ret)
|
||||
eq('unit-test-directory/test.file', failed_str)
|
||||
eq('unit-test-directory/test.file', failed_dir)
|
||||
eq(nil, created_dir)
|
||||
end)
|
||||
|
||||
itp('fails to create a directory where there is a file in path', function()
|
||||
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
|
||||
local ret, failed_str = os_mkdir_recurse(
|
||||
local ret, failed_dir, created_dir = os_mkdir_recurse(
|
||||
'unit-test-directory/test.file/test', mode)
|
||||
neq(0, ret)
|
||||
eq('unit-test-directory/test.file', failed_str)
|
||||
eq('unit-test-directory/test.file', failed_dir)
|
||||
eq(nil, created_dir)
|
||||
end)
|
||||
|
||||
itp('succeeds to create a directory', function()
|
||||
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
|
||||
local ret, failed_str = os_mkdir_recurse(
|
||||
local ret, failed_dir, created_dir = os_mkdir_recurse(
|
||||
'unit-test-directory/new-dir-recurse', mode)
|
||||
eq(0, ret)
|
||||
eq(nil, failed_str)
|
||||
eq(nil, failed_dir)
|
||||
ok(endswith(created_dir, 'unit-test-directory/new-dir-recurse'))
|
||||
eq(true, os_isdir('unit-test-directory/new-dir-recurse'))
|
||||
luv.fs_rmdir('unit-test-directory/new-dir-recurse')
|
||||
eq(false, os_isdir('unit-test-directory/new-dir-recurse'))
|
||||
@@ -808,10 +818,11 @@ describe('fs.c', function()
|
||||
|
||||
itp('succeeds to create a directory ending with ///', function()
|
||||
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
|
||||
local ret, failed_str = os_mkdir_recurse(
|
||||
local ret, failed_dir, created_dir = os_mkdir_recurse(
|
||||
'unit-test-directory/new-dir-recurse///', mode)
|
||||
eq(0, ret)
|
||||
eq(nil, failed_str)
|
||||
eq(nil, failed_dir)
|
||||
ok(endswith(created_dir, 'unit-test-directory/new-dir-recurse'))
|
||||
eq(true, os_isdir('unit-test-directory/new-dir-recurse'))
|
||||
luv.fs_rmdir('unit-test-directory/new-dir-recurse')
|
||||
eq(false, os_isdir('unit-test-directory/new-dir-recurse'))
|
||||
@@ -819,10 +830,11 @@ describe('fs.c', function()
|
||||
|
||||
itp('succeeds to create a directory ending with /', function()
|
||||
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
|
||||
local ret, failed_str = os_mkdir_recurse(
|
||||
local ret, failed_dir, created_dir = os_mkdir_recurse(
|
||||
'unit-test-directory/new-dir-recurse/', mode)
|
||||
eq(0, ret)
|
||||
eq(nil, failed_str)
|
||||
eq(nil, failed_dir)
|
||||
ok(endswith(created_dir, 'unit-test-directory/new-dir-recurse'))
|
||||
eq(true, os_isdir('unit-test-directory/new-dir-recurse'))
|
||||
luv.fs_rmdir('unit-test-directory/new-dir-recurse')
|
||||
eq(false, os_isdir('unit-test-directory/new-dir-recurse'))
|
||||
@@ -830,10 +842,11 @@ describe('fs.c', function()
|
||||
|
||||
itp('succeeds to create a directory tree', function()
|
||||
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
|
||||
local ret, failed_str = os_mkdir_recurse(
|
||||
local ret, failed_dir, created_dir = os_mkdir_recurse(
|
||||
'unit-test-directory/new-dir-recurse/1/2/3', mode)
|
||||
eq(0, ret)
|
||||
eq(nil, failed_str)
|
||||
eq(nil, failed_dir)
|
||||
ok(endswith(created_dir, 'unit-test-directory/new-dir-recurse'))
|
||||
eq(true, os_isdir('unit-test-directory/new-dir-recurse'))
|
||||
eq(true, os_isdir('unit-test-directory/new-dir-recurse/1'))
|
||||
eq(true, os_isdir('unit-test-directory/new-dir-recurse/1/2'))
|
||||
|
||||
Reference in New Issue
Block a user