Skip to content

Commit 6f14da1

Browse files
committed
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.
1 parent d763311 commit 6f14da1

File tree

9 files changed

+163
-27
lines changed

9 files changed

+163
-27
lines changed

runtime/doc/builtin.txt

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6239,8 +6239,26 @@ min({expr}) Return the minimum value of all items in {expr}. Example: >
62396239
mkdir({name} [, {path} [, {prot}]])
62406240
Create directory {name}.
62416241

6242-
If {path} is "p" then intermediate directories are created as
6243-
necessary. Otherwise it must be "".
6242+
If {path} contains "p" then intermediate directories are
6243+
created as necessary. Otherwise it must be "".
6244+
6245+
If {path} contains "D" then {name} is deleted at the end of
6246+
the current function, as with: >
6247+
defer delete({name}, 'd')
6248+
<
6249+
If {path} contains "R" then {name} is deleted recursively at
6250+
the end of the current function, as with: >
6251+
defer delete({name}, 'rf')
6252+
< Note that when {name} has more than one part and "p" is used
6253+
some directories may already exist. Only the first one that
6254+
is created and what it contains is scheduled to be deleted.
6255+
E.g. when using: >
6256+
call mkdir('subdir/tmp/autoload', 'pR')
6257+
< and "subdir" already exists then "subdir/tmp" will be
6258+
scheduled for deletion, like with: >
6259+
defer delete('subdir/tmp', 'rf')
6260+
< Note that if scheduling the defer fails the directory is not
6261+
deleted. This should only happen when out of memory.
62446262

62456263
If {prot} is given it is used to set the protection bits of
62466264
the new directory. The default is 0o755 (rwxr-xr-x: r/w for

src/filepath.c

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1428,10 +1428,12 @@ f_isabsolutepath(typval_T *argvars, typval_T *rettv)
14281428
/*
14291429
* Create the directory in which "dir" is located, and higher levels when
14301430
* needed.
1431+
* Set "created" to the full name of the first created directory. It will be
1432+
* NULL until that happens.
14311433
* Return OK or FAIL.
14321434
*/
14331435
static int
1434-
mkdir_recurse(char_u *dir, int prot)
1436+
mkdir_recurse(char_u *dir, int prot, char_u **created)
14351437
{
14361438
char_u *p;
14371439
char_u *updir;
@@ -1449,8 +1451,12 @@ mkdir_recurse(char_u *dir, int prot)
14491451
return FAIL;
14501452
if (mch_isdir(updir))
14511453
r = OK;
1452-
else if (mkdir_recurse(updir, prot) == OK)
1454+
else if (mkdir_recurse(updir, prot, created) == OK)
1455+
{
14531456
r = vim_mkdir_emsg(updir, prot);
1457+
if (r == OK && created != NULL && *created == NULL)
1458+
*created = FullName_save(updir, FALSE);
1459+
}
14541460
vim_free(updir);
14551461
return r;
14561462
}
@@ -1464,6 +1470,9 @@ f_mkdir(typval_T *argvars, typval_T *rettv)
14641470
char_u *dir;
14651471
char_u buf[NUMBUFLEN];
14661472
int prot = 0755;
1473+
int defer = FALSE;
1474+
int defer_recurse = FALSE;
1475+
char_u *created = NULL;
14671476

14681477
rettv->vval.v_number = FAIL;
14691478
if (check_restricted() || check_secure())
@@ -1486,24 +1495,55 @@ f_mkdir(typval_T *argvars, typval_T *rettv)
14861495

14871496
if (argvars[1].v_type != VAR_UNKNOWN)
14881497
{
1498+
char_u *arg2;
1499+
14891500
if (argvars[2].v_type != VAR_UNKNOWN)
14901501
{
14911502
prot = (int)tv_get_number_chk(&argvars[2], NULL);
14921503
if (prot == -1)
14931504
return;
14941505
}
1495-
if (STRCMP(tv_get_string(&argvars[1]), "p") == 0)
1506+
arg2 = tv_get_string(&argvars[1]);
1507+
defer = vim_strchr(arg2, 'D') != NULL;
1508+
defer_recurse = vim_strchr(arg2, 'R') != NULL;
1509+
if ((defer || defer_recurse) && !can_add_defer())
1510+
return;
1511+
1512+
if (vim_strchr(arg2, 'p') != NULL)
14961513
{
14971514
if (mch_isdir(dir))
14981515
{
14991516
// With the "p" flag it's OK if the dir already exists.
15001517
rettv->vval.v_number = OK;
15011518
return;
15021519
}
1503-
mkdir_recurse(dir, prot);
1520+
mkdir_recurse(dir, prot, defer || defer_recurse ? &created : NULL);
15041521
}
15051522
}
15061523
rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
1524+
1525+
// Handle "D" and "R": deferred deletion of the created directory.
1526+
if (rettv->vval.v_number == OK
1527+
&& created == NULL && (defer || defer_recurse))
1528+
created = FullName_save(dir, FALSE);
1529+
if (created != NULL)
1530+
{
1531+
typval_T tv[2];
1532+
1533+
tv[0].v_type = VAR_STRING;
1534+
tv[0].v_lock = 0;
1535+
tv[0].vval.v_string = created;
1536+
tv[1].v_type = VAR_STRING;
1537+
tv[1].v_lock = 0;
1538+
tv[1].vval.v_string = vim_strsave(
1539+
(char_u *)(defer_recurse ? "rf" : "d"));
1540+
if (tv[0].vval.v_string == NULL || tv[1].vval.v_string == NULL
1541+
|| add_defer((char_u *)"delete", 2, tv) == FAIL)
1542+
{
1543+
vim_free(tv[0].vval.v_string);
1544+
vim_free(tv[1].vval.v_string);
1545+
}
1546+
}
15071547
}
15081548

