0
0
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:
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}]]) 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

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 * 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)
{ {

View File

@@ -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);

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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[]".

View File

@@ -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,
/**/ /**/