diff options
author | zeertzjq <zeertzjq@outlook.com> | 2024-08-06 20:49:59 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-08-06 12:49:59 +0000 |
commit | 28fbba2092adb9659253434605cb94252241f5e0 (patch) | |
tree | e7665e98c3c87f3b57ab348d143da98196efdfcc | |
parent | b5f92c4e5c7428fe1c1f91620f4b545b30b49855 (diff) | |
download | rneovim-28fbba2092adb9659253434605cb94252241f5e0.tar.gz rneovim-28fbba2092adb9659253434605cb94252241f5e0.tar.bz2 rneovim-28fbba2092adb9659253434605cb94252241f5e0.zip |
vim-patch:9.1.0465: missing filecopy() function (#29989)
Problem: missing filecopy() function
Solution: implement filecopy() Vim script function
(Shougo Matsushita)
closes: vim/vim#12346
https://github.com/vim/vim/commit/60c8743ab6c90e402e6ed49d27455ef7e5698abe
Co-authored-by: Shougo Matsushita <Shougo.Matsu@gmail.com>
-rw-r--r-- | runtime/doc/builtin.txt | 9 | ||||
-rw-r--r-- | runtime/doc/usr_41.txt | 1 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/vimfn.lua | 13 | ||||
-rw-r--r-- | src/nvim/eval.lua | 17 | ||||
-rw-r--r-- | src/nvim/eval/fs.c | 21 | ||||
-rw-r--r-- | src/nvim/fileio.c | 48 | ||||
-rw-r--r-- | test/old/testdir/test_filecopy.vim | 72 |
7 files changed, 174 insertions, 7 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index ad50d271b6..97baed6cc9 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -1610,6 +1610,15 @@ feedkeys({string} [, {mode}]) *feedkeys()* Return value is always 0. +filecopy({from}, {to}) *filecopy()* + Copy the file pointed to by the name {from} to {to}. The + result is a Number, which is |TRUE| if the file was copied + successfully, and |FALSE| when it failed. + If a file with name {to} already exists, it will fail. + Note that it does not handle directories (yet). + + This function is not available in the |sandbox|. + filereadable({file}) *filereadable()* The result is a Number, which is |TRUE| when a file with the name {file} exists, and can be read. If {file} doesn't exist, diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index ab2eecdfaf..b3911dfd09 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -855,6 +855,7 @@ System functions and manipulation of files: readblob() read a file into a Blob readdir() get a List of file names in a directory writefile() write a List of lines or Blob into a file + filecopy() copy a file {from} to {to} Date and Time: *date-functions* *time-functions* getftime() get last modification time of a file diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 4ec6925134..4f9cc881c1 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -1993,6 +1993,19 @@ function vim.fn.feedkeys(string, mode) end --- @return any function vim.fn.file_readable(file) end +--- Copy the file pointed to by the name {from} to {to}. The +--- result is a Number, which is |TRUE| if the file was copied +--- successfully, and |FALSE| when it failed. +--- If a file with name {to} already exists, it will fail. +--- Note that it does not handle directories (yet). +--- +--- This function is not available in the |sandbox|. +--- +--- @param from string +--- @param to string +--- @return 0|1 +function vim.fn.filecopy(from, to) end + --- The result is a Number, which is |TRUE| when a file with the --- name {file} exists, and can be read. If {file} doesn't exist, --- or is a directory, the result is |FALSE|. {file} is any diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 4bb82e5b3e..430ee20081 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -2521,6 +2521,23 @@ M.funcs = { params = { { 'file', 'string' } }, signature = 'file_readable({file})', }, + filecopy = { + args = 2, + base = 1, + desc = [[ + Copy the file pointed to by the name {from} to {to}. The + result is a Number, which is |TRUE| if the file was copied + successfully, and |FALSE| when it failed. + If a file with name {to} already exists, it will fail. + Note that it does not handle directories (yet). + + This function is not available in the |sandbox|. + ]], + name = 'filecopy', + params = { { 'from', 'string' }, { 'to', 'string' } }, + returns = '0|1', + signature = 'filecopy({from}, {to})', + }, filereadable = { args = 1, base = 1, diff --git a/src/nvim/eval/fs.c b/src/nvim/eval/fs.c index 9719caa52e..381fee1a3f 100644 --- a/src/nvim/eval/fs.c +++ b/src/nvim/eval/fs.c @@ -154,6 +154,27 @@ void f_exepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) rettv->vval.v_string = path; } +/// "filecopy()" function +void f_filecopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = false; + + if (check_secure() + || tv_check_for_string_arg(argvars, 0) == FAIL + || tv_check_for_string_arg(argvars, 1) == FAIL) { + return; + } + + const char *from = tv_get_string(&argvars[0]); + + FileInfo from_info; + if (os_fileinfo_link(from, &from_info) + && (S_ISREG(from_info.stat.st_mode) || S_ISLNK(from_info.stat.st_mode))) { + rettv->vval.v_number + = vim_copyfile(tv_get_string(&argvars[0]), tv_get_string(&argvars[1])) == OK; + } +} + /// "filereadable()" function void f_filereadable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 6fc1d0717a..cfce9c2954 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -2654,7 +2654,6 @@ static int rename_with_tmp(const char *const from, const char *const to) int vim_rename(const char *from, const char *to) FUNC_ATTR_NONNULL_ALL { - char *errmsg = NULL; bool use_tmp_file = false; // When the names are identical, there is nothing to do. When they refer @@ -2698,13 +2697,49 @@ int vim_rename(const char *from, const char *to) } // Rename() failed, try copying the file. + int ret = vim_copyfile(from, to); + if (ret != OK) { + return -1; + } + + if (os_fileinfo(from, &from_info)) { + os_remove(from); + } + + return 0; +} + +/// Create the new file with same permissions as the original. +/// Return -1 for failure, 0 for success. +int vim_copyfile(const char *from, const char *to) +{ + char *errmsg = NULL; + +#ifdef HAVE_READLINK + FileInfo from_info; + if (os_fileinfo_link(from, &from_info) && S_ISLNK(from_info.stat.st_mode)) { + int ret = FAIL; + + char linkbuf[MAXPATHL + 1]; + ssize_t len = readlink(from, linkbuf, MAXPATHL); + if (len > 0) { + linkbuf[len] = NUL; + + // Create link + ret = symlink(linkbuf, to); + } + + return ret == 0 ? OK : FAIL; + } +#endif + int perm = os_getperm(from); // For systems that support ACL: get the ACL from the original file. vim_acl_T acl = os_get_acl(from); int fd_in = os_open(from, O_RDONLY, 0); if (fd_in < 0) { os_free_acl(acl); - return -1; + return FAIL; } // Create the new file with same permissions as the original. @@ -2712,7 +2747,7 @@ int vim_rename(const char *from, const char *to) if (fd_out < 0) { close(fd_in); os_free_acl(acl); - return -1; + return FAIL; } // Avoid xmalloc() here as vim_rename() is called by buf_write() when nvim @@ -2722,7 +2757,7 @@ int vim_rename(const char *from, const char *to) close(fd_out); close(fd_in); os_free_acl(acl); - return -1; + return FAIL; } int n; @@ -2749,10 +2784,9 @@ int vim_rename(const char *from, const char *to) os_free_acl(acl); if (errmsg != NULL) { semsg(errmsg, to); - return -1; + return FAIL; } - os_remove(from); - return 0; + return OK; } static bool already_warned = false; diff --git a/test/old/testdir/test_filecopy.vim b/test/old/testdir/test_filecopy.vim new file mode 100644 index 0000000000..b526dce7b8 --- /dev/null +++ b/test/old/testdir/test_filecopy.vim @@ -0,0 +1,72 @@ +" Test filecopy() + +source check.vim +source shared.vim + +func Test_copy_file_to_file() + call writefile(['foo'], 'Xcopy1') + + call assert_true(filecopy('Xcopy1', 'Xcopy2')) + + call assert_equal(['foo'], readfile('Xcopy2')) + + " When the destination file already exists, it should not be overwritten. + call writefile(['foo'], 'Xcopy1') + call writefile(['bar'], 'Xcopy2', 'D') + call assert_false(filecopy('Xcopy1', 'Xcopy2')) + call assert_equal(['bar'], readfile('Xcopy2')) + + call delete('Xcopy2') + call delete('Xcopy1') +endfunc + +func Test_copy_symbolic_link() + CheckUnix + + call writefile(['text'], 'Xtestfile', 'D') + silent !ln -s -f Xtestfile Xtestlink + + call assert_true(filecopy('Xtestlink', 'Xtestlink2')) + call assert_equal('link', getftype('Xtestlink2')) + call assert_equal(['text'], readfile('Xtestlink2')) + + " When the destination file already exists, it should not be overwritten. + call assert_false(filecopy('Xtestlink', 'Xtestlink2')) + + call delete('Xtestlink2') + call delete('Xtestlink') + call delete('Xtestfile') +endfunc + +func Test_copy_dir_to_dir() + call mkdir('Xcopydir1') + call writefile(['foo'], 'Xcopydir1/Xfilecopy') + call mkdir('Xcopydir2') + + " Directory copy is not supported + call assert_false(filecopy('Xcopydir1', 'Xcopydir2')) + + call delete('Xcopydir2', 'rf') + call delete('Xcopydir1', 'rf') +endfunc + +func Test_copy_fails() + CheckUnix + + call writefile(['foo'], 'Xfilecopy', 'D') + + " Can't copy into a non-existing directory. + call assert_false(filecopy('Xfilecopy', 'Xdoesnotexist/Xfilecopy')) + + " Can't copy a non-existing file. + call assert_false(filecopy('Xdoesnotexist', 'Xfilecopy2')) + call assert_equal('', glob('Xfilecopy2')) + + " Can't copy to en empty file name. + call assert_false(filecopy('Xfilecopy', '')) + + call assert_fails('call filecopy("Xfilecopy", [])', 'E1174:') + call assert_fails('call filecopy(0z, "Xfilecopy")', 'E1174:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab |