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 <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,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 -- [email protected] -- 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    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/202006011410.051EAT6R924781%40masaka.moolenaar.net.

Raspunde prin e-mail lui