To: vim_dev@googlegroups.com Subject: Patch 9.0.0411 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 9.0.0411 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. Files: runtime/doc/builtin.txt, src/filepath.c, src/userfunc.c, src/proto/userfunc.pro, src/testdir/test_autochdir.vim, src/testdir/test_autocmd.vim, src/testdir/test_eval_stuff.vim, src/testdir/test_writefile.vim *** ../vim-9.0.0410/runtime/doc/builtin.txt 2022-09-04 15:40:31.812188115 +0100 --- runtime/doc/builtin.txt 2022-09-07 20:49:25.656972204 +0100 *************** *** 6229,6236 **** mkdir({name} [, {path} [, {prot}]]) Create directory {name}. ! If {path} is "p" then intermediate directories are created as ! necessary. Otherwise it must be "". 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 --- 6239,6264 ---- mkdir({name} [, {path} [, {prot}]]) Create directory {name}. ! If {path} contains "p" then intermediate directories are ! 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 the new directory. The default is 0o755 (rwxr-xr-x: r/w for *** ../vim-9.0.0410/src/filepath.c 2022-09-04 15:40:31.812188115 +0100 --- src/filepath.c 2022-09-07 20:51:23.008694815 +0100 *************** *** 1428,1437 **** /* * Create the directory in which "dir" is located, and higher levels when * needed. * Return OK or FAIL. */ static int ! mkdir_recurse(char_u *dir, int prot) { char_u *p; char_u *updir; --- 1428,1439 ---- /* * Create the directory in which "dir" is located, and higher levels when * needed. + * Set "created" to the full name of the first created directory. It will be + * NULL until that happens. * Return OK or FAIL. */ static int ! mkdir_recurse(char_u *dir, int prot, char_u **created) { char_u *p; char_u *updir; *************** *** 1449,1456 **** return FAIL; if (mch_isdir(updir)) r = OK; ! else if (mkdir_recurse(updir, prot) == OK) r = vim_mkdir_emsg(updir, prot); vim_free(updir); return r; } --- 1451,1462 ---- return FAIL; if (mch_isdir(updir)) r = OK; ! else if (mkdir_recurse(updir, prot, created) == OK) ! { r = vim_mkdir_emsg(updir, prot); + if (r == OK && created != NULL && *created == NULL) + *created = FullName_save(updir, FALSE); + } vim_free(updir); return r; } *************** *** 1464,1469 **** --- 1470,1478 ---- char_u *dir; char_u buf[NUMBUFLEN]; int prot = 0755; + int defer = FALSE; + int defer_recurse = FALSE; + char_u *created = NULL; rettv->vval.v_number = FAIL; if (check_restricted() || check_secure()) *************** *** 1486,1498 **** if (argvars[1].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_UNKNOWN) { prot = (int)tv_get_number_chk(&argvars[2], NULL); if (prot == -1) return; } ! if (STRCMP(tv_get_string(&argvars[1]), "p") == 0) { if (mch_isdir(dir)) { --- 1495,1515 ---- if (argvars[1].v_type != VAR_UNKNOWN) { + char_u *arg2; + if (argvars[2].v_type != VAR_UNKNOWN) { prot = (int)tv_get_number_chk(&argvars[2], NULL); if (prot == -1) return; } ! 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)) { *************** *** 1500,1509 **** rettv->vval.v_number = OK; return; } ! mkdir_recurse(dir, prot); } } rettv->vval.v_number = vim_mkdir_emsg(dir, prot); } /* --- 1517,1549 ---- rettv->vval.v_number = OK; return; } ! mkdir_recurse(dir, prot, defer || defer_recurse ? &created : NULL); } } 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,2310 **** if (fname == NULL) return; ! if (defer && !in_def_function() && get_current_funccal() == NULL) ! { ! semsg(_(e_str_not_inside_function), "defer"); return; - } // Always open the file in binary mode, library functions have a mind of // their own about CR-LF conversion. --- 2340,2347 ---- if (fname == NULL) return; ! if (defer && !can_add_defer()) return; // Always open the file in binary mode, library functions have a mind of // their own about CR-LF conversion. *************** *** 2323,2329 **** tv.v_type = VAR_STRING; tv.v_lock = 0; ! tv.vval.v_string = vim_strsave(fname); if (tv.vval.v_string == NULL || add_defer((char_u *)"delete", 1, &tv) == FAIL) { --- 2360,2366 ---- tv.v_type = VAR_STRING; tv.v_lock = 0; ! tv.vval.v_string = FullName_save(fname, FALSE); if (tv.vval.v_string == NULL || add_defer((char_u *)"delete", 1, &tv) == FAIL) { *** ../vim-9.0.0410/src/userfunc.c 2022-09-07 17:28:05.849865176 +0100 --- src/userfunc.c 2022-09-07 20:43:14.877880964 +0100 *************** *** 5650,5655 **** --- 5650,5670 ---- } /* + * 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]". * Consumes "argvars[]". * Caller must check that in_def_function() returns TRUE or current_funccal is *** ../vim-9.0.0410/src/proto/userfunc.pro 2022-09-07 17:28:05.849865176 +0100 --- src/proto/userfunc.pro 2022-09-07 20:45:11.961587496 +0100 *************** *** 60,65 **** --- 60,66 ---- void func_ref(char_u *name); void func_ptr_ref(ufunc_T *fp); void ex_return(exarg_T *eap); + int can_add_defer(void); int add_defer(char_u *name, int argcount_arg, typval_T *argvars); void invoke_all_defer(void); void ex_call(exarg_T *eap); *** ../vim-9.0.0410/src/testdir/test_autochdir.vim 2022-08-29 11:02:55.227279554 +0100 --- src/testdir/test_autochdir.vim 2022-09-07 20:59:10.559616186 +0100 *************** *** 28,36 **** func Test_set_filename_other_window() let cwd = getcwd() call test_autochdir() ! call mkdir('Xa') ! call mkdir('Xb') ! call mkdir('Xc') try args Xa/aaa.txt Xb/bbb.txt set acd --- 28,36 ---- func Test_set_filename_other_window() let cwd = getcwd() call test_autochdir() ! call mkdir('Xa', 'R') ! call mkdir('Xb', 'R') ! call mkdir('Xc', 'R') try args Xa/aaa.txt Xb/bbb.txt set acd *************** *** 45,53 **** bwipe! aaa.txt bwipe! bbb.txt bwipe! ccc.txt - call delete('Xa', 'rf') - call delete('Xb', 'rf') - call delete('Xc', 'rf') endtry endfunc --- 45,50 ---- *************** *** 56,62 **** set acd call test_autochdir() ! call mkdir('XacdDir') let winid = win_getid() new XacdDir/file call assert_match('testdir.XacdDir$', getcwd()) --- 53,59 ---- set acd call test_autochdir() ! call mkdir('XacdDir', 'R') let winid = win_getid() new XacdDir/file call assert_match('testdir.XacdDir$', getcwd()) *************** *** 68,74 **** bwipe! set noacd call chdir(cwd) - call delete('XacdDir', 'rf') endfunc func Test_verbose_pwd() --- 65,70 ---- *************** *** 78,84 **** edit global.txt call assert_match('\[global\].*testdir$', execute('verbose pwd')) ! call mkdir('Xautodir') split Xautodir/local.txt lcd Xautodir call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd')) --- 74,80 ---- edit global.txt call assert_match('\[global\].*testdir$', execute('verbose pwd')) ! call mkdir('Xautodir', 'R') split Xautodir/local.txt lcd Xautodir call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd')) *************** *** 112,118 **** bwipe! call chdir(cwd) - call delete('Xautodir', 'rf') endfunc func Test_multibyte() --- 108,113 ---- *** ../vim-9.0.0410/src/testdir/test_autocmd.vim 2022-09-04 21:28:15.192614789 +0100 --- src/testdir/test_autocmd.vim 2022-09-07 21:01:38.499280388 +0100 *************** *** 707,720 **** call assert_equal('++', g:val) " Also get BufEnter when editing a directory ! call mkdir('Xbufenterdir') split Xbufenterdir call assert_equal('+++', g:val) " On MS-Windows we can't edit the directory, make sure we wipe the right " buffer. bwipe! Xbufenterdir - call delete('Xbufenterdir', 'd') au! BufEnter " Editing a "nofile" buffer doesn't read the file but does trigger BufEnter --- 707,719 ---- call assert_equal('++', g:val) " Also get BufEnter when editing a directory ! 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! Xbufenterdir au! BufEnter " Editing a "nofile" buffer doesn't read the file but does trigger BufEnter *************** *** 1902,1912 **** new file Xbufwritecmd set buftype=acwrite ! call mkdir('Xbufwritecmd') 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! --- 1901,1910 ---- new file Xbufwritecmd set buftype=acwrite ! call mkdir('Xbufwritecmd', 'D') write " BufWriteCmd should be triggered even if a directory has the same name call assert_equal(1, g:written) unlet g:written au! BufWriteCmd bwipe! *************** *** 2710,2716 **** endfunc func Test_autocmd_in_try_block() ! call mkdir('Xintrydir') au BufEnter * let g:fname = expand('%') try edit Xintrydir/ --- 2708,2714 ---- endfunc func Test_autocmd_in_try_block() ! call mkdir('Xintrydir', 'R') au BufEnter * let g:fname = expand('%') try edit Xintrydir/ *************** *** 2719,2725 **** unlet g:fname au! BufEnter - call delete('Xintrydir', 'rf') endfunc func Test_autocmd_SafeState() --- 2717,2722 ---- *** ../vim-9.0.0410/src/testdir/test_eval_stuff.vim 2022-09-02 21:55:45.499049444 +0100 --- src/testdir/test_eval_stuff.vim 2022-09-07 21:25:00.072137930 +0100 *************** *** 44,49 **** --- 44,107 ---- 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() let array = [5, "\ ignore this *** ../vim-9.0.0410/src/testdir/test_writefile.vim 2022-09-04 21:29:41.556536007 +0100 --- src/testdir/test_writefile.vim 2022-09-07 21:10:21.038104305 +0100 *************** *** 950,955 **** --- 950,968 ---- 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 *** ../vim-9.0.0410/src/version.c 2022-09-07 20:01:13.323141838 +0100 --- src/version.c 2022-09-07 21:28:56.075611230 +0100 *************** *** 705,706 **** --- 705,708 ---- { /* Add new patch number below this line */ + /**/ + 411, /**/ -- How To Keep A Healthy Level Of Insanity: 1. At lunch time, sit in your parked car with sunglasses on and point a hair dryer at passing cars. See if they slow down. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///