mirror of
https://github.com/vim/vim.git
synced 2025-09-25 03:54:15 -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:
@@ -6239,8 +6239,26 @@ min({expr}) Return the minimum value of all items in {expr}. Example: >
|
|||||||
mkdir({name} [, {path} [, {prot}]])
|
mkdir({name} [, {path} [, {prot}]])
|
||||||
Create directory {name}.
|
Create directory {name}.
|
||||||
|
|
||||||
If {path} is "p" then intermediate directories are created as
|
If {path} contains "p" then intermediate directories are
|
||||||
necessary. Otherwise it must be "".
|
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
|
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
|
the new directory. The default is 0o755 (rwxr-xr-x: r/w for
|
||||||
|
@@ -1428,10 +1428,12 @@ f_isabsolutepath(typval_T *argvars, typval_T *rettv)
|
|||||||
/*
|
/*
|
||||||
* Create the directory in which "dir" is located, and higher levels when
|
* Create the directory in which "dir" is located, and higher levels when
|
||||||
* needed.
|
* needed.
|
||||||
|
* Set "created" to the full name of the first created directory. It will be
|
||||||
|
* NULL until that happens.
|
||||||
* Return OK or FAIL.
|
* Return OK or FAIL.
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
mkdir_recurse(char_u *dir, int prot)
|
mkdir_recurse(char_u *dir, int prot, char_u **created)
|
||||||
{
|
{
|
||||||
char_u *p;
|
char_u *p;
|
||||||
char_u *updir;
|
char_u *updir;
|
||||||
@@ -1449,8 +1451,12 @@ mkdir_recurse(char_u *dir, int prot)
|
|||||||
return FAIL;
|
return FAIL;
|
||||||
if (mch_isdir(updir))
|
if (mch_isdir(updir))
|
||||||
r = OK;
|
r = OK;
|
||||||
else if (mkdir_recurse(updir, prot) == OK)
|
else if (mkdir_recurse(updir, prot, created) == OK)
|
||||||
|
{
|
||||||
r = vim_mkdir_emsg(updir, prot);
|
r = vim_mkdir_emsg(updir, prot);
|
||||||
|
if (r == OK && created != NULL && *created == NULL)
|
||||||
|
*created = FullName_save(updir, FALSE);
|
||||||
|
}
|
||||||
vim_free(updir);
|
vim_free(updir);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@@ -1464,6 +1470,9 @@ f_mkdir(typval_T *argvars, typval_T *rettv)
|
|||||||
char_u *dir;
|
char_u *dir;
|
||||||
char_u buf[NUMBUFLEN];
|
char_u buf[NUMBUFLEN];
|
||||||
int prot = 0755;
|
int prot = 0755;
|
||||||
|
int defer = FALSE;
|
||||||
|
int defer_recurse = FALSE;
|
||||||
|
char_u *created = NULL;
|
||||||
|
|
||||||
rettv->vval.v_number = FAIL;
|
rettv->vval.v_number = FAIL;
|
||||||
if (check_restricted() || check_secure())
|
if (check_restricted() || check_secure())
|
||||||
@@ -1486,13 +1495,21 @@ f_mkdir(typval_T *argvars, typval_T *rettv)
|
|||||||
|
|
||||||
if (argvars[1].v_type != VAR_UNKNOWN)
|
if (argvars[1].v_type != VAR_UNKNOWN)
|
||||||
{
|
{
|
||||||
|
char_u *arg2;
|
||||||
|
|
||||||
if (argvars[2].v_type != VAR_UNKNOWN)
|
if (argvars[2].v_type != VAR_UNKNOWN)
|
||||||
{
|
{
|
||||||
prot = (int)tv_get_number_chk(&argvars[2], NULL);
|
prot = (int)tv_get_number_chk(&argvars[2], NULL);
|
||||||
if (prot == -1)
|
if (prot == -1)
|
||||||
return;
|
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))
|
if (mch_isdir(dir))
|
||||||
{
|
{
|
||||||
@@ -1500,10 +1517,33 @@ f_mkdir(typval_T *argvars, typval_T *rettv)
|
|||||||
rettv->vval.v_number = OK;
|
rettv->vval.v_number = OK;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mkdir_recurse(dir, prot);
|
mkdir_recurse(dir, prot, defer || defer_recurse ? &created : NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
|
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)
|
if (fname == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (defer && !in_def_function() && get_current_funccal() == NULL)
|
if (defer && !can_add_defer())
|
||||||
{
|
|
||||||
semsg(_(e_str_not_inside_function), "defer");
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
// Always open the file in binary mode, library functions have a mind of
|
// Always open the file in binary mode, library functions have a mind of
|
||||||
// their own about CR-LF conversion.
|
// 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_type = VAR_STRING;
|
||||||
tv.v_lock = 0;
|
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
|
if (tv.vval.v_string == NULL
|
||||||
|| add_defer((char_u *)"delete", 1, &tv) == FAIL)
|
|| add_defer((char_u *)"delete", 1, &tv) == FAIL)
|
||||||
{
|
{
|
||||||
|
@@ -60,6 +60,7 @@ void func_ptr_unref(ufunc_T *fp);
|
|||||||
void func_ref(char_u *name);
|
void func_ref(char_u *name);
|
||||||
void func_ptr_ref(ufunc_T *fp);
|
void func_ptr_ref(ufunc_T *fp);
|
||||||
void ex_return(exarg_T *eap);
|
void ex_return(exarg_T *eap);
|
||||||
|
int can_add_defer(void);
|
||||||
int add_defer(char_u *name, int argcount_arg, typval_T *argvars);
|
int add_defer(char_u *name, int argcount_arg, typval_T *argvars);
|
||||||
void invoke_all_defer(void);
|
void invoke_all_defer(void);
|
||||||
void ex_call(exarg_T *eap);
|
void ex_call(exarg_T *eap);
|
||||||
|
@@ -28,9 +28,9 @@ endfunc
|
|||||||
func Test_set_filename_other_window()
|
func Test_set_filename_other_window()
|
||||||
let cwd = getcwd()
|
let cwd = getcwd()
|
||||||
call test_autochdir()
|
call test_autochdir()
|
||||||
call mkdir('Xa')
|
call mkdir('Xa', 'R')
|
||||||
call mkdir('Xb')
|
call mkdir('Xb', 'R')
|
||||||
call mkdir('Xc')
|
call mkdir('Xc', 'R')
|
||||||
try
|
try
|
||||||
args Xa/aaa.txt Xb/bbb.txt
|
args Xa/aaa.txt Xb/bbb.txt
|
||||||
set acd
|
set acd
|
||||||
@@ -45,9 +45,6 @@ func Test_set_filename_other_window()
|
|||||||
bwipe! aaa.txt
|
bwipe! aaa.txt
|
||||||
bwipe! bbb.txt
|
bwipe! bbb.txt
|
||||||
bwipe! ccc.txt
|
bwipe! ccc.txt
|
||||||
call delete('Xa', 'rf')
|
|
||||||
call delete('Xb', 'rf')
|
|
||||||
call delete('Xc', 'rf')
|
|
||||||
endtry
|
endtry
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
@@ -56,7 +53,7 @@ func Test_acd_win_execute()
|
|||||||
set acd
|
set acd
|
||||||
call test_autochdir()
|
call test_autochdir()
|
||||||
|
|
||||||
call mkdir('XacdDir')
|
call mkdir('XacdDir', 'R')
|
||||||
let winid = win_getid()
|
let winid = win_getid()
|
||||||
new XacdDir/file
|
new XacdDir/file
|
||||||
call assert_match('testdir.XacdDir$', getcwd())
|
call assert_match('testdir.XacdDir$', getcwd())
|
||||||
@@ -68,7 +65,6 @@ func Test_acd_win_execute()
|
|||||||
bwipe!
|
bwipe!
|
||||||
set noacd
|
set noacd
|
||||||
call chdir(cwd)
|
call chdir(cwd)
|
||||||
call delete('XacdDir', 'rf')
|
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
func Test_verbose_pwd()
|
func Test_verbose_pwd()
|
||||||
@@ -78,7 +74,7 @@ func Test_verbose_pwd()
|
|||||||
edit global.txt
|
edit global.txt
|
||||||
call assert_match('\[global\].*testdir$', execute('verbose pwd'))
|
call assert_match('\[global\].*testdir$', execute('verbose pwd'))
|
||||||
|
|
||||||
call mkdir('Xautodir')
|
call mkdir('Xautodir', 'R')
|
||||||
split Xautodir/local.txt
|
split Xautodir/local.txt
|
||||||
lcd Xautodir
|
lcd Xautodir
|
||||||
call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd'))
|
call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd'))
|
||||||
@@ -112,7 +108,6 @@ func Test_verbose_pwd()
|
|||||||
|
|
||||||
bwipe!
|
bwipe!
|
||||||
call chdir(cwd)
|
call chdir(cwd)
|
||||||
call delete('Xautodir', 'rf')
|
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
func Test_multibyte()
|
func Test_multibyte()
|
||||||
|
@@ -707,14 +707,13 @@ func Test_BufEnter()
|
|||||||
call assert_equal('++', g:val)
|
call assert_equal('++', g:val)
|
||||||
|
|
||||||
" Also get BufEnter when editing a directory
|
" Also get BufEnter when editing a directory
|
||||||
call mkdir('Xbufenterdir')
|
call mkdir('Xbufenterdir', 'D')
|
||||||
split Xbufenterdir
|
split Xbufenterdir
|
||||||
call assert_equal('+++', g:val)
|
call assert_equal('+++', g:val)
|
||||||
|
|
||||||
" On MS-Windows we can't edit the directory, make sure we wipe the right
|
" On MS-Windows we can't edit the directory, make sure we wipe the right
|
||||||
" buffer.
|
" buffer.
|
||||||
bwipe! Xbufenterdir
|
bwipe! Xbufenterdir
|
||||||
call delete('Xbufenterdir', 'd')
|
|
||||||
au! BufEnter
|
au! BufEnter
|
||||||
|
|
||||||
" Editing a "nofile" buffer doesn't read the file but does trigger BufEnter
|
" Editing a "nofile" buffer doesn't read the file but does trigger BufEnter
|
||||||
@@ -1902,11 +1901,10 @@ func Test_BufWriteCmd()
|
|||||||
new
|
new
|
||||||
file Xbufwritecmd
|
file Xbufwritecmd
|
||||||
set buftype=acwrite
|
set buftype=acwrite
|
||||||
call mkdir('Xbufwritecmd')
|
call mkdir('Xbufwritecmd', 'D')
|
||||||
write
|
write
|
||||||
" BufWriteCmd should be triggered even if a directory has the same name
|
" BufWriteCmd should be triggered even if a directory has the same name
|
||||||
call assert_equal(1, g:written)
|
call assert_equal(1, g:written)
|
||||||
call delete('Xbufwritecmd', 'd')
|
|
||||||
unlet g:written
|
unlet g:written
|
||||||
au! BufWriteCmd
|
au! BufWriteCmd
|
||||||
bwipe!
|
bwipe!
|
||||||
@@ -2710,7 +2708,7 @@ func Test_throw_in_BufWritePre()
|
|||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
func Test_autocmd_in_try_block()
|
func Test_autocmd_in_try_block()
|
||||||
call mkdir('Xintrydir')
|
call mkdir('Xintrydir', 'R')
|
||||||
au BufEnter * let g:fname = expand('%')
|
au BufEnter * let g:fname = expand('%')
|
||||||
try
|
try
|
||||||
edit Xintrydir/
|
edit Xintrydir/
|
||||||
@@ -2719,7 +2717,6 @@ func Test_autocmd_in_try_block()
|
|||||||
|
|
||||||
unlet g:fname
|
unlet g:fname
|
||||||
au! BufEnter
|
au! BufEnter
|
||||||
call delete('Xintrydir', 'rf')
|
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
func Test_autocmd_SafeState()
|
func Test_autocmd_SafeState()
|
||||||
|
@@ -44,6 +44,64 @@ func Test_mkdir_p()
|
|||||||
call assert_fails('call mkdir("abc", [], [])', 'E745:')
|
call assert_fails('call mkdir("abc", [], [])', 'E745:')
|
||||||
endfunc
|
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()
|
func Test_line_continuation()
|
||||||
let array = [5,
|
let array = [5,
|
||||||
"\ ignore this
|
"\ ignore this
|
||||||
|
@@ -950,6 +950,19 @@ func Test_write_with_deferred_delete()
|
|||||||
call assert_equal('', glob('XdefdeferDelete'))
|
call assert_equal('', glob('XdefdeferDelete'))
|
||||||
endfunc
|
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
|
" Check that buffer is written before triggering QuitPre
|
||||||
func Test_wq_quitpre_autocommand()
|
func Test_wq_quitpre_autocommand()
|
||||||
edit Xsomefile
|
edit Xsomefile
|
||||||
|
@@ -5649,6 +5649,21 @@ ex_defer_inner(
|
|||||||
return add_defer(name, argcount, argvars);
|
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]".
|
* Add a deferred call for "name" with arguments "argvars[argcount]".
|
||||||
* Consumes "argvars[]".
|
* Consumes "argvars[]".
|
||||||
|
@@ -703,6 +703,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
411,
|
||||||
/**/
|
/**/
|
||||||
410,
|
410,
|
||||||
/**/
|
/**/
|
||||||
|
Reference in New Issue
Block a user