]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 8.2.0875: getting attributes for directory entries is slow v8.2.0875
authorBram Moolenaar <Bram@vim.org>
Mon, 1 Jun 2020 14:09:41 +0000 (16:09 +0200)
committerBram Moolenaar <Bram@vim.org>
Mon, 1 Jun 2020 14:09:41 +0000 (16:09 +0200)
Problem:    Getting attributes for directory entries is slow.
Solution:   Add readdirex(). (Ken Takata, closes #5619)

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
src/version.c

index 7582a272408986d55a7a113159607f6f90bdcad4..421c091311aa21d803ebf4ae7deb6d2fd45d97b8 100644 (file)
@@ -2676,6 +2676,7 @@ rand([{expr}])                    Number  get pseudo-random number
 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": >
@@ -7871,6 +7873,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}]])
index 2642e0b39ee12c8b6be28b12c125c7c3a7f1f34b..0793a04ea9c4820f37dc1b13aa4827986bd0f004 100644 (file)
@@ -791,6 +791,7 @@ System functions and manipulation of files:
        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*
index 92cd4a44f7392018f2cd39237051c1a79dece466..45937fabf5e2627ef1dc832c216a780ce887cf1a 100644 (file)
@@ -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},
index 32af14c4d5c4b896b50a0c12b697563c38647fb0..e71b639a5a6f181050e61ec28b8c3a68e9ba31c2 100644 (file)
 #if defined(__TANDEM) || defined(__MINT__)
 # include <limits.h>           // for SSIZE_MAX
 #endif
+#if defined(UNIX) && defined(FEAT_EVAL)
+# include <pwd.h>
+# include <grp.h>
+#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;
+    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);
 
-    ga_init2(gap, (int)sizeof(char *), 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
+       failed = TRUE;
+       smsg(_(e_notopen), path);
+    }
+    else
+    {
+       while (ok)
        {
-           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;
+               failed = TRUE;
+               break;
+           }
 
-               p = utf16_to_enc(wfb.cFileName, NULL);   // p is allocated here
-               if (p == NULL)
-                   break;  // out of memory
+           if (!ignore && checkitem != NULL)
+           {
+               int r = checkitem(context, item);
 
-               ignore = p[0] == '.' && (p[1] == NUL
-                                                 || (p[1] == '.' && p[2] == NUL));
-               if (!ignore && checkitem != NULL)
+               if (r < 0)
                {
-                   int r = checkitem(context, p);
-
-                   if (r < 0)
-                   {
-                       vim_free(p);
-                       break;
-                   }
-                   if (r == 0)
-                       ignore = TRUE;
+                   FREE_ITEM(item);
+                   break;
                }
+               if (r == 0)
+                   ignore = TRUE;
+           }
 
-               if (!ignore)
+           if (!ignore)
+           {
+               if (ga_grow(gap, 1) == OK)
+                   ((void**)gap->ga_data)[gap->ga_len++] = item;
+               else
                {
-                   if (ga_grow(gap, 1) == OK)
-                       ((char_u**)gap->ga_data)[gap->ga_len++] = vim_strsave(p);
-                   else
-                   {
-                       failed = TRUE;
-                       vim_free(p);
-                       break;
-                   }
+                   failed = TRUE;
+                   FREE_ITEM(item);
+                   break;
                }
-
-               vim_free(p);
-               ok = FindNextFileW(hFind, &wfb);
            }
-           FindClose(hFind);
+           else
+               FREE_ITEM(item);
+
+           ok = FindNextFileW(hFind, &wfd);
        }
+       FindClose(hFind);
+    }
 
-       vim_free(buf);
-       vim_free(wn);
+    vim_free(buf);
+    vim_free(wn);
+# else // MSWIN
+    dirp = opendir((char *)path);
+    if (dirp == NULL)
+    {
+       failed = TRUE;
+       smsg(_(e_notopen), path);
     }
-# else
+    else
     {
-       DIR                     *dirp;
-       struct dirent   *dp;
-
-       dirp = opendir((char *)path);
-       if (dirp == NULL)
-       {
-           failed = TRUE;
-           smsg(_(e_notopen), path);
-       }
-       else
+       for (;;)
        {
-           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)
            {
-               int     ignore;
+               failed = TRUE;
+               break;
+           }
 
-               dp = readdir(dirp);
-               if (dp == NULL)
-                   break;
-               p = (char_u *)dp->d_name;
+           if (!ignore && checkitem != NULL)
+           {
+               int r = checkitem(context, item);
 
-               ignore = p[0] == '.' &&
-                       (p[1] == NUL ||
-                        (p[1] == '.' && p[2] == NUL));
-               if (!ignore && checkitem != NULL)
+               if (r < 0)
                {
-                   int r = checkitem(context, p);
-
-                   if (r < 0)
-                       break;
-                   if (r == 0)
-                       ignore = TRUE;
+                   FREE_ITEM(item);
+                   break;
                }
+               if (r == 0)
+                   ignore = TRUE;
+           }
 
-               if (!ignore)
+           if (!ignore)
+           {
+               if (ga_grow(gap, 1) == OK)
+                   ((void**)gap->ga_data)[gap->ga_len++] = item;
+               else
                {
-                   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);
        }
+
+       closedir(dirp);
     }
-# endif
+# 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)
            {
index 27a26e5edc161032d305cd0625eda844e106fc77..ad7b8b03d4ae82d9be063cf7d73e63f2f8ad89a8 100644 (file)
@@ -1028,6 +1028,25 @@ f_getcwd(typval_T *argvars, typval_T *rettv)
 #endif
 }
 
+/*
+ * 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
  */
@@ -1037,24 +1056,13 @@ f_getfperm(typval_T *argvars, typval_T *rettv)
     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;
 }
 
@@ -1105,6 +1113,33 @@ f_getftime(typval_T *argvars, typval_T *rettv)
        rettv->vval.v_number = -1;
 }
 
+/*
+ * 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
  */
@@ -1114,31 +1149,12 @@ f_getftype(typval_T *argvars, typval_T *rettv)
     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_T *rettv)
  * 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 *name)
     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 *rettv)
     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++)
        {
@@ -1420,6 +1435,70 @@ f_readdir(typval_T *argvars, typval_T *rettv)
     ga_clear_strings(&ga);
 }
 
+/*
+ * 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
  */
index c7b66ed21faa7358d5770e23fe075d92ddc16baf..2171e6b80481276050923c8bddaa394bebac04d1 100644 (file)
@@ -31,7 +31,7 @@ int buf_check_timestamp(buf_T *buf, int focus);
 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);
index bfb3ffb1024082e003a31f4cab5ac6ac4cc785ac..b0a6de9c9f51b598b2d6e2a339bb95d6f11a99c0 100644 (file)
@@ -10,9 +10,11 @@ void f_finddir(typval_T *argvars, typval_T *rettv);
 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, typval_T *rettv);
 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);
index b25d98861608cdeb4fbc87ddc6ebf401e601bc72..2253879c9ffa7cc857f03b768cb3b374c15e3bff 100644 (file)
@@ -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')
index 66102522a832a632f66f09b03faf1e0898be288b..8772e75e6a1a96a28f29cefe6a850d94634b9dab 100644 (file)
@@ -746,6 +746,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    875,
 /**/
     874,
 /**/