15091549
/*
@@ -2300,11 +2340,8 @@ f_writefile(typval_T *argvars, typval_T *rettv)
23002340
if (fname == NULL)
23012341
return;
23022342

2303-
if (defer && !in_def_function() && get_current_funccal() == NULL)
2304-
{
2305-
semsg(_(e_str_not_inside_function), "defer");
2343+
if (defer && !can_add_defer())
23062344
return;
2307-
}
23082345

23092346
// Always open the file in binary mode, library functions have a mind of
23102347
// their own about CR-LF conversion.
@@ -2323,7 +2360,7 @@ f_writefile(typval_T *argvars, typval_T *rettv)
23232360

23242361
tv.v_type = VAR_STRING;
23252362
tv.v_lock = 0;
2326-
tv.vval.v_string = vim_strsave(fname);
2363+
tv.vval.v_string = FullName_save(fname, FALSE);
23272364
if (tv.vval.v_string == NULL
23282365
|| add_defer((char_u *)"delete", 1, &tv) == FAIL)
23292366
{

src/proto/userfunc.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ void func_ptr_unref(ufunc_T *fp);
6060
void func_ref(char_u *name);
6161
void func_ptr_ref(ufunc_T *fp);
6262
void ex_return(exarg_T *eap);
63+
int can_add_defer(void);
6364
int add_defer(char_u *name, int argcount_arg, typval_T *argvars);
6465
void invoke_all_defer(void);
6566
void ex_call(exarg_T *eap);

src/testdir/test_autochdir.vim

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ endfunc
2828
func Test_set_filename_other_window()
2929
let cwd = getcwd()
3030
call test_autochdir()
31-
call mkdir('Xa')
32-
call mkdir('Xb')
33-
call mkdir('Xc')
31+
call mkdir('Xa', 'R')
32+
call mkdir('Xb', 'R')
33+
call mkdir('Xc', 'R')
3434
try
3535
args Xa/aaa.txt Xb/bbb.txt
3636
set acd
@@ -45,9 +45,6 @@ func Test_set_filename_other_window()
4545
bwipe! aaa.txt
4646
bwipe! bbb.txt
4747
bwipe! ccc.txt
48-
call delete('Xa', 'rf')
49-
call delete('Xb', 'rf')
50-
call delete('Xc', 'rf')
5148
endtry
5249
endfunc
5350

@@ -56,7 +53,7 @@ func Test_acd_win_execute()
5653
set acd
5754
call test_autochdir()
5855

59-
call mkdir('XacdDir')
56+
call mkdir('XacdDir', 'R')
6057
let winid = win_getid()
6158
new XacdDir/file
6259
call assert_match('testdir.XacdDir$', getcwd())
@@ -68,7 +65,6 @@ func Test_acd_win_execute()
6865
bwipe!
6966
set noacd
7067
call chdir(cwd)
71-
call delete('XacdDir', 'rf')
7268
endfunc
7369

7470
func Test_verbose_pwd()
@@ -78,7 +74,7 @@ func Test_verbose_pwd()
7874
edit global.txt
7975
call assert_match('\[global\].*testdir$', execute('verbose pwd'))
8076

81-
call mkdir('Xautodir')
77+
call mkdir('Xautodir', 'R')
8278
split Xautodir/local.txt
8379
lcd Xautodir
8480
call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd'))
@@ -112,7 +108,6 @@ func Test_verbose_pwd()
112108

113109
bwipe!
114110
call chdir(cwd)
115-
call delete('Xautodir', 'rf')
116111
endfunc
117112

118113
func Test_multibyte()

src/testdir/test_autocmd.vim

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -707,14 +707,13 @@ func Test_BufEnter()
707707
call assert_equal('++', g:val)
708708

709709
" Also get BufEnter when editing a directory
710-
call mkdir('Xbufenterdir')
710+
call mkdir('Xbufenterdir', 'D')
711711
split Xbufenterdir
712712
call assert_equal('+++', g:val)
713713

714714
" On MS-Windows we can't edit the directory, make sure we wipe the right
715715
" buffer.
716716
bwipe! Xbufenterdir
717-
call delete('Xbufenterdir', 'd')
718717
au! BufEnter
719718

720719
" Editing a "nofile" buffer doesn't read the file but does trigger BufEnter
@@ -1902,11 +1901,10 @@ func Test_BufWriteCmd()
19021901
new
19031902
file Xbufwritecmd
19041903
set buftype=acwrite
1905-
call mkdir('Xbufwritecmd')
1904+
call mkdir('Xbufwritecmd', 'D')
19061905
write
19071906
" BufWriteCmd should be triggered even if a directory has the same name
19081907
call assert_equal(1, g:written)
1909-
call delete('Xbufwritecmd', 'd')
19101908
unlet g:written
19111909
au! BufWriteCmd
19121910
bwipe!
@@ -2710,7 +2708,7 @@ func Test_throw_in_BufWritePre()
27102708
endfunc
27112709

27122710
func Test_autocmd_in_try_block()
2713-
call mkdir('Xintrydir')
2711+
call mkdir('Xintrydir', 'R')
27142712
au BufEnter * let g:fname = expand('%')
27152713
try
27162714
edit Xintrydir/
@@ -2719,7 +2717,6 @@ func Test_autocmd_in_try_block()
27192717

27202718
unlet g:fname
27212719
au! BufEnter
2722-
call delete('Xintrydir', 'rf')
27232720
endfunc
27242721

27252722
func Test_autocmd_SafeState()

src/testdir/test_eval_stuff.vim

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,64 @@ func Test_mkdir_p()
4444
call assert_fails('call mkdir("abc", [], [])', 'E745:')
4545
endfunc
4646

47+
func DoMkdirDel(name)
48+
call mkdir(a:name, 'pD')
49+
call assert_true(isdirectory(a:name))
50+
endfunc
51+
52+
func DoMkdirDelAddFile(name)
53+
call mkdir(a:name, 'pD')
54+
call assert_true(isdirectory(a:name))
55+
call writefile(['text'], a:name .. '/file')
56+
endfunc
57+
58+
func DoMkdirDelRec(name)
59+
call mkdir(a:name, 'pR')
60+
call assert_true(isdirectory(a:name))
61+
endfunc
62+
63+
func DoMkdirDelRecAddFile(name)
64+
call mkdir(a:name, 'pR')
65+
call assert_true(isdirectory(a:name))
66+
call writefile(['text'], a:name .. '/file')
67+
endfunc
68+
69+
func Test_mkdir_defer_del()
70+
" Xtopdir/tmp is created thus deleted, not Xtopdir itself
71+
call mkdir('Xtopdir', 'R')
72+
call DoMkdirDel('Xtopdir/tmp')
73+
call assert_true(isdirectory('Xtopdir'))
74+
call assert_false(isdirectory('Xtopdir/tmp'))
75+
76+
" Deletion fails because "tmp" contains "sub"
77+
call DoMkdirDel('Xtopdir/tmp/sub')
78+
call assert_true(isdirectory('Xtopdir'))
79+
call assert_true(isdirectory('Xtopdir/tmp'))
80+
call delete('Xtopdir/tmp', 'rf')
81+
82+
" Deletion fails because "tmp" contains "file"
83+
call DoMkdirDelAddFile('Xtopdir/tmp')
84+
call assert_true(isdirectory('Xtopdir'))
85+
call assert_true(isdirectory('Xtopdir/tmp'))
86+
call assert_true(filereadable('Xtopdir/tmp/file'))
87+
call delete('Xtopdir/tmp', 'rf')
88+
89+
" Xtopdir/tmp is created thus deleted, not Xtopdir itself
90+
call DoMkdirDelRec('Xtopdir/tmp')
91+
call assert_true(isdirectory('Xtopdir'))
92+
call assert_false(isdirectory('Xtopdir/tmp'))
93+
94+
" Deletion works even though "tmp" contains "sub"
95+
call DoMkdirDelRec('Xtopdir/tmp/sub')
96+
call assert_true(isdirectory('Xtopdir'))
97+
call assert_false(isdirectory('Xtopdir/tmp'))
98+
99+
" Deletion works even though "tmp" contains "file"
100+
call DoMkdirDelRecAddFile('Xtopdir/tmp')
101+
call assert_true(isdirectory('Xtopdir'))
102+
call assert_false(isdirectory('Xtopdir/tmp'))
103+
endfunc
104+
47105
func Test_line_continuation()
48106
let array = [5,
49107
"\ ignore this

src/testdir/test_writefile.vim

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,19 @@ func Test_write_with_deferred_delete()
950950
call assert_equal('', glob('XdefdeferDelete'))
951951
endfunc
952952

953+
func DoWriteFile()
954+
call writefile(['text'], 'Xthefile', 'D')
955+
cd ..
956+
endfunc
957+
958+
func Test_write_defer_delete_chdir()
959+
let dir = getcwd()
960+
call DoWriteFile()
961+
call assert_notequal(dir, getcwd())
962+
call chdir(dir)
963+
call assert_equal('', glob('Xthefile'))
964+
endfunc
965+
953966
" Check that buffer is written before triggering QuitPre
954967
func Test_wq_quitpre_autocommand()
955968
edit Xsomefile

src/userfunc.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5649,6 +5649,21 @@ ex_defer_inner(
56495649
return add_defer(name, argcount, argvars);
56505650
}
56515651

5652+
/*
5653+
* Return TRUE if currently inside a function call.
5654+
* Give an error message and return FALSE when not.
5655+
*/
5656+
int
5657+
can_add_defer(void)
5658+
{
5659+
if (!in_def_function() && get_current_funccal() == NULL)
5660+
{
5661+
semsg(_(e_str_not_inside_function), "defer");
5662+
return FALSE;
5663+
}
5664+
return TRUE;
5665+
}
5666+
56525667
/*
56535668
* Add a deferred call for "name" with arguments "argvars[argcount]".
56545669
* Consumes "argvars[]".

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,8 @@ static char *(features[]) =
703703

704704
static int included_patches[] =
705705
{ /* Add new patch number below this line */
706+
/**/
707+
411,
706708
/**/
707709
410,
708710
/**/

0 commit comments

Comments
 (0)