# HG changeset patch # User Bram Moolenaar # Date 1591020903 -7200 # Node ID c2beb6baa42c847a600f3c9b45fbda16dc5288d7 # Parent c0c5e1e1bad94a15b2b874ef288e732bb4d48f7c patch 8.2.0875: getting attributes for directory entries is slow Commit: https://github.com/vim/vim/commit/6c9ba0428041d5316871245be38c13faa0107026 Author: Bram Moolenaar Date: Mon Jun 1 16:09:41 2020 +0200 patch 8.2.0875: getting attributes for directory entries is slow Problem: Getting attributes for directory entries is slow. Solution: Add readdirex(). (Ken Takata, closes https://github.com/vim/vim/issues/5619) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2676,6 +2676,7 @@ rand([{expr}]) Number get pseudo-rando 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 @@ -7840,11 +7841,11 @@ rand([{expr}]) *rand()* *random* :echo rand(seed) :echo rand(seed) % 16 " random number 0 - 15 < - *readdir()* -readdir({directory} [, {expr}]) +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: @@ -7854,6 +7855,7 @@ readdir({directory} [, {expr}]) 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": > @@ -7872,6 +7874,59 @@ readdir({directory} [, {expr}]) 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 diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -791,6 +791,7 @@ System functions and manipulation of fil 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* diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -767,6 +767,7 @@ static funcentry_T global_functions[] = {"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}, diff --git a/src/fileio.c b/src/fileio.c --- a/src/fileio.c +++ b/src/fileio.c @@ -16,6 +16,10 @@ #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,151 +4424,425 @@ write_lnum_adjust(linenr_T offset) 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()" function. + * 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, char_u *name)) + int (*checkitem)(void *context, void *item)) { - int failed = FALSE; - char_u *p; - - ga_init2(gap, (int)sizeof(char *), 20); + 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) { - 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) { - 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) { - 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) + failed = TRUE; + break; + } + + if (!ignore && checkitem != NULL) + { + int r = checkitem(context, item); + + if (r < 0) { - if (ga_grow(gap, 1) == OK) - ((char_u**)gap->ga_data)[gap->ga_len++] = vim_strsave(p); - else - { - failed = TRUE; - vim_free(p); - break; - } + FREE_ITEM(item); + break; } - - vim_free(p); - ok = FindNextFileW(hFind, &wfb); + if (r == 0) + ignore = TRUE; } - 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 (;;) + + if (!ignore) { - 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) + if (ga_grow(gap, 1) == OK) + ((void**)gap->ga_data)[gap->ga_len++] = item; + else { - 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; - } + failed = TRUE; + FREE_ITEM(item); + break; } } - - closedir(dirp); + else + FREE_ITEM(item); + + ok = FindNextFileW(hFind, &wfd); } + FindClose(hFind); } -# endif + + 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) - sort_strings((char_u **)gap->ga_data, gap->ga_len); + { +# 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,7 +4872,7 @@ delete_recursive(char_u *name) exp = vim_strsave(name); if (exp == NULL) return -1; - if (readdir_core(&ga, exp, NULL, NULL) == OK) + if (readdir_core(&ga, exp, FALSE, NULL, NULL) == OK) { for (i = 0; i < ga.ga_len; ++i) { diff --git a/src/filepath.c b/src/filepath.c --- a/src/filepath.c +++ b/src/filepath.c @@ -1029,6 +1029,25 @@ f_getcwd(typval_T *argvars, typval_T *re } /* + * 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,24 +1056,13 @@ f_getfperm(typval_T *argvars, typval_T * char_u *fname; stat_T st; char_u *perm = NULL; - char_u flags[] = "rwx"; - int i; + char_u permbuf[] = "---------"; 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]; - } - } - } + perm = vim_strsave(getfpermst(&st, permbuf)); rettv->vval.v_string = perm; } @@ -1106,6 +1114,33 @@ f_getftime(typval_T *argvars, typval_T * } /* + * 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,31 +1149,12 @@ f_getftype(typval_T *argvars, typval_T * 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); - } + type = vim_strsave(getftypest(&st)); rettv->vval.v_string = type; } @@ -1359,7 +1375,7 @@ f_pathshorten(typval_T *argvars, typval_ * Evaluate "expr" (= "context") for readdir(). */ static int -readdir_checkitem(void *context, char_u *name) +readdir_checkitem(void *context, void *item) { typval_T *expr = (typval_T *)context; typval_T save_val; @@ -1367,9 +1383,7 @@ readdir_checkitem(void *context, char_u typval_T argv[2]; int retval = 0; int error = FALSE; - - if (expr->v_type == VAR_UNKNOWN) - return 1; + char_u *name = (char_u*)item; prepare_vimvar(VV_VAL, &save_val); set_vim_var_string(VV_VAL, name, -1); @@ -1408,8 +1422,9 @@ f_readdir(typval_T *argvars, typval_T *r 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) + 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,6 +1436,70 @@ f_readdir(typval_T *argvars, typval_T *r } /* + * 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 diff --git a/src/proto/fileio.pro b/src/proto/fileio.pro --- a/src/proto/fileio.pro +++ b/src/proto/fileio.pro @@ -31,7 +31,7 @@ int buf_check_timestamp(buf_T *buf, int 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 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); diff --git a/src/proto/filepath.pro b/src/proto/filepath.pro --- a/src/proto/filepath.pro +++ b/src/proto/filepath.pro @@ -10,9 +10,11 @@ void f_finddir(typval_T *argvars, typval 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,6 +23,7 @@ void f_isdirectory(typval_T *argvars, ty 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); diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim --- a/src/testdir/test_functions.vim +++ b/src/testdir/test_functions.vim @@ -1834,7 +1834,7 @@ func Test_readdir() call assert_equal(['bar.txt', 'dir', 'foo.txt'], sort(files)) " Only results containing "f" - let files = 'Xdir'->readdir({ x -> stridx(x, 'f') !=- 1 }) + let files = 'Xdir'->readdir({ x -> stridx(x, 'f') != -1 }) call assert_equal(['foo.txt'], sort(files)) " Only .txt files @@ -1857,6 +1857,45 @@ func Test_readdir() 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() call mkdir('Xdir') call writefile([], 'Xdir/foo.txt') diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -747,6 +747,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 875, +/**/ 874, /**/ 873,