0
0
mirror of https://github.com/vim/vim.git synced 2025-09-24 03:44:06 -04:00

patch 9.0.0411: only created files can be cleaned up with one call

Problem:    Only created files can be cleaned up with one call.
Solution:   Add flags to mkdir() to delete with a deferred function.
            Expand the writefile() name to a full path to handle changing
            directory.
This commit is contained in:
Bram Moolenaar
2022-09-07 21:30:44 +01:00
parent d7633114af
commit 6f14da15ac
9 changed files with 163 additions and 27 deletions

View File

@@ -6239,8 +6239,26 @@ min({expr}) Return the minimum value of all items in {expr}. Example: >
mkdir({name} [, {path} [, {prot}]])
Create directory {name}.
If {path} is "p" then intermediate directories are created as
necessary. Otherwise it must be "".
If {path} contains "p" then intermediate directories are
created as necessary. Otherwise it must be "".
If {path} contains "D" then {name} is deleted at the end of
the current function, as with: >
defer delete({name}, 'd')
<
If {path} 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')
< Note that if scheduling the defer fails the directory is not
deleted. This should only happen when out of memory.
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

View File

@@ -1428,10 +1428,12 @@ f_isabsolutepath(typval_T *argvars, typval_T *rettv)
/*
* Create the directory in which "dir" is located, and higher levels when
* needed.
* Set "created" to the full name of the first created directory. It will be
* NULL until that happens.
* Return OK or FAIL.
*/
static int
mkdir_recurse(char_u *dir, int prot)
mkdir_recurse(char_u *dir, int prot, char_u **created)
{
char_u *p;
char_u *updir;
@@ -1449,8 +1451,12 @@ mkdir_recurse(char_u *dir, int prot)
return FAIL;
if (mch_isdir(updir))
r = OK;
else if (mkdir_recurse(updir, prot) == OK)
else if (mkdir_recurse(updir, prot, created) == OK)
{
r = vim_mkdir_emsg(updir, prot);
if (r == OK && created != NULL && *created == NULL)
*created = FullName_save(updir, FALSE);
}
vim_free(updir);
return r;
}
@@ -1464,6 +1470,9 @@ f_mkdir(typval_T *argvars, typval_T *rettv)
char_u *dir;
char_u buf[NUMBUFLEN];
int prot = 0755;
int defer = FALSE;
int defer_recurse = FALSE;
char_u *created = NULL;
rettv->vval.v_number = FAIL;
if (check_restricted() || check_secure())
@@ -1486,13 +1495,21 @@ f_mkdir(typval_T *argvars, typval_T *rettv)
if (argvars[1].v_type != VAR_UNKNOWN)
{
char_u *arg2;
if (argvars[2].v_type != VAR_UNKNOWN)
{
prot = (int)tv_get_number_chk(&argvars[2], NULL);
if (prot == -1)
return;
}
if (STRCMP(tv_get_string(&argvars[1]), "p") == 0)
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)
{
if (mch_isdir(dir))
{
@@ -1500,10 +1517,33 @@ f_mkdir(typval_T *argvars, typval_T *rettv)
rettv->vval.v_number = OK;
return;
}
mkdir_recurse(dir, prot);
mkdir_recurse(dir, prot, defer || defer_recurse ? &created : NULL);
}
}
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 = 0;
tv[0].vval.v_string = created;
tv[1].v_type = VAR_STRING;
tv[1].v_lock = 0;
tv[1].vval.v_string = vim_strsave(
(char_u *)(defer_recurse ? "rf" : "d"));
if (tv[0].vval.v_string == NULL || tv[1].vval.v_string == NULL
|| add_defer((char_u *)"delete", 2, tv) == FAIL)
{
vim_free(tv[0].vval.v_string);
vim_free(tv[1].vval.v_string);
}
}
}
/*
@@ -2300,11 +2340,8 @@ f_writefile(typval_T *argvars, typval_T *rettv)
if (fname == NULL)
return;
if (defer && !in_def_function() && get_current_funccal() == NULL)
{
semsg(_(e_str_not_inside_function), "defer");
if (defer && !can_add_defer())
return;
}
// Always open the file in binary mode, library functions have a mind of
// their own about CR-LF conversion.
@@ -2323,7 +2360,7 @@ f_writefile(typval_T *argvars, typval_T *rettv)
tv.v_type = VAR_STRING;
tv.v_lock = 0;
tv.vval.v_string = vim_strsave(fname);
tv.vval.v_string = FullName_save(fname, FALSE);
if (tv.vval.v_string == NULL
|| add_defer((char_u *)"delete", 1, &tv) == FAIL)
{

View File

@@ -60,6 +60,7 @@ void func_ptr_unref(ufunc_T *fp);
void func_ref(char_u *name);
void func_ptr_ref(ufunc_T *fp);
void ex_return(exarg_T *eap);
int can_add_defer(void);
int add_defer(char_u *name, int argcount_arg, typval_T *argvars);
void invoke_all_defer(void);
void ex_call(exarg_T *eap);

View File

@@ -28,9 +28,9 @@ endfunc
func Test_set_filename_other_window()
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
@@ -45,9 +45,6 @@ func Test_set_filename_other_window()
bwipe! aaa.txt
bwipe! bbb.txt
bwipe! ccc.txt
call delete('Xa', 'rf')
call delete('Xb', 'rf')
call delete('Xc', 'rf')
endtry
endfunc
@@ -56,7 +53,7 @@ func Test_acd_win_execute()
set acd
call test_autochdir()
call mkdir('XacdDir')
call mkdir('XacdDir', 'R')
let winid = win_getid()
new XacdDir/file
call assert_match('testdir.XacdDir$', getcwd())
@@ -68,7 +65,6 @@ func Test_acd_win_execute()
bwipe!
set noacd
call chdir(cwd)
call delete('XacdDir', 'rf')
endfunc
func Test_verbose_pwd()
@@ -78,7 +74,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'))
@@ -112,7 +108,6 @@ func Test_verbose_pwd()
bwipe!
call chdir(cwd)
call delete('Xautodir', 'rf')
endfunc
func Test_multibyte()

View File

@@ -707,14 +707,13 @@ func Test_BufEnter()
call assert_equal('++', g:val)
" Also get BufEnter when editing a directory
call mkdir('Xbufenterdir')
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! Xbufenterdir
call delete('Xbufenterdir', 'd')
au! BufEnter
" Editing a "nofile" buffer doesn't read the file but does trigger BufEnter
@@ -1902,11 +1901,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!
@@ -2710,7 +2708,7 @@ func Test_throw_in_BufWritePre()
endfunc
func Test_autocmd_in_try_block()
call mkdir('Xintrydir')
call mkdir('Xintrydir', 'R')
au BufEnter * let g:fname = expand('%')
try
edit Xintrydir/
@@ -2719,7 +2717,6 @@ func Test_autocmd_in_try_block()
unlet g:fname
au! BufEnter
call delete('Xintrydir', 'rf')
endfunc
func Test_autocmd_SafeState()

View File

@@ -44,6 +44,64 @@ func Test_mkdir_p()
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()
let array = [5,
"\ ignore this

View File

@@ -950,6 +950,19 @@ func Test_write_with_deferred_delete()
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
func Test_wq_quitpre_autocommand()
edit Xsomefile

View File

@@ -5649,6 +5649,21 @@ ex_defer_inner(
return add_defer(name, argcount, argvars);
}
/*
* Return TRUE if currently inside a function call.
* Give an error message and return FALSE when not.
*/
int
can_add_defer(void)
{
if (!in_def_function() && 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[]".

View File

@@ -703,6 +703,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
411,
/**/
410,
/**/