diff options
author | zeertzjq <zeertzjq@outlook.com> | 2023-04-16 11:46:17 +0800 |
---|---|---|
committer | zeertzjq <zeertzjq@outlook.com> | 2023-04-16 15:04:41 +0800 |
commit | f39b33ee491a4a8d4b08425e582dd0dd53617edf (patch) | |
tree | 0214457dc93e79a07307268ea5015ecc49579c9c | |
parent | 7b05ddbb72717f995fedc81583d73f82c78c881d (diff) | |
download | rneovim-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.txt | 20 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 41 | ||||
-rw-r--r-- | src/nvim/eval/userfunc.c | 11 | ||||
-rw-r--r-- | src/nvim/fileio.c | 4 | ||||
-rw-r--r-- | src/nvim/log.c | 2 | ||||
-rw-r--r-- | src/nvim/memline.c | 2 | ||||
-rw-r--r-- | src/nvim/os/fs.c | 11 | ||||
-rw-r--r-- | src/nvim/shada.c | 2 | ||||
-rw-r--r-- | src/nvim/undo.c | 2 | ||||
-rw-r--r-- | test/old/testdir/test_autochdir.vim | 19 | ||||
-rw-r--r-- | test/old/testdir/test_autocmd.vim | 17 | ||||
-rw-r--r-- | test/old/testdir/test_eval_stuff.vim | 64 | ||||
-rw-r--r-- | test/old/testdir/test_writefile.vim | 13 | ||||
-rw-r--r-- | test/unit/os/fs_spec.lua | 2 |
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) |