To: vim_dev@googlegroups.com Subject: Patch 8.2.0875 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.0875 Problem: Getting attributes for directory entries is slow. Solution: Add readdirex(). (Ken Takata, closes #5619) Files: runtime/doc/eval.txt, runtime/doc/usr_41.txt, src/evalfunc.c, src/fileio.c, src/filepath.c src/proto/fileio.pro, src/proto/filepath.pro, src/testdir/test_functions.vim *** ../vim-8.2.0874/runtime/doc/eval.txt 2020-06-01 14:14:40.691899742 +0200 --- runtime/doc/eval.txt 2020-06-01 15:56:35.576460287 +0200 *************** *** 2670,2675 **** --- 2676,2682 ---- range({expr} [, {max} [, {stride}]]) List items from {expr} to {max} readdir({dir} [, {expr}]) List file names in {dir} selected by {expr} + readdirex({dir} [, {expr}]) List file info in {dir} selected by {expr} readfile({fname} [, {type} [, {max}]]) List get list of lines from file {fname} reg_executing() String get the executing register name *************** *** 7824,7834 **** :echo rand(seed) :echo rand(seed) % 16 " random number 0 - 15 < ! *readdir()* ! readdir({directory} [, {expr}]) Return a list with file and directory names in {directory}. You can also use |glob()| if you don't need to do complicated things, such as limiting the number of matches. When {expr} is omitted all entries are included. When {expr} is given, it is evaluated to check what to do: --- 7841,7851 ---- :echo rand(seed) :echo rand(seed) % 16 " random number 0 - 15 < ! readdir({directory} [, {expr}]) *readdir()* Return a list with file and directory names in {directory}. You can also use |glob()| if you don't need to do complicated things, such as limiting the number of matches. + The list will be sorted (case sensitive). When {expr} is omitted all entries are included. When {expr} is given, it is evaluated to check what to do: *************** *** 7838,7843 **** --- 7855,7861 ---- added to the list. If {expr} results in 1 then this entry will be added to the list. + The entries "." and ".." are always excluded. Each time {expr} is evaluated |v:val| is set to the entry name. When {expr} is a function the name is passed as the argument. For example, to get a list of files ending in ".txt": > *************** *** 7856,7861 **** --- 7874,7932 ---- Can also be used as a |method|: > GetDirName()->readdir() < + readdirex({directory} [, {expr}]) *readdirex()* + Extended version of |readdir()|. + Return a list of Dictionaries with file and directory + information in {directory}. + This is useful if you want to get the attributes of file and + directory at the same time as getting a list of a directory. + This is much faster than calling |readdir()| then calling + |getfperm()|, |getfsize()|, |getftime()| and |getftype()| for + each file and directory especially on MS-Windows. + The list will be sorted by name (case sensitive). + + The Dictionary for file and directory information has the + following items: + group Group name of the entry. (Only on Unix) + name Name of the entry. + perm Permissions of the entry. See |getfperm()|. + size Size of the entry. See |getfsize()|. + time Timestamp of the entry. See |getftime()|. + type Type of the entry. + On Unix, almost same as |getftype()| except: + Symlink to a dir "linkd" + Other symlink "link" + On MS-Windows: + Normal file "file" + Directory "dir" + Junction "junction" + Symlink to a dir "linkd" + Other symlink "link" + Other reparse point "reparse" + user User name of the entry's owner. (Only on Unix) + On Unix, if the entry is a symlink, the Dictionary includes + the information of the target (except the "type" item). + On MS-Windows, it includes the information of the symlink + itself because of performance reasons. + + When {expr} is omitted all entries are included. + When {expr} is given, it is evaluated to check what to do: + If {expr} results in -1 then no further entries will + be handled. + If {expr} results in 0 then this entry will not be + added to the list. + If {expr} results in 1 then this entry will be added + to the list. + The entries "." and ".." are always excluded. + Each time {expr} is evaluated |v:val| is set to a Dictionary + of the entry. + When {expr} is a function the entry is passed as the argument. + For example, to get a list of files ending in ".txt": > + readdirex(dirname, {e -> e.name =~ '.txt$'}) + < + Can also be used as a |method|: > + GetDirName()->readdirex() + < *readfile()* readfile({fname} [, {type} [, {max}]]) Read file {fname} and return a |List|, each line of the file *** ../vim-8.2.0874/runtime/doc/usr_41.txt 2020-05-31 15:41:53.159138610 +0200 --- runtime/doc/usr_41.txt 2020-06-01 15:56:35.576460287 +0200 *************** *** 791,796 **** --- 799,805 ---- hostname() name of the system readfile() read a file into a List of lines readdir() get a List of file names in a directory + readdirex() get a List of file information in a directory writefile() write a List of lines or Blob into a file Date and Time: *date-functions* *time-functions* *** ../vim-8.2.0874/src/evalfunc.c 2020-06-01 14:14:40.691899742 +0200 --- src/evalfunc.c 2020-06-01 15:56:35.576460287 +0200 *************** *** 767,772 **** --- 767,773 ---- {"rand", 0, 1, FEARG_1, ret_number, f_rand}, {"range", 1, 3, FEARG_1, ret_list_number, f_range}, {"readdir", 1, 2, FEARG_1, ret_list_string, f_readdir}, + {"readdirex", 1, 2, FEARG_1, ret_list_dict_any, f_readdirex}, {"readfile", 1, 3, FEARG_1, ret_any, f_readfile}, {"reg_executing", 0, 0, 0, ret_string, f_reg_executing}, {"reg_recording", 0, 0, 0, ret_string, f_reg_recording}, *** ../vim-8.2.0874/src/fileio.c 2020-05-30 20:30:42.892816571 +0200 --- src/fileio.c 2020-06-01 15:56:35.576460287 +0200 *************** *** 16,21 **** --- 16,25 ---- #if defined(__TANDEM) || defined(__MINT__) # include // for SSIZE_MAX #endif + #if defined(UNIX) && defined(FEAT_EVAL) + # include + # include + #endif // Is there any system that doesn't have access()? #define USE_MCH_ACCESS *************** *** 4420,4570 **** curbuf->b_no_eol_lnum += offset; } #if defined(TEMPDIRNAMES) || defined(FEAT_EVAL) || defined(PROTO) /* ! * Core part of "readdir()" function. * Retrieve the list of files/directories of "path" into "gap". * Return OK for success, FAIL for failure. */ int readdir_core( garray_T *gap, char_u *path, void *context, ! int (*checkitem)(void *context, char_u *name)) { ! int failed = FALSE; ! char_u *p; ! ga_init2(gap, (int)sizeof(char *), 20); # ifdef MSWIN { ! char_u *buf; ! int ok; ! HANDLE hFind = INVALID_HANDLE_VALUE; ! WIN32_FIND_DATAW wfb; ! WCHAR *wn = NULL; // UTF-16 name, NULL when not used. ! ! buf = alloc(MAXPATHL); ! if (buf == NULL) ! return FAIL; ! STRNCPY(buf, path, MAXPATHL-5); ! p = buf + STRLEN(buf); ! MB_PTR_BACK(buf, p); ! if (*p == '\\' || *p == '/') ! *p = NUL; ! STRCAT(buf, "\\*"); ! ! wn = enc_to_utf16(buf, NULL); ! if (wn != NULL) ! hFind = FindFirstFileW(wn, &wfb); ! ok = (hFind != INVALID_HANDLE_VALUE); ! if (!ok) ! { ! failed = TRUE; ! smsg(_(e_notopen), path); ! } ! else { ! while (ok) { ! int ignore; ! p = utf16_to_enc(wfb.cFileName, NULL); // p is allocated here ! if (p == NULL) ! break; // out of memory ! ignore = p[0] == '.' && (p[1] == NUL ! || (p[1] == '.' && p[2] == NUL)); ! if (!ignore && checkitem != NULL) { ! int r = checkitem(context, p); ! ! if (r < 0) ! { ! vim_free(p); ! break; ! } ! if (r == 0) ! ignore = TRUE; } ! if (!ignore) { ! if (ga_grow(gap, 1) == OK) ! ((char_u**)gap->ga_data)[gap->ga_len++] = vim_strsave(p); ! else ! { ! failed = TRUE; ! vim_free(p); ! break; ! } } - - vim_free(p); - ok = FindNextFileW(hFind, &wfb); } ! FindClose(hFind); } ! vim_free(buf); ! vim_free(wn); } ! # else { ! DIR *dirp; ! struct dirent *dp; ! ! dirp = opendir((char *)path); ! if (dirp == NULL) { ! failed = TRUE; ! smsg(_(e_notopen), path); ! } ! else ! { ! for (;;) { ! int ignore; ! dp = readdir(dirp); ! if (dp == NULL) ! break; ! p = (char_u *)dp->d_name; ! ignore = p[0] == '.' && ! (p[1] == NUL || ! (p[1] == '.' && p[2] == NUL)); ! if (!ignore && checkitem != NULL) { ! int r = checkitem(context, p); ! ! if (r < 0) ! break; ! if (r == 0) ! ignore = TRUE; } ! if (!ignore) { ! if (ga_grow(gap, 1) == OK) ! ((char_u**)gap->ga_data)[gap->ga_len++] = vim_strsave(p); ! else ! { ! failed = TRUE; ! break; ! } } } ! ! closedir(dirp); } } ! # endif if (!failed && gap->ga_len > 0) ! sort_strings((char_u **)gap->ga_data, gap->ga_len); return failed ? FAIL : OK; } --- 4424,4848 ---- curbuf->b_no_eol_lnum += offset; } + // Subfuncions for readdirex() + #ifdef FEAT_EVAL + # ifdef MSWIN + static char_u * + getfpermwfd(WIN32_FIND_DATAW *wfd, char_u *perm) + { + stat_T st; + unsigned short st_mode; + DWORD flag = wfd->dwFileAttributes; + WCHAR *wp; + + st_mode = (flag & FILE_ATTRIBUTE_DIRECTORY) + ? (_S_IFDIR | _S_IEXEC) : _S_IFREG; + st_mode |= (flag & FILE_ATTRIBUTE_READONLY) + ? _S_IREAD : (_S_IREAD | _S_IWRITE); + + wp = wcsrchr(wfd->cFileName, L'.'); + if (wp != NULL) + { + if (_wcsicmp(wp, L".exe") == 0 || + _wcsicmp(wp, L".com") == 0 || + _wcsicmp(wp, L".cmd") == 0 || + _wcsicmp(wp, L".bat") == 0) + st_mode |= _S_IEXEC; + } + + // Copy user bits to group/other. + st_mode |= (st_mode & 0700) >> 3; + st_mode |= (st_mode & 0700) >> 6; + + st.st_mode = st_mode; + return getfpermst(&st, perm); + } + + static char_u * + getftypewfd(WIN32_FIND_DATAW *wfd) + { + DWORD flag = wfd->dwFileAttributes; + DWORD tag = wfd->dwReserved0; + + if (flag & FILE_ATTRIBUTE_REPARSE_POINT) + { + if (tag == IO_REPARSE_TAG_MOUNT_POINT) + return (char_u*)"junction"; + else if (tag == IO_REPARSE_TAG_SYMLINK) + { + if (flag & FILE_ATTRIBUTE_DIRECTORY) + return (char_u*)"linkd"; + else + return (char_u*)"link"; + } + return (char_u*)"reparse"; // unknown reparse point type + } + if (flag & FILE_ATTRIBUTE_DIRECTORY) + return (char_u*)"dir"; + else + return (char_u*)"file"; + } + + static dict_T * + create_readdirex_item(WIN32_FIND_DATAW *wfd) + { + dict_T *item; + char_u *p; + varnumber_T size, time; + char_u permbuf[] = "---------"; + + item = dict_alloc(); + if (item == NULL) + return NULL; + item->dv_refcount++; + + p = utf16_to_enc(wfd->cFileName, NULL); + if (p == NULL) + goto theend; + if (dict_add_string(item, "name", p) == FAIL) + { + vim_free(p); + goto theend; + } + vim_free(p); + + size = (((varnumber_T)wfd->nFileSizeHigh) << 32) | wfd->nFileSizeLow; + if (dict_add_number(item, "size", size) == FAIL) + goto theend; + + // Convert FILETIME to unix time. + time = (((((varnumber_T)wfd->ftLastWriteTime.dwHighDateTime) << 32) | + wfd->ftLastWriteTime.dwLowDateTime) + - 116444736000000000) / 10000000; + if (dict_add_number(item, "time", time) == FAIL) + goto theend; + + if (dict_add_string(item, "type", getftypewfd(wfd)) == FAIL) + goto theend; + if (dict_add_string(item, "perm", getfpermwfd(wfd, permbuf)) == FAIL) + goto theend; + + if (dict_add_string(item, "user", (char_u*)"") == FAIL) + goto theend; + if (dict_add_string(item, "group", (char_u*)"") == FAIL) + goto theend; + + return item; + + theend: + dict_unref(item); + return NULL; + } + # else + static dict_T * + create_readdirex_item(char_u *path, char_u *name) + { + dict_T *item; + char *p; + size_t len; + stat_T st; + int ret, link = FALSE; + varnumber_T size; + char_u permbuf[] = "---------"; + char_u *q; + struct passwd *pw; + struct group *gr; + + item = dict_alloc(); + if (item == NULL) + return NULL; + item->dv_refcount++; + + len = STRLEN(path) + 1 + STRLEN(name) + 1; + p = alloc(len); + if (p == NULL) + goto theend; + vim_snprintf(p, len, "%s/%s", path, name); + ret = mch_lstat(p, &st); + if (ret >= 0 && S_ISLNK(st.st_mode)) + { + link = TRUE; + ret = mch_stat(p, &st); + } + vim_free(p); + + if (dict_add_string(item, "name", name) == FAIL) + goto theend; + + if (ret >= 0) + { + size = (varnumber_T)st.st_size; + if (S_ISDIR(st.st_mode)) + size = 0; + // non-perfect check for overflow + if ((off_T)size != (off_T)st.st_size) + size = -2; + if (dict_add_number(item, "size", size) == FAIL) + goto theend; + if (dict_add_number(item, "time", (varnumber_T)st.st_mtime) == FAIL) + goto theend; + + if (link) + { + if (S_ISDIR(st.st_mode)) + q = (char_u*)"linkd"; + else + q = (char_u*)"link"; + } + else + q = getftypest(&st); + if (dict_add_string(item, "type", q) == FAIL) + goto theend; + if (dict_add_string(item, "perm", getfpermst(&st, permbuf)) == FAIL) + goto theend; + + pw = getpwuid(st.st_uid); + if (pw == NULL) + q = (char_u*)""; + else + q = (char_u*)pw->pw_name; + if (dict_add_string(item, "user", q) == FAIL) + goto theend; + gr = getgrgid(st.st_gid); + if (gr == NULL) + q = (char_u*)""; + else + q = (char_u*)gr->gr_name; + if (dict_add_string(item, "group", q) == FAIL) + goto theend; + } + else + { + if (dict_add_number(item, "size", -1) == FAIL) + goto theend; + if (dict_add_number(item, "time", -1) == FAIL) + goto theend; + if (dict_add_string(item, "type", (char_u*)"") == FAIL) + goto theend; + if (dict_add_string(item, "perm", (char_u*)"") == FAIL) + goto theend; + if (dict_add_string(item, "user", (char_u*)"") == FAIL) + goto theend; + if (dict_add_string(item, "group", (char_u*)"") == FAIL) + goto theend; + } + return item; + + theend: + dict_unref(item); + return NULL; + } + # endif + + static int + compare_readdirex_item(const void *p1, const void *p2) + { + char_u *name1, *name2; + + name1 = dict_get_string(*(dict_T**)p1, (char_u*)"name", FALSE); + name2 = dict_get_string(*(dict_T**)p2, (char_u*)"name", FALSE); + return STRCMP(name1, name2); + } + #endif + #if defined(TEMPDIRNAMES) || defined(FEAT_EVAL) || defined(PROTO) /* ! * Core part of "readdir()" and "readdirex()" function. * Retrieve the list of files/directories of "path" into "gap". + * If "withattr" is TRUE, retrieve the names and their attributes. + * If "withattr" is FALSE, retrieve the names only. * Return OK for success, FAIL for failure. */ int readdir_core( garray_T *gap, char_u *path, + int withattr UNUSED, void *context, ! int (*checkitem)(void *context, void *item)) { ! int failed = FALSE; ! char_u *p; ! # ifdef MSWIN ! char_u *buf; ! int ok; ! HANDLE hFind = INVALID_HANDLE_VALUE; ! WIN32_FIND_DATAW wfd; ! WCHAR *wn = NULL; // UTF-16 name, NULL when not used. ! # else ! DIR *dirp; ! struct dirent *dp; ! # endif ! ga_init2(gap, (int)sizeof(void *), 20); ! ! # ifdef FEAT_EVAL ! # define FREE_ITEM(item) do { \ ! if (withattr) \ ! dict_unref((dict_T*)item); \ ! else \ ! vim_free(item); \ ! } while (0) ! # else ! # define FREE_ITEM(item) vim_free(item) ! # endif # ifdef MSWIN + buf = alloc(MAXPATHL); + if (buf == NULL) + return FAIL; + STRNCPY(buf, path, MAXPATHL-5); + p = buf + STRLEN(buf); + MB_PTR_BACK(buf, p); + if (*p == '\\' || *p == '/') + *p = NUL; + STRCAT(p, "\\*"); + + wn = enc_to_utf16(buf, NULL); + if (wn != NULL) + hFind = FindFirstFileW(wn, &wfd); + ok = (hFind != INVALID_HANDLE_VALUE); + if (!ok) { ! failed = TRUE; ! smsg(_(e_notopen), path); ! } ! else ! { ! while (ok) { ! int ignore; ! void *item; ! WCHAR *wp; ! ! wp = wfd.cFileName; ! ignore = wp[0] == L'.' && ! (wp[1] == NUL || ! (wp[1] == L'.' && wp[2] == NUL)); ! # ifdef FEAT_EVAL ! if (withattr) ! item = (void*)create_readdirex_item(&wfd); ! else ! # endif ! item = (void*)utf16_to_enc(wfd.cFileName, NULL); ! if (item == NULL) { ! failed = TRUE; ! break; ! } ! if (!ignore && checkitem != NULL) ! { ! int r = checkitem(context, item); ! if (r < 0) { ! FREE_ITEM(item); ! break; } + if (r == 0) + ignore = TRUE; + } ! if (!ignore) ! { ! if (ga_grow(gap, 1) == OK) ! ((void**)gap->ga_data)[gap->ga_len++] = item; ! else { ! failed = TRUE; ! FREE_ITEM(item); ! break; } } ! else ! FREE_ITEM(item); ! ! ok = FindNextFileW(hFind, &wfd); } + FindClose(hFind); + } ! vim_free(buf); ! vim_free(wn); ! # else // MSWIN ! dirp = opendir((char *)path); ! if (dirp == NULL) ! { ! failed = TRUE; ! smsg(_(e_notopen), path); } ! else { ! for (;;) { ! int ignore; ! void *item; ! ! dp = readdir(dirp); ! if (dp == NULL) ! break; ! p = (char_u *)dp->d_name; ! ! ignore = p[0] == '.' && ! (p[1] == NUL || ! (p[1] == '.' && p[2] == NUL)); ! # ifdef FEAT_EVAL ! if (withattr) ! item = (void*)create_readdirex_item(path, p); ! else ! # endif ! item = (void*)vim_strsave(p); ! if (item == NULL) { ! failed = TRUE; ! break; ! } ! if (!ignore && checkitem != NULL) ! { ! int r = checkitem(context, item); ! if (r < 0) { ! FREE_ITEM(item); ! break; } + if (r == 0) + ignore = TRUE; + } ! if (!ignore) ! { ! if (ga_grow(gap, 1) == OK) ! ((void**)gap->ga_data)[gap->ga_len++] = item; ! else { ! failed = TRUE; ! FREE_ITEM(item); ! break; } } ! else ! FREE_ITEM(item); } + + closedir(dirp); } ! # endif // MSWIN ! ! # undef FREE_ITEM if (!failed && gap->ga_len > 0) ! { ! # ifdef FEAT_EVAL ! if (withattr) ! qsort((void*)gap->ga_data, (size_t)gap->ga_len, sizeof(dict_T*), ! compare_readdirex_item); ! else ! # endif ! sort_strings((char_u **)gap->ga_data, gap->ga_len); ! } return failed ? FAIL : OK; } *************** *** 4594,4600 **** exp = vim_strsave(name); if (exp == NULL) return -1; ! if (readdir_core(&ga, exp, NULL, NULL) == OK) { for (i = 0; i < ga.ga_len; ++i) { --- 4872,4878 ---- exp = vim_strsave(name); if (exp == NULL) return -1; ! if (readdir_core(&ga, exp, FALSE, NULL, NULL) == OK) { for (i = 0; i < ga.ga_len; ++i) { *** ../vim-8.2.0874/src/filepath.c 2020-05-13 22:44:18.142288807 +0200 --- src/filepath.c 2020-06-01 15:56:35.576460287 +0200 *************** *** 1029,1034 **** --- 1029,1053 ---- } /* + * Convert "st" to file permission string. + */ + char_u * + getfpermst(stat_T *st, char_u *perm) + { + char_u flags[] = "rwx"; + int i; + + for (i = 0; i < 9; i++) + { + if (st->st_mode & (1 << (8 - i))) + perm[i] = flags[i % 3]; + else + perm[i] = '-'; + } + return perm; + } + + /* * "getfperm({fname})" function */ void *************** *** 1037,1060 **** char_u *fname; stat_T st; char_u *perm = NULL; ! char_u flags[] = "rwx"; ! int i; fname = tv_get_string(&argvars[0]); rettv->v_type = VAR_STRING; if (mch_stat((char *)fname, &st) >= 0) ! { ! perm = vim_strsave((char_u *)"---------"); ! if (perm != NULL) ! { ! for (i = 0; i < 9; i++) ! { ! if (st.st_mode & (1 << (8 - i))) ! perm[i] = flags[i % 3]; ! } ! } ! } rettv->vval.v_string = perm; } --- 1056,1068 ---- char_u *fname; stat_T st; char_u *perm = NULL; ! char_u permbuf[] = "---------"; fname = tv_get_string(&argvars[0]); rettv->v_type = VAR_STRING; if (mch_stat((char *)fname, &st) >= 0) ! perm = vim_strsave(getfpermst(&st, permbuf)); rettv->vval.v_string = perm; } *************** *** 1106,1111 **** --- 1114,1146 ---- } /* + * Convert "st" to file type string. + */ + char_u * + getftypest(stat_T *st) + { + char *t; + + if (S_ISREG(st->st_mode)) + t = "file"; + else if (S_ISDIR(st->st_mode)) + t = "dir"; + else if (S_ISLNK(st->st_mode)) + t = "link"; + else if (S_ISBLK(st->st_mode)) + t = "bdev"; + else if (S_ISCHR(st->st_mode)) + t = "cdev"; + else if (S_ISFIFO(st->st_mode)) + t = "fifo"; + else if (S_ISSOCK(st->st_mode)) + t = "socket"; + else + t = "other"; + return (char_u*)t; + } + + /* * "getftype({fname})" function */ void *************** *** 1114,1144 **** char_u *fname; stat_T st; char_u *type = NULL; - char *t; fname = tv_get_string(&argvars[0]); rettv->v_type = VAR_STRING; if (mch_lstat((char *)fname, &st) >= 0) ! { ! if (S_ISREG(st.st_mode)) ! t = "file"; ! else if (S_ISDIR(st.st_mode)) ! t = "dir"; ! else if (S_ISLNK(st.st_mode)) ! t = "link"; ! else if (S_ISBLK(st.st_mode)) ! t = "bdev"; ! else if (S_ISCHR(st.st_mode)) ! t = "cdev"; ! else if (S_ISFIFO(st.st_mode)) ! t = "fifo"; ! else if (S_ISSOCK(st.st_mode)) ! t = "socket"; ! else ! t = "other"; ! type = vim_strsave((char_u *)t); ! } rettv->vval.v_string = type; } --- 1149,1160 ---- char_u *fname; stat_T st; char_u *type = NULL; fname = tv_get_string(&argvars[0]); rettv->v_type = VAR_STRING; if (mch_lstat((char *)fname, &st) >= 0) ! type = vim_strsave(getftypest(&st)); rettv->vval.v_string = type; } *************** *** 1359,1365 **** * Evaluate "expr" (= "context") for readdir(). */ static int ! readdir_checkitem(void *context, char_u *name) { typval_T *expr = (typval_T *)context; typval_T save_val; --- 1375,1381 ---- * Evaluate "expr" (= "context") for readdir(). */ static int ! readdir_checkitem(void *context, void *item) { typval_T *expr = (typval_T *)context; typval_T save_val; *************** *** 1367,1375 **** typval_T argv[2]; int retval = 0; int error = FALSE; ! ! if (expr->v_type == VAR_UNKNOWN) ! return 1; prepare_vimvar(VV_VAL, &save_val); set_vim_var_string(VV_VAL, name, -1); --- 1383,1389 ---- typval_T argv[2]; int retval = 0; int error = FALSE; ! char_u *name = (char_u*)item; prepare_vimvar(VV_VAL, &save_val); set_vim_var_string(VV_VAL, name, -1); *************** *** 1408,1415 **** path = tv_get_string(&argvars[0]); expr = &argvars[1]; ! ret = readdir_core(&ga, path, (void *)expr, readdir_checkitem); ! if (ret == OK && rettv->vval.v_list != NULL && ga.ga_len > 0) { for (i = 0; i < ga.ga_len; i++) { --- 1422,1430 ---- path = tv_get_string(&argvars[0]); expr = &argvars[1]; ! ret = readdir_core(&ga, path, FALSE, (void *)expr, ! (expr->v_type == VAR_UNKNOWN) ? NULL : readdir_checkitem); ! if (ret == OK) { for (i = 0; i < ga.ga_len; i++) { *************** *** 1421,1426 **** --- 1436,1505 ---- } /* + * Evaluate "expr" (= "context") for readdirex(). + */ + static int + readdirex_checkitem(void *context, void *item) + { + typval_T *expr = (typval_T *)context; + typval_T save_val; + typval_T rettv; + typval_T argv[2]; + int retval = 0; + int error = FALSE; + dict_T *dict = (dict_T*)item; + + prepare_vimvar(VV_VAL, &save_val); + set_vim_var_dict(VV_VAL, dict); + argv[0].v_type = VAR_DICT; + argv[0].vval.v_dict = dict; + + if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) + goto theend; + + retval = tv_get_number_chk(&rettv, &error); + if (error) + retval = -1; + clear_tv(&rettv); + + theend: + set_vim_var_dict(VV_VAL, NULL); + restore_vimvar(VV_VAL, &save_val); + return retval; + } + + /* + * "readdirex()" function + */ + void + f_readdirex(typval_T *argvars, typval_T *rettv) + { + typval_T *expr; + int ret; + char_u *path; + garray_T ga; + int i; + + if (rettv_list_alloc(rettv) == FAIL) + return; + path = tv_get_string(&argvars[0]); + expr = &argvars[1]; + + ret = readdir_core(&ga, path, TRUE, (void *)expr, + (expr->v_type == VAR_UNKNOWN) ? NULL : readdirex_checkitem); + if (ret == OK) + { + for (i = 0; i < ga.ga_len; i++) + { + dict_T *dict = ((dict_T**)ga.ga_data)[i]; + list_append_dict(rettv->vval.v_list, dict); + dict_unref(dict); + } + } + ga_clear(&ga); + } + + /* * "readfile()" function */ void *** ../vim-8.2.0874/src/proto/fileio.pro 2019-12-12 12:55:21.000000000 +0100 --- src/proto/fileio.pro 2020-06-01 15:56:35.576460287 +0200 *************** *** 31,37 **** void buf_reload(buf_T *buf, int orig_mode); void buf_store_time(buf_T *buf, stat_T *st, char_u *fname); void write_lnum_adjust(linenr_T offset); ! int readdir_core(garray_T *gap, char_u *path, void *context, int (*checkitem)(void *context, char_u *name)); int delete_recursive(char_u *name); void vim_deltempdir(void); char_u *vim_tempname(int extra_char, int keep); --- 31,37 ---- void buf_reload(buf_T *buf, int orig_mode); void buf_store_time(buf_T *buf, stat_T *st, char_u *fname); void write_lnum_adjust(linenr_T offset); ! int readdir_core(garray_T *gap, char_u *path, int withattr, void *context, int (*checkitem)(void *context, void *item)); int delete_recursive(char_u *name); void vim_deltempdir(void); char_u *vim_tempname(int extra_char, int keep); *** ../vim-8.2.0874/src/proto/filepath.pro 2019-12-12 12:55:21.000000000 +0100 --- src/proto/filepath.pro 2020-06-01 15:56:35.576460287 +0200 *************** *** 10,18 **** --- 10,20 ---- void f_findfile(typval_T *argvars, typval_T *rettv); void f_fnamemodify(typval_T *argvars, typval_T *rettv); void f_getcwd(typval_T *argvars, typval_T *rettv); + char_u *getfpermst(stat_T *st, char_u *perm); void f_getfperm(typval_T *argvars, typval_T *rettv); void f_getfsize(typval_T *argvars, typval_T *rettv); void f_getftime(typval_T *argvars, typval_T *rettv); + char_u *getftypest(stat_T *st); void f_getftype(typval_T *argvars, typval_T *rettv); void f_glob(typval_T *argvars, typval_T *rettv); void f_glob2regpat(typval_T *argvars, typval_T *rettv); *************** *** 21,26 **** --- 23,29 ---- void f_mkdir(typval_T *argvars, typval_T *rettv); void f_pathshorten(typval_T *argvars, typval_T *rettv); void f_readdir(typval_T *argvars, typval_T *rettv); + void f_readdirex(typval_T *argvars, typval_T *rettv); void f_readfile(typval_T *argvars, typval_T *rettv); void f_resolve(typval_T *argvars, typval_T *rettv); void f_tempname(typval_T *argvars, typval_T *rettv); *** ../vim-8.2.0874/src/testdir/test_functions.vim 2020-05-31 22:19:52.898638027 +0200 --- src/testdir/test_functions.vim 2020-06-01 15:56:35.576460287 +0200 *************** *** 1834,1840 **** call assert_equal(['bar.txt', 'dir', 'foo.txt'], sort(files)) " Only results containing "f" ! let files = 'Xdir'->readdir({ x -> stridx(x, 'f') !=- 1 }) call assert_equal(['foo.txt'], sort(files)) " Only .txt files --- 1834,1840 ---- call assert_equal(['bar.txt', 'dir', 'foo.txt'], sort(files)) " Only results containing "f" ! let files = 'Xdir'->readdir({ x -> stridx(x, 'f') != -1 }) call assert_equal(['foo.txt'], sort(files)) " Only .txt files *************** *** 1855,1860 **** --- 1855,1899 ---- call sort(files)->assert_equal(['bar.txt', 'dir', 'foo.txt']) eval 'Xdir'->delete('rf') + endfunc + + func Test_readdirex() + call mkdir('Xdir') + call writefile([], 'Xdir/foo.txt') + call writefile([], 'Xdir/bar.txt') + call mkdir('Xdir/dir') + + " All results + let files = readdirex('Xdir')->map({-> v:val.name}) + call assert_equal(['bar.txt', 'dir', 'foo.txt'], sort(files)) + + " Only results containing "f" + let files = 'Xdir'->readdirex({ e -> stridx(e.name, 'f') != -1 }) + \ ->map({-> v:val.name}) + call assert_equal(['foo.txt'], sort(files)) + + " Only .txt files + let files = readdirex('Xdir', { e -> e.name =~ '.txt$' }) + \ ->map({-> v:val.name}) + call assert_equal(['bar.txt', 'foo.txt'], sort(files)) + + " Only .txt files with string + let files = readdirex('Xdir', 'v:val.name =~ ".txt$"') + \ ->map({-> v:val.name}) + call assert_equal(['bar.txt', 'foo.txt'], sort(files)) + + " Limit to 1 result. + let l = [] + let files = readdirex('Xdir', {e -> len(add(l, e.name)) == 2 ? -1 : 1}) + \ ->map({-> v:val.name}) + call assert_equal(1, len(files)) + + " Nested readdirex() must not crash + let files = readdirex('Xdir', 'readdirex("Xdir", "1") != []') + \ ->map({-> v:val.name}) + call sort(files)->assert_equal(['bar.txt', 'dir', 'foo.txt']) + + eval 'Xdir'->delete('rf') endfunc func Test_delete_rf() *** ../vim-8.2.0874/src/version.c 2020-06-01 15:05:16.300297224 +0200 --- src/version.c 2020-06-01 15:57:31.716243691 +0200 *************** *** 748,749 **** --- 748,751 ---- { /* Add new patch number below this line */ + /**/ + 875, /**/ -- Eagles may soar, but weasels don't get sucked into jet engines. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///