aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2023-04-16 11:46:17 +0800
committerzeertzjq <zeertzjq@outlook.com>2023-04-16 15:04:41 +0800
commitf39b33ee491a4a8d4b08425e582dd0dd53617edf (patch)
tree0214457dc93e79a07307268ea5015ecc49579c9c
parent7b05ddbb72717f995fedc81583d73f82c78c881d (diff)
downloadrneovim-f39b33ee491a4a8d4b08425e582dd0dd53617edf.tar.gz
rneovim-f39b33ee491a4a8d4b08425e582dd0dd53617edf.tar.bz2
rneovim-f39b33ee491a4a8d4b08425e582dd0dd53617edf.zip
vim-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. https://github.com/vim/vim/commit/6f14da15ac900589f2f413d77898b9bff3b31ece vim-patch:8.2.3742: dec mouse test fails without gnome terminfo entry Problem: Dec mouse test fails without gnome terminfo entry. Solution: Check if there is a gnome entry. Also fix 'acd' test on MS-Windows. (Dominique Pellé, closes vim/vim#9282) https://github.com/vim/vim/commit/f589fd3e1047cdf90566b68aaf9a13389e54d26a Cherry-pick test_autochdir.vim changes from patch 9.0.0313. Cherry-pick test_autocmd.vim changes from patch 9.0.0323. Co-authored-by: Bram Moolenaar <Bram@vim.org>
-rw-r--r--runtime/doc/builtin.txt20
-rw-r--r--src/nvim/eval/funcs.c41
-rw-r--r--src/nvim/eval/userfunc.c11
-rw-r--r--src/nvim/fileio.c4
-rw-r--r--src/nvim/log.c2
-rw-r--r--src/nvim/memline.c2
-rw-r--r--src/nvim/os/fs.c11
-rw-r--r--src/nvim/shada.c2
-rw-r--r--src/nvim/undo.c2
-rw-r--r--test/old/testdir/test_autochdir.vim19
-rw-r--r--test/old/testdir/test_autocmd.vim17
-rw-r--r--test/old/testdir/test_eval_stuff.vim64
-rw-r--r--test/old/testdir/test_writefile.vim13
-rw-r--r--test/unit/os/fs_spec.lua2
14 files changed, 166 insertions, 44 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index adb3164429..15ccfd9b92 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -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
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 66fd663e5e..f53b283c79 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -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
@@ -9332,8 +9360,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- if (defer && get_current_funccal() == NULL) {
- semsg(_(e_str_not_inside_function), "defer");
+ if (defer && !can_add_defer()) {
return;
}
@@ -9351,7 +9378,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
typval_T tv = {
.v_type = VAR_STRING,
.v_lock = VAR_UNLOCKED,
- .vval.v_string = xstrdup(fname),
+ .vval.v_string = FullName_save(fname, false),
};
add_defer("delete", 1, &tv);
}
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index 0a8e5c349a..808fb316fe 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -3153,6 +3153,17 @@ static int ex_defer_inner(char *name, char **arg, const partial_T *const partial
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.
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index d4725ccd86..fa2f72932f 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -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);
diff --git a/src/nvim/log.c b/src/nvim/log.c
index 77eeb09fec..4de0c4d88c 100644
--- a/src/nvim/log.c
+++ b/src/nvim/log.c
@@ -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.
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 18d5e75a53..0c38f18739 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -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));
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index cb51e81005..872d9c9314 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -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);
}
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 78499922bf..fcb8a15cde 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -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));
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 132c84231f..7eb0d390fc 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -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);
diff --git a/test/old/testdir/test_autochdir.vim b/test/old/testdir/test_autochdir.vim
index a8810047a0..652ce8b794 100644
--- a/test/old/testdir/test_autochdir.vim
+++ b/test/old/testdir/test_autochdir.vim
@@ -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()
diff --git a/test/old/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim
index 6d7f1649b3..ec671369f5 100644
--- a/test/old/testdir/test_autocmd.vim
+++ b/test/old/testdir/test_autocmd.vim
@@ -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()
diff --git a/test/old/testdir/test_eval_stuff.vim b/test/old/testdir/test_eval_stuff.vim
index 7acc91c17b..20eb873326 100644
--- a/test/old/testdir/test_eval_stuff.vim
+++ b/test/old/testdir/test_eval_stuff.vim
@@ -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()
diff --git a/test/old/testdir/test_writefile.vim b/test/old/testdir/test_writefile.vim
index 5e6428ded8..312d45e18f 100644
--- a/test/old/testdir/test_writefile.vim
+++ b/test/old/testdir/test_writefile.vim
@@ -956,6 +956,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
diff --git a/test/unit/os/fs_spec.lua b/test/unit/os/fs_spec.lua
index 95a12f5b17..c32098a395 100644
--- a/test/unit/os/fs_spec.lua
+++ b/test/unit/os/fs_spec.lua
@@ -748,7 +748,7 @@ 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 ret = fs.os_mkdir_recurse(path, mode, failed_str, nil)
local str = failed_str[0]
if str ~= nil then
str = ffi.string(str)